diff options
Diffstat (limited to 'src/hurd_pci.c')
-rw-r--r-- | src/hurd_pci.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/src/hurd_pci.c b/src/hurd_pci.c new file mode 100644 index 0000000..28bef16 --- /dev/null +++ b/src/hurd_pci.c @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2018, Damien Zammit + * Copyright (c) 2017, Joan Lledó + * Copyright (c) 2009, 2012 Samuel Thibault + * Heavily inspired from the freebsd, netbsd, and openbsd backends + * (C) Copyright Eric Anholt 2006 + * (C) Copyright IBM Corporation 2006 + * Copyright (c) 2008 Juan Romero Pardines + * Copyright (c) 2008 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define _GNU_SOURCE +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/mman.h> +#include <string.h> +#include <strings.h> +#include <hurd.h> +#include <hurd/pci.h> +#include <hurd/paths.h> + +#include "x86_pci.h" +#include "pciaccess.h" +#include "pciaccess_private.h" + +/* + * Hurd PCI access using RPCs. + * + * Some functions are shared with the x86 module to avoid repeating code. + */ + +/* Server path */ +#define _SERVERS_BUS_PCI _SERVERS_BUS "/pci" + +/* File names */ +#define FILE_CONFIG_NAME "config" +#define FILE_ROM_NAME "rom" + +/* Level in the fs tree */ +typedef enum { + LEVEL_NONE, + LEVEL_DOMAIN, + LEVEL_BUS, + LEVEL_DEV, + LEVEL_FUNC +} tree_level; + +struct pci_system_hurd { + struct pci_system system; +}; + +static int +pci_device_hurd_probe(struct pci_device *dev) +{ + uint8_t irq; + int err, i; + struct pci_bar regions[6]; + struct pci_xrom_bar rom; + struct pci_device_private *d; + size_t size; + char *buf; + + /* Many of the fields were filled in during initial device enumeration. + * At this point, we need to fill in regions, rom_size, and irq. + */ + + err = pci_device_cfg_read_u8(dev, &irq, PCI_IRQ); + if (err) + return err; + dev->irq = irq; + + /* Get regions */ + buf = (char *)®ions; + size = sizeof(regions); + d = (struct pci_device_private *)dev; + err = pci_get_dev_regions(d->device_port, &buf, &size); + if(err) + return err; + + if((char*)®ions != buf) + { + /* Sanity check for bogus server. */ + if(size > sizeof(regions)) + { + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + return EGRATUITOUS; + } + + memcpy(®ions, buf, size); + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + } + + for(i=0; i<6; i++) + { + if(regions[i].size == 0) + continue; + + dev->regions[i].base_addr = regions[i].base_addr; + dev->regions[i].size = regions[i].size; + dev->regions[i].is_IO = regions[i].is_IO; + dev->regions[i].is_prefetchable = regions[i].is_prefetchable; + dev->regions[i].is_64 = regions[i].is_64; + } + + /* Get rom info */ + buf = (char *)&rom; + size = sizeof(rom); + err = pci_get_dev_rom(d->device_port, &buf, &size); + if(err) + return err; + + if((char*)&rom != buf) + { + /* Sanity check for bogus server. */ + if(size > sizeof(rom)) + { + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + return EGRATUITOUS; + } + + memcpy(&rom, buf, size); + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + } + + d->rom_base = rom.base_addr; + dev->rom_size = rom.size; + + return 0; +} + +/* + * Read `nbytes' bytes from `reg' in device's configuretion space + * and store them in `buf'. + * + * It's assumed that `nbytes' bytes are allocated in `buf' + */ +static int +pciclient_cfg_read(mach_port_t device_port, int reg, char *buf, + size_t * nbytes) +{ + int err; + size_t nread; + char *data; + + data = buf; + nread = *nbytes; + err = pci_conf_read(device_port, reg, &data, &nread, *nbytes); + if (err) + return err; + + if (data != buf) { + if (nread > *nbytes) /* Sanity check for bogus server. */ { + vm_deallocate(mach_task_self(), (vm_address_t) data, nread); + return EGRATUITOUS; + } + + memcpy(buf, data, nread); + vm_deallocate(mach_task_self(), (vm_address_t)data, nread); + } + + *nbytes = nread; + + return 0; +} + +/* Write `nbytes' bytes from `buf' to `reg' in device's configuration space */ +static int +pciclient_cfg_write(mach_port_t device_port, int reg, char *buf, + size_t * nbytes) +{ + int err; + size_t nwrote; + + err = pci_conf_write(device_port, reg, buf, *nbytes, &nwrote); + + if (!err) + *nbytes = nwrote; + + return err; +} + +/* + * Read up to `size' bytes from `dev' configuration space to `data' starting + * at `offset'. Write the amount on read bytes in `bytes_read'. + */ +static int +pci_device_hurd_read(struct pci_device *dev, void *data, + pciaddr_t offset, pciaddr_t size, pciaddr_t *bytes_read) +{ + int err; + struct pci_device_private *d; + + *bytes_read = 0; + d = (struct pci_device_private *)dev; + while (size > 0) { + size_t toread = 1 << (ffs(0x4 + (offset & 0x03)) - 1); + if (toread > size) + toread = size; + + err = pciclient_cfg_read(d->device_port, offset, (char*)data, + &toread); + if (err) + return err; + + offset += toread; + data = (char*)data + toread; + size -= toread; + *bytes_read += toread; + } + return 0; +} + +/* + * Write up to `size' bytes from `data' to `dev' configuration space starting + * at `offset'. Write the amount on written bytes in `bytes_written'. + */ +static int +pci_device_hurd_write(struct pci_device *dev, const void *data, + pciaddr_t offset, pciaddr_t size, pciaddr_t *bytes_written) +{ + int err; + struct pci_device_private *d; + + *bytes_written = 0; + d = (struct pci_device_private *)dev; + while (size > 0) { + size_t towrite = 4; + if (towrite > size) + towrite = size; + if (towrite > 4 - (offset & 0x3)) + towrite = 4 - (offset & 0x3); + + err = pciclient_cfg_write(d->device_port, offset, (char*)data, + &towrite); + if (err) + return err; + + offset += towrite; + data = (const char*)data + towrite; + size -= towrite; + *bytes_written += towrite; + } + return 0; +} + +/* + * Copy the device's firmware in `buffer' + */ +static int +pci_device_hurd_read_rom(struct pci_device * dev, void * buffer) +{ + ssize_t rd; + int romfd; + char server[NAME_MAX]; + + snprintf(server, NAME_MAX, "%s/%04x/%02x/%02x/%01u/%s", _SERVERS_BUS_PCI, + dev->domain, dev->bus, dev->dev, dev->func, FILE_ROM_NAME); + + romfd = open(server, O_RDONLY | O_CLOEXEC); + if (romfd == -1) + return errno; + + rd = read(romfd, buffer, dev->rom_size); + if (rd != dev->rom_size) { + close(romfd); + return errno; + } + + close(romfd); + + return 0; +} + +/* + * Each device has its own server where send RPC's to. + * + * Deallocate the port before destroying the device. + */ +static void +pci_device_hurd_destroy(struct pci_device *dev) +{ + struct pci_device_private *d = (struct pci_device_private*) dev; + + mach_port_deallocate (mach_task_self (), d->device_port); +} + +/* Walk through the FS tree to see what is allowed for us */ +static int +enum_devices(const char *parent, struct pci_device_private **device, + int domain, int bus, int dev, int func, tree_level lev) +{ + int err, ret; + DIR *dir; + struct dirent *entry; + char path[NAME_MAX]; + char server[NAME_MAX]; + uint32_t reg; + size_t toread; + mach_port_t device_port; + + dir = opendir(parent); + if (!dir) + return errno; + + while ((entry = readdir(dir)) != 0) { + snprintf(path, NAME_MAX, "%s/%s", parent, entry->d_name); + if (entry->d_type == DT_DIR) { + if (!strncmp(entry->d_name, ".", NAME_MAX) + || !strncmp(entry->d_name, "..", NAME_MAX)) + continue; + + errno = 0; + ret = strtol(entry->d_name, 0, 16); + if (errno) + return errno; + + /* + * We found a valid directory. + * Update the address and switch to the next level. + */ + switch (lev) { + case LEVEL_DOMAIN: + domain = ret; + break; + case LEVEL_BUS: + bus = ret; + break; + case LEVEL_DEV: + dev = ret; + break; + case LEVEL_FUNC: + func = ret; + break; + default: + return -1; + } + + err = enum_devices(path, device, domain, bus, dev, func, lev+1); + if (err == EPERM) + continue; + } + else { + if (strncmp(entry->d_name, FILE_CONFIG_NAME, NAME_MAX)) + /* We are looking for the config file */ + continue; + + /* We found an available virtual device, add it to our list */ + snprintf(server, NAME_MAX, "%s/%04x/%02x/%02x/%01u/%s", + _SERVERS_BUS_PCI, domain, bus, dev, func, + entry->d_name); + device_port = file_name_lookup(server, 0, 0); + if (device_port == MACH_PORT_NULL) + return errno; + + toread = sizeof(reg); + err = pciclient_cfg_read(device_port, PCI_VENDOR_ID, (char*)®, + &toread); + if (err) + return err; + if (toread != sizeof(reg)) + return -1; + + (*device)->base.domain = domain; + (*device)->base.bus = bus; + (*device)->base.dev = dev; + (*device)->base.func = func; + (*device)->base.vendor_id = PCI_VENDOR(reg); + (*device)->base.device_id = PCI_DEVICE(reg); + + toread = sizeof(reg); + err = pciclient_cfg_read(device_port, PCI_CLASS, (char*)®, + &toread); + if (err) + return err; + if (toread != sizeof(reg)) + return -1; + + (*device)->base.device_class = reg >> 8; + (*device)->base.revision = reg & 0xFF; + + toread = sizeof(reg); + err = pciclient_cfg_read(device_port, PCI_SUB_VENDOR_ID, + (char*)®, &toread); + if (err) + return err; + if (toread != sizeof(reg)) + return -1; + + (*device)->base.subvendor_id = PCI_VENDOR(reg); + (*device)->base.subdevice_id = PCI_DEVICE(reg); + + (*device)->device_port = device_port; + + (*device)++; + } + } + + return 0; +} + +static const struct pci_system_methods hurd_pci_methods = { + .destroy = pci_system_x86_destroy, + .destroy_device = pci_device_hurd_destroy, + .read_rom = pci_device_hurd_read_rom, + .probe = pci_device_hurd_probe, + .map_range = pci_device_x86_map_range, + .unmap_range = pci_device_x86_unmap_range, + .read = pci_device_hurd_read, + .write = pci_device_hurd_write, + .fill_capabilities = pci_fill_capabilities_generic, + .open_legacy_io = pci_device_x86_open_legacy_io, + .close_io = pci_device_x86_close_io, + .read32 = pci_device_x86_read32, + .read16 = pci_device_x86_read16, + .read8 = pci_device_x86_read8, + .write32 = pci_device_x86_write32, + .write16 = pci_device_x86_write16, + .write8 = pci_device_x86_write8, + .map_legacy = pci_device_x86_map_legacy, + .unmap_legacy = pci_device_x86_unmap_legacy, +}; + +_pci_hidden int +pci_system_hurd_create(void) +{ + struct pci_device_private *device; + int err; + struct pci_system_hurd *pci_sys_hurd; + size_t ndevs; + mach_port_t pci_server_port; + + /* If we can open pci cfg io ports on hurd, + * we are the arbiter, therefore try x86 method first */ + err = pci_system_x86_create(); + if (!err) + return 0; + + pci_sys_hurd = calloc(1, sizeof(struct pci_system_hurd)); + if (pci_sys_hurd == NULL) { + x86_disable_io(); + return ENOMEM; + } + pci_sys = &pci_sys_hurd->system; + + pci_sys->methods = &hurd_pci_methods; + + pci_server_port = file_name_lookup(_SERVERS_BUS_PCI, 0, 0); + if (!pci_server_port) { + /* Fall back to x86 access method */ + return pci_system_x86_create(); + } + + /* The server gives us the number of available devices for us */ + err = pci_get_ndevs (pci_server_port, &ndevs); + if (err) { + mach_port_deallocate (mach_task_self (), pci_server_port); + /* Fall back to x86 access method */ + return pci_system_x86_create(); + } + mach_port_deallocate (mach_task_self (), pci_server_port); + + pci_sys->num_devices = ndevs; + pci_sys->devices = calloc(ndevs, sizeof(struct pci_device_private)); + if (pci_sys->devices == NULL) { + x86_disable_io(); + free(pci_sys_hurd); + pci_sys = NULL; + return ENOMEM; + } + + device = pci_sys->devices; + err = enum_devices(_SERVERS_BUS_PCI, &device, -1, -1, -1, -1, + LEVEL_DOMAIN); + if (err) + return pci_system_x86_create(); + + return 0; +} |