aboutsummaryrefslogtreecommitdiff
path: root/pci-arbiter/pci-ops.c
diff options
context:
space:
mode:
Diffstat (limited to 'pci-arbiter/pci-ops.c')
-rw-r--r--pci-arbiter/pci-ops.c273
1 files changed, 273 insertions, 0 deletions
diff --git a/pci-arbiter/pci-ops.c b/pci-arbiter/pci-ops.c
new file mode 100644
index 00000000..328db5b2
--- /dev/null
+++ b/pci-arbiter/pci-ops.c
@@ -0,0 +1,273 @@
+/*
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Implementation of PCI operations */
+
+#include <pci_S.h>
+
+#include <fcntl.h>
+#include <hurd/netfs.h>
+#include <sys/mman.h>
+
+#include "pci_access.h"
+#include "pcifs.h"
+#include "func_files.h"
+
+static error_t
+check_permissions (struct protid *master, int flags)
+{
+ error_t err = 0;
+ struct node *node;
+ struct pcifs_dirent *e;
+
+ node = master->po->np;
+ e = node->nn->ln;
+
+ /* Check wheter the user has permissions to access this node */
+ err = entry_check_perms (master->user, e, flags);
+ if (err)
+ return err;
+
+ /* Check wheter the request has been sent to the proper node */
+ if (e->domain != 0 /* Only domain 0 can be accessed by I/O ports */
+ || e->bus < 0 || e->dev < 0 || e->func < 0)
+ err = EINVAL;
+
+ return err;
+}
+
+static size_t
+calculate_ndevs (struct iouser *user)
+{
+ size_t ndevs = 0;
+ int i;
+ struct pcifs_dirent *e;
+
+ for (i = 0, e = fs->entries; i < fs->num_entries; i++, e++)
+ {
+ if (e->func < 0 /* Skip entries without a full address */
+ || !S_ISDIR (e->stat.st_mode)) /* and entries that are not folders */
+ continue;
+
+ if (!entry_check_perms (user, e, O_READ))
+ /* If no error, user may access this device */
+ ndevs++;
+ }
+
+ return ndevs;
+}
+
+/*
+ * Read min(amount,*datalen) bytes and store them on `*data'.
+ *
+ * `*datalen' is updated.
+ */
+error_t
+S_pci_conf_read (struct protid * master, int reg, char **data,
+ size_t * datalen, mach_msg_type_number_t amount)
+{
+ error_t err;
+ pthread_mutex_t *lock;
+ struct pcifs_dirent *e;
+
+ if (!master)
+ return EOPNOTSUPP;
+
+ e = master->po->np->nn->ln;
+ if (strncmp (e->name, FILE_CONFIG_NAME, NAME_SIZE))
+ /* This operation may only be addressed to the config file */
+ return EINVAL;
+
+ lock = &fs->pci_conf_lock;
+
+ err = check_permissions (master, O_READ);
+ if (err)
+ return err;
+
+ /*
+ * We don't allocate new memory since we expect no more than 4 bytes-long
+ * buffers. Instead, we just take the lower value as length.
+ */
+ if (amount > *datalen)
+ amount = *datalen;
+
+ /*
+ * The server is not single-threaded anymore. Incoming rpcs are handled by
+ * libnetfs which is multi-threaded. A lock is needed for arbitration.
+ */
+ pthread_mutex_lock (lock);
+ err = pci_sys->read (e->bus, e->dev, e->func, reg, *data, amount);
+ pthread_mutex_unlock (lock);
+
+ if (!err)
+ {
+ *datalen = amount;
+ /* Update atime */
+ UPDATE_TIMES (e, TOUCH_ATIME);
+ }
+
+ return err;
+}
+
+/* Write `datalen' bytes from `data'. `amount' is updated. */
+error_t
+S_pci_conf_write (struct protid * master, int reg, char *data, size_t datalen,
+ mach_msg_type_number_t * amount)
+{
+ error_t err;
+ pthread_mutex_t *lock;
+ struct pcifs_dirent *e;
+
+ if (!master)
+ return EOPNOTSUPP;
+
+ e = master->po->np->nn->ln;
+ if (strncmp (e->name, FILE_CONFIG_NAME, NAME_SIZE))
+ /* This operation may only be addressed to the config file */
+ return EINVAL;
+
+ lock = &fs->pci_conf_lock;
+
+ err = check_permissions (master, O_WRITE);
+ if (err)
+ return err;
+
+ pthread_mutex_lock (lock);
+ err = pci_sys->write (e->bus, e->dev, e->func, reg, data, datalen);
+ pthread_mutex_unlock (lock);
+
+ if (!err)
+ {
+ *amount = datalen;
+ /* Update mtime and ctime */
+ UPDATE_TIMES (e, TOUCH_MTIME | TOUCH_CTIME);
+ }
+
+ return err;
+}
+
+/* Write in `amount' the number of devices allowed for the user. */
+error_t
+S_pci_get_ndevs (struct protid * master, mach_msg_type_number_t * amount)
+{
+ /* This RPC may only be addressed to the root node */
+ if (master->po->np != fs->root)
+ return EINVAL;
+
+ *amount = calculate_ndevs (master->user);
+
+ return 0;
+}
+
+/*
+ * Return in `data' the information about the available memory
+ * regions in the given device.
+ */
+error_t
+S_pci_get_dev_regions (struct protid * master, char **data, size_t * datalen)
+{
+ error_t err;
+ struct pcifs_dirent *e;
+ struct pci_bar regions[6], *r;
+ size_t size;
+ int i;
+
+ if (!master)
+ return EOPNOTSUPP;
+
+ e = master->po->np->nn->ln;
+ if (strncmp (e->name, FILE_CONFIG_NAME, NAME_SIZE))
+ /* This operation may only be addressed to the config file */
+ return EINVAL;
+
+ err = check_permissions (master, O_READ);
+ if (err)
+ return err;
+
+ /* Allocate memory if needed */
+ size = sizeof (regions);
+ if (size > *datalen)
+ {
+ *data = mmap (0, size, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0);
+ if (*data == MAP_FAILED)
+ return ENOMEM;
+ }
+
+ /* Copy the regions info */
+ for (i = 0, r = (struct pci_bar *) *data; i < 6; i++, r++)
+ {
+ r->base_addr = e->device->regions[i].base_addr;
+ r->size = e->device->regions[i].size;
+ r->is_IO = e->device->regions[i].is_IO;
+ r->is_prefetchable = e->device->regions[i].is_prefetchable;
+ r->is_64 = e->device->regions[i].is_64;
+ }
+
+ /* Update atime */
+ UPDATE_TIMES (e, TOUCH_ATIME);
+
+ *datalen = size;
+
+ return 0;
+}
+
+/*
+ * Return in `data' the information about the expansion rom of the given device
+ */
+error_t
+S_pci_get_dev_rom (struct protid * master, char **data, size_t * datalen)
+{
+ error_t err;
+ struct pcifs_dirent *e;
+ struct pci_xrom_bar rom;
+ size_t size;
+
+ if (!master)
+ return EOPNOTSUPP;
+
+ e = master->po->np->nn->ln;
+ if (strncmp (e->name, FILE_CONFIG_NAME, NAME_SIZE))
+ /* This operation may only be addressed to the config file */
+ return EINVAL;
+
+ err = check_permissions (master, O_READ);
+ if (err)
+ return err;
+
+ /* Allocate memory if needed */
+ size = sizeof (rom);
+ if (size > *datalen)
+ {
+ *data = mmap (0, size, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0);
+ if (*data == MAP_FAILED)
+ return ENOMEM;
+ }
+
+ /* Copy the regions info */
+ rom.base_addr = e->device->rom_base;
+ rom.size = e->device->rom_size;
+ memcpy (*data, &rom, size);
+
+ /* Update atime */
+ UPDATE_TIMES (e, TOUCH_ATIME);
+
+ *datalen = size;
+
+ return 0;
+}