source: mainline/uspace/drv/bus/pci/pciintel/pci.c@ 832cbe7

Last change on this file since 832cbe7 was 832cbe7, checked in by Jiri Svoboda <jiri@…>, 5 months ago

Add proper IDE PCI to ISA fallback mechanism.

To determine if legacy IDE I/O ports are free, isa-ide asks isa,
who asks pciintel. pciintel waits for bus enumeration to complete,
then waits for all functions except the one who is asking
(which is ISA bus) to stabilize. During attach pci-ide will claim
the legacy IDE ports. Thus, if at this point legacy IDE ports
are unclaimed, pciintel tells ISA they are free, which tells isa-ide,
which continues to attach. If they are not free, isa-ide will not
attach.

This works for all use cases, including system without PCI bus,
system with PCI bus, but no (or disabled) PCI IDE, system with PCI
IDE with unrecognized VID/PID (which we will handle in legacy ISA mode).

  • Property mode set to 100644
File size: 25.8 KB
Line 
1/*
2 * Copyright (c) 2025 Jiri Svoboda
3 * Copyright (c) 2010 Lenka Trochtova
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 * @addtogroup pciintel
32 * @{
33 */
34
35/** @file
36 */
37
38#include <assert.h>
39#include <byteorder.h>
40#include <stdio.h>
41#include <errno.h>
42#include <stdbool.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 <ddf/log.h>
51#include <ipc/dev_iface.h>
52#include <irc.h>
53#include <ops/hw_res.h>
54#include <device/hw_res.h>
55#include <ops/pio_window.h>
56#include <device/pio_window.h>
57#include <ddi.h>
58#include <pci_dev_iface.h>
59
60#include "ctl.h"
61#include "pci.h"
62#include "pci_regs.h"
63
64#define NAME "pciintel"
65
66#define CONF_ADDR_ENABLE (((unsigned)1) << 31)
67#define CONF_ADDR(bus, dev, fn, reg) \
68 ((bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))
69
70/** Obtain PCI function soft-state from DDF function node */
71static pci_fun_t *pci_fun(ddf_fun_t *fnode)
72{
73 return ddf_fun_data_get(fnode);
74}
75
76/** Obtain PCI bus soft-state from DDF device node */
77pci_bus_t *pci_bus(ddf_dev_t *dnode)
78{
79 return ddf_dev_data_get(dnode);
80}
81
82/** Obtain PCI bus soft-state from function soft-state */
83static pci_bus_t *pci_bus_from_fun(pci_fun_t *fun)
84{
85 return fun->busptr;
86}
87
88/** Max is 47, align to something nice. */
89#define ID_MAX_STR_LEN 50
90
91static hw_resource_list_t *pciintel_get_resources(ddf_fun_t *fnode)
92{
93 pci_fun_t *fun = pci_fun(fnode);
94
95 if (fun == NULL)
96 return NULL;
97 return &fun->hw_resources;
98}
99
100static bool pciintel_fun_owns_interrupt(pci_fun_t *fun, int irq)
101{
102 size_t i;
103 hw_resource_list_t *res = &fun->hw_resources;
104
105 for (i = 0; i < res->count; i++) {
106 if (res->resources[i].type == INTERRUPT &&
107 res->resources[i].res.interrupt.irq == irq) {
108 return true;
109 }
110 }
111
112 return false;
113}
114
115static errno_t pciintel_enable_interrupt(ddf_fun_t *fnode, int irq)
116{
117 pci_fun_t *fun = pci_fun(fnode);
118
119 if (!pciintel_fun_owns_interrupt(fun, irq))
120 return EINVAL;
121
122 return irc_enable_interrupt(irq);
123}
124
125static errno_t pciintel_disable_interrupt(ddf_fun_t *fnode, int irq)
126{
127 pci_fun_t *fun = pci_fun(fnode);
128
129 if (!pciintel_fun_owns_interrupt(fun, irq))
130 return EINVAL;
131
132 return irc_disable_interrupt(irq);
133}
134
135static errno_t pciintel_clear_interrupt(ddf_fun_t *fnode, int irq)
136{
137 pci_fun_t *fun = pci_fun(fnode);
138
139 if (!pciintel_fun_owns_interrupt(fun, irq))
140 return EINVAL;
141
142 return irc_clear_interrupt(irq);
143}
144
145/** Handle legacy IO availability query from function.
146 *
147 * @param fnode Function performing the query
148 * @param rclaims Place to store the legacy IO claims bitmask
149 * @return EOK on success or an error code
150 */
151static errno_t pciintel_query_legacy_io(ddf_fun_t *fnode,
152 hw_res_claims_t *rclaims)
153{
154 pci_fun_t *fun = pci_fun(fnode);
155 pci_bus_t *bus = fun->busptr;
156 pci_fun_t *f;
157 hw_res_claims_t claims;
158
159 /*
160 * We need to wait for enumeration to complete so that we give
161 * the PCI IDE driver a chance to claim the legacy ISA IDE
162 * ranges.
163 */
164 ddf_msg(LVL_DEBUG, "pciintel_query_legacy_io");
165
166 fun->querying = true;
167
168 fibril_mutex_lock(&bus->enum_done_lock);
169 while (!bus->enum_done)
170 fibril_condvar_wait(&bus->enum_done_cv, &bus->enum_done_lock);
171 fibril_mutex_unlock(&bus->enum_done_lock);
172
173 ddf_msg(LVL_DEBUG, "Wait for PCI devices to stabilize");
174
175 f = pci_fun_first(bus);
176 while (f != NULL) {
177 if (!f->querying)
178 ddf_fun_wait_stable(f->fnode);
179 f = pci_fun_next(f);
180 }
181
182 /* Devices are stable. Now we can determine if ISA IDE was claimed. */
183
184 claims = 0;
185
186 ddf_msg(LVL_DEBUG, "PCI devices stabilized, leg_ide_claimed=%d\n",
187 (int)bus->leg_ide_claimed);
188 if (bus->leg_ide_claimed != false) {
189 ddf_msg(LVL_NOTE, "Legacy IDE I/O ports claimed by PCI driver.");
190 claims |= hwc_isa_ide;
191 }
192
193 fun->querying = false;
194 *rclaims = claims;
195 return EOK;
196}
197
198/** Handle legacy IO claim from function.
199 *
200 * @param fnode Function claiming the legacy I/O ports
201 * @param claims Bitmask of claimed I/O
202 * @return EOK on success or an error code
203 */
204static errno_t pciintel_claim_legacy_io(ddf_fun_t *fnode,
205 hw_res_claims_t claims)
206{
207 pci_fun_t *fun = pci_fun(fnode);
208 pci_bus_t *bus = fun->busptr;
209
210 ddf_msg(LVL_DEBUG, "pciintel_claim_legacy_io() claims=%x", claims);
211 if ((claims & hwc_isa_ide) != 0)
212 bus->leg_ide_claimed = true;
213
214 return EOK;
215}
216
217static pio_window_t *pciintel_get_pio_window(ddf_fun_t *fnode)
218{
219 pci_fun_t *fun = pci_fun(fnode);
220
221 if (fun == NULL)
222 return NULL;
223 return &fun->pio_window;
224}
225
226static errno_t config_space_write_32(ddf_fun_t *fun, uint32_t address,
227 uint32_t data)
228{
229 if (address > 252)
230 return EINVAL;
231 pci_conf_write_32(pci_fun(fun), address, data);
232 return EOK;
233}
234
235static errno_t config_space_write_16(
236 ddf_fun_t *fun, uint32_t address, uint16_t data)
237{
238 if (address > 254)
239 return EINVAL;
240 pci_conf_write_16(pci_fun(fun), address, data);
241 return EOK;
242}
243
244static errno_t config_space_write_8(
245 ddf_fun_t *fun, uint32_t address, uint8_t data)
246{
247 if (address > 255)
248 return EINVAL;
249 pci_conf_write_8(pci_fun(fun), address, data);
250 return EOK;
251}
252
253static errno_t config_space_read_32(
254 ddf_fun_t *fun, uint32_t address, uint32_t *data)
255{
256 if (address > 252)
257 return EINVAL;
258 *data = pci_conf_read_32(pci_fun(fun), address);
259 return EOK;
260}
261
262static errno_t config_space_read_16(
263 ddf_fun_t *fun, uint32_t address, uint16_t *data)
264{
265 if (address > 254)
266 return EINVAL;
267 *data = pci_conf_read_16(pci_fun(fun), address);
268 return EOK;
269}
270
271static errno_t config_space_read_8(
272 ddf_fun_t *fun, uint32_t address, uint8_t *data)
273{
274 if (address > 255)
275 return EINVAL;
276 *data = pci_conf_read_8(pci_fun(fun), address);
277 return EOK;
278}
279
280static hw_res_ops_t pciintel_hw_res_ops = {
281 .get_resource_list = &pciintel_get_resources,
282 .enable_interrupt = &pciintel_enable_interrupt,
283 .disable_interrupt = &pciintel_disable_interrupt,
284 .clear_interrupt = &pciintel_clear_interrupt,
285 .query_legacy_io = &pciintel_query_legacy_io,
286 .claim_legacy_io = &pciintel_claim_legacy_io
287};
288
289static pio_window_ops_t pciintel_pio_window_ops = {
290 .get_pio_window = &pciintel_get_pio_window
291};
292
293static pci_dev_iface_t pci_dev_ops = {
294 .config_space_read_8 = &config_space_read_8,
295 .config_space_read_16 = &config_space_read_16,
296 .config_space_read_32 = &config_space_read_32,
297 .config_space_write_8 = &config_space_write_8,
298 .config_space_write_16 = &config_space_write_16,
299 .config_space_write_32 = &config_space_write_32
300};
301
302static ddf_dev_ops_t pci_fun_ops = {
303 .interfaces[HW_RES_DEV_IFACE] = &pciintel_hw_res_ops,
304 .interfaces[PIO_WINDOW_DEV_IFACE] = &pciintel_pio_window_ops,
305 .interfaces[PCI_DEV_IFACE] = &pci_dev_ops
306};
307
308static errno_t pci_dev_add(ddf_dev_t *);
309static errno_t pci_fun_online(ddf_fun_t *);
310static errno_t pci_fun_offline(ddf_fun_t *);
311
312/** PCI bus driver standard operations */
313static driver_ops_t pci_ops = {
314 .dev_add = &pci_dev_add,
315 .fun_online = &pci_fun_online,
316 .fun_offline = &pci_fun_offline,
317};
318
319/** PCI bus driver structure */
320static driver_t pci_driver = {
321 .name = NAME,
322 .driver_ops = &pci_ops
323};
324
325static void pci_conf_read(pci_fun_t *fun, int reg, uint8_t *buf, size_t len)
326{
327 const uint32_t conf_addr = CONF_ADDR(fun->bus, fun->dev, fun->fn, reg);
328 pci_bus_t *bus = pci_bus_from_fun(fun);
329 uint32_t val;
330
331 fibril_mutex_lock(&bus->conf_mutex);
332
333 if (bus->conf_addr_reg) {
334 pio_write_32(bus->conf_addr_reg,
335 host2uint32_t_le(CONF_ADDR_ENABLE | conf_addr));
336 /*
337 * Always read full 32-bits from the PCI conf_data_port
338 * register and get the desired portion of it afterwards. Some
339 * architectures do not support shorter PIO reads offset from
340 * this register.
341 */
342 val = uint32_t_le2host(pio_read_32(bus->conf_data_reg));
343 } else {
344 val = uint32_t_le2host(pio_read_32(
345 &bus->conf_space[conf_addr / sizeof(ioport32_t)]));
346 }
347
348 switch (len) {
349 case 1:
350 *buf = (uint8_t) (val >> ((reg & 3) * 8));
351 break;
352 case 2:
353 *((uint16_t *) buf) = (uint16_t) (val >> ((reg & 3)) * 8);
354 break;
355 case 4:
356 *((uint32_t *) buf) = (uint32_t) val;
357 break;
358 }
359
360 fibril_mutex_unlock(&bus->conf_mutex);
361}
362
363static void pci_conf_write(pci_fun_t *fun, int reg, uint8_t *buf, size_t len)
364{
365 const uint32_t conf_addr = CONF_ADDR(fun->bus, fun->dev, fun->fn, reg);
366 pci_bus_t *bus = pci_bus_from_fun(fun);
367 uint32_t val = 0;
368
369 fibril_mutex_lock(&bus->conf_mutex);
370
371 /*
372 * Prepare to write full 32-bits to the PCI conf_data_port register.
373 * Some architectures do not support shorter PIO writes offset from this
374 * register.
375 */
376
377 if (len < 4) {
378 /*
379 * We have fewer than full 32-bits, so we need to read the
380 * missing bits first.
381 */
382 if (bus->conf_addr_reg) {
383 pio_write_32(bus->conf_addr_reg,
384 host2uint32_t_le(CONF_ADDR_ENABLE | conf_addr));
385 val = uint32_t_le2host(pio_read_32(bus->conf_data_reg));
386 } else {
387 val = uint32_t_le2host(pio_read_32(
388 &bus->conf_space[conf_addr / sizeof(ioport32_t)]));
389 }
390 }
391
392 switch (len) {
393 case 1:
394 val &= ~(0xffU << ((reg & 3) * 8));
395 val |= *buf << ((reg & 3) * 8);
396 break;
397 case 2:
398 val &= ~(0xffffU << ((reg & 3) * 8));
399 val |= *((uint16_t *) buf) << ((reg & 3) * 8);
400 break;
401 case 4:
402 val = *((uint32_t *) buf);
403 break;
404 }
405
406 if (bus->conf_addr_reg) {
407 pio_write_32(bus->conf_addr_reg,
408 host2uint32_t_le(CONF_ADDR_ENABLE | conf_addr));
409 pio_write_32(bus->conf_data_reg, host2uint32_t_le(val));
410 } else {
411 pio_write_32(&bus->conf_space[conf_addr / sizeof(ioport32_t)],
412 host2uint32_t_le(val));
413 }
414
415 fibril_mutex_unlock(&bus->conf_mutex);
416}
417
418uint8_t pci_conf_read_8(pci_fun_t *fun, int reg)
419{
420 uint8_t res;
421 pci_conf_read(fun, reg, &res, 1);
422 return res;
423}
424
425uint16_t pci_conf_read_16(pci_fun_t *fun, int reg)
426{
427 uint16_t res;
428 pci_conf_read(fun, reg, (uint8_t *) &res, 2);
429 return res;
430}
431
432uint32_t pci_conf_read_32(pci_fun_t *fun, int reg)
433{
434 uint32_t res;
435 pci_conf_read(fun, reg, (uint8_t *) &res, 4);
436 return res;
437}
438
439void pci_conf_write_8(pci_fun_t *fun, int reg, uint8_t val)
440{
441 pci_conf_write(fun, reg, (uint8_t *) &val, 1);
442}
443
444void pci_conf_write_16(pci_fun_t *fun, int reg, uint16_t val)
445{
446 pci_conf_write(fun, reg, (uint8_t *) &val, 2);
447}
448
449void pci_conf_write_32(pci_fun_t *fun, int reg, uint32_t val)
450{
451 pci_conf_write(fun, reg, (uint8_t *) &val, 4);
452}
453
454void pci_fun_create_match_ids(pci_fun_t *fun)
455{
456 errno_t rc;
457 int ret;
458 char match_id_str[ID_MAX_STR_LEN];
459
460 /* Vendor ID & Device ID, length(incl \0) 22 */
461 ret = snprintf(match_id_str, ID_MAX_STR_LEN, "pci/ven=%04"
462 PRIx16 "&dev=%04" PRIx16, fun->vendor_id, fun->device_id);
463 if (ret < 0) {
464 ddf_msg(LVL_ERROR, "Failed creating match ID str");
465 }
466
467 rc = ddf_fun_add_match_id(fun->fnode, match_id_str, 90);
468 if (rc != EOK) {
469 ddf_msg(LVL_ERROR, "Failed adding match ID: %s", str_error(rc));
470 }
471
472 /* Class, subclass, prog IF, revision, length(incl \0) 47 */
473 ret = snprintf(match_id_str, ID_MAX_STR_LEN,
474 "pci/class=%02x&subclass=%02x&progif=%02x&revision=%02x",
475 fun->class_code, fun->subclass_code, fun->prog_if, fun->revision);
476 if (ret < 0) {
477 ddf_msg(LVL_ERROR, "Failed creating match ID str");
478 }
479
480 rc = ddf_fun_add_match_id(fun->fnode, match_id_str, 70);
481 if (rc != EOK) {
482 ddf_msg(LVL_ERROR, "Failed adding match ID: %s", str_error(rc));
483 }
484
485 /* Class, subclass, prog IF, length(incl \0) 35 */
486 ret = snprintf(match_id_str, ID_MAX_STR_LEN,
487 "pci/class=%02x&subclass=%02x&progif=%02x",
488 fun->class_code, fun->subclass_code, fun->prog_if);
489 if (ret < 0) {
490 ddf_msg(LVL_ERROR, "Failed creating match ID str");
491 }
492
493 rc = ddf_fun_add_match_id(fun->fnode, match_id_str, 60);
494 if (rc != EOK) {
495 ddf_msg(LVL_ERROR, "Failed adding match ID: %s", str_error(rc));
496 }
497
498 /* Class, subclass, length(incl \0) 25 */
499 ret = snprintf(match_id_str, ID_MAX_STR_LEN,
500 "pci/class=%02x&subclass=%02x",
501 fun->class_code, fun->subclass_code);
502 if (ret < 0) {
503 ddf_msg(LVL_ERROR, "Failed creating match ID str");
504 }
505
506 rc = ddf_fun_add_match_id(fun->fnode, match_id_str, 50);
507 if (rc != EOK) {
508 ddf_msg(LVL_ERROR, "Failed adding match ID: %s", str_error(rc));
509 }
510
511 /* Class, length(incl \0) 13 */
512 ret = snprintf(match_id_str, ID_MAX_STR_LEN, "pci/class=%02x",
513 fun->class_code);
514 if (ret < 0) {
515 ddf_msg(LVL_ERROR, "Failed creating match ID str");
516 }
517
518 rc = ddf_fun_add_match_id(fun->fnode, match_id_str, 40);
519 if (rc != EOK) {
520 ddf_msg(LVL_ERROR, "Failed adding match ID: %s", str_error(rc));
521 }
522
523 /* TODO add subsys ids, but those exist only in header type 0 */
524}
525
526/** Get first PCI function.
527 *
528 * @param bus PCI bus
529 * @return First PCI function on @a bus or @c NULL if there is none
530 */
531pci_fun_t *pci_fun_first(pci_bus_t *bus)
532{
533 link_t *link;
534
535 link = list_first(&bus->funs);
536 if (link == NULL)
537 return NULL;
538
539 return list_get_instance(link, pci_fun_t, lfuns);
540}
541
542/** Get next PCI function.
543 *
544 * @param cur Current function
545 * @return Next PCI function on the same bus or @c NULL if there is none
546 */
547pci_fun_t *pci_fun_next(pci_fun_t *cur)
548{
549 link_t *link;
550
551 link = list_next(&cur->lfuns, &cur->busptr->funs);
552 if (link == NULL)
553 return NULL;
554
555 return list_get_instance(link, pci_fun_t, lfuns);
556}
557
558void pci_add_range(pci_fun_t *fun, uint64_t range_addr, size_t range_size,
559 bool io)
560{
561 hw_resource_list_t *hw_res_list = &fun->hw_resources;
562 hw_resource_t *hw_resources = hw_res_list->resources;
563 size_t count = hw_res_list->count;
564
565 assert(hw_resources != NULL);
566 assert(count < PCI_MAX_HW_RES);
567
568 if (io) {
569 hw_resources[count].type = IO_RANGE;
570 hw_resources[count].res.io_range.address = range_addr;
571 hw_resources[count].res.io_range.size = range_size;
572 hw_resources[count].res.io_range.relative = true;
573 hw_resources[count].res.io_range.endianness = LITTLE_ENDIAN;
574 } else {
575 hw_resources[count].type = MEM_RANGE;
576 hw_resources[count].res.mem_range.address = range_addr;
577 hw_resources[count].res.mem_range.size = range_size;
578 hw_resources[count].res.mem_range.relative = false;
579 hw_resources[count].res.mem_range.endianness = LITTLE_ENDIAN;
580 }
581
582 hw_res_list->count++;
583}
584
585/** Read the base address register (BAR) of the device and if it contains valid
586 * address add it to the devices hw resource list.
587 *
588 * @param fun PCI function
589 * @param addr The address of the BAR in the PCI configuration address space of
590 * the device
591 * @return The addr the address of the BAR which should be read next
592 */
593int pci_read_bar(pci_fun_t *fun, int addr)
594{
595 /* Value of the BAR */
596 uint32_t val;
597 uint32_t bar;
598 uint32_t mask;
599
600 /* IO space address */
601 bool io;
602 /* 64-bit wide address */
603 bool addrw64;
604
605 /* Size of the io or memory range specified by the BAR */
606 size_t range_size;
607 /* Beginning of the io or memory range specified by the BAR */
608 uint64_t range_addr;
609
610 /* Get the value of the BAR. */
611 val = pci_conf_read_32(fun, addr);
612
613#define IO_MASK (~0x3)
614#define MEM_MASK (~0xf)
615
616 io = (val & 1) != 0;
617 if (io) {
618 addrw64 = false;
619 mask = IO_MASK;
620 } else {
621 mask = MEM_MASK;
622 switch ((val >> 1) & 3) {
623 case 0:
624 addrw64 = false;
625 break;
626 case 2:
627 addrw64 = true;
628 break;
629 default:
630 /* reserved, go to the next BAR */
631 return addr + 4;
632 }
633 }
634
635 /* Get the address mask. */
636 pci_conf_write_32(fun, addr, 0xffffffff);
637 bar = pci_conf_read_32(fun, addr);
638
639 /*
640 * Unimplemented BARs read back as all 0's.
641 */
642 if (!bar)
643 return addr + (addrw64 ? 8 : 4);
644
645 mask &= bar;
646
647 /* Restore the original value. */
648 pci_conf_write_32(fun, addr, val);
649 val = pci_conf_read_32(fun, addr);
650
651 range_size = pci_bar_mask_to_size(mask);
652
653 if (addrw64) {
654 range_addr = ((uint64_t)pci_conf_read_32(fun, addr + 4) << 32) |
655 (val & 0xfffffff0);
656 } else {
657 range_addr = (val & 0xfffffff0);
658 }
659
660 if (range_addr != 0) {
661 ddf_msg(LVL_DEBUG, "Function %s : address = %" PRIx64
662 ", size = %x", ddf_fun_get_name(fun->fnode), range_addr,
663 (unsigned int) range_size);
664 }
665
666 pci_add_range(fun, range_addr, range_size, io);
667
668 if (addrw64)
669 return addr + 8;
670
671 return addr + 4;
672}
673
674void pci_add_interrupt(pci_fun_t *fun, int irq)
675{
676 hw_resource_list_t *hw_res_list = &fun->hw_resources;
677 hw_resource_t *hw_resources = hw_res_list->resources;
678 size_t count = hw_res_list->count;
679
680 assert(NULL != hw_resources);
681 assert(count < PCI_MAX_HW_RES);
682
683 hw_resources[count].type = INTERRUPT;
684 hw_resources[count].res.interrupt.irq = irq;
685
686 hw_res_list->count++;
687
688 ddf_msg(LVL_NOTE, "Function %s uses irq %x.", ddf_fun_get_name(fun->fnode), irq);
689}
690
691void pci_read_interrupt(pci_fun_t *fun)
692{
693 uint8_t irq = pci_conf_read_8(fun, PCI_BRIDGE_INT_LINE);
694 uint8_t pin = pci_conf_read_8(fun, PCI_BRIDGE_INT_PIN);
695
696 if (pin != 0 && irq != 0xff)
697 pci_add_interrupt(fun, irq);
698}
699
700/** Enumerate (recursively) and register the devices connected to a pci bus.
701 *
702 * @param bus Host-to-PCI bridge
703 * @param bus_num Bus number
704 *
705 * @return EOK on success, ENOENT if no PCI devices found, ENOMEM if out of
706 * memory, EIO on other I/O error
707 */
708errno_t pci_bus_scan(pci_bus_t *bus, int bus_num)
709{
710 pci_fun_t *fun;
711 errno_t rc;
712
713 int child_bus = 0;
714 int dnum, fnum;
715 bool multi;
716 uint8_t header_type;
717 bool device_found;
718
719 device_found = false;
720
721 for (dnum = 0; dnum < 32; dnum++) {
722 multi = true;
723 for (fnum = 0; multi && fnum < 8; fnum++) {
724 fun = pci_fun_new(bus);
725
726 pci_fun_init(fun, bus_num, dnum, fnum);
727 if (fun->vendor_id == 0xffff) {
728 pci_fun_delete(fun);
729 /*
730 * The device is not present, go on scanning the
731 * bus.
732 */
733 if (fnum == 0)
734 break;
735 else
736 continue;
737 }
738
739 device_found = true;
740
741 header_type = pci_conf_read_8(fun, PCI_HEADER_TYPE);
742 if (fnum == 0) {
743 /* Is the device multifunction? */
744 multi = header_type >> 7;
745 }
746 /* Clear the multifunction bit. */
747 header_type = header_type & 0x7F;
748
749 char *fun_name = pci_fun_create_name(fun);
750 if (fun_name == NULL) {
751 ddf_msg(LVL_ERROR, "Out of memory.");
752 pci_fun_delete(fun);
753 return ENOMEM;
754 }
755
756 rc = ddf_fun_set_name(fun->fnode, fun_name);
757 free(fun_name);
758 if (rc != EOK) {
759 ddf_msg(LVL_ERROR, "Failed setting function name.");
760 pci_fun_delete(fun);
761 return EIO;
762 }
763
764 pci_alloc_resource_list(fun);
765 pci_read_bars(fun);
766 pci_read_interrupt(fun);
767
768 /* Propagate the PIO window to the function. */
769 fun->pio_window = bus->pio_win;
770
771 ddf_fun_set_ops(fun->fnode, &pci_fun_ops);
772
773 ddf_msg(LVL_DEBUG, "Adding new function %s.",
774 ddf_fun_get_name(fun->fnode));
775
776 pci_fun_create_match_ids(fun);
777
778 if (header_type == PCI_HEADER_TYPE_BRIDGE ||
779 header_type == PCI_HEADER_TYPE_CARDBUS) {
780 child_bus = pci_conf_read_8(fun,
781 PCI_BRIDGE_SEC_BUS_NUM);
782 ddf_msg(LVL_DEBUG, "Device is pci-to-pci "
783 "bridge, secondary bus number = %d.",
784 bus_num);
785 if (child_bus > bus_num) {
786 rc = pci_bus_scan(bus, child_bus);
787 if (rc != EOK && rc != ENOENT) {
788 pci_fun_delete(fun);
789 return rc;
790 }
791 }
792 }
793
794 if (ddf_fun_bind(fun->fnode) != EOK) {
795 pci_clean_resource_list(fun);
796 pci_fun_delete(fun);
797 continue;
798 }
799
800 list_append(&fun->lfuns, &bus->funs);
801 }
802 }
803
804 /* Fail bus scan if no devices are found. */
805 if (!device_found)
806 return ENOENT;
807
808 return EOK;
809}
810
811static errno_t pci_dev_add(ddf_dev_t *dnode)
812{
813 hw_resource_list_t hw_resources;
814 pci_bus_t *bus = NULL;
815 ddf_fun_t *ctl = NULL;
816 bool got_res = false;
817 async_sess_t *sess;
818 errno_t rc;
819
820 ddf_msg(LVL_DEBUG, "pci_dev_add");
821
822 bus = ddf_dev_data_alloc(dnode, sizeof(pci_bus_t));
823 if (bus == NULL) {
824 ddf_msg(LVL_ERROR, "pci_dev_add allocation failed.");
825 rc = ENOMEM;
826 goto fail;
827 }
828
829 list_initialize(&bus->funs);
830 fibril_mutex_initialize(&bus->conf_mutex);
831 fibril_mutex_initialize(&bus->enum_done_lock);
832 fibril_condvar_initialize(&bus->enum_done_cv);
833
834 bus->dnode = dnode;
835
836 sess = ddf_dev_parent_sess_get(dnode);
837 if (sess == NULL) {
838 ddf_msg(LVL_ERROR, "pci_dev_add failed to connect to the "
839 "parent driver.");
840 rc = ENOENT;
841 goto fail;
842 }
843
844 rc = pio_window_get(sess, &bus->pio_win);
845 if (rc != EOK) {
846 ddf_msg(LVL_ERROR, "pci_dev_add failed to get PIO window "
847 "for the device.");
848 goto fail;
849 }
850
851 rc = hw_res_get_resource_list(sess, &hw_resources);
852 if (rc != EOK) {
853 ddf_msg(LVL_ERROR, "pci_dev_add failed to get hw resources "
854 "for the device.");
855 goto fail;
856 }
857 got_res = true;
858
859 assert(hw_resources.count >= 1);
860
861 if (hw_resources.count == 1) {
862 assert(hw_resources.resources[0].type == MEM_RANGE);
863
864 ddf_msg(LVL_DEBUG, "conf_addr_space = %" PRIx64 ".",
865 hw_resources.resources[0].res.mem_range.address);
866
867 if (pio_enable_resource(&bus->pio_win,
868 &hw_resources.resources[0], (void **) &bus->conf_space,
869 NULL, NULL)) {
870 ddf_msg(LVL_ERROR,
871 "Failed to map configuration space.");
872 rc = EADDRNOTAVAIL;
873 goto fail;
874 }
875
876 } else {
877 assert(hw_resources.resources[0].type == IO_RANGE);
878 assert(hw_resources.resources[0].res.io_range.size >= 4);
879
880 assert(hw_resources.resources[1].type == IO_RANGE);
881 assert(hw_resources.resources[1].res.io_range.size >= 4);
882
883 ddf_msg(LVL_DEBUG, "conf_addr = %" PRIx64 ".",
884 hw_resources.resources[0].res.io_range.address);
885 ddf_msg(LVL_DEBUG, "data_addr = %" PRIx64 ".",
886 hw_resources.resources[1].res.io_range.address);
887
888 if (pio_enable_resource(&bus->pio_win,
889 &hw_resources.resources[0], (void **) &bus->conf_addr_reg,
890 NULL, NULL)) {
891 ddf_msg(LVL_ERROR,
892 "Failed to enable configuration ports.");
893 rc = EADDRNOTAVAIL;
894 goto fail;
895 }
896 if (pio_enable_resource(&bus->pio_win,
897 &hw_resources.resources[1], (void **) &bus->conf_data_reg,
898 NULL, NULL)) {
899 ddf_msg(LVL_ERROR,
900 "Failed to enable configuration ports.");
901 rc = EADDRNOTAVAIL;
902 goto fail;
903 }
904 }
905
906 /* Make the bus device more visible. It has no use yet. */
907 ddf_msg(LVL_DEBUG, "Adding a 'ctl' function");
908
909 ctl = ddf_fun_create(bus->dnode, fun_exposed, "ctl");
910 if (ctl == NULL) {
911 ddf_msg(LVL_ERROR, "Failed creating control function.");
912 rc = ENOMEM;
913 goto fail;
914 }
915
916 ddf_fun_set_conn_handler(ctl, pci_ctl_connection);
917
918 /* Enumerate functions. */
919 ddf_msg(LVL_DEBUG, "Enumerating the bus");
920 rc = pci_bus_scan(bus, 0);
921 if (rc != EOK) {
922 ddf_msg(LVL_ERROR, "Bus enumeration failed.");
923 goto fail;
924 }
925
926 rc = ddf_fun_bind(ctl);
927 if (rc != EOK) {
928 ddf_msg(LVL_ERROR, "Failed binding control function.");
929 goto fail;
930 }
931
932 rc = ddf_fun_add_to_category(ctl, "pci");
933 if (rc != EOK) {
934 ddf_msg(LVL_ERROR, "Failed adding control function to category "
935 "'pci'.");
936 goto fail;
937 }
938
939 hw_res_clean_resource_list(&hw_resources);
940
941 ddf_msg(LVL_DEBUG, "Bus enumeration done.");
942
943 fibril_mutex_lock(&bus->enum_done_lock);
944 bus->enum_done = true;
945 fibril_mutex_unlock(&bus->enum_done_lock);
946 fibril_condvar_broadcast(&bus->enum_done_cv);
947
948 return EOK;
949fail:
950 if (got_res)
951 hw_res_clean_resource_list(&hw_resources);
952
953 if (ctl != NULL)
954 ddf_fun_destroy(ctl);
955
956 return rc;
957}
958
959static errno_t pci_fun_online(ddf_fun_t *fun)
960{
961 ddf_msg(LVL_DEBUG, "pci_fun_online()");
962 return ddf_fun_online(fun);
963}
964
965static errno_t pci_fun_offline(ddf_fun_t *fun)
966{
967 ddf_msg(LVL_DEBUG, "pci_fun_offline()");
968 return ddf_fun_offline(fun);
969}
970
971static void pciintel_init(void)
972{
973 ddf_log_init(NAME);
974}
975
976pci_fun_t *pci_fun_new(pci_bus_t *bus)
977{
978 pci_fun_t *fun;
979 ddf_fun_t *fnode;
980
981 fnode = ddf_fun_create(bus->dnode, fun_inner, NULL);
982 if (fnode == NULL)
983 return NULL;
984
985 fun = ddf_fun_data_alloc(fnode, sizeof(pci_fun_t));
986 if (fun == NULL)
987 return NULL;
988
989 fun->busptr = bus;
990 fun->fnode = fnode;
991 return fun;
992}
993
994void pci_fun_init(pci_fun_t *fun, int bus, int dev, int fn)
995{
996 fun->bus = bus;
997 fun->dev = dev;
998 fun->fn = fn;
999 fun->vendor_id = pci_conf_read_16(fun, PCI_VENDOR_ID);
1000 fun->device_id = pci_conf_read_16(fun, PCI_DEVICE_ID);
1001
1002 /* Explicitly enable PCI bus mastering */
1003 fun->command = pci_conf_read_16(fun, PCI_COMMAND) |
1004 PCI_COMMAND_MASTER;
1005 pci_conf_write_16(fun, PCI_COMMAND, fun->command);
1006
1007 fun->class_code = pci_conf_read_8(fun, PCI_BASE_CLASS);
1008 fun->subclass_code = pci_conf_read_8(fun, PCI_SUB_CLASS);
1009 fun->prog_if = pci_conf_read_8(fun, PCI_PROG_IF);
1010 fun->revision = pci_conf_read_8(fun, PCI_REVISION_ID);
1011}
1012
1013void pci_fun_delete(pci_fun_t *fun)
1014{
1015 hw_res_clean_resource_list(&fun->hw_resources);
1016 if (fun->fnode != NULL)
1017 ddf_fun_destroy(fun->fnode);
1018}
1019
1020char *pci_fun_create_name(pci_fun_t *fun)
1021{
1022 char *name = NULL;
1023
1024 asprintf(&name, "%02x:%02x.%01x", fun->bus, fun->dev,
1025 fun->fn);
1026 return name;
1027}
1028
1029bool pci_alloc_resource_list(pci_fun_t *fun)
1030{
1031 fun->hw_resources.resources = fun->resources;
1032 return true;
1033}
1034
1035void pci_clean_resource_list(pci_fun_t *fun)
1036{
1037 fun->hw_resources.resources = NULL;
1038}
1039
1040/** Read the base address registers (BARs) of the function and add the addresses
1041 * to its HW resource list.
1042 *
1043 * @param fun PCI function
1044 */
1045void pci_read_bars(pci_fun_t *fun)
1046{
1047 /*
1048 * Position of the BAR in the PCI configuration address space of the
1049 * device.
1050 */
1051 int addr = PCI_BASE_ADDR_0;
1052
1053 while (addr <= PCI_BASE_ADDR_5)
1054 addr = pci_read_bar(fun, addr);
1055}
1056
1057size_t pci_bar_mask_to_size(uint32_t mask)
1058{
1059 size_t size = mask & ~(mask - 1);
1060 return size;
1061}
1062
1063int main(int argc, char *argv[])
1064{
1065 printf(NAME ": HelenOS PCI bus driver (Intel method 1).\n");
1066 pciintel_init();
1067 return ddf_driver_main(&pci_driver);
1068}
1069
1070/**
1071 * @}
1072 */
Note: See TracBrowser for help on using the repository browser.