source: mainline/uspace/drv/pciintel/pci.c@ 19a1800

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 19a1800 was ad6857c, checked in by Jan Vesely <jano.vesely@…>, 14 years ago

Fixed: pciintel reports correct io sizes

BIG FAT WARNING!!!:
I don't know whether the new version is correct, but it gets correct
values for my nb with intel chipset (as far as I can tell).
Someone with actual knowledge about PCI bus should definitely look at this.

  • Property mode set to 100644
File size: 15.7 KB
Line 
1/*
2 * Copyright (c) 2010 Lenka Trochtova
3 * Copyright (c) 2011 Jiri Svoboda
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/**
31 * @defgroup pciintel pci bus driver for intel method 1.
32 * @brief HelenOS root pci bus driver for intel method 1.
33 * @{
34 */
35
36/** @file
37 */
38
39#include <assert.h>
40#include <stdio.h>
41#include <errno.h>
42#include <bool.h>
43#include <fibril_synch.h>
44#include <str.h>
45#include <ctype.h>
46#include <macros.h>
47#include <str_error.h>
48
49#include <ddf/driver.h>
50#include <devman.h>
51#include <ipc/devman.h>
52#include <ipc/dev_iface.h>
53#include <ipc/irc.h>
54#include <ipc/ns.h>
55#include <ipc/services.h>
56#include <sysinfo.h>
57#include <ops/hw_res.h>
58#include <device/hw_res.h>
59#include <ddi.h>
60#include <libarch/ddi.h>
61
62#include "pci.h"
63
64#define NAME "pciintel"
65
66#define CONF_ADDR(bus, dev, fn, reg) \
67 ((1 << 31) | (bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))
68
69/** Obtain PCI function soft-state from DDF function node */
70#define PCI_FUN(fnode) ((pci_fun_t *) (fnode)->driver_data)
71
72/** Obtain PCI bus soft-state from DDF device node */
73#define PCI_BUS(dnode) ((pci_bus_t *) (dnode)->driver_data)
74
75/** Obtain PCI bus soft-state from function soft-state */
76#define PCI_BUS_FROM_FUN(fun) ((fun)->busptr)
77
78static hw_resource_list_t *pciintel_get_resources(ddf_fun_t *fnode)
79{
80 pci_fun_t *fun = PCI_FUN(fnode);
81
82 if (fun == NULL)
83 return NULL;
84 return &fun->hw_resources;
85}
86
87static bool pciintel_enable_interrupt(ddf_fun_t *fnode)
88{
89 /* This is an old ugly way, copied from ne2000 driver */
90 assert(fnode);
91 pci_fun_t *dev_data = (pci_fun_t *) fnode->driver_data;
92
93 sysarg_t apic;
94 sysarg_t i8259;
95 int irc_phone = -1;
96 int irc_service = 0;
97
98 if ((sysinfo_get_value("apic", &apic) == EOK) && (apic)) {
99 irc_service = SERVICE_APIC;
100 } else if ((sysinfo_get_value("i8259", &i8259) == EOK) && (i8259)) {
101 irc_service = SERVICE_I8259;
102 }
103
104 if (irc_service) {
105 while (irc_phone < 0)
106 irc_phone = service_connect_blocking(irc_service, 0, 0);
107 } else {
108 return false;
109 }
110
111 size_t i;
112 for (i = 0; i < dev_data->hw_resources.count; i++) {
113 if (dev_data->hw_resources.resources[i].type == INTERRUPT) {
114 int irq = dev_data->hw_resources.resources[i].res.interrupt.irq;
115 async_msg_1(irc_phone, IRC_ENABLE_INTERRUPT, irq);
116 }
117 }
118
119 async_hangup(irc_phone);
120 return true;
121}
122
123static hw_res_ops_t pciintel_hw_res_ops = {
124 &pciintel_get_resources,
125 &pciintel_enable_interrupt
126};
127
128static ddf_dev_ops_t pci_fun_ops;
129
130static int pci_add_device(ddf_dev_t *);
131
132/** PCI bus driver standard operations */
133static driver_ops_t pci_ops = {
134 .add_device = &pci_add_device
135};
136
137/** PCI bus driver structure */
138static driver_t pci_driver = {
139 .name = NAME,
140 .driver_ops = &pci_ops
141};
142
143static pci_bus_t *pci_bus_new(void)
144{
145 pci_bus_t *bus;
146
147 bus = (pci_bus_t *) calloc(1, sizeof(pci_bus_t));
148 if (bus == NULL)
149 return NULL;
150
151 fibril_mutex_initialize(&bus->conf_mutex);
152 return bus;
153}
154
155static void pci_bus_delete(pci_bus_t *bus)
156{
157 assert(bus != NULL);
158 free(bus);
159}
160
161static void pci_conf_read(pci_fun_t *fun, int reg, uint8_t *buf, size_t len)
162{
163 pci_bus_t *bus = PCI_BUS_FROM_FUN(fun);
164
165 fibril_mutex_lock(&bus->conf_mutex);
166
167 uint32_t conf_addr;
168 conf_addr = CONF_ADDR(fun->bus, fun->dev, fun->fn, reg);
169 void *addr = bus->conf_data_port + (reg & 3);
170
171 pio_write_32(bus->conf_addr_port, conf_addr);
172
173 switch (len) {
174 case 1:
175 buf[0] = pio_read_8(addr);
176 break;
177 case 2:
178 ((uint16_t *) buf)[0] = pio_read_16(addr);
179 break;
180 case 4:
181 ((uint32_t *) buf)[0] = pio_read_32(addr);
182 break;
183 }
184
185 fibril_mutex_unlock(&bus->conf_mutex);
186}
187
188static void pci_conf_write(pci_fun_t *fun, int reg, uint8_t *buf, size_t len)
189{
190 pci_bus_t *bus = PCI_BUS_FROM_FUN(fun);
191
192 fibril_mutex_lock(&bus->conf_mutex);
193
194 uint32_t conf_addr;
195 conf_addr = CONF_ADDR(fun->bus, fun->dev, fun->fn, reg);
196 void *addr = bus->conf_data_port + (reg & 3);
197
198 pio_write_32(bus->conf_addr_port, conf_addr);
199
200 switch (len) {
201 case 1:
202 pio_write_8(addr, buf[0]);
203 break;
204 case 2:
205 pio_write_16(addr, ((uint16_t *) buf)[0]);
206 break;
207 case 4:
208 pio_write_32(addr, ((uint32_t *) buf)[0]);
209 break;
210 }
211
212 fibril_mutex_unlock(&bus->conf_mutex);
213}
214
215uint8_t pci_conf_read_8(pci_fun_t *fun, int reg)
216{
217 uint8_t res;
218 pci_conf_read(fun, reg, &res, 1);
219 return res;
220}
221
222uint16_t pci_conf_read_16(pci_fun_t *fun, int reg)
223{
224 uint16_t res;
225 pci_conf_read(fun, reg, (uint8_t *) &res, 2);
226 return res;
227}
228
229uint32_t pci_conf_read_32(pci_fun_t *fun, int reg)
230{
231 uint32_t res;
232 pci_conf_read(fun, reg, (uint8_t *) &res, 4);
233 return res;
234}
235
236void pci_conf_write_8(pci_fun_t *fun, int reg, uint8_t val)
237{
238 pci_conf_write(fun, reg, (uint8_t *) &val, 1);
239}
240
241void pci_conf_write_16(pci_fun_t *fun, int reg, uint16_t val)
242{
243 pci_conf_write(fun, reg, (uint8_t *) &val, 2);
244}
245
246void pci_conf_write_32(pci_fun_t *fun, int reg, uint32_t val)
247{
248 pci_conf_write(fun, reg, (uint8_t *) &val, 4);
249}
250
251void pci_fun_create_match_ids(pci_fun_t *fun)
252{
253 char *match_id_str;
254 int rc;
255
256 asprintf(&match_id_str, "pci/ven=%04x&dev=%04x",
257 fun->vendor_id, fun->device_id);
258
259 if (match_id_str == NULL) {
260 printf(NAME ": out of memory creating match ID.\n");
261 return;
262 }
263
264 rc = ddf_fun_add_match_id(fun->fnode, match_id_str, 90);
265 if (rc != EOK) {
266 printf(NAME ": error adding match ID: %s\n",
267 str_error(rc));
268 }
269
270 /* TODO add more ids (with subsys ids, using class id etc.) */
271}
272
273void pci_add_range(pci_fun_t *fun, uint64_t range_addr, size_t range_size,
274 bool io)
275{
276 hw_resource_list_t *hw_res_list = &fun->hw_resources;
277 hw_resource_t *hw_resources = hw_res_list->resources;
278 size_t count = hw_res_list->count;
279
280 assert(hw_resources != NULL);
281 assert(count < PCI_MAX_HW_RES);
282
283 if (io) {
284 hw_resources[count].type = IO_RANGE;
285 hw_resources[count].res.io_range.address = range_addr;
286 hw_resources[count].res.io_range.size = range_size;
287 hw_resources[count].res.io_range.endianness = LITTLE_ENDIAN;
288 } else {
289 hw_resources[count].type = MEM_RANGE;
290 hw_resources[count].res.mem_range.address = range_addr;
291 hw_resources[count].res.mem_range.size = range_size;
292 hw_resources[count].res.mem_range.endianness = LITTLE_ENDIAN;
293 }
294
295 hw_res_list->count++;
296}
297
298/** Read the base address register (BAR) of the device and if it contains valid
299 * address add it to the devices hw resource list.
300 *
301 * @param fun PCI function
302 * @param addr The address of the BAR in the PCI configuration address space of
303 * the device
304 * @return The addr the address of the BAR which should be read next
305 */
306int pci_read_bar(pci_fun_t *fun, int addr)
307{
308 /* Value of the BAR */
309 uint32_t val, mask;
310 /* IO space address */
311 bool io;
312 /* 64-bit wide address */
313 bool addrw64;
314
315 /* Size of the io or memory range specified by the BAR */
316 size_t range_size;
317 /* Beginning of the io or memory range specified by the BAR */
318 uint64_t range_addr;
319
320 /* Get the value of the BAR. */
321 val = pci_conf_read_32(fun, addr);
322
323#define IO_MASK (~0x3)
324#define MEM_MASK (~0xf)
325
326 io = (bool) (val & 1);
327 if (io) {
328 addrw64 = false;
329 mask = IO_MASK;
330 } else {
331 mask = MEM_MASK;
332 switch ((val >> 1) & 3) {
333 case 0:
334 addrw64 = false;
335 break;
336 case 2:
337 addrw64 = true;
338 break;
339 default:
340 /* reserved, go to the next BAR */
341 return addr + 4;
342 }
343 }
344
345 /* Get the address mask. */
346 pci_conf_write_32(fun, addr, 0xffffffff);
347 mask &= pci_conf_read_32(fun, addr);
348
349 /* Restore the original value. */
350 pci_conf_write_32(fun, addr, val);
351 val = pci_conf_read_32(fun, addr);
352
353 range_size = pci_bar_mask_to_size(mask);
354
355 if (addrw64) {
356 range_addr = ((uint64_t)pci_conf_read_32(fun, addr + 4) << 32) |
357 (val & 0xfffffff0);
358 } else {
359 range_addr = (val & 0xfffffff0);
360 }
361
362 if (range_addr != 0) {
363 printf(NAME ": function %s : ", fun->fnode->name);
364 printf("address = %" PRIx64, range_addr);
365 printf(", size = %x\n", (unsigned int) range_size);
366 }
367
368 pci_add_range(fun, range_addr, range_size, io);
369
370 if (addrw64)
371 return addr + 8;
372
373 return addr + 4;
374}
375
376void pci_add_interrupt(pci_fun_t *fun, int irq)
377{
378 hw_resource_list_t *hw_res_list = &fun->hw_resources;
379 hw_resource_t *hw_resources = hw_res_list->resources;
380 size_t count = hw_res_list->count;
381
382 assert(NULL != hw_resources);
383 assert(count < PCI_MAX_HW_RES);
384
385 hw_resources[count].type = INTERRUPT;
386 hw_resources[count].res.interrupt.irq = irq;
387
388 hw_res_list->count++;
389
390 printf(NAME ": function %s uses irq %x.\n", fun->fnode->name, irq);
391}
392
393void pci_read_interrupt(pci_fun_t *fun)
394{
395 uint8_t irq = pci_conf_read_8(fun, PCI_BRIDGE_INT_LINE);
396 if (irq != 0xff)
397 pci_add_interrupt(fun, irq);
398}
399
400/** Enumerate (recursively) and register the devices connected to a pci bus.
401 *
402 * @param bus Host-to-PCI bridge
403 * @param bus_num Bus number
404 */
405void pci_bus_scan(pci_bus_t *bus, int bus_num)
406{
407 ddf_fun_t *fnode;
408 pci_fun_t *fun;
409
410 int child_bus = 0;
411 int dnum, fnum;
412 bool multi;
413 uint8_t header_type;
414
415 fun = pci_fun_new(bus);
416
417 for (dnum = 0; dnum < 32; dnum++) {
418 multi = true;
419 for (fnum = 0; multi && fnum < 8; fnum++) {
420 pci_fun_init(fun, bus_num, dnum, fnum);
421 fun->vendor_id = pci_conf_read_16(fun,
422 PCI_VENDOR_ID);
423 fun->device_id = pci_conf_read_16(fun,
424 PCI_DEVICE_ID);
425 if (fun->vendor_id == 0xffff) {
426 /*
427 * The device is not present, go on scanning the
428 * bus.
429 */
430 if (fnum == 0)
431 break;
432 else
433 continue;
434 }
435
436 header_type = pci_conf_read_8(fun, PCI_HEADER_TYPE);
437 if (fnum == 0) {
438 /* Is the device multifunction? */
439 multi = header_type >> 7;
440 }
441 /* Clear the multifunction bit. */
442 header_type = header_type & 0x7F;
443
444 char *fun_name = pci_fun_create_name(fun);
445 if (fun_name == NULL) {
446 printf(NAME ": out of memory.\n");
447 return;
448 }
449
450 fnode = ddf_fun_create(bus->dnode, fun_inner, fun_name);
451 if (fnode == NULL) {
452 printf(NAME ": error creating function.\n");
453 return;
454 }
455
456 free(fun_name);
457 fun->fnode = fnode;
458
459 pci_alloc_resource_list(fun);
460 pci_read_bars(fun);
461 pci_read_interrupt(fun);
462
463 fnode->ops = &pci_fun_ops;
464 fnode->driver_data = fun;
465
466 printf(NAME ": adding new function %s.\n",
467 fnode->name);
468
469 pci_fun_create_match_ids(fun);
470
471 if (ddf_fun_bind(fnode) != EOK) {
472 pci_clean_resource_list(fun);
473 clean_match_ids(&fnode->match_ids);
474 free((char *) fnode->name);
475 fnode->name = NULL;
476 continue;
477 }
478
479 if (header_type == PCI_HEADER_TYPE_BRIDGE ||
480 header_type == PCI_HEADER_TYPE_CARDBUS) {
481 child_bus = pci_conf_read_8(fun,
482 PCI_BRIDGE_SEC_BUS_NUM);
483 printf(NAME ": device is pci-to-pci bridge, "
484 "secondary bus number = %d.\n", bus_num);
485 if (child_bus > bus_num)
486 pci_bus_scan(bus, child_bus);
487 }
488
489 fun = pci_fun_new(bus);
490 }
491 }
492
493 if (fun->vendor_id == 0xffff) {
494 /* Free the auxiliary function structure. */
495 pci_fun_delete(fun);
496 }
497}
498
499static int pci_add_device(ddf_dev_t *dnode)
500{
501 pci_bus_t *bus = NULL;
502 ddf_fun_t *ctl = NULL;
503 bool got_res = false;
504 int rc;
505
506 printf(NAME ": pci_add_device\n");
507 dnode->parent_phone = -1;
508
509 bus = pci_bus_new();
510 if (bus == NULL) {
511 printf(NAME ": pci_add_device allocation failed.\n");
512 rc = ENOMEM;
513 goto fail;
514 }
515 bus->dnode = dnode;
516 dnode->driver_data = bus;
517
518 dnode->parent_phone = devman_parent_device_connect(dnode->handle,
519 IPC_FLAG_BLOCKING);
520 if (dnode->parent_phone < 0) {
521 printf(NAME ": pci_add_device failed to connect to the "
522 "parent's driver.\n");
523 rc = dnode->parent_phone;
524 goto fail;
525 }
526
527 hw_resource_list_t hw_resources;
528
529 rc = hw_res_get_resource_list(dnode->parent_phone, &hw_resources);
530 if (rc != EOK) {
531 printf(NAME ": pci_add_device failed to get hw resources for "
532 "the device.\n");
533 goto fail;
534 }
535 got_res = true;
536
537 printf(NAME ": conf_addr = %" PRIx64 ".\n",
538 hw_resources.resources[0].res.io_range.address);
539
540 assert(hw_resources.count > 0);
541 assert(hw_resources.resources[0].type == IO_RANGE);
542 assert(hw_resources.resources[0].res.io_range.size == 8);
543
544 bus->conf_io_addr =
545 (uint32_t) hw_resources.resources[0].res.io_range.address;
546
547 if (pio_enable((void *)(uintptr_t)bus->conf_io_addr, 8,
548 &bus->conf_addr_port)) {
549 printf(NAME ": failed to enable configuration ports.\n");
550 rc = EADDRNOTAVAIL;
551 goto fail;
552 }
553 bus->conf_data_port = (char *) bus->conf_addr_port + 4;
554
555 /* Make the bus device more visible. It has no use yet. */
556 printf(NAME ": adding a 'ctl' function\n");
557
558 ctl = ddf_fun_create(bus->dnode, fun_exposed, "ctl");
559 if (ctl == NULL) {
560 printf(NAME ": error creating control function.\n");
561 rc = ENOMEM;
562 goto fail;
563 }
564
565 rc = ddf_fun_bind(ctl);
566 if (rc != EOK) {
567 printf(NAME ": error binding control function.\n");
568 goto fail;
569 }
570
571 /* Enumerate functions. */
572 printf(NAME ": scanning the bus\n");
573 pci_bus_scan(bus, 0);
574
575 hw_res_clean_resource_list(&hw_resources);
576
577 return EOK;
578
579fail:
580 if (bus != NULL)
581 pci_bus_delete(bus);
582 if (dnode->parent_phone >= 0)
583 async_hangup(dnode->parent_phone);
584 if (got_res)
585 hw_res_clean_resource_list(&hw_resources);
586 if (ctl != NULL)
587 ddf_fun_destroy(ctl);
588
589 return rc;
590}
591
592static void pciintel_init(void)
593{
594 pci_fun_ops.interfaces[HW_RES_DEV_IFACE] = &pciintel_hw_res_ops;
595}
596
597pci_fun_t *pci_fun_new(pci_bus_t *bus)
598{
599 pci_fun_t *fun;
600
601 fun = (pci_fun_t *) calloc(1, sizeof(pci_fun_t));
602 if (fun == NULL)
603 return NULL;
604
605 fun->busptr = bus;
606 return fun;
607}
608
609void pci_fun_init(pci_fun_t *fun, int bus, int dev, int fn)
610{
611 fun->bus = bus;
612 fun->dev = dev;
613 fun->fn = fn;
614}
615
616void pci_fun_delete(pci_fun_t *fun)
617{
618 assert(fun != NULL);
619 hw_res_clean_resource_list(&fun->hw_resources);
620 free(fun);
621}
622
623char *pci_fun_create_name(pci_fun_t *fun)
624{
625 char *name = NULL;
626
627 asprintf(&name, "%02x:%02x.%01x", fun->bus, fun->dev,
628 fun->fn);
629 return name;
630}
631
632bool pci_alloc_resource_list(pci_fun_t *fun)
633{
634 fun->hw_resources.resources =
635 (hw_resource_t *) malloc(PCI_MAX_HW_RES * sizeof(hw_resource_t));
636 return fun->hw_resources.resources != NULL;
637}
638
639void pci_clean_resource_list(pci_fun_t *fun)
640{
641 if (fun->hw_resources.resources != NULL) {
642 free(fun->hw_resources.resources);
643 fun->hw_resources.resources = NULL;
644 }
645}
646
647/** Read the base address registers (BARs) of the function and add the addresses
648 * to its HW resource list.
649 *
650 * @param fun PCI function
651 */
652void pci_read_bars(pci_fun_t *fun)
653{
654 /*
655 * Position of the BAR in the PCI configuration address space of the
656 * device.
657 */
658 int addr = PCI_BASE_ADDR_0;
659
660 while (addr <= PCI_BASE_ADDR_5)
661 addr = pci_read_bar(fun, addr);
662}
663
664size_t pci_bar_mask_to_size(uint32_t mask)
665{
666 size_t size = mask & ~(mask - 1);
667 return size;
668}
669
670int main(int argc, char *argv[])
671{
672 printf(NAME ": HelenOS pci bus driver (intel method 1).\n");
673 pciintel_init();
674 return ddf_driver_main(&pci_driver);
675}
676
677/**
678 * @}
679 */
Note: See TracBrowser for help on using the repository browser.