diff options
Diffstat (limited to 'sys/dev/pci/pci_user.c')
-rw-r--r-- | sys/dev/pci/pci_user.c | 487 |
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); +} + |