source: mainline/uspace/drv/pciintel/pci.c@ af6b5157

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since af6b5157 was af6b5157, checked in by Jiri Svoboda <jiri@…>, 14 years ago

Split driver.h into ddf/driver.h and ddf/interrupt.h.

  • Property mode set to 100644
File size: 14.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 <ops/hw_res.h>
54#include <device/hw_res.h>
55#include <ddi.h>
56#include <libarch/ddi.h>
57
58#include "pci.h"
59
60#define NAME "pciintel"
61
62#define CONF_ADDR(bus, dev, fn, reg) \
63 ((1 << 31) | (bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))
64
65/** Obtain PCI function soft-state from DDF function node */
66#define PCI_FUN(fnode) ((pci_fun_t *) (fnode)->driver_data)
67
68/** Obtain PCI bus soft-state from DDF device node */
69#define PCI_BUS(dnode) ((pci_bus_t *) (dnode)->driver_data)
70
71/** Obtain PCI bus soft-state from function soft-state */
72#define PCI_BUS_FROM_FUN(fun) ((fun)->busptr)
73
74static hw_resource_list_t *pciintel_get_resources(ddf_fun_t *fnode)
75{
76 pci_fun_t *fun = PCI_FUN(fnode);
77
78 if (fun == NULL)
79 return NULL;
80 return &fun->hw_resources;
81}
82
83static bool pciintel_enable_interrupt(ddf_fun_t *fnode)
84{
85 /* TODO */
86
87 return false;
88}
89
90static hw_res_ops_t pciintel_hw_res_ops = {
91 &pciintel_get_resources,
92 &pciintel_enable_interrupt
93};
94
95static ddf_dev_ops_t pci_fun_ops;
96
97static int pci_add_device(ddf_dev_t *);
98
99/** PCI bus driver standard operations */
100static driver_ops_t pci_ops = {
101 .add_device = &pci_add_device
102};
103
104/** PCI bus driver structure */
105static driver_t pci_driver = {
106 .name = NAME,
107 .driver_ops = &pci_ops
108};
109
110static pci_bus_t *pci_bus_new(void)
111{
112 pci_bus_t *bus;
113
114 bus = (pci_bus_t *) calloc(1, sizeof(pci_bus_t));
115 if (bus == NULL)
116 return NULL;
117
118 fibril_mutex_initialize(&bus->conf_mutex);
119 return bus;
120}
121
122static void pci_bus_delete(pci_bus_t *bus)
123{
124 assert(bus != NULL);
125 free(bus);
126}
127
128static void pci_conf_read(pci_fun_t *fun, int reg, uint8_t *buf, size_t len)
129{
130 pci_bus_t *bus = PCI_BUS_FROM_FUN(fun);
131
132 fibril_mutex_lock(&bus->conf_mutex);
133
134 uint32_t conf_addr;
135 conf_addr = CONF_ADDR(fun->bus, fun->dev, fun->fn, reg);
136 void *addr = bus->conf_data_port + (reg & 3);
137
138 pio_write_32(bus->conf_addr_port, conf_addr);
139
140 switch (len) {
141 case 1:
142 buf[0] = pio_read_8(addr);
143 break;
144 case 2:
145 ((uint16_t *) buf)[0] = pio_read_16(addr);
146 break;
147 case 4:
148 ((uint32_t *) buf)[0] = pio_read_32(addr);
149 break;
150 }
151
152 fibril_mutex_unlock(&bus->conf_mutex);
153}
154
155static void pci_conf_write(pci_fun_t *fun, int reg, uint8_t *buf, size_t len)
156{
157 pci_bus_t *bus = PCI_BUS_FROM_FUN(fun);
158
159 fibril_mutex_lock(&bus->conf_mutex);
160
161 uint32_t conf_addr;
162 conf_addr = CONF_ADDR(fun->bus, fun->dev, fun->fn, reg);
163 void *addr = bus->conf_data_port + (reg & 3);
164
165 pio_write_32(bus->conf_addr_port, conf_addr);
166
167 switch (len) {
168 case 1:
169 pio_write_8(addr, buf[0]);
170 break;
171 case 2:
172 pio_write_16(addr, ((uint16_t *) buf)[0]);
173 break;
174 case 4:
175 pio_write_32(addr, ((uint32_t *) buf)[0]);
176 break;
177 }
178
179 fibril_mutex_unlock(&bus->conf_mutex);
180}
181
182uint8_t pci_conf_read_8(pci_fun_t *fun, int reg)
183{
184 uint8_t res;
185 pci_conf_read(fun, reg, &res, 1);
186 return res;
187}
188
189uint16_t pci_conf_read_16(pci_fun_t *fun, int reg)
190{
191 uint16_t res;
192 pci_conf_read(fun, reg, (uint8_t *) &res, 2);
193 return res;
194}
195
196uint32_t pci_conf_read_32(pci_fun_t *fun, int reg)
197{
198 uint32_t res;
199 pci_conf_read(fun, reg, (uint8_t *) &res, 4);
200 return res;
201}
202
203void pci_conf_write_8(pci_fun_t *fun, int reg, uint8_t val)
204{
205 pci_conf_write(fun, reg, (uint8_t *) &val, 1);
206}
207
208void pci_conf_write_16(pci_fun_t *fun, int reg, uint16_t val)
209{
210 pci_conf_write(fun, reg, (uint8_t *) &val, 2);
211}
212
213void pci_conf_write_32(pci_fun_t *fun, int reg, uint32_t val)
214{
215 pci_conf_write(fun, reg, (uint8_t *) &val, 4);
216}
217
218void pci_fun_create_match_ids(pci_fun_t *fun)
219{
220 char *match_id_str;
221 int rc;
222
223 asprintf(&match_id_str, "pci/ven=%04x&dev=%04x",
224 fun->vendor_id, fun->device_id);
225
226 if (match_id_str == NULL) {
227 printf(NAME ": out of memory creating match ID.\n");
228 return;
229 }
230
231 rc = ddf_fun_add_match_id(fun->fnode, match_id_str, 90);
232 if (rc != EOK) {
233 printf(NAME ": error adding match ID: %s\n",
234 str_error(rc));
235 }
236
237 /* TODO add more ids (with subsys ids, using class id etc.) */
238}
239
240void pci_add_range(pci_fun_t *fun, uint64_t range_addr, size_t range_size,
241 bool io)
242{
243 hw_resource_list_t *hw_res_list = &fun->hw_resources;
244 hw_resource_t *hw_resources = hw_res_list->resources;
245 size_t count = hw_res_list->count;
246
247 assert(hw_resources != NULL);
248 assert(count < PCI_MAX_HW_RES);
249
250 if (io) {
251 hw_resources[count].type = IO_RANGE;
252 hw_resources[count].res.io_range.address = range_addr;
253 hw_resources[count].res.io_range.size = range_size;
254 hw_resources[count].res.io_range.endianness = LITTLE_ENDIAN;
255 } else {
256 hw_resources[count].type = MEM_RANGE;
257 hw_resources[count].res.mem_range.address = range_addr;
258 hw_resources[count].res.mem_range.size = range_size;
259 hw_resources[count].res.mem_range.endianness = LITTLE_ENDIAN;
260 }
261
262 hw_res_list->count++;
263}
264
265/** Read the base address register (BAR) of the device and if it contains valid
266 * address add it to the devices hw resource list.
267 *
268 * @param fun PCI function
269 * @param addr The address of the BAR in the PCI configuration address space of
270 * the device
271 * @return The addr the address of the BAR which should be read next
272 */
273int pci_read_bar(pci_fun_t *fun, int addr)
274{
275 /* Value of the BAR */
276 uint32_t val, mask;
277 /* IO space address */
278 bool io;
279 /* 64-bit wide address */
280 bool addrw64;
281
282 /* Size of the io or memory range specified by the BAR */
283 size_t range_size;
284 /* Beginning of the io or memory range specified by the BAR */
285 uint64_t range_addr;
286
287 /* Get the value of the BAR. */
288 val = pci_conf_read_32(fun, addr);
289
290 io = (bool) (val & 1);
291 if (io) {
292 addrw64 = false;
293 } else {
294 switch ((val >> 1) & 3) {
295 case 0:
296 addrw64 = false;
297 break;
298 case 2:
299 addrw64 = true;
300 break;
301 default:
302 /* reserved, go to the next BAR */
303 return addr + 4;
304 }
305 }
306
307 /* Get the address mask. */
308 pci_conf_write_32(fun, addr, 0xffffffff);
309 mask = pci_conf_read_32(fun, addr);
310
311 /* Restore the original value. */
312 pci_conf_write_32(fun, addr, val);
313 val = pci_conf_read_32(fun, addr);
314
315 range_size = pci_bar_mask_to_size(mask);
316
317 if (addrw64) {
318 range_addr = ((uint64_t)pci_conf_read_32(fun, addr + 4) << 32) |
319 (val & 0xfffffff0);
320 } else {
321 range_addr = (val & 0xfffffff0);
322 }
323
324 if (range_addr != 0) {
325 printf(NAME ": function %s : ", fun->fnode->name);
326 printf("address = %" PRIx64, range_addr);
327 printf(", size = %x\n", (unsigned int) range_size);
328 }
329
330 pci_add_range(fun, range_addr, range_size, io);
331
332 if (addrw64)
333 return addr + 8;
334
335 return addr + 4;
336}
337
338void pci_add_interrupt(pci_fun_t *fun, int irq)
339{
340 hw_resource_list_t *hw_res_list = &fun->hw_resources;
341 hw_resource_t *hw_resources = hw_res_list->resources;
342 size_t count = hw_res_list->count;
343
344 assert(NULL != hw_resources);
345 assert(count < PCI_MAX_HW_RES);
346
347 hw_resources[count].type = INTERRUPT;
348 hw_resources[count].res.interrupt.irq = irq;
349
350 hw_res_list->count++;
351
352 printf(NAME ": function %s uses irq %x.\n", fun->fnode->name, irq);
353}
354
355void pci_read_interrupt(pci_fun_t *fun)
356{
357 uint8_t irq = pci_conf_read_8(fun, PCI_BRIDGE_INT_LINE);
358 if (irq != 0xff)
359 pci_add_interrupt(fun, irq);
360}
361
362/** Enumerate (recursively) and register the devices connected to a pci bus.
363 *
364 * @param bus Host-to-PCI bridge
365 * @param bus_num Bus number
366 */
367void pci_bus_scan(pci_bus_t *bus, int bus_num)
368{
369 ddf_fun_t *fnode;
370 pci_fun_t *fun;
371
372 int child_bus = 0;
373 int dnum, fnum;
374 bool multi;
375 uint8_t header_type;
376
377 fun = pci_fun_new(bus);
378
379 for (dnum = 0; dnum < 32; dnum++) {
380 multi = true;
381 for (fnum = 0; multi && fnum < 8; fnum++) {
382 pci_fun_init(fun, bus_num, dnum, fnum);
383 fun->vendor_id = pci_conf_read_16(fun,
384 PCI_VENDOR_ID);
385 fun->device_id = pci_conf_read_16(fun,
386 PCI_DEVICE_ID);
387 if (fun->vendor_id == 0xffff) {
388 /*
389 * The device is not present, go on scanning the
390 * bus.
391 */
392 if (fnum == 0)
393 break;
394 else
395 continue;
396 }
397
398 header_type = pci_conf_read_8(fun, PCI_HEADER_TYPE);
399 if (fnum == 0) {
400 /* Is the device multifunction? */
401 multi = header_type >> 7;
402 }
403 /* Clear the multifunction bit. */
404 header_type = header_type & 0x7F;
405
406 char *fun_name = pci_fun_create_name(fun);
407 if (fun_name == NULL) {
408 printf(NAME ": out of memory.\n");
409 return;
410 }
411
412 fnode = ddf_fun_create(bus->dnode, fun_inner, fun_name);
413 if (fnode == NULL) {
414 printf(NAME ": error creating function.\n");
415 return;
416 }
417
418 free(fun_name);
419 fun->fnode = fnode;
420
421 pci_alloc_resource_list(fun);
422 pci_read_bars(fun);
423 pci_read_interrupt(fun);
424
425 fnode->ops = &pci_fun_ops;
426 fnode->driver_data = fun;
427
428 printf(NAME ": adding new function %s.\n",
429 fnode->name);
430
431 pci_fun_create_match_ids(fun);
432
433 if (ddf_fun_bind(fnode) != EOK) {
434 pci_clean_resource_list(fun);
435 clean_match_ids(&fnode->match_ids);
436 free((char *) fnode->name);
437 fnode->name = NULL;
438 continue;
439 }
440
441 if (header_type == PCI_HEADER_TYPE_BRIDGE ||
442 header_type == PCI_HEADER_TYPE_CARDBUS) {
443 child_bus = pci_conf_read_8(fun,
444 PCI_BRIDGE_SEC_BUS_NUM);
445 printf(NAME ": device is pci-to-pci bridge, "
446 "secondary bus number = %d.\n", bus_num);
447 if (child_bus > bus_num)
448 pci_bus_scan(bus, child_bus);
449 }
450
451 fun = pci_fun_new(bus);
452 }
453 }
454
455 if (fun->vendor_id == 0xffff) {
456 /* Free the auxiliary function structure. */
457 pci_fun_delete(fun);
458 }
459}
460
461static int pci_add_device(ddf_dev_t *dnode)
462{
463 pci_bus_t *bus = NULL;
464 ddf_fun_t *ctl = NULL;
465 bool got_res = false;
466 int rc;
467
468 printf(NAME ": pci_add_device\n");
469 dnode->parent_phone = -1;
470
471 bus = pci_bus_new();
472 if (bus == NULL) {
473 printf(NAME ": pci_add_device allocation failed.\n");
474 rc = ENOMEM;
475 goto fail;
476 }
477 bus->dnode = dnode;
478 dnode->driver_data = bus;
479
480 dnode->parent_phone = devman_parent_device_connect(dnode->handle,
481 IPC_FLAG_BLOCKING);
482 if (dnode->parent_phone < 0) {
483 printf(NAME ": pci_add_device failed to connect to the "
484 "parent's driver.\n");
485 rc = dnode->parent_phone;
486 goto fail;
487 }
488
489 hw_resource_list_t hw_resources;
490
491 rc = hw_res_get_resource_list(dnode->parent_phone, &hw_resources);
492 if (rc != EOK) {
493 printf(NAME ": pci_add_device failed to get hw resources for "
494 "the device.\n");
495 goto fail;
496 }
497 got_res = true;
498
499 printf(NAME ": conf_addr = %" PRIx64 ".\n",
500 hw_resources.resources[0].res.io_range.address);
501
502 assert(hw_resources.count > 0);
503 assert(hw_resources.resources[0].type == IO_RANGE);
504 assert(hw_resources.resources[0].res.io_range.size == 8);
505
506 bus->conf_io_addr =
507 (uint32_t) hw_resources.resources[0].res.io_range.address;
508
509 if (pio_enable((void *)(uintptr_t)bus->conf_io_addr, 8,
510 &bus->conf_addr_port)) {
511 printf(NAME ": failed to enable configuration ports.\n");
512 rc = EADDRNOTAVAIL;
513 goto fail;
514 }
515 bus->conf_data_port = (char *) bus->conf_addr_port + 4;
516
517 /* Make the bus device more visible. It has no use yet. */
518 printf(NAME ": adding a 'ctl' function\n");
519
520 ctl = ddf_fun_create(bus->dnode, fun_exposed, "ctl");
521 if (ctl == NULL) {
522 printf(NAME ": error creating control function.\n");
523 rc = ENOMEM;
524 goto fail;
525 }
526
527 rc = ddf_fun_bind(ctl);
528 if (rc != EOK) {
529 printf(NAME ": error binding control function.\n");
530 goto fail;
531 }
532
533 /* Enumerate functions. */
534 printf(NAME ": scanning the bus\n");
535 pci_bus_scan(bus, 0);
536
537 hw_res_clean_resource_list(&hw_resources);
538
539 return EOK;
540
541fail:
542 if (bus != NULL)
543 pci_bus_delete(bus);
544 if (dnode->parent_phone >= 0)
545 async_hangup(dnode->parent_phone);
546 if (got_res)
547 hw_res_clean_resource_list(&hw_resources);
548 if (ctl != NULL)
549 ddf_fun_destroy(ctl);
550
551 return rc;
552}
553
554static void pciintel_init(void)
555{
556 pci_fun_ops.interfaces[HW_RES_DEV_IFACE] = &pciintel_hw_res_ops;
557}
558
559pci_fun_t *pci_fun_new(pci_bus_t *bus)
560{
561 pci_fun_t *fun;
562
563 fun = (pci_fun_t *) calloc(1, sizeof(pci_fun_t));
564 if (fun == NULL)
565 return NULL;
566
567 fun->busptr = bus;
568 return fun;
569}
570
571void pci_fun_init(pci_fun_t *fun, int bus, int dev, int fn)
572{
573 fun->bus = bus;
574 fun->dev = dev;
575 fun->fn = fn;
576}
577
578void pci_fun_delete(pci_fun_t *fun)
579{
580 assert(fun != NULL);
581 hw_res_clean_resource_list(&fun->hw_resources);
582 free(fun);
583}
584
585char *pci_fun_create_name(pci_fun_t *fun)
586{
587 char *name = NULL;
588
589 asprintf(&name, "%02x:%02x.%01x", fun->bus, fun->dev,
590 fun->fn);
591 return name;
592}
593
594bool pci_alloc_resource_list(pci_fun_t *fun)
595{
596 fun->hw_resources.resources =
597 (hw_resource_t *) malloc(PCI_MAX_HW_RES * sizeof(hw_resource_t));
598 return fun->hw_resources.resources != NULL;
599}
600
601void pci_clean_resource_list(pci_fun_t *fun)
602{
603 if (fun->hw_resources.resources != NULL) {
604 free(fun->hw_resources.resources);
605 fun->hw_resources.resources = NULL;
606 }
607}
608
609/** Read the base address registers (BARs) of the function and add the addresses
610 * to its HW resource list.
611 *
612 * @param fun PCI function
613 */
614void pci_read_bars(pci_fun_t *fun)
615{
616 /*
617 * Position of the BAR in the PCI configuration address space of the
618 * device.
619 */
620 int addr = PCI_BASE_ADDR_0;
621
622 while (addr <= PCI_BASE_ADDR_5)
623 addr = pci_read_bar(fun, addr);
624}
625
626size_t pci_bar_mask_to_size(uint32_t mask)
627{
628 return ((mask & 0xfffffff0) ^ 0xffffffff) + 1;
629}
630
631int main(int argc, char *argv[])
632{
633 printf(NAME ": HelenOS pci bus driver (intel method 1).\n");
634 pciintel_init();
635 return ddf_driver_main(&pci_driver);
636}
637
638/**
639 * @}
640 */
Note: See TracBrowser for help on using the repository browser.