diff options
author | Eric Anholt <eric@anholt.net> | 2007-01-12 16:32:51 -0800 |
---|---|---|
committer | Eric Anholt <eric@anholt.net> | 2007-01-12 16:32:51 -0800 |
commit | 335d42f637dd44461bc20ba599ca5dc4971b6eaa (patch) | |
tree | d4a47ae71f4010197a7cc6764e5a25513d5dfeba | |
parent | 94355b6f49a61e7424ddc7ddfc70a9b344233a47 (diff) |
Add support for getting region information on FreeBSD.
This could stand a lot more testing -- all it has received is visual inspection
of scanpci output on one machine, with some differing results from XFree86
scanpci output.
-rw-r--r-- | src/freebsd_pci.c | 121 |
1 files changed, 120 insertions, 1 deletions
diff --git a/src/freebsd_pci.c b/src/freebsd_pci.c index d3b0018..3492311 100644 --- a/src/freebsd_pci.c +++ b/src/freebsd_pci.c @@ -182,11 +182,117 @@ pci_device_freebsd_write( struct pci_device * dev, const void * data, return 0; } +/** Returns the number of regions (base address registers) the device has */ + +static int +pci_device_freebsd_get_num_regions( struct pci_device * dev ) +{ + struct pci_device_private *priv = (struct pci_device_private *) dev; + + switch (priv->header_type & 0x7f) { + case 0: + return 6; + case 1: + return 2; + case 2: + return 1; + default: + printf("unknown header type %02x\n", priv->header_type); + return 0; + } +} + +/** Masks out the flag bigs of the base address register value */ +static uint32_t +get_map_base( uint32_t val ) +{ + if (val & 0x01) + return val & ~0x03; + else + return val & ~0x0f; +} + +/** Returns the size of a region based on the all-ones test value */ +static int +get_test_val_size( uint32_t testval ) +{ + int size = 1; + + if (testval == 0) + return 0; + + /* Mask out the flag bits */ + testval = get_map_base( testval ); + + while ((testval & 1) == 0) { + size <<= 1; + testval >>= 1; + } + + return size; +} + +/** + * Sets the address and size information for the region from config space + * registers. + * + * This would be much better provided by a kernel interface. + * + * \return 0 on success, or an errno value. + */ +static int +pci_device_freebsd_get_region_info( struct pci_device * dev, int region, + int bar ) +{ + uint32_t addr, testval, temp; + int err; + + /* Get the base address */ + err = pci_device_cfg_read_u32( dev, &addr, bar ); + if (err != 0) + return err; + + /* Test write all ones to the register, then restore it. */ + temp = 0xffffffff; + err = pci_device_cfg_write_u32( dev, &temp, bar ); + if (err != 0) + return err; + pci_device_cfg_read_u32( dev, &testval, bar ); + err = pci_device_cfg_write_u32( dev, &addr, bar ); + + if (addr & 0x01) + dev->regions[region].is_IO = 1; + if (addr & 0x04) + dev->regions[region].is_64 = 1; + if (addr & 0x08) + dev->regions[region].is_prefetchable = 1; + + /* Set the size */ + dev->regions[region].size = get_test_val_size( testval ); + + /* Set the base address value */ + if (dev->regions[region].is_64) { + uint32_t top; + + err = pci_device_cfg_read_u32( dev, &top, bar + 4 ); + if (err != 0) + return err; + + dev->regions[region].base_addr = ((uint64_t)top << 32) | + get_map_base(addr); + } else { + dev->regions[region].base_addr = get_map_base(addr); + } + + return 0; +} + static int pci_device_freebsd_probe( struct pci_device * dev ) { + struct pci_device_private *priv = (struct pci_device_private *) dev; uint8_t irq; - int err; + int err, i, bar; /* Many of the fields were filled in during initial device enumeration. * At this point, we need to fill in regions, rom_size, and irq. @@ -197,6 +303,19 @@ pci_device_freebsd_probe( struct pci_device * dev ) return errno; dev->irq = irq; + err = pci_device_cfg_read_u8( dev, &priv->header_type, 0x0e ); + if (err) + return errno; + + bar = 0x10; + for (i = 0; i < pci_device_freebsd_get_num_regions( dev ); i++) { + pci_device_freebsd_get_region_info( dev, i, bar ); + if (dev->regions[i].is_64) + bar += 0x08; + else + bar += 0x04; + } + return 0; } |