aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/pci/pci_user.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pci/pci_user.c')
-rw-r--r--sys/dev/pci/pci_user.c487
1 files changed, 487 insertions, 0 deletions
diff --git a/sys/dev/pci/pci_user.c b/sys/dev/pci/pci_user.c
new file mode 100644
index 000000000000..16990291538e
--- /dev/null
+++ b/sys/dev/pci/pci_user.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 1997, Stefan Esser <se@freebsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "opt_bus.h" /* XXX trim includes */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/linker.h>
+#include <sys/fcntl.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+#include <vm/vm_extern.h>
+
+#include <sys/bus.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <machine/resource.h>
+
+#include <sys/pciio.h>
+#include <pci/pcireg.h>
+#include <pci/pcivar.h>
+
+#include "pcib_if.h"
+#include "pci_if.h"
+
+/*
+ * This is the user interface to PCI configuration space.
+ */
+
+static int pci_open(dev_t dev, int oflags, int devtype, struct proc *p);
+static int pci_close(dev_t dev, int flag, int devtype, struct proc *p);
+static int pci_conf_match(struct pci_match_conf *matches, int num_matches,
+ struct pci_conf *match_buf);
+static int pci_ioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p);
+
+#define PCI_CDEV 78
+
+struct cdevsw pcicdev = {
+ /* open */ pci_open,
+ /* close */ pci_close,
+ /* read */ noread,
+ /* write */ nowrite,
+ /* ioctl */ pci_ioctl,
+ /* poll */ nopoll,
+ /* mmap */ nommap,
+ /* strategy */ nostrategy,
+ /* name */ "pci",
+ /* maj */ PCI_CDEV,
+ /* dump */ nodump,
+ /* psize */ nopsize,
+ /* flags */ 0,
+ /* bmaj */ -1
+};
+
+static int
+pci_open(dev_t dev, int oflags, int devtype, struct proc *p)
+{
+ if ((oflags & FWRITE) && securelevel > 0) {
+ return EPERM;
+ }
+ return 0;
+}
+
+static int
+pci_close(dev_t dev, int flag, int devtype, struct proc *p)
+{
+ return 0;
+}
+
+/*
+ * Match a single pci_conf structure against an array of pci_match_conf
+ * structures. The first argument, 'matches', is an array of num_matches
+ * pci_match_conf structures. match_buf is a pointer to the pci_conf
+ * structure that will be compared to every entry in the matches array.
+ * This function returns 1 on failure, 0 on success.
+ */
+static int
+pci_conf_match(struct pci_match_conf *matches, int num_matches,
+ struct pci_conf *match_buf)
+{
+ int i;
+
+ if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0))
+ return(1);
+
+ for (i = 0; i < num_matches; i++) {
+ /*
+ * I'm not sure why someone would do this...but...
+ */
+ if (matches[i].flags == PCI_GETCONF_NO_MATCH)
+ continue;
+
+ /*
+ * Look at each of the match flags. If it's set, do the
+ * comparison. If the comparison fails, we don't have a
+ * match, go on to the next item if there is one.
+ */
+ if (((matches[i].flags & PCI_GETCONF_MATCH_BUS) != 0)
+ && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus))
+ continue;
+
+ if (((matches[i].flags & PCI_GETCONF_MATCH_DEV) != 0)
+ && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev))
+ continue;
+
+ if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC) != 0)
+ && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func))
+ continue;
+
+ if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR) != 0)
+ && (match_buf->pc_vendor != matches[i].pc_vendor))
+ continue;
+
+ if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE) != 0)
+ && (match_buf->pc_device != matches[i].pc_device))
+ continue;
+
+ if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS) != 0)
+ && (match_buf->pc_class != matches[i].pc_class))
+ continue;
+
+ if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT) != 0)
+ && (match_buf->pd_unit != matches[i].pd_unit))
+ continue;
+
+ if (((matches[i].flags & PCI_GETCONF_MATCH_NAME) != 0)
+ && (strncmp(matches[i].pd_name, match_buf->pd_name,
+ sizeof(match_buf->pd_name)) != 0))
+ continue;
+
+ return(0);
+ }
+
+ return(1);
+}
+
+static int
+pci_ioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ device_t pci, pcib;
+ struct pci_io *io;
+ const char *name;
+ int error;
+
+ if (!(flag & FWRITE))
+ return EPERM;
+
+
+ switch(cmd) {
+ case PCIOCGETCONF:
+ {
+ struct pci_devinfo *dinfo;
+ struct pci_conf_io *cio;
+ struct devlist *devlist_head;
+ struct pci_match_conf *pattern_buf;
+ int num_patterns;
+ size_t iolen;
+ int ionum, i;
+
+ cio = (struct pci_conf_io *)data;
+
+ num_patterns = 0;
+ dinfo = NULL;
+
+ /*
+ * Hopefully the user won't pass in a null pointer, but it
+ * can't hurt to check.
+ */
+ if (cio == NULL) {
+ error = EINVAL;
+ break;
+ }
+
+ /*
+ * If the user specified an offset into the device list,
+ * but the list has changed since they last called this
+ * ioctl, tell them that the list has changed. They will
+ * have to get the list from the beginning.
+ */
+ if ((cio->offset != 0)
+ && (cio->generation != pci_generation)){
+ cio->num_matches = 0;
+ cio->status = PCI_GETCONF_LIST_CHANGED;
+ error = 0;
+ break;
+ }
+
+ /*
+ * Check to see whether the user has asked for an offset
+ * past the end of our list.
+ */
+ if (cio->offset >= pci_numdevs) {
+ cio->num_matches = 0;
+ cio->status = PCI_GETCONF_LAST_DEVICE;
+ error = 0;
+ break;
+ }
+
+ /* get the head of the device queue */
+ devlist_head = &pci_devq;
+
+ /*
+ * Determine how much room we have for pci_conf structures.
+ * Round the user's buffer size down to the nearest
+ * multiple of sizeof(struct pci_conf) in case the user
+ * didn't specify a multiple of that size.
+ */
+ iolen = min(cio->match_buf_len -
+ (cio->match_buf_len % sizeof(struct pci_conf)),
+ pci_numdevs * sizeof(struct pci_conf));
+
+ /*
+ * Since we know that iolen is a multiple of the size of
+ * the pciconf union, it's okay to do this.
+ */
+ ionum = iolen / sizeof(struct pci_conf);
+
+ /*
+ * If this test is true, the user wants the pci_conf
+ * structures returned to match the supplied entries.
+ */
+ if ((cio->num_patterns > 0)
+ && (cio->pat_buf_len > 0)) {
+ /*
+ * pat_buf_len needs to be:
+ * num_patterns * sizeof(struct pci_match_conf)
+ * While it is certainly possible the user just
+ * allocated a large buffer, but set the number of
+ * matches correctly, it is far more likely that
+ * their kernel doesn't match the userland utility
+ * they're using. It's also possible that the user
+ * forgot to initialize some variables. Yes, this
+ * may be overly picky, but I hazard to guess that
+ * it's far more likely to just catch folks that
+ * updated their kernel but not their userland.
+ */
+ if ((cio->num_patterns *
+ sizeof(struct pci_match_conf)) != cio->pat_buf_len){
+ /* The user made a mistake, return an error*/
+ cio->status = PCI_GETCONF_ERROR;
+ printf("pci_ioctl: pat_buf_len %d != "
+ "num_patterns (%d) * sizeof(struct "
+ "pci_match_conf) (%d)\npci_ioctl: "
+ "pat_buf_len should be = %d\n",
+ cio->pat_buf_len, cio->num_patterns,
+ (int)sizeof(struct pci_match_conf),
+ (int)sizeof(struct pci_match_conf) *
+ cio->num_patterns);
+ printf("pci_ioctl: do your headers match your "
+ "kernel?\n");
+ cio->num_matches = 0;
+ error = EINVAL;
+ break;
+ }
+
+ /*
+ * Check the user's buffer to make sure it's readable.
+ */
+ if (!useracc((caddr_t)cio->patterns,
+ cio->pat_buf_len, VM_PROT_READ)) {
+ printf("pci_ioctl: pattern buffer %p, "
+ "length %u isn't user accessible for"
+ " READ\n", cio->patterns,
+ cio->pat_buf_len);
+ error = EACCES;
+ break;
+ }
+ /*
+ * Allocate a buffer to hold the patterns.
+ */
+ pattern_buf = malloc(cio->pat_buf_len, M_TEMP,
+ M_WAITOK);
+ error = copyin(cio->patterns, pattern_buf,
+ cio->pat_buf_len);
+ if (error != 0)
+ break;
+ num_patterns = cio->num_patterns;
+
+ } else if ((cio->num_patterns > 0)
+ || (cio->pat_buf_len > 0)) {
+ /*
+ * The user made a mistake, spit out an error.
+ */
+ cio->status = PCI_GETCONF_ERROR;
+ cio->num_matches = 0;
+ printf("pci_ioctl: invalid GETCONF arguments\n");
+ error = EINVAL;
+ break;
+ } else
+ pattern_buf = NULL;
+
+ /*
+ * Make sure we can write to the match buffer.
+ */
+ if (!useracc((caddr_t)cio->matches,
+ cio->match_buf_len, VM_PROT_WRITE)) {
+ printf("pci_ioctl: match buffer %p, length %u "
+ "isn't user accessible for WRITE\n",
+ cio->matches, cio->match_buf_len);
+ error = EACCES;
+ break;
+ }
+
+ /*
+ * Go through the list of devices and copy out the devices
+ * that match the user's criteria.
+ */
+ for (cio->num_matches = 0, error = 0, i = 0,
+ dinfo = STAILQ_FIRST(devlist_head);
+ (dinfo != NULL) && (cio->num_matches < ionum)
+ && (error == 0) && (i < pci_numdevs);
+ dinfo = STAILQ_NEXT(dinfo, pci_links), i++) {
+
+ if (i < cio->offset)
+ continue;
+
+ /* Populate pd_name and pd_unit */
+ name = NULL;
+ if (dinfo->cfg.dev && dinfo->conf.pd_name[0] == '\0')
+ name = device_get_name(dinfo->cfg.dev);
+ if (name) {
+ strncpy(dinfo->conf.pd_name, name,
+ sizeof(dinfo->conf.pd_name));
+ dinfo->conf.pd_name[PCI_MAXNAMELEN] = 0;
+ dinfo->conf.pd_unit =
+ device_get_unit(dinfo->cfg.dev);
+ }
+
+ if ((pattern_buf == NULL) ||
+ (pci_conf_match(pattern_buf, num_patterns,
+ &dinfo->conf) == 0)) {
+
+ /*
+ * If we've filled up the user's buffer,
+ * break out at this point. Since we've
+ * got a match here, we'll pick right back
+ * up at the matching entry. We can also
+ * tell the user that there are more matches
+ * left.
+ */
+ if (cio->num_matches >= ionum)
+ break;
+
+ error = copyout(&dinfo->conf,
+ &cio->matches[cio->num_matches],
+ sizeof(struct pci_conf));
+ cio->num_matches++;
+ }
+ }
+
+ /*
+ * Set the pointer into the list, so if the user is getting
+ * n records at a time, where n < pci_numdevs,
+ */
+ cio->offset = i;
+
+ /*
+ * Set the generation, the user will need this if they make
+ * another ioctl call with offset != 0.
+ */
+ cio->generation = pci_generation;
+
+ /*
+ * If this is the last device, inform the user so he won't
+ * bother asking for more devices. If dinfo isn't NULL, we
+ * know that there are more matches in the list because of
+ * the way the traversal is done.
+ */
+ if (dinfo == NULL)
+ cio->status = PCI_GETCONF_LAST_DEVICE;
+ else
+ cio->status = PCI_GETCONF_MORE_DEVS;
+
+ if (pattern_buf != NULL)
+ free(pattern_buf, M_TEMP);
+
+ break;
+ }
+ case PCIOCREAD:
+ io = (struct pci_io *)data;
+ switch(io->pi_width) {
+ case 4:
+ case 2:
+ case 1:
+ /*
+ * Assume that the user-level bus number is
+ * actually the pciN instance number. We map
+ * from that to the real pcib+bus combination.
+ */
+ pci = devclass_get_device(devclass_find("pci"),
+ io->pi_sel.pc_bus);
+ if (pci) {
+ int b = pcib_get_bus(pci);
+ pcib = device_get_parent(pci);
+ io->pi_data =
+ PCIB_READ_CONFIG(pcib,
+ b,
+ io->pi_sel.pc_dev,
+ io->pi_sel.pc_func,
+ io->pi_reg,
+ io->pi_width);
+ error = 0;
+ } else {
+ error = ENODEV;
+ }
+ break;
+ default:
+ error = ENODEV;
+ break;
+ }
+ break;
+
+ case PCIOCWRITE:
+ io = (struct pci_io *)data;
+ switch(io->pi_width) {
+ case 4:
+ case 2:
+ case 1:
+ /*
+ * Assume that the user-level bus number is
+ * actually the pciN instance number. We map
+ * from that to the real pcib+bus combination.
+ */
+ pci = devclass_get_device(devclass_find("pci"),
+ io->pi_sel.pc_bus);
+ if (pci) {
+ int b = pcib_get_bus(pci);
+ pcib = device_get_parent(pci);
+ PCIB_WRITE_CONFIG(pcib,
+ b,
+ io->pi_sel.pc_dev,
+ io->pi_sel.pc_func,
+ io->pi_reg,
+ io->pi_data,
+ io->pi_width);
+ error = 0;
+ } else {
+ error = ENODEV;
+ }
+ break;
+ default:
+ error = ENODEV;
+ break;
+ }
+ break;
+
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ return (error);
+}
+