diff options
Diffstat (limited to 'lib/hurd.c')
-rw-r--r-- | lib/hurd.c | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/lib/hurd.c b/lib/hurd.c new file mode 100644 index 0000000..c5a381e --- /dev/null +++ b/lib/hurd.c @@ -0,0 +1,369 @@ +/* + * The PCI Library -- Hurd access via RPCs + * + * Copyright (c) 2017 Joan Lledó <jlledom@member.fsf.org> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#define _GNU_SOURCE + +#include "internal.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <fcntl.h> +#include <string.h> +#include <hurd.h> +#include <hurd/pci.h> +#include <hurd/paths.h> + +/* 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; + +/* Check whether there's a pci server */ +static int +hurd_detect(struct pci_access *a) +{ + int err; + struct stat st; + + err = stat(_SERVERS_BUS_PCI, &st); + if (err) + { + a->error("Could not open file `%s'", _SERVERS_BUS_PCI); + return 0; + } + + /* The node must be a directory and a translator */ + return S_ISDIR(st.st_mode) && ((st.st_mode & S_ITRANS) == S_IROOT); +} + +/* Empty callbacks, we don't need any special init or cleanup */ +static void +hurd_init(struct pci_access *a UNUSED) +{ +} + +static void +hurd_cleanup(struct pci_access *a UNUSED) +{ +} + +/* Each device has its own server path. Allocate space for the port. */ +static void +hurd_init_dev(struct pci_dev *d) +{ + d->aux = pci_malloc(d->access, sizeof(mach_port_t)); + *((mach_port_t *) d->aux) = MACH_PORT_NULL; +} + +/* Deallocate the port and free its space */ +static void +hurd_cleanup_dev(struct pci_dev *d) +{ + mach_port_t device_port; + + device_port = *((mach_port_t *) d->aux); + mach_port_deallocate(mach_task_self(), device_port); + + pci_mfree(d->aux); +} + +static mach_port_t +device_port_lookup(struct pci_dev *d) +{ + char server[NAME_MAX]; + mach_port_t device_port = *((mach_port_t *) d->aux); + + if (device_port != MACH_PORT_NULL) + return device_port; + + snprintf(server, NAME_MAX, "%s/%04x/%02x/%02x/%01u/%s", + _SERVERS_BUS_PCI, d->domain, d->bus, d->dev, d->func, + FILE_CONFIG_NAME); + device_port = file_name_lookup(server, 0, 0); + + if (device_port == MACH_PORT_NULL) + d->access->error("Cannot find the PCI arbiter"); + + *((mach_port_t *) d->aux) = device_port; + return device_port; +} + +/* Walk through the FS tree to see what is allowed for us */ +static void +enum_devices(const char *parent, struct pci_access *a, int domain, int bus, + int dev, int func, tree_level lev) +{ + int ret; + DIR *dir; + struct dirent *entry; + char path[NAME_MAX]; + struct pci_dev *d; + + dir = opendir(parent); + if (!dir) + { + if (errno == EPERM || errno == EACCES) + /* The client lacks the permissions to access this function, skip */ + return; + else + a->error("Cannot open directory: %s (%s)", parent, strerror(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) + { + if (closedir(dir) < 0) + a->warning("Cannot close directory: %s (%s)", parent, + strerror(errno)); + a->error("Wrong directory name: %s (number expected) probably " + "not connected to an arbiter", entry->d_name); + } + + /* + * 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: + if (closedir(dir) < 0) + a->warning("Cannot close directory: %s (%s)", parent, + strerror(errno)); + a->error("Wrong directory tree, probably not connected to an arbiter"); + } + + enum_devices(path, a, domain, bus, dev, func, lev + 1); + } + 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 */ + d = pci_alloc_dev(a); + d->domain = domain; + d->bus = bus; + d->dev = dev; + d->func = func; + pci_link_dev(a, d); + } + } + + if (closedir(dir) < 0) + a->error("Cannot close directory: %s (%s)", parent, strerror(errno)); +} + +/* Enumerate devices */ +static void +hurd_scan(struct pci_access *a) +{ + enum_devices(_SERVERS_BUS_PCI, a, -1, -1, -1, -1, LEVEL_DOMAIN); +} + +/* + * Read `len' bytes to `buf'. + * + * Returns error when the number of read bytes does not match `len'. + */ +static int +hurd_read(struct pci_dev *d, int pos, byte * buf, int len) +{ + int err; + size_t nread; + char *data; + mach_port_t device_port = device_port_lookup(d); + + if (len > 4) + return pci_generic_block_read(d, pos, buf, len); + + data = (char *) buf; + err = pci_conf_read(device_port, pos, &data, &nread, len); + + if (data != (char *) buf) + { + if (nread > (size_t) len) /* Sanity check for bogus server. */ + { + vm_deallocate(mach_task_self(), (vm_address_t) data, nread); + return 0; + } + + memcpy(buf, data, nread); + vm_deallocate(mach_task_self(), (vm_address_t) data, nread); + } + + return !err && nread == (size_t) len; +} + +/* + * Write `len' bytes from `buf'. + * + * Returns error when the number of written bytes does not match `len'. + */ +static int +hurd_write(struct pci_dev *d, int pos, byte * buf, int len) +{ + int err; + size_t nwrote; + mach_port_t device_port = device_port_lookup(d); + + if (len > 4) + return pci_generic_block_write(d, pos, buf, len); + + err = pci_conf_write(device_port, pos, (char *) buf, len, &nwrote); + + return !err && nwrote == (size_t) len; +} + +/* Get requested info from the server */ + +static int +hurd_fill_regions(struct pci_dev *d) +{ + mach_port_t device_port = device_port_lookup(d); + struct pci_bar regions[6]; + char *buf = (char *) ®ions; + size_t size = sizeof(regions); + + int err = pci_get_dev_regions(device_port, &buf, &size); + if (err) + return 0; + + if ((char *) ®ions != buf) + { + /* Sanity check for bogus server. */ + if (size > sizeof(regions)) + { + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + return 0; + } + + memcpy(®ions, buf, size); + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + } + + for (int i = 0; i < 6; i++) + { + if (regions[i].size == 0) + continue; + + d->base_addr[i] = regions[i].base_addr; + d->base_addr[i] |= regions[i].is_IO; + d->base_addr[i] |= regions[i].is_64 << 2; + d->base_addr[i] |= regions[i].is_prefetchable << 3; + + d->size[i] = regions[i].size; + } + + return 1; +} + +static int +hurd_fill_rom(struct pci_dev *d) +{ + struct pci_xrom_bar rom; + mach_port_t device_port = device_port_lookup(d); + char *buf = (char *) &rom; + size_t size = sizeof(rom); + + int err = pci_get_dev_rom(device_port, &buf, &size); + if (err) + return 0; + + if ((char *) &rom != buf) + { + /* Sanity check for bogus server. */ + if (size > sizeof(rom)) + { + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + return 0; + } + + memcpy(&rom, buf, size); + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + } + + d->rom_base_addr = rom.base_addr; + d->rom_size = rom.size; + + return 1; +} + +static void +hurd_fill_info(struct pci_dev *d, unsigned int flags) +{ + if (!d->access->buscentric) + { + if (want_fill(d, flags, PCI_FILL_BASES | PCI_FILL_SIZES)) + { + if (hurd_fill_regions(d)) + clear_fill(d, PCI_FILL_BASES | PCI_FILL_SIZES); + } + if (want_fill(d, flags, PCI_FILL_ROM_BASE)) + { + if (hurd_fill_rom(d)) + clear_fill(d, PCI_FILL_ROM_BASE); + } + } + + pci_generic_fill_info(d, flags); +} + +struct pci_methods pm_hurd = { + "hurd", + "Hurd access using RPCs", + NULL, /* config */ + hurd_detect, + hurd_init, + hurd_cleanup, + hurd_scan, + hurd_fill_info, + hurd_read, + hurd_write, + NULL, /* read_vpd */ + hurd_init_dev, + hurd_cleanup_dev +}; |