* \file linux_sysfs.c
* Access PCI subsystem using Linux's sysfs interface. This interface is
* available starting somewhere in the late 2.5.x kernel phase, and is the
- * prefered method on all 2.6.x kernels.
+ * preferred method on all 2.6.x kernels.
*
* \author Ian Romanick <idr@us.ibm.com>
*/
#include <dirent.h>
#include <errno.h>
-#include "pciaccess.h"
-#include "pciaccess_private.h"
-
-static int pci_device_linux_sysfs_read_rom( struct pci_device * dev,
- void * buffer );
-
-static int pci_device_linux_sysfs_probe( struct pci_device * dev );
-
-static int pci_device_linux_sysfs_map_region( struct pci_device * dev,
- unsigned region, int write_enable );
-
-static int pci_device_linux_sysfs_unmap_region( struct pci_device * dev,
- unsigned region );
+#include "config.h"
-static int pci_device_linux_sysfs_read( struct pci_device * dev, void * data,
- pciaddr_t offset, pciaddr_t size, pciaddr_t * bytes_read );
-
-static int pci_device_linux_sysfs_write( struct pci_device * dev,
- const void * data, pciaddr_t offset, pciaddr_t size,
- pciaddr_t * bytes_wrtten );
-
-static const struct pci_system_methods linux_sysfs_methods = {
- .destroy = NULL,
- .destroy_device = NULL,
- .read_rom = pci_device_linux_sysfs_read_rom,
- .probe = pci_device_linux_sysfs_probe,
- .map = pci_device_linux_sysfs_map_region,
- .unmap = pci_device_linux_sysfs_unmap_region,
+#ifdef HAVE_MTRR
+#include <asm/mtrr.h>
+#include <sys/ioctl.h>
+#endif
- .read = pci_device_linux_sysfs_read,
- .write = pci_device_linux_sysfs_write,
+#include "pciaccess.h"
+#include "pciaccess_private.h"
+#include "linux_devmem.h"
- .fill_capabilities = pci_fill_capabilities_generic
-};
+static const struct pci_system_methods linux_sysfs_methods;
#define SYS_BUS_PCI "/sys/bus/pci/devices"
+static int
+pci_device_linux_sysfs_read( struct pci_device * dev, void * data,
+ pciaddr_t offset, pciaddr_t size,
+ pciaddr_t * bytes_read );
-static void populate_entries( struct pci_system * pci_sys );
-
+static int populate_entries(struct pci_system * pci_sys);
/**
* Attempt to access PCI subsystem using Linux's sysfs interface.
*/
-int
+_pci_hidden int
pci_system_linux_sysfs_create( void )
{
int err = 0;
pci_sys = calloc( 1, sizeof( struct pci_system ) );
if ( pci_sys != NULL ) {
pci_sys->methods = & linux_sysfs_methods;
- populate_entries( pci_sys );
+#ifdef HAVE_MTRR
+ pci_sys->mtrr_fd = open("/proc/mtrr", O_WRONLY);
+#endif
+ err = populate_entries(pci_sys);
}
else {
err = ENOMEM;
}
-void
+int
populate_entries( struct pci_system * p )
{
struct dirent ** devices;
int n;
int i;
+ int err = 0;
n = scandir( SYS_BUS_PCI, & devices, scan_sys_pci_filter, alphasort );
p->num_devices = n;
p->devices = calloc( n, sizeof( struct pci_device_private ) );
+ if (p->devices != NULL) {
+ for (i = 0 ; i < n ; i++) {
+ uint8_t config[48];
+ pciaddr_t bytes;
+ unsigned dom, bus, dev, func;
+ struct pci_device_private *device =
+ (struct pci_device_private *) &p->devices[i];
+
+
+ sscanf(devices[i]->d_name, "%04x:%02x:%02x.%1u",
+ & dom, & bus, & dev, & func);
+
+ device->base.domain = dom;
+ device->base.bus = bus;
+ device->base.dev = dev;
+ device->base.func = func;
+
+
+ err = pci_device_linux_sysfs_read(& device->base, config, 0,
+ 48, & bytes);
+ if ((bytes == 48) && !err) {
+ device->base.vendor_id = (uint16_t)config[0]
+ + ((uint16_t)config[1] << 8);
+ device->base.device_id = (uint16_t)config[2]
+ + ((uint16_t)config[3] << 8);
+ device->base.device_class = (uint32_t)config[9]
+ + ((uint32_t)config[10] << 8)
+ + ((uint32_t)config[11] << 16);
+ device->base.revision = config[8];
+ device->base.subvendor_id = (uint16_t)config[44]
+ + ((uint16_t)config[45] << 8);
+ device->base.subdevice_id = (uint16_t)config[46]
+ + ((uint16_t)config[47] << 8);
+ }
- for ( i = 0 ; i < n ; i++ ) {
- unsigned dom, bus, dev, func;
-
+ if (err) {
+ break;
+ }
+ }
+ }
+ else {
+ err = ENOMEM;
+ }
+ }
- sscanf( devices[ i ]->d_name, "%04x:%02x:%02x.%1u",
- & dom, & bus, & dev, & func );
+ for (i = 0; i < n; i++)
+ free(devices[i]);
+ free(devices);
- p->devices[ i ].base.domain = dom;
- p->devices[ i ].base.bus = bus;
- p->devices[ i ].base.dev = dev;
- p->devices[ i ].base.func = func;
- }
+ if (err) {
+ free(p->devices);
+ p->devices = NULL;
}
+
+ return err;
}
if ( bytes >= 64 ) {
struct pci_device_private *priv = (struct pci_device_private *) dev;
- dev->vendor_id = (uint16_t)config[0] + ((uint16_t)config[1] << 8);
- dev->device_id = (uint16_t)config[2] + ((uint16_t)config[3] << 8);
- dev->device_class = (uint32_t)config[9] + ((uint32_t)config[10] << 8)
- + ((uint16_t)config[11] << 16);
- dev->revision = config[8];
- dev->subvendor_id = (uint16_t)config[44] + ((uint16_t)config[45] << 8);
- dev->subdevice_id = (uint16_t)config[46] + ((uint16_t)config[47] << 8);
dev->irq = config[60];
-
priv->header_type = config[14];
high_addr = strtoull( next, & next, 16 );
flags = strtoull( next, & next, 16 );
if ( low_addr != 0 ) {
+ priv->rom_base = low_addr;
dev->rom_size = (high_addr - low_addr) + 1;
}
}
int fd;
struct stat st;
int err = 0;
+ size_t rom_size;
size_t total_bytes;
fd = open( name, O_RDWR );
if ( fd == -1 ) {
+#ifdef LINUX_ROM
+ /* If reading the ROM using sysfs fails, fall back to the old
+ * /dev/mem based interface.
+ * disable this for newer kernels using configure
+ */
+ return pci_device_linux_devmem_read_rom(dev, buffer);
+#else
return errno;
+#endif
}
return errno;
}
+ rom_size = st.st_size;
+ if ( rom_size == 0 )
+ rom_size = 0x10000;
/* This is a quirky thing on Linux. Even though the ROM and the file
* for the ROM in sysfs are read-only, the string "1" must be written to
write( fd, "1", 1 );
lseek( fd, 0, SEEK_SET );
- for ( total_bytes = 0 ; total_bytes < st.st_size ; /* empty */ ) {
+ for ( total_bytes = 0 ; total_bytes < rom_size ; /* empty */ ) {
const int bytes = read( fd, (char *) buffer + total_bytes,
- st.st_size - total_bytes );
+ rom_size - total_bytes );
if ( bytes == -1 ) {
err = errno;
break;
pciaddr_t temp_size = size;
int err = 0;
int fd;
-
+ char *data_bytes = data;
if ( bytes_read != NULL ) {
*bytes_read = 0;
while ( temp_size > 0 ) {
- const ssize_t bytes = pread64( fd, data, temp_size, offset );
+ const ssize_t bytes = pread64( fd, data_bytes, temp_size, offset );
/* If zero bytes were read, then we assume it's the end of the
* config file.
*/
- if ( bytes <= 0 ) {
+ if (bytes == 0)
+ break;
+ if ( bytes < 0 ) {
err = errno;
break;
}
temp_size -= bytes;
offset += bytes;
- data += bytes;
+ data_bytes += bytes;
}
if ( bytes_read != NULL ) {
pciaddr_t temp_size = size;
int err = 0;
int fd;
-
+ const char *data_bytes = data;
if ( bytes_written != NULL ) {
*bytes_written = 0;
while ( temp_size > 0 ) {
- const ssize_t bytes = pwrite64( fd, data, temp_size, offset );
+ const ssize_t bytes = pwrite64( fd, data_bytes, temp_size, offset );
/* If zero bytes were written, then we assume it's the end of the
* config file.
*/
- if ( bytes <= 0 ) {
+ if ( bytes == 0 )
+ break;
+ if ( bytes < 0 ) {
err = errno;
break;
}
temp_size -= bytes;
offset += bytes;
- data += bytes;
+ data_bytes += bytes;
}
if ( bytes_written != NULL ) {
return err;
}
+static int
+pci_device_linux_sysfs_map_range_wc(struct pci_device *dev,
+ struct pci_device_mapping *map)
+{
+ char name[256];
+ int fd;
+ const int prot = ((map->flags & PCI_DEV_MAP_FLAG_WRITABLE) != 0)
+ ? (PROT_READ | PROT_WRITE) : PROT_READ;
+ const int open_flags = ((map->flags & PCI_DEV_MAP_FLAG_WRITABLE) != 0)
+ ? O_RDWR : O_RDONLY;
+ const off_t offset = map->base - dev->regions[map->region].base_addr;
+
+ snprintf(name, 255, "%s/%04x:%02x:%02x.%1u/resource%u_wc",
+ SYS_BUS_PCI,
+ dev->domain,
+ dev->bus,
+ dev->dev,
+ dev->func,
+ map->region);
+ fd = open(name, open_flags);
+ if (fd == -1)
+ return errno;
+
+ map->memory = mmap(NULL, map->size, prot, MAP_SHARED, fd, offset);
+ if (map->memory == MAP_FAILED) {
+ map->memory = NULL;
+ close(fd);
+ return errno;
+ }
+
+ close(fd);
+
+ return 0;
+}
/**
* Map a memory region for a device using the Linux sysfs interface.
*
- * \param dev Device whose memory region is to be mapped.
- * \param region Region, on the range [0, 5], that is to be mapped.
- * \param write_enable Map for writing (non-zero).
+ * \param dev Device whose memory region is to be mapped.
+ * \param map Parameters of the mapping that is to be created.
*
* \return
* Zero on success or an \c errno value on failure.
*
- * \sa pci_device_map_region, pci_device_linux_sysfs_unmap_region
+ * \sa pci_device_map_rrange, pci_device_linux_sysfs_unmap_range
*
* \todo
* Some older 2.6.x kernels don't implement the resourceN files. On those
* \c mmap64 may need to be used.
*/
static int
-pci_device_linux_sysfs_map_region( struct pci_device * dev, unsigned region,
- int write_enable )
+pci_device_linux_sysfs_map_range(struct pci_device *dev,
+ struct pci_device_mapping *map)
{
char name[256];
int fd;
int err = 0;
- const int prot = (write_enable) ? (PROT_READ | PROT_WRITE) : PROT_READ;
-
+ const int prot = ((map->flags & PCI_DEV_MAP_FLAG_WRITABLE) != 0)
+ ? (PROT_READ | PROT_WRITE) : PROT_READ;
+ const int open_flags = ((map->flags & PCI_DEV_MAP_FLAG_WRITABLE) != 0)
+ ? O_RDWR : O_RDONLY;
+ const off_t offset = map->base - dev->regions[map->region].base_addr;
+#ifdef HAVE_MTRR
+ struct mtrr_sentry sentry = {
+ .base = map->base,
+ .size = map->size,
+ .type = MTRR_TYPE_UNCACHABLE
+ };
+#endif
+
+ /* For WC mappings, try sysfs resourceN_wc file first */
+ if ((map->flags & PCI_DEV_MAP_FLAG_WRITE_COMBINE) &&
+ !pci_device_linux_sysfs_map_range_wc(dev, map))
+ return 0;
+
+ snprintf(name, 255, "%s/%04x:%02x:%02x.%1u/resource%u",
+ SYS_BUS_PCI,
+ dev->domain,
+ dev->bus,
+ dev->dev,
+ dev->func,
+ map->region);
+
+ fd = open(name, open_flags);
+ if (fd == -1) {
+ return errno;
+ }
- snprintf( name, 255, "%s/%04x:%02x:%02x.%1u/resource%u",
- SYS_BUS_PCI,
- dev->domain,
- dev->bus,
- dev->dev,
- dev->func,
- region );
- fd = open( name, (write_enable) ? O_RDWR : O_RDONLY );
- if ( fd == -1 ) {
+ map->memory = mmap(NULL, map->size, prot, MAP_SHARED, fd, offset);
+ if (map->memory == MAP_FAILED) {
+ map->memory = NULL;
+ close(fd);
return errno;
}
+#ifdef HAVE_MTRR
+ if ((map->flags & PCI_DEV_MAP_FLAG_CACHABLE) != 0) {
+ sentry.type = MTRR_TYPE_WRBACK;
+ } else if ((map->flags & PCI_DEV_MAP_FLAG_WRITE_COMBINE) != 0) {
+ sentry.type = MTRR_TYPE_WRCOMB;
+ }
- dev->regions[ region ].memory = mmap( NULL, dev->regions[ region ].size,
- prot, MAP_SHARED, fd, 0 );
- if ( dev->regions[ region ].memory == MAP_FAILED ) {
- err = errno;
- dev->regions[ region ].memory = NULL;
+ if (pci_sys->mtrr_fd != -1 && sentry.type != MTRR_TYPE_UNCACHABLE) {
+ if (ioctl(pci_sys->mtrr_fd, MTRRIOC_ADD_ENTRY, &sentry) < 0) {
+ /* FIXME: Should we report an error in this case?
+ */
+ fprintf(stderr, "error setting MTRR "
+ "(base = 0x%08lx, size = 0x%08x, type = %u) %s (%d)\n",
+ sentry.base, sentry.size, sentry.type,
+ strerror(errno), errno);
+/* err = errno;*/
+ }
+ /* KLUDGE ALERT -- rewrite the PTEs to turn off the CD and WT bits */
+ mprotect (map->memory, map->size, PROT_NONE);
+ err = mprotect (map->memory, map->size, PROT_READ|PROT_WRITE);
+
+ if (err != 0) {
+ fprintf(stderr, "mprotect(PROT_READ | PROT_WRITE) failed: %s\n",
+ strerror(errno));
+ fprintf(stderr, "remapping without mprotect performance kludge.\n");
+
+ munmap(map->memory, map->size);
+ map->memory = mmap(NULL, map->size, prot, MAP_SHARED, fd, offset);
+ if (map->memory == MAP_FAILED) {
+ map->memory = NULL;
+ close(fd);
+ return errno;
+ }
+ }
}
+#endif
- close( fd );
- return err;
-}
+ close(fd);
+ return 0;
+}
/**
- * Unmap the specified region using the Linux sysfs interface.
- *
- * \param dev Device whose memory region is to be mapped.
- * \param region Region, on the range [0, 5], that is to be mapped.
- *
+ * Unmap a memory region for a device using the Linux sysfs interface.
+ *
+ * \param dev Device whose memory region is to be unmapped.
+ * \param map Parameters of the mapping that is to be destroyed.
+ *
* \return
* Zero on success or an \c errno value on failure.
*
- * \sa pci_device_unmap_region, pci_device_linux_sysfs_map_region
+ * \sa pci_device_map_rrange, pci_device_linux_sysfs_map_range
*
* \todo
* Some older 2.6.x kernels don't implement the resourceN files. On those
* \c mmap64 may need to be used.
*/
static int
-pci_device_linux_sysfs_unmap_region( struct pci_device * dev, unsigned region )
+pci_device_linux_sysfs_unmap_range(struct pci_device *dev,
+ struct pci_device_mapping *map)
{
int err = 0;
-
- if ( munmap( dev->regions[ region ].memory, dev->regions[ region ].size )
- == -1 ) {
- err = errno;
+#ifdef HAVE_MTRR
+ struct mtrr_sentry sentry = {
+ .base = map->base,
+ .size = map->size,
+ .type = MTRR_TYPE_UNCACHABLE
+ };
+#endif
+
+ err = pci_device_generic_unmap_range (dev, map);
+ if (err)
+ return err;
+
+#ifdef HAVE_MTRR
+ if ((map->flags & PCI_DEV_MAP_FLAG_CACHABLE) != 0) {
+ sentry.type = MTRR_TYPE_WRBACK;
+ } else if ((map->flags & PCI_DEV_MAP_FLAG_WRITE_COMBINE) != 0) {
+ sentry.type = MTRR_TYPE_WRCOMB;
}
- dev->regions[ region ].memory = NULL;
+ if (pci_sys->mtrr_fd != -1 && sentry.type != MTRR_TYPE_UNCACHABLE) {
+ if (ioctl(pci_sys->mtrr_fd, MTRRIOC_DEL_ENTRY, &sentry) < 0) {
+ /* FIXME: Should we report an error in this case?
+ */
+ fprintf(stderr, "error setting MTRR "
+ "(base = 0x%08lx, size = 0x%08x, type = %u) %s (%d)\n",
+ sentry.base, sentry.size, sentry.type,
+ strerror(errno), errno);
+/* err = errno;*/
+ }
+ }
+#endif
return err;
}
+
+static void pci_device_linux_sysfs_enable(struct pci_device *dev)
+{
+ char name[256];
+ int fd;
+
+ snprintf( name, 255, "%s/%04x:%02x:%02x.%1u/enable",
+ SYS_BUS_PCI,
+ dev->domain,
+ dev->bus,
+ dev->dev,
+ dev->func );
+
+ fd = open( name, O_RDWR );
+ if (fd == -1)
+ return;
+
+ write( fd, "1", 1 );
+ close(fd);
+}
+
+static int pci_device_linux_sysfs_boot_vga(struct pci_device *dev)
+{
+ char name[256];
+ char reply[3];
+ int fd, bytes_read;
+ int ret = 0;
+
+ snprintf( name, 255, "%s/%04x:%02x:%02x.%1u/boot_vga",
+ SYS_BUS_PCI,
+ dev->domain,
+ dev->bus,
+ dev->dev,
+ dev->func );
+
+ fd = open( name, O_RDONLY );
+ if (fd == -1)
+ return 0;
+
+ bytes_read = read(fd, reply, 1);
+ if (bytes_read != 1)
+ goto out;
+ if (reply[0] == '1')
+ ret = 1;
+out:
+ close(fd);
+ return ret;
+}
+
+static int pci_device_linux_sysfs_has_kernel_driver(struct pci_device *dev)
+{
+ char name[256];
+ struct stat dummy;
+ int ret;
+
+ snprintf( name, 255, "%s/%04x:%02x:%02x.%1u/driver",
+ SYS_BUS_PCI,
+ dev->domain,
+ dev->bus,
+ dev->dev,
+ dev->func );
+
+ ret = stat(name, &dummy);
+ if (ret < 0)
+ return 0;
+ return 1;
+}
+
+static struct pci_io_handle *
+pci_device_linux_sysfs_open_device_io(struct pci_io_handle *ret,
+ struct pci_device *dev, int bar,
+ pciaddr_t base, pciaddr_t size)
+{
+ char name[PATH_MAX];
+
+ snprintf(name, PATH_MAX, "%s/%04x:%02x:%02x.%1u/resource%d",
+ SYS_BUS_PCI, dev->domain, dev->bus, dev->dev, dev->func, bar);
+
+ ret->fd = open(name, O_RDWR);
+
+ if (ret->fd < 0)
+ return NULL;
+
+ ret->base = base;
+ ret->size = size;
+
+ return ret;
+}
+
+static struct pci_io_handle *
+pci_device_linux_sysfs_open_legacy_io(struct pci_io_handle *ret,
+ struct pci_device *dev, pciaddr_t base,
+ pciaddr_t size)
+{
+ char name[PATH_MAX];
+
+ /* First check if there's a legacy io method for the device */
+ while (dev) {
+ snprintf(name, PATH_MAX, "/sys/class/pci_bus/%04x:%02x/legacy_io",
+ dev->domain, dev->bus);
+
+ ret->fd = open(name, O_RDWR);
+ if (ret->fd >= 0)
+ break;
+
+ dev = pci_device_get_parent_bridge(dev);
+ }
+
+ /* If not, /dev/port is the best we can do */
+ if (!dev)
+ ret->fd = open("/dev/port", O_RDWR);
+
+ if (ret->fd < 0)
+ return NULL;
+
+ ret->base = base;
+ ret->size = size;
+
+ return ret;
+}
+
+static void
+pci_device_linux_sysfs_close_io(struct pci_device *dev,
+ struct pci_io_handle *handle)
+{
+ close(handle->fd);
+}
+
+static uint32_t
+pci_device_linux_sysfs_read32(struct pci_io_handle *handle, uint32_t port)
+{
+ uint32_t ret;
+
+ pread(handle->fd, &ret, 4, port + handle->base);
+
+ return ret;
+}
+
+static uint16_t
+pci_device_linux_sysfs_read16(struct pci_io_handle *handle, uint32_t port)
+{
+ uint16_t ret;
+
+ pread(handle->fd, &ret, 2, port + handle->base);
+
+ return ret;
+}
+
+static uint8_t
+pci_device_linux_sysfs_read8(struct pci_io_handle *handle, uint32_t port)
+{
+ uint8_t ret;
+
+ pread(handle->fd, &ret, 1, port + handle->base);
+
+ return ret;
+}
+
+static void
+pci_device_linux_sysfs_write32(struct pci_io_handle *handle, uint32_t port,
+ uint32_t data)
+{
+ pwrite(handle->fd, &data, 4, port + handle->base);
+}
+
+static void
+pci_device_linux_sysfs_write16(struct pci_io_handle *handle, uint32_t port,
+ uint16_t data)
+{
+ pwrite(handle->fd, &data, 2, port + handle->base);
+}
+
+static void
+pci_device_linux_sysfs_write8(struct pci_io_handle *handle, uint32_t port,
+ uint8_t data)
+{
+ pwrite(handle->fd, &data, 1, port + handle->base);
+}
+
+static const struct pci_system_methods linux_sysfs_methods = {
+ .destroy = NULL,
+ .destroy_device = NULL,
+ .read_rom = pci_device_linux_sysfs_read_rom,
+ .probe = pci_device_linux_sysfs_probe,
+ .map_range = pci_device_linux_sysfs_map_range,
+ .unmap_range = pci_device_linux_sysfs_unmap_range,
+
+ .read = pci_device_linux_sysfs_read,
+ .write = pci_device_linux_sysfs_write,
+
+ .fill_capabilities = pci_fill_capabilities_generic,
+ .enable = pci_device_linux_sysfs_enable,
+ .boot_vga = pci_device_linux_sysfs_boot_vga,
+ .has_kernel_driver = pci_device_linux_sysfs_has_kernel_driver,
+
+ .open_device_io = pci_device_linux_sysfs_open_device_io,
+ .open_legacy_io = pci_device_linux_sysfs_open_legacy_io,
+ .close_io = pci_device_linux_sysfs_close_io,
+ .read32 = pci_device_linux_sysfs_read32,
+ .read16 = pci_device_linux_sysfs_read16,
+ .read8 = pci_device_linux_sysfs_read8,
+ .write32 = pci_device_linux_sysfs_write32,
+ .write16 = pci_device_linux_sysfs_write16,
+ .write8 = pci_device_linux_sysfs_write8,
+};