diff --git a/share/examples/drivers/make_device_driver.sh b/share/examples/drivers/make_device_driver.sh index 5b8f8efa6469..d6d3a8d7c6b9 100755 --- a/share/examples/drivers/make_device_driver.sh +++ b/share/examples/drivers/make_device_driver.sh @@ -1,1005 +1,1006 @@ #!/bin/sh # This writes a skeleton driver and puts it into the kernel tree for you. # It also adds FOO and files.FOO configuration files so you can compile # a kernel with your FOO driver linked in. # To do so: # cd /usr/src; make buildkernel KERNCONF=FOO # # More interestingly, it creates a modules/foo directory # which it populates, to allow you to compile a FOO module # which can be linked with your presently running kernel (if you feel brave). # To do so: # cd /sys/modules/foo; make depend; make; make install; kldload foo # # arg1 to this script is expected to be lowercase "foo" # arg2 path to the kernel sources, "/sys" if omitted # # Trust me, RUN THIS SCRIPT :) # # TODO: # o generate foo_isa.c, foo_pci.c, foo_pccard.c, foo_cardbus.c, and foovar.h # o Put pccard stuff in here. # # # if [ "X${1}" = "X" ]; then echo "Hey, how about some help here... give me a device name!" exit 1 fi if [ "X${2}" = "X" ]; then TOP=`cd /sys; pwd -P` echo "Using ${TOP} as the path to the kernel sources!" else TOP=${2} fi UPPER=`echo ${1} |tr "[:lower:]" "[:upper:]"` if [ -d ${TOP}/modules/${1} ]; then echo "There appears to already be a module called ${1}" echo -n "Should it be overwritten? [Y]" read VAL if [ "-z" "$VAL" ]; then VAL=YES fi case ${VAL} in [yY]*) echo "Cleaning up from prior runs" rm -rf ${TOP}/dev/${1} rm -rf ${TOP}/modules/${1} rm ${TOP}/conf/files.${UPPER} rm ${TOP}/i386/conf/${UPPER} rm ${TOP}/sys/${1}io.h ;; *) exit 1 ;; esac fi echo "The following files will be created:" echo ${TOP}/modules/${1} echo ${TOP}/conf/files.${UPPER} echo ${TOP}/i386/conf/${UPPER} echo ${TOP}/dev/${1} echo ${TOP}/dev/${1}/${1}.c echo ${TOP}/sys/${1}io.h echo ${TOP}/modules/${1} echo ${TOP}/modules/${1}/Makefile mkdir ${TOP}/modules/${1} ####################################################################### ####################################################################### # # Create configuration information needed to create a kernel # containing this driver. # # Not really needed if we are going to do this as a module. ####################################################################### # First add the file to a local file list. ####################################################################### cat >${TOP}/conf/files.${UPPER} <${TOP}/i386/conf/${UPPER} <>${TOP}/i386/conf/${UPPER} <${TOP}/dev/${1}/${1}.c < #include #include /* cdevsw stuff */ #include /* SYSINIT stuff */ #include /* SYSINIT stuff */ #include /* malloc region definitions */ #include #include #include #include #include /* ${1} IOCTL definitions */ #include #include #include #include #include #include #include "isa_if.h" /* XXX These should be defined in terms of bus-space ops. */ #define ${UPPER}_INB(port) inb(port_start) #define ${UPPER}_OUTB(port, val) ( port_start, (val)) #define SOME_PORT 123 #define EXPECTED_VALUE 0x42 /* * The softc is automatically allocated by the parent bus using the * size specified in the driver_t declaration below. */ #define DEV2SOFTC(dev) ((struct ${1}_softc *) (dev)->si_drv1) #define DEVICE2SOFTC(dev) ((struct ${1}_softc *) device_get_softc(dev)) /* * Device specific misc defines. */ #define BUFFERSIZE 1024 #define NUMPORTS 4 #define MEMSIZE (4 * 1024) /* Imaginable h/w buffer size. */ /* * One of these per allocated device. */ struct ${1}_softc { bus_space_tag_t bt; bus_space_handle_t bh; int rid_ioport; int rid_memory; int rid_irq; int rid_drq; struct resource* res_ioport; /* Resource for port range. */ struct resource* res_memory; /* Resource for mem range. */ struct resource* res_irq; /* Resource for irq range. */ struct resource* res_drq; /* Resource for dma channel. */ device_t device; struct cdev *dev; void *intr_cookie; void *vaddr; /* Virtual address of mem resource. */ char buffer[BUFFERSIZE]; /* If we need to buffer something. */ }; /* Function prototypes (these should all be static). */ static int ${1}_deallocate_resources(device_t device); static int ${1}_allocate_resources(device_t device); static int ${1}_attach(device_t device, struct ${1}_softc *scp); static int ${1}_detach(device_t device, struct ${1}_softc *scp); static d_open_t ${1}open; static d_close_t ${1}close; static d_read_t ${1}read; static d_write_t ${1}write; static d_ioctl_t ${1}ioctl; static d_mmap_t ${1}mmap; static d_poll_t ${1}poll; static void ${1}intr(void *arg); static struct cdevsw ${1}_cdevsw = { .d_version = D_VERSION, .d_open = ${1}open, .d_close = ${1}close, .d_read = ${1}read, .d_write = ${1}write, .d_ioctl = ${1}ioctl, .d_poll = ${1}poll, .d_mmap = ${1}mmap, .d_name = "${1}", }; static devclass_t ${1}_devclass; /* ****************************************** * ISA Attachment structures and functions. ****************************************** */ static void ${1}_isa_identify (driver_t *, device_t); static int ${1}_isa_probe (device_t); static int ${1}_isa_attach (device_t); static int ${1}_isa_detach (device_t); static struct isa_pnp_id ${1}_ids[] = { {0x12345678, "ABCco Widget"}, {0xfedcba98, "shining moon Widget ripoff"}, {0, NULL} }; static device_method_t ${1}_methods[] = { DEVMETHOD(device_identify, ${1}_isa_identify), DEVMETHOD(device_probe, ${1}_isa_probe), DEVMETHOD(device_attach, ${1}_isa_attach), DEVMETHOD(device_detach, ${1}_isa_detach), DEVMETHOD_END }; static driver_t ${1}_isa_driver = { "${1}", ${1}_methods, sizeof (struct ${1}_softc) }; DRIVER_MODULE(${1}, isa, ${1}_isa_driver, ${1}_devclass, 0, 0); /* * Here list some port addresses we might expect our widget to appear at: * This list should only be used for cards that have some non-destructive * (to other cards) way of probing these address. Otherwise the driver * should not go looking for instances of itself, but instead rely on * the hints file. Strange failures for people with other cards might * result. */ static struct localhints { int ioport; int irq; int drq; int mem; } res[] = { { 0x210, 11, 2, 0xcd000}, { 0x310, 12, 3, 0xdd000}, { 0x320, 9, 6, 0xd4000}, {0,0,0,0} }; #define MAXHINTS 10 /* Just an arbitrary safety limit. */ /* * Called once when the driver is somehow connected with the bus, * (Either linked in and the bus is started, or loaded as a module). * * The aim of this routine in an ISA driver is to add child entries to * the parent bus so that it looks as if the devices were detected by * some pnp-like method, or at least mentioned in the hints. * * For NON-PNP "dumb" devices: * Add entries into the bus's list of likely devices, so that * our 'probe routine' will be called for them. * This is similar to what the 'hints' code achieves, except this is * loadable with the driver. * In the 'dumb' case we end up with more children than needed but * some (or all) of them will fail probe() and only waste a little memory. * * For NON-PNP "Smart" devices: * If the device has a NON-PNP way of being detected and setting/sensing * the card, then do that here and add a child for each set of * hardware found. * * For PNP devices: * If the device is always PNP capable then this function can be removed. * The ISA PNP system will have automatically added it to the system and * so your identify routine needn't do anything. * * If the device is mentioned in the 'hints' file then this * function can be removed. All devices mentioned in the hints * file get added as children for probing, whether or not the * driver is linked in. So even as a module it MAY still be there. * See isa/isahint.c for hints being added in. */ static void ${1}_isa_identify (driver_t *driver, device_t parent) { u_int32_t irq=0; u_int32_t ioport; device_t child; int i; /* * If we've already got ${UPPER} attached somehow, don't try again. * Maybe it was in the hints file. or it was loaded before. */ if (device_find_child(parent, "${1}", 0)) { printf("${UPPER}: already attached\n"); return; } /* XXX Look at dev/acpica/acpi_isa.c for use of ISA_ADD_CONFIG() macro. */ /* XXX What is ISA_SET_CONFIG_CALLBACK(parent, child, pnpbios_set_config, 0)? */ for (i = 0; i < MAXHINTS; i++) { ioport = res[i].ioport; irq = res[i].irq; if ((ioport == 0) && (irq == 0)) return; /* We've added all our local hints. */ - child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "${1}", -1); + child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "${1}", + DEVICE_UNIT_ANY); bus_set_resource(child, SYS_RES_IOPORT, 0, ioport, NUMPORTS); bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1); bus_set_resource(child, SYS_RES_DRQ, 0, res[i].drq, 1); bus_set_resource(child, SYS_RES_MEMORY, 0, res[i].mem, MEMSIZE); #if 0 /* * If we wanted to pretend PNP found it * we could do this, and put matching entries * in the PNP table, but I think it's probably too hacky. * As you see, some people have done it though. * Basically EISA (remember that?) would do this I think. */ isa_set_vendorid(child, PNP_EISAID("ESS1888")); isa_set_logicalid(child, PNP_EISAID("ESS1888")); #endif } #if 0 /* * Do some smart probing (e.g. like the lnc driver) * and add a child for each one found. */ #endif return; } /* * The ISA code calls this for each device it knows about, * whether via the PNP code or via the hints etc. * If the device nas no PNP capabilities, remove all the * PNP entries, but keep the call to ISA_PNP_PROBE() * As it will guard against accidentally recognising * foreign hardware. This is because we will be called to check against * ALL PNP hardware. */ static int ${1}_isa_probe (device_t device) { int error; device_t parent = device_get_parent(device); struct ${1}_softc *scp = DEVICE2SOFTC(device); u_long port_start, port_count; bzero(scp, sizeof(*scp)); scp->device = device; /* * Check this device for a PNP match in our table. * There are several possible outcomes. * error == 0 We match a PNP. * error == ENXIO, It is a PNP device but not in our table. * error == ENOENT, It is not a PNP device.. try heuristic probes. * -- logic from if_ed_isa.c, added info from isa/isa_if.m: * * If we had a list of devices that we could handle really well, * and a list which we could handle only basic functions, then * we would call this twice, once for each list, * and return a value of '-2' or something if we could * only handle basic functions. This would allow a specific * Widgetplus driver to make a better offer if it knows how to * do all the extended functions. (See non-pnp part for more info). */ error = ISA_PNP_PROBE(parent, device, ${1}_ids); switch (error) { case 0: /* * We found a PNP device. * Do nothing, as it's all done in attach(). */ break; case ENOENT: /* * Well it didn't show up in the PNP tables * so look directly at known ports (if we have any) * in case we are looking for an old pre-PNP card. * * Hopefully the 'identify' routine will have picked these * up for us first if they use some proprietary detection * method. * * The ports, irqs etc should come from a 'hints' section * which is read in by code in isa/isahint.c * and kern/subr_bus.c to create resource entries, * or have been added by the 'identify routine above. * Note that HINTS based resource requests have NO * SIZE for the memory or ports requests (just a base) * so we may need to 'correct' this before we * do any probing. */ /* * Find out the values of any resources we * need for our dumb probe. Also check we have enough ports * in the request. (could be hints based). * Should probably do the same for memory regions too. */ error = bus_get_resource(device, SYS_RES_IOPORT, 0, &port_start, &port_count); if (port_count != NUMPORTS) { bus_set_resource(device, SYS_RES_IOPORT, 0, port_start, NUMPORTS); } /* * Make a temporary resource reservation. * If we can't get the resources we need then * we need to abort. Possibly this indicates * the resources were used by another device * in which case the probe would have failed anyhow. */ if ((error = (${1}_allocate_resources(device)))) { error = ENXIO; goto errexit; } /* Dummy heuristic type probe. */ if (inb(port_start) != EXPECTED_VALUE) { /* * It isn't what we hoped, so quit looking for it. */ error = ENXIO; } else { u_long membase = bus_get_resource_start(device, SYS_RES_MEMORY, 0 /*rid*/); u_long memsize; /* * If we discover in some way that the device has * XXX bytes of memory window, we can override * or set the memory size in the child resource list. */ memsize = inb(port_start + 1) * 1024; /* for example */ error = bus_set_resource(device, SYS_RES_MEMORY, /*rid*/0, membase, memsize); /* * We found one, return non-positive numbers.. * Return -N if we can't handle it, but not well. * Return -2 if we would LIKE the device. * Return -1 if we want it a lot. * Return 0 if we MUST get the device. * This allows drivers to 'bid' for a device. */ device_set_desc(device, "ACME Widget model 1234"); error = -1; /* We want it but someone else may be even better. */ } /* * Unreserve the resources for now because * another driver may bid for device too. * If we lose the bid, but still hold the resources, we will * effectively have disabled the other driver from getting them * which will result in neither driver getting the device. * We will ask for them again in attach if we win. */ ${1}_deallocate_resources(device); break; case ENXIO: /* It was PNP but not ours, leave immediately. */ default: error = ENXIO; } errexit: return (error); } /* * Called if the probe succeeded and our bid won the device. * We can be destructive here as we know we have the device. * This is the first place we can be sure we have a softc structure. * You would do ISA specific attach things here, but generically there aren't * any (yay new-bus!). */ static int ${1}_isa_attach (device_t device) { int error; struct ${1}_softc *scp = DEVICE2SOFTC(device); error = ${1}_attach(device, scp); if (error) ${1}_isa_detach(device); return (error); } /* * Detach the driver (e.g. module unload), * call the bus independent version * and undo anything we did in the ISA attach routine. */ static int ${1}_isa_detach (device_t device) { int error; struct ${1}_softc *scp = DEVICE2SOFTC(device); error = ${1}_detach(device, scp); return (error); } /* *************************************** * PCI Attachment structures and code *************************************** */ static int ${1}_pci_probe(device_t); static int ${1}_pci_attach(device_t); static int ${1}_pci_detach(device_t); static device_method_t ${1}_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ${1}_pci_probe), DEVMETHOD(device_attach, ${1}_pci_attach), DEVMETHOD(device_detach, ${1}_pci_detach), { 0, 0 } }; static driver_t ${1}_pci_driver = { "${1}", ${1}_pci_methods, sizeof(struct ${1}_softc), }; DRIVER_MODULE(${1}, pci, ${1}_pci_driver, ${1}_devclass, 0, 0); /* * Cardbus is a pci bus plus extra, so use the pci driver unless special * things need to be done only in the cardbus case. */ DRIVER_MODULE(${1}, cardbus, ${1}_pci_driver, ${1}_devclass, 0, 0); static struct _pcsid { u_int32_t type; const char *desc; } pci_ids[] = { { 0x1234abcd, "ACME PCI Widgetplus" }, { 0x1243fedc, "Happy moon brand RIPOFFplus" }, { 0x00000000, NULL } }; /* * See if this card is specifically mentioned in our list of known devices. * Theoretically we might also put in a weak bid for some devices that * report themselves to be some generic type of device if we can handle * that generic type. (other PCI_XXX calls give that info). * This would allow a specific driver to over-ride us. * * See the comments in the ISA section regarding returning non-positive * values from probe routines. */ static int ${1}_pci_probe (device_t device) { u_int32_t type = pci_get_devid(device); struct _pcsid *ep =pci_ids; while (ep->type && ep->type != type) ++ep; if (ep->desc) { device_set_desc(device, ep->desc); return 0; /* If there might be a better driver, return -2 */ } else return ENXIO; } static int ${1}_pci_attach(device_t device) { int error; struct ${1}_softc *scp = DEVICE2SOFTC(device); error = ${1}_attach(device, scp); if (error) ${1}_pci_detach(device); return (error); } static int ${1}_pci_detach (device_t device) { int error; struct ${1}_softc *scp = DEVICE2SOFTC(device); error = ${1}_detach(device, scp); return (error); } /* **************************************** * Common Attachment sub-functions **************************************** */ static int ${1}_attach(device_t device, struct ${1}_softc * scp) { device_t parent = device_get_parent(device); int unit = device_get_unit(device); scp->dev = make_dev(&${1}_cdevsw, 0, UID_ROOT, GID_OPERATOR, 0600, "${1}%d", unit); scp->dev->si_drv1 = scp; if (${1}_allocate_resources(device)) goto errexit; scp->bt = rman_get_bustag(scp->res_ioport); scp->bh = rman_get_bushandle(scp->res_ioport); /* Register the interrupt handler. */ /* * The type should be one of: * INTR_TYPE_TTY * INTR_TYPE_BIO * INTR_TYPE_CAM * INTR_TYPE_NET * INTR_TYPE_MISC * This will probably change with SMPng. INTR_TYPE_FAST may be * OR'd into this type to mark the interrupt fast. However, fast * interrupts cannot be shared at all so special precautions are * necessary when coding fast interrupt routines. */ if (scp->res_irq) { /* Default to the tty mask for registration. */ /* XXX */ if (BUS_SETUP_INTR(parent, device, scp->res_irq, INTR_TYPE_TTY, ${1}intr, scp, &scp->intr_cookie) == 0) { /* Do something if successful. */ } else goto errexit; } /* * If we want to access the memory we will need * to know where it was mapped. * * Use of this function is discouraged, however. You should * be accessing the device with the bus_space API if at all * possible. */ scp->vaddr = rman_get_virtual(scp->res_memory); return 0; errexit: /* * Undo anything we may have done. */ ${1}_detach(device, scp); return (ENXIO); } static int ${1}_detach(device_t device, struct ${1}_softc *scp) { device_t parent = device_get_parent(device); /* * At this point stick a strong piece of wood into the device * to make sure it is stopped safely. The alternative is to * simply REFUSE to detach if it's busy. What you do depends on * your specific situation. * * Sometimes the parent bus will detach you anyway, even if you * are busy. You must cope with that possibility. Your hardware * might even already be gone in the case of cardbus or pccard * devices. */ /* ZAP some register */ /* * Take our interrupt handler out of the list of handlers * that can handle this irq. */ if (scp->intr_cookie != NULL) { if (BUS_TEARDOWN_INTR(parent, device, scp->res_irq, scp->intr_cookie) != 0) printf("intr teardown failed.. continuing\n"); scp->intr_cookie = NULL; } /* * Deallocate any system resources we may have * allocated on behalf of this driver. */ scp->vaddr = NULL; return ${1}_deallocate_resources(device); } static int ${1}_allocate_resources(device_t device) { int error; struct ${1}_softc *scp = DEVICE2SOFTC(device); int size = 16; /* SIZE of port range used. */ scp->res_ioport = bus_alloc_resource(device, SYS_RES_IOPORT, &scp->rid_ioport, 0ul, ~0ul, size, RF_ACTIVE); if (scp->res_ioport == NULL) goto errexit; scp->res_irq = bus_alloc_resource(device, SYS_RES_IRQ, &scp->rid_irq, 0ul, ~0ul, 1, RF_SHAREABLE|RF_ACTIVE); if (scp->res_irq == NULL) goto errexit; scp->res_drq = bus_alloc_resource(device, SYS_RES_DRQ, &scp->rid_drq, 0ul, ~0ul, 1, RF_ACTIVE); if (scp->res_drq == NULL) goto errexit; scp->res_memory = bus_alloc_resource(device, SYS_RES_MEMORY, &scp->rid_memory, 0ul, ~0ul, MSIZE, RF_ACTIVE); if (scp->res_memory == NULL) goto errexit; return (0); errexit: error = ENXIO; /* Cleanup anything we may have assigned. */ ${1}_deallocate_resources(device); return (ENXIO); /* For want of a better idea. */ } static int ${1}_deallocate_resources(device_t device) { struct ${1}_softc *scp = DEVICE2SOFTC(device); if (scp->res_irq != 0) { bus_deactivate_resource(device, SYS_RES_IRQ, scp->rid_irq, scp->res_irq); bus_release_resource(device, SYS_RES_IRQ, scp->rid_irq, scp->res_irq); scp->res_irq = 0; } if (scp->res_ioport != 0) { bus_deactivate_resource(device, SYS_RES_IOPORT, scp->rid_ioport, scp->res_ioport); bus_release_resource(device, SYS_RES_IOPORT, scp->rid_ioport, scp->res_ioport); scp->res_ioport = 0; } if (scp->res_memory != 0) { bus_deactivate_resource(device, SYS_RES_MEMORY, scp->rid_memory, scp->res_memory); bus_release_resource(device, SYS_RES_MEMORY, scp->rid_memory, scp->res_memory); scp->res_memory = 0; } if (scp->res_drq != 0) { bus_deactivate_resource(device, SYS_RES_DRQ, scp->rid_drq, scp->res_drq); bus_release_resource(device, SYS_RES_DRQ, scp->rid_drq, scp->res_drq); scp->res_drq = 0; } if (scp->dev) destroy_dev(scp->dev); return (0); } static void ${1}intr(void *arg) { struct ${1}_softc *scp = (struct ${1}_softc *) arg; /* * Well we got an interrupt, now what? * * Make sure that the interrupt routine will always terminate, * even in the face of "bogus" data from the card. */ (void)scp; /* Delete this line after using scp. */ return; } static int ${1}ioctl (struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) { struct ${1}_softc *scp = DEV2SOFTC(dev); (void)scp; /* Delete this line after using scp. */ switch (cmd) { case DHIOCRESET: /* Whatever resets it. */ #if 0 ${UPPER}_OUTB(SOME_PORT, 0xff); #endif break; default: return ENXIO; } return (0); } /* * You also need read, write, open, close routines. * This should get you started. */ static int ${1}open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct ${1}_softc *scp = DEV2SOFTC(dev); /* * Do processing. */ (void)scp; /* Delete this line after using scp. */ return (0); } static int ${1}close(struct cdev *dev, int fflag, int devtype, struct thread *td) { struct ${1}_softc *scp = DEV2SOFTC(dev); /* * Do processing. */ (void)scp; /* Delete this line after using scp. */ return (0); } static int ${1}read(struct cdev *dev, struct uio *uio, int ioflag) { struct ${1}_softc *scp = DEV2SOFTC(dev); int toread; /* * Do processing. * Read from buffer. */ (void)scp; /* Delete this line after using scp. */ toread = (min(uio->uio_resid, sizeof(scp->buffer))); return(uiomove(scp->buffer, toread, uio)); } static int ${1}write(struct cdev *dev, struct uio *uio, int ioflag) { struct ${1}_softc *scp = DEV2SOFTC(dev); int towrite; /* * Do processing. * Write to buffer. */ (void)scp; /* Delete this line after using scp. */ towrite = (min(uio->uio_resid, sizeof(scp->buffer))); return(uiomove(scp->buffer, towrite, uio)); } static int ${1}mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) { struct ${1}_softc *scp = DEV2SOFTC(dev); /* * Given a byte offset into your device, return the PHYSICAL * page number that it would map to. */ (void)scp; /* Delete this line after using scp. */ #if 0 /* If we had a frame buffer or whatever... do this. */ if (offset > FRAMEBUFFERSIZE - PAGE_SIZE) return (-1); return i386_btop((FRAMEBASE + offset)); #else return (-1); #endif } static int ${1}poll(struct cdev *dev, int which, struct thread *td) { struct ${1}_softc *scp = DEV2SOFTC(dev); /* * Do processing. */ (void)scp; /* Delete this line after using scp. */ return (0); /* This is the wrong value I'm sure. */ } DONE cat >${TOP}/sys/${1}io.h < #endif #include /* * Define an ioctl here. */ #define DHIOCRESET _IO('D', 0) /* Reset the ${1} device. */ #endif DONE if [ ! -d ${TOP}/modules/${1} ]; then mkdir -p ${TOP}/modules/${1} fi cat >${TOP}/modules/${1}/Makefile < opt_inet.h .include DONE echo -n "Do you want to build the '${1}' module? [Y]" read VAL if [ "-z" "$VAL" ]; then VAL=YES fi case ${VAL} in [yY]*) (cd ${TOP}/modules/${1}; make depend; make ) ;; *) # exit ;; esac echo "" echo -n "Do you want to build the '${UPPER}' kernel? [Y]" read VAL if [ "-z" "$VAL" ]; then VAL=YES fi case ${VAL} in [yY]*) ( cd ${TOP}/i386/conf; \ config ${UPPER}; \ cd ${TOP}/i386/compile/${UPPER}; \ make depend; \ make; \ ) ;; *) # exit ;; esac #--------------end of script--------------- # # Edit to your taste... # # diff --git a/stand/kshim/bsd_kernel.c b/stand/kshim/bsd_kernel.c index 91ca46e18d74..455ae570d8ae 100644 --- a/stand/kshim/bsd_kernel.c +++ b/stand/kshim/bsd_kernel.c @@ -1,1447 +1,1448 @@ /*- * Copyright (c) 2013 Hans Petter Selasky. 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include struct usb_process usb_process[USB_PROC_MAX]; static device_t usb_pci_root; int (*bus_alloc_resource_any_cb)(struct resource *res, device_t dev, int type, int *rid, unsigned int flags); int (*ofw_bus_status_ok_cb)(device_t dev); int (*ofw_bus_is_compatible_cb)(device_t dev, char *name); /*------------------------------------------------------------------------* * Implementation of busdma API *------------------------------------------------------------------------*/ int bus_dma_tag_create(bus_dma_tag_t parent, bus_size_t alignment, bus_size_t boundary, bus_addr_t lowaddr, bus_addr_t highaddr, bus_dma_filter_t *filter, void *filterarg, bus_size_t maxsize, int nsegments, bus_size_t maxsegsz, int flags, bus_dma_lock_t *lockfunc, void *lockfuncarg, bus_dma_tag_t *dmat) { struct bus_dma_tag *ret; ret = malloc(sizeof(struct bus_dma_tag), XXX, XXX); if (*dmat == NULL) return (ENOMEM); ret->alignment = alignment; ret->maxsize = maxsize; *dmat = ret; return (0); } int bus_dmamem_alloc(bus_dma_tag_t dmat, void** vaddr, int flags, bus_dmamap_t *mapp) { void *addr; addr = malloc(dmat->maxsize + dmat->alignment, XXX, XXX); if (addr == NULL) return (ENOMEM); *mapp = addr; addr = (void*)(((uintptr_t)addr + dmat->alignment - 1) & ~(dmat->alignment - 1)); *vaddr = addr; return (0); } int bus_dmamap_load(bus_dma_tag_t dmat, bus_dmamap_t map, void *buf, bus_size_t buflen, bus_dmamap_callback_t *callback, void *callback_arg, int flags) { bus_dma_segment_t segs[1]; segs[0].ds_addr = (uintptr_t)buf; segs[0].ds_len = buflen; (*callback)(callback_arg, segs, 1, 0); return (0); } void bus_dmamap_sync(bus_dma_tag_t dmat, bus_dmamap_t map, int flags) { /* Assuming coherent memory */ __asm__ __volatile__("": : :"memory"); } void bus_dmamem_free(bus_dma_tag_t dmat, void *vaddr, bus_dmamap_t map) { free(map, XXX); } int bus_dma_tag_destroy(bus_dma_tag_t dmat) { free(dmat, XXX); return (0); } /*------------------------------------------------------------------------* * Implementation of resource management API *------------------------------------------------------------------------*/ struct resource * bus_alloc_resource_any(device_t dev, int type, int *rid, unsigned int flags) { struct resource *res; int ret = EINVAL; res = malloc(sizeof(*res), XXX, XXX); if (res == NULL) return (NULL); res->__r_i = malloc(sizeof(struct resource_i), XXX, XXX); if (res->__r_i == NULL) { free(res, XXX); return (NULL); } if (bus_alloc_resource_any_cb != NULL) ret = (*bus_alloc_resource_any_cb)(res, dev, type, rid, flags); if (ret == 0) return (res); free(res->__r_i, XXX); free(res, XXX); return (NULL); } int bus_alloc_resources(device_t dev, struct resource_spec *rs, struct resource **res) { int i; for (i = 0; rs[i].type != -1; i++) res[i] = NULL; for (i = 0; rs[i].type != -1; i++) { res[i] = bus_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bus_release_resources(dev, rs, res); return (ENXIO); } } return (0); } void bus_release_resources(device_t dev, const struct resource_spec *rs, struct resource **res) { int i; for (i = 0; rs[i].type != -1; i++) if (res[i] != NULL) { bus_release_resource( dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } int bus_setup_intr(device_t dev, struct resource *r, int flags, driver_filter_t filter, driver_intr_t handler, void *arg, void **cookiep) { dev->dev_irq_filter = filter; dev->dev_irq_fn = handler; dev->dev_irq_arg = arg; return (0); } int bus_teardown_intr(device_t dev, struct resource *r, void *cookie) { dev->dev_irq_filter = NULL; dev->dev_irq_fn = NULL; dev->dev_irq_arg = NULL; return (0); } int bus_release_resource(device_t dev, int type, int rid, struct resource *r) { /* Resource releasing is not supported */ return (EINVAL); } void bus_attach_children(device_t dev) { device_t child; TAILQ_FOREACH(child, &dev->dev_children, dev_link) { device_probe_and_attach(child); } } bus_space_tag_t rman_get_bustag(struct resource *r) { return (r->r_bustag); } bus_space_handle_t rman_get_bushandle(struct resource *r) { return (r->r_bushandle); } u_long rman_get_size(struct resource *r) { return (r->__r_i->r_end - r->__r_i->r_start + 1); } int ofw_bus_status_okay(device_t dev) { if (ofw_bus_status_ok_cb == NULL) return (0); return ((*ofw_bus_status_ok_cb)(dev)); } int ofw_bus_is_compatible(device_t dev, char *name) { if (ofw_bus_is_compatible_cb == NULL) return (0); return ((*ofw_bus_is_compatible_cb)(dev, name)); } /*------------------------------------------------------------------------* * Implementation of mutex API *------------------------------------------------------------------------*/ struct mtx Giant; static void mtx_system_init(void *arg) { mtx_init(&Giant, "Giant", NULL, MTX_DEF | MTX_RECURSE); } SYSINIT(mtx_system_init, SI_SUB_LOCK, SI_ORDER_MIDDLE, mtx_system_init, NULL); void mtx_init(struct mtx *mtx, const char *name, const char *type, int opt) { mtx->owned = 0; mtx->parent = mtx; } void mtx_lock(struct mtx *mtx) { mtx = mtx->parent; mtx->owned++; } void mtx_unlock(struct mtx *mtx) { mtx = mtx->parent; mtx->owned--; } int mtx_owned(struct mtx *mtx) { mtx = mtx->parent; return (mtx->owned != 0); } void mtx_destroy(struct mtx *mtx) { /* NOP */ } /*------------------------------------------------------------------------* * Implementation of shared/exclusive mutex API *------------------------------------------------------------------------*/ void sx_init_flags(struct sx *sx, const char *name, int flags) { sx->owned = 0; } void sx_destroy(struct sx *sx) { /* NOP */ } void sx_xlock(struct sx *sx) { sx->owned++; } void sx_xunlock(struct sx *sx) { sx->owned--; } int sx_xlocked(struct sx *sx) { return (sx->owned != 0); } /*------------------------------------------------------------------------* * Implementaiton of condition variable API *------------------------------------------------------------------------*/ void cv_init(struct cv *cv, const char *desc) { cv->sleeping = 0; } void cv_destroy(struct cv *cv) { /* NOP */ } void cv_wait(struct cv *cv, struct mtx *mtx) { cv_timedwait(cv, mtx, -1); } int cv_timedwait(struct cv *cv, struct mtx *mtx, int timo) { int start = ticks; int delta; int time = 0; if (cv->sleeping) return (EWOULDBLOCK); /* not allowed */ cv->sleeping = 1; while (cv->sleeping) { if (timo >= 0) { delta = ticks - start; if (delta >= timo || delta < 0) break; } mtx_unlock(mtx); usb_idle(); if (++time >= (1000000 / hz)) { time = 0; callout_process(1); } /* Sleep for 1 us */ delay(1); mtx_lock(mtx); } if (cv->sleeping) { cv->sleeping = 0; return (EWOULDBLOCK); /* not allowed */ } return (0); } void cv_signal(struct cv *cv) { cv->sleeping = 0; } void cv_broadcast(struct cv *cv) { cv->sleeping = 0; } /*------------------------------------------------------------------------* * Implementation of callout API *------------------------------------------------------------------------*/ static void callout_proc_msg(struct usb_proc_msg *); volatile int ticks = 0; static LIST_HEAD(, callout) head_callout = LIST_HEAD_INITIALIZER(&head_callout); static struct mtx mtx_callout; static struct usb_proc_msg callout_msg[2]; static void callout_system_init(void *arg) { mtx_init(&mtx_callout, "callout-mtx", NULL, MTX_DEF | MTX_RECURSE); callout_msg[0].pm_callback = &callout_proc_msg; callout_msg[1].pm_callback = &callout_proc_msg; } SYSINIT(callout_system_init, SI_SUB_LOCK, SI_ORDER_MIDDLE, callout_system_init, NULL); static void callout_callback(struct callout *c) { mtx_lock(c->mtx); mtx_lock(&mtx_callout); if (c->entry.le_prev != NULL) { LIST_REMOVE(c, entry); c->entry.le_prev = NULL; } mtx_unlock(&mtx_callout); if (c->c_func != NULL) (c->c_func) (c->c_arg); if (!(c->flags & CALLOUT_RETURNUNLOCKED)) mtx_unlock(c->mtx); } void callout_process(int timeout) { ticks += timeout; usb_proc_msignal(usb_process + 2, &callout_msg[0], &callout_msg[1]); } static void callout_proc_msg(struct usb_proc_msg *pmsg) { struct callout *c; int delta; repeat: mtx_lock(&mtx_callout); LIST_FOREACH(c, &head_callout, entry) { delta = c->timeout - ticks; if (delta < 0) { mtx_unlock(&mtx_callout); callout_callback(c); goto repeat; } } mtx_unlock(&mtx_callout); } void callout_init_mtx(struct callout *c, struct mtx *mtx, int flags) { memset(c, 0, sizeof(*c)); if (mtx == NULL) mtx = &Giant; c->mtx = mtx; c->flags = (flags & CALLOUT_RETURNUNLOCKED); } void callout_reset(struct callout *c, int to_ticks, void (*func) (void *), void *arg) { callout_stop(c); c->c_func = func; c->c_arg = arg; c->timeout = ticks + to_ticks; mtx_lock(&mtx_callout); LIST_INSERT_HEAD(&head_callout, c, entry); mtx_unlock(&mtx_callout); } void callout_stop(struct callout *c) { mtx_lock(&mtx_callout); if (c->entry.le_prev != NULL) { LIST_REMOVE(c, entry); c->entry.le_prev = NULL; } mtx_unlock(&mtx_callout); c->c_func = NULL; c->c_arg = NULL; } void callout_drain(struct callout *c) { if (c->mtx == NULL) return; /* not initialised */ mtx_lock(c->mtx); callout_stop(c); mtx_unlock(c->mtx); } int callout_pending(struct callout *c) { int retval; mtx_lock(&mtx_callout); retval = (c->entry.le_prev != NULL); mtx_unlock(&mtx_callout); return (retval); } /*------------------------------------------------------------------------* * Implementation of device API *------------------------------------------------------------------------*/ static const char unknown_string[] = { "unknown" }; static TAILQ_HEAD(, module_data) module_head = TAILQ_HEAD_INITIALIZER(module_head); static TAILQ_HEAD(, devclass) devclasses = TAILQ_HEAD_INITIALIZER(devclasses); int bus_generic_resume(device_t dev) { return (0); } int bus_generic_shutdown(device_t dev) { return (0); } int bus_generic_suspend(device_t dev) { return (0); } int bus_generic_print_child(device_t dev, device_t child) { return (0); } void bus_generic_driver_added(device_t dev, driver_t *driver) { return; } device_t device_get_parent(device_t dev) { return (dev ? dev->dev_parent : NULL); } void device_set_interrupt(device_t dev, driver_filter_t *filter, driver_intr_t *fn, void *arg) { dev->dev_irq_filter = filter; dev->dev_irq_fn = fn; dev->dev_irq_arg = arg; } void device_run_interrupts(device_t parent) { device_t child; if (parent == NULL) return; TAILQ_FOREACH(child, &parent->dev_children, dev_link) { int status; if (child->dev_irq_filter != NULL) status = child->dev_irq_filter(child->dev_irq_arg); else status = FILTER_SCHEDULE_THREAD; if (status == FILTER_SCHEDULE_THREAD) { if (child->dev_irq_fn != NULL) (child->dev_irq_fn) (child->dev_irq_arg); } } } void device_set_ivars(device_t dev, void *ivars) { dev->dev_aux = ivars; } void * device_get_ivars(device_t dev) { return (dev ? dev->dev_aux : NULL); } int device_get_unit(device_t dev) { return (dev ? dev->dev_unit : 0); } int bus_detach_children(device_t dev) { device_t child; int error; if (!dev->dev_attached) return (EBUSY); TAILQ_FOREACH(child, &dev->dev_children, dev_link) { if ((error = device_detach(child)) != 0) return (error); } return (0); } int bus_generic_detach(device_t dev) { int error; error = bus_detach_children(dev); if (error == 0) error = device_delete_children(dev); return (error); } const char * device_get_nameunit(device_t dev) { if (dev && dev->dev_nameunit[0]) return (dev->dev_nameunit); return (unknown_string); } static devclass_t devclass_create(const char *classname) { devclass_t dc; dc = malloc(sizeof(*dc), M_DEVBUF, M_WAITOK | M_ZERO); if (dc == NULL) { return (NULL); } dc->name = classname; TAILQ_INSERT_TAIL(&devclasses, dc, link); return (dc); } static devclass_t devclass_find_create(const char *classname) { devclass_t dc; dc = devclass_find(classname); if (dc == NULL) dc = devclass_create(classname); return (dc); } static uint8_t devclass_add_device(devclass_t dc, device_t dev) { device_t *pp_dev; device_t *end; uint8_t unit; pp_dev = dc->dev_list; end = pp_dev + DEVCLASS_MAXUNIT; unit = 0; while (pp_dev != end) { if (*pp_dev == NULL) { *pp_dev = dev; dev->dev_class = dc; dev->dev_unit = unit; snprintf(dev->dev_nameunit, sizeof(dev->dev_nameunit), "%s%d", dc->name, unit); return (0); } pp_dev++; unit++; } DPRINTF("Could not add device to devclass.\n"); return (1); } static void devclass_delete_device(devclass_t dc, device_t dev) { if (dc == NULL) { return; } dc->dev_list[dev->dev_unit] = NULL; dev->dev_class = NULL; } static device_t make_device(device_t parent, const char *name) { device_t dev = NULL; devclass_t dc = NULL; if (name) { dc = devclass_find_create(name); if (!dc) { DPRINTF("%s:%d:%s: can't find device " "class %s\n", __FILE__, __LINE__, __FUNCTION__, name); goto done; } } dev = malloc(sizeof(*dev), M_DEVBUF, M_WAITOK | M_ZERO); if (dev == NULL) goto done; dev->dev_parent = parent; TAILQ_INIT(&dev->dev_children); if (name) { dev->dev_fixed_class = 1; if (devclass_add_device(dc, dev)) { goto error; } } done: return (dev); error: if (dev) { free(dev, M_DEVBUF); } return (NULL); } device_t device_add_child(device_t dev, const char *name, int unit) { device_t child; if (unit != -1) { device_printf(dev, "Unit is not -1\n"); } child = make_device(dev, name); if (child == NULL) { device_printf(dev, "Could not add child '%s'\n", name); goto done; } if (dev == NULL) { /* no parent */ goto done; } TAILQ_INSERT_TAIL(&dev->dev_children, child, dev_link); done: return (child); } int device_delete_child(device_t dev, device_t child) { int error = 0; device_t grandchild; /* detach parent before deleting children, if any */ error = device_detach(child); if (error) goto done; /* remove children second */ while ((grandchild = TAILQ_FIRST(&child->dev_children))) { error = device_delete_child(child, grandchild); if (error) { device_printf(dev, "Error deleting child!\n"); goto done; } } if (child->dev_class != NULL) devclass_delete_device(child->dev_class, child); if (dev != NULL) { /* remove child from parent */ TAILQ_REMOVE(&dev->dev_children, child, dev_link); } free(child, M_DEVBUF); done: return (error); } int device_delete_children(device_t dev) { device_t child; int error = 0; while ((child = TAILQ_FIRST(&dev->dev_children))) { error = device_delete_child(dev, child); if (error) { device_printf(dev, "Error deleting child!\n"); break; } } return (error); } void device_quiet(device_t dev) { dev->dev_quiet = 1; } const char * device_get_desc(device_t dev) { if (dev) return &(dev->dev_desc[0]); return (unknown_string); } static int default_method(void) { /* do nothing */ DPRINTF("Default method called\n"); return (0); } void * device_get_method(device_t dev, const char *what) { const struct device_method *mtod; mtod = dev->dev_module->driver->methods; while (mtod->func != NULL) { if (strcmp(mtod->desc, what) == 0) { return (mtod->func); } mtod++; } return ((void *)&default_method); } const char * device_get_name(device_t dev) { if (dev == NULL || dev->dev_module == NULL) return (unknown_string); return (dev->dev_module->driver->name); } static int device_allocate_softc(device_t dev) { const struct module_data *mod; mod = dev->dev_module; if ((dev->dev_softc_alloc == 0) && (mod->driver->size != 0)) { dev->dev_sc = malloc(mod->driver->size, M_DEVBUF, M_WAITOK | M_ZERO); if (dev->dev_sc == NULL) return (ENOMEM); dev->dev_softc_alloc = 1; } return (0); } int device_probe_and_attach(device_t dev) { const struct module_data *mod; const char *bus_name_parent; devclass_t dc; if (dev->dev_attached) return (0); /* fail-safe */ /* * Find a module for our device, if any */ bus_name_parent = device_get_name(device_get_parent(dev)); TAILQ_FOREACH(mod, &module_head, entry) { if (strcmp(mod->bus_name, bus_name_parent) != 0) continue; dc = devclass_find(mod->mod_name); /* Does this device need assigning to the new devclass? */ if (dev->dev_class != dc) { if (dev->dev_fixed_class) continue; if (dev->dev_class != NULL) devclass_delete_device(dev->dev_class, dev); if (devclass_add_device(dc, dev)) { continue; } } dev->dev_module = mod; if (DEVICE_PROBE(dev) <= 0) { if (device_allocate_softc(dev) == 0) { if (DEVICE_ATTACH(dev) == 0) { /* success */ dev->dev_attached = 1; return (0); } } } /* else try next driver */ device_detach(dev); } return (ENODEV); } int device_detach(device_t dev) { const struct module_data *mod = dev->dev_module; int error; if (dev->dev_attached) { error = DEVICE_DETACH(dev); if (error) { return error; } dev->dev_attached = 0; } device_set_softc(dev, NULL); dev->dev_module = NULL; if (dev->dev_fixed_class == 0) devclass_delete_device(dev->dev_class, dev); return (0); } void device_set_softc(device_t dev, void *softc) { if (dev->dev_softc_alloc) { free(dev->dev_sc, M_DEVBUF); dev->dev_sc = NULL; } dev->dev_sc = softc; dev->dev_softc_alloc = 0; } void * device_get_softc(device_t dev) { if (dev == NULL) return (NULL); return (dev->dev_sc); } int device_is_attached(device_t dev) { return (dev->dev_attached); } void device_set_desc(device_t dev, const char *desc) { snprintf(dev->dev_desc, sizeof(dev->dev_desc), "%s", desc); } void device_set_desc_copy(device_t dev, const char *desc) { device_set_desc(dev, desc); } void * devclass_get_softc(devclass_t dc, int unit) { return (device_get_softc(devclass_get_device(dc, unit))); } int devclass_get_maxunit(devclass_t dc) { int max_unit = 0; if (dc) { max_unit = DEVCLASS_MAXUNIT; while (max_unit--) { if (dc->dev_list[max_unit]) { break; } } max_unit++; } return (max_unit); } device_t devclass_get_device(devclass_t dc, int unit) { return (((unit < 0) || (unit >= DEVCLASS_MAXUNIT) || (dc == NULL)) ? NULL : dc->dev_list[unit]); } devclass_t devclass_find(const char *classname) { devclass_t dc; TAILQ_FOREACH(dc, &devclasses, link) { if (strcmp(dc->name, classname) == 0) return (dc); } return (NULL); } void module_register(void *data) { struct module_data *mdata = data; TAILQ_INSERT_TAIL(&module_head, mdata, entry); (void)devclass_find_create(mdata->mod_name); } /*------------------------------------------------------------------------* * System startup *------------------------------------------------------------------------*/ static void sysinit_run(const void **ppdata) { const struct sysinit *psys; while ((psys = *ppdata) != NULL) { (psys->func) (psys->data); ppdata++; } } /*------------------------------------------------------------------------* * USB process API *------------------------------------------------------------------------*/ static int usb_do_process(struct usb_process *); static int usb_proc_level = -1; static struct mtx usb_proc_mtx; void usb_idle(void) { int old_level = usb_proc_level; int old_giant = Giant.owned; int worked; device_run_interrupts(usb_pci_root); do { worked = 0; Giant.owned = 0; while (++usb_proc_level < USB_PROC_MAX) worked |= usb_do_process(usb_process + usb_proc_level); usb_proc_level = old_level; Giant.owned = old_giant; } while (worked); } void usb_init(void) { sysinit_run(sysinit_data); } void usb_uninit(void) { sysinit_run(sysuninit_data); } static void usb_process_init_sub(struct usb_process *up) { TAILQ_INIT(&up->up_qhead); cv_init(&up->up_cv, "-"); cv_init(&up->up_drain, "usbdrain"); up->up_mtx = &usb_proc_mtx; } static void usb_process_init(void *arg) { uint8_t x; mtx_init(&usb_proc_mtx, "usb-proc-mtx", NULL, MTX_DEF | MTX_RECURSE); for (x = 0; x != USB_PROC_MAX; x++) usb_process_init_sub(&usb_process[x]); } SYSINIT(usb_process_init, SI_SUB_LOCK, SI_ORDER_MIDDLE, usb_process_init, NULL); static int usb_do_process(struct usb_process *up) { struct usb_proc_msg *pm; int worked = 0; mtx_lock(&usb_proc_mtx); repeat: pm = TAILQ_FIRST(&up->up_qhead); if (pm != NULL) { worked = 1; (pm->pm_callback) (pm); if (pm == TAILQ_FIRST(&up->up_qhead)) { /* nothing changed */ TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry); pm->pm_qentry.tqe_prev = NULL; } goto repeat; } mtx_unlock(&usb_proc_mtx); return (worked); } void * usb_proc_msignal(struct usb_process *up, void *_pm0, void *_pm1) { struct usb_proc_msg *pm0 = _pm0; struct usb_proc_msg *pm1 = _pm1; struct usb_proc_msg *pm2; usb_size_t d; uint8_t t; t = 0; if (pm0->pm_qentry.tqe_prev) { t |= 1; } if (pm1->pm_qentry.tqe_prev) { t |= 2; } if (t == 0) { /* * No entries are queued. Queue "pm0" and use the existing * message number. */ pm2 = pm0; } else if (t == 1) { /* Check if we need to increment the message number. */ if (pm0->pm_num == up->up_msg_num) { up->up_msg_num++; } pm2 = pm1; } else if (t == 2) { /* Check if we need to increment the message number. */ if (pm1->pm_num == up->up_msg_num) { up->up_msg_num++; } pm2 = pm0; } else if (t == 3) { /* * Both entries are queued. Re-queue the entry closest to * the end. */ d = (pm1->pm_num - pm0->pm_num); /* Check sign after subtraction */ if (d & 0x80000000) { pm2 = pm0; } else { pm2 = pm1; } TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry); } else { pm2 = NULL; /* panic - should not happen */ } /* Put message last on queue */ pm2->pm_num = up->up_msg_num; TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry); return (pm2); } /*------------------------------------------------------------------------* * usb_proc_is_gone * * Return values: * 0: USB process is running * Else: USB process is tearing down *------------------------------------------------------------------------*/ uint8_t usb_proc_is_gone(struct usb_process *up) { return (0); } /*------------------------------------------------------------------------* * usb_proc_mwait * * This function will return when the USB process message pointed to * by "pm" is no longer on a queue. This function must be called * having "usb_proc_mtx" locked. *------------------------------------------------------------------------*/ void usb_proc_mwait(struct usb_process *up, void *_pm0, void *_pm1) { struct usb_proc_msg *pm0 = _pm0; struct usb_proc_msg *pm1 = _pm1; /* Just remove the messages from the queue. */ if (pm0->pm_qentry.tqe_prev) { TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry); pm0->pm_qentry.tqe_prev = NULL; } if (pm1->pm_qentry.tqe_prev) { TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry); pm1->pm_qentry.tqe_prev = NULL; } } /*------------------------------------------------------------------------* * SYSTEM attach *------------------------------------------------------------------------*/ #ifdef USB_PCI_PROBE_LIST static device_method_t pci_methods[] = { DEVMETHOD_END }; static driver_t pci_driver = { .name = "pci", .methods = pci_methods, }; static devclass_t pci_devclass; DRIVER_MODULE(pci, pci, pci_driver, pci_devclass, 0, 0); static const char *usb_pci_devices[] = { USB_PCI_PROBE_LIST }; #define USB_PCI_USB_MAX (sizeof(usb_pci_devices) / sizeof(void *)) static device_t usb_pci_dev[USB_PCI_USB_MAX]; static void usb_pci_mod_load(void *arg) { uint32_t x; - usb_pci_root = device_add_child(NULL, "pci", -1); + usb_pci_root = device_add_child(NULL, "pci", DEVICE_UNIT_ANY); if (usb_pci_root == NULL) return; for (x = 0; x != USB_PCI_USB_MAX; x++) { - usb_pci_dev[x] = device_add_child(usb_pci_root, usb_pci_devices[x], -1); + usb_pci_dev[x] = device_add_child(usb_pci_root, + usb_pci_devices[x], DEVICE_UNIT_ANY); if (usb_pci_dev[x] == NULL) continue; if (device_probe_and_attach(usb_pci_dev[x])) { device_printf(usb_pci_dev[x], "WARNING: Probe and attach failed!\n"); } } } SYSINIT(usb_pci_mod_load, SI_SUB_RUN_SCHEDULER, SI_ORDER_MIDDLE, usb_pci_mod_load, 0); static void usb_pci_mod_unload(void *arg) { uint32_t x; for (x = 0; x != USB_PCI_USB_MAX; x++) { if (usb_pci_dev[x]) { device_detach(usb_pci_dev[x]); device_delete_child(usb_pci_root, usb_pci_dev[x]); } } if (usb_pci_root) device_delete_child(NULL, usb_pci_root); } SYSUNINIT(usb_pci_mod_unload, SI_SUB_RUN_SCHEDULER, SI_ORDER_MIDDLE, usb_pci_mod_unload, 0); #endif /*------------------------------------------------------------------------* * MALLOC API *------------------------------------------------------------------------*/ #ifndef HAVE_MALLOC #define USB_POOL_ALIGN 8 static uint8_t usb_pool[USB_POOL_SIZE] __aligned(USB_POOL_ALIGN); static uint32_t usb_pool_rem = USB_POOL_SIZE; static uint32_t usb_pool_entries; struct malloc_hdr { TAILQ_ENTRY(malloc_hdr) entry; uint32_t size; } __aligned(USB_POOL_ALIGN); static TAILQ_HEAD(, malloc_hdr) malloc_head = TAILQ_HEAD_INITIALIZER(malloc_head); void * usb_malloc(unsigned long size) { struct malloc_hdr *hdr; size = (size + USB_POOL_ALIGN - 1) & ~(USB_POOL_ALIGN - 1); size += sizeof(struct malloc_hdr); TAILQ_FOREACH(hdr, &malloc_head, entry) { if (hdr->size == size) break; } if (hdr) { DPRINTF("MALLOC: Entries = %d; Remainder = %d; Size = %d\n", (int)usb_pool_entries, (int)usb_pool_rem, (int)size); TAILQ_REMOVE(&malloc_head, hdr, entry); memset(hdr + 1, 0, hdr->size - sizeof(*hdr)); return (hdr + 1); } if (usb_pool_rem >= size) { hdr = (void *)(usb_pool + USB_POOL_SIZE - usb_pool_rem); hdr->size = size; usb_pool_rem -= size; usb_pool_entries++; DPRINTF("MALLOC: Entries = %d; Remainder = %d; Size = %d\n", (int)usb_pool_entries, (int)usb_pool_rem, (int)size); memset(hdr + 1, 0, hdr->size - sizeof(*hdr)); return (hdr + 1); } return (NULL); } void usb_free(void *arg) { struct malloc_hdr *hdr; if (arg == NULL) return; hdr = arg; hdr--; TAILQ_INSERT_TAIL(&malloc_head, hdr, entry); } #endif char * usb_strdup(const char *str) { char *tmp; int len; len = 1 + strlen(str); tmp = malloc(len,XXX,XXX); if (tmp == NULL) return (NULL); memcpy(tmp, str, len); return (tmp); } diff --git a/sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c b/sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c index 3ef564c6ae13..2bcf6ba8da1e 100644 --- a/sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c +++ b/sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c @@ -1,1579 +1,1579 @@ /*- * Copyright (C) 2013-2015 Daisuke Aoyama * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #ifdef DEBUG #define DPRINTF(fmt, ...) do { \ printf("%s:%u: ", __func__, __LINE__); \ printf(fmt, ##__VA_ARGS__); \ } while (0) #else #define DPRINTF(fmt, ...) #endif #define HZ2MHZ(freq) ((freq) / (1000 * 1000)) #define MHZ2HZ(freq) ((freq) * (1000 * 1000)) #define OFFSET2MVOLT(val) (((val) / 1000)) #define MVOLT2OFFSET(val) (((val) * 1000)) #define DEFAULT_ARM_FREQUENCY 600 #define DEFAULT_LOWEST_FREQ 600 #define DEFAULT_CORE_FREQUENCY 250 #define DEFAULT_SDRAM_FREQUENCY 400 #define TRANSITION_LATENCY 1000 #define MIN_OVER_VOLTAGE -16 #define MAX_OVER_VOLTAGE 6 #define MSG_ERROR -999999999 #define MHZSTEP 100 #define HZSTEP (MHZ2HZ(MHZSTEP)) #define TZ_ZEROC 2731 #define VC_LOCK(sc) do { \ sema_wait(&vc_sema); \ } while (0) #define VC_UNLOCK(sc) do { \ sema_post(&vc_sema); \ } while (0) /* ARM->VC mailbox property semaphore */ static struct sema vc_sema; static struct sysctl_ctx_list bcm2835_sysctl_ctx; struct bcm2835_cpufreq_softc { device_t dev; device_t firmware; int arm_max_freq; int arm_min_freq; int core_max_freq; int core_min_freq; int sdram_max_freq; int sdram_min_freq; int max_voltage_core; int min_voltage_core; /* the values written in mbox */ int voltage_core; int voltage_sdram; int voltage_sdram_c; int voltage_sdram_i; int voltage_sdram_p; int turbo_mode; /* initial hook for waiting mbox intr */ struct intr_config_hook init_hook; }; static struct ofw_compat_data compat_data[] = { { "broadcom,bcm2835-vc", 1 }, { "broadcom,bcm2708-vc", 1 }, { "brcm,bcm2709", 1 }, { "brcm,bcm2835", 1 }, { "brcm,bcm2836", 1 }, { "brcm,bcm2837", 1 }, { "brcm,bcm2711", 1 }, { NULL, 0 } }; static int cpufreq_verbose = 0; TUNABLE_INT("hw.bcm2835.cpufreq.verbose", &cpufreq_verbose); static int cpufreq_lowest_freq = DEFAULT_LOWEST_FREQ; TUNABLE_INT("hw.bcm2835.cpufreq.lowest_freq", &cpufreq_lowest_freq); #ifdef PROP_DEBUG static void bcm2835_dump(const void *data, int len) { const uint8_t *p = (const uint8_t*)data; int i; printf("dump @ %p:\n", data); for (i = 0; i < len; i++) { printf("%2.2x ", p[i]); if ((i % 4) == 3) printf(" "); if ((i % 16) == 15) printf("\n"); } printf("\n"); } #endif static int bcm2835_cpufreq_get_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id) { union msg_get_clock_rate_body msg; int rate; int err; /* * Get clock rate * Tag: 0x00030002 * Request: * Length: 4 * Value: * u32: clock id * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.clock_id = clock_id; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_CLOCK_RATE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* result (Hz) */ rate = (int)msg.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_get_max_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id) { union msg_get_clock_rate_body msg; int rate; int err; /* * Get max clock rate * Tag: 0x00030004 * Request: * Length: 4 * Value: * u32: clock id * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.clock_id = clock_id; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_MAX_CLOCK_RATE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get max clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* result (Hz) */ rate = (int)msg.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_get_min_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id) { union msg_get_clock_rate_body msg; int rate; int err; /* * Get min clock rate * Tag: 0x00030007 * Request: * Length: 4 * Value: * u32: clock id * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.clock_id = clock_id; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_MIN_CLOCK_RATE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get min clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* result (Hz) */ rate = (int)msg.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_set_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id, uint32_t rate_hz) { union msg_set_clock_rate_body msg; int rate; int err; /* * Set clock rate * Tag: 0x00038002 * Request: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.clock_id = clock_id; msg.req.rate_hz = rate_hz; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_SET_CLOCK_RATE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* workaround for core clock */ if (clock_id == BCM2835_FIRMWARE_CLOCK_ID_CORE) { /* for safety (may change voltage without changing clock) */ DELAY(TRANSITION_LATENCY); /* * XXX: the core clock is unable to change at once, * to change certainly, write it twice now. */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.clock_id = clock_id; msg.req.rate_hz = rate_hz; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_SET_CLOCK_RATE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } } /* result (Hz) */ rate = (int)msg.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_get_turbo(struct bcm2835_cpufreq_softc *sc) { union msg_get_turbo_body msg; int level; int err; /* * Get turbo * Tag: 0x00030009 * Request: * Length: 4 * Value: * u32: id * Response: * Length: 8 * Value: * u32: id * u32: level */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.id = 0; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_TURBO, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get turbo\n"); return (MSG_ERROR); } /* result 0=non-turbo, 1=turbo */ level = (int)msg.resp.level; DPRINTF("level = %d\n", level); return (level); } static int bcm2835_cpufreq_set_turbo(struct bcm2835_cpufreq_softc *sc, uint32_t level) { union msg_set_turbo_body msg; int value; int err; /* * Set turbo * Tag: 0x00038009 * Request: * Length: 8 * Value: * u32: id * u32: level * Response: * Length: 8 * Value: * u32: id * u32: level */ /* replace unknown value to OFF */ if (level != BCM2835_FIRMWARE_TURBO_ON && level != BCM2835_FIRMWARE_TURBO_OFF) level = BCM2835_FIRMWARE_TURBO_OFF; /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.id = 0; msg.req.level = level; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_SET_TURBO, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set turbo\n"); return (MSG_ERROR); } /* result 0=non-turbo, 1=turbo */ value = (int)msg.resp.level; DPRINTF("level = %d\n", value); return (value); } static int bcm2835_cpufreq_get_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id) { union msg_get_voltage_body msg; int value; int err; /* * Get voltage * Tag: 0x00030003 * Request: * Length: 4 * Value: * u32: voltage id * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.voltage_id = voltage_id; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_VOLTAGE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_get_max_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id) { union msg_get_voltage_body msg; int value; int err; /* * Get voltage * Tag: 0x00030005 * Request: * Length: 4 * Value: * u32: voltage id * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.voltage_id = voltage_id; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_MAX_VOLTAGE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get max voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_get_min_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id) { union msg_get_voltage_body msg; int value; int err; /* * Get voltage * Tag: 0x00030008 * Request: * Length: 4 * Value: * u32: voltage id * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.voltage_id = voltage_id; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_MIN_VOLTAGE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get min voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_set_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id, int32_t value) { union msg_set_voltage_body msg; int err; /* * Set voltage * Tag: 0x00038003 * Request: * Length: 4 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* * over_voltage: * 0 (1.2 V). Values above 6 are only allowed when force_turbo or * current_limit_override are specified (which set the warranty bit). */ if (value > MAX_OVER_VOLTAGE || value < MIN_OVER_VOLTAGE) { /* currently not supported */ device_printf(sc->dev, "not supported voltage: %d\n", value); return (MSG_ERROR); } /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.voltage_id = voltage_id; msg.req.value = (uint32_t)value; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_SET_VOLTAGE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_get_temperature(struct bcm2835_cpufreq_softc *sc) { union msg_get_temperature_body msg; int value; int err; /* * Get temperature * Tag: 0x00030006 * Request: * Length: 4 * Value: * u32: temperature id * Response: * Length: 8 * Value: * u32: temperature id * u32: value */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.req.temperature_id = 0; /* call mailbox property */ err = bcm2835_firmware_property(sc->firmware, BCM2835_FIRMWARE_TAG_GET_TEMPERATURE, &msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get temperature\n"); return (MSG_ERROR); } /* result (temperature of degree C) */ value = (int)msg.resp.value; DPRINTF("value = %d\n", value); return (value); } static int sysctl_bcm2835_cpufreq_arm_freq(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ VC_LOCK(sc); err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM, val); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set clock arm_freq error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_core_freq(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ VC_LOCK(sc); err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set clock core_freq error\n"); return (EIO); } VC_UNLOCK(sc); DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_sdram_freq(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ VC_LOCK(sc); err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM, val); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set clock sdram_freq error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_turbo(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_turbo(sc); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > 0) sc->turbo_mode = BCM2835_FIRMWARE_TURBO_ON; else sc->turbo_mode = BCM2835_FIRMWARE_TURBO_OFF; VC_LOCK(sc); err = bcm2835_cpufreq_set_turbo(sc, sc->turbo_mode); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set turbo error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_core(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_CORE); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_core = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_CORE, sc->voltage_core); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage core error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram_c(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_C); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram_c = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_C, sc->voltage_sdram_c); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage sdram_c error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram_i(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_I); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram_i = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_I, sc->voltage_sdram_i); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage sdram_i error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram_p(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_P); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram_p = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_P, sc->voltage_sdram_p); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage sdram_p error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* multiple write only */ if (!req->newptr) return (EINVAL); val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err) return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_C, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set voltage sdram_c error\n"); return (EIO); } err = bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_I, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set voltage sdram_i error\n"); return (EIO); } err = bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_P, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set voltage sdram_p error\n"); return (EIO); } VC_UNLOCK(sc); DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_temperature(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_temperature(sc); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ return (EINVAL); } static int sysctl_bcm2835_devcpu_temperature(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_temperature(sc); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); /* 1/1000 celsius (raw) to 1/10 kelvin */ val = val / 100 + TZ_ZEROC; err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ return (EINVAL); } static void bcm2835_cpufreq_init(void *arg) { struct bcm2835_cpufreq_softc *sc = arg; struct sysctl_ctx_list *ctx; device_t cpu; int arm_freq, core_freq, sdram_freq; int arm_max_freq, arm_min_freq, core_max_freq, core_min_freq; int sdram_max_freq, sdram_min_freq; int voltage_core, voltage_sdram_c, voltage_sdram_i, voltage_sdram_p; int max_voltage_core, min_voltage_core; int max_voltage_sdram_c, min_voltage_sdram_c; int max_voltage_sdram_i, min_voltage_sdram_i; int max_voltage_sdram_p, min_voltage_sdram_p; int turbo, temperature; VC_LOCK(sc); /* current clock */ arm_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM); core_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE); sdram_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM); /* max/min clock */ arm_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM); arm_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM); core_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE); core_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE); sdram_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM); sdram_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM); /* turbo mode */ turbo = bcm2835_cpufreq_get_turbo(sc); if (turbo > 0) sc->turbo_mode = BCM2835_FIRMWARE_TURBO_ON; else sc->turbo_mode = BCM2835_FIRMWARE_TURBO_OFF; /* voltage */ voltage_core = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_CORE); voltage_sdram_c = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_C); voltage_sdram_i = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_I); voltage_sdram_p = bcm2835_cpufreq_get_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_P); /* current values (offset from 1.2V) */ sc->voltage_core = voltage_core; sc->voltage_sdram = voltage_sdram_c; sc->voltage_sdram_c = voltage_sdram_c; sc->voltage_sdram_i = voltage_sdram_i; sc->voltage_sdram_p = voltage_sdram_p; /* max/min voltage */ max_voltage_core = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_CORE); min_voltage_core = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_CORE); max_voltage_sdram_c = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_C); max_voltage_sdram_i = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_I); max_voltage_sdram_p = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_P); min_voltage_sdram_c = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_C); min_voltage_sdram_i = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_I); min_voltage_sdram_p = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_SDRAM_P); /* temperature */ temperature = bcm2835_cpufreq_get_temperature(sc); /* show result */ if (cpufreq_verbose || bootverbose) { device_printf(sc->dev, "Boot settings:\n"); device_printf(sc->dev, "current ARM %dMHz, Core %dMHz, SDRAM %dMHz, Turbo %s\n", HZ2MHZ(arm_freq), HZ2MHZ(core_freq), HZ2MHZ(sdram_freq), (sc->turbo_mode == BCM2835_FIRMWARE_TURBO_ON) ? "ON":"OFF"); device_printf(sc->dev, "max/min ARM %d/%dMHz, Core %d/%dMHz, SDRAM %d/%dMHz\n", HZ2MHZ(arm_max_freq), HZ2MHZ(arm_min_freq), HZ2MHZ(core_max_freq), HZ2MHZ(core_min_freq), HZ2MHZ(sdram_max_freq), HZ2MHZ(sdram_min_freq)); device_printf(sc->dev, "current Core %dmV, SDRAM_C %dmV, SDRAM_I %dmV, " "SDRAM_P %dmV\n", OFFSET2MVOLT(voltage_core), OFFSET2MVOLT(voltage_sdram_c), OFFSET2MVOLT(voltage_sdram_i), OFFSET2MVOLT(voltage_sdram_p)); device_printf(sc->dev, "max/min Core %d/%dmV, SDRAM_C %d/%dmV, SDRAM_I %d/%dmV, " "SDRAM_P %d/%dmV\n", OFFSET2MVOLT(max_voltage_core), OFFSET2MVOLT(min_voltage_core), OFFSET2MVOLT(max_voltage_sdram_c), OFFSET2MVOLT(min_voltage_sdram_c), OFFSET2MVOLT(max_voltage_sdram_i), OFFSET2MVOLT(min_voltage_sdram_i), OFFSET2MVOLT(max_voltage_sdram_p), OFFSET2MVOLT(min_voltage_sdram_p)); device_printf(sc->dev, "Temperature %d.%dC\n", (temperature / 1000), (temperature % 1000) / 100); } else { /* !cpufreq_verbose && !bootverbose */ device_printf(sc->dev, "ARM %dMHz, Core %dMHz, SDRAM %dMHz, Turbo %s\n", HZ2MHZ(arm_freq), HZ2MHZ(core_freq), HZ2MHZ(sdram_freq), (sc->turbo_mode == BCM2835_FIRMWARE_TURBO_ON) ? "ON":"OFF"); } /* keep in softc (MHz/mV) */ sc->arm_max_freq = HZ2MHZ(arm_max_freq); sc->arm_min_freq = HZ2MHZ(arm_min_freq); sc->core_max_freq = HZ2MHZ(core_max_freq); sc->core_min_freq = HZ2MHZ(core_min_freq); sc->sdram_max_freq = HZ2MHZ(sdram_max_freq); sc->sdram_min_freq = HZ2MHZ(sdram_min_freq); sc->max_voltage_core = OFFSET2MVOLT(max_voltage_core); sc->min_voltage_core = OFFSET2MVOLT(min_voltage_core); /* if turbo is on, set to max values */ if (sc->turbo_mode == BCM2835_FIRMWARE_TURBO_ON) { bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM, arm_max_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE, core_max_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM, sdram_max_freq); DELAY(TRANSITION_LATENCY); } else { bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM, arm_min_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE, core_min_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM, sdram_min_freq); DELAY(TRANSITION_LATENCY); } VC_UNLOCK(sc); /* add human readable temperature to dev.cpu node */ cpu = device_get_parent(sc->dev); if (cpu != NULL) { ctx = device_get_sysctl_ctx(cpu); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_devcpu_temperature, "IK", "Current SoC temperature"); } /* release this hook (continue boot) */ config_intrhook_disestablish(&sc->init_hook); } static void bcm2835_cpufreq_identify(driver_t *driver, device_t parent) { const struct ofw_compat_data *compat; phandle_t root; root = OF_finddevice("/"); for (compat = compat_data; compat->ocd_str != NULL; compat++) if (ofw_bus_node_is_compatible(root, compat->ocd_str)) break; if (compat->ocd_data == 0) return; DPRINTF("driver=%p, parent=%p\n", driver, parent); - if (device_find_child(parent, "bcm2835_cpufreq", -1) != NULL) + if (device_find_child(parent, "bcm2835_cpufreq", DEVICE_UNIT_ANY) != NULL) return; - if (BUS_ADD_CHILD(parent, 0, "bcm2835_cpufreq", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "bcm2835_cpufreq", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int bcm2835_cpufreq_probe(device_t dev) { if (device_get_unit(dev) != 0) return (ENXIO); device_set_desc(dev, "CPU Frequency Control"); return (0); } static int bcm2835_cpufreq_attach(device_t dev) { struct bcm2835_cpufreq_softc *sc; struct sysctl_oid *oid; /* set self dev */ sc = device_get_softc(dev); sc->dev = dev; sc->firmware = devclass_get_device( devclass_find("bcm2835_firmware"), 0); if (sc->firmware == NULL) { device_printf(dev, "Unable to find firmware device\n"); return (ENXIO); } /* initial values */ sc->arm_max_freq = -1; sc->arm_min_freq = -1; sc->core_max_freq = -1; sc->core_min_freq = -1; sc->sdram_max_freq = -1; sc->sdram_min_freq = -1; sc->max_voltage_core = 0; sc->min_voltage_core = 0; /* setup sysctl at first device */ if (device_get_unit(dev) == 0) { sysctl_ctx_init(&bcm2835_sysctl_ctx); /* create node for hw.cpufreq */ oid = SYSCTL_ADD_NODE(&bcm2835_sysctl_ctx, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "cpufreq", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); /* Frequency (Hz) */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "arm_freq", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_arm_freq, "IU", "ARM frequency (Hz)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "core_freq", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_core_freq, "IU", "Core frequency (Hz)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "sdram_freq", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_sdram_freq, "IU", "SDRAM frequency (Hz)"); /* Turbo state */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "turbo", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_turbo, "IU", "Disables dynamic clocking"); /* Voltage (offset from 1.2V in units of 0.025V) */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_core", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_voltage_core, "I", "ARM/GPU core voltage" "(offset from 1.2V in units of 0.025V)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram", CTLTYPE_INT | CTLFLAG_WR | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram, "I", "SDRAM voltage (offset from 1.2V in units of 0.025V)"); /* Voltage individual SDRAM */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram_c", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram_c, "I", "SDRAM controller voltage" "(offset from 1.2V in units of 0.025V)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram_i", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram_i, "I", "SDRAM I/O voltage (offset from 1.2V in units of 0.025V)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram_p", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram_p, "I", "SDRAM phy voltage (offset from 1.2V in units of 0.025V)"); /* Temperature */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 0, sysctl_bcm2835_cpufreq_temperature, "I", "SoC temperature (thousandths of a degree C)"); } /* ARM->VC lock */ sema_init(&vc_sema, 1, "vcsema"); /* register callback for using mbox when interrupts are enabled */ sc->init_hook.ich_func = bcm2835_cpufreq_init; sc->init_hook.ich_arg = sc; if (config_intrhook_establish(&sc->init_hook) != 0) { device_printf(dev, "config_intrhook_establish failed\n"); return (ENOMEM); } /* this device is controlled by cpufreq(4) */ cpufreq_register(dev); return (0); } static int bcm2835_cpufreq_detach(device_t dev) { sema_destroy(&vc_sema); return (cpufreq_unregister(dev)); } static int bcm2835_cpufreq_set(device_t dev, const struct cf_setting *cf) { struct bcm2835_cpufreq_softc *sc; uint32_t rate_hz, rem; int resp_freq, arm_freq, min_freq, core_freq; #ifdef DEBUG int cur_freq; #endif if (cf == NULL || cf->freq < 0) return (EINVAL); sc = device_get_softc(dev); /* setting clock (Hz) */ rate_hz = (uint32_t)MHZ2HZ(cf->freq); rem = rate_hz % HZSTEP; rate_hz -= rem; if (rate_hz == 0) return (EINVAL); /* adjust min freq */ min_freq = sc->arm_min_freq; if (sc->turbo_mode != BCM2835_FIRMWARE_TURBO_ON) if (min_freq > cpufreq_lowest_freq) min_freq = cpufreq_lowest_freq; if (rate_hz < MHZ2HZ(min_freq) || rate_hz > MHZ2HZ(sc->arm_max_freq)) return (EINVAL); /* set new value and verify it */ VC_LOCK(sc); #ifdef DEBUG cur_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM); #endif resp_freq = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM, rate_hz); DELAY(TRANSITION_LATENCY); arm_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM); /* * if non-turbo and lower than or equal min_freq, * clock down core and sdram to default first. */ if (sc->turbo_mode != BCM2835_FIRMWARE_TURBO_ON) { core_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE); if (rate_hz > MHZ2HZ(sc->arm_min_freq)) { bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE, MHZ2HZ(sc->core_max_freq)); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM, MHZ2HZ(sc->sdram_max_freq)); DELAY(TRANSITION_LATENCY); } else { if (sc->core_min_freq < DEFAULT_CORE_FREQUENCY && core_freq > DEFAULT_CORE_FREQUENCY) { /* first, down to 250, then down to min */ DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE, MHZ2HZ(DEFAULT_CORE_FREQUENCY)); DELAY(TRANSITION_LATENCY); /* reset core voltage */ bcm2835_cpufreq_set_voltage(sc, BCM2835_FIRMWARE_VOLTAGE_ID_CORE, 0); DELAY(TRANSITION_LATENCY); } bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_CORE, MHZ2HZ(sc->core_min_freq)); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_SDRAM, MHZ2HZ(sc->sdram_min_freq)); DELAY(TRANSITION_LATENCY); } } VC_UNLOCK(sc); if (resp_freq < 0 || arm_freq < 0 || resp_freq != arm_freq) { device_printf(dev, "wrong freq\n"); return (EIO); } DPRINTF("cpufreq: %d -> %d\n", cur_freq, arm_freq); return (0); } static int bcm2835_cpufreq_get(device_t dev, struct cf_setting *cf) { struct bcm2835_cpufreq_softc *sc; int arm_freq; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); cf->dev = NULL; /* get cuurent value */ VC_LOCK(sc); arm_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_FIRMWARE_CLOCK_ID_ARM); VC_UNLOCK(sc); if (arm_freq < 0) { device_printf(dev, "can't get clock\n"); return (EINVAL); } /* CPU clock in MHz or 100ths of a percent. */ cf->freq = HZ2MHZ(arm_freq); /* Voltage in mV. */ cf->volts = CPUFREQ_VAL_UNKNOWN; /* Power consumed in mW. */ cf->power = CPUFREQ_VAL_UNKNOWN; /* Transition latency in us. */ cf->lat = TRANSITION_LATENCY; /* Driver providing this setting. */ cf->dev = dev; return (0); } static int bcm2835_cpufreq_make_freq_list(device_t dev, struct cf_setting *sets, int *count) { struct bcm2835_cpufreq_softc *sc; int freq, min_freq, volts, rem; int idx; sc = device_get_softc(dev); freq = sc->arm_max_freq; min_freq = sc->arm_min_freq; /* adjust head freq to STEP */ rem = freq % MHZSTEP; freq -= rem; if (freq < min_freq) freq = min_freq; /* if non-turbo, add extra low freq */ if (sc->turbo_mode != BCM2835_FIRMWARE_TURBO_ON) if (min_freq > cpufreq_lowest_freq) min_freq = cpufreq_lowest_freq; /* XXX RPi2 have only 900/600MHz */ idx = 0; volts = sc->min_voltage_core; sets[idx].freq = freq; sets[idx].volts = volts; sets[idx].lat = TRANSITION_LATENCY; sets[idx].dev = dev; idx++; if (freq != min_freq) { sets[idx].freq = min_freq; sets[idx].volts = volts; sets[idx].lat = TRANSITION_LATENCY; sets[idx].dev = dev; idx++; } *count = idx; return (0); } static int bcm2835_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) { struct bcm2835_cpufreq_softc *sc; if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); if (sc->arm_min_freq < 0 || sc->arm_max_freq < 0) { printf("device is not configured\n"); return (EINVAL); } /* fill data with unknown value */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); /* create new array up to count */ bcm2835_cpufreq_make_freq_list(dev, sets, count); return (0); } static int bcm2835_cpufreq_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static device_method_t bcm2835_cpufreq_methods[] = { /* Device interface */ DEVMETHOD(device_identify, bcm2835_cpufreq_identify), DEVMETHOD(device_probe, bcm2835_cpufreq_probe), DEVMETHOD(device_attach, bcm2835_cpufreq_attach), DEVMETHOD(device_detach, bcm2835_cpufreq_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, bcm2835_cpufreq_set), DEVMETHOD(cpufreq_drv_get, bcm2835_cpufreq_get), DEVMETHOD(cpufreq_drv_settings, bcm2835_cpufreq_settings), DEVMETHOD(cpufreq_drv_type, bcm2835_cpufreq_type), DEVMETHOD_END }; static driver_t bcm2835_cpufreq_driver = { "bcm2835_cpufreq", bcm2835_cpufreq_methods, sizeof(struct bcm2835_cpufreq_softc), }; DRIVER_MODULE(bcm2835_cpufreq, cpu, bcm2835_cpufreq_driver, 0, 0); MODULE_DEPEND(bcm2835_cpufreq, bcm2835_firmware, 1, 1, 1); diff --git a/sys/arm/nvidia/tegra124/tegra124_coretemp.c b/sys/arm/nvidia/tegra124/tegra124_coretemp.c index 42ed02de4f86..1a756c62e490 100644 --- a/sys/arm/nvidia/tegra124/tegra124_coretemp.c +++ b/sys/arm/nvidia/tegra124/tegra124_coretemp.c @@ -1,260 +1,260 @@ /*- * Copyright (c) 2016 Michal Meloun * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tegra_soctherm_if.h" enum therm_info { CORETEMP_TEMP, CORETEMP_DELTA, CORETEMP_RESOLUTION, CORETEMP_TJMAX, }; struct tegra124_coretemp_softc { device_t dev; int overheat_log; int core_max_temp; int cpu_id; device_t tsens_dev; intptr_t tsens_id; }; static int coretemp_get_val_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; int val, temp, rv; struct tegra124_coretemp_softc *sc; enum therm_info type; char stemp[16]; dev = (device_t) arg1; sc = device_get_softc(dev); type = arg2; rv = TEGRA_SOCTHERM_GET_TEMPERATURE(sc->tsens_dev, sc->dev, sc->tsens_id, &temp); if (rv != 0) { device_printf(sc->dev, "Cannot read temperature sensor %d: %d\n", sc->tsens_id, rv); return (rv); } switch (type) { case CORETEMP_TEMP: val = temp / 100; val += 2731; break; case CORETEMP_DELTA: val = (sc->core_max_temp - temp) / 1000; break; case CORETEMP_RESOLUTION: val = 1; break; case CORETEMP_TJMAX: val = sc->core_max_temp / 100; val += 2731; break; } if ((temp > sc->core_max_temp) && !sc->overheat_log) { sc->overheat_log = 1; /* * Check for Critical Temperature Status and Critical * Temperature Log. It doesn't really matter if the * current temperature is invalid because the "Critical * Temperature Log" bit will tell us if the Critical * Temperature has * been reached in past. It's not * directly related to the current temperature. * * If we reach a critical level, allow devctl(4) * to catch this and shutdown the system. */ device_printf(dev, "critical temperature detected, " "suggest system shutdown\n"); snprintf(stemp, sizeof(stemp), "%d", val); devctl_notify("coretemp", "Thermal", stemp, "notify=0xcc"); } else { sc->overheat_log = 0; } return (sysctl_handle_int(oidp, 0, val, req)); } static int tegra124_coretemp_ofw_parse(struct tegra124_coretemp_softc *sc) { int rv, ncells; phandle_t node, xnode; pcell_t *cells; node = OF_peer(0); node = ofw_bus_find_child(node, "thermal-zones"); if (node <= 0) { device_printf(sc->dev, "Cannot find 'thermal-zones'.\n"); return (ENXIO); } node = ofw_bus_find_child(node, "cpu"); if (node <= 0) { device_printf(sc->dev, "Cannot find 'cpu'\n"); return (ENXIO); } rv = ofw_bus_parse_xref_list_alloc(node, "thermal-sensors", "#thermal-sensor-cells", 0, &xnode, &ncells, &cells); if (rv != 0) { device_printf(sc->dev, "Cannot parse 'thermal-sensors' property.\n"); return (ENXIO); } if (ncells != 1) { device_printf(sc->dev, "Invalid format of 'thermal-sensors' property(%d).\n", ncells); return (ENXIO); } sc->tsens_id = 0x100 + sc->cpu_id; //cells[0]; OF_prop_free(cells); sc->tsens_dev = OF_device_from_xref(xnode); if (sc->tsens_dev == NULL) { device_printf(sc->dev, "Cannot find thermal sensors device."); return (ENXIO); } return (0); } static void tegra124_coretemp_identify(driver_t *driver, device_t parent) { phandle_t root; root = OF_finddevice("/"); if (!ofw_bus_node_is_compatible(root, "nvidia,tegra124")) return; - if (device_find_child(parent, "tegra124_coretemp", -1) != NULL) + if (device_find_child(parent, "tegra124_coretemp", DEVICE_UNIT_ANY) != NULL) return; - if (BUS_ADD_CHILD(parent, 0, "tegra124_coretemp", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "tegra124_coretemp", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int tegra124_coretemp_probe(device_t dev) { device_set_desc(dev, "CPU Thermal Sensor"); return (0); } static int tegra124_coretemp_attach(device_t dev) { struct tegra124_coretemp_softc *sc; device_t pdev; struct sysctl_oid *oid; struct sysctl_ctx_list *ctx; int rv; sc = device_get_softc(dev); sc->dev = dev; sc->cpu_id = device_get_unit(dev); sc->core_max_temp = 102000; pdev = device_get_parent(dev); rv = tegra124_coretemp_ofw_parse(sc); if (rv != 0) return (rv); ctx = device_get_sysctl_ctx(dev); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "coretemp", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Per-CPU thermal information"); /* * Add the MIBs to dev.cpu.N and dev.cpu.N.coretemp. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TEMP, coretemp_get_val_sysctl, "IK", "Current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "delta", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_DELTA, coretemp_get_val_sysctl, "I", "Delta between TCC activation and current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "resolution", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_RESOLUTION, coretemp_get_val_sysctl, "I", "Resolution of CPU thermal sensor"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "tjmax", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TJMAX, coretemp_get_val_sysctl, "IK", "TCC activation temperature"); return (0); } static int tegra124_coretemp_detach(device_t dev) { return (0); } static device_method_t tegra124_coretemp_methods[] = { /* Device interface */ DEVMETHOD(device_identify, tegra124_coretemp_identify), DEVMETHOD(device_probe, tegra124_coretemp_probe), DEVMETHOD(device_attach, tegra124_coretemp_attach), DEVMETHOD(device_detach, tegra124_coretemp_detach), DEVMETHOD_END }; static DEFINE_CLASS_0(tegra124_coretemp, tegra124_coretemp_driver, tegra124_coretemp_methods, sizeof(struct tegra124_coretemp_softc)); DRIVER_MODULE(tegra124_coretemp, cpu, tegra124_coretemp_driver, NULL, NULL); diff --git a/sys/arm/nvidia/tegra124/tegra124_cpufreq.c b/sys/arm/nvidia/tegra124/tegra124_cpufreq.c index a537d9397722..2fa6f902cad7 100644 --- a/sys/arm/nvidia/tegra124/tegra124_cpufreq.c +++ b/sys/arm/nvidia/tegra124/tegra124_cpufreq.c @@ -1,588 +1,588 @@ /*- * Copyright (c) 2016 Michal Meloun * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #define XXX /* CPU voltage table entry */ struct speedo_entry { uint64_t freq; /* Frequency point */ int c0; /* Coeeficient values for */ int c1; /* quadratic equation: */ int c2; /* c2 * speedo^2 + c1 * speedo + c0 */ }; struct cpu_volt_def { int min_uvolt; /* Min allowed CPU voltage */ int max_uvolt; /* Max allowed CPU voltage */ int step_uvolt; /* Step of CPU voltage */ int speedo_scale; /* Scaling factor for cvt */ int speedo_nitems; /* Size of speedo table */ struct speedo_entry *speedo_tbl; /* CPU voltage table */ }; struct cpu_speed_point { uint64_t freq; /* Frequecy */ int uvolt; /* Requested voltage */ }; static struct speedo_entry tegra124_speedo_dpll_tbl[] = { { 204000000ULL, 1112619, -29295, 402}, { 306000000ULL, 1150460, -30585, 402}, { 408000000ULL, 1190122, -31865, 402}, { 510000000ULL, 1231606, -33155, 402}, { 612000000ULL, 1274912, -34435, 402}, { 714000000ULL, 1320040, -35725, 402}, { 816000000ULL, 1366990, -37005, 402}, { 918000000ULL, 1415762, -38295, 402}, {1020000000ULL, 1466355, -39575, 402}, {1122000000ULL, 1518771, -40865, 402}, {1224000000ULL, 1573009, -42145, 402}, {1326000000ULL, 1629068, -43435, 402}, {1428000000ULL, 1686950, -44715, 402}, {1530000000ULL, 1746653, -46005, 402}, {1632000000ULL, 1808179, -47285, 402}, {1734000000ULL, 1871526, -48575, 402}, {1836000000ULL, 1936696, -49855, 402}, {1938000000ULL, 2003687, -51145, 402}, {2014500000ULL, 2054787, -52095, 402}, {2116500000ULL, 2124957, -53385, 402}, {2218500000ULL, 2196950, -54665, 402}, {2320500000ULL, 2270765, -55955, 402}, {2320500000ULL, 2270765, -55955, 402}, {2422500000ULL, 2346401, -57235, 402}, {2524500000ULL, 2437299, -58535, 402}, }; static struct cpu_volt_def tegra124_cpu_volt_dpll_def = { .min_uvolt = 900000, /* 0.9 V */ .max_uvolt = 1260000, /* 1.26 */ .step_uvolt = 10000, /* 10 mV */ .speedo_scale = 100, .speedo_nitems = nitems(tegra124_speedo_dpll_tbl), .speedo_tbl = tegra124_speedo_dpll_tbl, }; static struct speedo_entry tegra124_speedo_pllx_tbl[] = { { 204000000ULL, 800000, 0, 0}, { 306000000ULL, 800000, 0, 0}, { 408000000ULL, 800000, 0, 0}, { 510000000ULL, 800000, 0, 0}, { 612000000ULL, 800000, 0, 0}, { 714000000ULL, 800000, 0, 0}, { 816000000ULL, 820000, 0, 0}, { 918000000ULL, 840000, 0, 0}, {1020000000ULL, 880000, 0, 0}, {1122000000ULL, 900000, 0, 0}, {1224000000ULL, 930000, 0, 0}, {1326000000ULL, 960000, 0, 0}, {1428000000ULL, 990000, 0, 0}, {1530000000ULL, 1020000, 0, 0}, {1632000000ULL, 1070000, 0, 0}, {1734000000ULL, 1100000, 0, 0}, {1836000000ULL, 1140000, 0, 0}, {1938000000ULL, 1180000, 0, 0}, {2014500000ULL, 1220000, 0, 0}, {2116500000ULL, 1260000, 0, 0}, {2218500000ULL, 1310000, 0, 0}, {2320500000ULL, 1360000, 0, 0}, {2397000000ULL, 1400000, 0, 0}, {2499000000ULL, 1400000, 0, 0}, }; static struct cpu_volt_def tegra124_cpu_volt_pllx_def = { .min_uvolt = 1000000, /* XXX 0.9 V doesn't work on all boards */ .max_uvolt = 1260000, /* 1.26 */ .step_uvolt = 10000, /* 10 mV */ .speedo_scale = 100, .speedo_nitems = nitems(tegra124_speedo_pllx_tbl), .speedo_tbl = tegra124_speedo_pllx_tbl, }; static uint64_t cpu_freq_tbl[] = { 204000000ULL, 306000000ULL, 408000000ULL, 510000000ULL, 612000000ULL, 714000000ULL, 816000000ULL, 918000000ULL, 1020000000ULL, 1122000000ULL, 1224000000ULL, 1326000000ULL, 1428000000ULL, 1530000000ULL, 1632000000ULL, 1734000000ULL, 1836000000ULL, 1938000000ULL, 2014000000ULL, 2116000000ULL, 2218000000ULL, 2320000000ULL, 2422000000ULL, 2524000000ULL, }; static uint64_t cpu_max_freq[] = { 2014500000ULL, 2320500000ULL, 2116500000ULL, 2524500000ULL, }; struct tegra124_cpufreq_softc { device_t dev; phandle_t node; regulator_t supply_vdd_cpu; clk_t clk_cpu_g; clk_t clk_cpu_lp; clk_t clk_pll_x; clk_t clk_pll_p; clk_t clk_dfll; int process_id; int speedo_id; int speedo_value; uint64_t cpu_max_freq; struct cpu_volt_def *cpu_def; struct cpu_speed_point *speed_points; int nspeed_points; struct cpu_speed_point *act_speed_point; int latency; }; static int cpufreq_lowest_freq = 1; TUNABLE_INT("hw.tegra124.cpufreq.lowest_freq", &cpufreq_lowest_freq); #define DIV_ROUND_CLOSEST(val, div) (((val) + ((div) / 2)) / (div)) #define ROUND_UP(val, div) roundup(val, div) #define ROUND_DOWN(val, div) rounddown(val, div) /* * Compute requesetd voltage for given frequency and SoC process variations, * - compute base voltage from speedo value using speedo table * - round up voltage to next regulator step * - clamp it to regulator limits */ static int freq_to_voltage(struct tegra124_cpufreq_softc *sc, uint64_t freq) { int uv, scale, min_uvolt, max_uvolt, step_uvolt; struct speedo_entry *ent; int i; /* Get speedo entry with higher frequency */ ent = NULL; for (i = 0; i < sc->cpu_def->speedo_nitems; i++) { if (sc->cpu_def->speedo_tbl[i].freq >= freq) { ent = &sc->cpu_def->speedo_tbl[i]; break; } } if (ent == NULL) ent = &sc->cpu_def->speedo_tbl[sc->cpu_def->speedo_nitems - 1]; scale = sc->cpu_def->speedo_scale; /* uV = (c2 * speedo / scale + c1) * speedo / scale + c0) */ uv = DIV_ROUND_CLOSEST(ent->c2 * sc->speedo_value, scale); uv = DIV_ROUND_CLOSEST((uv + ent->c1) * sc->speedo_value, scale) + ent->c0; step_uvolt = sc->cpu_def->step_uvolt; /* Round up it to next regulator step */ uv = ROUND_UP(uv, step_uvolt); /* Clamp result */ min_uvolt = ROUND_UP(sc->cpu_def->min_uvolt, step_uvolt); max_uvolt = ROUND_DOWN(sc->cpu_def->max_uvolt, step_uvolt); if (uv < min_uvolt) uv = min_uvolt; if (uv > max_uvolt) uv = max_uvolt; return (uv); } static void build_speed_points(struct tegra124_cpufreq_softc *sc) { int i; sc->nspeed_points = nitems(cpu_freq_tbl); sc->speed_points = malloc(sizeof(struct cpu_speed_point) * sc->nspeed_points, M_DEVBUF, M_NOWAIT); for (i = 0; i < sc->nspeed_points; i++) { sc->speed_points[i].freq = cpu_freq_tbl[i]; sc->speed_points[i].uvolt = freq_to_voltage(sc, cpu_freq_tbl[i]); } } static struct cpu_speed_point * get_speed_point(struct tegra124_cpufreq_softc *sc, uint64_t freq) { int i; if (sc->speed_points[0].freq >= freq) return (sc->speed_points + 0); for (i = 0; i < sc->nspeed_points - 1; i++) { if (sc->speed_points[i + 1].freq > freq) return (sc->speed_points + i); } return (sc->speed_points + sc->nspeed_points - 1); } static int tegra124_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) { struct tegra124_cpufreq_softc *sc; int i, j; if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); for (i = 0, j = sc->nspeed_points - 1; j >= 0; j--) { if (sc->cpu_max_freq < sc->speed_points[j].freq) continue; sets[i].freq = sc->speed_points[j].freq / 1000000; sets[i].volts = sc->speed_points[j].uvolt / 1000; sets[i].lat = sc->latency; sets[i].dev = dev; i++; } *count = i; return (0); } static int set_cpu_freq(struct tegra124_cpufreq_softc *sc, uint64_t freq) { struct cpu_speed_point *point; int rv; point = get_speed_point(sc, freq); if (sc->act_speed_point->uvolt < point->uvolt) { /* set cpu voltage */ rv = regulator_set_voltage(sc->supply_vdd_cpu, point->uvolt, point->uvolt); DELAY(10000); if (rv != 0) return (rv); } /* Switch supermux to PLLP first */ rv = clk_set_parent_by_clk(sc->clk_cpu_g, sc->clk_pll_p); if (rv != 0) { device_printf(sc->dev, "Can't set parent to PLLP\n"); return (rv); } /* Set PLLX frequency */ rv = clk_set_freq(sc->clk_pll_x, point->freq, CLK_SET_ROUND_DOWN); if (rv != 0) { device_printf(sc->dev, "Can't set CPU clock frequency\n"); return (rv); } rv = clk_set_parent_by_clk(sc->clk_cpu_g, sc->clk_pll_x); if (rv != 0) { device_printf(sc->dev, "Can't set parent to PLLX\n"); return (rv); } if (sc->act_speed_point->uvolt > point->uvolt) { /* set cpu voltage */ rv = regulator_set_voltage(sc->supply_vdd_cpu, point->uvolt, point->uvolt); if (rv != 0) return (rv); } sc->act_speed_point = point; return (0); } static int tegra124_cpufreq_set(device_t dev, const struct cf_setting *cf) { struct tegra124_cpufreq_softc *sc; uint64_t freq; int rv; if (cf == NULL || cf->freq < 0) return (EINVAL); sc = device_get_softc(dev); freq = cf->freq; if (freq < cpufreq_lowest_freq) freq = cpufreq_lowest_freq; freq *= 1000000; if (freq >= sc->cpu_max_freq) freq = sc->cpu_max_freq; rv = set_cpu_freq(sc, freq); return (rv); } static int tegra124_cpufreq_get(device_t dev, struct cf_setting *cf) { struct tegra124_cpufreq_softc *sc; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); cf->dev = NULL; cf->freq = sc->act_speed_point->freq / 1000000; cf->volts = sc->act_speed_point->uvolt / 1000; /* Transition latency in us. */ cf->lat = sc->latency; /* Driver providing this setting. */ cf->dev = dev; return (0); } static int tegra124_cpufreq_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static int get_fdt_resources(struct tegra124_cpufreq_softc *sc, phandle_t node) { int rv; device_t parent_dev; parent_dev = device_get_parent(sc->dev); rv = regulator_get_by_ofw_property(parent_dev, 0, "vdd-cpu-supply", &sc->supply_vdd_cpu); if (rv != 0) { device_printf(sc->dev, "Cannot get 'vdd-cpu' regulator\n"); return (rv); } rv = clk_get_by_ofw_name(parent_dev, 0, "cpu_g", &sc->clk_cpu_g); if (rv != 0) { device_printf(sc->dev, "Cannot get 'cpu_g' clock: %d\n", rv); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "cpu_lp", &sc->clk_cpu_lp); if (rv != 0) { device_printf(sc->dev, "Cannot get 'cpu_lp' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "pll_x", &sc->clk_pll_x); if (rv != 0) { device_printf(sc->dev, "Cannot get 'pll_x' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "pll_p", &sc->clk_pll_p); if (rv != 0) { device_printf(parent_dev, "Cannot get 'pll_p' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "dfll", &sc->clk_dfll); if (rv != 0) { /* XXX DPLL is not implemented yet */ /* device_printf(sc->dev, "Cannot get 'dfll' clock\n"); return (ENXIO); */ } return (0); } static void tegra124_cpufreq_identify(driver_t *driver, device_t parent) { phandle_t root; root = OF_finddevice("/"); if (!ofw_bus_node_is_compatible(root, "nvidia,tegra124")) return; if (device_get_unit(parent) != 0) return; - if (device_find_child(parent, "tegra124_cpufreq", -1) != NULL) + if (device_find_child(parent, "tegra124_cpufreq", DEVICE_UNIT_ANY) != NULL) return; - if (BUS_ADD_CHILD(parent, 0, "tegra124_cpufreq", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "tegra124_cpufreq", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int tegra124_cpufreq_probe(device_t dev) { device_set_desc(dev, "CPU Frequency Control"); return (0); } static int tegra124_cpufreq_attach(device_t dev) { struct tegra124_cpufreq_softc *sc; uint64_t freq; int rv; sc = device_get_softc(dev); sc->dev = dev; sc->node = ofw_bus_get_node(device_get_parent(dev)); sc->process_id = tegra_sku_info.cpu_process_id; sc->speedo_id = tegra_sku_info.cpu_speedo_id; sc->speedo_value = tegra_sku_info.cpu_speedo_value; /* Tegra 124 */ /* XXX DPLL is not implemented yet */ if (1) sc->cpu_def = &tegra124_cpu_volt_pllx_def; else sc->cpu_def = &tegra124_cpu_volt_dpll_def; rv = get_fdt_resources(sc, sc->node); if (rv != 0) { return (rv); } build_speed_points(sc); rv = clk_get_freq(sc->clk_cpu_g, &freq); if (rv != 0) { device_printf(dev, "Can't get CPU clock frequency\n"); return (rv); } if (sc->speedo_id < nitems(cpu_max_freq)) sc->cpu_max_freq = cpu_max_freq[sc->speedo_id]; else sc->cpu_max_freq = cpu_max_freq[0]; sc->act_speed_point = get_speed_point(sc, freq); /* Set safe startup CPU frequency. */ rv = set_cpu_freq(sc, 1632000000); if (rv != 0) { device_printf(dev, "Can't set initial CPU clock frequency\n"); return (rv); } /* This device is controlled by cpufreq(4). */ cpufreq_register(dev); return (0); } static int tegra124_cpufreq_detach(device_t dev) { struct tegra124_cpufreq_softc *sc; sc = device_get_softc(dev); cpufreq_unregister(dev); if (sc->supply_vdd_cpu != NULL) regulator_release(sc->supply_vdd_cpu); if (sc->clk_cpu_g != NULL) clk_release(sc->clk_cpu_g); if (sc->clk_cpu_lp != NULL) clk_release(sc->clk_cpu_lp); if (sc->clk_pll_x != NULL) clk_release(sc->clk_pll_x); if (sc->clk_pll_p != NULL) clk_release(sc->clk_pll_p); if (sc->clk_dfll != NULL) clk_release(sc->clk_dfll); return (0); } static device_method_t tegra124_cpufreq_methods[] = { /* Device interface */ DEVMETHOD(device_identify, tegra124_cpufreq_identify), DEVMETHOD(device_probe, tegra124_cpufreq_probe), DEVMETHOD(device_attach, tegra124_cpufreq_attach), DEVMETHOD(device_detach, tegra124_cpufreq_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, tegra124_cpufreq_set), DEVMETHOD(cpufreq_drv_get, tegra124_cpufreq_get), DEVMETHOD(cpufreq_drv_settings, tegra124_cpufreq_settings), DEVMETHOD(cpufreq_drv_type, tegra124_cpufreq_type), DEVMETHOD_END }; static DEFINE_CLASS_0(tegra124_cpufreq, tegra124_cpufreq_driver, tegra124_cpufreq_methods, sizeof(struct tegra124_cpufreq_softc)); DRIVER_MODULE(tegra124_cpufreq, cpu, tegra124_cpufreq_driver, NULL, NULL); diff --git a/sys/arm/ti/am335x/am335x_ehrpwm.c b/sys/arm/ti/am335x/am335x_ehrpwm.c index 59ef0931439d..e19933396156 100644 --- a/sys/arm/ti/am335x/am335x_ehrpwm.c +++ b/sys/arm/ti/am335x/am335x_ehrpwm.c @@ -1,596 +1,597 @@ /*- * Copyright (c) 2013 Oleksandr Tymoshenko * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pwmbus_if.h" #include "am335x_pwm.h" /******************************************************************************* * Enhanced resolution PWM driver. Many of the advanced featues of the hardware * are not supported by this driver. What is implemented here is simple * variable-duty-cycle PWM output. ******************************************************************************/ /* In ticks */ #define DEFAULT_PWM_PERIOD 1000 #define PWM_CLOCK 100000000UL #define NS_PER_SEC 1000000000 #define PWM_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define PWM_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define PWM_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define PWM_LOCK_INIT(_sc) mtx_init(&(_sc)->sc_mtx, \ device_get_nameunit(_sc->sc_dev), "am335x_ehrpwm softc", MTX_DEF) #define PWM_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define EPWM_READ2(_sc, reg) bus_read_2((_sc)->sc_mem_res, reg) #define EPWM_WRITE2(_sc, reg, value) \ bus_write_2((_sc)->sc_mem_res, reg, value) #define EPWM_TBCTL 0x00 /* see 15.2.2.11 for the first two, used in debug situations */ #define TBCTL_FREERUN_STOP_NEXT_TBC_INCREMENT (0 << 14) #define TBCTL_FREERUN_STOP_COMPLETE_CYCLE (1 << 14) /* ignore suspend control signal */ #define TBCTL_FREERUN (2 << 14) #define TBCTL_PHDIR_UP (1 << 13) #define TBCTL_PHDIR_DOWN (0 << 13) #define TBCTL_CLKDIV(x) ((x) << 10) #define TBCTL_CLKDIV_MASK (7 << 10) #define TBCTL_HSPCLKDIV(x) ((x) << 7) #define TBCTL_HSPCLKDIV_MASK (7 << 7) #define TBCTL_SYNCOSEL_DISABLED (3 << 4) #define TBCTL_PRDLD_SHADOW (0 << 3) #define TBCTL_PRDLD_IMMEDIATE (1 << 3) #define TBCTL_PHSEN_DISABLED (0 << 2) #define TBCTL_PHSEN_ENABLED (1 << 2) #define TBCTL_CTRMODE_MASK (3) #define TBCTL_CTRMODE_UP (0 << 0) #define TBCTL_CTRMODE_DOWN (1 << 0) #define TBCTL_CTRMODE_UPDOWN (2 << 0) #define TBCTL_CTRMODE_FREEZE (3 << 0) #define EPWM_TBSTS 0x02 #define EPWM_TBPHSHR 0x04 #define EPWM_TBPHS 0x06 #define EPWM_TBCNT 0x08 #define EPWM_TBPRD 0x0a /* Counter-compare */ #define EPWM_CMPCTL 0x0e #define CMPCTL_SHDWBMODE_SHADOW (1 << 6) #define CMPCTL_SHDWBMODE_IMMEDIATE (0 << 6) #define CMPCTL_SHDWAMODE_SHADOW (1 << 4) #define CMPCTL_SHDWAMODE_IMMEDIATE (0 << 4) #define CMPCTL_LOADBMODE_ZERO (0 << 2) #define CMPCTL_LOADBMODE_PRD (1 << 2) #define CMPCTL_LOADBMODE_EITHER (2 << 2) #define CMPCTL_LOADBMODE_FREEZE (3 << 2) #define CMPCTL_LOADAMODE_ZERO (0 << 0) #define CMPCTL_LOADAMODE_PRD (1 << 0) #define CMPCTL_LOADAMODE_EITHER (2 << 0) #define CMPCTL_LOADAMODE_FREEZE (3 << 0) #define EPWM_CMPAHR 0x10 #define EPWM_CMPA 0x12 #define EPWM_CMPB 0x14 /* CMPCTL_LOADAMODE_ZERO */ #define EPWM_AQCTLA 0x16 #define EPWM_AQCTLB 0x18 #define AQCTL_CBU_NONE (0 << 8) #define AQCTL_CBU_CLEAR (1 << 8) #define AQCTL_CBU_SET (2 << 8) #define AQCTL_CBU_TOGGLE (3 << 8) #define AQCTL_CAU_NONE (0 << 4) #define AQCTL_CAU_CLEAR (1 << 4) #define AQCTL_CAU_SET (2 << 4) #define AQCTL_CAU_TOGGLE (3 << 4) #define AQCTL_ZRO_NONE (0 << 0) #define AQCTL_ZRO_CLEAR (1 << 0) #define AQCTL_ZRO_SET (2 << 0) #define AQCTL_ZRO_TOGGLE (3 << 0) #define EPWM_AQSFRC 0x1a #define EPWM_AQCSFRC 0x1c #define AQCSFRC_OFF 0 #define AQCSFRC_LO 1 #define AQCSFRC_HI 2 #define AQCSFRC_MASK 3 #define AQCSFRC(chan, hilo) ((hilo) << (2 * chan)) /* Trip-Zone module */ #define EPWM_TZSEL 0x24 #define EPWM_TZCTL 0x28 #define EPWM_TZFLG 0x2C /* Dead band */ #define EPWM_DBCTL 0x1E #define DBCTL_MASK (3 << 0) #define DBCTL_BYPASS 0 #define DBCTL_RISING_EDGE 1 #define DBCTL_FALLING_EDGE 2 #define DBCTL_BOTH_EDGE 3 /* PWM-chopper */ #define EPWM_PCCTL 0x3C #define PCCTL_CHPEN_MASK (1 << 0) #define PCCTL_CHPEN_DISABLE 0 #define PCCTL_CHPEN_ENABLE 1 /* High-Resolution PWM */ #define EPWM_HRCTL 0x40 #define HRCTL_DELMODE_BOTH 3 #define HRCTL_DELMODE_FALL 2 #define HRCTL_DELMODE_RISE 1 static device_probe_t am335x_ehrpwm_probe; static device_attach_t am335x_ehrpwm_attach; static device_detach_t am335x_ehrpwm_detach; struct ehrpwm_channel { u_int duty; /* on duration, in ns */ bool enabled; /* channel enabled? */ bool inverted; /* signal inverted? */ }; #define NUM_CHANNELS 2 struct am335x_ehrpwm_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_mem_res; int sc_mem_rid; /* Things used for configuration via pwm(9) api. */ u_int sc_clkfreq; /* frequency in Hz */ u_int sc_clktick; /* duration in ns */ u_int sc_period; /* duration in ns */ struct ehrpwm_channel sc_channels[NUM_CHANNELS]; }; static struct ofw_compat_data compat_data[] = { {"ti,am3352-ehrpwm", true}, {"ti,am33xx-ehrpwm", true}, {NULL, false}, }; SIMPLEBUS_PNP_INFO(compat_data); static void am335x_ehrpwm_cfg_duty(struct am335x_ehrpwm_softc *sc, u_int chan, u_int duty) { u_int tbcmp; if (duty == 0) tbcmp = 0; else tbcmp = max(1, duty / sc->sc_clktick); sc->sc_channels[chan].duty = tbcmp * sc->sc_clktick; PWM_LOCK_ASSERT(sc); EPWM_WRITE2(sc, (chan == 0) ? EPWM_CMPA : EPWM_CMPB, tbcmp); } static void am335x_ehrpwm_cfg_enable(struct am335x_ehrpwm_softc *sc, u_int chan, bool enable) { uint16_t regval; sc->sc_channels[chan].enabled = enable; /* * Turn off any existing software-force of the channel, then force * it in the right direction (high or low) if it's not being enabled. */ PWM_LOCK_ASSERT(sc); regval = EPWM_READ2(sc, EPWM_AQCSFRC); regval &= ~AQCSFRC(chan, AQCSFRC_MASK); if (!sc->sc_channels[chan].enabled) { if (sc->sc_channels[chan].inverted) regval |= AQCSFRC(chan, AQCSFRC_HI); else regval |= AQCSFRC(chan, AQCSFRC_LO); } EPWM_WRITE2(sc, EPWM_AQCSFRC, regval); } static bool am335x_ehrpwm_cfg_period(struct am335x_ehrpwm_softc *sc, u_int period) { uint16_t regval; u_int clkdiv, hspclkdiv, pwmclk, pwmtick, tbprd; /* Can't do a period shorter than 2 clock ticks. */ if (period < 2 * NS_PER_SEC / PWM_CLOCK) { sc->sc_clkfreq = 0; sc->sc_clktick = 0; sc->sc_period = 0; return (false); } /* * Figure out how much we have to divide down the base 100MHz clock so * that we can express the requested period as a 16-bit tick count. */ tbprd = 0; for (clkdiv = 0; clkdiv < 8; ++clkdiv) { const u_int cd = 1 << clkdiv; for (hspclkdiv = 0; hspclkdiv < 8; ++hspclkdiv) { const u_int cdhs = max(1, hspclkdiv * 2); pwmclk = PWM_CLOCK / (cd * cdhs); pwmtick = NS_PER_SEC / pwmclk; if (period / pwmtick < 65536) { tbprd = period / pwmtick; break; } } if (tbprd != 0) break; } /* Handle requested period too long for available clock divisors. */ if (tbprd == 0) return (false); /* * If anything has changed from the current settings, reprogram the * clock divisors and period register. */ if (sc->sc_clkfreq != pwmclk || sc->sc_clktick != pwmtick || sc->sc_period != tbprd * pwmtick) { sc->sc_clkfreq = pwmclk; sc->sc_clktick = pwmtick; sc->sc_period = tbprd * pwmtick; PWM_LOCK_ASSERT(sc); regval = EPWM_READ2(sc, EPWM_TBCTL); regval &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK); regval |= TBCTL_CLKDIV(clkdiv) | TBCTL_HSPCLKDIV(hspclkdiv); EPWM_WRITE2(sc, EPWM_TBCTL, regval); EPWM_WRITE2(sc, EPWM_TBPRD, tbprd - 1); #if 0 device_printf(sc->sc_dev, "clkdiv %u hspclkdiv %u tbprd %u " "clkfreq %u Hz clktick %u ns period got %u requested %u\n", clkdiv, hspclkdiv, tbprd - 1, sc->sc_clkfreq, sc->sc_clktick, sc->sc_period, period); #endif /* * If the period changed, that invalidates the current CMP * registers (duty values), just zero them out. */ am335x_ehrpwm_cfg_duty(sc, 0, 0); am335x_ehrpwm_cfg_duty(sc, 1, 0); } return (true); } static int am335x_ehrpwm_channel_count(device_t dev, u_int *nchannel) { *nchannel = NUM_CHANNELS; return (0); } static int am335x_ehrpwm_channel_config(device_t dev, u_int channel, u_int period, u_int duty) { struct am335x_ehrpwm_softc *sc; bool status; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); PWM_LOCK(sc); status = am335x_ehrpwm_cfg_period(sc, period); if (status) am335x_ehrpwm_cfg_duty(sc, channel, duty); PWM_UNLOCK(sc); return (status ? 0 : EINVAL); } static int am335x_ehrpwm_channel_get_config(device_t dev, u_int channel, u_int *period, u_int *duty) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); *period = sc->sc_period; *duty = sc->sc_channels[channel].duty; return (0); } static int am335x_ehrpwm_channel_set_flags(device_t dev, u_int channel, uint32_t flags) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); PWM_LOCK(sc); if (flags & PWM_POLARITY_INVERTED) { sc->sc_channels[channel].inverted = true; /* Action-Qualifier 15.2.2.5 */ if (channel == 0) EPWM_WRITE2(sc, EPWM_AQCTLA, (AQCTL_ZRO_CLEAR | AQCTL_CAU_SET)); else EPWM_WRITE2(sc, EPWM_AQCTLB, (AQCTL_ZRO_CLEAR | AQCTL_CBU_SET)); } else { sc->sc_channels[channel].inverted = false; if (channel == 0) EPWM_WRITE2(sc, EPWM_AQCTLA, (AQCTL_ZRO_SET | AQCTL_CAU_CLEAR)); else EPWM_WRITE2(sc, EPWM_AQCTLB, (AQCTL_ZRO_SET | AQCTL_CBU_CLEAR)); } PWM_UNLOCK(sc); return (0); } static int am335x_ehrpwm_channel_get_flags(device_t dev, u_int channel, uint32_t *flags) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); if (sc->sc_channels[channel].inverted == true) *flags = PWM_POLARITY_INVERTED; else *flags = 0; return (0); } static int am335x_ehrpwm_channel_enable(device_t dev, u_int channel, bool enable) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); PWM_LOCK(sc); am335x_ehrpwm_cfg_enable(sc, channel, enable); PWM_UNLOCK(sc); return (0); } static int am335x_ehrpwm_channel_is_enabled(device_t dev, u_int channel, bool *enabled) { struct am335x_ehrpwm_softc *sc; if (channel >= NUM_CHANNELS) return (EINVAL); sc = device_get_softc(dev); *enabled = sc->sc_channels[channel].enabled; return (0); } static int am335x_ehrpwm_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "AM335x EHRPWM"); return (BUS_PROBE_DEFAULT); } static int am335x_ehrpwm_attach(device_t dev) { struct am335x_ehrpwm_softc *sc; uint16_t reg; sc = device_get_softc(dev); sc->sc_dev = dev; PWM_LOCK_INIT(sc); sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "cannot allocate memory resources\n"); goto fail; } /* CONFIGURE EPWM */ reg = EPWM_READ2(sc, EPWM_TBCTL); reg &= ~(TBCTL_CLKDIV_MASK | TBCTL_HSPCLKDIV_MASK); EPWM_WRITE2(sc, EPWM_TBCTL, reg); EPWM_WRITE2(sc, EPWM_TBPRD, DEFAULT_PWM_PERIOD - 1); EPWM_WRITE2(sc, EPWM_CMPA, 0); EPWM_WRITE2(sc, EPWM_CMPB, 0); /* Action-Qualifier 15.2.2.5 */ EPWM_WRITE2(sc, EPWM_AQCTLA, (AQCTL_ZRO_SET | AQCTL_CAU_CLEAR)); EPWM_WRITE2(sc, EPWM_AQCTLB, (AQCTL_ZRO_SET | AQCTL_CBU_CLEAR)); /* Dead band 15.2.2.6 */ reg = EPWM_READ2(sc, EPWM_DBCTL); reg &= ~DBCTL_MASK; reg |= DBCTL_BYPASS; EPWM_WRITE2(sc, EPWM_DBCTL, reg); /* PWM-chopper described in 15.2.2.7 */ /* Acc. TRM used in pulse transformerbased gate drivers * to control the power switching-elements */ reg = EPWM_READ2(sc, EPWM_PCCTL); reg &= ~PCCTL_CHPEN_MASK; reg |= PCCTL_CHPEN_DISABLE; EPWM_WRITE2(sc, EPWM_PCCTL, PCCTL_CHPEN_DISABLE); /* Trip zone are described in 15.2.2.8. * Essential its used to detect faults and can be configured * to react on such faults.. */ /* disable TZn as one-shot / CVC trip source 15.2.4.18 */ EPWM_WRITE2(sc, EPWM_TZSEL, 0x0); /* reg described in 15.2.4.19 */ EPWM_WRITE2(sc, EPWM_TZCTL, 0xf); reg = EPWM_READ2(sc, EPWM_TZFLG); /* START EPWM */ reg &= ~TBCTL_CTRMODE_MASK; reg |= TBCTL_CTRMODE_UP | TBCTL_FREERUN; EPWM_WRITE2(sc, EPWM_TBCTL, reg); - if ((sc->sc_busdev = device_add_child(dev, "pwmbus", -1)) == NULL) { + if ((sc->sc_busdev = device_add_child(dev, "pwmbus", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "Cannot add child pwmbus\n"); // This driver can still do things even without the bus child. } bus_identify_children(dev); bus_attach_children(dev); return (0); fail: PWM_LOCK_DESTROY(sc); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); return(ENXIO); } static int am335x_ehrpwm_detach(device_t dev) { struct am335x_ehrpwm_softc *sc; int error; sc = device_get_softc(dev); if ((error = bus_generic_detach(sc->sc_dev)) != 0) return (error); PWM_LOCK(sc); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); PWM_UNLOCK(sc); PWM_LOCK_DESTROY(sc); return (0); } static phandle_t am335x_ehrpwm_get_node(device_t bus, device_t dev) { /* * Share our controller node with our pwmbus child; it instantiates * devices by walking the children contained within our node. */ return ofw_bus_get_node(bus); } static device_method_t am335x_ehrpwm_methods[] = { DEVMETHOD(device_probe, am335x_ehrpwm_probe), DEVMETHOD(device_attach, am335x_ehrpwm_attach), DEVMETHOD(device_detach, am335x_ehrpwm_detach), /* ofw_bus_if */ DEVMETHOD(ofw_bus_get_node, am335x_ehrpwm_get_node), /* pwm interface */ DEVMETHOD(pwmbus_channel_count, am335x_ehrpwm_channel_count), DEVMETHOD(pwmbus_channel_config, am335x_ehrpwm_channel_config), DEVMETHOD(pwmbus_channel_get_config, am335x_ehrpwm_channel_get_config), DEVMETHOD(pwmbus_channel_set_flags, am335x_ehrpwm_channel_set_flags), DEVMETHOD(pwmbus_channel_get_flags, am335x_ehrpwm_channel_get_flags), DEVMETHOD(pwmbus_channel_enable, am335x_ehrpwm_channel_enable), DEVMETHOD(pwmbus_channel_is_enabled, am335x_ehrpwm_channel_is_enabled), DEVMETHOD_END }; static driver_t am335x_ehrpwm_driver = { "pwm", am335x_ehrpwm_methods, sizeof(struct am335x_ehrpwm_softc), }; DRIVER_MODULE(am335x_ehrpwm, am335x_pwmss, am335x_ehrpwm_driver, 0, 0); MODULE_VERSION(am335x_ehrpwm, 1); MODULE_DEPEND(am335x_ehrpwm, am335x_pwmss, 1, 1, 1); MODULE_DEPEND(am335x_ehrpwm, pwmbus, 1, 1, 1); diff --git a/sys/arm/ti/am335x/am335x_scm.c b/sys/arm/ti/am335x/am335x_scm.c index 8245f35617ad..33268bd0a94b 100644 --- a/sys/arm/ti/am335x/am335x_scm.c +++ b/sys/arm/ti/am335x/am335x_scm.c @@ -1,193 +1,193 @@ /*- * Copyright (c) 2016 Rubicon Communications, LLC (Netgate) * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "syscon_if.h" #define TZ_ZEROC 2731 struct am335x_scm_softc { int sc_last_temp; struct sysctl_oid *sc_temp_oid; struct syscon *syscon; }; static int am335x_scm_temp_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; int i, temp; struct am335x_scm_softc *sc; uint32_t reg; dev = (device_t)arg1; sc = device_get_softc(dev); /* Read the temperature and convert to Kelvin. */ for(i = 50; i > 0; i--) { reg = SYSCON_READ_4(sc->syscon, SCM_BGAP_CTRL); if ((reg & SCM_BGAP_EOCZ) == 0) break; DELAY(50); } if ((reg & SCM_BGAP_EOCZ) == 0) { sc->sc_last_temp = (reg >> SCM_BGAP_TEMP_SHIFT) & SCM_BGAP_TEMP_MASK; sc->sc_last_temp *= 10; } temp = sc->sc_last_temp + TZ_ZEROC; return (sysctl_handle_int(oidp, &temp, 0, req)); } static void am335x_scm_identify(driver_t *driver, device_t parent) { device_t child; /* AM335x only. */ if (ti_chip() != CHIP_AM335X) return; /* Make sure we attach only once. */ - if (device_find_child(parent, "am335x_scm", -1) != NULL) + if (device_find_child(parent, "am335x_scm", DEVICE_UNIT_ANY) != NULL) return; child = device_add_child(parent, "am335x_scm", DEVICE_UNIT_ANY); if (child == NULL) device_printf(parent, "cannot add ti_scm child\n"); } static int am335x_scm_probe(device_t dev) { /* Just allow the first one */ if (strcmp(device_get_nameunit(dev), "am335x_scm0") != 0) return (ENXIO); device_set_desc(dev, "AM335x Control Module Extension"); return (BUS_PROBE_DEFAULT); } static int am335x_scm_attach(device_t dev) { struct am335x_scm_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid_list *tree; uint32_t reg; phandle_t opp_table; int err; sc = device_get_softc(dev); /* FIXME: For now; Go and kidnap syscon from opp-table */ opp_table = OF_finddevice("/opp-table"); if (opp_table == -1) { device_printf(dev, "Cant find /opp-table\n"); return (ENXIO); } if (!OF_hasprop(opp_table, "syscon")) { device_printf(dev, "/opp-table missing syscon property\n"); return (ENXIO); } err = syscon_get_by_ofw_property(dev, opp_table, "syscon", &sc->syscon); if (err) { device_printf(dev, "Failed to get syscon\n"); return (ENXIO); } /* Reset the digital outputs. */ SYSCON_WRITE_4(sc->syscon, SCM_BGAP_CTRL, 0); reg = SYSCON_READ_4(sc->syscon, SCM_BGAP_CTRL); DELAY(500); /* Set continuous mode. */ SYSCON_WRITE_4(sc->syscon, SCM_BGAP_CTRL, SCM_BGAP_CONTCONV); reg = SYSCON_READ_4(sc->syscon, SCM_BGAP_CTRL); DELAY(500); /* Start the ADC conversion. */ reg = SCM_BGAP_CLRZ | SCM_BGAP_CONTCONV | SCM_BGAP_SOC; SYSCON_WRITE_4(sc->syscon, SCM_BGAP_CTRL, reg); /* Temperature sysctl. */ ctx = device_get_sysctl_ctx(dev); tree = SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); sc->sc_temp_oid = SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0, am335x_scm_temp_sysctl, "IK", "Current temperature"); return (0); } static int am335x_scm_detach(device_t dev) { struct am335x_scm_softc *sc; sc = device_get_softc(dev); /* Remove temperature sysctl. */ if (sc->sc_temp_oid != NULL) sysctl_remove_oid(sc->sc_temp_oid, 1, 0); /* Stop the bandgap ADC. */ SYSCON_WRITE_4(sc->syscon, SCM_BGAP_CTRL, SCM_BGAP_BGOFF); return (0); } static device_method_t am335x_scm_methods[] = { DEVMETHOD(device_identify, am335x_scm_identify), DEVMETHOD(device_probe, am335x_scm_probe), DEVMETHOD(device_attach, am335x_scm_attach), DEVMETHOD(device_detach, am335x_scm_detach), DEVMETHOD_END }; static driver_t am335x_scm_driver = { "am335x_scm", am335x_scm_methods, sizeof(struct am335x_scm_softc), }; DRIVER_MODULE(am335x_scm, ti_scm, am335x_scm_driver, 0, 0); MODULE_VERSION(am335x_scm, 1); MODULE_DEPEND(am335x_scm, ti_scm_syscon, 1, 1, 1); diff --git a/sys/arm/ti/ti_i2c.c b/sys/arm/ti/ti_i2c.c index 53b48e4fe87b..c89809c7a218 100644 --- a/sys/arm/ti/ti_i2c.c +++ b/sys/arm/ti/ti_i2c.c @@ -1,937 +1,938 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Ben Gray . * Copyright (c) 2014 Luiz Otavio O Souza . * 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, 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 AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. */ /** * Driver for the I2C module on the TI SoC. * * This driver is heavily based on the TWI driver for the AT91 (at91_twi.c). * * CAUTION: The I2Ci registers are limited to 16 bit and 8 bit data accesses, * 32 bit data access is not allowed and can corrupt register content. * * This driver currently doesn't use DMA for the transfer, although I hope to * incorporate that sometime in the future. The idea being that for transaction * larger than a certain size the DMA engine is used, for anything less the * normal interrupt/fifo driven option is used. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" /** * I2C device driver context, a pointer to this is stored in the device * driver structure. */ struct ti_i2c_softc { device_t sc_dev; struct resource* sc_irq_res; struct resource* sc_mem_res; device_t sc_iicbus; void* sc_irq_h; struct mtx sc_mtx; struct iic_msg* sc_buffer; int sc_bus_inuse; int sc_buffer_pos; int sc_error; int sc_fifo_trsh; int sc_timeout; uint16_t sc_con_reg; uint16_t sc_rev; }; struct ti_i2c_clock_config { u_int frequency; /* Bus frequency in Hz */ uint8_t psc; /* Fast/Standard mode prescale divider */ uint8_t scll; /* Fast/Standard mode SCL low time */ uint8_t sclh; /* Fast/Standard mode SCL high time */ uint8_t hsscll; /* High Speed mode SCL low time */ uint8_t hssclh; /* High Speed mode SCL high time */ }; #if defined(SOC_TI_AM335X) /* * AM335x i2c bus clock is 48MHZ / ((psc + 1) * (scll + 7 + sclh + 5)) * In all cases we prescale the clock to 24MHz as recommended in the manual. */ static struct ti_i2c_clock_config ti_am335x_i2c_clock_configs[] = { { 100000, 1, 111, 117, 0, 0}, { 400000, 1, 23, 25, 0, 0}, { 1000000, 1, 5, 7, 0, 0}, { 0 /* Table terminator */ } }; #endif /** * Locking macros used throughout the driver */ #define TI_I2C_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define TI_I2C_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define TI_I2C_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \ "ti_i2c", MTX_DEF) #define TI_I2C_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx) #define TI_I2C_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED) #define TI_I2C_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED) #ifdef DEBUG #define ti_i2c_dbg(_sc, fmt, args...) \ device_printf((_sc)->sc_dev, fmt, ##args) #else #define ti_i2c_dbg(_sc, fmt, args...) #endif /** * ti_i2c_read_2 - reads a 16-bit value from one of the I2C registers * @sc: I2C device context * @off: the byte offset within the register bank to read from. * * * LOCKING: * No locking required * * RETURNS: * 16-bit value read from the register. */ static inline uint16_t ti_i2c_read_2(struct ti_i2c_softc *sc, bus_size_t off) { return (bus_read_2(sc->sc_mem_res, off)); } /** * ti_i2c_write_2 - writes a 16-bit value to one of the I2C registers * @sc: I2C device context * @off: the byte offset within the register bank to read from. * @val: the value to write into the register * * LOCKING: * No locking required * * RETURNS: * 16-bit value read from the register. */ static inline void ti_i2c_write_2(struct ti_i2c_softc *sc, bus_size_t off, uint16_t val) { bus_write_2(sc->sc_mem_res, off, val); } static int ti_i2c_transfer_intr(struct ti_i2c_softc* sc, uint16_t status) { int amount, done, i; done = 0; amount = 0; /* Check for the error conditions. */ if (status & I2C_STAT_NACK) { /* No ACK from slave. */ ti_i2c_dbg(sc, "NACK\n"); ti_i2c_write_2(sc, I2C_REG_STATUS, I2C_STAT_NACK); sc->sc_error = ENXIO; } else if (status & I2C_STAT_AL) { /* Arbitration lost. */ ti_i2c_dbg(sc, "Arbitration lost\n"); ti_i2c_write_2(sc, I2C_REG_STATUS, I2C_STAT_AL); sc->sc_error = ENXIO; } /* Check if we have finished. */ if (status & I2C_STAT_ARDY) { /* Register access ready - transaction complete basically. */ ti_i2c_dbg(sc, "ARDY transaction complete\n"); if (sc->sc_error != 0 && sc->sc_buffer->flags & IIC_M_NOSTOP) { ti_i2c_write_2(sc, I2C_REG_CON, sc->sc_con_reg | I2C_CON_STP); } ti_i2c_write_2(sc, I2C_REG_STATUS, I2C_STAT_ARDY | I2C_STAT_RDR | I2C_STAT_RRDY | I2C_STAT_XDR | I2C_STAT_XRDY); return (1); } if (sc->sc_buffer->flags & IIC_M_RD) { /* Read some data. */ if (status & I2C_STAT_RDR) { /* * Receive draining interrupt - last data received. * The set FIFO threshold won't be reached to trigger * RRDY. */ ti_i2c_dbg(sc, "Receive draining interrupt\n"); /* * Drain the FIFO. Read the pending data in the FIFO. */ amount = sc->sc_buffer->len - sc->sc_buffer_pos; } else if (status & I2C_STAT_RRDY) { /* * Receive data ready interrupt - FIFO has reached the * set threshold. */ ti_i2c_dbg(sc, "Receive data ready interrupt\n"); amount = min(sc->sc_fifo_trsh, sc->sc_buffer->len - sc->sc_buffer_pos); } /* Read the bytes from the fifo. */ for (i = 0; i < amount; i++) sc->sc_buffer->buf[sc->sc_buffer_pos++] = (uint8_t)(ti_i2c_read_2(sc, I2C_REG_DATA) & 0xff); if (status & I2C_STAT_RDR) ti_i2c_write_2(sc, I2C_REG_STATUS, I2C_STAT_RDR); if (status & I2C_STAT_RRDY) ti_i2c_write_2(sc, I2C_REG_STATUS, I2C_STAT_RRDY); } else { /* Write some data. */ if (status & I2C_STAT_XDR) { /* * Transmit draining interrupt - FIFO level is below * the set threshold and the amount of data still to * be transferred won't reach the set FIFO threshold. */ ti_i2c_dbg(sc, "Transmit draining interrupt\n"); /* * Drain the TX data. Write the pending data in the * FIFO. */ amount = sc->sc_buffer->len - sc->sc_buffer_pos; } else if (status & I2C_STAT_XRDY) { /* * Transmit data ready interrupt - the FIFO level * is below the set threshold. */ ti_i2c_dbg(sc, "Transmit data ready interrupt\n"); amount = min(sc->sc_fifo_trsh, sc->sc_buffer->len - sc->sc_buffer_pos); } /* Write the bytes from the fifo. */ for (i = 0; i < amount; i++) ti_i2c_write_2(sc, I2C_REG_DATA, sc->sc_buffer->buf[sc->sc_buffer_pos++]); if (status & I2C_STAT_XDR) ti_i2c_write_2(sc, I2C_REG_STATUS, I2C_STAT_XDR); if (status & I2C_STAT_XRDY) ti_i2c_write_2(sc, I2C_REG_STATUS, I2C_STAT_XRDY); } return (done); } /** * ti_i2c_intr - interrupt handler for the I2C module * @dev: i2c device handle * * * * LOCKING: * Called from timer context * * RETURNS: * EH_HANDLED or EH_NOT_HANDLED */ static void ti_i2c_intr(void *arg) { int done; struct ti_i2c_softc *sc; uint16_t events, status; sc = (struct ti_i2c_softc *)arg; TI_I2C_LOCK(sc); status = ti_i2c_read_2(sc, I2C_REG_STATUS); if (status == 0) { TI_I2C_UNLOCK(sc); return; } /* Save enabled interrupts. */ events = ti_i2c_read_2(sc, I2C_REG_IRQENABLE_SET); /* We only care about enabled interrupts. */ status &= events; done = 0; if (sc->sc_buffer != NULL) done = ti_i2c_transfer_intr(sc, status); else { ti_i2c_dbg(sc, "Transfer interrupt without buffer\n"); sc->sc_error = EINVAL; done = 1; } if (done) /* Wakeup the process that started the transaction. */ wakeup(sc); TI_I2C_UNLOCK(sc); } /** * ti_i2c_transfer - called to perform the transfer * @dev: i2c device handle * @msgs: the messages to send/receive * @nmsgs: the number of messages in the msgs array * * * LOCKING: * Internally locked * * RETURNS: * 0 on function succeeded * EINVAL if invalid message is passed as an arg */ static int ti_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { int err, i, repstart, timeout; struct ti_i2c_softc *sc; uint16_t reg; sc = device_get_softc(dev); TI_I2C_LOCK(sc); /* If the controller is busy wait until it is available. */ while (sc->sc_bus_inuse == 1) mtx_sleep(sc, &sc->sc_mtx, 0, "i2cbuswait", 0); /* Now we have control over the I2C controller. */ sc->sc_bus_inuse = 1; err = 0; repstart = 0; for (i = 0; i < nmsgs; i++) { sc->sc_buffer = &msgs[i]; sc->sc_buffer_pos = 0; sc->sc_error = 0; /* Zero byte transfers aren't allowed. */ if (sc->sc_buffer == NULL || sc->sc_buffer->buf == NULL || sc->sc_buffer->len == 0) { err = EINVAL; break; } /* Check if the i2c bus is free. */ if (repstart == 0) { /* * On repeated start we send the START condition while * the bus _is_ busy. */ timeout = 0; while (ti_i2c_read_2(sc, I2C_REG_STATUS_RAW) & I2C_STAT_BB) { if (timeout++ > 100) { err = EBUSY; goto out; } DELAY(1000); } timeout = 0; } else repstart = 0; if (sc->sc_buffer->flags & IIC_M_NOSTOP) repstart = 1; /* Set the slave address. */ ti_i2c_write_2(sc, I2C_REG_SA, msgs[i].slave >> 1); /* Write the data length. */ ti_i2c_write_2(sc, I2C_REG_CNT, sc->sc_buffer->len); /* Clear the RX and the TX FIFO. */ reg = ti_i2c_read_2(sc, I2C_REG_BUF); reg |= I2C_BUF_RXFIFO_CLR | I2C_BUF_TXFIFO_CLR; ti_i2c_write_2(sc, I2C_REG_BUF, reg); reg = sc->sc_con_reg | I2C_CON_STT; if (repstart == 0) reg |= I2C_CON_STP; if ((sc->sc_buffer->flags & IIC_M_RD) == 0) reg |= I2C_CON_TRX; ti_i2c_write_2(sc, I2C_REG_CON, reg); /* Wait for an event. */ err = mtx_sleep(sc, &sc->sc_mtx, 0, "i2ciowait", sc->sc_timeout); if (err == 0) err = sc->sc_error; if (err) break; } out: if (timeout == 0) { while (ti_i2c_read_2(sc, I2C_REG_STATUS_RAW) & I2C_STAT_BB) { if (timeout++ > 100) break; DELAY(1000); } } /* Put the controller in master mode again. */ if ((ti_i2c_read_2(sc, I2C_REG_CON) & I2C_CON_MST) == 0) ti_i2c_write_2(sc, I2C_REG_CON, sc->sc_con_reg); sc->sc_buffer = NULL; sc->sc_bus_inuse = 0; /* Wake up the processes that are waiting for the bus. */ wakeup(sc); TI_I2C_UNLOCK(sc); return (err); } static int ti_i2c_reset(struct ti_i2c_softc *sc, u_char speed) { int timeout; struct ti_i2c_clock_config *clkcfg; u_int busfreq; uint16_t fifo_trsh, reg, scll, sclh; switch (ti_chip()) { #ifdef SOC_TI_AM335X case CHIP_AM335X: clkcfg = ti_am335x_i2c_clock_configs; break; #endif default: panic("Unknown TI SoC, unable to reset the i2c"); } /* * If we haven't attached the bus yet, just init at the default slow * speed. This lets us get the hardware initialized enough to attach * the bus which is where the real speed configuration is handled. After * the bus is attached, get the configured speed from it. Search the * configuration table for the best speed we can do that doesn't exceed * the requested speed. */ if (sc->sc_iicbus == NULL) busfreq = 100000; else busfreq = IICBUS_GET_FREQUENCY(sc->sc_iicbus, speed); for (;;) { if (clkcfg[1].frequency == 0 || clkcfg[1].frequency > busfreq) break; clkcfg++; } /* * 23.1.4.3 - HS I2C Software Reset * From OMAP4 TRM at page 4068. * * 1. Ensure that the module is disabled. */ sc->sc_con_reg = 0; ti_i2c_write_2(sc, I2C_REG_CON, sc->sc_con_reg); /* 2. Issue a softreset to the controller. */ bus_write_2(sc->sc_mem_res, I2C_REG_SYSC, I2C_REG_SYSC_SRST); /* * 3. Enable the module. * The I2Ci.I2C_SYSS[0] RDONE bit is asserted only after the module * is enabled by setting the I2Ci.I2C_CON[15] I2C_EN bit to 1. */ ti_i2c_write_2(sc, I2C_REG_CON, I2C_CON_I2C_EN); /* 4. Wait for the software reset to complete. */ timeout = 0; while ((ti_i2c_read_2(sc, I2C_REG_SYSS) & I2C_SYSS_RDONE) == 0) { if (timeout++ > 100) return (EBUSY); DELAY(100); } /* * Disable the I2C controller once again, now that the reset has * finished. */ ti_i2c_write_2(sc, I2C_REG_CON, sc->sc_con_reg); /* * The following sequence is taken from the OMAP4 TRM at page 4077. * * 1. Enable the functional and interface clocks (see Section * 23.1.5.1.1.1.1). Done at ti_i2c_activate(). * * 2. Program the prescaler to obtain an approximately 12MHz internal * sampling clock (I2Ci_INTERNAL_CLK) by programming the * corresponding value in the I2Ci.I2C_PSC[3:0] PSC field. * This value depends on the frequency of the functional clock * (I2Ci_FCLK). Because this frequency is 96MHz, the * I2Ci.I2C_PSC[7:0] PSC field value is 0x7. */ ti_i2c_write_2(sc, I2C_REG_PSC, clkcfg->psc); /* * 3. Program the I2Ci.I2C_SCLL[7:0] SCLL and I2Ci.I2C_SCLH[7:0] SCLH * bit fields to obtain a bit rate of 100 Kbps, 400 Kbps or 1Mbps. * These values depend on the internal sampling clock frequency * (see Table 23-8). */ scll = clkcfg->scll & I2C_SCLL_MASK; sclh = clkcfg->sclh & I2C_SCLH_MASK; /* * 4. (Optional) Program the I2Ci.I2C_SCLL[15:8] HSSCLL and * I2Ci.I2C_SCLH[15:8] HSSCLH fields to obtain a bit rate of * 400K bps or 3.4M bps (for the second phase of HS mode). These * values depend on the internal sampling clock frequency (see * Table 23-8). * * 5. (Optional) If a bit rate of 3.4M bps is used and the bus line * capacitance exceeds 45 pF, (see Section 18.4.8, PAD Functional * Multiplexing and Configuration). */ /* Write the selected bit rate. */ ti_i2c_write_2(sc, I2C_REG_SCLL, scll); ti_i2c_write_2(sc, I2C_REG_SCLH, sclh); /* * 6. Configure the Own Address of the I2C controller by storing it in * the I2Ci.I2C_OA0 register. Up to four Own Addresses can be * programmed in the I2Ci.I2C_OAi registers (where i = 0, 1, 2, 3) * for each I2C controller. * * Note: For a 10-bit address, set the corresponding expand Own Address * bit in the I2Ci.I2C_CON register. * * Driver currently always in single master mode so ignore this step. */ /* * 7. Set the TX threshold (in transmitter mode) and the RX threshold * (in receiver mode) by setting the I2Ci.I2C_BUF[5:0]XTRSH field to * (TX threshold - 1) and the I2Ci.I2C_BUF[13:8]RTRSH field to (RX * threshold - 1), where the TX and RX thresholds are greater than * or equal to 1. * * The threshold is set to 5 for now. */ fifo_trsh = (sc->sc_fifo_trsh - 1) & I2C_BUF_TRSH_MASK; reg = fifo_trsh | (fifo_trsh << I2C_BUF_RXTRSH_SHIFT); ti_i2c_write_2(sc, I2C_REG_BUF, reg); /* * 8. Take the I2C controller out of reset by setting the * I2Ci.I2C_CON[15] I2C_EN bit to 1. * * 23.1.5.1.1.1.2 - Initialize the I2C Controller * * To initialize the I2C controller, perform the following steps: * * 1. Configure the I2Ci.I2C_CON register: * . For master or slave mode, set the I2Ci.I2C_CON[10] MST bit * (0: slave, 1: master). * . For transmitter or receiver mode, set the I2Ci.I2C_CON[9] TRX * bit (0: receiver, 1: transmitter). */ /* Enable the I2C controller in master mode. */ sc->sc_con_reg |= I2C_CON_I2C_EN | I2C_CON_MST; ti_i2c_write_2(sc, I2C_REG_CON, sc->sc_con_reg); /* * 2. If using an interrupt to transmit/receive data, set the * corresponding bit in the I2Ci.I2C_IE register (the I2Ci.I2C_IE[4] * XRDY_IE bit for the transmit interrupt, the I2Ci.I2C_IE[3] RRDY * bit for the receive interrupt). */ /* Set the interrupts we want to be notified. */ reg = I2C_IE_XDR | /* Transmit draining interrupt. */ I2C_IE_XRDY | /* Transmit Data Ready interrupt. */ I2C_IE_RDR | /* Receive draining interrupt. */ I2C_IE_RRDY | /* Receive Data Ready interrupt. */ I2C_IE_ARDY | /* Register Access Ready interrupt. */ I2C_IE_NACK | /* No Acknowledgment interrupt. */ I2C_IE_AL; /* Arbitration lost interrupt. */ /* Enable the interrupts. */ ti_i2c_write_2(sc, I2C_REG_IRQENABLE_SET, reg); /* * 3. If using DMA to receive/transmit data, set to 1 the corresponding * bit in the I2Ci.I2C_BUF register (the I2Ci.I2C_BUF[15] RDMA_EN * bit for the receive DMA channel, the I2Ci.I2C_BUF[7] XDMA_EN bit * for the transmit DMA channel). * * Not using DMA for now, so ignore this. */ return (0); } static int ti_i2c_iicbus_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct ti_i2c_softc *sc; int err; sc = device_get_softc(dev); TI_I2C_LOCK(sc); err = ti_i2c_reset(sc, speed); TI_I2C_UNLOCK(sc); if (err) return (err); return (IIC_ENOADDR); } static int ti_i2c_activate(device_t dev) { int err; struct ti_i2c_softc *sc; sc = (struct ti_i2c_softc*)device_get_softc(dev); /* * 1. Enable the functional and interface clocks (see Section * 23.1.5.1.1.1.1). */ err = ti_sysc_clock_enable(device_get_parent(dev)); if (err) return (err); return (ti_i2c_reset(sc, IIC_UNKNOWN)); } /** * ti_i2c_deactivate - deactivates the controller and releases resources * @dev: i2c device handle * * * * LOCKING: * Assumed called in an atomic context. * * RETURNS: * nothing */ static void ti_i2c_deactivate(device_t dev) { struct ti_i2c_softc *sc = device_get_softc(dev); /* Disable the controller - cancel all transactions. */ ti_i2c_write_2(sc, I2C_REG_IRQENABLE_CLR, 0xffff); ti_i2c_write_2(sc, I2C_REG_STATUS, 0xffff); ti_i2c_write_2(sc, I2C_REG_CON, 0); /* Release the interrupt handler. */ if (sc->sc_irq_h != NULL) { bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_h); sc->sc_irq_h = NULL; } /* Unmap the I2C controller registers. */ if (sc->sc_mem_res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); sc->sc_mem_res = NULL; } /* Release the IRQ resource. */ if (sc->sc_irq_res != NULL) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } /* Finally disable the functional and interface clocks. */ ti_sysc_clock_disable(device_get_parent(dev)); } static int ti_i2c_sysctl_clk(SYSCTL_HANDLER_ARGS) { int clk, psc, sclh, scll; struct ti_i2c_softc *sc; sc = arg1; TI_I2C_LOCK(sc); /* Get the system prescaler value. */ psc = (int)ti_i2c_read_2(sc, I2C_REG_PSC) + 1; /* Get the bitrate. */ scll = (int)ti_i2c_read_2(sc, I2C_REG_SCLL) & I2C_SCLL_MASK; sclh = (int)ti_i2c_read_2(sc, I2C_REG_SCLH) & I2C_SCLH_MASK; clk = I2C_CLK / psc / (scll + 7 + sclh + 5); TI_I2C_UNLOCK(sc); return (sysctl_handle_int(oidp, &clk, 0, req)); } static int ti_i2c_sysctl_timeout(SYSCTL_HANDLER_ARGS) { struct ti_i2c_softc *sc; unsigned int val; int err; sc = arg1; /* * MTX_DEF lock can't be held while doing uimove in * sysctl_handle_int */ TI_I2C_LOCK(sc); val = sc->sc_timeout; TI_I2C_UNLOCK(sc); err = sysctl_handle_int(oidp, &val, 0, req); /* Write request? */ if ((err == 0) && (req->newptr != NULL)) { TI_I2C_LOCK(sc); sc->sc_timeout = val; TI_I2C_UNLOCK(sc); } return (err); } static int ti_i2c_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,omap4-i2c")) return (ENXIO); device_set_desc(dev, "TI I2C Controller"); return (0); } static int ti_i2c_attach(device_t dev) { int err, rid; struct ti_i2c_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid_list *tree; uint16_t fifosz; sc = device_get_softc(dev); sc->sc_dev = dev; /* Get the memory resource for the register mapping. */ rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "Cannot map registers.\n"); return (ENXIO); } /* Allocate our IRQ resource. */ rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); if (sc->sc_irq_res == NULL) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); device_printf(dev, "Cannot allocate interrupt.\n"); return (ENXIO); } TI_I2C_LOCK_INIT(sc); /* First of all, we _must_ activate the H/W. */ err = ti_i2c_activate(dev); if (err) { device_printf(dev, "ti_i2c_activate failed\n"); goto out; } /* Read the version number of the I2C module */ sc->sc_rev = ti_i2c_read_2(sc, I2C_REG_REVNB_HI) & 0xff; /* Get the fifo size. */ fifosz = ti_i2c_read_2(sc, I2C_REG_BUFSTAT); fifosz >>= I2C_BUFSTAT_FIFODEPTH_SHIFT; fifosz &= I2C_BUFSTAT_FIFODEPTH_MASK; device_printf(dev, "I2C revision %d.%d FIFO size: %d bytes\n", sc->sc_rev >> 4, sc->sc_rev & 0xf, 8 << fifosz); /* Set the FIFO threshold to 5 for now. */ sc->sc_fifo_trsh = 5; /* Set I2C bus timeout */ sc->sc_timeout = 5*hz; ctx = device_get_sysctl_ctx(dev); tree = SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "i2c_clock", CTLFLAG_RD | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ti_i2c_sysctl_clk, "IU", "I2C bus clock"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "i2c_timeout", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, sc, 0, ti_i2c_sysctl_timeout, "IU", "I2C bus timeout (in ticks)"); /* Activate the interrupt. */ err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, ti_i2c_intr, sc, &sc->sc_irq_h); if (err) goto out; /* Attach the iicbus. */ - if ((sc->sc_iicbus = device_add_child(dev, "iicbus", -1)) == NULL) { + if ((sc->sc_iicbus = device_add_child(dev, "iicbus", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "could not allocate iicbus instance\n"); err = ENXIO; goto out; } /* Probe and attach the iicbus when interrupts are available. */ bus_delayed_attach_children(dev); out: if (err) { ti_i2c_deactivate(dev); TI_I2C_LOCK_DESTROY(sc); } return (err); } static int ti_i2c_detach(device_t dev) { struct ti_i2c_softc *sc; int rv; sc = device_get_softc(dev); if ((rv = bus_generic_detach(dev)) != 0) { device_printf(dev, "cannot detach child devices\n"); return (rv); } ti_i2c_deactivate(dev); TI_I2C_LOCK_DESTROY(sc); return (0); } static phandle_t ti_i2c_get_node(device_t bus, device_t dev) { /* Share controller node with iibus device. */ return (ofw_bus_get_node(bus)); } static device_method_t ti_i2c_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ti_i2c_probe), DEVMETHOD(device_attach, ti_i2c_attach), DEVMETHOD(device_detach, ti_i2c_detach), /* Bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), /* OFW methods */ DEVMETHOD(ofw_bus_get_node, ti_i2c_get_node), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_reset, ti_i2c_iicbus_reset), DEVMETHOD(iicbus_transfer, ti_i2c_transfer), DEVMETHOD_END }; static driver_t ti_i2c_driver = { "iichb", ti_i2c_methods, sizeof(struct ti_i2c_softc), }; DRIVER_MODULE(ti_iic, simplebus, ti_i2c_driver, 0, 0); DRIVER_MODULE(iicbus, ti_iic, iicbus_driver, 0, 0); MODULE_DEPEND(ti_iic, ti_sysc, 1, 1, 1); MODULE_DEPEND(ti_iic, iicbus, 1, 1, 1); diff --git a/sys/arm64/arm64/gic_v3_acpi.c b/sys/arm64/arm64/gic_v3_acpi.c index 7c3495fd442b..88fd0394c548 100644 --- a/sys/arm64/arm64/gic_v3_acpi.c +++ b/sys/arm64/arm64/gic_v3_acpi.c @@ -1,480 +1,480 @@ /*- * Copyright (c) 2016 The FreeBSD Foundation * Copyright (c) 2022 Arm Ltd * * This software was developed by Andrew Turner under * the sponsorship of the FreeBSD Foundation. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include "gic_v3_reg.h" #include "gic_v3_var.h" #define GICV3_PRIV_VGIC 0x80000000 #define GICV3_PRIV_FLAGS 0x80000000 #define HV_MSI_SPI_START 64 #define HV_MSI_SPI_LAST 0 struct gic_v3_acpi_devinfo { struct gic_v3_devinfo di_gic_dinfo; struct resource_list di_rl; }; static device_identify_t gic_v3_acpi_identify; static device_probe_t gic_v3_acpi_probe; static device_attach_t gic_v3_acpi_attach; static bus_get_resource_list_t gic_v3_acpi_get_resource_list; static void gic_v3_acpi_bus_attach(device_t); static device_method_t gic_v3_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, gic_v3_acpi_identify), DEVMETHOD(device_probe, gic_v3_acpi_probe), DEVMETHOD(device_attach, gic_v3_acpi_attach), /* Bus interface */ DEVMETHOD(bus_get_resource_list, gic_v3_acpi_get_resource_list), /* End */ DEVMETHOD_END }; DEFINE_CLASS_1(gic, gic_v3_acpi_driver, gic_v3_acpi_methods, sizeof(struct gic_v3_softc), gic_v3_driver); EARLY_DRIVER_MODULE(gic_v3, acpi, gic_v3_acpi_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); struct madt_table_data { device_t parent; device_t dev; ACPI_MADT_GENERIC_DISTRIBUTOR *dist; int count; bool rdist_use_gicc; bool have_vgic; }; static void madt_handler(ACPI_SUBTABLE_HEADER *entry, void *arg) { struct madt_table_data *madt_data; madt_data = (struct madt_table_data *)arg; switch(entry->Type) { case ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR: if (madt_data->dist != NULL) { if (bootverbose) device_printf(madt_data->parent, "gic: Already have a distributor table"); break; } madt_data->dist = (ACPI_MADT_GENERIC_DISTRIBUTOR *)entry; break; case ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR: break; default: break; } } static void rdist_map(ACPI_SUBTABLE_HEADER *entry, void *arg) { ACPI_MADT_GENERIC_REDISTRIBUTOR *redist; ACPI_MADT_GENERIC_INTERRUPT *intr; struct madt_table_data *madt_data; rman_res_t count; madt_data = (struct madt_table_data *)arg; switch(entry->Type) { case ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR: if (madt_data->rdist_use_gicc) break; redist = (ACPI_MADT_GENERIC_REDISTRIBUTOR *)entry; madt_data->count++; BUS_SET_RESOURCE(madt_data->parent, madt_data->dev, SYS_RES_MEMORY, madt_data->count, redist->BaseAddress, redist->Length); break; case ACPI_MADT_TYPE_GENERIC_INTERRUPT: if (!madt_data->rdist_use_gicc) break; intr = (ACPI_MADT_GENERIC_INTERRUPT *)entry; madt_data->count++; /* * Map the two 64k redistributor frames. */ count = GICR_RD_BASE_SIZE + GICR_SGI_BASE_SIZE; if (madt_data->dist->Version == ACPI_MADT_GIC_VERSION_V4) count += GICR_VLPI_BASE_SIZE + GICR_RESERVED_SIZE; BUS_SET_RESOURCE(madt_data->parent, madt_data->dev, SYS_RES_MEMORY, madt_data->count, intr->GicrBaseAddress, count); if (intr->VgicInterrupt == 0) madt_data->have_vgic = false; default: break; } } static void gic_v3_acpi_identify(driver_t *driver, device_t parent) { struct madt_table_data madt_data; ACPI_TABLE_MADT *madt; vm_paddr_t physaddr; uintptr_t private; device_t dev; physaddr = acpi_find_table(ACPI_SIG_MADT); if (physaddr == 0) return; madt = acpi_map_table(physaddr, ACPI_SIG_MADT); if (madt == NULL) { device_printf(parent, "gic: Unable to map the MADT\n"); return; } madt_data.parent = parent; madt_data.dist = NULL; madt_data.count = 0; acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length, madt_handler, &madt_data); if (madt_data.dist == NULL) { device_printf(parent, "No gic interrupt or distributor table\n"); goto out; } /* Check the GIC version is supported by thiss driver */ switch(madt_data.dist->Version) { case ACPI_MADT_GIC_VERSION_V3: case ACPI_MADT_GIC_VERSION_V4: break; default: goto out; } dev = BUS_ADD_CHILD(parent, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE, "gic", -1); if (dev == NULL) { device_printf(parent, "add gic child failed\n"); goto out; } /* Add the MADT data */ BUS_SET_RESOURCE(parent, dev, SYS_RES_MEMORY, 0, madt_data.dist->BaseAddress, GICD_SIZE); madt_data.dev = dev; madt_data.rdist_use_gicc = false; madt_data.have_vgic = true; acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length, rdist_map, &madt_data); if (madt_data.count == 0) { /* * No redistributors found, fall back to use the GICR * address from the GICC sub-table. */ madt_data.rdist_use_gicc = true; acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length, rdist_map, &madt_data); } private = madt_data.dist->Version; /* Flag that the VGIC is in use */ if (madt_data.have_vgic) private |= GICV3_PRIV_VGIC; acpi_set_private(dev, (void *)private); out: acpi_unmap_table(madt); } static int gic_v3_acpi_probe(device_t dev) { switch((uintptr_t)acpi_get_private(dev) & ~GICV3_PRIV_FLAGS) { case ACPI_MADT_GIC_VERSION_V3: case ACPI_MADT_GIC_VERSION_V4: break; default: return (ENXIO); } device_set_desc(dev, GIC_V3_DEVSTR); return (BUS_PROBE_NOWILDCARD); } static void madt_count_redistrib(ACPI_SUBTABLE_HEADER *entry, void *arg) { struct gic_v3_softc *sc = arg; if (entry->Type == ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR) sc->gic_redists.nregions++; } static void madt_count_gicc_redistrib(ACPI_SUBTABLE_HEADER *entry, void *arg) { struct gic_v3_softc *sc = arg; if (entry->Type == ACPI_MADT_TYPE_GENERIC_INTERRUPT) sc->gic_redists.nregions++; } static int gic_v3_acpi_count_regions(device_t dev) { struct gic_v3_softc *sc; ACPI_TABLE_MADT *madt; vm_paddr_t physaddr; sc = device_get_softc(dev); physaddr = acpi_find_table(ACPI_SIG_MADT); if (physaddr == 0) return (ENXIO); madt = acpi_map_table(physaddr, ACPI_SIG_MADT); if (madt == NULL) { device_printf(dev, "Unable to map the MADT\n"); return (ENXIO); } acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length, madt_count_redistrib, sc); /* Fall back to use the distributor GICR base address */ if (sc->gic_redists.nregions == 0) { acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length, madt_count_gicc_redistrib, sc); sc->gic_redists.single = true; } acpi_unmap_table(madt); return (sc->gic_redists.nregions > 0 ? 0 : ENXIO); } static int gic_v3_acpi_attach(device_t dev) { struct gic_v3_softc *sc; int err; sc = device_get_softc(dev); sc->dev = dev; sc->gic_bus = GIC_BUS_ACPI; err = gic_v3_acpi_count_regions(dev); if (err != 0) goto count_error; if (vm_guest == VM_GUEST_HV) { sc->gic_mbi_start = HV_MSI_SPI_START; sc->gic_mbi_end = HV_MSI_SPI_LAST; } err = gic_v3_attach(dev); if (err != 0) goto error; sc->gic_pic = intr_pic_register(dev, ACPI_INTR_XREF); if (sc->gic_pic == NULL) { device_printf(dev, "could not register PIC\n"); err = ENXIO; goto error; } /* * Registering for MSI with SPI range, as this is * required for Hyper-V GIC to work in ARM64. */ if (vm_guest == VM_GUEST_HV) { err = intr_msi_register(dev, ACPI_MSI_XREF); if (err) { device_printf(dev, "could not register MSI\n"); goto error; } } err = intr_pic_claim_root(dev, ACPI_INTR_XREF, arm_gic_v3_intr, sc, INTR_ROOT_IRQ); if (err != 0) { err = ENXIO; goto error; } #ifdef SMP err = intr_ipi_pic_register(dev, 0); if (err != 0) { device_printf(dev, "could not register for IPIs\n"); goto error; } #endif /* * Try to register the ITS driver to this GIC. The GIC will act as * a bus in that case. Failure here will not affect the main GIC * functionality. */ gic_v3_acpi_bus_attach(dev); if (device_get_children(dev, &sc->gic_children, &sc->gic_nchildren) !=0) sc->gic_nchildren = 0; return (0); error: /* Failure so free resources */ gic_v3_detach(dev); count_error: if (bootverbose) { device_printf(dev, "Failed to attach. Error %d\n", err); } return (err); } static void gic_v3_add_children(ACPI_SUBTABLE_HEADER *entry, void *arg) { ACPI_MADT_GENERIC_TRANSLATOR *gict; struct gic_v3_acpi_devinfo *di; struct gic_v3_softc *sc; device_t child, dev; u_int xref; int err, pxm; if (entry->Type == ACPI_MADT_TYPE_GENERIC_TRANSLATOR) { /* We have an ITS, add it as a child */ gict = (ACPI_MADT_GENERIC_TRANSLATOR *)entry; dev = arg; sc = device_get_softc(dev); di = malloc(sizeof(*di), M_GIC_V3, M_WAITOK | M_ZERO); err = acpi_iort_its_lookup(gict->TranslationId, &xref, &pxm); if (err != 0) { free(di, M_GIC_V3); return; } child = device_add_child(dev, "its", DEVICE_UNIT_ANY); if (child == NULL) { free(di, M_GIC_V3); return; } di->di_gic_dinfo.gic_domain = pxm; di->di_gic_dinfo.msi_xref = xref; resource_list_init(&di->di_rl); resource_list_add(&di->di_rl, SYS_RES_MEMORY, 0, gict->BaseAddress, gict->BaseAddress + 128 * 1024 - 1, 128 * 1024); sc->gic_nchildren++; device_set_ivars(child, di); } } static void gic_v3_acpi_bus_attach(device_t dev) { struct gic_v3_acpi_devinfo *di; struct gic_v3_softc *sc; ACPI_TABLE_MADT *madt; device_t child; vm_paddr_t physaddr; sc = device_get_softc(dev); physaddr = acpi_find_table(ACPI_SIG_MADT); if (physaddr == 0) return; madt = acpi_map_table(physaddr, ACPI_SIG_MADT); if (madt == NULL) { device_printf(dev, "Unable to map the MADT to add children\n"); return; } acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length, gic_v3_add_children, dev); /* Add the vgic child if needed */ if (((uintptr_t)acpi_get_private(dev) & GICV3_PRIV_FLAGS) != 0) { - child = device_add_child(dev, "vgic", -1); + child = device_add_child(dev, "vgic", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "Could not add vgic child\n"); } else { di = malloc(sizeof(*di), M_GIC_V3, M_WAITOK | M_ZERO); resource_list_init(&di->di_rl); di->di_gic_dinfo.gic_domain = -1; di->di_gic_dinfo.is_vgic = 1; device_set_ivars(child, di); sc->gic_nchildren++; } } acpi_unmap_table(madt); bus_attach_children(dev); } static struct resource_list * gic_v3_acpi_get_resource_list(device_t bus, device_t child) { struct gic_v3_acpi_devinfo *di; di = device_get_ivars(child); KASSERT(di != NULL, ("%s: No devinfo", __func__)); return (&di->di_rl); } diff --git a/sys/arm64/arm64/gic_v3_fdt.c b/sys/arm64/arm64/gic_v3_fdt.c index 9c034b417624..4bea4040c0ba 100644 --- a/sys/arm64/arm64/gic_v3_fdt.c +++ b/sys/arm64/arm64/gic_v3_fdt.c @@ -1,392 +1,392 @@ /*- * Copyright (c) 2015 The FreeBSD Foundation * * This software was developed by Semihalf under * the sponsorship of the FreeBSD Foundation. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gic_v3_reg.h" #include "gic_v3_var.h" /* * FDT glue. */ static int gic_v3_fdt_probe(device_t); static int gic_v3_fdt_attach(device_t); static const struct ofw_bus_devinfo *gic_v3_ofw_get_devinfo(device_t, device_t); static bus_get_resource_list_t gic_v3_fdt_get_resource_list; static device_method_t gic_v3_fdt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gic_v3_fdt_probe), DEVMETHOD(device_attach, gic_v3_fdt_attach), /* Bus interface */ DEVMETHOD(bus_get_resource_list, gic_v3_fdt_get_resource_list), DEVMETHOD(bus_get_device_path, ofw_bus_gen_get_device_path), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, gic_v3_ofw_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), /* End */ DEVMETHOD_END }; DEFINE_CLASS_1(gic, gic_v3_fdt_driver, gic_v3_fdt_methods, sizeof(struct gic_v3_softc), gic_v3_driver); EARLY_DRIVER_MODULE(gic_v3, simplebus, gic_v3_fdt_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); EARLY_DRIVER_MODULE(gic_v3, ofwbus, gic_v3_fdt_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); /* * Helper functions declarations. */ static int gic_v3_ofw_bus_attach(device_t); /* * Device interface. */ static int gic_v3_fdt_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "arm,gic-v3")) return (ENXIO); device_set_desc(dev, GIC_V3_DEVSTR); return (BUS_PROBE_DEFAULT); } static int gic_v3_fdt_attach(device_t dev) { struct gic_v3_softc *sc; pcell_t redist_regions; intptr_t xref; int err; uint32_t *mbi_ranges; ssize_t ret; sc = device_get_softc(dev); sc->dev = dev; sc->gic_bus = GIC_BUS_FDT; /* * Recover number of the Re-Distributor regions. */ if (OF_getencprop(ofw_bus_get_node(dev), "#redistributor-regions", &redist_regions, sizeof(redist_regions)) <= 0) sc->gic_redists.nregions = 1; else sc->gic_redists.nregions = redist_regions; /* Add Message Based Interrupts using SPIs. */ ret = OF_getencprop_alloc_multi(ofw_bus_get_node(dev), "mbi-ranges", sizeof(*mbi_ranges), (void **)&mbi_ranges); if (ret > 0) { if (ret % 2 == 0) { /* Limit to a single range for now. */ sc->gic_mbi_start = mbi_ranges[0]; sc->gic_mbi_end = mbi_ranges[0] + mbi_ranges[1]; } else { if (bootverbose) device_printf(dev, "Malformed mbi-ranges property\n"); } free(mbi_ranges, M_OFWPROP); } err = gic_v3_attach(dev); if (err != 0) goto error; xref = OF_xref_from_node(ofw_bus_get_node(dev)); sc->gic_pic = intr_pic_register(dev, xref); if (sc->gic_pic == NULL) { device_printf(dev, "could not register PIC\n"); err = ENXIO; goto error; } if (sc->gic_mbi_start > 0) intr_msi_register(dev, xref); /* Register xref */ OF_device_register_xref(xref, dev); err = intr_pic_claim_root(dev, xref, arm_gic_v3_intr, sc, INTR_ROOT_IRQ); if (err != 0) { err = ENXIO; goto error; } #ifdef SMP err = intr_ipi_pic_register(dev, 0); if (err != 0) { device_printf(dev, "could not register for IPIs\n"); goto error; } #endif /* * Try to register ITS to this GIC. * GIC will act as a bus in that case. * Failure here will not affect main GIC functionality. */ if (gic_v3_ofw_bus_attach(dev) != 0) { if (bootverbose) { device_printf(dev, "Failed to attach ITS to this GIC\n"); } } if (device_get_children(dev, &sc->gic_children, &sc->gic_nchildren) != 0) sc->gic_nchildren = 0; return (err); error: if (bootverbose) { device_printf(dev, "Failed to attach. Error %d\n", err); } /* Failure so free resources */ gic_v3_detach(dev); return (err); } /* OFW bus interface */ struct gic_v3_ofw_devinfo { struct gic_v3_devinfo di_gic_dinfo; struct ofw_bus_devinfo di_dinfo; struct resource_list di_rl; }; static const struct ofw_bus_devinfo * gic_v3_ofw_get_devinfo(device_t bus __unused, device_t child) { struct gic_v3_ofw_devinfo *di; di = device_get_ivars(child); if (di->di_gic_dinfo.is_vgic) return (NULL); return (&di->di_dinfo); } /* Helper functions */ static int gic_v3_ofw_fill_ranges(phandle_t parent, struct gic_v3_softc *sc, pcell_t *addr_cellsp, pcell_t *size_cellsp) { pcell_t addr_cells, host_cells, size_cells; cell_t *base_ranges; ssize_t nbase_ranges; int i, j, k; host_cells = 1; OF_getencprop(OF_parent(parent), "#address-cells", &host_cells, sizeof(host_cells)); addr_cells = 2; OF_getencprop(parent, "#address-cells", &addr_cells, sizeof(addr_cells)); size_cells = 2; OF_getencprop(parent, "#size-cells", &size_cells, sizeof(size_cells)); *addr_cellsp = addr_cells; *size_cellsp = size_cells; nbase_ranges = OF_getproplen(parent, "ranges"); if (nbase_ranges < 0) return (EINVAL); sc->nranges = nbase_ranges / sizeof(cell_t) / (addr_cells + host_cells + size_cells); if (sc->nranges == 0) return (0); sc->ranges = malloc(sc->nranges * sizeof(sc->ranges[0]), M_GIC_V3, M_WAITOK); base_ranges = malloc(nbase_ranges, M_DEVBUF, M_WAITOK); OF_getencprop(parent, "ranges", base_ranges, nbase_ranges); for (i = 0, j = 0; i < sc->nranges; i++) { sc->ranges[i].bus = 0; for (k = 0; k < addr_cells; k++) { sc->ranges[i].bus <<= 32; sc->ranges[i].bus |= base_ranges[j++]; } sc->ranges[i].host = 0; for (k = 0; k < host_cells; k++) { sc->ranges[i].host <<= 32; sc->ranges[i].host |= base_ranges[j++]; } sc->ranges[i].size = 0; for (k = 0; k < size_cells; k++) { sc->ranges[i].size <<= 32; sc->ranges[i].size |= base_ranges[j++]; } } free(base_ranges, M_DEVBUF); return (0); } /* * Bus capability support for GICv3. * Collects and configures device informations and finally * adds ITS device as a child of GICv3 in Newbus hierarchy. */ static int gic_v3_ofw_bus_attach(device_t dev) { struct gic_v3_ofw_devinfo *di; struct gic_v3_softc *sc; device_t child; phandle_t parent, node; pcell_t addr_cells, size_cells; int rv; sc = device_get_softc(dev); parent = ofw_bus_get_node(dev); if (parent > 0) { rv = gic_v3_ofw_fill_ranges(parent, sc, &addr_cells, &size_cells); if (rv != 0) return (rv); /* Iterate through all GIC subordinates */ for (node = OF_child(parent); node > 0; node = OF_peer(node)) { /* * Ignore children that lack a compatible property. * Some of them may be for configuration, for example * ppi-partitions. */ if (!OF_hasprop(node, "compatible")) continue; /* Allocate and populate devinfo. */ di = malloc(sizeof(*di), M_GIC_V3, M_WAITOK | M_ZERO); /* Read the numa node, or -1 if there is none */ if (OF_getencprop(node, "numa-node-id", &di->di_gic_dinfo.gic_domain, sizeof(di->di_gic_dinfo.gic_domain)) <= 0) { di->di_gic_dinfo.gic_domain = -1; } if (ofw_bus_gen_setup_devinfo(&di->di_dinfo, node)) { if (bootverbose) { device_printf(dev, "Could not set up devinfo for ITS\n"); } free(di, M_GIC_V3); continue; } /* Initialize and populate resource list. */ resource_list_init(&di->di_rl); ofw_bus_reg_to_rl(dev, node, addr_cells, size_cells, &di->di_rl); /* Should not have any interrupts, so don't add any */ /* Add newbus device for this FDT node */ child = device_add_child(dev, NULL, DEVICE_UNIT_ANY); if (!child) { if (bootverbose) { device_printf(dev, "Could not add child: %s\n", di->di_dinfo.obd_name); } resource_list_free(&di->di_rl); ofw_bus_gen_destroy_devinfo(&di->di_dinfo); free(di, M_GIC_V3); continue; } sc->gic_nchildren++; device_set_ivars(child, di); } } /* * If there is a vgic maintanance interrupt add a virtual gic * child so we can use this in the vmm module for bhyve. */ if (OF_hasprop(parent, "interrupts")) { - child = device_add_child(dev, "vgic", -1); + child = device_add_child(dev, "vgic", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "Could not add vgic child\n"); } else { di = malloc(sizeof(*di), M_GIC_V3, M_WAITOK | M_ZERO); resource_list_init(&di->di_rl); di->di_gic_dinfo.gic_domain = -1; di->di_gic_dinfo.is_vgic = 1; device_set_ivars(child, di); sc->gic_nchildren++; } } bus_attach_children(dev); return (0); } static struct resource_list * gic_v3_fdt_get_resource_list(device_t bus, device_t child) { struct gic_v3_ofw_devinfo *di; di = device_get_ivars(child); KASSERT(di != NULL, ("%s: No devinfo", __func__)); return (&di->di_rl); } diff --git a/sys/arm64/cavium/thunder_pcie_fdt.c b/sys/arm64/cavium/thunder_pcie_fdt.c index f173a28b637d..87dc113ad781 100644 --- a/sys/arm64/cavium/thunder_pcie_fdt.c +++ b/sys/arm64/cavium/thunder_pcie_fdt.c @@ -1,301 +1,301 @@ /* * Copyright (C) 2016 Cavium Inc. * All rights reserved. * * Developed by Semihalf. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "thunder_pcie_common.h" #include "pcib_if.h" #ifdef THUNDERX_PASS_1_1_ERRATA static struct resource * thunder_pcie_fdt_alloc_resource(device_t, device_t, int, int *, rman_res_t, rman_res_t, rman_res_t, u_int); static int thunder_pcie_fdt_release_resource(device_t, device_t, struct resource*); #endif static int thunder_pcie_fdt_attach(device_t); static int thunder_pcie_fdt_probe(device_t); static int thunder_pcie_fdt_get_id(device_t, device_t, enum pci_id_type, uintptr_t *); static const struct ofw_bus_devinfo *thunder_pcie_ofw_get_devinfo(device_t, device_t); /* OFW bus interface */ struct thunder_pcie_ofw_devinfo { struct ofw_bus_devinfo di_dinfo; struct resource_list di_rl; }; static device_method_t thunder_pcie_fdt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, thunder_pcie_fdt_probe), DEVMETHOD(device_attach, thunder_pcie_fdt_attach), #ifdef THUNDERX_PASS_1_1_ERRATA DEVMETHOD(bus_alloc_resource, thunder_pcie_fdt_alloc_resource), DEVMETHOD(bus_release_resource, thunder_pcie_fdt_release_resource), #endif /* pcib interface */ DEVMETHOD(pcib_get_id, thunder_pcie_fdt_get_id), /* ofw interface */ DEVMETHOD(ofw_bus_get_devinfo, thunder_pcie_ofw_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), /* End */ DEVMETHOD_END }; DEFINE_CLASS_1(pcib, thunder_pcie_fdt_driver, thunder_pcie_fdt_methods, sizeof(struct generic_pcie_fdt_softc), generic_pcie_fdt_driver); DRIVER_MODULE(thunder_pcib, simplebus, thunder_pcie_fdt_driver, 0, 0); DRIVER_MODULE(thunder_pcib, ofwbus, thunder_pcie_fdt_driver, 0, 0); static const struct ofw_bus_devinfo * thunder_pcie_ofw_get_devinfo(device_t bus __unused, device_t child) { struct thunder_pcie_ofw_devinfo *di; di = device_get_ivars(child); return (&di->di_dinfo); } static void get_addr_size_cells(phandle_t node, pcell_t *addr_cells, pcell_t *size_cells) { *addr_cells = 2; /* Find address cells if present */ OF_getencprop(node, "#address-cells", addr_cells, sizeof(*addr_cells)); *size_cells = 2; /* Find size cells if present */ OF_getencprop(node, "#size-cells", size_cells, sizeof(*size_cells)); } static int thunder_pcie_ofw_bus_attach(device_t dev) { struct thunder_pcie_ofw_devinfo *di; device_t child; phandle_t parent, node; pcell_t addr_cells, size_cells; parent = ofw_bus_get_node(dev); if (parent > 0) { get_addr_size_cells(parent, &addr_cells, &size_cells); /* Iterate through all bus subordinates */ for (node = OF_child(parent); node > 0; node = OF_peer(node)) { /* Allocate and populate devinfo. */ di = malloc(sizeof(*di), M_DEVBUF, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(&di->di_dinfo, node) != 0) { free(di, M_DEVBUF); continue; } /* Initialize and populate resource list. */ resource_list_init(&di->di_rl); ofw_bus_reg_to_rl(dev, node, addr_cells, size_cells, &di->di_rl); ofw_bus_intr_to_rl(dev, node, &di->di_rl, NULL); /* Add newbus device for this FDT node */ - child = device_add_child(dev, NULL, -1); + child = device_add_child(dev, NULL, DEVICE_UNIT_ANY); if (child == NULL) { resource_list_free(&di->di_rl); ofw_bus_gen_destroy_devinfo(&di->di_dinfo); free(di, M_DEVBUF); continue; } device_set_ivars(child, di); } } return (0); } static int thunder_pcie_fdt_probe(device_t dev) { /* Check if we're running on Cavium ThunderX */ if (!CPU_MATCH(CPU_IMPL_MASK | CPU_PART_MASK, CPU_IMPL_CAVIUM, CPU_PART_THUNDERX, 0, 0)) return (ENXIO); if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_is_compatible(dev, "pci-host-ecam-generic") || ofw_bus_is_compatible(dev, "cavium,thunder-pcie") || ofw_bus_is_compatible(dev, "cavium,pci-host-thunder-ecam")) { device_set_desc(dev, "Cavium Integrated PCI/PCI-E Controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int thunder_pcie_fdt_attach(device_t dev) { struct generic_pcie_fdt_softc *sc; sc = device_get_softc(dev); thunder_pcie_identify_ecam(dev, &sc->base.ecam); sc->base.coherent = 1; /* Attach OFW bus */ if (thunder_pcie_ofw_bus_attach(dev) != 0) return (ENXIO); return (pci_host_generic_fdt_attach(dev)); } static int thunder_pcie_fdt_get_id(device_t pci, device_t child, enum pci_id_type type, uintptr_t *id) { phandle_t node; int bsf; if (type != PCI_ID_MSI) return (pcib_get_id(pci, child, type, id)); node = ofw_bus_get_node(pci); if (OF_hasprop(node, "msi-map")) return (generic_pcie_get_id(pci, child, type, id)); bsf = pci_get_rid(child); *id = (pci_get_domain(child) << PCI_RID_DOMAIN_SHIFT) | bsf; return (0); } #ifdef THUNDERX_PASS_1_1_ERRATA struct resource * thunder_pcie_fdt_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct generic_pcie_fdt_softc *sc; struct thunder_pcie_ofw_devinfo *di; struct resource_list_entry *rle; int i; /* * For PCIe devices that do not have FDT nodes pass * the request to the core driver. */ if ((int)ofw_bus_get_node(child) <= 0) return (thunder_pcie_alloc_resource(dev, child, type, rid, start, end, count, flags)); /* For other devices use OFW method */ sc = device_get_softc(dev); if (RMAN_IS_DEFAULT_RANGE(start, end)) { if ((di = device_get_ivars(child)) == NULL) return (NULL); if (type == SYS_RES_IOPORT) type = SYS_RES_MEMORY; /* Find defaults for this rid */ rle = resource_list_find(&di->di_rl, type, *rid); if (rle == NULL) return (NULL); start = rle->start; end = rle->end; count = rle->count; } if (type == SYS_RES_MEMORY) { /* Remap through ranges property */ for (i = 0; i < MAX_RANGES_TUPLES; i++) { if (start >= sc->base.ranges[i].phys_base && end < (sc->base.ranges[i].pci_base + sc->base.ranges[i].size)) { start -= sc->base.ranges[i].phys_base; start += sc->base.ranges[i].pci_base; end -= sc->base.ranges[i].phys_base; end += sc->base.ranges[i].pci_base; break; } } if (i == MAX_RANGES_TUPLES) { device_printf(dev, "Could not map resource " "%#jx-%#jx\n", start, end); return (NULL); } } return (bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags)); } static int thunder_pcie_fdt_release_resource(device_t dev, device_t child, struct resource *res) { if ((int)ofw_bus_get_node(child) <= 0) return (pci_host_generic_core_release_resource(dev, child, res)); return (bus_generic_release_resource(dev, child, res)); } #endif diff --git a/sys/arm64/nvidia/tegra210/tegra210_coretemp.c b/sys/arm64/nvidia/tegra210/tegra210_coretemp.c index ac037d4ac385..973cbc4759fb 100644 --- a/sys/arm64/nvidia/tegra210/tegra210_coretemp.c +++ b/sys/arm64/nvidia/tegra210/tegra210_coretemp.c @@ -1,264 +1,264 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Michal Meloun * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tegra_soctherm_if.h" enum therm_info { CORETEMP_TEMP, CORETEMP_DELTA, CORETEMP_RESOLUTION, CORETEMP_TJMAX, }; struct tegra210_coretemp_softc { device_t dev; int overheat_log; int core_max_temp; int cpu_id; device_t tsens_dev; intptr_t tsens_id; }; static int coretemp_get_val_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; int val, temp, rv; struct tegra210_coretemp_softc *sc; enum therm_info type; char stemp[16]; dev = (device_t) arg1; sc = device_get_softc(dev); type = arg2; rv = TEGRA_SOCTHERM_GET_TEMPERATURE(sc->tsens_dev, sc->dev, sc->tsens_id, &temp); if (rv != 0) { device_printf(sc->dev, "Cannot read temperature sensor %u: %d\n", (unsigned int)sc->tsens_id, rv); return (rv); } switch (type) { case CORETEMP_TEMP: val = temp / 100; val += 2731; break; case CORETEMP_DELTA: val = (sc->core_max_temp - temp) / 1000; break; case CORETEMP_RESOLUTION: val = 1; break; case CORETEMP_TJMAX: val = sc->core_max_temp / 100; val += 2731; break; } if ((temp > sc->core_max_temp) && !sc->overheat_log) { sc->overheat_log = 1; /* * Check for Critical Temperature Status and Critical * Temperature Log. It doesn't really matter if the * current temperature is invalid because the "Critical * Temperature Log" bit will tell us if the Critical * Temperature has * been reached in past. It's not * directly related to the current temperature. * * If we reach a critical level, allow devctl(4) * to catch this and shutdown the system. */ device_printf(dev, "critical temperature detected, " "suggest system shutdown\n"); snprintf(stemp, sizeof(stemp), "%d", val); devctl_notify("coretemp", "Thermal", stemp, "notify=0xcc"); } else { sc->overheat_log = 0; } return (sysctl_handle_int(oidp, 0, val, req)); } static int tegra210_coretemp_ofw_parse(struct tegra210_coretemp_softc *sc) { int rv, ncells; phandle_t node, xnode; pcell_t *cells; node = OF_peer(0); node = ofw_bus_find_child(node, "thermal-zones"); if (node <= 0) { device_printf(sc->dev, "Cannot find 'thermal-zones'.\n"); return (ENXIO); } node = ofw_bus_find_child(node, "cpu"); if (node <= 0) { device_printf(sc->dev, "Cannot find 'cpu'\n"); return (ENXIO); } rv = ofw_bus_parse_xref_list_alloc(node, "thermal-sensors", "#thermal-sensor-cells", 0, &xnode, &ncells, &cells); if (rv != 0) { device_printf(sc->dev, "Cannot parse 'thermal-sensors' property.\n"); return (ENXIO); } if (ncells != 1) { device_printf(sc->dev, "Invalid format of 'thermal-sensors' property(%d).\n", ncells); return (ENXIO); } sc->tsens_id = 0x100 + sc->cpu_id; OF_prop_free(cells); sc->tsens_dev = OF_device_from_xref(xnode); if (sc->tsens_dev == NULL) { device_printf(sc->dev, "Cannot find thermal sensors device."); return (ENXIO); } return (0); } static void tegra210_coretemp_identify(driver_t *driver, device_t parent) { phandle_t root; root = OF_finddevice("/"); if (!ofw_bus_node_is_compatible(root, "nvidia,tegra210")) return; - if (device_find_child(parent, "tegra210_coretemp", -1) != NULL) + if (device_find_child(parent, "tegra210_coretemp", DEVICE_UNIT_ANY) != NULL) return; - if (BUS_ADD_CHILD(parent, 0, "tegra210_coretemp", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "tegra210_coretemp", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int tegra210_coretemp_probe(device_t dev) { device_set_desc(dev, "CPU Thermal Sensor"); return (0); } static int tegra210_coretemp_attach(device_t dev) { struct tegra210_coretemp_softc *sc; device_t pdev; struct sysctl_oid *oid; struct sysctl_ctx_list *ctx; int rv; sc = device_get_softc(dev); sc->dev = dev; sc->cpu_id = device_get_unit(dev); sc->core_max_temp = 102000; pdev = device_get_parent(dev); rv = tegra210_coretemp_ofw_parse(sc); if (rv != 0) return (rv); ctx = device_get_sysctl_ctx(dev); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "coretemp", CTLFLAG_RD, NULL, "Per-CPU thermal information"); /* * Add the MIBs to dev.cpu.N and dev.cpu.N.coretemp. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TEMP, coretemp_get_val_sysctl, "IK", "Current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "delta", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_DELTA, coretemp_get_val_sysctl, "I", "Delta between TCC activation and current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "resolution", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_RESOLUTION, coretemp_get_val_sysctl, "I", "Resolution of CPU thermal sensor"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "tjmax", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TJMAX, coretemp_get_val_sysctl, "IK", "TCC activation temperature"); return (0); } static int tegra210_coretemp_detach(device_t dev) { return (0); } static device_method_t tegra210_coretemp_methods[] = { /* Device interface */ DEVMETHOD(device_identify, tegra210_coretemp_identify), DEVMETHOD(device_probe, tegra210_coretemp_probe), DEVMETHOD(device_attach, tegra210_coretemp_attach), DEVMETHOD(device_detach, tegra210_coretemp_detach), DEVMETHOD_END }; static DEFINE_CLASS_0(tegra210_coretemp, tegra210_coretemp_driver, tegra210_coretemp_methods, sizeof(struct tegra210_coretemp_softc)); DRIVER_MODULE(tegra210_coretemp, cpu, tegra210_coretemp_driver, NULL, NULL); diff --git a/sys/arm64/nvidia/tegra210/tegra210_cpufreq.c b/sys/arm64/nvidia/tegra210/tegra210_cpufreq.c index 9b248a09bd58..56dfc1b32500 100644 --- a/sys/arm64/nvidia/tegra210/tegra210_cpufreq.c +++ b/sys/arm64/nvidia/tegra210/tegra210_cpufreq.c @@ -1,496 +1,496 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Michal Meloun * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" /* CPU voltage table entry */ struct speedo_entry { uint64_t freq; /* Frequency point */ int c0; /* Coeeficient values for */ int c1; /* quadratic equation: */ int c2; /* c2 * speedo^2 + c1 * speedo + c0 */ }; struct cpu_volt_def { int min_uvolt; /* Min allowed CPU voltage */ int max_uvolt; /* Max allowed CPU voltage */ int step_uvolt; /* Step of CPU voltage */ int speedo_scale; /* Scaling factor for cvt */ int speedo_nitems; /* Size of speedo table */ struct speedo_entry *speedo_tbl; /* CPU voltage table */ }; struct cpu_speed_point { uint64_t freq; /* Frequecy */ int uvolt; /* Requested voltage */ }; static struct speedo_entry tegra210_speedo_tbl[] = { {204000000UL, 1007452, -23865, 370}, {306000000UL, 1052709, -24875, 370}, {408000000UL, 1099069, -25895, 370}, {510000000UL, 1146534, -26905, 370}, {612000000UL, 1195102, -27915, 370}, {714000000UL, 1244773, -28925, 370}, {816000000UL, 1295549, -29935, 370}, {918000000UL, 1347428, -30955, 370}, {1020000000UL, 1400411, -31965, 370}, {1122000000UL, 1454497, -32975, 370}, {1224000000UL, 1509687, -33985, 370}, {1326000000UL, 1565981, -35005, 370}, {1428000000UL, 1623379, -36015, 370}, {1530000000UL, 1681880, -37025, 370}, {1632000000UL, 1741485, -38035, 370}, {1734000000UL, 1802194, -39055, 370}, {1836000000UL, 1864006, -40065, 370}, {1912500000UL, 1910780, -40815, 370}, {2014500000UL, 1227000, 0, 0}, {2218500000UL, 1227000, 0, 0}, }; static struct cpu_volt_def tegra210_cpu_volt_def = { .min_uvolt = 900000, /* 0.9 V */ .max_uvolt = 1227000, /* 1.227 */ .step_uvolt = 10000, /* 10 mV */ .speedo_scale = 100, .speedo_nitems = nitems(tegra210_speedo_tbl), .speedo_tbl = tegra210_speedo_tbl, }; static uint64_t cpu_max_freq[] = { 1912500000UL, 1912500000UL, 2218500000UL, 1785000000UL, 1632000000UL, 1912500000UL, 2014500000UL, 1734000000UL, 1683000000UL, 1555500000UL, 1504500000UL, }; static uint64_t cpu_freq_tbl[] = { 204000000UL, 306000000UL, 408000000UL, 510000000UL, 612000000UL, 714000000UL, 816000000UL, 918000000UL, 1020000000UL, 1122000000UL, 1224000000UL, 1326000000UL, 1428000000UL, 1530000000UL, 1632000000UL, 1734000000UL, 1836000000UL, 1912500000UL, 2014500000UL, 2218500000UL, }; struct tegra210_cpufreq_softc { device_t dev; phandle_t node; clk_t clk_cpu_g; clk_t clk_pll_x; clk_t clk_pll_p; clk_t clk_dfll; int process_id; int speedo_id; int speedo_value; uint64_t cpu_max_freq; struct cpu_volt_def *cpu_def; struct cpu_speed_point *speed_points; int nspeed_points; struct cpu_speed_point *act_speed_point; int latency; }; static int cpufreq_lowest_freq = 1; TUNABLE_INT("hw.tegra210.cpufreq.lowest_freq", &cpufreq_lowest_freq); #define DIV_ROUND_CLOSEST(val, div) (((val) + ((div) / 2)) / (div)) #define ROUND_UP(val, div) roundup(val, div) #define ROUND_DOWN(val, div) rounddown(val, div) /* * Compute requesetd voltage for given frequency and SoC process variations, * - compute base voltage from speedo value using speedo table * - round up voltage to next regulator step * - clamp it to regulator limits */ static int freq_to_voltage(struct tegra210_cpufreq_softc *sc, uint64_t freq) { int uv, scale, min_uvolt, max_uvolt, step_uvolt; struct speedo_entry *ent; int i; /* Get speedo entry with higher frequency */ ent = NULL; for (i = 0; i < sc->cpu_def->speedo_nitems; i++) { if (sc->cpu_def->speedo_tbl[i].freq >= freq) { ent = &sc->cpu_def->speedo_tbl[i]; break; } } if (ent == NULL) ent = &sc->cpu_def->speedo_tbl[sc->cpu_def->speedo_nitems - 1]; scale = sc->cpu_def->speedo_scale; /* uV = (c2 * speedo / scale + c1) * speedo / scale + c0) */ uv = DIV_ROUND_CLOSEST(ent->c2 * sc->speedo_value, scale); uv = DIV_ROUND_CLOSEST((uv + ent->c1) * sc->speedo_value, scale) + ent->c0; step_uvolt = sc->cpu_def->step_uvolt; /* Round up it to next regulator step */ uv = ROUND_UP(uv, step_uvolt); /* Clamp result */ min_uvolt = ROUND_UP(sc->cpu_def->min_uvolt, step_uvolt); max_uvolt = ROUND_DOWN(sc->cpu_def->max_uvolt, step_uvolt); if (uv < min_uvolt) uv = min_uvolt; if (uv > max_uvolt) uv = max_uvolt; return (uv); } static void build_speed_points(struct tegra210_cpufreq_softc *sc) { int i; sc->nspeed_points = nitems(cpu_freq_tbl); sc->speed_points = malloc(sizeof(struct cpu_speed_point) * sc->nspeed_points, M_DEVBUF, M_NOWAIT); for (i = 0; i < sc->nspeed_points; i++) { sc->speed_points[i].freq = cpu_freq_tbl[i]; sc->speed_points[i].uvolt = freq_to_voltage(sc, cpu_freq_tbl[i]); } } static struct cpu_speed_point * get_speed_point(struct tegra210_cpufreq_softc *sc, uint64_t freq) { int i; if (sc->speed_points[0].freq >= freq) return (sc->speed_points + 0); for (i = 0; i < sc->nspeed_points - 1; i++) { if (sc->speed_points[i + 1].freq > freq) return (sc->speed_points + i); } return (sc->speed_points + sc->nspeed_points - 1); } static int tegra210_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) { struct tegra210_cpufreq_softc *sc; int i, j; if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); for (i = 0, j = sc->nspeed_points - 1; j >= 0; j--) { if (sc->cpu_max_freq < sc->speed_points[j].freq) continue; sets[i].freq = sc->speed_points[j].freq / 1000000; sets[i].volts = sc->speed_points[j].uvolt / 1000; sets[i].lat = sc->latency; sets[i].dev = dev; i++; } *count = i; return (0); } static int set_cpu_freq(struct tegra210_cpufreq_softc *sc, uint64_t freq) { struct cpu_speed_point *point; int rv; point = get_speed_point(sc, freq); /* Set PLLX frequency */ rv = clk_set_freq(sc->clk_pll_x, point->freq, CLK_SET_ROUND_DOWN); if (rv != 0) { device_printf(sc->dev, "Can't set CPU clock frequency\n"); return (rv); } sc->act_speed_point = point; return (0); } static int tegra210_cpufreq_set(device_t dev, const struct cf_setting *cf) { struct tegra210_cpufreq_softc *sc; uint64_t freq; int rv; if (cf == NULL || cf->freq < 0) return (EINVAL); sc = device_get_softc(dev); freq = cf->freq; if (freq < cpufreq_lowest_freq) freq = cpufreq_lowest_freq; freq *= 1000000; if (freq >= sc->cpu_max_freq) freq = sc->cpu_max_freq; rv = set_cpu_freq(sc, freq); return (rv); } static int tegra210_cpufreq_get(device_t dev, struct cf_setting *cf) { struct tegra210_cpufreq_softc *sc; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); cf->dev = NULL; cf->freq = sc->act_speed_point->freq / 1000000; cf->volts = sc->act_speed_point->uvolt / 1000; /* Transition latency in us. */ cf->lat = sc->latency; /* Driver providing this setting. */ cf->dev = dev; return (0); } static int tegra210_cpufreq_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static int get_fdt_resources(struct tegra210_cpufreq_softc *sc, phandle_t node) { int rv; device_t parent_dev; parent_dev = device_get_parent(sc->dev); rv = clk_get_by_ofw_name(parent_dev, 0, "cpu_g", &sc->clk_cpu_g); if (rv != 0) { device_printf(sc->dev, "Cannot get 'cpu_g' clock: %d\n", rv); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "pll_x", &sc->clk_pll_x); if (rv != 0) { device_printf(sc->dev, "Cannot get 'pll_x' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "pll_p", &sc->clk_pll_p); if (rv != 0) { device_printf(parent_dev, "Cannot get 'pll_p' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(parent_dev, 0, "dfll", &sc->clk_dfll); /* XXX DPLL is not implemented yet */ #if 0 if (rv != 0) { device_printf(sc->dev, "Cannot get 'dfll' clock\n"); return (ENXIO); } #endif return (0); } static void tegra210_cpufreq_identify(driver_t *driver, device_t parent) { phandle_t root; root = OF_finddevice("/"); if (!ofw_bus_node_is_compatible(root, "nvidia,tegra210")) return; if (device_get_unit(parent) != 0) return; - if (device_find_child(parent, "tegra210_cpufreq", -1) != NULL) + if (device_find_child(parent, "tegra210_cpufreq", DEVICE_UNIT_ANY) != NULL) return; - if (BUS_ADD_CHILD(parent, 0, "tegra210_cpufreq", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "tegra210_cpufreq", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int tegra210_cpufreq_probe(device_t dev) { device_set_desc(dev, "CPU Frequency Control"); return (0); } static int tegra210_cpufreq_attach(device_t dev) { struct tegra210_cpufreq_softc *sc; uint64_t freq; int rv; sc = device_get_softc(dev); sc->dev = dev; sc->node = ofw_bus_get_node(device_get_parent(dev)); sc->process_id = tegra_sku_info.cpu_process_id; sc->speedo_id = tegra_sku_info.cpu_speedo_id; sc->speedo_value = tegra_sku_info.cpu_speedo_value; sc->cpu_def = &tegra210_cpu_volt_def; rv = get_fdt_resources(sc, sc->node); if (rv != 0) { return (rv); } build_speed_points(sc); rv = clk_get_freq(sc->clk_cpu_g, &freq); if (rv != 0) { device_printf(dev, "Can't get CPU clock frequency\n"); return (rv); } if (sc->speedo_id < nitems(cpu_max_freq)) sc->cpu_max_freq = cpu_max_freq[sc->speedo_id]; else sc->cpu_max_freq = cpu_max_freq[0]; sc->act_speed_point = get_speed_point(sc, freq); /* Set safe startup CPU frequency. */ rv = set_cpu_freq(sc, 1632000000); if (rv != 0) { device_printf(dev, "Can't set initial CPU clock frequency\n"); return (rv); } /* This device is controlled by cpufreq(4). */ cpufreq_register(dev); return (0); } static int tegra210_cpufreq_detach(device_t dev) { struct tegra210_cpufreq_softc *sc; sc = device_get_softc(dev); cpufreq_unregister(dev); if (sc->clk_cpu_g != NULL) clk_release(sc->clk_cpu_g); if (sc->clk_pll_x != NULL) clk_release(sc->clk_pll_x); if (sc->clk_pll_p != NULL) clk_release(sc->clk_pll_p); if (sc->clk_dfll != NULL) clk_release(sc->clk_dfll); return (0); } static device_method_t tegra210_cpufreq_methods[] = { /* Device interface */ DEVMETHOD(device_identify, tegra210_cpufreq_identify), DEVMETHOD(device_probe, tegra210_cpufreq_probe), DEVMETHOD(device_attach, tegra210_cpufreq_attach), DEVMETHOD(device_detach, tegra210_cpufreq_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, tegra210_cpufreq_set), DEVMETHOD(cpufreq_drv_get, tegra210_cpufreq_get), DEVMETHOD(cpufreq_drv_settings, tegra210_cpufreq_settings), DEVMETHOD(cpufreq_drv_type, tegra210_cpufreq_type), DEVMETHOD_END }; static DEFINE_CLASS_0(tegra210_cpufreq, tegra210_cpufreq_driver, tegra210_cpufreq_methods, sizeof(struct tegra210_cpufreq_softc)); DRIVER_MODULE(tegra210_cpufreq, cpu, tegra210_cpufreq_driver, NULL, NULL); diff --git a/sys/compat/linuxkpi/common/src/linux_i2c.c b/sys/compat/linuxkpi/common/src/linux_i2c.c index d3e69d5df212..f18570202f74 100644 --- a/sys/compat/linuxkpi/common/src/linux_i2c.c +++ b/sys/compat/linuxkpi/common/src/linux_i2c.c @@ -1,380 +1,381 @@ /*- * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "iicbb_if.h" #include "lkpi_iic_if.h" static int lkpi_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs); static int lkpi_i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr); struct lkpi_iic_softc { device_t iicbus; struct i2c_adapter *adapter; }; static struct sx lkpi_sx_i2c; static void lkpi_sysinit_i2c(void *arg __unused) { sx_init(&lkpi_sx_i2c, "lkpi-i2c"); } static void lkpi_sysuninit_i2c(void *arg __unused) { sx_destroy(&lkpi_sx_i2c); } SYSINIT(lkpi_i2c, SI_SUB_DRIVERS, SI_ORDER_ANY, lkpi_sysinit_i2c, NULL); SYSUNINIT(lkpi_i2c, SI_SUB_DRIVERS, SI_ORDER_ANY, lkpi_sysuninit_i2c, NULL); static int lkpi_iic_probe(device_t dev) { device_set_desc(dev, "LinuxKPI I2C"); return (BUS_PROBE_NOWILDCARD); } static int lkpi_iic_attach(device_t dev) { struct lkpi_iic_softc *sc; sc = device_get_softc(dev); - sc->iicbus = device_add_child(dev, "iicbus", -1); + sc->iicbus = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY); if (sc->iicbus == NULL) { device_printf(dev, "Couldn't add iicbus child, aborting\n"); return (ENXIO); } bus_attach_children(dev); return (0); } static int lkpi_iic_detach(device_t dev) { struct lkpi_iic_softc *sc; sc = device_get_softc(dev); if (sc->iicbus) device_delete_child(dev, sc->iicbus); return (0); } static int lkpi_iic_add_adapter(device_t dev, struct i2c_adapter *adapter) { struct lkpi_iic_softc *sc; sc = device_get_softc(dev); sc->adapter = adapter; return (0); } static struct i2c_adapter * lkpi_iic_get_adapter(device_t dev) { struct lkpi_iic_softc *sc; sc = device_get_softc(dev); return (sc->adapter); } static device_method_t lkpi_iic_methods[] = { /* device interface */ DEVMETHOD(device_probe, lkpi_iic_probe), DEVMETHOD(device_attach, lkpi_iic_attach), DEVMETHOD(device_detach, lkpi_iic_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* iicbus interface */ DEVMETHOD(iicbus_transfer, lkpi_i2c_transfer), DEVMETHOD(iicbus_reset, lkpi_i2c_reset), DEVMETHOD(iicbus_callback, iicbus_null_callback), /* lkpi_iic interface */ DEVMETHOD(lkpi_iic_add_adapter, lkpi_iic_add_adapter), DEVMETHOD(lkpi_iic_get_adapter, lkpi_iic_get_adapter), DEVMETHOD_END }; driver_t lkpi_iic_driver = { "lkpi_iic", lkpi_iic_methods, sizeof(struct lkpi_iic_softc), }; DRIVER_MODULE(lkpi_iic, drmn, lkpi_iic_driver, 0, 0); DRIVER_MODULE(lkpi_iic, drm, lkpi_iic_driver, 0, 0); DRIVER_MODULE(iicbus, lkpi_iic, iicbus_driver, 0, 0); MODULE_DEPEND(linuxkpi, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); static int lkpi_i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { /* That doesn't seems to be supported in linux */ return (0); } static int i2c_check_for_quirks(struct i2c_adapter *adapter, struct iic_msg *msgs, uint32_t nmsgs) { const struct i2c_adapter_quirks *quirks; device_t dev; int i, max_nmsgs; bool check_len; dev = adapter->dev.parent->bsddev; quirks = adapter->quirks; if (quirks == NULL) return (0); check_len = true; max_nmsgs = quirks->max_num_msgs; if (quirks->flags & I2C_AQ_COMB) { max_nmsgs = 2; if (nmsgs == 2) { if (quirks->flags & I2C_AQ_COMB_WRITE_FIRST && msgs[0].flags & IIC_M_RD) { device_printf(dev, "Error: " "first combined message must be write\n"); return (EOPNOTSUPP); } if (quirks->flags & I2C_AQ_COMB_READ_SECOND && !(msgs[1].flags & IIC_M_RD)) { device_printf(dev, "Error: " "second combined message must be read\n"); return (EOPNOTSUPP); } if (quirks->flags & I2C_AQ_COMB_SAME_ADDR && msgs[0].slave != msgs[1].slave) { device_printf(dev, "Error: " "combined message must be use the same " "address\n"); return (EOPNOTSUPP); } if (quirks->max_comb_1st_msg_len && msgs[0].len > quirks->max_comb_1st_msg_len) { device_printf(dev, "Error: " "message too long: %hu > %hu max\n", msgs[0].len, quirks->max_comb_1st_msg_len); return (EOPNOTSUPP); } if (quirks->max_comb_2nd_msg_len && msgs[1].len > quirks->max_comb_2nd_msg_len) { device_printf(dev, "Error: " "message too long: %hu > %hu max\n", msgs[1].len, quirks->max_comb_2nd_msg_len); return (EOPNOTSUPP); } check_len = false; } } if (max_nmsgs && nmsgs > max_nmsgs) { device_printf(dev, "Error: too many messages: %d > %d max\n", nmsgs, max_nmsgs); return (EOPNOTSUPP); } for (i = 0; i < nmsgs; i++) { if (msgs[i].flags & IIC_M_RD) { if (check_len && quirks->max_read_len && msgs[i].len > quirks->max_read_len) { device_printf(dev, "Error: " "message %d too long: %hu > %hu max\n", i, msgs[i].len, quirks->max_read_len); return (EOPNOTSUPP); } if (quirks->flags & I2C_AQ_NO_ZERO_LEN_READ && msgs[i].len == 0) { device_printf(dev, "Error: message %d of length 0\n", i); return (EOPNOTSUPP); } } else { if (check_len && quirks->max_write_len && msgs[i].len > quirks->max_write_len) { device_printf(dev, "Message %d too long: %hu > %hu max\n", i, msgs[i].len, quirks->max_write_len); return (EOPNOTSUPP); } if (quirks->flags & I2C_AQ_NO_ZERO_LEN_WRITE && msgs[i].len == 0) { device_printf(dev, "Error: message %d of length 0\n", i); return (EOPNOTSUPP); } } } return (0); } static int lkpi_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct lkpi_iic_softc *sc; struct i2c_msg *linux_msgs; int i, ret = 0; sc = device_get_softc(dev); if (sc->adapter == NULL) return (ENXIO); ret = i2c_check_for_quirks(sc->adapter, msgs, nmsgs); if (ret != 0) return (ret); linux_set_current(curthread); linux_msgs = malloc(sizeof(struct i2c_msg) * nmsgs, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0; i < nmsgs; i++) { linux_msgs[i].addr = msgs[i].slave >> 1; linux_msgs[i].len = msgs[i].len; linux_msgs[i].buf = msgs[i].buf; if (msgs[i].flags & IIC_M_RD) { linux_msgs[i].flags |= I2C_M_RD; for (int j = 0; j < msgs[i].len; j++) msgs[i].buf[j] = 0; } if (msgs[i].flags & IIC_M_NOSTART) linux_msgs[i].flags |= I2C_M_NOSTART; } ret = i2c_transfer(sc->adapter, linux_msgs, nmsgs); free(linux_msgs, M_DEVBUF); if (ret < 0) return (-ret); return (0); } int lkpi_i2c_add_adapter(struct i2c_adapter *adapter) { device_t lkpi_iic; if (adapter->name[0] == '\0') return (-EINVAL); if (bootverbose) device_printf(adapter->dev.parent->bsddev, "Adding i2c adapter %s\n", adapter->name); sx_xlock(&lkpi_sx_i2c); - lkpi_iic = device_add_child(adapter->dev.parent->bsddev, "lkpi_iic", -1); + lkpi_iic = device_add_child(adapter->dev.parent->bsddev, "lkpi_iic", + DEVICE_UNIT_ANY); if (lkpi_iic == NULL) { device_printf(adapter->dev.parent->bsddev, "Couldn't add lkpi_iic\n"); sx_xunlock(&lkpi_sx_i2c); return (ENXIO); } bus_topo_lock(); bus_attach_children(adapter->dev.parent->bsddev); bus_topo_unlock(); LKPI_IIC_ADD_ADAPTER(lkpi_iic, adapter); sx_xunlock(&lkpi_sx_i2c); return (0); } int lkpi_i2c_del_adapter(struct i2c_adapter *adapter) { device_t child; int unit, rv; if (adapter == NULL) return (-EINVAL); if (bootverbose) device_printf(adapter->dev.parent->bsddev, "Removing i2c adapter %s\n", adapter->name); sx_xlock(&lkpi_sx_i2c); unit = 0; while ((child = device_find_child(adapter->dev.parent->bsddev, "lkpi_iic", unit++)) != NULL) { if (adapter == LKPI_IIC_GET_ADAPTER(child)) { bus_topo_lock(); device_delete_child(adapter->dev.parent->bsddev, child); bus_topo_unlock(); rv = 0; goto out; } } unit = 0; while ((child = device_find_child(adapter->dev.parent->bsddev, "lkpi_iicbb", unit++)) != NULL) { if (adapter == LKPI_IIC_GET_ADAPTER(child)) { bus_topo_lock(); device_delete_child(adapter->dev.parent->bsddev, child); bus_topo_unlock(); rv = 0; goto out; } } rv = -EINVAL; out: sx_xunlock(&lkpi_sx_i2c); return (rv); } diff --git a/sys/compat/linuxkpi/common/src/linux_i2cbb.c b/sys/compat/linuxkpi/common/src/linux_i2cbb.c index f266a1404af7..48a018ec2533 100644 --- a/sys/compat/linuxkpi/common/src/linux_i2cbb.c +++ b/sys/compat/linuxkpi/common/src/linux_i2cbb.c @@ -1,324 +1,325 @@ /*- * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "iicbb_if.h" #include "lkpi_iic_if.h" static void lkpi_iicbb_setsda(device_t dev, int val); static void lkpi_iicbb_setscl(device_t dev, int val); static int lkpi_iicbb_getscl(device_t dev); static int lkpi_iicbb_getsda(device_t dev); static int lkpi_iicbb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr); static int lkpi_iicbb_pre_xfer(device_t dev); static void lkpi_iicbb_post_xfer(device_t dev); struct lkpi_iicbb_softc { device_t iicbb; struct i2c_adapter *adapter; }; static struct sx lkpi_sx_i2cbb; static void lkpi_sysinit_i2cbb(void *arg __unused) { sx_init(&lkpi_sx_i2cbb, "lkpi-i2cbb"); } static void lkpi_sysuninit_i2cbb(void *arg __unused) { sx_destroy(&lkpi_sx_i2cbb); } SYSINIT(lkpi_i2cbb, SI_SUB_DRIVERS, SI_ORDER_ANY, lkpi_sysinit_i2cbb, NULL); SYSUNINIT(lkpi_i2cbb, SI_SUB_DRIVERS, SI_ORDER_ANY, lkpi_sysuninit_i2cbb, NULL); static int lkpi_iicbb_probe(device_t dev) { device_set_desc(dev, "LinuxKPI I2CBB"); return (BUS_PROBE_NOWILDCARD); } static int lkpi_iicbb_attach(device_t dev) { struct lkpi_iicbb_softc *sc; sc = device_get_softc(dev); - sc->iicbb = device_add_child(dev, "iicbb", -1); + sc->iicbb = device_add_child(dev, "iicbb", DEVICE_UNIT_ANY); if (sc->iicbb == NULL) { device_printf(dev, "Couldn't add iicbb child, aborting\n"); return (ENXIO); } bus_attach_children(dev); return (0); } static int lkpi_iicbb_detach(device_t dev) { struct lkpi_iicbb_softc *sc; sc = device_get_softc(dev); if (sc->iicbb) device_delete_child(dev, sc->iicbb); return (0); } static int lkpi_iicbb_add_adapter(device_t dev, struct i2c_adapter *adapter) { struct lkpi_iicbb_softc *sc; struct i2c_algo_bit_data *algo_data; sc = device_get_softc(dev); sc->adapter = adapter; /* * Set iicbb timing parameters deriving speed from the protocol delay. */ algo_data = adapter->algo_data; if (algo_data->udelay != 0) IICBUS_RESET(sc->iicbb, 1000000 / algo_data->udelay, 0, NULL); return (0); } static struct i2c_adapter * lkpi_iicbb_get_adapter(device_t dev) { struct lkpi_iicbb_softc *sc; sc = device_get_softc(dev); return (sc->adapter); } static device_method_t lkpi_iicbb_methods[] = { /* device interface */ DEVMETHOD(device_probe, lkpi_iicbb_probe), DEVMETHOD(device_attach, lkpi_iicbb_attach), DEVMETHOD(device_detach, lkpi_iicbb_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* iicbb interface */ DEVMETHOD(iicbb_setsda, lkpi_iicbb_setsda), DEVMETHOD(iicbb_setscl, lkpi_iicbb_setscl), DEVMETHOD(iicbb_getsda, lkpi_iicbb_getsda), DEVMETHOD(iicbb_getscl, lkpi_iicbb_getscl), DEVMETHOD(iicbb_reset, lkpi_iicbb_reset), DEVMETHOD(iicbb_pre_xfer, lkpi_iicbb_pre_xfer), DEVMETHOD(iicbb_post_xfer, lkpi_iicbb_post_xfer), /* lkpi_iicbb interface */ DEVMETHOD(lkpi_iic_add_adapter, lkpi_iicbb_add_adapter), DEVMETHOD(lkpi_iic_get_adapter, lkpi_iicbb_get_adapter), DEVMETHOD_END }; driver_t lkpi_iicbb_driver = { "lkpi_iicbb", lkpi_iicbb_methods, sizeof(struct lkpi_iicbb_softc), }; DRIVER_MODULE(lkpi_iicbb, drmn, lkpi_iicbb_driver, 0, 0); DRIVER_MODULE(lkpi_iicbb, drm, lkpi_iicbb_driver, 0, 0); DRIVER_MODULE(iicbb, lkpi_iicbb, iicbb_driver, 0, 0); MODULE_DEPEND(linuxkpi, iicbb, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); static void lkpi_iicbb_setsda(device_t dev, int val) { struct lkpi_iicbb_softc *sc; struct i2c_algo_bit_data *algo_data; sc = device_get_softc(dev); algo_data = sc->adapter->algo_data; algo_data->setsda(algo_data->data, val); } static void lkpi_iicbb_setscl(device_t dev, int val) { struct lkpi_iicbb_softc *sc; struct i2c_algo_bit_data *algo_data; sc = device_get_softc(dev); algo_data = sc->adapter->algo_data; algo_data->setscl(algo_data->data, val); } static int lkpi_iicbb_getscl(device_t dev) { struct lkpi_iicbb_softc *sc; struct i2c_algo_bit_data *algo_data; int ret; sc = device_get_softc(dev); algo_data = sc->adapter->algo_data; ret = algo_data->getscl(algo_data->data); return (ret); } static int lkpi_iicbb_getsda(device_t dev) { struct lkpi_iicbb_softc *sc; struct i2c_algo_bit_data *algo_data; int ret; sc = device_get_softc(dev); algo_data = sc->adapter->algo_data; ret = algo_data->getsda(algo_data->data); return (ret); } static int lkpi_iicbb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { /* That doesn't seems to be supported in linux */ return (0); } static int lkpi_iicbb_pre_xfer(device_t dev) { struct lkpi_iicbb_softc *sc; struct i2c_algo_bit_data *algo_data; int rc = 0; sc = device_get_softc(dev); algo_data = sc->adapter->algo_data; if (algo_data->pre_xfer != 0) rc = algo_data->pre_xfer(sc->adapter); return (rc); } static void lkpi_iicbb_post_xfer(device_t dev) { struct lkpi_iicbb_softc *sc; struct i2c_algo_bit_data *algo_data; sc = device_get_softc(dev); algo_data = sc->adapter->algo_data; if (algo_data->post_xfer != NULL) algo_data->post_xfer(sc->adapter); } int lkpi_i2cbb_transfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int nmsgs) { struct iic_msg *bsd_msgs; int ret = ENXIO; linux_set_current(curthread); bsd_msgs = malloc(sizeof(struct iic_msg) * nmsgs, M_DEVBUF, M_WAITOK | M_ZERO); for (int i = 0; i < nmsgs; i++) { bsd_msgs[i].slave = msgs[i].addr << 1; bsd_msgs[i].len = msgs[i].len; bsd_msgs[i].buf = msgs[i].buf; if (msgs[i].flags & I2C_M_RD) bsd_msgs[i].flags |= IIC_M_RD; if (msgs[i].flags & I2C_M_NOSTART) bsd_msgs[i].flags |= IIC_M_NOSTART; } for (int unit = 0; ; unit++) { device_t child; struct lkpi_iicbb_softc *sc; child = device_find_child(adapter->dev.parent->bsddev, "lkpi_iicbb", unit); if (child == NULL) break; if (adapter == LKPI_IIC_GET_ADAPTER(child)) { sc = device_get_softc(child); ret = IICBUS_TRANSFER(sc->iicbb, bsd_msgs, nmsgs); ret = iic2errno(ret); break; } } free(bsd_msgs, M_DEVBUF); if (ret != 0) return (-ret); return (nmsgs); } int lkpi_i2c_bit_add_bus(struct i2c_adapter *adapter) { device_t lkpi_iicbb; if (bootverbose) device_printf(adapter->dev.parent->bsddev, "Adding i2c adapter %s\n", adapter->name); sx_xlock(&lkpi_sx_i2cbb); - lkpi_iicbb = device_add_child(adapter->dev.parent->bsddev, "lkpi_iicbb", -1); + lkpi_iicbb = device_add_child(adapter->dev.parent->bsddev, "lkpi_iicbb", + DEVICE_UNIT_ANY); if (lkpi_iicbb == NULL) { device_printf(adapter->dev.parent->bsddev, "Couldn't add lkpi_iicbb\n"); sx_xunlock(&lkpi_sx_i2cbb); return (ENXIO); } bus_topo_lock(); bus_attach_children(adapter->dev.parent->bsddev); bus_topo_unlock(); LKPI_IIC_ADD_ADAPTER(lkpi_iicbb, adapter); sx_xunlock(&lkpi_sx_i2cbb); return (0); } diff --git a/sys/crypto/aesni/aesni.c b/sys/crypto/aesni/aesni.c index 6a551577d1b9..be8b234d796c 100644 --- a/sys/crypto/aesni/aesni.c +++ b/sys/crypto/aesni/aesni.c @@ -1,868 +1,868 @@ /*- * Copyright (c) 2005-2008 Pawel Jakub Dawidek * Copyright (c) 2010 Konstantin Belousov * Copyright (c) 2014-2021 The FreeBSD Foundation * Copyright (c) 2017 Conrad Meyer * All rights reserved. * * Portions of this software were developed by John-Mark Gurney * under sponsorship of the FreeBSD Foundation and * Rubicon Communications, LLC (Netgate). * * Portions of this software were developed by Ararat River * Consulting, LLC under sponsorship of the FreeBSD Foundation. * * 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, 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct aesni_softc { int32_t cid; bool has_aes; bool has_sha; }; static int aesni_cipher_setup(struct aesni_session *ses, const struct crypto_session_params *csp); static int aesni_cipher_process(struct aesni_session *ses, struct cryptop *crp); static int aesni_cipher_crypt(struct aesni_session *ses, struct cryptop *crp, const struct crypto_session_params *csp); static int aesni_cipher_mac(struct aesni_session *ses, struct cryptop *crp, const struct crypto_session_params *csp); MALLOC_DEFINE(M_AESNI, "aesni_data", "AESNI Data"); static void aesni_identify(driver_t *drv, device_t parent) { /* NB: order 10 is so we get attached after h/w devices */ - if (device_find_child(parent, "aesni", -1) == NULL && - BUS_ADD_CHILD(parent, 10, "aesni", -1) == 0) + if (device_find_child(parent, "aesni", DEVICE_UNIT_ANY) == NULL && + BUS_ADD_CHILD(parent, 10, "aesni", DEVICE_UNIT_ANY) == 0) panic("aesni: could not attach"); } static void detect_cpu_features(bool *has_aes, bool *has_sha) { *has_aes = ((cpu_feature2 & CPUID2_AESNI) != 0 && (cpu_feature2 & CPUID2_SSE41) != 0); *has_sha = ((cpu_stdext_feature & CPUID_STDEXT_SHA) != 0 && (cpu_feature2 & CPUID2_SSSE3) != 0); } static int aesni_probe(device_t dev) { bool has_aes, has_sha; detect_cpu_features(&has_aes, &has_sha); if (!has_aes && !has_sha) { device_printf(dev, "No AES or SHA support.\n"); return (EINVAL); } else if (has_aes && has_sha) device_set_desc(dev, "AES-CBC,AES-CCM,AES-GCM,AES-ICM,AES-XTS,SHA1,SHA256"); else if (has_aes) device_set_desc(dev, "AES-CBC,AES-CCM,AES-GCM,AES-ICM,AES-XTS"); else device_set_desc(dev, "SHA1,SHA256"); return (0); } static int aesni_attach(device_t dev) { struct aesni_softc *sc; sc = device_get_softc(dev); sc->cid = crypto_get_driverid(dev, sizeof(struct aesni_session), CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_SYNC | CRYPTOCAP_F_ACCEL_SOFTWARE); if (sc->cid < 0) { device_printf(dev, "Could not get crypto driver id.\n"); return (ENOMEM); } detect_cpu_features(&sc->has_aes, &sc->has_sha); return (0); } static int aesni_detach(device_t dev) { struct aesni_softc *sc; sc = device_get_softc(dev); crypto_unregister_all(sc->cid); return (0); } static bool aesni_auth_supported(struct aesni_softc *sc, const struct crypto_session_params *csp) { if (!sc->has_sha) return (false); switch (csp->csp_auth_alg) { case CRYPTO_SHA1: case CRYPTO_SHA2_224: case CRYPTO_SHA2_256: case CRYPTO_SHA1_HMAC: case CRYPTO_SHA2_224_HMAC: case CRYPTO_SHA2_256_HMAC: break; default: return (false); } return (true); } static bool aesni_cipher_supported(struct aesni_softc *sc, const struct crypto_session_params *csp) { if (!sc->has_aes) return (false); switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: case CRYPTO_AES_ICM: switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: CRYPTDEB("invalid CBC/ICM key length"); return (false); } if (csp->csp_ivlen != AES_BLOCK_LEN) return (false); break; case CRYPTO_AES_XTS: switch (csp->csp_cipher_klen * 8) { case 256: case 512: break; default: CRYPTDEB("invalid XTS key length"); return (false); } if (csp->csp_ivlen != AES_XTS_IV_LEN) return (false); break; default: return (false); } return (true); } #define SUPPORTED_SES (CSP_F_SEPARATE_OUTPUT | CSP_F_SEPARATE_AAD | CSP_F_ESN) static int aesni_probesession(device_t dev, const struct crypto_session_params *csp) { struct aesni_softc *sc; sc = device_get_softc(dev); if ((csp->csp_flags & ~(SUPPORTED_SES)) != 0) return (EINVAL); switch (csp->csp_mode) { case CSP_MODE_DIGEST: if (!aesni_auth_supported(sc, csp)) return (EINVAL); break; case CSP_MODE_CIPHER: if (!aesni_cipher_supported(sc, csp)) return (EINVAL); break; case CSP_MODE_AEAD: switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: CRYPTDEB("invalid GCM key length"); return (EINVAL); } if (csp->csp_auth_mlen != 0 && csp->csp_auth_mlen != GMAC_DIGEST_LEN) return (EINVAL); if (!sc->has_aes) return (EINVAL); break; case CRYPTO_AES_CCM_16: switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: CRYPTDEB("invalid CCM key length"); return (EINVAL); } if (!sc->has_aes) return (EINVAL); break; default: return (EINVAL); } break; case CSP_MODE_ETA: if (!aesni_auth_supported(sc, csp) || !aesni_cipher_supported(sc, csp)) return (EINVAL); break; default: return (EINVAL); } return (CRYPTODEV_PROBE_ACCEL_SOFTWARE); } static int aesni_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { struct aesni_session *ses; int error; ses = crypto_get_driver_session(cses); switch (csp->csp_mode) { case CSP_MODE_DIGEST: case CSP_MODE_CIPHER: case CSP_MODE_AEAD: case CSP_MODE_ETA: break; default: return (EINVAL); } error = aesni_cipher_setup(ses, csp); if (error != 0) { CRYPTDEB("setup failed"); return (error); } return (0); } static int aesni_process(device_t dev, struct cryptop *crp, int hint __unused) { struct aesni_session *ses; int error; ses = crypto_get_driver_session(crp->crp_session); error = aesni_cipher_process(ses, crp); crp->crp_etype = error; crypto_done(crp); return (0); } static uint8_t * aesni_cipher_alloc(struct cryptop *crp, int start, int length, bool *allocated) { uint8_t *addr; addr = crypto_contiguous_subsegment(crp, start, length); if (addr != NULL) { *allocated = false; return (addr); } addr = malloc(length, M_AESNI, M_NOWAIT); if (addr != NULL) { *allocated = true; crypto_copydata(crp, start, length, addr); } else *allocated = false; return (addr); } static device_method_t aesni_methods[] = { DEVMETHOD(device_identify, aesni_identify), DEVMETHOD(device_probe, aesni_probe), DEVMETHOD(device_attach, aesni_attach), DEVMETHOD(device_detach, aesni_detach), DEVMETHOD(cryptodev_probesession, aesni_probesession), DEVMETHOD(cryptodev_newsession, aesni_newsession), DEVMETHOD(cryptodev_process, aesni_process), DEVMETHOD_END }; static driver_t aesni_driver = { "aesni", aesni_methods, sizeof(struct aesni_softc), }; DRIVER_MODULE(aesni, nexus, aesni_driver, 0, 0); MODULE_VERSION(aesni, 1); MODULE_DEPEND(aesni, crypto, 1, 1, 1); static int intel_sha1_update(void *vctx, const void *vdata, u_int datalen) { struct sha1_ctxt *ctx = vctx; const char *data = vdata; size_t gaplen; size_t gapstart; size_t off; size_t copysiz; u_int blocks; off = 0; /* Do any aligned blocks without redundant copying. */ if (datalen >= 64 && ctx->count % 64 == 0) { blocks = datalen / 64; ctx->c.b64[0] += blocks * 64 * 8; intel_sha1_step(ctx->h.b32, data + off, blocks); off += blocks * 64; } while (off < datalen) { gapstart = ctx->count % 64; gaplen = 64 - gapstart; copysiz = (gaplen < datalen - off) ? gaplen : datalen - off; bcopy(&data[off], &ctx->m.b8[gapstart], copysiz); ctx->count += copysiz; ctx->count %= 64; ctx->c.b64[0] += copysiz * 8; if (ctx->count % 64 == 0) intel_sha1_step(ctx->h.b32, (void *)ctx->m.b8, 1); off += copysiz; } return (0); } static void SHA1_Init_fn(void *ctx) { sha1_init(ctx); } static void SHA1_Finalize_fn(void *digest, void *ctx) { sha1_result(ctx, digest); } static int intel_sha256_update(void *vctx, const void *vdata, u_int len) { SHA256_CTX *ctx = vctx; uint64_t bitlen; uint32_t r; u_int blocks; const unsigned char *src = vdata; /* Number of bytes left in the buffer from previous updates */ r = (ctx->count >> 3) & 0x3f; /* Convert the length into a number of bits */ bitlen = len << 3; /* Update number of bits */ ctx->count += bitlen; /* Handle the case where we don't need to perform any transforms */ if (len < 64 - r) { memcpy(&ctx->buf[r], src, len); return (0); } /* Finish the current block */ memcpy(&ctx->buf[r], src, 64 - r); intel_sha256_step(ctx->state, ctx->buf, 1); src += 64 - r; len -= 64 - r; /* Perform complete blocks */ if (len >= 64) { blocks = len / 64; intel_sha256_step(ctx->state, src, blocks); src += blocks * 64; len -= blocks * 64; } /* Copy left over data into buffer */ memcpy(ctx->buf, src, len); return (0); } static void SHA224_Init_fn(void *ctx) { SHA224_Init(ctx); } static void SHA224_Finalize_fn(void *digest, void *ctx) { SHA224_Final(digest, ctx); } static void SHA256_Init_fn(void *ctx) { SHA256_Init(ctx); } static void SHA256_Finalize_fn(void *digest, void *ctx) { SHA256_Final(digest, ctx); } static int aesni_authprepare(struct aesni_session *ses, int klen) { if (klen > SHA1_BLOCK_LEN) return (EINVAL); if ((ses->hmac && klen == 0) || (!ses->hmac && klen != 0)) return (EINVAL); return (0); } static int aesni_cipher_setup(struct aesni_session *ses, const struct crypto_session_params *csp) { uint8_t *schedbase; int error; bool kt; schedbase = (uint8_t *)roundup2((uintptr_t)ses->schedules, AES_SCHED_ALIGN); ses->enc_schedule = schedbase; ses->dec_schedule = schedbase + AES_SCHED_LEN; ses->xts_schedule = schedbase + AES_SCHED_LEN * 2; switch (csp->csp_auth_alg) { case CRYPTO_SHA1_HMAC: ses->hmac = true; /* FALLTHROUGH */ case CRYPTO_SHA1: ses->hash_len = SHA1_HASH_LEN; ses->hash_init = SHA1_Init_fn; ses->hash_update = intel_sha1_update; ses->hash_finalize = SHA1_Finalize_fn; break; case CRYPTO_SHA2_224_HMAC: ses->hmac = true; /* FALLTHROUGH */ case CRYPTO_SHA2_224: ses->hash_len = SHA2_224_HASH_LEN; ses->hash_init = SHA224_Init_fn; ses->hash_update = intel_sha256_update; ses->hash_finalize = SHA224_Finalize_fn; break; case CRYPTO_SHA2_256_HMAC: ses->hmac = true; /* FALLTHROUGH */ case CRYPTO_SHA2_256: ses->hash_len = SHA2_256_HASH_LEN; ses->hash_init = SHA256_Init_fn; ses->hash_update = intel_sha256_update; ses->hash_finalize = SHA256_Finalize_fn; break; } if (ses->hash_len != 0) { if (csp->csp_auth_mlen == 0) ses->mlen = ses->hash_len; else ses->mlen = csp->csp_auth_mlen; error = aesni_authprepare(ses, csp->csp_auth_klen); if (error != 0) return (error); } else if (csp->csp_cipher_alg == CRYPTO_AES_CCM_16) { if (csp->csp_auth_mlen == 0) ses->mlen = AES_CBC_MAC_HASH_LEN; else ses->mlen = csp->csp_auth_mlen; } kt = (csp->csp_cipher_alg == 0); if (!kt) { fpu_kern_enter(curthread, NULL, FPU_KERN_NORMAL | FPU_KERN_NOCTX); } error = 0; if (csp->csp_cipher_key != NULL) aesni_cipher_setup_common(ses, csp, csp->csp_cipher_key, csp->csp_cipher_klen); if (!kt) { fpu_kern_leave(curthread, NULL); } return (error); } static int aesni_cipher_process(struct aesni_session *ses, struct cryptop *crp) { const struct crypto_session_params *csp; int error; csp = crypto_get_params(crp->crp_session); switch (csp->csp_cipher_alg) { case CRYPTO_AES_CCM_16: if (crp->crp_payload_length > ccm_max_payload_length(csp)) return (EMSGSIZE); /* FALLTHROUGH */ case CRYPTO_AES_ICM: case CRYPTO_AES_NIST_GCM_16: if ((crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0) return (EINVAL); break; case CRYPTO_AES_CBC: case CRYPTO_AES_XTS: /* CBC & XTS can only handle full blocks for now */ if ((crp->crp_payload_length % AES_BLOCK_LEN) != 0) return (EINVAL); break; } /* Do work */ if (csp->csp_mode == CSP_MODE_ETA) { if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { error = aesni_cipher_crypt(ses, crp, csp); if (error == 0) error = aesni_cipher_mac(ses, crp, csp); } else { error = aesni_cipher_mac(ses, crp, csp); if (error == 0) error = aesni_cipher_crypt(ses, crp, csp); } } else if (csp->csp_mode == CSP_MODE_DIGEST) error = aesni_cipher_mac(ses, crp, csp); else error = aesni_cipher_crypt(ses, crp, csp); return (error); } static int aesni_cipher_crypt(struct aesni_session *ses, struct cryptop *crp, const struct crypto_session_params *csp) { uint8_t iv[AES_BLOCK_LEN], tag[GMAC_DIGEST_LEN]; uint8_t *authbuf, *buf, *outbuf; int error; bool encflag, allocated, authallocated, outallocated, outcopy; if (crp->crp_payload_length == 0) { buf = NULL; allocated = false; } else { buf = aesni_cipher_alloc(crp, crp->crp_payload_start, crp->crp_payload_length, &allocated); if (buf == NULL) return (ENOMEM); } outallocated = false; authallocated = false; authbuf = NULL; if (csp->csp_cipher_alg == CRYPTO_AES_NIST_GCM_16 || csp->csp_cipher_alg == CRYPTO_AES_CCM_16) { if (crp->crp_aad_length == 0) { authbuf = NULL; } else if (crp->crp_aad != NULL) { authbuf = crp->crp_aad; } else { authbuf = aesni_cipher_alloc(crp, crp->crp_aad_start, crp->crp_aad_length, &authallocated); if (authbuf == NULL) { error = ENOMEM; goto out; } } } if (CRYPTO_HAS_OUTPUT_BUFFER(crp) && crp->crp_payload_length > 0) { outbuf = crypto_buffer_contiguous_subsegment(&crp->crp_obuf, crp->crp_payload_output_start, crp->crp_payload_length); if (outbuf == NULL) { outcopy = true; if (allocated) outbuf = buf; else { outbuf = malloc(crp->crp_payload_length, M_AESNI, M_NOWAIT); if (outbuf == NULL) { error = ENOMEM; goto out; } outallocated = true; } } else outcopy = false; } else { outbuf = buf; outcopy = allocated; } fpu_kern_enter(curthread, NULL, FPU_KERN_NORMAL | FPU_KERN_NOCTX); error = 0; encflag = CRYPTO_OP_IS_ENCRYPT(crp->crp_op); if (crp->crp_cipher_key != NULL) aesni_cipher_setup_common(ses, csp, crp->crp_cipher_key, csp->csp_cipher_klen); crypto_read_iv(crp, iv); switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: if (encflag) aesni_encrypt_cbc(ses->rounds, ses->enc_schedule, crp->crp_payload_length, buf, outbuf, iv); else { if (buf != outbuf) memcpy(outbuf, buf, crp->crp_payload_length); aesni_decrypt_cbc(ses->rounds, ses->dec_schedule, crp->crp_payload_length, outbuf, iv); } break; case CRYPTO_AES_ICM: /* encryption & decryption are the same */ aesni_encrypt_icm(ses->rounds, ses->enc_schedule, crp->crp_payload_length, buf, outbuf, iv); break; case CRYPTO_AES_XTS: if (encflag) aesni_encrypt_xts(ses->rounds, ses->enc_schedule, ses->xts_schedule, crp->crp_payload_length, buf, outbuf, iv); else aesni_decrypt_xts(ses->rounds, ses->dec_schedule, ses->xts_schedule, crp->crp_payload_length, buf, outbuf, iv); break; case CRYPTO_AES_NIST_GCM_16: if (encflag) { memset(tag, 0, sizeof(tag)); AES_GCM_encrypt(buf, outbuf, authbuf, iv, tag, crp->crp_payload_length, crp->crp_aad_length, csp->csp_ivlen, ses->enc_schedule, ses->rounds); crypto_copyback(crp, crp->crp_digest_start, sizeof(tag), tag); } else { crypto_copydata(crp, crp->crp_digest_start, sizeof(tag), tag); if (!AES_GCM_decrypt(buf, outbuf, authbuf, iv, tag, crp->crp_payload_length, crp->crp_aad_length, csp->csp_ivlen, ses->enc_schedule, ses->rounds)) error = EBADMSG; } break; case CRYPTO_AES_CCM_16: if (encflag) { memset(tag, 0, sizeof(tag)); AES_CCM_encrypt(buf, outbuf, authbuf, iv, tag, crp->crp_payload_length, crp->crp_aad_length, csp->csp_ivlen, ses->mlen, ses->enc_schedule, ses->rounds); crypto_copyback(crp, crp->crp_digest_start, ses->mlen, tag); } else { crypto_copydata(crp, crp->crp_digest_start, ses->mlen, tag); if (!AES_CCM_decrypt(buf, outbuf, authbuf, iv, tag, crp->crp_payload_length, crp->crp_aad_length, csp->csp_ivlen, ses->mlen, ses->enc_schedule, ses->rounds)) error = EBADMSG; } break; } fpu_kern_leave(curthread, NULL); if (outcopy && error == 0) crypto_copyback(crp, CRYPTO_HAS_OUTPUT_BUFFER(crp) ? crp->crp_payload_output_start : crp->crp_payload_start, crp->crp_payload_length, outbuf); out: if (allocated) zfree(buf, M_AESNI); if (authallocated) zfree(authbuf, M_AESNI); if (outallocated) zfree(outbuf, M_AESNI); explicit_bzero(iv, sizeof(iv)); explicit_bzero(tag, sizeof(tag)); return (error); } static int aesni_cipher_mac(struct aesni_session *ses, struct cryptop *crp, const struct crypto_session_params *csp) { union { struct SHA256Context sha2 __aligned(16); struct sha1_ctxt sha1 __aligned(16); } sctx; uint32_t res[SHA2_256_HASH_LEN / sizeof(uint32_t)]; const uint8_t *key; int i, keylen; if (crp->crp_auth_key != NULL) key = crp->crp_auth_key; else key = csp->csp_auth_key; keylen = csp->csp_auth_klen; fpu_kern_enter(curthread, NULL, FPU_KERN_NORMAL | FPU_KERN_NOCTX); if (ses->hmac) { uint8_t hmac_key[SHA1_BLOCK_LEN] __aligned(16); /* Inner hash: (K ^ IPAD) || data */ ses->hash_init(&sctx); for (i = 0; i < keylen; i++) hmac_key[i] = key[i] ^ HMAC_IPAD_VAL; for (i = keylen; i < sizeof(hmac_key); i++) hmac_key[i] = 0 ^ HMAC_IPAD_VAL; ses->hash_update(&sctx, hmac_key, sizeof(hmac_key)); if (crp->crp_aad != NULL) ses->hash_update(&sctx, crp->crp_aad, crp->crp_aad_length); else crypto_apply(crp, crp->crp_aad_start, crp->crp_aad_length, ses->hash_update, &sctx); if (CRYPTO_HAS_OUTPUT_BUFFER(crp) && CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) crypto_apply_buf(&crp->crp_obuf, crp->crp_payload_output_start, crp->crp_payload_length, ses->hash_update, &sctx); else crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, ses->hash_update, &sctx); if (csp->csp_flags & CSP_F_ESN) ses->hash_update(&sctx, crp->crp_esn, 4); ses->hash_finalize(res, &sctx); /* Outer hash: (K ^ OPAD) || inner hash */ ses->hash_init(&sctx); for (i = 0; i < keylen; i++) hmac_key[i] = key[i] ^ HMAC_OPAD_VAL; for (i = keylen; i < sizeof(hmac_key); i++) hmac_key[i] = 0 ^ HMAC_OPAD_VAL; ses->hash_update(&sctx, hmac_key, sizeof(hmac_key)); ses->hash_update(&sctx, res, ses->hash_len); ses->hash_finalize(res, &sctx); explicit_bzero(hmac_key, sizeof(hmac_key)); } else { ses->hash_init(&sctx); if (crp->crp_aad != NULL) ses->hash_update(&sctx, crp->crp_aad, crp->crp_aad_length); else crypto_apply(crp, crp->crp_aad_start, crp->crp_aad_length, ses->hash_update, &sctx); if (CRYPTO_HAS_OUTPUT_BUFFER(crp) && CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) crypto_apply_buf(&crp->crp_obuf, crp->crp_payload_output_start, crp->crp_payload_length, ses->hash_update, &sctx); else crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, ses->hash_update, &sctx); ses->hash_finalize(res, &sctx); } fpu_kern_leave(curthread, NULL); if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) { uint32_t res2[SHA2_256_HASH_LEN / sizeof(uint32_t)]; crypto_copydata(crp, crp->crp_digest_start, ses->mlen, res2); if (timingsafe_bcmp(res, res2, ses->mlen) != 0) return (EBADMSG); explicit_bzero(res2, sizeof(res2)); } else crypto_copyback(crp, crp->crp_digest_start, ses->mlen, res); explicit_bzero(res, sizeof(res)); return (0); } diff --git a/sys/crypto/armv8/armv8_crypto.c b/sys/crypto/armv8/armv8_crypto.c index 0a63d052b7a9..5b7ec4fbe125 100644 --- a/sys/crypto/armv8/armv8_crypto.c +++ b/sys/crypto/armv8/armv8_crypto.c @@ -1,415 +1,415 @@ /*- * Copyright (c) 2005-2008 Pawel Jakub Dawidek * Copyright (c) 2010 Konstantin Belousov * Copyright (c) 2014,2016 The FreeBSD Foundation * Copyright (c) 2020 Ampere Computing * All rights reserved. * * Portions of this software were developed by John-Mark Gurney * under sponsorship of the FreeBSD Foundation and * Rubicon Communications, LLC (Netgate). * * This software was developed by Andrew Turner under * sponsorship from the FreeBSD Foundation. * * 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, 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. */ /* * This is based on the aesni code. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct armv8_crypto_softc { int32_t cid; bool has_pmul; }; static int armv8_crypto_cipher_process(struct armv8_crypto_session *, struct cryptop *); MALLOC_DEFINE(M_ARMV8_CRYPTO, "armv8_crypto", "ARMv8 Crypto Data"); static void armv8_crypto_identify(driver_t *drv, device_t parent) { /* NB: order 10 is so we get attached after h/w devices */ - if (device_find_child(parent, "armv8crypto", -1) == NULL && - BUS_ADD_CHILD(parent, 10, "armv8crypto", -1) == 0) + if (device_find_child(parent, "armv8crypto", DEVICE_UNIT_ANY) == NULL && + BUS_ADD_CHILD(parent, 10, "armv8crypto", DEVICE_UNIT_ANY) == 0) panic("ARMv8 crypto: could not attach"); } static int armv8_crypto_probe(device_t dev) { uint64_t reg; int ret = ENXIO; reg = READ_SPECIALREG(id_aa64isar0_el1); switch (ID_AA64ISAR0_AES_VAL(reg)) { case ID_AA64ISAR0_AES_BASE: ret = 0; device_set_desc(dev, "AES-CBC,AES-XTS"); break; case ID_AA64ISAR0_AES_PMULL: ret = 0; device_set_desc(dev, "AES-CBC,AES-XTS,AES-GCM"); break; default: break; case ID_AA64ISAR0_AES_NONE: device_printf(dev, "CPU lacks AES instructions\n"); break; } /* TODO: Check more fields as we support more features */ return (ret); } static int armv8_crypto_attach(device_t dev) { struct armv8_crypto_softc *sc; uint64_t reg; sc = device_get_softc(dev); reg = READ_SPECIALREG(id_aa64isar0_el1); if (ID_AA64ISAR0_AES_VAL(reg) == ID_AA64ISAR0_AES_PMULL) sc->has_pmul = true; sc->cid = crypto_get_driverid(dev, sizeof(struct armv8_crypto_session), CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_SYNC | CRYPTOCAP_F_ACCEL_SOFTWARE); if (sc->cid < 0) { device_printf(dev, "Could not get crypto driver id.\n"); return (ENOMEM); } return (0); } static int armv8_crypto_detach(device_t dev) { struct armv8_crypto_softc *sc; sc = device_get_softc(dev); crypto_unregister_all(sc->cid); return (0); } #define SUPPORTED_SES (CSP_F_SEPARATE_OUTPUT | CSP_F_SEPARATE_AAD) static int armv8_crypto_probesession(device_t dev, const struct crypto_session_params *csp) { struct armv8_crypto_softc *sc; sc = device_get_softc(dev); if ((csp->csp_flags & ~(SUPPORTED_SES)) != 0) return (EINVAL); switch (csp->csp_mode) { case CSP_MODE_AEAD: switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: if (!sc->has_pmul) return (EINVAL); if (csp->csp_auth_mlen != 0 && csp->csp_auth_mlen != GMAC_DIGEST_LEN) return (EINVAL); switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: return (EINVAL); } break; default: return (EINVAL); } break; case CSP_MODE_CIPHER: switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: if (csp->csp_ivlen != AES_BLOCK_LEN) return (EINVAL); switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: return (EINVAL); } break; case CRYPTO_AES_XTS: if (csp->csp_ivlen != AES_XTS_IV_LEN) return (EINVAL); switch (csp->csp_cipher_klen * 8) { case 256: case 512: break; default: return (EINVAL); } break; default: return (EINVAL); } break; default: return (EINVAL); } return (CRYPTODEV_PROBE_ACCEL_SOFTWARE); } static int armv8_crypto_cipher_setup(struct armv8_crypto_session *ses, const struct crypto_session_params *csp, const uint8_t *key, int keylen) { __uint128_val_t H; if (csp->csp_cipher_alg == CRYPTO_AES_XTS) keylen /= 2; switch (keylen * 8) { case 128: case 192: case 256: break; default: return (EINVAL); } fpu_kern_enter(curthread, NULL, FPU_KERN_NORMAL | FPU_KERN_NOCTX); aes_v8_set_encrypt_key(key, keylen * 8, &ses->enc_schedule); if ((csp->csp_cipher_alg == CRYPTO_AES_XTS) || (csp->csp_cipher_alg == CRYPTO_AES_CBC)) aes_v8_set_decrypt_key(key, keylen * 8, &ses->dec_schedule); if (csp->csp_cipher_alg == CRYPTO_AES_XTS) aes_v8_set_encrypt_key(key + keylen, keylen * 8, &ses->xts_schedule); if (csp->csp_cipher_alg == CRYPTO_AES_NIST_GCM_16) { memset(H.c, 0, sizeof(H.c)); aes_v8_encrypt(H.c, H.c, &ses->enc_schedule); H.u[0] = bswap64(H.u[0]); H.u[1] = bswap64(H.u[1]); gcm_init_v8(ses->Htable, H.u); } fpu_kern_leave(curthread, NULL); return (0); } static int armv8_crypto_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { struct armv8_crypto_session *ses; int error; ses = crypto_get_driver_session(cses); error = armv8_crypto_cipher_setup(ses, csp, csp->csp_cipher_key, csp->csp_cipher_klen); return (error); } static int armv8_crypto_process(device_t dev, struct cryptop *crp, int hint __unused) { struct armv8_crypto_session *ses; ses = crypto_get_driver_session(crp->crp_session); crp->crp_etype = armv8_crypto_cipher_process(ses, crp); crypto_done(crp); return (0); } static uint8_t * armv8_crypto_cipher_alloc(struct cryptop *crp, int start, int length, int *allocated) { uint8_t *addr; addr = crypto_contiguous_subsegment(crp, start, length); if (addr != NULL) { *allocated = 0; return (addr); } addr = malloc(crp->crp_payload_length, M_ARMV8_CRYPTO, M_NOWAIT); if (addr != NULL) { *allocated = 1; crypto_copydata(crp, start, length, addr); } else *allocated = 0; return (addr); } static int armv8_crypto_cipher_process(struct armv8_crypto_session *ses, struct cryptop *crp) { struct crypto_buffer_cursor fromc, toc; const struct crypto_session_params *csp; uint8_t *authbuf; uint8_t iv[AES_BLOCK_LEN], tag[GMAC_DIGEST_LEN]; int authallocated; int encflag; int error; csp = crypto_get_params(crp->crp_session); encflag = CRYPTO_OP_IS_ENCRYPT(crp->crp_op); authallocated = 0; authbuf = NULL; if (csp->csp_cipher_alg == CRYPTO_AES_NIST_GCM_16) { if (crp->crp_aad != NULL) authbuf = crp->crp_aad; else authbuf = armv8_crypto_cipher_alloc(crp, crp->crp_aad_start, crp->crp_aad_length, &authallocated); if (authbuf == NULL) return (ENOMEM); } crypto_cursor_init(&fromc, &crp->crp_buf); crypto_cursor_advance(&fromc, crp->crp_payload_start); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) { crypto_cursor_init(&toc, &crp->crp_obuf); crypto_cursor_advance(&toc, crp->crp_payload_output_start); } else { crypto_cursor_copy(&fromc, &toc); } if (crp->crp_cipher_key != NULL) { armv8_crypto_cipher_setup(ses, csp, crp->crp_cipher_key, csp->csp_cipher_klen); } crypto_read_iv(crp, iv); fpu_kern_enter(curthread, NULL, FPU_KERN_NORMAL | FPU_KERN_NOCTX); error = 0; switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: if ((crp->crp_payload_length % AES_BLOCK_LEN) != 0) { error = EINVAL; break; } if (encflag) armv8_aes_encrypt_cbc(&ses->enc_schedule, crp->crp_payload_length, &fromc, &toc, iv); else armv8_aes_decrypt_cbc(&ses->dec_schedule, crp->crp_payload_length, &fromc, &toc, iv); break; case CRYPTO_AES_XTS: if (encflag) armv8_aes_encrypt_xts(&ses->enc_schedule, &ses->xts_schedule.aes_key, crp->crp_payload_length, &fromc, &toc, iv); else armv8_aes_decrypt_xts(&ses->dec_schedule, &ses->xts_schedule.aes_key, crp->crp_payload_length, &fromc, &toc, iv); break; case CRYPTO_AES_NIST_GCM_16: if (encflag) { memset(tag, 0, sizeof(tag)); armv8_aes_encrypt_gcm(&ses->enc_schedule, crp->crp_payload_length, &fromc, &toc, crp->crp_aad_length, authbuf, tag, iv, ses->Htable); crypto_copyback(crp, crp->crp_digest_start, sizeof(tag), tag); } else { crypto_copydata(crp, crp->crp_digest_start, sizeof(tag), tag); error = armv8_aes_decrypt_gcm(&ses->enc_schedule, crp->crp_payload_length, &fromc, &toc, crp->crp_aad_length, authbuf, tag, iv, ses->Htable); } break; } fpu_kern_leave(curthread, NULL); if (authallocated) zfree(authbuf, M_ARMV8_CRYPTO); explicit_bzero(iv, sizeof(iv)); explicit_bzero(tag, sizeof(tag)); return (error); } static device_method_t armv8_crypto_methods[] = { DEVMETHOD(device_identify, armv8_crypto_identify), DEVMETHOD(device_probe, armv8_crypto_probe), DEVMETHOD(device_attach, armv8_crypto_attach), DEVMETHOD(device_detach, armv8_crypto_detach), DEVMETHOD(cryptodev_probesession, armv8_crypto_probesession), DEVMETHOD(cryptodev_newsession, armv8_crypto_newsession), DEVMETHOD(cryptodev_process, armv8_crypto_process), DEVMETHOD_END, }; static DEFINE_CLASS_0(armv8crypto, armv8_crypto_driver, armv8_crypto_methods, sizeof(struct armv8_crypto_softc)); DRIVER_MODULE(armv8crypto, nexus, armv8_crypto_driver, 0, 0); diff --git a/sys/crypto/blake2/blake2_cryptodev.c b/sys/crypto/blake2/blake2_cryptodev.c index 00211544c42b..702a76a8e57a 100644 --- a/sys/crypto/blake2/blake2_cryptodev.c +++ b/sys/crypto/blake2/blake2_cryptodev.c @@ -1,324 +1,324 @@ /*- * Copyright (c) 2018 Conrad Meyer * 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, 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include struct blake2_session { size_t mlen; }; CTASSERT((size_t)BLAKE2B_KEYBYTES > (size_t)BLAKE2S_KEYBYTES); struct blake2_softc { int32_t cid; }; static int blake2_cipher_setup(struct blake2_session *ses, const struct crypto_session_params *csp); static int blake2_cipher_process(struct blake2_session *ses, struct cryptop *crp); MALLOC_DEFINE(M_BLAKE2, "blake2_data", "Blake2 Data"); static void blake2_identify(driver_t *drv, device_t parent) { /* NB: order 10 is so we get attached after h/w devices */ - if (device_find_child(parent, "blaketwo", -1) == NULL && - BUS_ADD_CHILD(parent, 10, "blaketwo", -1) == 0) + if (device_find_child(parent, "blaketwo", DEVICE_UNIT_ANY) == NULL && + BUS_ADD_CHILD(parent, 10, "blaketwo", DEVICE_UNIT_ANY) == 0) panic("blaketwo: could not attach"); } static int blake2_probe(device_t dev) { device_set_desc(dev, "Blake2"); return (0); } static int blake2_attach(device_t dev) { struct blake2_softc *sc; sc = device_get_softc(dev); sc->cid = crypto_get_driverid(dev, sizeof(struct blake2_session), CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_SYNC | CRYPTOCAP_F_ACCEL_SOFTWARE); if (sc->cid < 0) { device_printf(dev, "Could not get crypto driver id.\n"); return (ENOMEM); } return (0); } static int blake2_detach(device_t dev) { struct blake2_softc *sc; sc = device_get_softc(dev); crypto_unregister_all(sc->cid); return (0); } static int blake2_probesession(device_t dev, const struct crypto_session_params *csp) { if (csp->csp_flags != 0) return (EINVAL); switch (csp->csp_mode) { case CSP_MODE_DIGEST: switch (csp->csp_auth_alg) { case CRYPTO_BLAKE2B: case CRYPTO_BLAKE2S: break; default: return (EINVAL); } break; default: return (EINVAL); } return (CRYPTODEV_PROBE_ACCEL_SOFTWARE); } static int blake2_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { struct blake2_session *ses; int error; ses = crypto_get_driver_session(cses); error = blake2_cipher_setup(ses, csp); if (error != 0) { CRYPTDEB("setup failed"); return (error); } return (0); } static int blake2_process(device_t dev, struct cryptop *crp, int hint __unused) { struct blake2_session *ses; int error; ses = crypto_get_driver_session(crp->crp_session); error = blake2_cipher_process(ses, crp); crp->crp_etype = error; crypto_done(crp); return (0); } static device_method_t blake2_methods[] = { DEVMETHOD(device_identify, blake2_identify), DEVMETHOD(device_probe, blake2_probe), DEVMETHOD(device_attach, blake2_attach), DEVMETHOD(device_detach, blake2_detach), DEVMETHOD(cryptodev_probesession, blake2_probesession), DEVMETHOD(cryptodev_newsession, blake2_newsession), DEVMETHOD(cryptodev_process, blake2_process), DEVMETHOD_END }; static driver_t blake2_driver = { "blaketwo", blake2_methods, sizeof(struct blake2_softc), }; DRIVER_MODULE(blake2, nexus, blake2_driver, 0, 0); MODULE_VERSION(blake2, 1); MODULE_DEPEND(blake2, crypto, 1, 1, 1); static bool blake2_check_klen(const struct crypto_session_params *csp, unsigned klen) { if (csp->csp_auth_alg == CRYPTO_BLAKE2S) return (klen <= BLAKE2S_KEYBYTES); else return (klen <= BLAKE2B_KEYBYTES); } static int blake2_cipher_setup(struct blake2_session *ses, const struct crypto_session_params *csp) { int hashlen; CTASSERT((size_t)BLAKE2S_OUTBYTES <= (size_t)BLAKE2B_OUTBYTES); if (!blake2_check_klen(csp, csp->csp_auth_klen)) return (EINVAL); if (csp->csp_auth_mlen < 0) return (EINVAL); switch (csp->csp_auth_alg) { case CRYPTO_BLAKE2S: hashlen = BLAKE2S_OUTBYTES; break; case CRYPTO_BLAKE2B: hashlen = BLAKE2B_OUTBYTES; break; default: return (EINVAL); } if (csp->csp_auth_mlen > hashlen) return (EINVAL); if (csp->csp_auth_mlen == 0) ses->mlen = hashlen; else ses->mlen = csp->csp_auth_mlen; return (0); } static int blake2b_applicator(void *state, const void *buf, u_int len) { int rc; rc = blake2b_update(state, buf, len); if (rc != 0) return (EINVAL); return (0); } static int blake2s_applicator(void *state, const void *buf, u_int len) { int rc; rc = blake2s_update(state, buf, len); if (rc != 0) return (EINVAL); return (0); } static int blake2_cipher_process(struct blake2_session *ses, struct cryptop *crp) { union { blake2b_state sb; blake2s_state ss; } bctx; char res[BLAKE2B_OUTBYTES], res2[BLAKE2B_OUTBYTES]; const struct crypto_session_params *csp; const void *key; int error, rc; unsigned klen; csp = crypto_get_params(crp->crp_session); if (crp->crp_auth_key != NULL) key = crp->crp_auth_key; else key = csp->csp_auth_key; klen = csp->csp_auth_klen; fpu_kern_enter(curthread, NULL, FPU_KERN_NORMAL | FPU_KERN_NOCTX); switch (csp->csp_auth_alg) { case CRYPTO_BLAKE2B: if (klen > 0) rc = blake2b_init_key(&bctx.sb, ses->mlen, key, klen); else rc = blake2b_init(&bctx.sb, ses->mlen); if (rc != 0) { error = EINVAL; break; } error = crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, blake2b_applicator, &bctx.sb); if (error != 0) break; rc = blake2b_final(&bctx.sb, res, ses->mlen); if (rc != 0) error = EINVAL; break; case CRYPTO_BLAKE2S: if (klen > 0) rc = blake2s_init_key(&bctx.ss, ses->mlen, key, klen); else rc = blake2s_init(&bctx.ss, ses->mlen); if (rc != 0) { error = EINVAL; break; } error = crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, blake2s_applicator, &bctx.ss); if (error != 0) break; rc = blake2s_final(&bctx.ss, res, ses->mlen); if (rc != 0) error = EINVAL; break; default: __assert_unreachable(); } fpu_kern_leave(curthread, NULL); if (error != 0) return (error); if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) { crypto_copydata(crp, crp->crp_digest_start, ses->mlen, res2); if (timingsafe_bcmp(res, res2, ses->mlen) != 0) error = EBADMSG; } else crypto_copyback(crp, crp->crp_digest_start, ses->mlen, res); return (error); } diff --git a/sys/crypto/openssl/ossl.c b/sys/crypto/openssl/ossl.c index c2ca28133a78..203091c1e50b 100644 --- a/sys/crypto/openssl/ossl.c +++ b/sys/crypto/openssl/ossl.c @@ -1,484 +1,484 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Netflix, Inc * * 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, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ /* * A driver for the OpenCrypto framework which uses assembly routines * from OpenSSL. */ #include #include #include #include #include #include #include #include #include #include #include #include "cryptodev_if.h" static MALLOC_DEFINE(M_OSSL, "ossl", "OpenSSL crypto"); static void ossl_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "ossl", -1) == NULL) + if (device_find_child(parent, "ossl", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 10, "ossl", DEVICE_UNIT_ANY); } static int ossl_probe(device_t dev) { device_set_desc(dev, "OpenSSL crypto"); return (BUS_PROBE_DEFAULT); } static int ossl_attach(device_t dev) { struct ossl_softc *sc; sc = device_get_softc(dev); sc->has_aes = sc->has_aes_gcm = false; ossl_cpuid(sc); sc->sc_cid = crypto_get_driverid(dev, sizeof(struct ossl_session), CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_SYNC | CRYPTOCAP_F_ACCEL_SOFTWARE); if (sc->sc_cid < 0) { device_printf(dev, "failed to allocate crypto driver id\n"); return (ENXIO); } return (0); } static int ossl_detach(device_t dev) { struct ossl_softc *sc; sc = device_get_softc(dev); crypto_unregister_all(sc->sc_cid); return (0); } static struct auth_hash * ossl_lookup_hash(const struct crypto_session_params *csp) { switch (csp->csp_auth_alg) { case CRYPTO_SHA1: case CRYPTO_SHA1_HMAC: return (&ossl_hash_sha1); case CRYPTO_SHA2_224: case CRYPTO_SHA2_224_HMAC: return (&ossl_hash_sha224); case CRYPTO_SHA2_256: case CRYPTO_SHA2_256_HMAC: return (&ossl_hash_sha256); case CRYPTO_SHA2_384: case CRYPTO_SHA2_384_HMAC: return (&ossl_hash_sha384); case CRYPTO_SHA2_512: case CRYPTO_SHA2_512_HMAC: return (&ossl_hash_sha512); case CRYPTO_POLY1305: return (&ossl_hash_poly1305); default: return (NULL); } } static struct ossl_cipher* ossl_lookup_cipher(const struct crypto_session_params *csp) { switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: return (NULL); } return (&ossl_cipher_aes_cbc); case CRYPTO_AES_NIST_GCM_16: switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: return (NULL); } return (&ossl_cipher_aes_gcm); case CRYPTO_CHACHA20: if (csp->csp_cipher_klen != CHACHA_KEY_SIZE) return (NULL); return (&ossl_cipher_chacha20); default: return (NULL); } } static int ossl_probesession(device_t dev, const struct crypto_session_params *csp) { struct ossl_softc *sc = device_get_softc(dev); if ((csp->csp_flags & ~(CSP_F_SEPARATE_OUTPUT | CSP_F_SEPARATE_AAD)) != 0) return (EINVAL); switch (csp->csp_mode) { case CSP_MODE_DIGEST: if (ossl_lookup_hash(csp) == NULL) return (EINVAL); break; case CSP_MODE_CIPHER: if (csp->csp_cipher_alg != CRYPTO_CHACHA20 && !sc->has_aes) return (EINVAL); if (ossl_lookup_cipher(csp) == NULL) return (EINVAL); break; case CSP_MODE_ETA: if (!sc->has_aes || csp->csp_cipher_alg == CRYPTO_CHACHA20 || ossl_lookup_hash(csp) == NULL || ossl_lookup_cipher(csp) == NULL) return (EINVAL); break; case CSP_MODE_AEAD: switch (csp->csp_cipher_alg) { case CRYPTO_CHACHA20_POLY1305: break; case CRYPTO_AES_NIST_GCM_16: if (!sc->has_aes_gcm || ossl_lookup_cipher(csp) == NULL) return (EINVAL); if (csp->csp_ivlen != AES_GCM_IV_LEN) return (EINVAL); if (csp->csp_auth_mlen != 0 && csp->csp_auth_mlen != GMAC_DIGEST_LEN) return (EINVAL); break; default: return (EINVAL); } break; default: return (EINVAL); } return (CRYPTODEV_PROBE_ACCEL_SOFTWARE); } static void ossl_newsession_hash(struct ossl_session *s, const struct crypto_session_params *csp) { struct auth_hash *axf; axf = ossl_lookup_hash(csp); s->hash.axf = axf; if (csp->csp_auth_mlen == 0) s->hash.mlen = axf->hashsize; else s->hash.mlen = csp->csp_auth_mlen; if (csp->csp_auth_klen == 0) { axf->Init(&s->hash.ictx); } else { if (csp->csp_auth_key != NULL) { fpu_kern_enter(curthread, NULL, FPU_KERN_NOCTX); if (axf->Setkey != NULL) { axf->Init(&s->hash.ictx); axf->Setkey(&s->hash.ictx, csp->csp_auth_key, csp->csp_auth_klen); } else { hmac_init_ipad(axf, csp->csp_auth_key, csp->csp_auth_klen, &s->hash.ictx); hmac_init_opad(axf, csp->csp_auth_key, csp->csp_auth_klen, &s->hash.octx); } fpu_kern_leave(curthread, NULL); } } } static int ossl_newsession_cipher(struct ossl_session *s, const struct crypto_session_params *csp) { struct ossl_cipher *cipher; int error = 0; cipher = ossl_lookup_cipher(csp); if (cipher == NULL) return (EINVAL); s->cipher.cipher = cipher; if (csp->csp_cipher_key == NULL) return (0); fpu_kern_enter(curthread, NULL, FPU_KERN_NOCTX); if (cipher->set_encrypt_key != NULL) { error = cipher->set_encrypt_key(csp->csp_cipher_key, 8 * csp->csp_cipher_klen, &s->cipher.enc_ctx); if (error != 0) { fpu_kern_leave(curthread, NULL); return (error); } } if (cipher->set_decrypt_key != NULL) error = cipher->set_decrypt_key(csp->csp_cipher_key, 8 * csp->csp_cipher_klen, &s->cipher.dec_ctx); fpu_kern_leave(curthread, NULL); return (error); } static int ossl_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { struct ossl_session *s; int error = 0; s = crypto_get_driver_session(cses); switch (csp->csp_mode) { case CSP_MODE_DIGEST: ossl_newsession_hash(s, csp); break; case CSP_MODE_CIPHER: error = ossl_newsession_cipher(s, csp); break; case CSP_MODE_ETA: ossl_newsession_hash(s, csp); error = ossl_newsession_cipher(s, csp); break; case CSP_MODE_AEAD: if (csp->csp_cipher_alg != CRYPTO_CHACHA20_POLY1305) error = ossl_newsession_cipher(s, csp); break; default: __assert_unreachable(); } return (error); } static int ossl_process_hash(struct ossl_session *s, struct cryptop *crp, const struct crypto_session_params *csp) { struct ossl_hash_context ctx; char digest[HASH_MAX_LEN]; struct auth_hash *axf; int error; axf = s->hash.axf; if (crp->crp_auth_key == NULL) { ctx = s->hash.ictx; } else { if (axf->Setkey != NULL) { axf->Init(&ctx); axf->Setkey(&ctx, crp->crp_auth_key, csp->csp_auth_klen); } else { hmac_init_ipad(axf, crp->crp_auth_key, csp->csp_auth_klen, &ctx); } } if (crp->crp_aad != NULL) error = axf->Update(&ctx, crp->crp_aad, crp->crp_aad_length); else error = crypto_apply(crp, crp->crp_aad_start, crp->crp_aad_length, axf->Update, &ctx); if (error) goto out; error = crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, axf->Update, &ctx); if (error) goto out; axf->Final(digest, &ctx); if (csp->csp_auth_klen != 0 && axf->Setkey == NULL) { if (crp->crp_auth_key == NULL) ctx = s->hash.octx; else hmac_init_opad(axf, crp->crp_auth_key, csp->csp_auth_klen, &ctx); axf->Update(&ctx, digest, axf->hashsize); axf->Final(digest, &ctx); } if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) { char digest2[HASH_MAX_LEN]; crypto_copydata(crp, crp->crp_digest_start, s->hash.mlen, digest2); if (timingsafe_bcmp(digest, digest2, s->hash.mlen) != 0) error = EBADMSG; explicit_bzero(digest2, sizeof(digest2)); } else { crypto_copyback(crp, crp->crp_digest_start, s->hash.mlen, digest); } explicit_bzero(digest, sizeof(digest)); out: explicit_bzero(&ctx, sizeof(ctx)); return (error); } static int ossl_process_cipher(struct ossl_session *s, struct cryptop *crp, const struct crypto_session_params *csp) { return (s->cipher.cipher->process(&s->cipher, crp, csp)); } static int ossl_process_eta(struct ossl_session *s, struct cryptop *crp, const struct crypto_session_params *csp) { int error; if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { error = s->cipher.cipher->process(&s->cipher, crp, csp); if (error == 0) error = ossl_process_hash(s, crp, csp); } else { error = ossl_process_hash(s, crp, csp); if (error == 0) error = s->cipher.cipher->process(&s->cipher, crp, csp); } return (error); } static int ossl_process_aead(struct ossl_session *s, struct cryptop *crp, const struct crypto_session_params *csp) { if (csp->csp_cipher_alg == CRYPTO_CHACHA20_POLY1305) { if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) return (ossl_chacha20_poly1305_encrypt(crp, csp)); else return (ossl_chacha20_poly1305_decrypt(crp, csp)); } else { return (s->cipher.cipher->process(&s->cipher, crp, csp)); } } static int ossl_process(device_t dev, struct cryptop *crp, int hint) { const struct crypto_session_params *csp; struct ossl_session *s; int error; bool fpu_entered; s = crypto_get_driver_session(crp->crp_session); csp = crypto_get_params(crp->crp_session); if (is_fpu_kern_thread(0)) { fpu_entered = false; } else { fpu_kern_enter(curthread, NULL, FPU_KERN_NOCTX); fpu_entered = true; } switch (csp->csp_mode) { case CSP_MODE_DIGEST: error = ossl_process_hash(s, crp, csp); break; case CSP_MODE_CIPHER: error = ossl_process_cipher(s, crp, csp); break; case CSP_MODE_ETA: error = ossl_process_eta(s, crp, csp); break; case CSP_MODE_AEAD: error = ossl_process_aead(s, crp, csp); break; default: __assert_unreachable(); } if (fpu_entered) fpu_kern_leave(curthread, NULL); crp->crp_etype = error; crypto_done(crp); return (0); } static device_method_t ossl_methods[] = { DEVMETHOD(device_identify, ossl_identify), DEVMETHOD(device_probe, ossl_probe), DEVMETHOD(device_attach, ossl_attach), DEVMETHOD(device_detach, ossl_detach), DEVMETHOD(cryptodev_probesession, ossl_probesession), DEVMETHOD(cryptodev_newsession, ossl_newsession), DEVMETHOD(cryptodev_process, ossl_process), DEVMETHOD_END }; static driver_t ossl_driver = { "ossl", ossl_methods, sizeof(struct ossl_softc) }; DRIVER_MODULE(ossl, nexus, ossl_driver, NULL, NULL); MODULE_VERSION(ossl, 1); MODULE_DEPEND(ossl, crypto, 1, 1, 1); diff --git a/sys/crypto/via/padlock.c b/sys/crypto/via/padlock.c index 359618409f09..1123aa2f0c74 100644 --- a/sys/crypto/via/padlock.c +++ b/sys/crypto/via/padlock.c @@ -1,279 +1,279 @@ /*- * Copyright (c) 2005-2008 Pawel Jakub Dawidek * 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, 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 AUTHORS AND CONTRIBUTORS ``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 AUTHORS OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #if defined(__amd64__) || defined(__i386__) #include #include #include #include #include #endif #include #include #include #include #include "cryptodev_if.h" /* * Technical documentation about the PadLock engine can be found here: * * http://www.via.com.tw/en/downloads/whitepapers/initiatives/padlock/programming_guide.pdf */ struct padlock_softc { int32_t sc_cid; }; static int padlock_probesession(device_t, const struct crypto_session_params *); static int padlock_newsession(device_t, crypto_session_t cses, const struct crypto_session_params *); static void padlock_freesession(device_t, crypto_session_t cses); static void padlock_freesession_one(struct padlock_session *ses); static int padlock_process(device_t, struct cryptop *crp, int hint __unused); MALLOC_DEFINE(M_PADLOCK, "padlock_data", "PadLock Data"); static void padlock_identify(driver_t *drv, device_t parent) { /* NB: order 10 is so we get attached after h/w devices */ - if (device_find_child(parent, "padlock", -1) == NULL && - BUS_ADD_CHILD(parent, 10, "padlock", -1) == 0) + if (device_find_child(parent, "padlock", DEVICE_UNIT_ANY) == NULL && + BUS_ADD_CHILD(parent, 10, "padlock", DEVICE_UNIT_ANY) == 0) panic("padlock: could not attach"); } static int padlock_probe(device_t dev) { #if defined(__amd64__) || defined(__i386__) /* If there is no AES support, we has nothing to do here. */ if (!(via_feature_xcrypt & VIA_HAS_AES)) { device_printf(dev, "No ACE support.\n"); return (EINVAL); } device_set_descf(dev, "AES-CBC%s", (via_feature_xcrypt & VIA_HAS_SHA) ? ",SHA1,SHA256" : ""); return (0); #else return (EINVAL); #endif } static int padlock_attach(device_t dev) { struct padlock_softc *sc = device_get_softc(dev); sc->sc_cid = crypto_get_driverid(dev, sizeof(struct padlock_session), CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_SYNC | CRYPTOCAP_F_ACCEL_SOFTWARE); if (sc->sc_cid < 0) { device_printf(dev, "Could not get crypto driver id.\n"); return (ENOMEM); } return (0); } static int padlock_detach(device_t dev) { struct padlock_softc *sc = device_get_softc(dev); crypto_unregister_all(sc->sc_cid); return (0); } static int padlock_probesession(device_t dev, const struct crypto_session_params *csp) { if (csp->csp_flags != 0) return (EINVAL); /* * We only support HMAC algorithms to be able to work with * ipsec(4), so if we are asked only for authentication without * encryption, don't pretend we can accelerate it. * * XXX: For CPUs with SHA instructions we should probably * permit CSP_MODE_DIGEST so that those can be tested. */ switch (csp->csp_mode) { case CSP_MODE_ETA: if (!padlock_hash_check(csp)) return (EINVAL); /* FALLTHROUGH */ case CSP_MODE_CIPHER: switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: if (csp->csp_ivlen != AES_BLOCK_LEN) return (EINVAL); break; default: return (EINVAL); } break; default: return (EINVAL); } return (CRYPTODEV_PROBE_ACCEL_SOFTWARE); } static int padlock_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { struct padlock_session *ses; struct thread *td; int error; ses = crypto_get_driver_session(cses); error = padlock_cipher_setup(ses, csp); if (error != 0) { padlock_freesession_one(ses); return (error); } if (csp->csp_mode == CSP_MODE_ETA) { td = curthread; fpu_kern_enter(td, NULL, FPU_KERN_NORMAL | FPU_KERN_NOCTX); error = padlock_hash_setup(ses, csp); fpu_kern_leave(td, NULL); if (error != 0) { padlock_freesession_one(ses); return (error); } } return (0); } static void padlock_freesession_one(struct padlock_session *ses) { padlock_hash_free(ses); } static void padlock_freesession(device_t dev, crypto_session_t cses) { struct padlock_session *ses; ses = crypto_get_driver_session(cses); padlock_freesession_one(ses); } static int padlock_process(device_t dev, struct cryptop *crp, int hint __unused) { const struct crypto_session_params *csp; struct padlock_session *ses; int error; if ((crp->crp_payload_length % AES_BLOCK_LEN) != 0) { error = EINVAL; goto out; } ses = crypto_get_driver_session(crp->crp_session); csp = crypto_get_params(crp->crp_session); /* Perform data authentication if requested before decryption. */ if (csp->csp_mode == CSP_MODE_ETA && !CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { error = padlock_hash_process(ses, crp, csp); if (error != 0) goto out; } error = padlock_cipher_process(ses, crp, csp); if (error != 0) goto out; /* Perform data authentication if requested after encryption. */ if (csp->csp_mode == CSP_MODE_ETA && CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { error = padlock_hash_process(ses, crp, csp); if (error != 0) goto out; } out: #if 0 /* * This code is not necessary, because contexts will be freed on next * padlock_setup_mackey() call or at padlock_freesession() call. */ if (ses != NULL && maccrd != NULL && (maccrd->crd_flags & CRD_F_KEY_EXPLICIT) != 0) { padlock_free_ctx(ses->ses_axf, ses->ses_ictx); padlock_free_ctx(ses->ses_axf, ses->ses_octx); } #endif crp->crp_etype = error; crypto_done(crp); return (0); } static device_method_t padlock_methods[] = { DEVMETHOD(device_identify, padlock_identify), DEVMETHOD(device_probe, padlock_probe), DEVMETHOD(device_attach, padlock_attach), DEVMETHOD(device_detach, padlock_detach), DEVMETHOD(cryptodev_probesession, padlock_probesession), DEVMETHOD(cryptodev_newsession, padlock_newsession), DEVMETHOD(cryptodev_freesession,padlock_freesession), DEVMETHOD(cryptodev_process, padlock_process), {0, 0}, }; static driver_t padlock_driver = { "padlock", padlock_methods, sizeof(struct padlock_softc), }; /* XXX where to attach */ DRIVER_MODULE(padlock, nexus, padlock_driver, 0, 0); MODULE_VERSION(padlock, 1); MODULE_DEPEND(padlock, crypto, 1, 1, 1); diff --git a/sys/dev/aac/aac.c b/sys/dev/aac/aac.c index 912b3dc903e3..2519c66e81d4 100644 --- a/sys/dev/aac/aac.c +++ b/sys/dev/aac/aac.c @@ -1,3809 +1,3809 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Michael Smith * Copyright (c) 2001 Scott Long * Copyright (c) 2000 BSDi * Copyright (c) 2001 Adaptec, Inc. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include /* * Driver for the Adaptec 'FSA' family of PCI/SCSI RAID adapters. */ #define AAC_DRIVERNAME "aac" #include "opt_aac.h" /* #include */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void aac_startup(void *arg); static void aac_add_container(struct aac_softc *sc, struct aac_mntinforesp *mir, int f); static void aac_get_bus_info(struct aac_softc *sc); static void aac_daemon(void *arg); /* Command Processing */ static void aac_timeout(struct aac_softc *sc); static void aac_complete(void *context, int pending); static int aac_bio_command(struct aac_softc *sc, struct aac_command **cmp); static void aac_bio_complete(struct aac_command *cm); static int aac_wait_command(struct aac_command *cm); static void aac_command_thread(struct aac_softc *sc); /* Command Buffer Management */ static void aac_map_command_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error); static void aac_map_command_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int aac_alloc_commands(struct aac_softc *sc); static void aac_free_commands(struct aac_softc *sc); static void aac_unmap_command(struct aac_command *cm); /* Hardware Interface */ static int aac_alloc(struct aac_softc *sc); static void aac_common_map(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int aac_check_firmware(struct aac_softc *sc); static int aac_init(struct aac_softc *sc); static int aac_sync_command(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3, u_int32_t *sp); static int aac_setup_intr(struct aac_softc *sc); static int aac_enqueue_fib(struct aac_softc *sc, int queue, struct aac_command *cm); static int aac_dequeue_fib(struct aac_softc *sc, int queue, u_int32_t *fib_size, struct aac_fib **fib_addr); static int aac_enqueue_response(struct aac_softc *sc, int queue, struct aac_fib *fib); /* StrongARM interface */ static int aac_sa_get_fwstatus(struct aac_softc *sc); static void aac_sa_qnotify(struct aac_softc *sc, int qbit); static int aac_sa_get_istatus(struct aac_softc *sc); static void aac_sa_clear_istatus(struct aac_softc *sc, int mask); static void aac_sa_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_sa_get_mailbox(struct aac_softc *sc, int mb); static void aac_sa_set_interrupts(struct aac_softc *sc, int enable); const struct aac_interface aac_sa_interface = { aac_sa_get_fwstatus, aac_sa_qnotify, aac_sa_get_istatus, aac_sa_clear_istatus, aac_sa_set_mailbox, aac_sa_get_mailbox, aac_sa_set_interrupts, NULL, NULL, NULL }; /* i960Rx interface */ static int aac_rx_get_fwstatus(struct aac_softc *sc); static void aac_rx_qnotify(struct aac_softc *sc, int qbit); static int aac_rx_get_istatus(struct aac_softc *sc); static void aac_rx_clear_istatus(struct aac_softc *sc, int mask); static void aac_rx_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_rx_get_mailbox(struct aac_softc *sc, int mb); static void aac_rx_set_interrupts(struct aac_softc *sc, int enable); static int aac_rx_send_command(struct aac_softc *sc, struct aac_command *cm); static int aac_rx_get_outb_queue(struct aac_softc *sc); static void aac_rx_set_outb_queue(struct aac_softc *sc, int index); const struct aac_interface aac_rx_interface = { aac_rx_get_fwstatus, aac_rx_qnotify, aac_rx_get_istatus, aac_rx_clear_istatus, aac_rx_set_mailbox, aac_rx_get_mailbox, aac_rx_set_interrupts, aac_rx_send_command, aac_rx_get_outb_queue, aac_rx_set_outb_queue }; /* Rocket/MIPS interface */ static int aac_rkt_get_fwstatus(struct aac_softc *sc); static void aac_rkt_qnotify(struct aac_softc *sc, int qbit); static int aac_rkt_get_istatus(struct aac_softc *sc); static void aac_rkt_clear_istatus(struct aac_softc *sc, int mask); static void aac_rkt_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_rkt_get_mailbox(struct aac_softc *sc, int mb); static void aac_rkt_set_interrupts(struct aac_softc *sc, int enable); static int aac_rkt_send_command(struct aac_softc *sc, struct aac_command *cm); static int aac_rkt_get_outb_queue(struct aac_softc *sc); static void aac_rkt_set_outb_queue(struct aac_softc *sc, int index); const struct aac_interface aac_rkt_interface = { aac_rkt_get_fwstatus, aac_rkt_qnotify, aac_rkt_get_istatus, aac_rkt_clear_istatus, aac_rkt_set_mailbox, aac_rkt_get_mailbox, aac_rkt_set_interrupts, aac_rkt_send_command, aac_rkt_get_outb_queue, aac_rkt_set_outb_queue }; /* Debugging and Diagnostics */ static void aac_describe_controller(struct aac_softc *sc); static const char *aac_describe_code(const struct aac_code_lookup *table, u_int32_t code); /* Management Interface */ static d_open_t aac_open; static d_ioctl_t aac_ioctl; static d_poll_t aac_poll; static void aac_cdevpriv_dtor(void *arg); static int aac_ioctl_sendfib(struct aac_softc *sc, caddr_t ufib); static int aac_ioctl_send_raw_srb(struct aac_softc *sc, caddr_t arg); static void aac_handle_aif(struct aac_softc *sc, struct aac_fib *fib); static int aac_rev_check(struct aac_softc *sc, caddr_t udata); static int aac_open_aif(struct aac_softc *sc, caddr_t arg); static int aac_close_aif(struct aac_softc *sc, caddr_t arg); static int aac_getnext_aif(struct aac_softc *sc, caddr_t arg); static int aac_return_aif(struct aac_softc *sc, struct aac_fib_context *ctx, caddr_t uptr); static int aac_query_disk(struct aac_softc *sc, caddr_t uptr); static int aac_get_pci_info(struct aac_softc *sc, caddr_t uptr); static int aac_supported_features(struct aac_softc *sc, caddr_t uptr); static void aac_ioctl_event(struct aac_softc *sc, struct aac_event *event, void *arg); static struct aac_mntinforesp * aac_get_container_info(struct aac_softc *sc, struct aac_fib *fib, int cid); static struct cdevsw aac_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = aac_open, .d_ioctl = aac_ioctl, .d_poll = aac_poll, .d_name = "aac", }; static MALLOC_DEFINE(M_AACBUF, "aacbuf", "Buffers for the AAC driver"); /* sysctl node */ SYSCTL_NODE(_hw, OID_AUTO, aac, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "AAC driver parameters"); /* * Device Interface */ /* * Initialize the controller and softc */ int aac_attach(struct aac_softc *sc) { int error, unit; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Initialize per-controller queues. */ aac_initq_free(sc); aac_initq_ready(sc); aac_initq_busy(sc); aac_initq_bio(sc); /* * Initialize command-completion task. */ TASK_INIT(&sc->aac_task_complete, 0, aac_complete, sc); /* mark controller as suspended until we get ourselves organised */ sc->aac_state |= AAC_STATE_SUSPEND; /* * Check that the firmware on the card is supported. */ if ((error = aac_check_firmware(sc)) != 0) return(error); /* * Initialize locks */ mtx_init(&sc->aac_aifq_lock, "AAC AIF lock", NULL, MTX_DEF); mtx_init(&sc->aac_io_lock, "AAC I/O lock", NULL, MTX_DEF); mtx_init(&sc->aac_container_lock, "AAC container lock", NULL, MTX_DEF); TAILQ_INIT(&sc->aac_container_tqh); TAILQ_INIT(&sc->aac_ev_cmfree); /* Initialize the clock daemon callout. */ callout_init_mtx(&sc->aac_daemontime, &sc->aac_io_lock, 0); /* * Initialize the adapter. */ if ((error = aac_alloc(sc)) != 0) return(error); if ((error = aac_init(sc)) != 0) return(error); /* * Allocate and connect our interrupt. */ if ((error = aac_setup_intr(sc)) != 0) return(error); /* * Print a little information about the controller. */ aac_describe_controller(sc); /* * Add sysctls. */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->aac_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->aac_dev)), OID_AUTO, "firmware_build", CTLFLAG_RD, &sc->aac_revision.buildNumber, 0, "firmware build number"); /* * Register to probe our containers later. */ sc->aac_ich.ich_func = aac_startup; sc->aac_ich.ich_arg = sc; if (config_intrhook_establish(&sc->aac_ich) != 0) { device_printf(sc->aac_dev, "can't establish configuration hook\n"); return(ENXIO); } /* * Make the control device. */ unit = device_get_unit(sc->aac_dev); sc->aac_dev_t = make_dev(&aac_cdevsw, unit, UID_ROOT, GID_OPERATOR, 0640, "aac%d", unit); (void)make_dev_alias(sc->aac_dev_t, "afa%d", unit); (void)make_dev_alias(sc->aac_dev_t, "hpn%d", unit); sc->aac_dev_t->si_drv1 = sc; /* Create the AIF thread */ if (kproc_create((void(*)(void *))aac_command_thread, sc, &sc->aifthread, 0, 0, "aac%daif", unit)) panic("Could not create AIF thread"); /* Register the shutdown method to only be called post-dump */ if ((sc->eh = EVENTHANDLER_REGISTER(shutdown_final, aac_shutdown, sc->aac_dev, SHUTDOWN_PRI_DEFAULT)) == NULL) device_printf(sc->aac_dev, "shutdown event registration failed\n"); /* Register with CAM for the non-DASD devices */ if ((sc->flags & AAC_FLAGS_ENABLE_CAM) != 0) { TAILQ_INIT(&sc->aac_sim_tqh); aac_get_bus_info(sc); } mtx_lock(&sc->aac_io_lock); callout_reset(&sc->aac_daemontime, 60 * hz, aac_daemon, sc); mtx_unlock(&sc->aac_io_lock); return(0); } static void aac_daemon(void *arg) { struct timeval tv; struct aac_softc *sc; struct aac_fib *fib; sc = arg; mtx_assert(&sc->aac_io_lock, MA_OWNED); if (callout_pending(&sc->aac_daemontime) || callout_active(&sc->aac_daemontime) == 0) return; getmicrotime(&tv); aac_alloc_sync_fib(sc, &fib); *(uint32_t *)fib->data = tv.tv_sec; aac_sync_fib(sc, SendHostTime, 0, fib, sizeof(uint32_t)); aac_release_sync_fib(sc); callout_schedule(&sc->aac_daemontime, 30 * 60 * hz); } void aac_add_event(struct aac_softc *sc, struct aac_event *event) { switch (event->ev_type & AAC_EVENT_MASK) { case AAC_EVENT_CMFREE: TAILQ_INSERT_TAIL(&sc->aac_ev_cmfree, event, ev_links); break; default: device_printf(sc->aac_dev, "aac_add event: unknown event %d\n", event->ev_type); break; } } /* * Request information of container #cid */ static struct aac_mntinforesp * aac_get_container_info(struct aac_softc *sc, struct aac_fib *fib, int cid) { struct aac_mntinfo *mi; mi = (struct aac_mntinfo *)&fib->data[0]; /* use 64-bit LBA if enabled */ mi->Command = (sc->flags & AAC_FLAGS_LBA_64BIT) ? VM_NameServe64 : VM_NameServe; mi->MntType = FT_FILESYS; mi->MntCount = cid; if (aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_mntinfo))) { device_printf(sc->aac_dev, "Error probing container %d\n", cid); return (NULL); } return ((struct aac_mntinforesp *)&fib->data[0]); } /* * Probe for containers, create disks. */ static void aac_startup(void *arg) { struct aac_softc *sc; struct aac_fib *fib; struct aac_mntinforesp *mir; int count = 0, i = 0; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); /* loop over possible containers */ do { if ((mir = aac_get_container_info(sc, fib, i)) == NULL) continue; if (i == 0) count = mir->MntRespCount; aac_add_container(sc, mir, 0); i++; } while ((i < count) && (i < AAC_MAX_CONTAINERS)); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); /* mark the controller up */ sc->aac_state &= ~AAC_STATE_SUSPEND; /* poke the bus to actually attach the child devices */ bus_attach_children(sc->aac_dev); /* disconnect ourselves from the intrhook chain */ config_intrhook_disestablish(&sc->aac_ich); /* enable interrupts now */ AAC_UNMASK_INTERRUPTS(sc); } /* * Create a device to represent a new container */ static void aac_add_container(struct aac_softc *sc, struct aac_mntinforesp *mir, int f) { struct aac_container *co; device_t child; /* * Check container volume type for validity. Note that many of * the possible types may never show up. */ if ((mir->Status == ST_OK) && (mir->MntTable[0].VolType != CT_NONE)) { co = (struct aac_container *)malloc(sizeof *co, M_AACBUF, M_NOWAIT | M_ZERO); if (co == NULL) panic("Out of memory?!"); fwprintf(sc, HBA_FLAGS_DBG_INIT_B, "id %x name '%.16s' size %u type %d", mir->MntTable[0].ObjectId, mir->MntTable[0].FileSystemName, mir->MntTable[0].Capacity, mir->MntTable[0].VolType); - if ((child = device_add_child(sc->aac_dev, "aacd", -1)) == NULL) + if ((child = device_add_child(sc->aac_dev, "aacd", DEVICE_UNIT_ANY)) == NULL) device_printf(sc->aac_dev, "device_add_child failed\n"); else device_set_ivars(child, co); device_set_desc(child, aac_describe_code(aac_container_types, mir->MntTable[0].VolType)); co->co_disk = child; co->co_found = f; bcopy(&mir->MntTable[0], &co->co_mntobj, sizeof(struct aac_mntobj)); mtx_lock(&sc->aac_container_lock); TAILQ_INSERT_TAIL(&sc->aac_container_tqh, co, co_link); mtx_unlock(&sc->aac_container_lock); } } /* * Allocate resources associated with (sc) */ static int aac_alloc(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Create DMA tag for mapping buffers into controller-addressable space. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_SG_64BIT) ? BUS_SPACE_MAXADDR : BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->aac_max_sectors << 9, /* maxsize */ sc->aac_sg_tablesize, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->aac_io_lock, /* lockfuncarg */ &sc->aac_buffer_dmat)) { device_printf(sc->aac_dev, "can't allocate buffer DMA tag\n"); return (ENOMEM); } /* * Create DMA tag for mapping FIBs into controller-addressable space.. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_4GB_WINDOW) ? BUS_SPACE_MAXADDR_32BIT : 0x7fffffff, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->aac_max_fibs_alloc * sc->aac_max_fib_size, /* maxsize */ 1, /* nsegments */ sc->aac_max_fibs_alloc * sc->aac_max_fib_size, /* maxsize */ 0, /* flags */ NULL, NULL, /* No locking needed */ &sc->aac_fib_dmat)) { device_printf(sc->aac_dev, "can't allocate FIB DMA tag\n"); return (ENOMEM); } /* * Create DMA tag for the common structure and allocate it. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_4GB_WINDOW) ? BUS_SPACE_MAXADDR_32BIT : 0x7fffffff, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ 8192 + sizeof(struct aac_common), /* maxsize */ 1, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* No locking needed */ &sc->aac_common_dmat)) { device_printf(sc->aac_dev, "can't allocate common structure DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->aac_common_dmat, (void **)&sc->aac_common, BUS_DMA_NOWAIT, &sc->aac_common_dmamap)) { device_printf(sc->aac_dev, "can't allocate common structure\n"); return (ENOMEM); } /* * Work around a bug in the 2120 and 2200 that cannot DMA commands * below address 8192 in physical memory. * XXX If the padding is not needed, can it be put to use instead * of ignored? */ (void)bus_dmamap_load(sc->aac_common_dmat, sc->aac_common_dmamap, sc->aac_common, 8192 + sizeof(*sc->aac_common), aac_common_map, sc, 0); if (sc->aac_common_busaddr < 8192) { sc->aac_common = (struct aac_common *) ((uint8_t *)sc->aac_common + 8192); sc->aac_common_busaddr += 8192; } bzero(sc->aac_common, sizeof(*sc->aac_common)); /* Allocate some FIBs and associated command structs */ TAILQ_INIT(&sc->aac_fibmap_tqh); sc->aac_commands = malloc(sc->aac_max_fibs * sizeof(struct aac_command), M_AACBUF, M_WAITOK|M_ZERO); while (sc->total_fibs < sc->aac_max_fibs) { if (aac_alloc_commands(sc) != 0) break; } if (sc->total_fibs == 0) return (ENOMEM); return (0); } /* * Free all of the resources associated with (sc) * * Should not be called if the controller is active. */ void aac_free(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* remove the control device */ if (sc->aac_dev_t != NULL) destroy_dev(sc->aac_dev_t); /* throw away any FIB buffers, discard the FIB DMA tag */ aac_free_commands(sc); if (sc->aac_fib_dmat) bus_dma_tag_destroy(sc->aac_fib_dmat); free(sc->aac_commands, M_AACBUF); /* destroy the common area */ if (sc->aac_common) { bus_dmamap_unload(sc->aac_common_dmat, sc->aac_common_dmamap); bus_dmamem_free(sc->aac_common_dmat, sc->aac_common, sc->aac_common_dmamap); } if (sc->aac_common_dmat) bus_dma_tag_destroy(sc->aac_common_dmat); /* disconnect the interrupt handler */ if (sc->aac_intr) bus_teardown_intr(sc->aac_dev, sc->aac_irq, sc->aac_intr); if (sc->aac_irq != NULL) { bus_release_resource(sc->aac_dev, SYS_RES_IRQ, rman_get_rid(sc->aac_irq), sc->aac_irq); pci_release_msi(sc->aac_dev); } /* destroy data-transfer DMA tag */ if (sc->aac_buffer_dmat) bus_dma_tag_destroy(sc->aac_buffer_dmat); /* destroy the parent DMA tag */ if (sc->aac_parent_dmat) bus_dma_tag_destroy(sc->aac_parent_dmat); /* release the register window mapping */ if (sc->aac_regs_res0 != NULL) bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, rman_get_rid(sc->aac_regs_res0), sc->aac_regs_res0); if (sc->aac_hwif == AAC_HWIF_NARK && sc->aac_regs_res1 != NULL) bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, rman_get_rid(sc->aac_regs_res1), sc->aac_regs_res1); } /* * Disconnect from the controller completely, in preparation for unload. */ int aac_detach(device_t dev) { struct aac_softc *sc; struct aac_container *co; struct aac_sim *sim; int error; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); error = bus_generic_detach(dev); if (error != 0) return (error); callout_drain(&sc->aac_daemontime); mtx_lock(&sc->aac_io_lock); while (sc->aifflags & AAC_AIFFLAGS_RUNNING) { sc->aifflags |= AAC_AIFFLAGS_EXIT; wakeup(sc->aifthread); msleep(sc->aac_dev, &sc->aac_io_lock, PUSER, "aacdch", 0); } mtx_unlock(&sc->aac_io_lock); KASSERT((sc->aifflags & AAC_AIFFLAGS_RUNNING) == 0, ("%s: invalid detach state", __func__)); /* Remove the child containers */ while ((co = TAILQ_FIRST(&sc->aac_container_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_container_tqh, co, co_link); free(co, M_AACBUF); } /* Remove the CAM SIMs */ while ((sim = TAILQ_FIRST(&sc->aac_sim_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_sim_tqh, sim, sim_link); free(sim, M_AACBUF); } if ((error = aac_shutdown(dev))) return(error); EVENTHANDLER_DEREGISTER(shutdown_final, sc->eh); aac_free(sc); mtx_destroy(&sc->aac_aifq_lock); mtx_destroy(&sc->aac_io_lock); mtx_destroy(&sc->aac_container_lock); return(0); } /* * Bring the controller down to a dormant state and detach all child devices. * * This function is called before detach or system shutdown. * * Note that we can assume that the bioq on the controller is empty, as we won't * allow shutdown if any device is open. */ int aac_shutdown(device_t dev) { struct aac_softc *sc; struct aac_fib *fib; struct aac_close_command *cc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state |= AAC_STATE_SUSPEND; /* * Send a Container shutdown followed by a HostShutdown FIB to the * controller to convince it that we don't want to talk to it anymore. * We've been closed and all I/O completed already */ device_printf(sc->aac_dev, "shutting down controller..."); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); cc = (struct aac_close_command *)&fib->data[0]; bzero(cc, sizeof(struct aac_close_command)); cc->Command = VM_CloseAll; cc->ContainerId = 0xffffffff; if (aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_close_command))) printf("FAILED.\n"); else printf("done\n"); #if 0 else { fib->data[0] = 0; /* * XXX Issuing this command to the controller makes it shut down * but also keeps it from coming back up without a reset of the * PCI bus. This is not desirable if you are just unloading the * driver module with the intent to reload it later. */ if (aac_sync_fib(sc, FsaHostShutdown, AAC_FIBSTATE_SHUTDOWN, fib, 1)) { printf("FAILED.\n"); } else { printf("done.\n"); } } #endif AAC_MASK_INTERRUPTS(sc); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return(0); } /* * Bring the controller to a quiescent state, ready for system suspend. */ int aac_suspend(device_t dev) { struct aac_softc *sc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state |= AAC_STATE_SUSPEND; AAC_MASK_INTERRUPTS(sc); return(0); } /* * Bring the controller back to a state ready for operation. */ int aac_resume(device_t dev) { struct aac_softc *sc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state &= ~AAC_STATE_SUSPEND; AAC_UNMASK_INTERRUPTS(sc); return(0); } /* * Interrupt handler for NEW_COMM interface. */ void aac_new_intr(void *arg) { struct aac_softc *sc; u_int32_t index, fast; struct aac_command *cm; struct aac_fib *fib; int i; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); while (1) { index = AAC_GET_OUTB_QUEUE(sc); if (index == 0xffffffff) index = AAC_GET_OUTB_QUEUE(sc); if (index == 0xffffffff) break; if (index & 2) { if (index == 0xfffffffe) { /* XXX This means that the controller wants * more work. Ignore it for now. */ continue; } /* AIF */ fib = (struct aac_fib *)malloc(sizeof *fib, M_AACBUF, M_NOWAIT | M_ZERO); if (fib == NULL) { /* If we're really this short on memory, * hopefully breaking out of the handler will * allow something to get freed. This * actually sucks a whole lot. */ break; } index &= ~2; for (i = 0; i < sizeof(struct aac_fib)/4; ++i) ((u_int32_t *)fib)[i] = AAC_MEM1_GETREG4(sc, index + i*4); aac_handle_aif(sc, fib); free(fib, M_AACBUF); /* * AIF memory is owned by the adapter, so let it * know that we are done with it. */ AAC_SET_OUTB_QUEUE(sc, index); AAC_CLEAR_ISTATUS(sc, AAC_DB_RESPONSE_READY); } else { fast = index & 1; cm = sc->aac_commands + (index >> 2); fib = cm->cm_fib; if (fast) { fib->Header.XferState |= AAC_FIBSTATE_DONEADAP; *((u_int32_t *)(fib->data)) = AAC_ERROR_NORMAL; } aac_remove_busy(cm); aac_unmap_command(cm); cm->cm_flags |= AAC_CMD_COMPLETED; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this * command */ wakeup(cm); } sc->flags &= ~AAC_QUEUE_FRZN; } } /* see if we can start some more I/O */ if ((sc->flags & AAC_QUEUE_FRZN) == 0) aac_startio(sc); mtx_unlock(&sc->aac_io_lock); } /* * Interrupt filter for !NEW_COMM interface. */ int aac_filter(void *arg) { struct aac_softc *sc; u_int16_t reason; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Read the status register directly. This is faster than taking the * driver lock and reading the queues directly. It also saves having * to turn parts of the driver lock into a spin mutex, which would be * ugly. */ reason = AAC_GET_ISTATUS(sc); AAC_CLEAR_ISTATUS(sc, reason); /* handle completion processing */ if (reason & AAC_DB_RESPONSE_READY) taskqueue_enqueue(taskqueue_fast, &sc->aac_task_complete); /* controller wants to talk to us */ if (reason & (AAC_DB_PRINTF | AAC_DB_COMMAND_READY)) { /* * XXX Make sure that we don't get fooled by strange messages * that start with a NULL. */ if ((reason & AAC_DB_PRINTF) && (sc->aac_common->ac_printf[0] == 0)) sc->aac_common->ac_printf[0] = 32; /* * This might miss doing the actual wakeup. However, the * msleep that this is waking up has a timeout, so it will * wake up eventually. AIFs and printfs are low enough * priority that they can handle hanging out for a few seconds * if needed. */ wakeup(sc->aifthread); } return (FILTER_HANDLED); } /* * Command Processing */ /* * Start as much queued I/O as possible on the controller */ void aac_startio(struct aac_softc *sc) { struct aac_command *cm; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); for (;;) { /* * This flag might be set if the card is out of resources. * Checking it here prevents an infinite loop of deferrals. */ if (sc->flags & AAC_QUEUE_FRZN) break; /* * Try to get a command that's been put off for lack of * resources */ cm = aac_dequeue_ready(sc); /* * Try to build a command off the bio queue (ignore error * return) */ if (cm == NULL) aac_bio_command(sc, &cm); /* nothing to do? */ if (cm == NULL) break; /* don't map more than once */ if (cm->cm_flags & AAC_CMD_MAPPED) panic("aac: command %p already mapped", cm); /* * Set up the command to go to the controller. If there are no * data buffers associated with the command then it can bypass * busdma. */ if (cm->cm_datalen != 0) { if (cm->cm_flags & AAC_REQ_BIO) error = bus_dmamap_load_bio( sc->aac_buffer_dmat, cm->cm_datamap, (struct bio *)cm->cm_private, aac_map_command_sg, cm, 0); else error = bus_dmamap_load(sc->aac_buffer_dmat, cm->cm_datamap, cm->cm_data, cm->cm_datalen, aac_map_command_sg, cm, 0); if (error == EINPROGRESS) { fwprintf(sc, HBA_FLAGS_DBG_COMM_B, "freezing queue\n"); sc->flags |= AAC_QUEUE_FRZN; } else if (error != 0) panic("aac_startio: unexpected error %d from " "busdma", error); } else aac_map_command_sg(cm, NULL, 0, 0); } } /* * Handle notification of one or more FIBs coming from the controller. */ static void aac_command_thread(struct aac_softc *sc) { struct aac_fib *fib; u_int32_t fib_size; int size, retval; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); sc->aifflags = AAC_AIFFLAGS_RUNNING; while ((sc->aifflags & AAC_AIFFLAGS_EXIT) == 0) { retval = 0; if ((sc->aifflags & AAC_AIFFLAGS_PENDING) == 0) retval = msleep(sc->aifthread, &sc->aac_io_lock, PRIBIO, "aifthd", AAC_PERIODIC_INTERVAL * hz); /* * First see if any FIBs need to be allocated. This needs * to be called without the driver lock because contigmalloc * can sleep. */ if ((sc->aifflags & AAC_AIFFLAGS_ALLOCFIBS) != 0) { mtx_unlock(&sc->aac_io_lock); aac_alloc_commands(sc); mtx_lock(&sc->aac_io_lock); sc->aifflags &= ~AAC_AIFFLAGS_ALLOCFIBS; aac_startio(sc); } /* * While we're here, check to see if any commands are stuck. * This is pretty low-priority, so it's ok if it doesn't * always fire. */ if (retval == EWOULDBLOCK) aac_timeout(sc); /* Check the hardware printf message buffer */ if (sc->aac_common->ac_printf[0] != 0) aac_print_printf(sc); /* Also check to see if the adapter has a command for us. */ if (sc->flags & AAC_FLAGS_NEW_COMM) continue; for (;;) { if (aac_dequeue_fib(sc, AAC_HOST_NORM_CMD_QUEUE, &fib_size, &fib)) break; AAC_PRINT_FIB(sc, fib); switch (fib->Header.Command) { case AifRequest: aac_handle_aif(sc, fib); break; default: device_printf(sc->aac_dev, "unknown command " "from controller\n"); break; } if ((fib->Header.XferState == 0) || (fib->Header.StructType != AAC_FIBTYPE_TFIB)) { break; } /* Return the AIF to the controller. */ if (fib->Header.XferState & AAC_FIBSTATE_FROMADAP) { fib->Header.XferState |= AAC_FIBSTATE_DONEHOST; *(AAC_FSAStatus*)fib->data = ST_OK; /* XXX Compute the Size field? */ size = fib->Header.Size; if (size > sizeof(struct aac_fib)) { size = sizeof(struct aac_fib); fib->Header.Size = size; } /* * Since we did not generate this command, it * cannot go through the normal * enqueue->startio chain. */ aac_enqueue_response(sc, AAC_ADAP_NORM_RESP_QUEUE, fib); } } } sc->aifflags &= ~AAC_AIFFLAGS_RUNNING; mtx_unlock(&sc->aac_io_lock); wakeup(sc->aac_dev); kproc_exit(0); } /* * Process completed commands. */ static void aac_complete(void *context, int pending) { struct aac_softc *sc; struct aac_command *cm; struct aac_fib *fib; u_int32_t fib_size; sc = (struct aac_softc *)context; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); /* pull completed commands off the queue */ for (;;) { /* look for completed FIBs on our queue */ if (aac_dequeue_fib(sc, AAC_HOST_NORM_RESP_QUEUE, &fib_size, &fib)) break; /* nothing to do */ /* get the command, unmap and hand off for processing */ cm = sc->aac_commands + fib->Header.SenderData; if (cm == NULL) { AAC_PRINT_FIB(sc, fib); break; } if ((cm->cm_flags & AAC_CMD_TIMEDOUT) != 0) device_printf(sc->aac_dev, "COMMAND %p COMPLETED AFTER %d SECONDS\n", cm, (int)(time_uptime-cm->cm_timestamp)); aac_remove_busy(cm); aac_unmap_command(cm); cm->cm_flags |= AAC_CMD_COMPLETED; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this command */ wakeup(cm); } } /* see if we can start some more I/O */ sc->flags &= ~AAC_QUEUE_FRZN; aac_startio(sc); mtx_unlock(&sc->aac_io_lock); } /* * Handle a bio submitted from a disk device. */ void aac_submit_bio(struct bio *bp) { struct aac_disk *ad; struct aac_softc *sc; ad = (struct aac_disk *)bp->bio_disk->d_drv1; sc = ad->ad_controller; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* queue the BIO and try to get some work done */ aac_enqueue_bio(sc, bp); aac_startio(sc); } /* * Get a bio and build a command to go with it. */ static int aac_bio_command(struct aac_softc *sc, struct aac_command **cmp) { struct aac_command *cm; struct aac_fib *fib; struct aac_disk *ad; struct bio *bp; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* get the resources we will need */ cm = NULL; bp = NULL; if (aac_alloc_command(sc, &cm)) /* get a command */ goto fail; if ((bp = aac_dequeue_bio(sc)) == NULL) goto fail; /* fill out the command */ cm->cm_datalen = bp->bio_bcount; cm->cm_complete = aac_bio_complete; cm->cm_flags = AAC_REQ_BIO; cm->cm_private = bp; cm->cm_timestamp = time_uptime; /* build the FIB */ fib = cm->cm_fib; fib->Header.Size = sizeof(struct aac_fib_header); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC | AAC_FIBSTATE_FAST_RESPONSE; /* build the read/write request */ ad = (struct aac_disk *)bp->bio_disk->d_drv1; if (sc->flags & AAC_FLAGS_RAW_IO) { struct aac_raw_io *raw; raw = (struct aac_raw_io *)&fib->data[0]; fib->Header.Command = RawIo; raw->BlockNumber = (u_int64_t)bp->bio_pblkno; raw->ByteCount = bp->bio_bcount; raw->ContainerId = ad->ad_container->co_mntobj.ObjectId; raw->BpTotal = 0; raw->BpComplete = 0; fib->Header.Size += sizeof(struct aac_raw_io); cm->cm_sgtable = (struct aac_sg_table *)&raw->SgMapRaw; if (bp->bio_cmd == BIO_READ) { raw->Flags = 1; cm->cm_flags |= AAC_CMD_DATAIN; } else { raw->Flags = 0; cm->cm_flags |= AAC_CMD_DATAOUT; } } else if ((sc->flags & AAC_FLAGS_SG_64BIT) == 0) { fib->Header.Command = ContainerCommand; if (bp->bio_cmd == BIO_READ) { struct aac_blockread *br; br = (struct aac_blockread *)&fib->data[0]; br->Command = VM_CtBlockRead; br->ContainerId = ad->ad_container->co_mntobj.ObjectId; br->BlockNumber = bp->bio_pblkno; br->ByteCount = bp->bio_bcount; fib->Header.Size += sizeof(struct aac_blockread); cm->cm_sgtable = &br->SgMap; cm->cm_flags |= AAC_CMD_DATAIN; } else { struct aac_blockwrite *bw; bw = (struct aac_blockwrite *)&fib->data[0]; bw->Command = VM_CtBlockWrite; bw->ContainerId = ad->ad_container->co_mntobj.ObjectId; bw->BlockNumber = bp->bio_pblkno; bw->ByteCount = bp->bio_bcount; bw->Stable = CUNSTABLE; fib->Header.Size += sizeof(struct aac_blockwrite); cm->cm_flags |= AAC_CMD_DATAOUT; cm->cm_sgtable = &bw->SgMap; } } else { fib->Header.Command = ContainerCommand64; if (bp->bio_cmd == BIO_READ) { struct aac_blockread64 *br; br = (struct aac_blockread64 *)&fib->data[0]; br->Command = VM_CtHostRead64; br->ContainerId = ad->ad_container->co_mntobj.ObjectId; br->SectorCount = bp->bio_bcount / AAC_BLOCK_SIZE; br->BlockNumber = bp->bio_pblkno; br->Pad = 0; br->Flags = 0; fib->Header.Size += sizeof(struct aac_blockread64); cm->cm_flags |= AAC_CMD_DATAIN; cm->cm_sgtable = (struct aac_sg_table *)&br->SgMap64; } else { struct aac_blockwrite64 *bw; bw = (struct aac_blockwrite64 *)&fib->data[0]; bw->Command = VM_CtHostWrite64; bw->ContainerId = ad->ad_container->co_mntobj.ObjectId; bw->SectorCount = bp->bio_bcount / AAC_BLOCK_SIZE; bw->BlockNumber = bp->bio_pblkno; bw->Pad = 0; bw->Flags = 0; fib->Header.Size += sizeof(struct aac_blockwrite64); cm->cm_flags |= AAC_CMD_DATAOUT; cm->cm_sgtable = (struct aac_sg_table *)&bw->SgMap64; } } *cmp = cm; return(0); fail: if (bp != NULL) aac_enqueue_bio(sc, bp); if (cm != NULL) aac_release_command(cm); return(ENOMEM); } /* * Handle a bio-instigated command that has been completed. */ static void aac_bio_complete(struct aac_command *cm) { struct aac_blockread_response *brr; struct aac_blockwrite_response *bwr; struct bio *bp; AAC_FSAStatus status; /* fetch relevant status and then release the command */ bp = (struct bio *)cm->cm_private; if (bp->bio_cmd == BIO_READ) { brr = (struct aac_blockread_response *)&cm->cm_fib->data[0]; status = brr->Status; } else { bwr = (struct aac_blockwrite_response *)&cm->cm_fib->data[0]; status = bwr->Status; } aac_release_command(cm); /* fix up the bio based on status */ if (status == ST_OK) { bp->bio_resid = 0; } else { bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; } aac_biodone(bp); } /* * Submit a command to the controller, return when it completes. * XXX This is very dangerous! If the card has gone out to lunch, we could * be stuck here forever. At the same time, signals are not caught * because there is a risk that a signal could wakeup the sleep before * the card has a chance to complete the command. Since there is no way * to cancel a command that is in progress, we can't protect against the * card completing a command late and spamming the command and data * memory. So, we are held hostage until the command completes. */ static int aac_wait_command(struct aac_command *cm) { struct aac_softc *sc; int error; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* Put the command on the ready queue and get things going */ aac_enqueue_ready(cm); aac_startio(sc); error = msleep(cm, &sc->aac_io_lock, PRIBIO, "aacwait", 0); return(error); } /* *Command Buffer Management */ /* * Allocate a command. */ int aac_alloc_command(struct aac_softc *sc, struct aac_command **cmp) { struct aac_command *cm; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if ((cm = aac_dequeue_free(sc)) == NULL) { if (sc->total_fibs < sc->aac_max_fibs) { mtx_lock(&sc->aac_io_lock); sc->aifflags |= AAC_AIFFLAGS_ALLOCFIBS; mtx_unlock(&sc->aac_io_lock); wakeup(sc->aifthread); } return (EBUSY); } *cmp = cm; return(0); } /* * Release a command back to the freelist. */ void aac_release_command(struct aac_command *cm) { struct aac_event *event; struct aac_softc *sc; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* (re)initialize the command/FIB */ cm->cm_datalen = 0; cm->cm_sgtable = NULL; cm->cm_flags = 0; cm->cm_complete = NULL; cm->cm_private = NULL; cm->cm_queue = AAC_ADAP_NORM_CMD_QUEUE; cm->cm_fib->Header.XferState = AAC_FIBSTATE_EMPTY; cm->cm_fib->Header.StructType = AAC_FIBTYPE_TFIB; cm->cm_fib->Header.Flags = 0; cm->cm_fib->Header.SenderSize = cm->cm_sc->aac_max_fib_size; /* * These are duplicated in aac_start to cover the case where an * intermediate stage may have destroyed them. They're left * initialized here for debugging purposes only. */ cm->cm_fib->Header.ReceiverFibAddress = (u_int32_t)cm->cm_fibphys; cm->cm_fib->Header.SenderData = 0; aac_enqueue_free(cm); if ((event = TAILQ_FIRST(&sc->aac_ev_cmfree)) != NULL) { TAILQ_REMOVE(&sc->aac_ev_cmfree, event, ev_links); event->ev_callback(sc, event, event->ev_arg); } } /* * Map helper for command/FIB allocation. */ static void aac_map_command_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { uint64_t *fibphys; fibphys = (uint64_t *)arg; *fibphys = segs[0].ds_addr; } /* * Allocate and initialize commands/FIBs for this adapter. */ static int aac_alloc_commands(struct aac_softc *sc) { struct aac_command *cm; struct aac_fibmap *fm; uint64_t fibphys; int i, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (sc->total_fibs + sc->aac_max_fibs_alloc > sc->aac_max_fibs) return (ENOMEM); fm = malloc(sizeof(struct aac_fibmap), M_AACBUF, M_NOWAIT|M_ZERO); if (fm == NULL) return (ENOMEM); /* allocate the FIBs in DMAable memory and load them */ if (bus_dmamem_alloc(sc->aac_fib_dmat, (void **)&fm->aac_fibs, BUS_DMA_NOWAIT, &fm->aac_fibmap)) { device_printf(sc->aac_dev, "Not enough contiguous memory available.\n"); free(fm, M_AACBUF); return (ENOMEM); } /* Ignore errors since this doesn't bounce */ (void)bus_dmamap_load(sc->aac_fib_dmat, fm->aac_fibmap, fm->aac_fibs, sc->aac_max_fibs_alloc * sc->aac_max_fib_size, aac_map_command_helper, &fibphys, 0); /* initialize constant fields in the command structure */ bzero(fm->aac_fibs, sc->aac_max_fibs_alloc * sc->aac_max_fib_size); for (i = 0; i < sc->aac_max_fibs_alloc; i++) { cm = sc->aac_commands + sc->total_fibs; fm->aac_commands = cm; cm->cm_sc = sc; cm->cm_fib = (struct aac_fib *) ((u_int8_t *)fm->aac_fibs + i*sc->aac_max_fib_size); cm->cm_fibphys = fibphys + i*sc->aac_max_fib_size; cm->cm_index = sc->total_fibs; if ((error = bus_dmamap_create(sc->aac_buffer_dmat, 0, &cm->cm_datamap)) != 0) break; mtx_lock(&sc->aac_io_lock); aac_release_command(cm); sc->total_fibs++; mtx_unlock(&sc->aac_io_lock); } if (i > 0) { mtx_lock(&sc->aac_io_lock); TAILQ_INSERT_TAIL(&sc->aac_fibmap_tqh, fm, fm_link); fwprintf(sc, HBA_FLAGS_DBG_COMM_B, "total_fibs= %d\n", sc->total_fibs); mtx_unlock(&sc->aac_io_lock); return (0); } bus_dmamap_unload(sc->aac_fib_dmat, fm->aac_fibmap); bus_dmamem_free(sc->aac_fib_dmat, fm->aac_fibs, fm->aac_fibmap); free(fm, M_AACBUF); return (ENOMEM); } /* * Free FIBs owned by this adapter. */ static void aac_free_commands(struct aac_softc *sc) { struct aac_fibmap *fm; struct aac_command *cm; int i; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); while ((fm = TAILQ_FIRST(&sc->aac_fibmap_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_fibmap_tqh, fm, fm_link); /* * We check against total_fibs to handle partially * allocated blocks. */ for (i = 0; i < sc->aac_max_fibs_alloc && sc->total_fibs--; i++) { cm = fm->aac_commands + i; bus_dmamap_destroy(sc->aac_buffer_dmat, cm->cm_datamap); } bus_dmamap_unload(sc->aac_fib_dmat, fm->aac_fibmap); bus_dmamem_free(sc->aac_fib_dmat, fm->aac_fibs, fm->aac_fibmap); free(fm, M_AACBUF); } } /* * Command-mapping helper function - populate this command's s/g table. */ static void aac_map_command_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aac_softc *sc; struct aac_command *cm; struct aac_fib *fib; int i; cm = (struct aac_command *)arg; sc = cm->cm_sc; fib = cm->cm_fib; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* copy into the FIB */ if (cm->cm_sgtable != NULL) { if (fib->Header.Command == RawIo) { struct aac_sg_tableraw *sg; sg = (struct aac_sg_tableraw *)cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntryRaw[i].SgAddress = segs[i].ds_addr; sg->SgEntryRaw[i].SgByteCount = segs[i].ds_len; sg->SgEntryRaw[i].Next = 0; sg->SgEntryRaw[i].Prev = 0; sg->SgEntryRaw[i].Flags = 0; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entryraw); } else if ((cm->cm_sc->flags & AAC_FLAGS_SG_64BIT) == 0) { struct aac_sg_table *sg; sg = cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntry[i].SgAddress = segs[i].ds_addr; sg->SgEntry[i].SgByteCount = segs[i].ds_len; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entry); } else { struct aac_sg_table64 *sg; sg = (struct aac_sg_table64 *)cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntry64[i].SgAddress = segs[i].ds_addr; sg->SgEntry64[i].SgByteCount = segs[i].ds_len; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entry64); } } /* Fix up the address values in the FIB. Use the command array index * instead of a pointer since these fields are only 32 bits. Shift * the SenderFibAddress over to make room for the fast response bit * and for the AIF bit */ cm->cm_fib->Header.SenderFibAddress = (cm->cm_index << 2); cm->cm_fib->Header.ReceiverFibAddress = (u_int32_t)cm->cm_fibphys; /* save a pointer to the command for speedy reverse-lookup */ cm->cm_fib->Header.SenderData = cm->cm_index; if (cm->cm_flags & AAC_CMD_DATAIN) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_PREREAD); if (cm->cm_flags & AAC_CMD_DATAOUT) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_PREWRITE); cm->cm_flags |= AAC_CMD_MAPPED; if (sc->flags & AAC_FLAGS_NEW_COMM) { int count = 10000000L; while (AAC_SEND_COMMAND(sc, cm) != 0) { if (--count == 0) { aac_unmap_command(cm); sc->flags |= AAC_QUEUE_FRZN; aac_requeue_ready(cm); } DELAY(5); /* wait 5 usec. */ } } else { /* Put the FIB on the outbound queue */ if (aac_enqueue_fib(sc, cm->cm_queue, cm) == EBUSY) { aac_unmap_command(cm); sc->flags |= AAC_QUEUE_FRZN; aac_requeue_ready(cm); } } } /* * Unmap a command from controller-visible space. */ static void aac_unmap_command(struct aac_command *cm) { struct aac_softc *sc; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (!(cm->cm_flags & AAC_CMD_MAPPED)) return; if (cm->cm_datalen != 0) { if (cm->cm_flags & AAC_CMD_DATAIN) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_POSTREAD); if (cm->cm_flags & AAC_CMD_DATAOUT) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->aac_buffer_dmat, cm->cm_datamap); } cm->cm_flags &= ~AAC_CMD_MAPPED; } /* * Hardware Interface */ /* * Initialize the adapter. */ static void aac_common_map(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aac_softc *sc; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_common_busaddr = segs[0].ds_addr; } static int aac_check_firmware(struct aac_softc *sc) { u_int32_t code, major, minor, options = 0, atu_size = 0; int rid, status; time_t then; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Wait for the adapter to come ready. */ then = time_uptime; do { code = AAC_GET_FWSTATUS(sc); if (code & AAC_SELF_TEST_FAILED) { device_printf(sc->aac_dev, "FATAL: selftest failed\n"); return(ENXIO); } if (code & AAC_KERNEL_PANIC) { device_printf(sc->aac_dev, "FATAL: controller kernel panic"); return(ENXIO); } if (time_uptime > (then + AAC_BOOT_TIMEOUT)) { device_printf(sc->aac_dev, "FATAL: controller not coming ready, " "status %x\n", code); return(ENXIO); } } while (!(code & AAC_UP_AND_RUNNING)); /* * Retrieve the firmware version numbers. Dell PERC2/QC cards with * firmware version 1.x are not compatible with this driver. */ if (sc->flags & AAC_FLAGS_PERC2QC) { if (aac_sync_command(sc, AAC_MONKER_GETKERNVER, 0, 0, 0, 0, NULL)) { device_printf(sc->aac_dev, "Error reading firmware version\n"); return (EIO); } /* These numbers are stored as ASCII! */ major = (AAC_GET_MAILBOX(sc, 1) & 0xff) - 0x30; minor = (AAC_GET_MAILBOX(sc, 2) & 0xff) - 0x30; if (major == 1) { device_printf(sc->aac_dev, "Firmware version %d.%d is not supported.\n", major, minor); return (EINVAL); } } /* * Retrieve the capabilities/supported options word so we know what * work-arounds to enable. Some firmware revs don't support this * command. */ if (aac_sync_command(sc, AAC_MONKER_GETINFO, 0, 0, 0, 0, &status)) { if (status != AAC_SRB_STS_INVALID_REQUEST) { device_printf(sc->aac_dev, "RequestAdapterInfo failed\n"); return (EIO); } } else { options = AAC_GET_MAILBOX(sc, 1); atu_size = AAC_GET_MAILBOX(sc, 2); sc->supported_options = options; if ((options & AAC_SUPPORTED_4GB_WINDOW) != 0 && (sc->flags & AAC_FLAGS_NO4GB) == 0) sc->flags |= AAC_FLAGS_4GB_WINDOW; if (options & AAC_SUPPORTED_NONDASD) sc->flags |= AAC_FLAGS_ENABLE_CAM; if ((options & AAC_SUPPORTED_SGMAP_HOST64) != 0 && (sizeof(bus_addr_t) > 4)) { device_printf(sc->aac_dev, "Enabling 64-bit address support\n"); sc->flags |= AAC_FLAGS_SG_64BIT; } if ((options & AAC_SUPPORTED_NEW_COMM) && sc->aac_if->aif_send_command) sc->flags |= AAC_FLAGS_NEW_COMM; if (options & AAC_SUPPORTED_64BIT_ARRAYSIZE) sc->flags |= AAC_FLAGS_ARRAY_64BIT; } /* Check for broken hardware that does a lower number of commands */ sc->aac_max_fibs = (sc->flags & AAC_FLAGS_256FIBS ? 256:512); /* Remap mem. resource, if required */ if ((sc->flags & AAC_FLAGS_NEW_COMM) && atu_size > rman_get_size(sc->aac_regs_res1)) { rid = rman_get_rid(sc->aac_regs_res1); bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, rid, sc->aac_regs_res1); sc->aac_regs_res1 = bus_alloc_resource_anywhere(sc->aac_dev, SYS_RES_MEMORY, &rid, atu_size, RF_ACTIVE); if (sc->aac_regs_res1 == NULL) { sc->aac_regs_res1 = bus_alloc_resource_any( sc->aac_dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->aac_regs_res1 == NULL) { device_printf(sc->aac_dev, "couldn't allocate register window\n"); return (ENXIO); } sc->flags &= ~AAC_FLAGS_NEW_COMM; } sc->aac_btag1 = rman_get_bustag(sc->aac_regs_res1); sc->aac_bhandle1 = rman_get_bushandle(sc->aac_regs_res1); if (sc->aac_hwif == AAC_HWIF_NARK) { sc->aac_regs_res0 = sc->aac_regs_res1; sc->aac_btag0 = sc->aac_btag1; sc->aac_bhandle0 = sc->aac_bhandle1; } } /* Read preferred settings */ sc->aac_max_fib_size = sizeof(struct aac_fib); sc->aac_max_sectors = 128; /* 64KB */ if (sc->flags & AAC_FLAGS_SG_64BIT) sc->aac_sg_tablesize = (AAC_FIB_DATASIZE - sizeof(struct aac_blockwrite64)) / sizeof(struct aac_sg_entry64); else sc->aac_sg_tablesize = (AAC_FIB_DATASIZE - sizeof(struct aac_blockwrite)) / sizeof(struct aac_sg_entry); if (!aac_sync_command(sc, AAC_MONKER_GETCOMMPREF, 0, 0, 0, 0, NULL)) { options = AAC_GET_MAILBOX(sc, 1); sc->aac_max_fib_size = (options & 0xFFFF); sc->aac_max_sectors = (options >> 16) << 1; options = AAC_GET_MAILBOX(sc, 2); sc->aac_sg_tablesize = (options >> 16); options = AAC_GET_MAILBOX(sc, 3); sc->aac_max_fibs = (options & 0xFFFF); } if (sc->aac_max_fib_size > PAGE_SIZE) sc->aac_max_fib_size = PAGE_SIZE; sc->aac_max_fibs_alloc = PAGE_SIZE / sc->aac_max_fib_size; if (sc->aac_max_fib_size > sizeof(struct aac_fib)) { sc->flags |= AAC_FLAGS_RAW_IO; device_printf(sc->aac_dev, "Enable Raw I/O\n"); } if ((sc->flags & AAC_FLAGS_RAW_IO) && (sc->flags & AAC_FLAGS_ARRAY_64BIT)) { sc->flags |= AAC_FLAGS_LBA_64BIT; device_printf(sc->aac_dev, "Enable 64-bit array\n"); } return (0); } static int aac_init(struct aac_softc *sc) { struct aac_adapter_init *ip; u_int32_t qoffset; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Fill in the init structure. This tells the adapter about the * physical location of various important shared data structures. */ ip = &sc->aac_common->ac_init; ip->InitStructRevision = AAC_INIT_STRUCT_REVISION; if (sc->aac_max_fib_size > sizeof(struct aac_fib)) { ip->InitStructRevision = AAC_INIT_STRUCT_REVISION_4; sc->flags |= AAC_FLAGS_RAW_IO; } ip->MiniPortRevision = AAC_INIT_STRUCT_MINIPORT_REVISION; ip->AdapterFibsPhysicalAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_fibs); ip->AdapterFibsVirtualAddress = 0; ip->AdapterFibsSize = AAC_ADAPTER_FIBS * sizeof(struct aac_fib); ip->AdapterFibAlign = sizeof(struct aac_fib); ip->PrintfBufferAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_printf); ip->PrintfBufferSize = AAC_PRINTF_BUFSIZE; /* * The adapter assumes that pages are 4K in size, except on some * broken firmware versions that do the page->byte conversion twice, * therefore 'assuming' that this value is in 16MB units (2^24). * Round up since the granularity is so high. */ ip->HostPhysMemPages = ctob(physmem) / AAC_PAGE_SIZE; if (sc->flags & AAC_FLAGS_BROKEN_MEMMAP) { ip->HostPhysMemPages = (ip->HostPhysMemPages + AAC_PAGE_SIZE) / AAC_PAGE_SIZE; } ip->HostElapsedSeconds = time_uptime; /* reset later if invalid */ ip->InitFlags = 0; if (sc->flags & AAC_FLAGS_NEW_COMM) { ip->InitFlags |= AAC_INITFLAGS_NEW_COMM_SUPPORTED; device_printf(sc->aac_dev, "New comm. interface enabled\n"); } ip->MaxIoCommands = sc->aac_max_fibs; ip->MaxIoSize = sc->aac_max_sectors << 9; ip->MaxFibSize = sc->aac_max_fib_size; /* * Initialize FIB queues. Note that it appears that the layout of the * indexes and the segmentation of the entries may be mandated by the * adapter, which is only told about the base of the queue index fields. * * The initial values of the indices are assumed to inform the adapter * of the sizes of the respective queues, and theoretically it could * work out the entire layout of the queue structures from this. We * take the easy route and just lay this area out like everyone else * does. * * The Linux driver uses a much more complex scheme whereby several * header records are kept for each queue. We use a couple of generic * list manipulation functions which 'know' the size of each list by * virtue of a table. */ qoffset = offsetof(struct aac_common, ac_qbuf) + AAC_QUEUE_ALIGN; qoffset &= ~(AAC_QUEUE_ALIGN - 1); sc->aac_queues = (struct aac_queue_table *)((uintptr_t)sc->aac_common + qoffset); ip->CommHeaderAddress = sc->aac_common_busaddr + qoffset; sc->aac_queues->qt_qindex[AAC_HOST_NORM_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_HOST_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_NORM_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_HOST_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_HOST_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_HOST_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_ADAP_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_ADAP_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_ADAP_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_ADAP_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_NORM_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_HOST_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_NORM_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_HOST_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_HOST_HIGH_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_HOST_HIGH_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_ADAP_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_ADAP_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_ADAP_HIGH_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_ADAP_HIGH_RESP_ENTRIES; sc->aac_qentries[AAC_HOST_NORM_CMD_QUEUE] = &sc->aac_queues->qt_HostNormCmdQueue[0]; sc->aac_qentries[AAC_HOST_HIGH_CMD_QUEUE] = &sc->aac_queues->qt_HostHighCmdQueue[0]; sc->aac_qentries[AAC_ADAP_NORM_CMD_QUEUE] = &sc->aac_queues->qt_AdapNormCmdQueue[0]; sc->aac_qentries[AAC_ADAP_HIGH_CMD_QUEUE] = &sc->aac_queues->qt_AdapHighCmdQueue[0]; sc->aac_qentries[AAC_HOST_NORM_RESP_QUEUE] = &sc->aac_queues->qt_HostNormRespQueue[0]; sc->aac_qentries[AAC_HOST_HIGH_RESP_QUEUE] = &sc->aac_queues->qt_HostHighRespQueue[0]; sc->aac_qentries[AAC_ADAP_NORM_RESP_QUEUE] = &sc->aac_queues->qt_AdapNormRespQueue[0]; sc->aac_qentries[AAC_ADAP_HIGH_RESP_QUEUE] = &sc->aac_queues->qt_AdapHighRespQueue[0]; /* * Do controller-type-specific initialisation */ switch (sc->aac_hwif) { case AAC_HWIF_I960RX: AAC_MEM0_SETREG4(sc, AAC_RX_ODBR, ~0); break; case AAC_HWIF_RKT: AAC_MEM0_SETREG4(sc, AAC_RKT_ODBR, ~0); break; default: break; } /* * Give the init structure to the controller. */ if (aac_sync_command(sc, AAC_MONKER_INITSTRUCT, sc->aac_common_busaddr + offsetof(struct aac_common, ac_init), 0, 0, 0, NULL)) { device_printf(sc->aac_dev, "error establishing init structure\n"); error = EIO; goto out; } error = 0; out: return(error); } static int aac_setup_intr(struct aac_softc *sc) { if (sc->flags & AAC_FLAGS_NEW_COMM) { if (bus_setup_intr(sc->aac_dev, sc->aac_irq, INTR_MPSAFE|INTR_TYPE_BIO, NULL, aac_new_intr, sc, &sc->aac_intr)) { device_printf(sc->aac_dev, "can't set up interrupt\n"); return (EINVAL); } } else { if (bus_setup_intr(sc->aac_dev, sc->aac_irq, INTR_TYPE_BIO, aac_filter, NULL, sc, &sc->aac_intr)) { device_printf(sc->aac_dev, "can't set up interrupt filter\n"); return (EINVAL); } } return (0); } /* * Send a synchronous command to the controller and wait for a result. * Indicate if the controller completed the command with an error status. */ static int aac_sync_command(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3, u_int32_t *sp) { time_t then; u_int32_t status; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* populate the mailbox */ AAC_SET_MAILBOX(sc, command, arg0, arg1, arg2, arg3); /* ensure the sync command doorbell flag is cleared */ AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* then set it to signal the adapter */ AAC_QNOTIFY(sc, AAC_DB_SYNC_COMMAND); /* spin waiting for the command to complete */ then = time_uptime; do { if (time_uptime > (then + AAC_IMMEDIATE_TIMEOUT)) { fwprintf(sc, HBA_FLAGS_DBG_ERROR_B, "timed out"); return(EIO); } } while (!(AAC_GET_ISTATUS(sc) & AAC_DB_SYNC_COMMAND)); /* clear the completion flag */ AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* get the command status */ status = AAC_GET_MAILBOX(sc, 0); if (sp != NULL) *sp = status; if (status != AAC_SRB_STS_SUCCESS) return (-1); return(0); } int aac_sync_fib(struct aac_softc *sc, u_int32_t command, u_int32_t xferstate, struct aac_fib *fib, u_int16_t datasize) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_assert(&sc->aac_io_lock, MA_OWNED); if (datasize > AAC_FIB_DATASIZE) return(EINVAL); /* * Set up the sync FIB */ fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY; fib->Header.XferState |= xferstate; fib->Header.Command = command; fib->Header.StructType = AAC_FIBTYPE_TFIB; fib->Header.Size = sizeof(struct aac_fib_header) + datasize; fib->Header.SenderSize = sizeof(struct aac_fib); fib->Header.SenderFibAddress = 0; /* Not needed */ fib->Header.ReceiverFibAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_sync_fib); /* * Give the FIB to the controller, wait for a response. */ if (aac_sync_command(sc, AAC_MONKER_SYNCFIB, fib->Header.ReceiverFibAddress, 0, 0, 0, NULL)) { fwprintf(sc, HBA_FLAGS_DBG_ERROR_B, "IO error"); return(EIO); } return (0); } /* * Adapter-space FIB queue manipulation * * Note that the queue implementation here is a little funky; neither the PI or * CI will ever be zero. This behaviour is a controller feature. */ static const struct { int size; int notify; } aac_qinfo[] = { {AAC_HOST_NORM_CMD_ENTRIES, AAC_DB_COMMAND_NOT_FULL}, {AAC_HOST_HIGH_CMD_ENTRIES, 0}, {AAC_ADAP_NORM_CMD_ENTRIES, AAC_DB_COMMAND_READY}, {AAC_ADAP_HIGH_CMD_ENTRIES, 0}, {AAC_HOST_NORM_RESP_ENTRIES, AAC_DB_RESPONSE_NOT_FULL}, {AAC_HOST_HIGH_RESP_ENTRIES, 0}, {AAC_ADAP_NORM_RESP_ENTRIES, AAC_DB_RESPONSE_READY}, {AAC_ADAP_HIGH_RESP_ENTRIES, 0} }; /* * Atomically insert an entry into the nominated queue, returns 0 on success or * EBUSY if the queue is full. * * Note: it would be more efficient to defer notifying the controller in * the case where we may be inserting several entries in rapid succession, * but implementing this usefully may be difficult (it would involve a * separate queue/notify interface). */ static int aac_enqueue_fib(struct aac_softc *sc, int queue, struct aac_command *cm) { u_int32_t pi, ci; int error; u_int32_t fib_size; u_int32_t fib_addr; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); fib_size = cm->cm_fib->Header.Size; fib_addr = cm->cm_fib->Header.ReceiverFibAddress; /* get the producer/consumer indices */ pi = sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]; ci = sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]; /* wrap the queue? */ if (pi >= aac_qinfo[queue].size) pi = 0; /* check for queue full */ if ((pi + 1) == ci) { error = EBUSY; goto out; } /* * To avoid a race with its completion interrupt, place this command on * the busy queue prior to advertising it to the controller. */ aac_enqueue_busy(cm); /* populate queue entry */ (sc->aac_qentries[queue] + pi)->aq_fib_size = fib_size; (sc->aac_qentries[queue] + pi)->aq_fib_addr = fib_addr; /* update producer index */ sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX] = pi + 1; /* notify the adapter if we know how */ if (aac_qinfo[queue].notify != 0) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); error = 0; out: return(error); } /* * Atomically remove one entry from the nominated queue, returns 0 on * success or ENOENT if the queue is empty. */ static int aac_dequeue_fib(struct aac_softc *sc, int queue, u_int32_t *fib_size, struct aac_fib **fib_addr) { u_int32_t pi, ci; u_int32_t fib_index; int error; int notify; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* get the producer/consumer indices */ pi = sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]; ci = sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]; /* check for queue empty */ if (ci == pi) { error = ENOENT; goto out; } /* wrap the pi so the following test works */ if (pi >= aac_qinfo[queue].size) pi = 0; notify = 0; if (ci == pi + 1) notify++; /* wrap the queue? */ if (ci >= aac_qinfo[queue].size) ci = 0; /* fetch the entry */ *fib_size = (sc->aac_qentries[queue] + ci)->aq_fib_size; switch (queue) { case AAC_HOST_NORM_CMD_QUEUE: case AAC_HOST_HIGH_CMD_QUEUE: /* * The aq_fib_addr is only 32 bits wide so it can't be counted * on to hold an address. For AIF's, the adapter assumes * that it's giving us an address into the array of AIF fibs. * Therefore, we have to convert it to an index. */ fib_index = (sc->aac_qentries[queue] + ci)->aq_fib_addr / sizeof(struct aac_fib); *fib_addr = &sc->aac_common->ac_fibs[fib_index]; break; case AAC_HOST_NORM_RESP_QUEUE: case AAC_HOST_HIGH_RESP_QUEUE: { struct aac_command *cm; /* * As above, an index is used instead of an actual address. * Gotta shift the index to account for the fast response * bit. No other correction is needed since this value was * originally provided by the driver via the SenderFibAddress * field. */ fib_index = (sc->aac_qentries[queue] + ci)->aq_fib_addr; cm = sc->aac_commands + (fib_index >> 2); *fib_addr = cm->cm_fib; /* * Is this a fast response? If it is, update the fib fields in * local memory since the whole fib isn't DMA'd back up. */ if (fib_index & 0x01) { (*fib_addr)->Header.XferState |= AAC_FIBSTATE_DONEADAP; *((u_int32_t*)((*fib_addr)->data)) = AAC_ERROR_NORMAL; } break; } default: panic("Invalid queue in aac_dequeue_fib()"); break; } /* update consumer index */ sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX] = ci + 1; /* if we have made the queue un-full, notify the adapter */ if (notify && (aac_qinfo[queue].notify != 0)) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); error = 0; out: return(error); } /* * Put our response to an Adapter Initialed Fib on the response queue */ static int aac_enqueue_response(struct aac_softc *sc, int queue, struct aac_fib *fib) { u_int32_t pi, ci; int error; u_int32_t fib_size; u_int32_t fib_addr; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* Tell the adapter where the FIB is */ fib_size = fib->Header.Size; fib_addr = fib->Header.SenderFibAddress; fib->Header.ReceiverFibAddress = fib_addr; /* get the producer/consumer indices */ pi = sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]; ci = sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]; /* wrap the queue? */ if (pi >= aac_qinfo[queue].size) pi = 0; /* check for queue full */ if ((pi + 1) == ci) { error = EBUSY; goto out; } /* populate queue entry */ (sc->aac_qentries[queue] + pi)->aq_fib_size = fib_size; (sc->aac_qentries[queue] + pi)->aq_fib_addr = fib_addr; /* update producer index */ sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX] = pi + 1; /* notify the adapter if we know how */ if (aac_qinfo[queue].notify != 0) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); error = 0; out: return(error); } /* * Check for commands that have been outstanding for a suspiciously long time, * and complain about them. */ static void aac_timeout(struct aac_softc *sc) { struct aac_command *cm; time_t deadline; int timedout, code; /* * Traverse the busy command list, bitch about late commands once * only. */ timedout = 0; deadline = time_uptime - AAC_CMD_TIMEOUT; TAILQ_FOREACH(cm, &sc->aac_busy, cm_link) { if ((cm->cm_timestamp < deadline) && !(cm->cm_flags & AAC_CMD_TIMEDOUT)) { cm->cm_flags |= AAC_CMD_TIMEDOUT; device_printf(sc->aac_dev, "COMMAND %p (TYPE %d) TIMEOUT AFTER %d SECONDS\n", cm, cm->cm_fib->Header.Command, (int)(time_uptime-cm->cm_timestamp)); AAC_PRINT_FIB(sc, cm->cm_fib); timedout++; } } if (timedout) { code = AAC_GET_FWSTATUS(sc); if (code != AAC_UP_AND_RUNNING) { device_printf(sc->aac_dev, "WARNING! Controller is no " "longer running! code= 0x%x\n", code); } } } /* * Interface Function Vectors */ /* * Read the current firmware status word. */ static int aac_sa_get_fwstatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_SA_FWSTATUS)); } static int aac_rx_get_fwstatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, sc->flags & AAC_FLAGS_NEW_COMM ? AAC_RX_OMR0 : AAC_RX_FWSTATUS)); } static int aac_rkt_get_fwstatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, sc->flags & AAC_FLAGS_NEW_COMM ? AAC_RKT_OMR0 : AAC_RKT_FWSTATUS)); } /* * Notify the controller of a change in a given queue */ static void aac_sa_qnotify(struct aac_softc *sc, int qbit) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG2(sc, AAC_SA_DOORBELL1_SET, qbit); } static void aac_rx_qnotify(struct aac_softc *sc, int qbit) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RX_IDBR, qbit); } static void aac_rkt_qnotify(struct aac_softc *sc, int qbit) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RKT_IDBR, qbit); } /* * Get the interrupt reason bits */ static int aac_sa_get_istatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG2(sc, AAC_SA_DOORBELL0)); } static int aac_rx_get_istatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RX_ODBR)); } static int aac_rkt_get_istatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RKT_ODBR)); } /* * Clear some interrupt reason bits */ static void aac_sa_clear_istatus(struct aac_softc *sc, int mask) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG2(sc, AAC_SA_DOORBELL0_CLEAR, mask); } static void aac_rx_clear_istatus(struct aac_softc *sc, int mask) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RX_ODBR, mask); } static void aac_rkt_clear_istatus(struct aac_softc *sc, int mask) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RKT_ODBR, mask); } /* * Populate the mailbox and set the command word */ static void aac_sa_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX, command); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 4, arg0); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 8, arg1); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 12, arg2); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 16, arg3); } static void aac_rx_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX, command); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 4, arg0); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 8, arg1); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 12, arg2); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 16, arg3); } static void aac_rkt_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX, command); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 4, arg0); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 8, arg1); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 12, arg2); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 16, arg3); } /* * Fetch the immediate command status word */ static int aac_sa_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM1_GETREG4(sc, AAC_SA_MAILBOX + (mb * 4))); } static int aac_rx_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM1_GETREG4(sc, AAC_RX_MAILBOX + (mb * 4))); } static int aac_rkt_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM1_GETREG4(sc, AAC_RKT_MAILBOX + (mb * 4))); } /* * Set/clear interrupt masks */ static void aac_sa_set_interrupts(struct aac_softc *sc, int enable) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "%sable interrupts", enable ? "en" : "dis"); if (enable) { AAC_MEM0_SETREG2((sc), AAC_SA_MASK0_CLEAR, AAC_DB_INTERRUPTS); } else { AAC_MEM0_SETREG2((sc), AAC_SA_MASK0_SET, ~0); } } static void aac_rx_set_interrupts(struct aac_softc *sc, int enable) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "%sable interrupts", enable ? "en" : "dis"); if (enable) { if (sc->flags & AAC_FLAGS_NEW_COMM) AAC_MEM0_SETREG4(sc, AAC_RX_OIMR, ~AAC_DB_INT_NEW_COMM); else AAC_MEM0_SETREG4(sc, AAC_RX_OIMR, ~AAC_DB_INTERRUPTS); } else { AAC_MEM0_SETREG4(sc, AAC_RX_OIMR, ~0); } } static void aac_rkt_set_interrupts(struct aac_softc *sc, int enable) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "%sable interrupts", enable ? "en" : "dis"); if (enable) { if (sc->flags & AAC_FLAGS_NEW_COMM) AAC_MEM0_SETREG4(sc, AAC_RKT_OIMR, ~AAC_DB_INT_NEW_COMM); else AAC_MEM0_SETREG4(sc, AAC_RKT_OIMR, ~AAC_DB_INTERRUPTS); } else { AAC_MEM0_SETREG4(sc, AAC_RKT_OIMR, ~0); } } /* * New comm. interface: Send command functions */ static int aac_rx_send_command(struct aac_softc *sc, struct aac_command *cm) { u_int32_t index, device; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "send command (new comm.)"); index = AAC_MEM0_GETREG4(sc, AAC_RX_IQUE); if (index == 0xffffffffL) index = AAC_MEM0_GETREG4(sc, AAC_RX_IQUE); if (index == 0xffffffffL) return index; aac_enqueue_busy(cm); device = index; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys & 0xffffffffUL)); device += 4; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys >> 32)); device += 4; AAC_MEM1_SETREG4(sc, device, cm->cm_fib->Header.Size); AAC_MEM0_SETREG4(sc, AAC_RX_IQUE, index); return 0; } static int aac_rkt_send_command(struct aac_softc *sc, struct aac_command *cm) { u_int32_t index, device; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "send command (new comm.)"); index = AAC_MEM0_GETREG4(sc, AAC_RKT_IQUE); if (index == 0xffffffffL) index = AAC_MEM0_GETREG4(sc, AAC_RKT_IQUE); if (index == 0xffffffffL) return index; aac_enqueue_busy(cm); device = index; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys & 0xffffffffUL)); device += 4; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys >> 32)); device += 4; AAC_MEM1_SETREG4(sc, device, cm->cm_fib->Header.Size); AAC_MEM0_SETREG4(sc, AAC_RKT_IQUE, index); return 0; } /* * New comm. interface: get, set outbound queue index */ static int aac_rx_get_outb_queue(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RX_OQUE)); } static int aac_rkt_get_outb_queue(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RKT_OQUE)); } static void aac_rx_set_outb_queue(struct aac_softc *sc, int index) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RX_OQUE, index); } static void aac_rkt_set_outb_queue(struct aac_softc *sc, int index) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RKT_OQUE, index); } /* * Debugging and Diagnostics */ /* * Print some information about the controller. */ static void aac_describe_controller(struct aac_softc *sc) { struct aac_fib *fib; struct aac_adapter_info *info; char *adapter_type = "Adaptec RAID controller"; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); fib->data[0] = 0; if (aac_sync_fib(sc, RequestAdapterInfo, 0, fib, 1)) { device_printf(sc->aac_dev, "RequestAdapterInfo failed\n"); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } /* save the kernel revision structure for later use */ info = (struct aac_adapter_info *)&fib->data[0]; sc->aac_revision = info->KernelRevision; if (bootverbose) { device_printf(sc->aac_dev, "%s %dMHz, %dMB memory " "(%dMB cache, %dMB execution), %s\n", aac_describe_code(aac_cpu_variant, info->CpuVariant), info->ClockSpeed, info->TotalMem / (1024 * 1024), info->BufferMem / (1024 * 1024), info->ExecutionMem / (1024 * 1024), aac_describe_code(aac_battery_platform, info->batteryPlatform)); device_printf(sc->aac_dev, "Kernel %d.%d-%d, Build %d, S/N %6X\n", info->KernelRevision.external.comp.major, info->KernelRevision.external.comp.minor, info->KernelRevision.external.comp.dash, info->KernelRevision.buildNumber, (u_int32_t)(info->SerialNumber & 0xffffff)); device_printf(sc->aac_dev, "Supported Options=%b\n", sc->supported_options, "\20" "\1SNAPSHOT" "\2CLUSTERS" "\3WCACHE" "\4DATA64" "\5HOSTTIME" "\6RAID50" "\7WINDOW4GB" "\10SCSIUPGD" "\11SOFTERR" "\12NORECOND" "\13SGMAP64" "\14ALARM" "\15NONDASD" "\16SCSIMGT" "\17RAIDSCSI" "\21ADPTINFO" "\22NEWCOMM" "\23ARRAY64BIT" "\24HEATSENSOR"); } if (sc->supported_options & AAC_SUPPORTED_SUPPLEMENT_ADAPTER_INFO) { fib->data[0] = 0; if (aac_sync_fib(sc, RequestSupplementAdapterInfo, 0, fib, 1)) device_printf(sc->aac_dev, "RequestSupplementAdapterInfo failed\n"); else adapter_type = ((struct aac_supplement_adapter_info *) &fib->data[0])->AdapterTypeText; } device_printf(sc->aac_dev, "%s, aac driver %d.%d.%d-%d\n", adapter_type, AAC_DRIVER_MAJOR_VERSION, AAC_DRIVER_MINOR_VERSION, AAC_DRIVER_BUGFIX_LEVEL, AAC_DRIVER_BUILD); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); } /* * Look up a text description of a numeric error code and return a pointer to * same. */ static const char * aac_describe_code(const struct aac_code_lookup *table, u_int32_t code) { int i; for (i = 0; table[i].string != NULL; i++) if (table[i].code == code) return(table[i].string); return(table[i + 1].string); } /* * Management Interface */ static int aac_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct aac_softc *sc; sc = dev->si_drv1; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); device_busy(sc->aac_dev); devfs_set_cdevpriv(sc, aac_cdevpriv_dtor); return 0; } static int aac_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td) { union aac_statrequest *as; struct aac_softc *sc; int error = 0; as = (union aac_statrequest *)arg; sc = dev->si_drv1; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); switch (cmd) { case AACIO_STATS: switch (as->as_item) { case AACQ_FREE: case AACQ_BIO: case AACQ_READY: case AACQ_BUSY: bcopy(&sc->aac_qstat[as->as_item], &as->as_qstat, sizeof(struct aac_qstat)); break; default: error = ENOENT; break; } break; case FSACTL_SENDFIB: case FSACTL_SEND_LARGE_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_SENDFIB: case FSACTL_LNX_SEND_LARGE_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_SENDFIB"); error = aac_ioctl_sendfib(sc, arg); break; case FSACTL_SEND_RAW_SRB: arg = *(caddr_t*)arg; case FSACTL_LNX_SEND_RAW_SRB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_SEND_RAW_SRB"); error = aac_ioctl_send_raw_srb(sc, arg); break; case FSACTL_AIF_THREAD: case FSACTL_LNX_AIF_THREAD: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_AIF_THREAD"); error = EINVAL; break; case FSACTL_OPEN_GET_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_OPEN_GET_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_OPEN_GET_ADAPTER_FIB"); error = aac_open_aif(sc, arg); break; case FSACTL_GET_NEXT_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_NEXT_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_NEXT_ADAPTER_FIB"); error = aac_getnext_aif(sc, arg); break; case FSACTL_CLOSE_GET_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_CLOSE_GET_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_CLOSE_GET_ADAPTER_FIB"); error = aac_close_aif(sc, arg); break; case FSACTL_MINIPORT_REV_CHECK: arg = *(caddr_t*)arg; case FSACTL_LNX_MINIPORT_REV_CHECK: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_MINIPORT_REV_CHECK"); error = aac_rev_check(sc, arg); break; case FSACTL_QUERY_DISK: arg = *(caddr_t*)arg; case FSACTL_LNX_QUERY_DISK: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_QUERY_DISK"); error = aac_query_disk(sc, arg); break; case FSACTL_DELETE_DISK: case FSACTL_LNX_DELETE_DISK: /* * We don't trust the underland to tell us when to delete a * container, rather we rely on an AIF coming from the * controller */ error = 0; break; case FSACTL_GET_PCI_INFO: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_PCI_INFO: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_PCI_INFO"); error = aac_get_pci_info(sc, arg); break; case FSACTL_GET_FEATURES: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_FEATURES: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_FEATURES"); error = aac_supported_features(sc, arg); break; default: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "unsupported cmd 0x%lx\n", cmd); error = EINVAL; break; } return(error); } static int aac_poll(struct cdev *dev, int poll_events, struct thread *td) { struct aac_softc *sc; struct aac_fib_context *ctx; int revents; sc = dev->si_drv1; revents = 0; mtx_lock(&sc->aac_aifq_lock); if ((poll_events & (POLLRDNORM | POLLIN)) != 0) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (ctx->ctx_idx != sc->aifq_idx || ctx->ctx_wrap) { revents |= poll_events & (POLLIN | POLLRDNORM); break; } } } mtx_unlock(&sc->aac_aifq_lock); if (revents == 0) { if (poll_events & (POLLIN | POLLRDNORM)) selrecord(td, &sc->rcv_select); } return (revents); } static void aac_ioctl_event(struct aac_softc *sc, struct aac_event *event, void *arg) { switch (event->ev_type) { case AAC_EVENT_CMFREE: mtx_assert(&sc->aac_io_lock, MA_OWNED); if (aac_alloc_command(sc, (struct aac_command **)arg)) { aac_add_event(sc, event); return; } free(event, M_AACBUF); wakeup(arg); break; default: break; } } /* * Send a FIB supplied from userspace */ static int aac_ioctl_sendfib(struct aac_softc *sc, caddr_t ufib) { struct aac_command *cm; int size, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); cm = NULL; /* * Get a command */ mtx_lock(&sc->aac_io_lock); if (aac_alloc_command(sc, &cm)) { struct aac_event *event; event = malloc(sizeof(struct aac_event), M_AACBUF, M_NOWAIT | M_ZERO); if (event == NULL) { error = EBUSY; mtx_unlock(&sc->aac_io_lock); goto out; } event->ev_type = AAC_EVENT_CMFREE; event->ev_callback = aac_ioctl_event; event->ev_arg = &cm; aac_add_event(sc, event); msleep(&cm, &sc->aac_io_lock, 0, "sendfib", 0); } mtx_unlock(&sc->aac_io_lock); /* * Fetch the FIB header, then re-copy to get data as well. */ if ((error = copyin(ufib, cm->cm_fib, sizeof(struct aac_fib_header))) != 0) goto out; size = cm->cm_fib->Header.Size + sizeof(struct aac_fib_header); if (size > sc->aac_max_fib_size) { device_printf(sc->aac_dev, "incoming FIB oversized (%d > %d)\n", size, sc->aac_max_fib_size); size = sc->aac_max_fib_size; } if ((error = copyin(ufib, cm->cm_fib, size)) != 0) goto out; cm->cm_fib->Header.Size = size; cm->cm_timestamp = time_uptime; /* * Pass the FIB to the controller, wait for it to complete. */ mtx_lock(&sc->aac_io_lock); error = aac_wait_command(cm); mtx_unlock(&sc->aac_io_lock); if (error != 0) { device_printf(sc->aac_dev, "aac_wait_command return %d\n", error); goto out; } /* * Copy the FIB and data back out to the caller. */ size = cm->cm_fib->Header.Size; if (size > sc->aac_max_fib_size) { device_printf(sc->aac_dev, "outbound FIB oversized (%d > %d)\n", size, sc->aac_max_fib_size); size = sc->aac_max_fib_size; } error = copyout(cm->cm_fib, ufib, size); out: if (cm != NULL) { mtx_lock(&sc->aac_io_lock); aac_release_command(cm); mtx_unlock(&sc->aac_io_lock); } return(error); } /* * Send a passthrough FIB supplied from userspace */ static int aac_ioctl_send_raw_srb(struct aac_softc *sc, caddr_t arg) { struct aac_command *cm; struct aac_event *event; struct aac_fib *fib; struct aac_srb *srbcmd, *user_srb; struct aac_sg_entry *sge; void *srb_sg_address, *ureply; uint32_t fibsize, srb_sg_bytecount; int error, transfer_data; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); cm = NULL; transfer_data = 0; fibsize = 0; user_srb = (struct aac_srb *)arg; mtx_lock(&sc->aac_io_lock); if (aac_alloc_command(sc, &cm)) { event = malloc(sizeof(struct aac_event), M_AACBUF, M_NOWAIT | M_ZERO); if (event == NULL) { error = EBUSY; mtx_unlock(&sc->aac_io_lock); goto out; } event->ev_type = AAC_EVENT_CMFREE; event->ev_callback = aac_ioctl_event; event->ev_arg = &cm; aac_add_event(sc, event); msleep(cm, &sc->aac_io_lock, 0, "aacraw", 0); } mtx_unlock(&sc->aac_io_lock); cm->cm_data = NULL; fib = cm->cm_fib; srbcmd = (struct aac_srb *)fib->data; error = copyin(&user_srb->data_len, &fibsize, sizeof(uint32_t)); if (error != 0) goto out; if (fibsize > (sc->aac_max_fib_size - sizeof(struct aac_fib_header))) { error = EINVAL; goto out; } error = copyin(user_srb, srbcmd, fibsize); if (error != 0) goto out; srbcmd->function = 0; srbcmd->retry_limit = 0; if (srbcmd->sg_map.SgCount > 1) { error = EINVAL; goto out; } /* Retrieve correct SG entries. */ if (fibsize == (sizeof(struct aac_srb) + srbcmd->sg_map.SgCount * sizeof(struct aac_sg_entry))) { struct aac_sg_entry sg; sge = srbcmd->sg_map.SgEntry; if ((error = copyin(sge, &sg, sizeof(sg))) != 0) goto out; srb_sg_bytecount = sg.SgByteCount; srb_sg_address = (void *)(uintptr_t)sg.SgAddress; } #ifdef __amd64__ else if (fibsize == (sizeof(struct aac_srb) + srbcmd->sg_map.SgCount * sizeof(struct aac_sg_entry64))) { struct aac_sg_entry64 *sge64; struct aac_sg_entry64 sg; sge = NULL; sge64 = (struct aac_sg_entry64 *)srbcmd->sg_map.SgEntry; if ((error = copyin(sge64, &sg, sizeof(sg))) != 0) goto out; srb_sg_bytecount = sg.SgByteCount; srb_sg_address = (void *)sg.SgAddress; if (sge64->SgAddress > 0xffffffffull && (sc->flags & AAC_FLAGS_SG_64BIT) == 0) { error = EINVAL; goto out; } } #endif else { error = EINVAL; goto out; } ureply = (char *)arg + fibsize; srbcmd->data_len = srb_sg_bytecount; if (srbcmd->sg_map.SgCount == 1) transfer_data = 1; cm->cm_sgtable = (struct aac_sg_table *)&srbcmd->sg_map; if (transfer_data) { cm->cm_datalen = srb_sg_bytecount; cm->cm_data = malloc(cm->cm_datalen, M_AACBUF, M_NOWAIT); if (cm->cm_data == NULL) { error = ENOMEM; goto out; } if (srbcmd->flags & AAC_SRB_FLAGS_DATA_IN) cm->cm_flags |= AAC_CMD_DATAIN; if (srbcmd->flags & AAC_SRB_FLAGS_DATA_OUT) { cm->cm_flags |= AAC_CMD_DATAOUT; error = copyin(srb_sg_address, cm->cm_data, cm->cm_datalen); if (error != 0) goto out; } } fib->Header.Size = sizeof(struct aac_fib_header) + sizeof(struct aac_srb); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC | AAC_FIBSTATE_FAST_RESPONSE; fib->Header.Command = (sc->flags & AAC_FLAGS_SG_64BIT) != 0 ? ScsiPortCommandU64 : ScsiPortCommand; mtx_lock(&sc->aac_io_lock); aac_wait_command(cm); mtx_unlock(&sc->aac_io_lock); if (transfer_data && (srbcmd->flags & AAC_SRB_FLAGS_DATA_IN) != 0) { error = copyout(cm->cm_data, srb_sg_address, cm->cm_datalen); if (error != 0) goto out; } error = copyout(fib->data, ureply, sizeof(struct aac_srb_response)); out: if (cm != NULL) { if (cm->cm_data != NULL) free(cm->cm_data, M_AACBUF); mtx_lock(&sc->aac_io_lock); aac_release_command(cm); mtx_unlock(&sc->aac_io_lock); } return(error); } /* * cdevpriv interface private destructor. */ static void aac_cdevpriv_dtor(void *arg) { struct aac_softc *sc; sc = arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); device_unbusy(sc->aac_dev); } /* * Handle an AIF sent to us by the controller; queue it for later reference. * If the queue fills up, then drop the older entries. */ static void aac_handle_aif(struct aac_softc *sc, struct aac_fib *fib) { struct aac_aif_command *aif; struct aac_container *co, *co_next; struct aac_fib_context *ctx; struct aac_mntinforesp *mir; int next, current, found; int count = 0, added = 0, i = 0; uint32_t channel; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); aif = (struct aac_aif_command*)&fib->data[0]; aac_print_aif(sc, aif); /* Is it an event that we should care about? */ switch (aif->command) { case AifCmdEventNotify: switch (aif->data.EN.type) { case AifEnAddContainer: case AifEnDeleteContainer: /* * A container was added or deleted, but the message * doesn't tell us anything else! Re-enumerate the * containers and sort things out. */ aac_alloc_sync_fib(sc, &fib); do { /* * Ask the controller for its containers one at * a time. * XXX What if the controller's list changes * midway through this enumaration? * XXX This should be done async. */ if ((mir = aac_get_container_info(sc, fib, i)) == NULL) continue; if (i == 0) count = mir->MntRespCount; /* * Check the container against our list. * co->co_found was already set to 0 in a * previous run. */ if ((mir->Status == ST_OK) && (mir->MntTable[0].VolType != CT_NONE)) { found = 0; TAILQ_FOREACH(co, &sc->aac_container_tqh, co_link) { if (co->co_mntobj.ObjectId == mir->MntTable[0].ObjectId) { co->co_found = 1; found = 1; break; } } /* * If the container matched, continue * in the list. */ if (found) { i++; continue; } /* * This is a new container. Do all the * appropriate things to set it up. */ aac_add_container(sc, mir, 1); added = 1; } i++; } while ((i < count) && (i < AAC_MAX_CONTAINERS)); aac_release_sync_fib(sc); /* * Go through our list of containers and see which ones * were not marked 'found'. Since the controller didn't * list them they must have been deleted. Do the * appropriate steps to destroy the device. Also reset * the co->co_found field. */ co = TAILQ_FIRST(&sc->aac_container_tqh); while (co != NULL) { if (co->co_found == 0) { mtx_unlock(&sc->aac_io_lock); bus_topo_lock(); device_delete_child(sc->aac_dev, co->co_disk); bus_topo_unlock(); mtx_lock(&sc->aac_io_lock); co_next = TAILQ_NEXT(co, co_link); mtx_lock(&sc->aac_container_lock); TAILQ_REMOVE(&sc->aac_container_tqh, co, co_link); mtx_unlock(&sc->aac_container_lock); free(co, M_AACBUF); co = co_next; } else { co->co_found = 0; co = TAILQ_NEXT(co, co_link); } } /* Attach the newly created containers */ if (added) { mtx_unlock(&sc->aac_io_lock); bus_topo_lock(); bus_attach_children(sc->aac_dev); bus_topo_unlock(); mtx_lock(&sc->aac_io_lock); } break; case AifEnEnclosureManagement: switch (aif->data.EN.data.EEE.eventType) { case AIF_EM_DRIVE_INSERTION: case AIF_EM_DRIVE_REMOVAL: channel = aif->data.EN.data.EEE.unitID; if (sc->cam_rescan_cb != NULL) sc->cam_rescan_cb(sc, (channel >> 24) & 0xF, (channel & 0xFFFF)); break; } break; case AifEnAddJBOD: case AifEnDeleteJBOD: channel = aif->data.EN.data.ECE.container; if (sc->cam_rescan_cb != NULL) sc->cam_rescan_cb(sc, (channel >> 24) & 0xF, AAC_CAM_TARGET_WILDCARD); break; default: break; } default: break; } /* Copy the AIF data to the AIF queue for ioctl retrieval */ mtx_lock(&sc->aac_aifq_lock); current = sc->aifq_idx; next = (current + 1) % AAC_AIFQ_LENGTH; if (next == 0) sc->aifq_filled = 1; bcopy(fib, &sc->aac_aifq[current], sizeof(struct aac_fib)); /* modify AIF contexts */ if (sc->aifq_filled) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (next == ctx->ctx_idx) ctx->ctx_wrap = 1; else if (current == ctx->ctx_idx && ctx->ctx_wrap) ctx->ctx_idx = next; } } sc->aifq_idx = next; /* On the off chance that someone is sleeping for an aif... */ if (sc->aac_state & AAC_STATE_AIF_SLEEPER) wakeup(sc->aac_aifq); /* Wakeup any poll()ers */ selwakeuppri(&sc->rcv_select, PRIBIO); mtx_unlock(&sc->aac_aifq_lock); } /* * Return the Revision of the driver to userspace and check to see if the * userspace app is possibly compatible. This is extremely bogus since * our driver doesn't follow Adaptec's versioning system. Cheat by just * returning what the card reported. */ static int aac_rev_check(struct aac_softc *sc, caddr_t udata) { struct aac_rev_check rev_check; struct aac_rev_check_resp rev_check_resp; int error = 0; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Copyin the revision struct from userspace */ if ((error = copyin(udata, (caddr_t)&rev_check, sizeof(struct aac_rev_check))) != 0) { return error; } fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "Userland revision= %d\n", rev_check.callingRevision.buildNumber); /* * Doctor up the response struct. */ rev_check_resp.possiblyCompatible = 1; rev_check_resp.adapterSWRevision.external.comp.major = AAC_DRIVER_MAJOR_VERSION; rev_check_resp.adapterSWRevision.external.comp.minor = AAC_DRIVER_MINOR_VERSION; rev_check_resp.adapterSWRevision.external.comp.type = AAC_DRIVER_TYPE; rev_check_resp.adapterSWRevision.external.comp.dash = AAC_DRIVER_BUGFIX_LEVEL; rev_check_resp.adapterSWRevision.buildNumber = AAC_DRIVER_BUILD; return(copyout((caddr_t)&rev_check_resp, udata, sizeof(struct aac_rev_check_resp))); } /* * Pass the fib context to the caller */ static int aac_open_aif(struct aac_softc *sc, caddr_t arg) { struct aac_fib_context *fibctx, *ctx; int error = 0; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); fibctx = malloc(sizeof(struct aac_fib_context), M_AACBUF, M_NOWAIT|M_ZERO); if (fibctx == NULL) return (ENOMEM); mtx_lock(&sc->aac_aifq_lock); /* all elements are already 0, add to queue */ if (sc->fibctx == NULL) sc->fibctx = fibctx; else { for (ctx = sc->fibctx; ctx->next; ctx = ctx->next) ; ctx->next = fibctx; fibctx->prev = ctx; } /* evaluate unique value */ fibctx->unique = (*(u_int32_t *)&fibctx & 0xffffffff); ctx = sc->fibctx; while (ctx != fibctx) { if (ctx->unique == fibctx->unique) { fibctx->unique++; ctx = sc->fibctx; } else { ctx = ctx->next; } } mtx_unlock(&sc->aac_aifq_lock); error = copyout(&fibctx->unique, (void *)arg, sizeof(u_int32_t)); if (error) aac_close_aif(sc, (caddr_t)ctx); return error; } /* * Close the caller's fib context */ static int aac_close_aif(struct aac_softc *sc, caddr_t arg) { struct aac_fib_context *ctx; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_aifq_lock); for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (ctx->unique == *(uint32_t *)&arg) { if (ctx == sc->fibctx) sc->fibctx = NULL; else { ctx->prev->next = ctx->next; if (ctx->next) ctx->next->prev = ctx->prev; } break; } } mtx_unlock(&sc->aac_aifq_lock); if (ctx) free(ctx, M_AACBUF); return 0; } /* * Pass the caller the next AIF in their queue */ static int aac_getnext_aif(struct aac_softc *sc, caddr_t arg) { struct get_adapter_fib_ioctl agf; struct aac_fib_context *ctx; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); #ifdef COMPAT_FREEBSD32 if (SV_CURPROC_FLAG(SV_ILP32)) { struct get_adapter_fib_ioctl32 agf32; error = copyin(arg, &agf32, sizeof(agf32)); if (error == 0) { agf.AdapterFibContext = agf32.AdapterFibContext; agf.Wait = agf32.Wait; agf.AifFib = (caddr_t)(uintptr_t)agf32.AifFib; } } else #endif error = copyin(arg, &agf, sizeof(agf)); if (error == 0) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (agf.AdapterFibContext == ctx->unique) break; } if (!ctx) return (EFAULT); error = aac_return_aif(sc, ctx, agf.AifFib); if (error == EAGAIN && agf.Wait) { fwprintf(sc, HBA_FLAGS_DBG_AIF_B, "aac_getnext_aif(): waiting for AIF"); sc->aac_state |= AAC_STATE_AIF_SLEEPER; while (error == EAGAIN) { error = tsleep(sc->aac_aifq, PRIBIO | PCATCH, "aacaif", 0); if (error == 0) error = aac_return_aif(sc, ctx, agf.AifFib); } sc->aac_state &= ~AAC_STATE_AIF_SLEEPER; } } return(error); } /* * Hand the next AIF off the top of the queue out to userspace. */ static int aac_return_aif(struct aac_softc *sc, struct aac_fib_context *ctx, caddr_t uptr) { int current, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_aifq_lock); current = ctx->ctx_idx; if (current == sc->aifq_idx && !ctx->ctx_wrap) { /* empty */ mtx_unlock(&sc->aac_aifq_lock); return (EAGAIN); } error = copyout(&sc->aac_aifq[current], (void *)uptr, sizeof(struct aac_fib)); if (error) device_printf(sc->aac_dev, "aac_return_aif: copyout returned %d\n", error); else { ctx->ctx_wrap = 0; ctx->ctx_idx = (current + 1) % AAC_AIFQ_LENGTH; } mtx_unlock(&sc->aac_aifq_lock); return(error); } static int aac_get_pci_info(struct aac_softc *sc, caddr_t uptr) { struct aac_pci_info { u_int32_t bus; u_int32_t slot; } pciinf; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); pciinf.bus = pci_get_bus(sc->aac_dev); pciinf.slot = pci_get_slot(sc->aac_dev); error = copyout((caddr_t)&pciinf, uptr, sizeof(struct aac_pci_info)); return (error); } static int aac_supported_features(struct aac_softc *sc, caddr_t uptr) { struct aac_features f; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if ((error = copyin(uptr, &f, sizeof (f))) != 0) return (error); /* * When the management driver receives FSACTL_GET_FEATURES ioctl with * ALL zero in the featuresState, the driver will return the current * state of all the supported features, the data field will not be * valid. * When the management driver receives FSACTL_GET_FEATURES ioctl with * a specific bit set in the featuresState, the driver will return the * current state of this specific feature and whatever data that are * associated with the feature in the data field or perform whatever * action needed indicates in the data field. */ if (f.feat.fValue == 0) { f.feat.fBits.largeLBA = (sc->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; /* TODO: In the future, add other features state here as well */ } else { if (f.feat.fBits.largeLBA) f.feat.fBits.largeLBA = (sc->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; /* TODO: Add other features state and data in the future */ } error = copyout(&f, uptr, sizeof (f)); return (error); } /* * Give the userland some information about the container. The AAC arch * expects the driver to be a SCSI passthrough type driver, so it expects * the containers to have b:t:l numbers. Fake it. */ static int aac_query_disk(struct aac_softc *sc, caddr_t uptr) { struct aac_query_disk query_disk; struct aac_container *co; struct aac_disk *disk; int error, id; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); disk = NULL; error = copyin(uptr, (caddr_t)&query_disk, sizeof(struct aac_query_disk)); if (error) return (error); id = query_disk.ContainerNumber; if (id == -1) return (EINVAL); mtx_lock(&sc->aac_container_lock); TAILQ_FOREACH(co, &sc->aac_container_tqh, co_link) { if (co->co_mntobj.ObjectId == id) break; } if (co == NULL) { query_disk.Valid = 0; query_disk.Locked = 0; query_disk.Deleted = 1; /* XXX is this right? */ } else { disk = device_get_softc(co->co_disk); query_disk.Valid = 1; query_disk.Locked = (disk->ad_flags & AAC_DISK_OPEN) ? 1 : 0; query_disk.Deleted = 0; query_disk.Bus = device_get_unit(sc->aac_dev); query_disk.Target = disk->unit; query_disk.Lun = 0; query_disk.UnMapped = 0; sprintf(&query_disk.diskDeviceName[0], "%s%d", disk->ad_disk->d_name, disk->ad_disk->d_unit); } mtx_unlock(&sc->aac_container_lock); error = copyout((caddr_t)&query_disk, uptr, sizeof(struct aac_query_disk)); return (error); } static void aac_get_bus_info(struct aac_softc *sc) { struct aac_fib *fib; struct aac_ctcfg *c_cmd; struct aac_ctcfg_resp *c_resp; struct aac_vmioctl *vmi; struct aac_vmi_businf_resp *vmi_resp; struct aac_getbusinf businfo; struct aac_sim *caminf; device_t child; int i, found, error; mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); c_cmd = (struct aac_ctcfg *)&fib->data[0]; bzero(c_cmd, sizeof(struct aac_ctcfg)); c_cmd->Command = VM_ContainerConfig; c_cmd->cmd = CT_GET_SCSI_METHOD; c_cmd->param = 0; error = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_ctcfg)); if (error) { device_printf(sc->aac_dev, "Error %d sending " "VM_ContainerConfig command\n", error); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } c_resp = (struct aac_ctcfg_resp *)&fib->data[0]; if (c_resp->Status != ST_OK) { device_printf(sc->aac_dev, "VM_ContainerConfig returned 0x%x\n", c_resp->Status); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } sc->scsi_method_id = c_resp->param; vmi = (struct aac_vmioctl *)&fib->data[0]; bzero(vmi, sizeof(struct aac_vmioctl)); vmi->Command = VM_Ioctl; vmi->ObjType = FT_DRIVE; vmi->MethId = sc->scsi_method_id; vmi->ObjId = 0; vmi->IoctlCmd = GetBusInfo; error = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_vmi_businf_resp)); if (error) { device_printf(sc->aac_dev, "Error %d sending VMIoctl command\n", error); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } vmi_resp = (struct aac_vmi_businf_resp *)&fib->data[0]; if (vmi_resp->Status != ST_OK) { device_printf(sc->aac_dev, "VM_Ioctl returned %d\n", vmi_resp->Status); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } bcopy(&vmi_resp->BusInf, &businfo, sizeof(struct aac_getbusinf)); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); found = 0; for (i = 0; i < businfo.BusCount; i++) { if (businfo.BusValid[i] != AAC_BUS_VALID) continue; caminf = (struct aac_sim *)malloc( sizeof(struct aac_sim), M_AACBUF, M_NOWAIT | M_ZERO); if (caminf == NULL) { device_printf(sc->aac_dev, "No memory to add passthrough bus %d\n", i); break; } child = device_add_child(sc->aac_dev, "aacp", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(sc->aac_dev, "device_add_child failed for passthrough bus %d\n", i); free(caminf, M_AACBUF); break; } caminf->TargetsPerBus = businfo.TargetsPerBus; caminf->BusNumber = i; caminf->InitiatorBusId = businfo.InitiatorBusId[i]; caminf->aac_sc = sc; caminf->sim_dev = child; device_set_ivars(child, caminf); device_set_desc(child, "SCSI Passthrough Bus"); TAILQ_INSERT_TAIL(&sc->aac_sim_tqh, caminf, sim_link); found = 1; } if (found) bus_attach_children(sc->aac_dev); } diff --git a/sys/dev/acpi_support/acpi_asus_wmi.c b/sys/dev/acpi_support/acpi_asus_wmi.c index 0e9c35d42793..0198ccada3ed 100644 --- a/sys/dev/acpi_support/acpi_asus_wmi.c +++ b/sys/dev/acpi_support/acpi_asus_wmi.c @@ -1,1006 +1,1006 @@ /*- * Copyright (c) 2012 Alexander Motin * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_acpi.h" #include "opt_evdev.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_wmi_if.h" #include #include "backlight_if.h" #ifdef EVDEV_SUPPORT #include #include #define NO_KEY KEY_RESERVED #endif #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("ASUS-WMI") #define ACPI_ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" #define ACPI_ASUS_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" #define ACPI_EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" /* WMI Methods */ #define ASUS_WMI_METHODID_SPEC 0x43455053 #define ASUS_WMI_METHODID_SFUN 0x4E554653 #define ASUS_WMI_METHODID_DSTS 0x53544344 #define ASUS_WMI_METHODID_DSTS2 0x53545344 #define ASUS_WMI_METHODID_DEVS 0x53564544 #define ASUS_WMI_METHODID_INIT 0x54494E49 #define ASUS_WMI_METHODID_HKEY 0x59454B48 #define ASUS_WMI_UNSUPPORTED_METHOD 0xFFFFFFFE /* Wireless */ #define ASUS_WMI_DEVID_HW_SWITCH 0x00010001 #define ASUS_WMI_DEVID_WIRELESS_LED 0x00010002 #define ASUS_WMI_DEVID_CWAP 0x00010003 #define ASUS_WMI_DEVID_WLAN 0x00010011 #define ASUS_WMI_DEVID_BLUETOOTH 0x00010013 #define ASUS_WMI_DEVID_GPS 0x00010015 #define ASUS_WMI_DEVID_WIMAX 0x00010017 #define ASUS_WMI_DEVID_WWAN3G 0x00010019 #define ASUS_WMI_DEVID_UWB 0x00010021 /* LEDs */ #define ASUS_WMI_DEVID_LED1 0x00020011 #define ASUS_WMI_DEVID_LED2 0x00020012 #define ASUS_WMI_DEVID_LED3 0x00020013 #define ASUS_WMI_DEVID_LED4 0x00020014 #define ASUS_WMI_DEVID_LED5 0x00020015 #define ASUS_WMI_DEVID_LED6 0x00020016 /* Backlight and Brightness */ #define ASUS_WMI_DEVID_BACKLIGHT 0x00050011 #define ASUS_WMI_DEVID_BRIGHTNESS 0x00050012 #define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 #define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* Misc */ #define ASUS_WMI_DEVID_CAMERA 0x00060013 #define ASUS_WMI_DEVID_CARDREADER 0x00080013 #define ASUS_WMI_DEVID_TOUCHPAD 0x00100011 #define ASUS_WMI_DEVID_TOUCHPAD_LED 0x00100012 #define ASUS_WMI_DEVID_TUF_RGB_MODE 0x00100056 #define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011 #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY 0x00120075 /* DSTS masks */ #define ASUS_WMI_DSTS_STATUS_BIT 0x00000001 #define ASUS_WMI_DSTS_UNKNOWN_BIT 0x00000002 #define ASUS_WMI_DSTS_PRESENCE_BIT 0x00010000 #define ASUS_WMI_DSTS_USER_BIT 0x00020000 #define ASUS_WMI_DSTS_BIOS_BIT 0x00040000 #define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 /* Events */ #define ASUS_WMI_EVENT_QUEUE_SIZE 0x10 #define ASUS_WMI_EVENT_QUEUE_END 0x1 #define ASUS_WMI_EVENT_MASK 0xFFFF #define ASUS_WMI_EVENT_VALUE_ATK 0xFF struct acpi_asus_wmi_softc { device_t dev; device_t wmi_dev; const char *notify_guid; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; int dsts_id; int handle_keys; bool event_queue; struct cdev *kbd_bkl; uint32_t kbd_bkl_level; uint32_t tuf_rgb_mode; uint32_t ttp_mode; #ifdef EVDEV_SUPPORT struct evdev_dev *evdev; #endif }; static struct { char *name; int dev_id; char *description; int flag_rdonly; } acpi_asus_wmi_sysctls[] = { { .name = "hw_switch", .dev_id = ASUS_WMI_DEVID_HW_SWITCH, .description = "hw_switch", }, { .name = "wireless_led", .dev_id = ASUS_WMI_DEVID_WIRELESS_LED, .description = "Wireless LED control", }, { .name = "cwap", .dev_id = ASUS_WMI_DEVID_CWAP, .description = "Alt+F2 function", }, { .name = "wlan", .dev_id = ASUS_WMI_DEVID_WLAN, .description = "WLAN power control", }, { .name = "bluetooth", .dev_id = ASUS_WMI_DEVID_BLUETOOTH, .description = "Bluetooth power control", }, { .name = "gps", .dev_id = ASUS_WMI_DEVID_GPS, .description = "GPS power control", }, { .name = "wimax", .dev_id = ASUS_WMI_DEVID_WIMAX, .description = "WiMAX power control", }, { .name = "wwan3g", .dev_id = ASUS_WMI_DEVID_WWAN3G, .description = "WWAN-3G power control", }, { .name = "uwb", .dev_id = ASUS_WMI_DEVID_UWB, .description = "UWB power control", }, { .name = "led1", .dev_id = ASUS_WMI_DEVID_LED1, .description = "LED1 control", }, { .name = "led2", .dev_id = ASUS_WMI_DEVID_LED2, .description = "LED2 control", }, { .name = "led3", .dev_id = ASUS_WMI_DEVID_LED3, .description = "LED3 control", }, { .name = "led4", .dev_id = ASUS_WMI_DEVID_LED4, .description = "LED4 control", }, { .name = "led5", .dev_id = ASUS_WMI_DEVID_LED5, .description = "LED5 control", }, { .name = "led6", .dev_id = ASUS_WMI_DEVID_LED6, .description = "LED6 control", }, { .name = "backlight", .dev_id = ASUS_WMI_DEVID_BACKLIGHT, .description = "LCD backlight on/off control", }, { .name = "brightness", .dev_id = ASUS_WMI_DEVID_BRIGHTNESS, .description = "LCD backlight brightness control", }, { .name = "kbd_backlight", .dev_id = ASUS_WMI_DEVID_KBD_BACKLIGHT, .description = "Keyboard backlight brightness control", }, { .name = "light_sensor", .dev_id = ASUS_WMI_DEVID_LIGHT_SENSOR, .description = "Ambient light sensor", }, { .name = "camera", .dev_id = ASUS_WMI_DEVID_CAMERA, .description = "Camera power control", }, { .name = "cardreader", .dev_id = ASUS_WMI_DEVID_CARDREADER, .description = "Cardreader power control", }, { .name = "touchpad", .dev_id = ASUS_WMI_DEVID_TOUCHPAD, .description = "Touchpad control", }, { .name = "touchpad_led", .dev_id = ASUS_WMI_DEVID_TOUCHPAD_LED, .description = "Touchpad LED control", }, { .name = "themperature", .dev_id = ASUS_WMI_DEVID_THERMAL_CTRL, .description = "Temperature (C)", .flag_rdonly = 1 }, { .name = "fan_speed", .dev_id = ASUS_WMI_DEVID_FAN_CTRL, .description = "Fan speed (0-3)", .flag_rdonly = 1 }, { .name = "processor_state", .dev_id = ASUS_WMI_DEVID_PROCESSOR_STATE, .flag_rdonly = 1 }, { .name = "throttle_thermal_policy", .dev_id = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY, .description = "Throttle Thermal Policy " "(0 - default, 1 - overboost, 2 - silent)", }, { NULL, 0, NULL, 0 } }; #ifdef EVDEV_SUPPORT static const struct { UINT32 notify; uint16_t key; } acpi_asus_wmi_evdev_map[] = { { 0x20, KEY_BRIGHTNESSDOWN }, { 0x2f, KEY_BRIGHTNESSUP }, { 0x30, KEY_VOLUMEUP }, { 0x31, KEY_VOLUMEDOWN }, { 0x32, KEY_MUTE }, { 0x35, KEY_SCREENLOCK }, { 0x38, KEY_PROG3 }, /* Armoury Crate */ { 0x40, KEY_PREVIOUSSONG }, { 0x41, KEY_NEXTSONG }, { 0x43, KEY_STOPCD }, /* Stop/Eject */ { 0x45, KEY_PLAYPAUSE }, { 0x4f, KEY_LEFTMETA }, /* Fn-locked "Windows" Key */ { 0x4c, KEY_MEDIA }, /* WMP Key */ { 0x50, KEY_EMAIL }, { 0x51, KEY_WWW }, { 0x55, KEY_CALC }, { 0x57, NO_KEY }, /* Battery mode */ { 0x58, NO_KEY }, /* AC mode */ { 0x5C, KEY_F15 }, /* Power Gear key */ { 0x5D, KEY_WLAN }, /* Wireless console Toggle */ { 0x5E, KEY_WLAN }, /* Wireless console Enable */ { 0x5F, KEY_WLAN }, /* Wireless console Disable */ { 0x60, KEY_TOUCHPAD_ON }, { 0x61, KEY_SWITCHVIDEOMODE }, /* SDSP LCD only */ { 0x62, KEY_SWITCHVIDEOMODE }, /* SDSP CRT only */ { 0x63, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + CRT */ { 0x64, KEY_SWITCHVIDEOMODE }, /* SDSP TV */ { 0x65, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + TV */ { 0x66, KEY_SWITCHVIDEOMODE }, /* SDSP CRT + TV */ { 0x67, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + CRT + TV */ { 0x6B, KEY_TOUCHPAD_TOGGLE }, { 0x6E, NO_KEY }, /* Low Battery notification */ { 0x71, KEY_F13 }, /* General-purpose button */ { 0x79, NO_KEY }, /* Charger type dectection notification */ { 0x7a, KEY_ALS_TOGGLE }, /* Ambient Light Sensor Toggle */ { 0x7c, KEY_MICMUTE }, { 0x7D, KEY_BLUETOOTH }, /* Bluetooth Enable */ { 0x7E, KEY_BLUETOOTH }, /* Bluetooth Disable */ { 0x82, KEY_CAMERA }, { 0x86, KEY_PROG1 }, /* MyASUS Key */ { 0x88, KEY_RFKILL }, /* Radio Toggle Key */ { 0x8A, KEY_PROG1 }, /* Color enhancement mode */ { 0x8C, KEY_SWITCHVIDEOMODE }, /* SDSP DVI only */ { 0x8D, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + DVI */ { 0x8E, KEY_SWITCHVIDEOMODE }, /* SDSP CRT + DVI */ { 0x8F, KEY_SWITCHVIDEOMODE }, /* SDSP TV + DVI */ { 0x90, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + CRT + DVI */ { 0x91, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + TV + DVI */ { 0x92, KEY_SWITCHVIDEOMODE }, /* SDSP CRT + TV + DVI */ { 0x93, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + CRT + TV + DVI */ { 0x95, KEY_MEDIA }, { 0x99, KEY_PHONE }, /* Conflicts with fan mode switch */ { 0xA0, KEY_SWITCHVIDEOMODE }, /* SDSP HDMI only */ { 0xA1, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + HDMI */ { 0xA2, KEY_SWITCHVIDEOMODE }, /* SDSP CRT + HDMI */ { 0xA3, KEY_SWITCHVIDEOMODE }, /* SDSP TV + HDMI */ { 0xA4, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + CRT + HDMI */ { 0xA5, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + TV + HDMI */ { 0xA6, KEY_SWITCHVIDEOMODE }, /* SDSP CRT + TV + HDMI */ { 0xA7, KEY_SWITCHVIDEOMODE }, /* SDSP LCD + CRT + TV + HDMI */ { 0xAE, KEY_FN_F5 }, /* Fn+F5 fan mode on 2020+ */ { 0xB3, KEY_PROG4 }, /* AURA */ { 0xB5, KEY_CALC }, { 0xC4, KEY_KBDILLUMUP }, { 0xC5, KEY_KBDILLUMDOWN }, { 0xC6, NO_KEY }, /* Ambient Light Sensor notification */ { 0xFA, KEY_PROG2 }, /* Lid flip action */ { 0xBD, KEY_PROG2 }, /* Lid flip action on ROG xflow laptops */ }; #endif ACPI_SERIAL_DECL(asus_wmi, "ASUS WMI device"); static void acpi_asus_wmi_identify(driver_t *driver, device_t parent); static int acpi_asus_wmi_probe(device_t dev); static int acpi_asus_wmi_attach(device_t dev); static int acpi_asus_wmi_detach(device_t dev); static int acpi_asus_wmi_suspend(device_t dev); static int acpi_asus_wmi_resume(device_t dev); static int acpi_asus_wmi_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_asus_wmi_sysctl_set(struct acpi_asus_wmi_softc *sc, int dev_id, int arg, int oldarg); static int acpi_asus_wmi_sysctl_get(struct acpi_asus_wmi_softc *sc, int dev_id); static int acpi_asus_wmi_evaluate_method(device_t wmi_dev, int method, UINT32 arg0, UINT32 arg1, UINT32 arg2, UINT32 *retval); static int acpi_wpi_asus_get_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 *retval); static int acpi_wpi_asus_set_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 ctrl_param, UINT32 *retval); static int acpi_asus_wmi_get_event_code(device_t wmi_dev, UINT32 notify, int *code); static void acpi_asus_wmi_notify(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_asus_wmi_backlight_update_status(device_t dev, struct backlight_props *props); static int acpi_asus_wmi_backlight_get_status(device_t dev, struct backlight_props *props); static int acpi_asus_wmi_backlight_get_info(device_t dev, struct backlight_info *info); static device_method_t acpi_asus_wmi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, acpi_asus_wmi_identify), DEVMETHOD(device_probe, acpi_asus_wmi_probe), DEVMETHOD(device_attach, acpi_asus_wmi_attach), DEVMETHOD(device_detach, acpi_asus_wmi_detach), DEVMETHOD(device_suspend, acpi_asus_wmi_suspend), DEVMETHOD(device_resume, acpi_asus_wmi_resume), /* Backlight interface */ DEVMETHOD(backlight_update_status, acpi_asus_wmi_backlight_update_status), DEVMETHOD(backlight_get_status, acpi_asus_wmi_backlight_get_status), DEVMETHOD(backlight_get_info, acpi_asus_wmi_backlight_get_info), DEVMETHOD_END }; static driver_t acpi_asus_wmi_driver = { "acpi_asus_wmi", acpi_asus_wmi_methods, sizeof(struct acpi_asus_wmi_softc), }; DRIVER_MODULE(acpi_asus_wmi, acpi_wmi, acpi_asus_wmi_driver, 0, 0); MODULE_DEPEND(acpi_asus_wmi, acpi_wmi, 1, 1, 1); MODULE_DEPEND(acpi_asus_wmi, acpi, 1, 1, 1); MODULE_DEPEND(acpi_asus_wmi, backlight, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(acpi_asus_wmi, evdev, 1, 1, 1); #endif static const uint32_t acpi_asus_wmi_backlight_levels[] = { 0, 33, 66, 100 }; static inline uint32_t devstate_to_kbd_bkl_level(UINT32 val) { return (acpi_asus_wmi_backlight_levels[val & 0x3]); } static inline UINT32 kbd_bkl_level_to_devstate(uint32_t bkl) { UINT32 val; int i; for (i = 0; i < nitems(acpi_asus_wmi_backlight_levels); i++) { if (bkl < acpi_asus_wmi_backlight_levels[i]) break; } val = (i - 1) & 0x3; if (val != 0) val |= 0x80; return(val); } static void acpi_asus_wmi_identify(driver_t *driver, device_t parent) { /* Don't do anything if driver is disabled. */ if (acpi_disabled("asus_wmi")) return; /* Add only a single device instance. */ - if (device_find_child(parent, "acpi_asus_wmi", -1) != NULL) + if (device_find_child(parent, "acpi_asus_wmi", DEVICE_UNIT_ANY) != NULL) return; /* Check management GUID to see whether system is compatible. */ if (!ACPI_WMI_PROVIDES_GUID_STRING(parent, ACPI_ASUS_WMI_MGMT_GUID)) return; - if (BUS_ADD_CHILD(parent, 0, "acpi_asus_wmi", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "acpi_asus_wmi", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add acpi_asus_wmi child failed\n"); } static int acpi_asus_wmi_probe(device_t dev) { if (!ACPI_WMI_PROVIDES_GUID_STRING(device_get_parent(dev), ACPI_ASUS_WMI_MGMT_GUID)) return (EINVAL); device_set_desc(dev, "ASUS WMI device"); return (0); } static int acpi_asus_wmi_attach(device_t dev) { struct acpi_asus_wmi_softc *sc; UINT32 val; int dev_id, i, code; bool have_kbd_bkl = false; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); sc = device_get_softc(dev); sc->dev = dev; sc->wmi_dev = device_get_parent(dev); sc->handle_keys = 1; /* Check management GUID. */ if (!ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_ASUS_WMI_MGMT_GUID)) { device_printf(dev, "WMI device does not provide the ASUS management GUID\n"); return (EINVAL); } /* Find proper DSTS method. */ sc->dsts_id = ASUS_WMI_METHODID_DSTS; next: for (i = 0; acpi_asus_wmi_sysctls[i].name != NULL; ++i) { dev_id = acpi_asus_wmi_sysctls[i].dev_id; if (acpi_wpi_asus_get_devstate(sc, dev_id, &val)) continue; break; } if (acpi_asus_wmi_sysctls[i].name == NULL) { if (sc->dsts_id == ASUS_WMI_METHODID_DSTS) { sc->dsts_id = ASUS_WMI_METHODID_DSTS2; goto next; } else { device_printf(dev, "Can not detect DSTS method ID\n"); return (EINVAL); } } /* Find proper and attach to notufy GUID. */ if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_ASUS_WMI_EVENT_GUID)) sc->notify_guid = ACPI_ASUS_WMI_EVENT_GUID; else if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_EEEPC_WMI_EVENT_GUID)) sc->notify_guid = ACPI_EEEPC_WMI_EVENT_GUID; else sc->notify_guid = NULL; if (sc->notify_guid != NULL) { if (ACPI_WMI_INSTALL_EVENT_HANDLER(sc->wmi_dev, sc->notify_guid, acpi_asus_wmi_notify, dev)) sc->notify_guid = NULL; } if (sc->notify_guid == NULL) device_printf(dev, "Could not install event handler!\n"); /* Initialize. */ if (!acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_INIT, 0, 0, 0, &val) && bootverbose) device_printf(dev, "Initialization: %#x\n", val); if (!acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_SPEC, 0, 0x9, 0, &val) && bootverbose) device_printf(dev, "WMI BIOS version: %d.%d\n", val >> 16, val & 0xFF); if (!acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_SFUN, 0, 0, 0, &val) && bootverbose) device_printf(dev, "SFUN value: %#x\n", val); ACPI_SERIAL_BEGIN(asus_wmi); sc->sysctl_ctx = device_get_sysctl_ctx(dev); sc->sysctl_tree = device_get_sysctl_tree(dev); SYSCTL_ADD_INT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "handle_keys", CTLFLAG_RW, &sc->handle_keys, 0, "Handle some hardware keys inside the driver"); for (i = 0; acpi_asus_wmi_sysctls[i].name != NULL; ++i) { dev_id = acpi_asus_wmi_sysctls[i].dev_id; if (acpi_wpi_asus_get_devstate(sc, dev_id, &val)) continue; switch (dev_id) { case ASUS_WMI_DEVID_THERMAL_CTRL: case ASUS_WMI_DEVID_PROCESSOR_STATE: case ASUS_WMI_DEVID_FAN_CTRL: case ASUS_WMI_DEVID_BRIGHTNESS: if (val == 0) continue; break; case ASUS_WMI_DEVID_KBD_BACKLIGHT: sc->kbd_bkl_level = devstate_to_kbd_bkl_level(val); have_kbd_bkl = true; /* FALLTHROUGH */ default: if ((val & ASUS_WMI_DSTS_PRESENCE_BIT) == 0) continue; break; } if (acpi_asus_wmi_sysctls[i].flag_rdonly != 0) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_asus_wmi_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, i, acpi_asus_wmi_sysctl, "I", acpi_asus_wmi_sysctls[i].description); } else { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_asus_wmi_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, i, acpi_asus_wmi_sysctl, "I", acpi_asus_wmi_sysctls[i].description); } } ACPI_SERIAL_END(asus_wmi); /* Detect and flush event queue */ if (sc->dsts_id == ASUS_WMI_METHODID_DSTS2) { for (i = 0; i <= ASUS_WMI_EVENT_QUEUE_SIZE; i++) { if (acpi_asus_wmi_get_event_code(sc->wmi_dev, ASUS_WMI_EVENT_VALUE_ATK, &code) != 0) { device_printf(dev, "Can not flush event queue\n"); break; } if (code == ASUS_WMI_EVENT_QUEUE_END || code == ASUS_WMI_EVENT_MASK) { sc->event_queue = true; break; } } } #ifdef EVDEV_SUPPORT if (sc->notify_guid != NULL) { sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(dev)); evdev_set_phys(sc->evdev, device_get_nameunit(dev)); evdev_set_id(sc->evdev, BUS_HOST, 0, 0, 1); evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_KEY); for (i = 0; i < nitems(acpi_asus_wmi_evdev_map); i++) { if (acpi_asus_wmi_evdev_map[i].key != NO_KEY) evdev_support_key(sc->evdev, acpi_asus_wmi_evdev_map[i].key); } if (evdev_register(sc->evdev) != 0) { device_printf(dev, "Can not register evdev\n"); acpi_asus_wmi_detach(dev); return (ENXIO); } } #endif if (have_kbd_bkl) { sc->kbd_bkl = backlight_register("acpi_asus_wmi", dev); if (sc->kbd_bkl == NULL) { device_printf(dev, "Can not register backlight\n"); acpi_asus_wmi_detach(dev); return (ENXIO); } } return (0); } static int acpi_asus_wmi_detach(device_t dev) { struct acpi_asus_wmi_softc *sc = device_get_softc(dev); ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); if (sc->kbd_bkl != NULL) backlight_destroy(sc->kbd_bkl); if (sc->notify_guid) { ACPI_WMI_REMOVE_EVENT_HANDLER(dev, sc->notify_guid); #ifdef EVDEV_SUPPORT evdev_free(sc->evdev); #endif } return (0); } static int acpi_asus_wmi_suspend(device_t dev) { struct acpi_asus_wmi_softc *sc = device_get_softc(dev); if (sc->kbd_bkl != NULL) { ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, NULL); } return (0); } static int acpi_asus_wmi_resume(device_t dev) { struct acpi_asus_wmi_softc *sc = device_get_softc(dev); if (sc->kbd_bkl != NULL) { ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_KBD_BACKLIGHT, kbd_bkl_level_to_devstate(sc->kbd_bkl_level), NULL); } return (0); } static int acpi_asus_wmi_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_asus_wmi_softc *sc; int arg; int oldarg; int error = 0; int function; int dev_id; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_asus_wmi_softc *)oidp->oid_arg1; function = oidp->oid_arg2; dev_id = acpi_asus_wmi_sysctls[function].dev_id; ACPI_SERIAL_BEGIN(asus_wmi); arg = acpi_asus_wmi_sysctl_get(sc, dev_id); oldarg = arg; error = sysctl_handle_int(oidp, &arg, 0, req); if (!error && req->newptr != NULL) error = acpi_asus_wmi_sysctl_set(sc, dev_id, arg, oldarg); ACPI_SERIAL_END(asus_wmi); return (error); } static int acpi_asus_wmi_sysctl_get(struct acpi_asus_wmi_softc *sc, int dev_id) { UINT32 val = 0; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(asus_wmi); switch(dev_id) { case ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY: return (sc->ttp_mode); default: break; } acpi_wpi_asus_get_devstate(sc, dev_id, &val); switch(dev_id) { case ASUS_WMI_DEVID_THERMAL_CTRL: val = (val - 2731 + 5) / 10; break; case ASUS_WMI_DEVID_PROCESSOR_STATE: case ASUS_WMI_DEVID_FAN_CTRL: break; case ASUS_WMI_DEVID_BRIGHTNESS: val &= ASUS_WMI_DSTS_BRIGHTNESS_MASK; break; case ASUS_WMI_DEVID_KBD_BACKLIGHT: val &= 0x3; break; default: if (val & ASUS_WMI_DSTS_UNKNOWN_BIT) val = -1; else val = !!(val & ASUS_WMI_DSTS_STATUS_BIT); break; } return (val); } static int acpi_asus_wmi_sysctl_set(struct acpi_asus_wmi_softc *sc, int dev_id, int arg, int oldarg) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(asus_wmi); switch(dev_id) { case ASUS_WMI_DEVID_KBD_BACKLIGHT: arg = min(0x3, arg); if (arg != 0) arg |= 0x80; break; case ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY: arg = min(0x2, arg); sc->ttp_mode = arg; break; } acpi_wpi_asus_set_devstate(sc, dev_id, arg, NULL); return (0); } static __inline void acpi_asus_wmi_free_buffer(ACPI_BUFFER* buf) { if (buf && buf->Pointer) { AcpiOsFree(buf->Pointer); } } static int acpi_asus_wmi_get_event_code(device_t wmi_dev, UINT32 notify, int *code) { ACPI_BUFFER response = { ACPI_ALLOCATE_BUFFER, NULL }; ACPI_OBJECT *obj; int error = 0; if (ACPI_FAILURE(ACPI_WMI_GET_EVENT_DATA(wmi_dev, notify, &response))) return (EIO); obj = (ACPI_OBJECT*) response.Pointer; if (obj && obj->Type == ACPI_TYPE_INTEGER) *code = obj->Integer.Value & ASUS_WMI_EVENT_MASK; else error = EINVAL; acpi_asus_wmi_free_buffer(&response); return (error); } #ifdef EVDEV_SUPPORT static void acpi_asus_wmi_push_evdev_event(struct evdev_dev *evdev, UINT32 notify) { int i; uint16_t key; for (i = 0; i < nitems(acpi_asus_wmi_evdev_map); i++) { if (acpi_asus_wmi_evdev_map[i].notify == notify && acpi_asus_wmi_evdev_map[i].key != NO_KEY) { key = acpi_asus_wmi_evdev_map[i].key; evdev_push_key(evdev, key, 1); evdev_sync(evdev); evdev_push_key(evdev, key, 0); evdev_sync(evdev); break; } } } #endif static void acpi_asus_wmi_handle_event(struct acpi_asus_wmi_softc *sc, int code) { UINT32 val; if (code != 0) { acpi_UserNotify("ASUS", ACPI_ROOT_OBJECT, code); #ifdef EVDEV_SUPPORT acpi_asus_wmi_push_evdev_event(sc->evdev, code); #endif } if (code && sc->handle_keys) { /* Keyboard backlight control. */ if (code == 0xc4 || code == 0xc5) { acpi_wpi_asus_get_devstate(sc, ASUS_WMI_DEVID_KBD_BACKLIGHT, &val); val &= 0x3; if (code == 0xc4) { if (val < 0x3) val++; } else if (val > 0) val--; if (val != 0) val |= 0x80; acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_KBD_BACKLIGHT, val, NULL); sc->kbd_bkl_level = devstate_to_kbd_bkl_level(val); } /* Touchpad control. */ if (code == 0x6b) { acpi_wpi_asus_get_devstate(sc, ASUS_WMI_DEVID_TOUCHPAD, &val); val = !(val & 1); acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_TOUCHPAD, val, NULL); } /* Throttle thermal policy control. */ if (code == 0xae) { sc->ttp_mode++; if (sc->ttp_mode > 2) sc->ttp_mode = 0; acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY, sc->ttp_mode, NULL); } /* TUF laptop RGB mode control. */ if (code == 0xb3) { const uint32_t cmd = 0xb4; /* Save to BIOS */ const uint32_t r = 0xff, g = 0xff, b = 0xff; const uint32_t speed = 0xeb; /* Medium */ if (sc->tuf_rgb_mode < 2) sc->tuf_rgb_mode++; else if (sc->tuf_rgb_mode == 2) sc->tuf_rgb_mode = 10; else sc->tuf_rgb_mode = 0; acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_DEVS, ASUS_WMI_DEVID_TUF_RGB_MODE, cmd | (sc->tuf_rgb_mode << 8) | (r << 16) | (g << 24), b | (speed << 8), NULL); } } } static void acpi_asus_wmi_notify(ACPI_HANDLE h, UINT32 notify, void *context) { device_t dev = context; struct acpi_asus_wmi_softc *sc = device_get_softc(dev); int code = 0, i = 1; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); if (sc->event_queue) i += ASUS_WMI_EVENT_QUEUE_SIZE; do { if (acpi_asus_wmi_get_event_code(sc->wmi_dev, notify, &code) != 0) { device_printf(dev, "Failed to get event code\n"); return; } if (code == ASUS_WMI_EVENT_QUEUE_END || code == ASUS_WMI_EVENT_MASK) return; acpi_asus_wmi_handle_event(sc, code); if (notify != ASUS_WMI_EVENT_VALUE_ATK) return; } while (--i != 0); if (sc->event_queue) device_printf(dev, "Can not read event queue, " "last code: 0x%x\n", code); } static int acpi_asus_wmi_evaluate_method(device_t wmi_dev, int method, UINT32 arg0, UINT32 arg1, UINT32 arg2, UINT32 *retval) { UINT32 params[3] = { arg0, arg1, arg2 }; UINT32 result; ACPI_OBJECT *obj; ACPI_BUFFER in = { sizeof(params), ¶ms }; ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL }; if (ACPI_FAILURE(ACPI_WMI_EVALUATE_CALL(wmi_dev, ACPI_ASUS_WMI_MGMT_GUID, 1, method, &in, &out))) { acpi_asus_wmi_free_buffer(&out); return (-EINVAL); } obj = out.Pointer; if (obj && obj->Type == ACPI_TYPE_INTEGER) result = (UINT32) obj->Integer.Value; else result = 0; acpi_asus_wmi_free_buffer(&out); if (retval) *retval = result; return (result == ASUS_WMI_UNSUPPORTED_METHOD ? -ENODEV : 0); } static int acpi_wpi_asus_get_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 *retval) { return (acpi_asus_wmi_evaluate_method(sc->wmi_dev, sc->dsts_id, dev_id, 0, 0, retval)); } static int acpi_wpi_asus_set_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 ctrl_param, UINT32 *retval) { return (acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_DEVS, dev_id, ctrl_param, 0, retval)); } static int acpi_asus_wmi_backlight_update_status(device_t dev, struct backlight_props *props) { struct acpi_asus_wmi_softc *sc = device_get_softc(dev); acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_KBD_BACKLIGHT, kbd_bkl_level_to_devstate(props->brightness), NULL); sc->kbd_bkl_level = props->brightness; return (0); } static int acpi_asus_wmi_backlight_get_status(device_t dev, struct backlight_props *props) { struct acpi_asus_wmi_softc *sc = device_get_softc(dev); props->brightness = sc->kbd_bkl_level; props->nlevels = nitems(acpi_asus_wmi_backlight_levels); memcpy(props->levels, acpi_asus_wmi_backlight_levels, sizeof(acpi_asus_wmi_backlight_levels)); return (0); } static int acpi_asus_wmi_backlight_get_info(device_t dev, struct backlight_info *info) { info->type = BACKLIGHT_TYPE_KEYBOARD; strlcpy(info->name, "ASUS Keyboard", BACKLIGHTMAXNAMELENGTH); return (0); } diff --git a/sys/dev/acpi_support/acpi_hp.c b/sys/dev/acpi_support/acpi_hp.c index 088e46af2ce3..5523b8768d41 100644 --- a/sys/dev/acpi_support/acpi_hp.c +++ b/sys/dev/acpi_support/acpi_hp.c @@ -1,1287 +1,1287 @@ /*- * Copyright (c) 2009 Michael Gmelin * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include /* * Driver for extra ACPI-controlled features found on HP laptops * that use a WMI enabled BIOS (e.g. HP Compaq 8510p and 6510p). * Allows to control and read status of integrated hardware and read * BIOS settings through CMI. * Inspired by the hp-wmi driver, which implements a subset of these * features (hotkeys) on Linux. * * HP CMI whitepaper: * http://h20331.www2.hp.com/Hpsub/downloads/cmi_whitepaper.pdf * wmi-hp for Linux: * http://www.kernel.org * WMI and ACPI: * http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_wmi_if.h" #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("HP") #define ACPI_HP_WMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" #define ACPI_HP_WMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4" #define ACPI_HP_WMI_CMI_GUID "2D114B49-2DFB-4130-B8FE-4A3C09E75133" #define ACPI_HP_WMI_DISPLAY_COMMAND 0x1 #define ACPI_HP_WMI_HDDTEMP_COMMAND 0x2 #define ACPI_HP_WMI_ALS_COMMAND 0x3 #define ACPI_HP_WMI_DOCK_COMMAND 0x4 #define ACPI_HP_WMI_WIRELESS_COMMAND 0x5 #define ACPI_HP_WMI_BIOS_COMMAND 0x9 #define ACPI_HP_WMI_FEATURE_COMMAND 0xb #define ACPI_HP_WMI_HOTKEY_COMMAND 0xc #define ACPI_HP_WMI_FEATURE2_COMMAND 0xd #define ACPI_HP_WMI_WIRELESS2_COMMAND 0x1b #define ACPI_HP_WMI_POSTCODEERROR_COMMAND 0x2a #define ACPI_HP_METHOD_WLAN_ENABLED 1 #define ACPI_HP_METHOD_WLAN_RADIO 2 #define ACPI_HP_METHOD_WLAN_ON_AIR 3 #define ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON 4 #define ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF 5 #define ACPI_HP_METHOD_BLUETOOTH_ENABLED 6 #define ACPI_HP_METHOD_BLUETOOTH_RADIO 7 #define ACPI_HP_METHOD_BLUETOOTH_ON_AIR 8 #define ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON 9 #define ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF 10 #define ACPI_HP_METHOD_WWAN_ENABLED 11 #define ACPI_HP_METHOD_WWAN_RADIO 12 #define ACPI_HP_METHOD_WWAN_ON_AIR 13 #define ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON 14 #define ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF 15 #define ACPI_HP_METHOD_ALS 16 #define ACPI_HP_METHOD_DISPLAY 17 #define ACPI_HP_METHOD_HDDTEMP 18 #define ACPI_HP_METHOD_DOCK 19 #define ACPI_HP_METHOD_CMI_DETAIL 20 #define ACPI_HP_METHOD_VERBOSE 21 #define HP_MASK_WWAN_ON_AIR 0x1000000 #define HP_MASK_BLUETOOTH_ON_AIR 0x10000 #define HP_MASK_WLAN_ON_AIR 0x100 #define HP_MASK_WWAN_RADIO 0x8000000 #define HP_MASK_BLUETOOTH_RADIO 0x80000 #define HP_MASK_WLAN_RADIO 0x800 #define HP_MASK_WWAN_ENABLED 0x2000000 #define HP_MASK_BLUETOOTH_ENABLED 0x20000 #define HP_MASK_WLAN_ENABLED 0x200 #define ACPI_HP_EVENT_DOCK 0x01 #define ACPI_HP_EVENT_PARK_HDD 0x02 #define ACPI_HP_EVENT_SMART_ADAPTER 0x03 #define ACPI_HP_EVENT_BEZEL_BUTTON 0x04 #define ACPI_HP_EVENT_WIRELESS 0x05 #define ACPI_HP_EVENT_CPU_BATTERY_THROTTLE 0x06 #define ACPI_HP_EVENT_LOCK_SWITCH 0x07 #define ACPI_HP_EVENT_LID_SWITCH 0x08 #define ACPI_HP_EVENT_SCREEN_ROTATION 0x09 #define ACPI_HP_EVENT_COOLSENSE_SYSTEM_MOBILE 0x0A #define ACPI_HP_EVENT_COOLSENSE_SYSTEM_HOT 0x0B #define ACPI_HP_EVENT_PROXIMITY_SENSOR 0x0C #define ACPI_HP_EVENT_BACKLIT_KB_BRIGHTNESS 0x0D #define ACPI_HP_EVENT_PEAKSHIFT_PERIOD 0x0F #define ACPI_HP_EVENT_BATTERY_CHARGE_PERIOD 0x10 #define ACPI_HP_CMI_DETAIL_PATHS 0x01 #define ACPI_HP_CMI_DETAIL_ENUMS 0x02 #define ACPI_HP_CMI_DETAIL_FLAGS 0x04 #define ACPI_HP_CMI_DETAIL_SHOW_MAX_INSTANCE 0x08 #define ACPI_HP_WMI_RET_WRONG_SIGNATURE 0x02 #define ACPI_HP_WMI_RET_UNKNOWN_COMMAND 0x03 #define ACPI_HP_WMI_RET_UNKNOWN_CMDTYPE 0x04 #define ACPI_HP_WMI_RET_INVALID_PARAMETERS 0x05 struct acpi_hp_inst_seq_pair { UINT32 sequence; /* sequence number as suggested by cmi bios */ UINT8 instance; /* object instance on guid */ }; struct acpi_hp_softc { device_t dev; device_t wmi_dev; int has_notify; /* notification GUID found */ int has_cmi; /* CMI GUID found */ int has_wireless; /* Wireless command found */ int cmi_detail; /* CMI detail level (set by sysctl) */ int verbose; /* add debug output */ int wlan_enable_if_radio_on; /* set by sysctl */ int wlan_disable_if_radio_off; /* set by sysctl */ int bluetooth_enable_if_radio_on; /* set by sysctl */ int bluetooth_disable_if_radio_off; /* set by sysctl */ int wwan_enable_if_radio_on; /* set by sysctl */ int wwan_disable_if_radio_off; /* set by sysctl */ int was_wlan_on_air; /* last known WLAN on air status */ int was_bluetooth_on_air; /* last known BT on air status */ int was_wwan_on_air; /* last known WWAN on air status */ struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; struct cdev *hpcmi_dev_t; /* hpcmi device handle */ struct sbuf hpcmi_sbuf; /* /dev/hpcmi output sbuf */ pid_t hpcmi_open_pid; /* pid operating on /dev/hpcmi */ int hpcmi_bufptr; /* current pointer position in /dev/hpcmi output buffer */ int cmi_order_size; /* size of cmi_order list */ struct acpi_hp_inst_seq_pair cmi_order[128]; /* list of CMI instances ordered by BIOS suggested sequence */ }; static struct { char *name; int method; char *description; int flag_rdonly; } acpi_hp_sysctls[] = { { .name = "wlan_enabled", .method = ACPI_HP_METHOD_WLAN_ENABLED, .description = "Enable/Disable WLAN (WiFi)", }, { .name = "wlan_radio", .method = ACPI_HP_METHOD_WLAN_RADIO, .description = "WLAN radio status", .flag_rdonly = 1 }, { .name = "wlan_on_air", .method = ACPI_HP_METHOD_WLAN_ON_AIR, .description = "WLAN radio ready to use (enabled and radio)", .flag_rdonly = 1 }, { .name = "wlan_enable_if_radio_on", .method = ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON, .description = "Enable WLAN if radio is turned on", }, { .name = "wlan_disable_if_radio_off", .method = ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF, .description = "Disable WLAN if radio is turned off", }, { .name = "bt_enabled", .method = ACPI_HP_METHOD_BLUETOOTH_ENABLED, .description = "Enable/Disable Bluetooth", }, { .name = "bt_radio", .method = ACPI_HP_METHOD_BLUETOOTH_RADIO, .description = "Bluetooth radio status", .flag_rdonly = 1 }, { .name = "bt_on_air", .method = ACPI_HP_METHOD_BLUETOOTH_ON_AIR, .description = "Bluetooth radio ready to use" " (enabled and radio)", .flag_rdonly = 1 }, { .name = "bt_enable_if_radio_on", .method = ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON, .description = "Enable bluetooth if radio is turned on", }, { .name = "bt_disable_if_radio_off", .method = ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF, .description = "Disable bluetooth if radio is turned off", }, { .name = "wwan_enabled", .method = ACPI_HP_METHOD_WWAN_ENABLED, .description = "Enable/Disable WWAN (UMTS)", }, { .name = "wwan_radio", .method = ACPI_HP_METHOD_WWAN_RADIO, .description = "WWAN radio status", .flag_rdonly = 1 }, { .name = "wwan_on_air", .method = ACPI_HP_METHOD_WWAN_ON_AIR, .description = "WWAN radio ready to use (enabled and radio)", .flag_rdonly = 1 }, { .name = "wwan_enable_if_radio_on", .method = ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON, .description = "Enable WWAN if radio is turned on", }, { .name = "wwan_disable_if_radio_off", .method = ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF, .description = "Disable WWAN if radio is turned off", }, { .name = "als_enabled", .method = ACPI_HP_METHOD_ALS, .description = "Enable/Disable ALS (Ambient light sensor)", }, { .name = "display", .method = ACPI_HP_METHOD_DISPLAY, .description = "Display status", .flag_rdonly = 1 }, { .name = "hdd_temperature", .method = ACPI_HP_METHOD_HDDTEMP, .description = "HDD temperature", .flag_rdonly = 1 }, { .name = "is_docked", .method = ACPI_HP_METHOD_DOCK, .description = "Docking station status", .flag_rdonly = 1 }, { .name = "cmi_detail", .method = ACPI_HP_METHOD_CMI_DETAIL, .description = "Details shown in CMI output " "(cat /dev/hpcmi)", }, { .name = "verbose", .method = ACPI_HP_METHOD_VERBOSE, .description = "Verbosity level", }, { NULL, 0, NULL, 0 } }; ACPI_SERIAL_DECL(hp, "HP ACPI-WMI Mapping"); static void acpi_hp_identify(driver_t *driver, device_t parent); static int acpi_hp_probe(device_t dev); static int acpi_hp_attach(device_t dev); static int acpi_hp_detach(device_t dev); static void acpi_hp_evaluate_auto_on_off(struct acpi_hp_softc* sc); static int acpi_hp_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_hp_sysctl_set(struct acpi_hp_softc *sc, int method, int arg, int oldarg); static int acpi_hp_sysctl_get(struct acpi_hp_softc *sc, int method); static int acpi_hp_exec_wmi_command(device_t wmi_dev, int command, int is_write, int val, int *retval); static void acpi_hp_notify(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_hp_get_cmi_block(device_t wmi_dev, const char* guid, UINT8 instance, char* outbuf, size_t outsize, UINT32* sequence, int detail); static void acpi_hp_hex_decode(char* buffer); static d_open_t acpi_hp_hpcmi_open; static d_close_t acpi_hp_hpcmi_close; static d_read_t acpi_hp_hpcmi_read; /* handler /dev/hpcmi device */ static struct cdevsw hpcmi_cdevsw = { .d_version = D_VERSION, .d_open = acpi_hp_hpcmi_open, .d_close = acpi_hp_hpcmi_close, .d_read = acpi_hp_hpcmi_read, .d_name = "hpcmi", }; static device_method_t acpi_hp_methods[] = { DEVMETHOD(device_identify, acpi_hp_identify), DEVMETHOD(device_probe, acpi_hp_probe), DEVMETHOD(device_attach, acpi_hp_attach), DEVMETHOD(device_detach, acpi_hp_detach), DEVMETHOD_END }; static driver_t acpi_hp_driver = { "acpi_hp", acpi_hp_methods, sizeof(struct acpi_hp_softc), }; DRIVER_MODULE(acpi_hp, acpi_wmi, acpi_hp_driver, 0, 0); MODULE_DEPEND(acpi_hp, acpi_wmi, 1, 1, 1); MODULE_DEPEND(acpi_hp, acpi, 1, 1, 1); static void acpi_hp_evaluate_auto_on_off(struct acpi_hp_softc *sc) { int res; int wireless; int new_wlan_status; int new_bluetooth_status; int new_wwan_status; res = acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &wireless); if (res != 0) { device_printf(sc->wmi_dev, "Wireless command error %x\n", res); return; } new_wlan_status = -1; new_bluetooth_status = -1; new_wwan_status = -1; if (sc->verbose) device_printf(sc->wmi_dev, "Wireless status is %x\n", wireless); if (sc->wlan_disable_if_radio_off && !(wireless & HP_MASK_WLAN_RADIO) && (wireless & HP_MASK_WLAN_ENABLED)) { acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x100, NULL); new_wlan_status = 0; } else if (sc->wlan_enable_if_radio_on && (wireless & HP_MASK_WLAN_RADIO) && !(wireless & HP_MASK_WLAN_ENABLED)) { acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x101, NULL); new_wlan_status = 1; } if (sc->bluetooth_disable_if_radio_off && !(wireless & HP_MASK_BLUETOOTH_RADIO) && (wireless & HP_MASK_BLUETOOTH_ENABLED)) { acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x200, NULL); new_bluetooth_status = 0; } else if (sc->bluetooth_enable_if_radio_on && (wireless & HP_MASK_BLUETOOTH_RADIO) && !(wireless & HP_MASK_BLUETOOTH_ENABLED)) { acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x202, NULL); new_bluetooth_status = 1; } if (sc->wwan_disable_if_radio_off && !(wireless & HP_MASK_WWAN_RADIO) && (wireless & HP_MASK_WWAN_ENABLED)) { acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x400, NULL); new_wwan_status = 0; } else if (sc->wwan_enable_if_radio_on && (wireless & HP_MASK_WWAN_RADIO) && !(wireless & HP_MASK_WWAN_ENABLED)) { acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x404, NULL); new_wwan_status = 1; } if (new_wlan_status == -1) { new_wlan_status = (wireless & HP_MASK_WLAN_ON_AIR); if ((new_wlan_status?1:0) != sc->was_wlan_on_air) { sc->was_wlan_on_air = sc->was_wlan_on_air?0:1; if (sc->verbose) device_printf(sc->wmi_dev, "WLAN on air changed to %i " "(new_wlan_status is %i)\n", sc->was_wlan_on_air, new_wlan_status); acpi_UserNotify("HP", ACPI_ROOT_OBJECT, 0xc0+sc->was_wlan_on_air); } } if (new_bluetooth_status == -1) { new_bluetooth_status = (wireless & HP_MASK_BLUETOOTH_ON_AIR); if ((new_bluetooth_status?1:0) != sc->was_bluetooth_on_air) { sc->was_bluetooth_on_air = sc->was_bluetooth_on_air? 0:1; if (sc->verbose) device_printf(sc->wmi_dev, "BLUETOOTH on air changed" " to %i (new_bluetooth_status is %i)\n", sc->was_bluetooth_on_air, new_bluetooth_status); acpi_UserNotify("HP", ACPI_ROOT_OBJECT, 0xd0+sc->was_bluetooth_on_air); } } if (new_wwan_status == -1) { new_wwan_status = (wireless & HP_MASK_WWAN_ON_AIR); if ((new_wwan_status?1:0) != sc->was_wwan_on_air) { sc->was_wwan_on_air = sc->was_wwan_on_air?0:1; if (sc->verbose) device_printf(sc->wmi_dev, "WWAN on air changed to %i" " (new_wwan_status is %i)\n", sc->was_wwan_on_air, new_wwan_status); acpi_UserNotify("HP", ACPI_ROOT_OBJECT, 0xe0+sc->was_wwan_on_air); } } } static void acpi_hp_identify(driver_t *driver, device_t parent) { /* Don't do anything if driver is disabled. */ if (acpi_disabled("hp")) return; /* Add only a single device instance. */ - if (device_find_child(parent, "acpi_hp", -1) != NULL) + if (device_find_child(parent, "acpi_hp", DEVICE_UNIT_ANY) != NULL) return; /* Check BIOS GUID to see whether system is compatible. */ if (!ACPI_WMI_PROVIDES_GUID_STRING(parent, ACPI_HP_WMI_BIOS_GUID)) return; - if (BUS_ADD_CHILD(parent, 0, "acpi_hp", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "acpi_hp", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add acpi_hp child failed\n"); } static int acpi_hp_probe(device_t dev) { device_set_desc(dev, "HP ACPI-WMI Mapping"); return (0); } static int acpi_hp_attach(device_t dev) { struct acpi_hp_softc *sc; int arg; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); sc = device_get_softc(dev); sc->dev = dev; sc->has_notify = 0; sc->has_cmi = 0; sc->bluetooth_enable_if_radio_on = 0; sc->bluetooth_disable_if_radio_off = 0; sc->wlan_enable_if_radio_on = 0; sc->wlan_disable_if_radio_off = 0; sc->wlan_enable_if_radio_on = 0; sc->wlan_disable_if_radio_off = 0; sc->was_wlan_on_air = 0; sc->was_bluetooth_on_air = 0; sc->was_wwan_on_air = 0; sc->cmi_detail = 0; sc->cmi_order_size = -1; sc->verbose = bootverbose; memset(sc->cmi_order, 0, sizeof(sc->cmi_order)); sc->wmi_dev = device_get_parent(dev); if (!ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_HP_WMI_BIOS_GUID)) { device_printf(dev, "WMI device does not provide the HP BIOS GUID\n"); return (EINVAL); } if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_HP_WMI_EVENT_GUID)) { device_printf(dev, "HP event GUID detected, installing event handler\n"); if (ACPI_WMI_INSTALL_EVENT_HANDLER(sc->wmi_dev, ACPI_HP_WMI_EVENT_GUID, acpi_hp_notify, dev)) { device_printf(dev, "Could not install notification handler!\n"); } else { sc->has_notify = 1; } } if ((sc->has_cmi = ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_HP_WMI_CMI_GUID) )) { device_printf(dev, "HP CMI GUID detected\n"); } if (sc->has_cmi) { sc->hpcmi_dev_t = make_dev(&hpcmi_cdevsw, 0, UID_ROOT, GID_WHEEL, 0644, "hpcmi"); sc->hpcmi_dev_t->si_drv1 = sc; sc->hpcmi_open_pid = 0; sc->hpcmi_bufptr = -1; } if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, NULL) == 0) sc->has_wireless = 1; ACPI_SERIAL_BEGIN(hp); sc->sysctl_ctx = device_get_sysctl_ctx(dev); sc->sysctl_tree = device_get_sysctl_tree(dev); for (int i = 0; acpi_hp_sysctls[i].name != NULL; ++i) { arg = 0; if (((!sc->has_notify || !sc->has_wireless) && (acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON || acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF || acpi_hp_sysctls[i].method == ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON || acpi_hp_sysctls[i].method == ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF || acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON || acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF)) || (arg = acpi_hp_sysctl_get(sc, acpi_hp_sysctls[i].method)) < 0) { continue; } if (acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WLAN_ON_AIR) { sc->was_wlan_on_air = arg; } else if (acpi_hp_sysctls[i].method == ACPI_HP_METHOD_BLUETOOTH_ON_AIR) { sc->was_bluetooth_on_air = arg; } else if (acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WWAN_ON_AIR) { sc->was_wwan_on_air = arg; } if (acpi_hp_sysctls[i].flag_rdonly != 0) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_hp_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, i, acpi_hp_sysctl, "I", acpi_hp_sysctls[i].description); } else { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_hp_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, i, acpi_hp_sysctl, "I", acpi_hp_sysctls[i].description); } } ACPI_SERIAL_END(hp); return (0); } static int acpi_hp_detach(device_t dev) { struct acpi_hp_softc *sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); sc = device_get_softc(dev); if (sc->has_cmi && sc->hpcmi_open_pid != 0) return (EBUSY); if (sc->has_notify) { ACPI_WMI_REMOVE_EVENT_HANDLER(sc->wmi_dev, ACPI_HP_WMI_EVENT_GUID); } if (sc->has_cmi) { if (sc->hpcmi_bufptr != -1) { sbuf_delete(&sc->hpcmi_sbuf); sc->hpcmi_bufptr = -1; } sc->hpcmi_open_pid = 0; destroy_dev(sc->hpcmi_dev_t); } return (0); } static int acpi_hp_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_hp_softc *sc; int arg; int oldarg; int error = 0; int function; int method; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_hp_softc *)oidp->oid_arg1; function = oidp->oid_arg2; method = acpi_hp_sysctls[function].method; ACPI_SERIAL_BEGIN(hp); arg = acpi_hp_sysctl_get(sc, method); oldarg = arg; error = sysctl_handle_int(oidp, &arg, 0, req); if (!error && req->newptr != NULL) { error = acpi_hp_sysctl_set(sc, method, arg, oldarg); } ACPI_SERIAL_END(hp); return (error); } static int acpi_hp_sysctl_get(struct acpi_hp_softc *sc, int method) { int val = 0; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(hp); switch (method) { case ACPI_HP_METHOD_WLAN_ENABLED: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_WLAN_ENABLED) != 0); break; case ACPI_HP_METHOD_WLAN_RADIO: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_WLAN_RADIO) != 0); break; case ACPI_HP_METHOD_WLAN_ON_AIR: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_WLAN_ON_AIR) != 0); break; case ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON: val = sc->wlan_enable_if_radio_on; break; case ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF: val = sc->wlan_disable_if_radio_off; break; case ACPI_HP_METHOD_BLUETOOTH_ENABLED: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_BLUETOOTH_ENABLED) != 0); break; case ACPI_HP_METHOD_BLUETOOTH_RADIO: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_BLUETOOTH_RADIO) != 0); break; case ACPI_HP_METHOD_BLUETOOTH_ON_AIR: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_BLUETOOTH_ON_AIR) != 0); break; case ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON: val = sc->bluetooth_enable_if_radio_on; break; case ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF: val = sc->bluetooth_disable_if_radio_off; break; case ACPI_HP_METHOD_WWAN_ENABLED: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_WWAN_ENABLED) != 0); break; case ACPI_HP_METHOD_WWAN_RADIO: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_WWAN_RADIO) != 0); break; case ACPI_HP_METHOD_WWAN_ON_AIR: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0, &val)) return (-EINVAL); val = ((val & HP_MASK_WWAN_ON_AIR) != 0); break; case ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON: val = sc->wwan_enable_if_radio_on; break; case ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF: val = sc->wwan_disable_if_radio_off; break; case ACPI_HP_METHOD_ALS: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_ALS_COMMAND, 0, 0, &val)) return (-EINVAL); break; case ACPI_HP_METHOD_DISPLAY: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_DISPLAY_COMMAND, 0, 0, &val)) return (-EINVAL); break; case ACPI_HP_METHOD_HDDTEMP: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_HDDTEMP_COMMAND, 0, 0, &val)) return (-EINVAL); break; case ACPI_HP_METHOD_DOCK: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_DOCK_COMMAND, 0, 0, &val)) return (-EINVAL); break; case ACPI_HP_METHOD_CMI_DETAIL: val = sc->cmi_detail; break; case ACPI_HP_METHOD_VERBOSE: val = sc->verbose; break; } return (val); } static int acpi_hp_sysctl_set(struct acpi_hp_softc *sc, int method, int arg, int oldarg) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(hp); if (method != ACPI_HP_METHOD_CMI_DETAIL && method != ACPI_HP_METHOD_VERBOSE) arg = arg?1:0; if (arg != oldarg) { switch (method) { case ACPI_HP_METHOD_WLAN_ENABLED: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, arg?0x101:0x100, NULL)) return (-EINVAL); break; case ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON: sc->wlan_enable_if_radio_on = arg; acpi_hp_evaluate_auto_on_off(sc); break; case ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF: sc->wlan_disable_if_radio_off = arg; acpi_hp_evaluate_auto_on_off(sc); break; case ACPI_HP_METHOD_BLUETOOTH_ENABLED: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, arg?0x202:0x200, NULL)) return (-EINVAL); break; case ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON: sc->bluetooth_enable_if_radio_on = arg; acpi_hp_evaluate_auto_on_off(sc); break; case ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF: sc->bluetooth_disable_if_radio_off = arg?1:0; acpi_hp_evaluate_auto_on_off(sc); break; case ACPI_HP_METHOD_WWAN_ENABLED: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_WIRELESS_COMMAND, 1, arg?0x404:0x400, NULL)) return (-EINVAL); break; case ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON: sc->wwan_enable_if_radio_on = arg?1:0; acpi_hp_evaluate_auto_on_off(sc); break; case ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF: sc->wwan_disable_if_radio_off = arg?1:0; acpi_hp_evaluate_auto_on_off(sc); break; case ACPI_HP_METHOD_ALS: if (acpi_hp_exec_wmi_command(sc->wmi_dev, ACPI_HP_WMI_ALS_COMMAND, 1, arg?1:0, NULL)) return (-EINVAL); break; case ACPI_HP_METHOD_CMI_DETAIL: sc->cmi_detail = arg; if ((arg & ACPI_HP_CMI_DETAIL_SHOW_MAX_INSTANCE) != (oldarg & ACPI_HP_CMI_DETAIL_SHOW_MAX_INSTANCE)) { sc->cmi_order_size = -1; } break; case ACPI_HP_METHOD_VERBOSE: sc->verbose = arg; break; } } return (0); } static __inline void acpi_hp_free_buffer(ACPI_BUFFER* buf) { if (buf && buf->Pointer) { AcpiOsFree(buf->Pointer); } } static void acpi_hp_notify(ACPI_HANDLE h, UINT32 notify, void *context) { device_t dev = context; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); struct acpi_hp_softc *sc = device_get_softc(dev); ACPI_BUFFER response = { ACPI_ALLOCATE_BUFFER, NULL }; ACPI_OBJECT *obj; ACPI_WMI_GET_EVENT_DATA(sc->wmi_dev, notify, &response); obj = (ACPI_OBJECT*) response.Pointer; if (obj && obj->Type == ACPI_TYPE_BUFFER && obj->Buffer.Length == 8) { switch (*((UINT8 *) obj->Buffer.Pointer)) { case ACPI_HP_EVENT_WIRELESS: acpi_hp_evaluate_auto_on_off(sc); break; default: if (sc->verbose) { device_printf(sc->dev, "Event %02x\n", *((UINT8 *) obj->Buffer.Pointer)); } break; } } acpi_hp_free_buffer(&response); } static int acpi_hp_exec_wmi_command(device_t wmi_dev, int command, int is_write, int val, int *retval) { UINT32 params[4+32] = { 0x55434553, is_write ? 2 : 1, command, 4, val}; UINT32* result; ACPI_OBJECT *obj; ACPI_BUFFER in = { sizeof(params), ¶ms }; ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL }; int res; if (ACPI_FAILURE(ACPI_WMI_EVALUATE_CALL(wmi_dev, ACPI_HP_WMI_BIOS_GUID, 0, 0x3, &in, &out))) { acpi_hp_free_buffer(&out); return (-EINVAL); } obj = out.Pointer; if (!obj || obj->Type != ACPI_TYPE_BUFFER) { acpi_hp_free_buffer(&out); return (-EINVAL); } result = (UINT32*) obj->Buffer.Pointer; res = result[1]; if (res == 0 && retval != NULL) *retval = result[2]; acpi_hp_free_buffer(&out); return (res); } static __inline char* acpi_hp_get_string_from_object(ACPI_OBJECT* obj, char* dst, size_t size) { int length; dst[0] = 0; if (obj->Type == ACPI_TYPE_STRING) { length = obj->String.Length+1; if (length > size) { length = size - 1; } strlcpy(dst, obj->String.Pointer, length); acpi_hp_hex_decode(dst); } return (dst); } /* * Read BIOS Setting block in instance "instance". * The block returned is ACPI_TYPE_PACKAGE which should contain the following * elements: * Index Meaning * 0 Setting Name [string] * 1 Value (comma separated, asterisk marks the current value) [string] * 2 Path within the bios hierarchy [string] * 3 IsReadOnly [int] * 4 DisplayInUI [int] * 5 RequiresPhysicalPresence [int] * 6 Sequence for ordering within the bios settings (absolute) [int] * 7 Length of prerequisites array [int] * 8..8+[7] PrerequisiteN [string] * 9+[7] Current value (in case of enum) [string] / Array length [int] * 10+[7] Enum length [int] / Array values * 11+[7]ff Enum value at index x [string] */ static int acpi_hp_get_cmi_block(device_t wmi_dev, const char* guid, UINT8 instance, char* outbuf, size_t outsize, UINT32* sequence, int detail) { ACPI_OBJECT *obj; ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL }; int i; int outlen; int has_enums = 0; int valuebase = 0; char string_buffer[255]; int enumbase; outlen = 0; outbuf[0] = 0; if (ACPI_FAILURE(ACPI_WMI_GET_BLOCK(wmi_dev, guid, instance, &out))) { acpi_hp_free_buffer(&out); return (-EINVAL); } obj = out.Pointer; if (!obj || obj->Type != ACPI_TYPE_PACKAGE) { acpi_hp_free_buffer(&out); return (-EINVAL); } /* Check if first 6 bytes matches our expectations. */ if (obj->Package.Count < 8 || obj->Package.Elements[0].Type != ACPI_TYPE_STRING || obj->Package.Elements[1].Type != ACPI_TYPE_STRING || obj->Package.Elements[2].Type != ACPI_TYPE_STRING || obj->Package.Elements[3].Type != ACPI_TYPE_INTEGER || obj->Package.Elements[4].Type != ACPI_TYPE_INTEGER || obj->Package.Elements[5].Type != ACPI_TYPE_INTEGER || obj->Package.Elements[6].Type != ACPI_TYPE_INTEGER || obj->Package.Elements[7].Type != ACPI_TYPE_INTEGER) { acpi_hp_free_buffer(&out); return (-EINVAL); } /* Skip prerequisites and optionally array. */ valuebase = 8 + obj->Package.Elements[7].Integer.Value; if (obj->Package.Count <= valuebase) { acpi_hp_free_buffer(&out); return (-EINVAL); } if (obj->Package.Elements[valuebase].Type == ACPI_TYPE_INTEGER) valuebase += 1 + obj->Package.Elements[valuebase].Integer.Value; /* Check if we have value and enum. */ if (obj->Package.Count <= valuebase + 1 || obj->Package.Elements[valuebase].Type != ACPI_TYPE_STRING || obj->Package.Elements[valuebase+1].Type != ACPI_TYPE_INTEGER) { acpi_hp_free_buffer(&out); return (-EINVAL); } enumbase = valuebase + 1; if (obj->Package.Count <= valuebase + obj->Package.Elements[enumbase].Integer.Value) { acpi_hp_free_buffer(&out); return (-EINVAL); } if (detail & ACPI_HP_CMI_DETAIL_PATHS) { strlcat(outbuf, acpi_hp_get_string_from_object( &obj->Package.Elements[2], string_buffer, sizeof(string_buffer)), outsize); outlen += 48; while (strlen(outbuf) < outlen) strlcat(outbuf, " ", outsize); } strlcat(outbuf, acpi_hp_get_string_from_object( &obj->Package.Elements[0], string_buffer, sizeof(string_buffer)), outsize); outlen += 43; while (strlen(outbuf) < outlen) strlcat(outbuf, " ", outsize); strlcat(outbuf, acpi_hp_get_string_from_object( &obj->Package.Elements[valuebase], string_buffer, sizeof(string_buffer)), outsize); outlen += 21; while (strlen(outbuf) < outlen) strlcat(outbuf, " ", outsize); for (i = 0; i < strlen(outbuf); ++i) if (outbuf[i] == '\\') outbuf[i] = '/'; if (detail & ACPI_HP_CMI_DETAIL_ENUMS) { for (i = enumbase + 1; i < enumbase + 1 + obj->Package.Elements[enumbase].Integer.Value; ++i) { acpi_hp_get_string_from_object( &obj->Package.Elements[i], string_buffer, sizeof(string_buffer)); if (strlen(string_buffer) > 1 || (strlen(string_buffer) == 1 && string_buffer[0] != ' ')) { if (has_enums) strlcat(outbuf, "/", outsize); else strlcat(outbuf, " (", outsize); strlcat(outbuf, string_buffer, outsize); has_enums = 1; } } } if (has_enums) strlcat(outbuf, ")", outsize); if (detail & ACPI_HP_CMI_DETAIL_FLAGS) { strlcat(outbuf, obj->Package.Elements[3].Integer.Value ? " [ReadOnly]" : "", outsize); strlcat(outbuf, obj->Package.Elements[4].Integer.Value ? "" : " [NOUI]", outsize); strlcat(outbuf, obj->Package.Elements[5].Integer.Value ? " [RPP]" : "", outsize); } *sequence = (UINT32) obj->Package.Elements[6].Integer.Value; acpi_hp_free_buffer(&out); return (0); } /* * Convert given two digit hex string (hexin) to an UINT8 referenced * by byteout. * Return != 0 if the was a problem (invalid input) */ static __inline int acpi_hp_hex_to_int(const UINT8 *hexin, UINT8 *byteout) { unsigned int hi; unsigned int lo; hi = hexin[0]; lo = hexin[1]; if ('0' <= hi && hi <= '9') hi -= '0'; else if ('A' <= hi && hi <= 'F') hi -= ('A' - 10); else if ('a' <= hi && hi <= 'f') hi -= ('a' - 10); else return (1); if ('0' <= lo && lo <= '9') lo -= '0'; else if ('A' <= lo && lo <= 'F') lo -= ('A' - 10); else if ('a' <= lo && lo <= 'f') lo -= ('a' - 10); else return (1); *byteout = (hi << 4) + lo; return (0); } static void acpi_hp_hex_decode(char* buffer) { int i; int length = strlen(buffer); UINT8 *uin; UINT8 uout; if (rounddown((int)length, 2) == length || length < 10) return; for (i = 0; i= '0' && buffer[i] <= '9') || (buffer[i] >= 'A' && buffer[i] <= 'F'))) return; } for (i = 0; isi_drv1 == NULL) return (EBADF); sc = dev->si_drv1; ACPI_SERIAL_BEGIN(hp); if (sc->hpcmi_open_pid != 0) { ret = EBUSY; } else { if (sbuf_new(&sc->hpcmi_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { ret = ENXIO; } else { sc->hpcmi_open_pid = td->td_proc->p_pid; sc->hpcmi_bufptr = 0; ret = 0; } } ACPI_SERIAL_END(hp); return (ret); } /* * close hpcmi device */ static int acpi_hp_hpcmi_close(struct cdev* dev, int flags, int mode, struct thread *td) { struct acpi_hp_softc *sc; int ret; if (dev == NULL || dev->si_drv1 == NULL) return (EBADF); sc = dev->si_drv1; ACPI_SERIAL_BEGIN(hp); if (sc->hpcmi_open_pid == 0) { ret = EBADF; } else { if (sc->hpcmi_bufptr != -1) { sbuf_delete(&sc->hpcmi_sbuf); sc->hpcmi_bufptr = -1; } sc->hpcmi_open_pid = 0; ret = 0; } ACPI_SERIAL_END(hp); return (ret); } /* * Read from hpcmi bios information */ static int acpi_hp_hpcmi_read(struct cdev *dev, struct uio *buf, int flag) { struct acpi_hp_softc *sc; int pos, i, l, ret; UINT8 instance; UINT8 maxInstance; UINT32 sequence; char line[1025]; if (dev == NULL || dev->si_drv1 == NULL) return (EBADF); sc = dev->si_drv1; ACPI_SERIAL_BEGIN(hp); if (sc->hpcmi_open_pid != buf->uio_td->td_proc->p_pid || sc->hpcmi_bufptr == -1) { ret = EBADF; } else { if (!sbuf_done(&sc->hpcmi_sbuf)) { if (sc->cmi_order_size < 0) { maxInstance = sc->has_cmi; if (!(sc->cmi_detail & ACPI_HP_CMI_DETAIL_SHOW_MAX_INSTANCE) && maxInstance > 0) { maxInstance--; } sc->cmi_order_size = 0; for (instance = 0; instance < maxInstance; ++instance) { if (acpi_hp_get_cmi_block(sc->wmi_dev, ACPI_HP_WMI_CMI_GUID, instance, line, sizeof(line), &sequence, sc->cmi_detail)) { instance = maxInstance; } else { pos = sc->cmi_order_size; for (i=0; icmi_order_size && i<127; ++i) { if (sc->cmi_order[i].sequence > sequence) { pos = i; break; } } for (i=sc->cmi_order_size; i>pos; --i) { sc->cmi_order[i].sequence = sc->cmi_order[i-1].sequence; sc->cmi_order[i].instance = sc->cmi_order[i-1].instance; } sc->cmi_order[pos].sequence = sequence; sc->cmi_order[pos].instance = instance; sc->cmi_order_size++; } } } for (i=0; icmi_order_size; ++i) { if (!acpi_hp_get_cmi_block(sc->wmi_dev, ACPI_HP_WMI_CMI_GUID, sc->cmi_order[i].instance, line, sizeof(line), &sequence, sc->cmi_detail)) { sbuf_printf(&sc->hpcmi_sbuf, "%s\n", line); } } sbuf_finish(&sc->hpcmi_sbuf); } if (sbuf_len(&sc->hpcmi_sbuf) <= 0) { sbuf_delete(&sc->hpcmi_sbuf); sc->hpcmi_bufptr = -1; sc->hpcmi_open_pid = 0; ret = ENOMEM; } else { l = min(buf->uio_resid, sbuf_len(&sc->hpcmi_sbuf) - sc->hpcmi_bufptr); ret = (l > 0)?uiomove(sbuf_data(&sc->hpcmi_sbuf) + sc->hpcmi_bufptr, l, buf) : 0; sc->hpcmi_bufptr += l; } } ACPI_SERIAL_END(hp); return (ret); } diff --git a/sys/dev/acpi_support/acpi_sbl_wmi.c b/sys/dev/acpi_support/acpi_sbl_wmi.c index 7f8bbeb88317..8abee8c94e26 100644 --- a/sys/dev/acpi_support/acpi_sbl_wmi.c +++ b/sys/dev/acpi_support/acpi_sbl_wmi.c @@ -1,193 +1,193 @@ /*- * Copyright (c) 2024 Rubicon Communications, LLC (Netgate) * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_wmi_if.h" #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("SBL-FW-UPDATE-WMI") ACPI_SERIAL_DECL(sbl_wmi, "SBL WMI device"); #define ACPI_SBL_FW_UPDATE_WMI_GUID "44FADEB1-B204-40F2-8581-394BBDC1B651" struct acpi_sbl_wmi_softc { device_t dev; device_t wmi_dev; }; static void acpi_sbl_wmi_identify(driver_t *driver, device_t parent) { /* Don't do anything if driver is disabled. */ if (acpi_disabled("sbl_wmi")) return; /* Add only a single device instance. */ - if (device_find_child(parent, "acpi_sbl_wmi", -1) != NULL) + if (device_find_child(parent, "acpi_sbl_wmi", DEVICE_UNIT_ANY) != NULL) return; /* Check management GUID to see whether system is compatible. */ if (!ACPI_WMI_PROVIDES_GUID_STRING(parent, ACPI_SBL_FW_UPDATE_WMI_GUID)) return; - if (BUS_ADD_CHILD(parent, 0, "acpi_sbl_wmi", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "acpi_sbl_wmi", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add acpi_sbl_wmi child failed\n"); } static int acpi_sbl_wmi_probe(device_t dev) { if (!ACPI_WMI_PROVIDES_GUID_STRING(device_get_parent(dev), ACPI_SBL_FW_UPDATE_WMI_GUID)) return (EINVAL); device_set_desc(dev, "SBL Firmware Update WMI device"); return (0); } static int acpi_sbl_wmi_sysctl_get(struct acpi_sbl_wmi_softc *sc, int *val) { ACPI_OBJECT *obj; ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL }; int error = 0; if (ACPI_FAILURE(ACPI_WMI_GET_BLOCK(sc->wmi_dev, ACPI_SBL_FW_UPDATE_WMI_GUID, 0, &out))) { error = EINVAL; goto out; } obj = out.Pointer; if (obj->Type != ACPI_TYPE_INTEGER) { error = EINVAL; goto out; } *val = obj->Integer.Value; out: if (out.Pointer) AcpiOsFree(out.Pointer); return (error); } static int acpi_sbl_wmi_sysctl_set(struct acpi_sbl_wmi_softc *sc, int in) { ACPI_BUFFER input = { ACPI_ALLOCATE_BUFFER, NULL }; uint32_t val; val = in; input.Length = sizeof(val); input.Pointer = &val; if (ACPI_FAILURE(ACPI_WMI_SET_BLOCK(sc->wmi_dev, ACPI_SBL_FW_UPDATE_WMI_GUID, 0, &input))) return (ENODEV); return (0); } static int acpi_sbl_wmi_fw_upgrade_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_sbl_wmi_softc *sc; int arg; int error = 0; ACPI_SERIAL_BEGIN(sbl_wmi); sc = (struct acpi_sbl_wmi_softc *)oidp->oid_arg1; error = acpi_sbl_wmi_sysctl_get(sc, &arg); if (error != 0) goto out; error = sysctl_handle_int(oidp, &arg, 0, req); if (! error && req->newptr != NULL) error = acpi_sbl_wmi_sysctl_set(sc, arg); out: ACPI_SERIAL_END(sbl_wmi); return (error); } static int acpi_sbl_wmi_attach(device_t dev) { struct acpi_sbl_wmi_softc *sc; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; sc = device_get_softc(dev); sc->dev = dev; sc->wmi_dev = device_get_parent(dev); sysctl_ctx = device_get_sysctl_ctx(dev); sysctl_tree = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "firmware_update_request", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, acpi_sbl_wmi_fw_upgrade_sysctl, "I", "Signal SBL that a firmware update is available"); return (0); } static device_method_t acpi_sbl_wmi_methods[] = { DEVMETHOD(device_identify, acpi_sbl_wmi_identify), DEVMETHOD(device_probe, acpi_sbl_wmi_probe), DEVMETHOD(device_attach, acpi_sbl_wmi_attach), DEVMETHOD_END }; static driver_t acpi_sbl_wmi_driver = { "acpi_sbl_wmi", acpi_sbl_wmi_methods, sizeof(struct acpi_sbl_wmi_softc), }; DRIVER_MODULE(acpi_sbl_wmi, acpi_wmi, acpi_sbl_wmi_driver, 0, 0); MODULE_DEPEND(acpi_sbl_wmi, acpi_wmi, 1, 1, 1); MODULE_DEPEND(acpi_sbl_wmi, acpi, 1, 1, 1); diff --git a/sys/dev/acpica/acpi_pcib_acpi.c b/sys/dev/acpica/acpi_pcib_acpi.c index e8ab481d776f..3913ec612f79 100644 --- a/sys/dev/acpica/acpi_pcib_acpi.c +++ b/sys/dev/acpica/acpi_pcib_acpi.c @@ -1,717 +1,717 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_acpi.h" #include "opt_pci.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" #include /* Hooks for the ACPI CA debugging infrastructure. */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("PCI_ACPI") struct acpi_hpcib_softc { device_t ap_dev; ACPI_HANDLE ap_handle; bus_dma_tag_t ap_dma_tag; int ap_flags; uint32_t ap_osc_ctl; int ap_segment; /* PCI domain */ int ap_bus; /* bios-assigned bus number */ int ap_addr; /* device/func of PCI-Host bridge */ ACPI_BUFFER ap_prt; /* interrupt routing table */ struct pcib_host_resources ap_host_res; }; static int acpi_pcib_acpi_probe(device_t bus); static int acpi_pcib_acpi_attach(device_t bus); static int acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); static int acpi_pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value); static uint32_t acpi_pcib_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int bytes); static void acpi_pcib_write_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, uint32_t data, int bytes); static int acpi_pcib_acpi_route_interrupt(device_t pcib, device_t dev, int pin); static int acpi_pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs); static int acpi_pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data); static int acpi_pcib_alloc_msix(device_t pcib, device_t dev, int *irq); static struct resource *acpi_pcib_acpi_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); static int acpi_pcib_acpi_adjust_resource(device_t dev, device_t child, struct resource *r, rman_res_t start, rman_res_t end); static int acpi_pcib_acpi_release_resource(device_t dev, device_t child, struct resource *r); static int acpi_pcib_acpi_activate_resource(device_t dev, device_t child, struct resource *r); static int acpi_pcib_acpi_deactivate_resource(device_t dev, device_t child, struct resource *r); static int acpi_pcib_request_feature(device_t pcib, device_t dev, enum pci_feature feature); static bus_dma_tag_t acpi_pcib_get_dma_tag(device_t bus, device_t child); static device_method_t acpi_pcib_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_pcib_acpi_probe), DEVMETHOD(device_attach, acpi_pcib_acpi_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, acpi_pcib_read_ivar), DEVMETHOD(bus_write_ivar, acpi_pcib_write_ivar), DEVMETHOD(bus_alloc_resource, acpi_pcib_acpi_alloc_resource), DEVMETHOD(bus_adjust_resource, acpi_pcib_acpi_adjust_resource), DEVMETHOD(bus_release_resource, acpi_pcib_acpi_release_resource), DEVMETHOD(bus_activate_resource, acpi_pcib_acpi_activate_resource), DEVMETHOD(bus_deactivate_resource, acpi_pcib_acpi_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_cpus, acpi_pcib_get_cpus), DEVMETHOD(bus_get_dma_tag, acpi_pcib_get_dma_tag), /* pcib interface */ DEVMETHOD(pcib_maxslots, pcib_maxslots), DEVMETHOD(pcib_read_config, acpi_pcib_read_config), DEVMETHOD(pcib_write_config, acpi_pcib_write_config), DEVMETHOD(pcib_route_interrupt, acpi_pcib_acpi_route_interrupt), DEVMETHOD(pcib_alloc_msi, acpi_pcib_alloc_msi), DEVMETHOD(pcib_release_msi, pcib_release_msi), DEVMETHOD(pcib_alloc_msix, acpi_pcib_alloc_msix), DEVMETHOD(pcib_release_msix, pcib_release_msix), DEVMETHOD(pcib_map_msi, acpi_pcib_map_msi), DEVMETHOD(pcib_power_for_sleep, acpi_pcib_power_for_sleep), DEVMETHOD(pcib_request_feature, acpi_pcib_request_feature), DEVMETHOD_END }; DEFINE_CLASS_0(pcib, acpi_pcib_acpi_driver, acpi_pcib_acpi_methods, sizeof(struct acpi_hpcib_softc)); DRIVER_MODULE(acpi_pcib, acpi, acpi_pcib_acpi_driver, 0, 0); MODULE_DEPEND(acpi_pcib, acpi, 1, 1, 1); static int acpi_pcib_acpi_probe(device_t dev) { ACPI_DEVICE_INFO *devinfo; ACPI_HANDLE h; int root; if (acpi_disabled("pcib") || (h = acpi_get_handle(dev)) == NULL || ACPI_FAILURE(AcpiGetObjectInfo(h, &devinfo))) return (ENXIO); root = (devinfo->Flags & ACPI_PCI_ROOT_BRIDGE) != 0; AcpiOsFree(devinfo); if (!root || pci_cfgregopen() == 0) return (ENXIO); device_set_desc(dev, "ACPI Host-PCI bridge"); return (0); } static ACPI_STATUS acpi_pcib_producer_handler(ACPI_RESOURCE *res, void *context) { struct acpi_hpcib_softc *sc; UINT64 length, min, max; u_int flags; int error, type; sc = context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: case ACPI_RESOURCE_TYPE_END_DEPENDENT: panic("host bridge has depenedent resources"); case ACPI_RESOURCE_TYPE_ADDRESS16: case ACPI_RESOURCE_TYPE_ADDRESS32: case ACPI_RESOURCE_TYPE_ADDRESS64: case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: if (res->Data.Address.ProducerConsumer != ACPI_PRODUCER) break; switch (res->Type) { case ACPI_RESOURCE_TYPE_ADDRESS16: min = res->Data.Address16.Address.Minimum; max = res->Data.Address16.Address.Maximum; length = res->Data.Address16.Address.AddressLength; break; case ACPI_RESOURCE_TYPE_ADDRESS32: min = res->Data.Address32.Address.Minimum; max = res->Data.Address32.Address.Maximum; length = res->Data.Address32.Address.AddressLength; break; case ACPI_RESOURCE_TYPE_ADDRESS64: min = res->Data.Address64.Address.Minimum; max = res->Data.Address64.Address.Maximum; length = res->Data.Address64.Address.AddressLength; break; default: KASSERT(res->Type == ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64, ("should never happen")); min = res->Data.ExtAddress64.Address.Minimum; max = res->Data.ExtAddress64.Address.Maximum; length = res->Data.ExtAddress64.Address.AddressLength; break; } if (length == 0) break; if (min + length - 1 != max && (res->Data.Address.MinAddressFixed != ACPI_ADDRESS_FIXED || res->Data.Address.MaxAddressFixed != ACPI_ADDRESS_FIXED)) break; flags = 0; switch (res->Data.Address.ResourceType) { case ACPI_MEMORY_RANGE: type = SYS_RES_MEMORY; if (res->Type != ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64) { if (res->Data.Address.Info.Mem.Caching == ACPI_PREFETCHABLE_MEMORY) flags |= RF_PREFETCHABLE; } else { /* * XXX: Parse prefetch flag out of * TypeSpecific. */ } break; case ACPI_IO_RANGE: type = SYS_RES_IOPORT; break; case ACPI_BUS_NUMBER_RANGE: type = PCI_RES_BUS; break; default: return (AE_OK); } if (min + length - 1 != max) device_printf(sc->ap_dev, "Length mismatch for %d range: %jx vs %jx\n", type, (uintmax_t)(max - min + 1), (uintmax_t)length); #ifdef __i386__ if (min > ULONG_MAX) { device_printf(sc->ap_dev, "Ignoring %d range above 4GB (%#jx-%#jx)\n", type, (uintmax_t)min, (uintmax_t)max); break; } if (max > ULONG_MAX) { device_printf(sc->ap_dev, "Truncating end of %d range above 4GB (%#jx-%#jx)\n", type, (uintmax_t)min, (uintmax_t)max); max = ULONG_MAX; } #endif error = pcib_host_res_decodes(&sc->ap_host_res, type, min, max, flags); if (error) panic("Failed to manage %d range (%#jx-%#jx): %d", type, (uintmax_t)min, (uintmax_t)max, error); break; default: break; } return (AE_OK); } static bool get_decoded_bus_range(struct acpi_hpcib_softc *sc, rman_res_t *startp, rman_res_t *endp) { struct resource_list_entry *rle; rle = resource_list_find(&sc->ap_host_res.hr_rl, PCI_RES_BUS, 0); if (rle == NULL) return (false); *startp = rle->start; *endp = rle->end; return (true); } static int acpi_pcib_acpi_attach(device_t dev) { struct acpi_hpcib_softc *sc; ACPI_STATUS status; static int bus0_seen = 0; u_int slot, func, busok; struct resource *bus_res; rman_res_t end, start; int rid; int error, domain; uint8_t busno; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->ap_dev = dev; sc->ap_handle = acpi_get_handle(dev); /* * Don't attach if we're not really there. */ if (!acpi_DeviceIsPresent(dev)) return (ENXIO); acpi_pcib_osc(dev, &sc->ap_osc_ctl, 0); /* * Get our segment number by evaluating _SEG. * It's OK for this to not exist. */ status = acpi_GetInteger(sc->ap_handle, "_SEG", &sc->ap_segment); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) { device_printf(dev, "could not evaluate _SEG - %s\n", AcpiFormatException(status)); return_VALUE (ENXIO); } /* If it's not found, assume 0. */ sc->ap_segment = 0; } /* * Get the address (device and function) of the associated * PCI-Host bridge device from _ADR. Assume we don't have one if * it doesn't exist. */ status = acpi_GetInteger(sc->ap_handle, "_ADR", &sc->ap_addr); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) device_printf(dev, "could not evaluate _ADR - %s\n", AcpiFormatException(status)); sc->ap_addr = -1; } /* * Determine which address ranges this bridge decodes and setup * resource managers for those ranges. */ if (pcib_host_res_init(sc->ap_dev, &sc->ap_host_res) != 0) panic("failed to init hostb resources"); if (!acpi_disabled("hostres")) { status = AcpiWalkResources(sc->ap_handle, "_CRS", acpi_pcib_producer_handler, sc); if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) device_printf(sc->ap_dev, "failed to parse resources: %s\n", AcpiFormatException(status)); } /* * Get our base bus number by evaluating _BBN. * If this doesn't work, we assume we're bus number 0. * * XXX note that it may also not exist in the case where we are * meant to use a private configuration space mechanism for this bus, * so we should dig out our resources and check to see if we have * anything like that. How do we do this? * XXX If we have the requisite information, and if we don't think the * default PCI configuration space handlers can deal with this bus, * we should attach our own handler. * XXX invoke _REG on this for the PCI config space address space? * XXX It seems many BIOS's with multiple Host-PCI bridges do not set * _BBN correctly. They set _BBN to zero for all bridges. Thus, * if _BBN is zero and PCI bus 0 already exists, we try to read our * bus number from the configuration registers at address _ADR. * We only do this for domain/segment 0 in the hopes that this is * only needed for old single-domain machines. */ status = acpi_GetInteger(sc->ap_handle, "_BBN", &sc->ap_bus); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) { device_printf(dev, "could not evaluate _BBN - %s\n", AcpiFormatException(status)); return (ENXIO); } else { /* If it's not found, assume 0. */ sc->ap_bus = 0; } } /* * If this is segment 0, the bus is zero, and PCI bus 0 already * exists, read the bus number via PCI config space. */ busok = 1; if (sc->ap_segment == 0 && sc->ap_bus == 0 && bus0_seen) { busok = 0; if (sc->ap_addr != -1) { /* XXX: We assume bus 0. */ slot = ACPI_ADR_PCI_SLOT(sc->ap_addr); func = ACPI_ADR_PCI_FUNC(sc->ap_addr); if (bootverbose) device_printf(dev, "reading config registers from 0:%d:%d\n", slot, func); if (host_pcib_get_busno(pci_cfgregread, 0, slot, func, &busno) == 0) device_printf(dev, "couldn't read bus number from cfg space\n"); else { sc->ap_bus = busno; busok = 1; } } } /* * If nothing else worked, hope that ACPI at least lays out the * Host-PCI bridges in order and that as a result the next free * bus number is our bus number. */ if (busok == 0) { /* * If we have a region of bus numbers, use the first * number for our bus. */ if (get_decoded_bus_range(sc, &start, &end)) sc->ap_bus = start; else { rid = 0; bus_res = pci_domain_alloc_bus(sc->ap_segment, dev, &rid, 0, PCI_BUSMAX, 1, 0); if (bus_res == NULL) { device_printf(dev, "could not allocate bus number\n"); pcib_host_res_free(dev, &sc->ap_host_res); return (ENXIO); } sc->ap_bus = rman_get_start(bus_res); pci_domain_release_bus(sc->ap_segment, dev, bus_res); } } else { /* * If there is a decoded bus range, assume the bus number is * the first value in the range. Warn if _BBN doesn't match. */ if (get_decoded_bus_range(sc, &start, &end)) { if (sc->ap_bus != start) { device_printf(dev, "WARNING: BIOS configured bus number (%d) is " "not within decoded bus number range " "(%ju - %ju).\n", sc->ap_bus, (uintmax_t)start, (uintmax_t)end); device_printf(dev, "Using range start (%ju) as bus number.\n", (uintmax_t)start); sc->ap_bus = start; } } } /* If this is bus 0 on segment 0, note that it has been seen already. */ if (sc->ap_segment == 0 && sc->ap_bus == 0) bus0_seen = 1; acpi_pcib_fetch_prt(dev, &sc->ap_prt); error = bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED, BUS_SPACE_MAXSIZE, 0, NULL, NULL, &sc->ap_dma_tag); if (error != 0) goto errout; error = bus_get_domain(dev, &domain); if (error == 0) error = bus_dma_tag_set_domain(sc->ap_dma_tag, domain); /* Don't fail to attach if the domain can't be queried or set. */ error = 0; bus_identify_children(dev); - if (device_add_child(dev, "pci", -1) == NULL) { + if (device_add_child(dev, "pci", DEVICE_UNIT_ANY) == NULL) { bus_dma_tag_destroy(sc->ap_dma_tag); sc->ap_dma_tag = NULL; error = ENXIO; goto errout; } bus_attach_children(dev); return (0); errout: device_printf(device_get_parent(dev), "couldn't attach pci bus\n"); pcib_host_res_free(dev, &sc->ap_host_res); return (error); } /* * Support for standard PCI bridge ivars. */ static int acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct acpi_hpcib_softc *sc = device_get_softc(dev); switch (which) { case PCIB_IVAR_DOMAIN: *result = sc->ap_segment; return (0); case PCIB_IVAR_BUS: *result = sc->ap_bus; return (0); case ACPI_IVAR_HANDLE: *result = (uintptr_t)sc->ap_handle; return (0); case ACPI_IVAR_FLAGS: *result = (uintptr_t)sc->ap_flags; return (0); } return (ENOENT); } static int acpi_pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct acpi_hpcib_softc *sc = device_get_softc(dev); switch (which) { case PCIB_IVAR_DOMAIN: return (EINVAL); case PCIB_IVAR_BUS: sc->ap_bus = value; return (0); case ACPI_IVAR_HANDLE: sc->ap_handle = (ACPI_HANDLE)value; return (0); case ACPI_IVAR_FLAGS: sc->ap_flags = (int)value; return (0); } return (ENOENT); } static uint32_t acpi_pcib_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int bytes) { struct acpi_hpcib_softc *sc = device_get_softc(dev); return (pci_cfgregread(sc->ap_segment, bus, slot, func, reg, bytes)); } static void acpi_pcib_write_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, uint32_t data, int bytes) { struct acpi_hpcib_softc *sc = device_get_softc(dev); pci_cfgregwrite(sc->ap_segment, bus, slot, func, reg, data, bytes); } static int acpi_pcib_acpi_route_interrupt(device_t pcib, device_t dev, int pin) { struct acpi_hpcib_softc *sc = device_get_softc(pcib); return (acpi_pcib_route_interrupt(pcib, dev, pin, &sc->ap_prt)); } static int acpi_pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs) { device_t bus; bus = device_get_parent(pcib); return (PCIB_ALLOC_MSI(device_get_parent(bus), dev, count, maxcount, irqs)); } static int acpi_pcib_alloc_msix(device_t pcib, device_t dev, int *irq) { device_t bus; bus = device_get_parent(pcib); return (PCIB_ALLOC_MSIX(device_get_parent(bus), dev, irq)); } static int acpi_pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data) { struct acpi_hpcib_softc *sc; device_t bus, hostb; int error; bus = device_get_parent(pcib); error = PCIB_MAP_MSI(device_get_parent(bus), dev, irq, addr, data); if (error) return (error); sc = device_get_softc(pcib); if (sc->ap_addr == -1) return (0); /* XXX: Assumes all bridges are on bus 0. */ hostb = pci_find_dbsf(sc->ap_segment, 0, ACPI_ADR_PCI_SLOT(sc->ap_addr), ACPI_ADR_PCI_FUNC(sc->ap_addr)); if (hostb != NULL) pci_ht_map_msi(hostb, *addr); return (0); } struct resource * acpi_pcib_acpi_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct acpi_hpcib_softc *sc; struct resource *res; #if defined(__i386__) || defined(__amd64__) start = hostb_alloc_start(type, start, end, count); #endif sc = device_get_softc(dev); if (type == PCI_RES_BUS) return (pci_domain_alloc_bus(sc->ap_segment, child, rid, start, end, count, flags)); res = pcib_host_res_alloc(&sc->ap_host_res, child, type, rid, start, end, count, flags); /* * XXX: If this is a request for a specific range, assume it is * correct and pass it up to the parent. What we probably want to * do long-term is explicitly trust any firmware-configured * resources during the initial bus scan on boot and then disable * this after that. */ if (res == NULL && start + count - 1 == end) res = bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags); return (res); } int acpi_pcib_acpi_adjust_resource(device_t dev, device_t child, struct resource *r, rman_res_t start, rman_res_t end) { struct acpi_hpcib_softc *sc; sc = device_get_softc(dev); if (rman_get_type(r) == PCI_RES_BUS) return (pci_domain_adjust_bus(sc->ap_segment, child, r, start, end)); return (pcib_host_res_adjust(&sc->ap_host_res, child, r, start, end)); } int acpi_pcib_acpi_release_resource(device_t dev, device_t child, struct resource *r) { struct acpi_hpcib_softc *sc; sc = device_get_softc(dev); if (rman_get_type(r) == PCI_RES_BUS) return (pci_domain_release_bus(sc->ap_segment, child, r)); return (bus_generic_release_resource(dev, child, r)); } int acpi_pcib_acpi_activate_resource(device_t dev, device_t child, struct resource *r) { struct acpi_hpcib_softc *sc; sc = device_get_softc(dev); if (rman_get_type(r) == PCI_RES_BUS) return (pci_domain_activate_bus(sc->ap_segment, child, r)); return (bus_generic_activate_resource(dev, child, r)); } int acpi_pcib_acpi_deactivate_resource(device_t dev, device_t child, struct resource *r) { struct acpi_hpcib_softc *sc; sc = device_get_softc(dev); if (rman_get_type(r) == PCI_RES_BUS) return (pci_domain_deactivate_bus(sc->ap_segment, child, r)); return (bus_generic_deactivate_resource(dev, child, r)); } static int acpi_pcib_request_feature(device_t pcib, device_t dev, enum pci_feature feature) { uint32_t osc_ctl; struct acpi_hpcib_softc *sc; sc = device_get_softc(pcib); switch (feature) { case PCI_FEATURE_HP: osc_ctl = PCIM_OSC_CTL_PCIE_HP; break; case PCI_FEATURE_AER: osc_ctl = PCIM_OSC_CTL_PCIE_AER; break; default: return (EINVAL); } return (acpi_pcib_osc(pcib, &sc->ap_osc_ctl, osc_ctl)); } static bus_dma_tag_t acpi_pcib_get_dma_tag(device_t bus, device_t child) { struct acpi_hpcib_softc *sc; sc = device_get_softc(bus); return (sc->ap_dma_tag); } diff --git a/sys/dev/acpica/acpi_perf.c b/sys/dev/acpica/acpi_perf.c index 0013d2a94552..ee7a4355f32a 100644 --- a/sys/dev/acpica/acpi_perf.c +++ b/sys/dev/acpica/acpi_perf.c @@ -1,593 +1,593 @@ /*- * Copyright (c) 2003-2005 Nate Lawson (SDG) * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" /* * Support for ACPI processor performance states (Px) according to * section 8.3.3 of the ACPI 2.0c specification. */ struct acpi_px { uint32_t core_freq; uint32_t power; uint32_t trans_lat; uint32_t bm_lat; uint32_t ctrl_val; uint32_t sts_val; }; /* Offsets in struct cf_setting array for storing driver-specific values. */ #define PX_SPEC_CONTROL 0 #define PX_SPEC_STATUS 1 #define MAX_PX_STATES 16 struct acpi_perf_softc { device_t dev; ACPI_HANDLE handle; struct resource *perf_ctrl; /* Set new performance state. */ int perf_ctrl_type; /* Resource type for perf_ctrl. */ struct resource *perf_status; /* Check that transition succeeded. */ int perf_sts_type; /* Resource type for perf_status. */ struct acpi_px *px_states; /* ACPI perf states. */ uint32_t px_count; /* Total number of perf states. */ uint32_t px_max_avail; /* Lowest index state available. */ int px_curr_state; /* Active state index. */ int px_rid; int info_only; /* Can we set new states? */ }; #define PX_GET_REG(reg) \ (bus_space_read_4(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0)) #define PX_SET_REG(reg, val) \ (bus_space_write_4(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0, (val))) #define ACPI_NOTIFY_PERF_STATES 0x80 /* _PSS changed. */ static void acpi_perf_identify(driver_t *driver, device_t parent); static int acpi_perf_probe(device_t dev); static int acpi_perf_attach(device_t dev); static int acpi_perf_detach(device_t dev); static int acpi_perf_evaluate(device_t dev); static int acpi_px_to_set(device_t dev, struct acpi_px *px, struct cf_setting *set); static void acpi_px_available(struct acpi_perf_softc *sc); static void acpi_px_startup(void *arg); static void acpi_px_notify(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_px_settings(device_t dev, struct cf_setting *sets, int *count); static int acpi_px_set(device_t dev, const struct cf_setting *set); static int acpi_px_get(device_t dev, struct cf_setting *set); static int acpi_px_type(device_t dev, int *type); static device_method_t acpi_perf_methods[] = { /* Device interface */ DEVMETHOD(device_identify, acpi_perf_identify), DEVMETHOD(device_probe, acpi_perf_probe), DEVMETHOD(device_attach, acpi_perf_attach), DEVMETHOD(device_detach, acpi_perf_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, acpi_px_set), DEVMETHOD(cpufreq_drv_get, acpi_px_get), DEVMETHOD(cpufreq_drv_type, acpi_px_type), DEVMETHOD(cpufreq_drv_settings, acpi_px_settings), DEVMETHOD_END }; static driver_t acpi_perf_driver = { "acpi_perf", acpi_perf_methods, sizeof(struct acpi_perf_softc), }; DRIVER_MODULE(acpi_perf, cpu, acpi_perf_driver, 0, 0); MODULE_DEPEND(acpi_perf, acpi, 1, 1, 1); static MALLOC_DEFINE(M_ACPIPERF, "acpi_perf", "ACPI Performance states"); static void acpi_perf_identify(driver_t *driver, device_t parent) { ACPI_HANDLE handle; device_t dev; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "acpi_perf", -1) != NULL) + if (device_find_child(parent, "acpi_perf", DEVICE_UNIT_ANY) != NULL) return; /* Get the handle for the Processor object and check for perf states. */ handle = acpi_get_handle(parent); if (handle == NULL) return; if (ACPI_FAILURE(AcpiEvaluateObject(handle, "_PSS", NULL, NULL))) return; /* * Add a child to every CPU that has the right methods. In future * versions of the ACPI spec, CPUs can have different settings. * We probe this child now so that other devices that depend * on it (i.e., for info about supported states) will see it. */ if ((dev = BUS_ADD_CHILD(parent, 0, "acpi_perf", device_get_unit(parent))) != NULL) device_probe_and_attach(dev); else device_printf(parent, "add acpi_perf child failed\n"); } static int acpi_perf_probe(device_t dev) { ACPI_HANDLE handle; ACPI_OBJECT *pkg; struct resource *res; ACPI_BUFFER buf; int error, rid, type; if (resource_disabled("acpi_perf", 0)) return (ENXIO); /* * Check the performance state registers. If they are of type * "functional fixed hardware", we attach quietly since we will * only be providing information on settings to other drivers. */ error = ENXIO; handle = acpi_get_handle(dev); buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; if (ACPI_FAILURE(AcpiEvaluateObject(handle, "_PCT", NULL, &buf))) return (error); pkg = (ACPI_OBJECT *)buf.Pointer; if (ACPI_PKG_VALID(pkg, 2)) { rid = 0; error = acpi_PkgGas(dev, pkg, 0, &type, &rid, &res, 0); switch (error) { case 0: bus_release_resource(dev, type, rid, res); bus_delete_resource(dev, type, rid); device_set_desc(dev, "ACPI CPU Frequency Control"); break; case EOPNOTSUPP: device_quiet(dev); error = 0; break; } } AcpiOsFree(buf.Pointer); return (error); } static int acpi_perf_attach(device_t dev) { struct acpi_perf_softc *sc; sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); sc->px_max_avail = 0; sc->px_curr_state = CPUFREQ_VAL_UNKNOWN; if (acpi_perf_evaluate(dev) != 0) return (ENXIO); AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_px_startup, NULL); if (!sc->info_only) cpufreq_register(dev); return (0); } static int acpi_perf_detach(device_t dev) { /* TODO: teardown registers, remove notify handler. */ return (ENXIO); } /* Probe and setup any valid performance states (Px). */ static int acpi_perf_evaluate(device_t dev) { struct acpi_perf_softc *sc; ACPI_BUFFER buf; ACPI_OBJECT *pkg, *res; ACPI_STATUS status; int count, error, i, j; static int once = 1; uint32_t *p; /* Get the control values and parameters for each state. */ error = ENXIO; sc = device_get_softc(dev); buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(sc->handle, "_PSS", NULL, &buf); if (ACPI_FAILURE(status)) return (ENXIO); pkg = (ACPI_OBJECT *)buf.Pointer; if (!ACPI_PKG_VALID(pkg, 1)) { device_printf(dev, "invalid top level _PSS package\n"); goto out; } sc->px_count = pkg->Package.Count; sc->px_states = malloc(sc->px_count * sizeof(struct acpi_px), M_ACPIPERF, M_WAITOK | M_ZERO); /* * Each state is a package of {CoreFreq, Power, TransitionLatency, * BusMasterLatency, ControlVal, StatusVal}, sorted from highest * performance to lowest. */ count = 0; for (i = 0; i < sc->px_count; i++) { res = &pkg->Package.Elements[i]; if (!ACPI_PKG_VALID(res, 6)) { if (once) { once = 0; device_printf(dev, "invalid _PSS package\n"); } continue; } /* Parse the rest of the package into the struct. */ p = &sc->px_states[count].core_freq; for (j = 0; j < 6; j++, p++) acpi_PkgInt32(res, j, p); /* * Check for some impossible frequencies that some systems * use to indicate they don't actually support this Px state. */ if (sc->px_states[count].core_freq == 0 || sc->px_states[count].core_freq == 9999 || sc->px_states[count].core_freq == 0x9999 || sc->px_states[count].core_freq >= 0xffff) continue; /* Check for duplicate entries */ if (count > 0 && sc->px_states[count - 1].core_freq == sc->px_states[count].core_freq) continue; count++; } sc->px_count = count; /* No valid Px state found so give up. */ if (count == 0) goto out; AcpiOsFree(buf.Pointer); /* Get the control and status registers (one of each). */ buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(sc->handle, "_PCT", NULL, &buf); if (ACPI_FAILURE(status)) goto out; /* Check the package of two registers, each a Buffer in GAS format. */ pkg = (ACPI_OBJECT *)buf.Pointer; if (!ACPI_PKG_VALID(pkg, 2)) { device_printf(dev, "invalid perf register package\n"); goto out; } error = acpi_PkgGas(sc->dev, pkg, 0, &sc->perf_ctrl_type, &sc->px_rid, &sc->perf_ctrl, 0); if (error) { /* * If the register is of type FFixedHW, we can only return * info, we can't get or set new settings. */ if (error == EOPNOTSUPP) { sc->info_only = TRUE; error = 0; } else device_printf(dev, "failed in PERF_CTL attach\n"); goto out; } sc->px_rid++; error = acpi_PkgGas(sc->dev, pkg, 1, &sc->perf_sts_type, &sc->px_rid, &sc->perf_status, 0); if (error) { if (error == EOPNOTSUPP) { sc->info_only = TRUE; error = 0; } else device_printf(dev, "failed in PERF_STATUS attach\n"); goto out; } sc->px_rid++; /* Get our current limit and register for notifies. */ acpi_px_available(sc); AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_px_notify, sc); error = 0; out: if (error) { if (sc->px_states) { free(sc->px_states, M_ACPIPERF); sc->px_states = NULL; } if (sc->perf_ctrl) { bus_release_resource(sc->dev, sc->perf_ctrl_type, 0, sc->perf_ctrl); bus_delete_resource(sc->dev, sc->perf_ctrl_type, 0); sc->perf_ctrl = NULL; } if (sc->perf_status) { bus_release_resource(sc->dev, sc->perf_sts_type, 1, sc->perf_status); bus_delete_resource(sc->dev, sc->perf_sts_type, 1); sc->perf_status = NULL; } sc->px_rid = 0; sc->px_count = 0; } if (buf.Pointer) AcpiOsFree(buf.Pointer); return (error); } static void acpi_px_startup(void *arg) { /* Signal to the platform that we are taking over CPU control. */ if (AcpiGbl_FADT.PstateControl == 0) return; ACPI_LOCK(acpi); AcpiOsWritePort(AcpiGbl_FADT.SmiCommand, AcpiGbl_FADT.PstateControl, 8); ACPI_UNLOCK(acpi); } static void acpi_px_notify(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_perf_softc *sc; sc = context; if (notify != ACPI_NOTIFY_PERF_STATES) return; acpi_px_available(sc); /* TODO: Implement notification when frequency changes. */ } /* * Find the highest currently-supported performance state. * This can be called at runtime (e.g., due to a docking event) at * the request of a Notify on the processor object. */ static void acpi_px_available(struct acpi_perf_softc *sc) { ACPI_STATUS status; struct cf_setting set; status = acpi_GetInteger(sc->handle, "_PPC", &sc->px_max_avail); /* If the old state is too high, set current state to the new max. */ if (ACPI_SUCCESS(status)) { if (sc->px_curr_state != CPUFREQ_VAL_UNKNOWN && sc->px_curr_state > sc->px_max_avail) { acpi_px_to_set(sc->dev, &sc->px_states[sc->px_max_avail], &set); acpi_px_set(sc->dev, &set); } } else sc->px_max_avail = 0; } static int acpi_px_to_set(device_t dev, struct acpi_px *px, struct cf_setting *set) { if (px == NULL || set == NULL) return (EINVAL); set->freq = px->core_freq; set->power = px->power; /* XXX Include BM latency too? */ set->lat = px->trans_lat; set->volts = CPUFREQ_VAL_UNKNOWN; set->dev = dev; set->spec[PX_SPEC_CONTROL] = px->ctrl_val; set->spec[PX_SPEC_STATUS] = px->sts_val; return (0); } static int acpi_px_settings(device_t dev, struct cf_setting *sets, int *count) { struct acpi_perf_softc *sc; int x, y; sc = device_get_softc(dev); if (sets == NULL || count == NULL) return (EINVAL); if (*count < sc->px_count - sc->px_max_avail) return (E2BIG); /* Return a list of settings that are currently valid. */ y = 0; for (x = sc->px_max_avail; x < sc->px_count; x++, y++) acpi_px_to_set(dev, &sc->px_states[x], &sets[y]); *count = sc->px_count - sc->px_max_avail; return (0); } static int acpi_px_set(device_t dev, const struct cf_setting *set) { struct acpi_perf_softc *sc; int i, status, sts_val, tries; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); /* If we can't set new states, return immediately. */ if (sc->info_only) return (ENXIO); /* Look up appropriate state, based on frequency. */ for (i = sc->px_max_avail; i < sc->px_count; i++) { if (CPUFREQ_CMP(set->freq, sc->px_states[i].core_freq)) break; } if (i == sc->px_count) return (EINVAL); /* Write the appropriate value to the register. */ PX_SET_REG(sc->perf_ctrl, sc->px_states[i].ctrl_val); /* * Try for up to 10 ms to verify the desired state was selected. * This is longer than the standard says (1 ms) but in some modes, * systems may take longer to respond. */ sts_val = sc->px_states[i].sts_val; for (tries = 0; tries < 1000; tries++) { status = PX_GET_REG(sc->perf_status); /* * If we match the status or the desired status is 8 bits * and matches the relevant bits, assume we succeeded. It * appears some systems (IBM R32) expect byte-wide access * even though the standard says the register is 32-bit. */ if (status == sts_val || ((sts_val & ~0xff) == 0 && (status & 0xff) == sts_val)) break; DELAY(10); } if (tries == 1000) { device_printf(dev, "Px transition to %d failed\n", sc->px_states[i].core_freq); return (ENXIO); } sc->px_curr_state = i; return (0); } static int acpi_px_get(device_t dev, struct cf_setting *set) { struct acpi_perf_softc *sc; uint64_t rate; int i; struct pcpu *pc; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); /* If we can't get new states, return immediately. */ if (sc->info_only) return (ENXIO); /* If we've set the rate before, use the cached value. */ if (sc->px_curr_state != CPUFREQ_VAL_UNKNOWN) { acpi_px_to_set(dev, &sc->px_states[sc->px_curr_state], set); return (0); } /* Otherwise, estimate and try to match against our settings. */ pc = cpu_get_pcpu(dev); if (pc == NULL) return (ENXIO); cpu_est_clockrate(pc->pc_cpuid, &rate); rate /= 1000000; for (i = 0; i < sc->px_count; i++) { if (CPUFREQ_CMP(sc->px_states[i].core_freq, rate)) { sc->px_curr_state = i; acpi_px_to_set(dev, &sc->px_states[i], set); break; } } /* No match, give up. */ if (i == sc->px_count) { sc->px_curr_state = CPUFREQ_VAL_UNKNOWN; set->freq = CPUFREQ_VAL_UNKNOWN; } return (0); } static int acpi_px_type(device_t dev, int *type) { struct acpi_perf_softc *sc; if (type == NULL) return (EINVAL); sc = device_get_softc(dev); *type = CPUFREQ_TYPE_ABSOLUTE; if (sc->info_only) *type |= CPUFREQ_FLAG_INFO_ONLY; return (0); } diff --git a/sys/dev/acpica/acpi_throttle.c b/sys/dev/acpica/acpi_throttle.c index 5cdca63c27c6..8b2919c71073 100644 --- a/sys/dev/acpica/acpi_throttle.c +++ b/sys/dev/acpica/acpi_throttle.c @@ -1,440 +1,440 @@ /*- * Copyright (c) 2003-2005 Nate Lawson (SDG) * Copyright (c) 2001 Michael Smith * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" /* * Throttling provides relative frequency control. It involves modulating * the clock so that the CPU is active for only a fraction of the normal * clock cycle. It does not change voltage and so is less efficient than * other mechanisms. Since it is relative, it can be used in addition to * absolute cpufreq drivers. We support the ACPI 2.0 specification. */ struct acpi_throttle_softc { device_t cpu_dev; ACPI_HANDLE cpu_handle; uint32_t cpu_p_blk; /* ACPI P_BLK location */ uint32_t cpu_p_blk_len; /* P_BLK length (must be 6). */ struct resource *cpu_p_cnt; /* Throttling control register */ int cpu_p_type; /* Resource type for cpu_p_cnt. */ uint32_t cpu_thr_state; /* Current throttle setting. */ }; #define THR_GET_REG(reg) \ (bus_space_read_4(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0)) #define THR_SET_REG(reg, val) \ (bus_space_write_4(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0, (val))) /* * Speeds are stored in counts, from 1 to CPU_MAX_SPEED, and * reported to the user in hundredths of a percent. */ #define CPU_MAX_SPEED (1 << cpu_duty_width) #define CPU_SPEED_PERCENT(x) ((10000 * (x)) / CPU_MAX_SPEED) #define CPU_SPEED_PRINTABLE(x) (CPU_SPEED_PERCENT(x) / 10), \ (CPU_SPEED_PERCENT(x) % 10) #define CPU_P_CNT_THT_EN (1<<4) #define CPU_QUIRK_NO_THROTTLE (1<<1) /* Throttling is not usable. */ #define PCI_VENDOR_INTEL 0x8086 #define PCI_DEVICE_82371AB_3 0x7113 /* PIIX4 chipset for quirks. */ #define PCI_REVISION_A_STEP 0 #define PCI_REVISION_B_STEP 1 static uint32_t cpu_duty_offset; /* Offset in P_CNT of throttle val. */ static uint32_t cpu_duty_width; /* Bit width of throttle value. */ static int thr_rid; /* Driver-wide resource id. */ static int thr_quirks; /* Indicate any hardware bugs. */ static void acpi_throttle_identify(driver_t *driver, device_t parent); static int acpi_throttle_probe(device_t dev); static int acpi_throttle_attach(device_t dev); static int acpi_throttle_evaluate(struct acpi_throttle_softc *sc); static void acpi_throttle_quirks(struct acpi_throttle_softc *sc); static int acpi_thr_settings(device_t dev, struct cf_setting *sets, int *count); static int acpi_thr_set(device_t dev, const struct cf_setting *set); static int acpi_thr_get(device_t dev, struct cf_setting *set); static int acpi_thr_type(device_t dev, int *type); static device_method_t acpi_throttle_methods[] = { /* Device interface */ DEVMETHOD(device_identify, acpi_throttle_identify), DEVMETHOD(device_probe, acpi_throttle_probe), DEVMETHOD(device_attach, acpi_throttle_attach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, acpi_thr_set), DEVMETHOD(cpufreq_drv_get, acpi_thr_get), DEVMETHOD(cpufreq_drv_type, acpi_thr_type), DEVMETHOD(cpufreq_drv_settings, acpi_thr_settings), DEVMETHOD_END }; static driver_t acpi_throttle_driver = { "acpi_throttle", acpi_throttle_methods, sizeof(struct acpi_throttle_softc), }; DRIVER_MODULE(acpi_throttle, cpu, acpi_throttle_driver, 0, 0); static void acpi_throttle_identify(driver_t *driver, device_t parent) { ACPI_BUFFER buf; ACPI_HANDLE handle; ACPI_OBJECT *obj; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "acpi_throttle", -1)) + if (device_find_child(parent, "acpi_throttle", DEVICE_UNIT_ANY)) return; /* Check for a valid duty width and parent CPU type. */ handle = acpi_get_handle(parent); if (handle == NULL) return; if (AcpiGbl_FADT.DutyWidth == 0 || acpi_get_type(parent) != ACPI_TYPE_PROCESSOR) return; /* * Add a child if there's a non-NULL P_BLK and correct length, or * if the _PTC method is present. */ buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; if (ACPI_FAILURE(AcpiEvaluateObject(handle, NULL, NULL, &buf))) return; obj = (ACPI_OBJECT *)buf.Pointer; if ((obj->Processor.PblkAddress && obj->Processor.PblkLength >= 4) || ACPI_SUCCESS(AcpiEvaluateObject(handle, "_PTC", NULL, NULL))) { if (BUS_ADD_CHILD(parent, 0, "acpi_throttle", device_get_unit(parent)) == NULL) device_printf(parent, "add throttle child failed\n"); } AcpiOsFree(obj); } static int acpi_throttle_probe(device_t dev) { if (resource_disabled("acpi_throttle", 0)) return (ENXIO); /* * On i386 platforms at least, ACPI throttling is accomplished by * the chipset modulating the STPCLK# pin based on the duty cycle. * Since p4tcc uses the same mechanism (but internal to the CPU), * we disable acpi_throttle when p4tcc is also present. */ - if (device_find_child(device_get_parent(dev), "p4tcc", -1) && - !resource_disabled("p4tcc", 0)) + if (device_find_child(device_get_parent(dev), "p4tcc", DEVICE_UNIT_ANY) + && !resource_disabled("p4tcc", 0)) return (ENXIO); device_set_desc(dev, "ACPI CPU Throttling"); return (0); } static int acpi_throttle_attach(device_t dev) { struct acpi_throttle_softc *sc; struct cf_setting set; ACPI_BUFFER buf; ACPI_OBJECT *obj; ACPI_STATUS status; int error; sc = device_get_softc(dev); sc->cpu_dev = dev; sc->cpu_handle = acpi_get_handle(dev); buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf); if (ACPI_FAILURE(status)) { device_printf(dev, "attach failed to get Processor obj - %s\n", AcpiFormatException(status)); return (ENXIO); } obj = (ACPI_OBJECT *)buf.Pointer; sc->cpu_p_blk = obj->Processor.PblkAddress; sc->cpu_p_blk_len = obj->Processor.PblkLength; AcpiOsFree(obj); /* If this is the first device probed, check for quirks. */ if (device_get_unit(dev) == 0) acpi_throttle_quirks(sc); /* Attempt to attach the actual throttling register. */ error = acpi_throttle_evaluate(sc); if (error) return (error); /* * Set our initial frequency to the highest since some systems * seem to boot with this at the lowest setting. */ set.freq = 10000; acpi_thr_set(dev, &set); /* Everything went ok, register with cpufreq(4). */ cpufreq_register(dev); return (0); } static int acpi_throttle_evaluate(struct acpi_throttle_softc *sc) { uint32_t duty_end; ACPI_BUFFER buf; ACPI_OBJECT obj; ACPI_GENERIC_ADDRESS gas; ACPI_STATUS status; /* Get throttling parameters from the FADT. 0 means not supported. */ if (device_get_unit(sc->cpu_dev) == 0) { cpu_duty_offset = AcpiGbl_FADT.DutyOffset; cpu_duty_width = AcpiGbl_FADT.DutyWidth; } if (cpu_duty_width == 0 || (thr_quirks & CPU_QUIRK_NO_THROTTLE) != 0) return (ENXIO); /* Validate the duty offset/width. */ duty_end = cpu_duty_offset + cpu_duty_width - 1; if (duty_end > 31) { device_printf(sc->cpu_dev, "CLK_VAL field overflows P_CNT register\n"); return (ENXIO); } if (cpu_duty_offset <= 4 && duty_end >= 4) { device_printf(sc->cpu_dev, "CLK_VAL field overlaps THT_EN bit\n"); return (ENXIO); } /* * If not present, fall back to using the processor's P_BLK to find * the P_CNT register. * * Note that some systems seem to duplicate the P_BLK pointer * across multiple CPUs, so not getting the resource is not fatal. */ buf.Pointer = &obj; buf.Length = sizeof(obj); status = AcpiEvaluateObject(sc->cpu_handle, "_PTC", NULL, &buf); if (ACPI_SUCCESS(status)) { if (obj.Buffer.Length < sizeof(ACPI_GENERIC_ADDRESS) + 3) { device_printf(sc->cpu_dev, "_PTC buffer too small\n"); return (ENXIO); } memcpy(&gas, obj.Buffer.Pointer + 3, sizeof(gas)); acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid, &gas, &sc->cpu_p_cnt, 0); if (sc->cpu_p_cnt != NULL && bootverbose) { device_printf(sc->cpu_dev, "P_CNT from _PTC %#jx\n", gas.Address); } } /* If _PTC not present or other failure, try the P_BLK. */ if (sc->cpu_p_cnt == NULL) { /* * The spec says P_BLK must be 6 bytes long. However, some * systems use it to indicate a fractional set of features * present so we take anything >= 4. */ if (sc->cpu_p_blk_len < 4) return (ENXIO); gas.Address = sc->cpu_p_blk; gas.SpaceId = ACPI_ADR_SPACE_SYSTEM_IO; gas.BitWidth = 32; acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid, &gas, &sc->cpu_p_cnt, 0); if (sc->cpu_p_cnt != NULL) { if (bootverbose) device_printf(sc->cpu_dev, "P_CNT from P_BLK %#x\n", sc->cpu_p_blk); } else { device_printf(sc->cpu_dev, "failed to attach P_CNT\n"); return (ENXIO); } } thr_rid++; return (0); } static void acpi_throttle_quirks(struct acpi_throttle_softc *sc) { #ifdef __i386__ device_t acpi_dev; /* Look for various quirks of the PIIX4 part. */ acpi_dev = pci_find_device(PCI_VENDOR_INTEL, PCI_DEVICE_82371AB_3); if (acpi_dev) { switch (pci_get_revid(acpi_dev)) { /* * Disable throttling control on PIIX4 A and B-step. * See specification changes #13 ("Manual Throttle Duty Cycle") * and #14 ("Enabling and Disabling Manual Throttle"), plus * erratum #5 ("STPCLK# Deassertion Time") from the January * 2002 PIIX4 specification update. Note that few (if any) * mobile systems ever used this part. */ case PCI_REVISION_A_STEP: case PCI_REVISION_B_STEP: thr_quirks |= CPU_QUIRK_NO_THROTTLE; break; default: break; } } #endif } static int acpi_thr_settings(device_t dev, struct cf_setting *sets, int *count) { int i, speed; if (sets == NULL || count == NULL) return (EINVAL); if (*count < CPU_MAX_SPEED) return (E2BIG); /* Return a list of valid settings for this driver. */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * CPU_MAX_SPEED); for (i = 0, speed = CPU_MAX_SPEED; speed != 0; i++, speed--) { sets[i].freq = CPU_SPEED_PERCENT(speed); sets[i].dev = dev; } *count = CPU_MAX_SPEED; return (0); } static int acpi_thr_set(device_t dev, const struct cf_setting *set) { struct acpi_throttle_softc *sc; uint32_t clk_val, p_cnt, speed; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); /* * Validate requested state converts to a duty cycle that is an * integer from [1 .. CPU_MAX_SPEED]. */ speed = set->freq * CPU_MAX_SPEED / 10000; if (speed * 10000 != set->freq * CPU_MAX_SPEED || speed < 1 || speed > CPU_MAX_SPEED) return (EINVAL); /* If we're at this setting, don't bother applying it again. */ if (speed == sc->cpu_thr_state) return (0); /* Get the current P_CNT value and disable throttling */ p_cnt = THR_GET_REG(sc->cpu_p_cnt); p_cnt &= ~CPU_P_CNT_THT_EN; THR_SET_REG(sc->cpu_p_cnt, p_cnt); /* If we're at maximum speed, that's all */ if (speed < CPU_MAX_SPEED) { /* Mask the old CLK_VAL off and OR in the new value */ clk_val = (CPU_MAX_SPEED - 1) << cpu_duty_offset; p_cnt &= ~clk_val; p_cnt |= (speed << cpu_duty_offset); /* Write the new P_CNT value and then enable throttling */ THR_SET_REG(sc->cpu_p_cnt, p_cnt); p_cnt |= CPU_P_CNT_THT_EN; THR_SET_REG(sc->cpu_p_cnt, p_cnt); } sc->cpu_thr_state = speed; return (0); } static int acpi_thr_get(device_t dev, struct cf_setting *set) { struct acpi_throttle_softc *sc; uint32_t p_cnt, clk_val; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); /* Get the current throttling setting from P_CNT. */ p_cnt = THR_GET_REG(sc->cpu_p_cnt); clk_val = (p_cnt >> cpu_duty_offset) & (CPU_MAX_SPEED - 1); sc->cpu_thr_state = clk_val; memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); set->freq = CPU_SPEED_PERCENT(clk_val); set->dev = dev; return (0); } static int acpi_thr_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_RELATIVE; return (0); } diff --git a/sys/dev/acpica/acpi_video.c b/sys/dev/acpica/acpi_video.c index 472a38d3eb56..7a22c9dc0994 100644 --- a/sys/dev/acpica/acpi_video.c +++ b/sys/dev/acpica/acpi_video.c @@ -1,1250 +1,1250 @@ /*- * Copyright (c) 2002-2003 Taku YAMAMOTO * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * * $Id: acpi_vid.c,v 1.4 2003/10/13 10:07:36 taku Exp $ */ #include #include "opt_evdev.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef EVDEV_SUPPORT #include #include #endif /* ACPI video extension driver. */ struct acpi_video_output { ACPI_HANDLE handle; UINT32 adr; STAILQ_ENTRY(acpi_video_output) vo_next; struct { int num; STAILQ_ENTRY(acpi_video_output) next; } vo_unit; int vo_hasbqc; /* Query method is present. */ int vo_level; /* Cached level when !vo_hasbqc. */ int vo_brightness; int vo_fullpower; int vo_economy; int vo_numlevels; int *vo_levels; struct sysctl_ctx_list vo_sysctl_ctx; struct sysctl_oid *vo_sysctl_tree; #ifdef EVDEV_SUPPORT struct evdev_dev *evdev; #endif }; STAILQ_HEAD(acpi_video_output_queue, acpi_video_output); struct acpi_video_softc { device_t device; ACPI_HANDLE handle; struct acpi_video_output_queue vid_outputs; eventhandler_tag vid_pwr_evh; #ifdef EVDEV_SUPPORT struct evdev_dev *evdev; #endif }; /* interfaces */ static int acpi_video_modevent(struct module*, int, void *); static void acpi_video_identify(driver_t *driver, device_t parent); static int acpi_video_probe(device_t); static int acpi_video_attach(device_t); static int acpi_video_detach(device_t); static int acpi_video_resume(device_t); static int acpi_video_shutdown(device_t); static void acpi_video_notify_handler(ACPI_HANDLE, UINT32, void *); static void acpi_video_power_profile(void *); static void acpi_video_bind_outputs(struct acpi_video_softc *); static struct acpi_video_output *acpi_video_vo_init(UINT32); static void acpi_video_vo_bind(struct acpi_video_output *, ACPI_HANDLE); static void acpi_video_vo_destroy(struct acpi_video_output *); static int acpi_video_vo_check_level(struct acpi_video_output *, int); static void acpi_video_vo_notify_handler(ACPI_HANDLE, UINT32, void *); static int acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS); /* operations */ static void vid_set_switch_policy(ACPI_HANDLE, UINT32); static int vid_enum_outputs(ACPI_HANDLE, void(*)(ACPI_HANDLE, UINT32, void *), void *); static int vo_get_brightness_levels(ACPI_HANDLE, int **); static int vo_get_brightness(struct acpi_video_output *); static void vo_set_brightness(struct acpi_video_output *, int); static UINT32 vo_get_device_status(ACPI_HANDLE); static UINT32 vo_get_graphics_state(ACPI_HANDLE); static void vo_set_device_state(ACPI_HANDLE, UINT32); /* events */ #define VID_NOTIFY_SWITCHED 0x80 #define VID_NOTIFY_REPROBE 0x81 #define VID_NOTIFY_CYCLE_OUT 0x82 #define VID_NOTIFY_NEXT_OUT 0x83 #define VID_NOTIFY_PREV_OUT 0x84 #define VID_NOTIFY_CYCLE_BRN 0x85 #define VID_NOTIFY_INC_BRN 0x86 #define VID_NOTIFY_DEC_BRN 0x87 #define VID_NOTIFY_ZERO_BRN 0x88 #define VID_NOTIFY_DISP_OFF 0x89 /* _DOS (Enable/Disable Output Switching) argument bits */ #define DOS_SWITCH_MASK 3 #define DOS_SWITCH_BY_OSPM 0 #define DOS_SWITCH_BY_BIOS 1 #define DOS_SWITCH_LOCKED 2 #define DOS_BRIGHTNESS_BY_OSPM (1 << 2) /* _DOD and subdev's _ADR */ #define DOD_DEVID_MASK 0x0f00 #define DOD_DEVID_MASK_FULL 0xffff #define DOD_DEVID_MASK_DISPIDX 0x000f #define DOD_DEVID_MASK_DISPPORT 0x00f0 #define DOD_DEVID_MONITOR 0x0100 #define DOD_DEVID_LCD 0x0110 #define DOD_DEVID_TV 0x0200 #define DOD_DEVID_EXT 0x0300 #define DOD_DEVID_INTDFP 0x0400 #define DOD_BIOS (1 << 16) #define DOD_NONVGA (1 << 17) #define DOD_HEAD_ID_SHIFT 18 #define DOD_HEAD_ID_BITS 3 #define DOD_HEAD_ID_MASK \ (((1 << DOD_HEAD_ID_BITS) - 1) << DOD_HEAD_ID_SHIFT) #define DOD_DEVID_SCHEME_STD (1U << 31) /* _BCL related constants */ #define BCL_FULLPOWER 0 #define BCL_ECONOMY 1 /* _DCS (Device Currrent Status) value bits and masks. */ #define DCS_EXISTS (1 << 0) #define DCS_ACTIVE (1 << 1) #define DCS_READY (1 << 2) #define DCS_FUNCTIONAL (1 << 3) #define DCS_ATTACHED (1 << 4) /* _DSS (Device Set Status) argument bits and masks. */ #define DSS_INACTIVE 0 #define DSS_ACTIVE (1 << 0) #define DSS_SETNEXT (1 << 30) #define DSS_COMMIT (1U << 31) static device_method_t acpi_video_methods[] = { DEVMETHOD(device_identify, acpi_video_identify), DEVMETHOD(device_probe, acpi_video_probe), DEVMETHOD(device_attach, acpi_video_attach), DEVMETHOD(device_detach, acpi_video_detach), DEVMETHOD(device_resume, acpi_video_resume), DEVMETHOD(device_shutdown, acpi_video_shutdown), { 0, 0 } }; static driver_t acpi_video_driver = { "acpi_video", acpi_video_methods, sizeof(struct acpi_video_softc), }; DRIVER_MODULE(acpi_video, vgapci, acpi_video_driver, acpi_video_modevent, NULL); MODULE_DEPEND(acpi_video, acpi, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(acpi_video, evdev, 1, 1, 1); #endif static struct sysctl_ctx_list acpi_video_sysctl_ctx; static struct sysctl_oid *acpi_video_sysctl_tree; static struct acpi_video_output_queue crt_units, tv_units, ext_units, lcd_units, other_units; /* * The 'video' lock protects the hierarchy of video output devices * (the video "bus"). The 'video_output' lock protects per-output * data is equivalent to a softc lock for each video output. */ ACPI_SERIAL_DECL(video, "ACPI video"); ACPI_SERIAL_DECL(video_output, "ACPI video output"); static MALLOC_DEFINE(M_ACPIVIDEO, "acpivideo", "ACPI video extension"); #ifdef EVDEV_SUPPORT static const struct { UINT32 notify; uint16_t key; } acpi_video_evdev_map[] = { { VID_NOTIFY_SWITCHED, KEY_SWITCHVIDEOMODE }, { VID_NOTIFY_REPROBE, KEY_SWITCHVIDEOMODE }, { VID_NOTIFY_CYCLE_OUT, KEY_SWITCHVIDEOMODE }, { VID_NOTIFY_NEXT_OUT, KEY_VIDEO_NEXT }, { VID_NOTIFY_PREV_OUT, KEY_VIDEO_PREV }, { VID_NOTIFY_CYCLE_BRN, KEY_BRIGHTNESS_CYCLE }, { VID_NOTIFY_INC_BRN, KEY_BRIGHTNESSUP }, { VID_NOTIFY_DEC_BRN, KEY_BRIGHTNESSDOWN }, { VID_NOTIFY_ZERO_BRN, KEY_BRIGHTNESS_ZERO }, { VID_NOTIFY_DISP_OFF, KEY_DISPLAY_OFF }, }; static void acpi_video_push_evdev_event(struct evdev_dev *evdev, UINT32 notify) { int i; uint16_t key; /* Do not allow to execute 2 instances this routine concurrently */ ACPI_SERIAL_ASSERT(video_output); for (i = 0; i < nitems(acpi_video_evdev_map); i++) { if (acpi_video_evdev_map[i].notify == notify) { key = acpi_video_evdev_map[i].key; evdev_push_key(evdev, key, 1); evdev_sync(evdev); evdev_push_key(evdev, key, 0); evdev_sync(evdev); break; } } } #endif static int acpi_video_modevent(struct module *mod __unused, int evt, void *cookie __unused) { int err; err = 0; switch (evt) { case MOD_LOAD: sysctl_ctx_init(&acpi_video_sysctl_ctx); STAILQ_INIT(&crt_units); STAILQ_INIT(&tv_units); STAILQ_INIT(&ext_units); STAILQ_INIT(&lcd_units); STAILQ_INIT(&other_units); break; case MOD_UNLOAD: sysctl_ctx_free(&acpi_video_sysctl_ctx); acpi_video_sysctl_tree = NULL; break; default: err = EINVAL; } return (err); } static void acpi_video_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "acpi_video", -1) == NULL) + if (device_find_child(parent, "acpi_video", DEVICE_UNIT_ANY) == NULL) device_add_child(parent, "acpi_video", DEVICE_UNIT_ANY); } static int acpi_video_probe(device_t dev) { ACPI_HANDLE devh, h; ACPI_OBJECT_TYPE t_dos; devh = acpi_get_handle(dev); if (acpi_disabled("video") || ACPI_FAILURE(AcpiGetHandle(devh, "_DOD", &h)) || ACPI_FAILURE(AcpiGetHandle(devh, "_DOS", &h)) || ACPI_FAILURE(AcpiGetType(h, &t_dos)) || t_dos != ACPI_TYPE_METHOD) return (ENXIO); device_set_desc(dev, "ACPI video extension"); return (0); } static int acpi_video_attach(device_t dev) { struct acpi_softc *acpi_sc; struct acpi_video_softc *sc; #ifdef EVDEV_SUPPORT int i; #endif sc = device_get_softc(dev); acpi_sc = devclass_get_softc(devclass_find("acpi"), 0); if (acpi_sc == NULL) return (ENXIO); #ifdef EVDEV_SUPPORT sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(dev)); evdev_set_phys(sc->evdev, device_get_nameunit(dev)); evdev_set_id(sc->evdev, BUS_HOST, 0, 0, 1); evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_KEY); for (i = 0; i < nitems(acpi_video_evdev_map); i++) evdev_support_key(sc->evdev, acpi_video_evdev_map[i].key); if (evdev_register(sc->evdev) != 0) return (ENXIO); #endif ACPI_SERIAL_BEGIN(video); if (acpi_video_sysctl_tree == NULL) { acpi_video_sysctl_tree = SYSCTL_ADD_NODE(&acpi_video_sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "video", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "video extension control"); } ACPI_SERIAL_END(video); sc->device = dev; sc->handle = acpi_get_handle(dev); STAILQ_INIT(&sc->vid_outputs); AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_video_notify_handler, sc); sc->vid_pwr_evh = EVENTHANDLER_REGISTER(power_profile_change, acpi_video_power_profile, sc, 0); ACPI_SERIAL_BEGIN(video); acpi_video_bind_outputs(sc); ACPI_SERIAL_END(video); /* * Notify the BIOS that we want to switch both active outputs and * brightness levels. */ vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_OSPM | DOS_BRIGHTNESS_BY_OSPM); acpi_video_power_profile(sc); return (0); } static int acpi_video_detach(device_t dev) { struct acpi_video_softc *sc; struct acpi_video_output *vo, *vn; sc = device_get_softc(dev); vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS); EVENTHANDLER_DEREGISTER(power_profile_change, sc->vid_pwr_evh); AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_video_notify_handler); ACPI_SERIAL_BEGIN(video); STAILQ_FOREACH_SAFE(vo, &sc->vid_outputs, vo_next, vn) { acpi_video_vo_destroy(vo); } ACPI_SERIAL_END(video); #ifdef EVDEV_SUPPORT evdev_free(sc->evdev); #endif return (0); } static int acpi_video_resume(device_t dev) { struct acpi_video_softc *sc; struct acpi_video_output *vo, *vn; int level; sc = device_get_softc(dev); /* Restore brightness level */ ACPI_SERIAL_BEGIN(video); ACPI_SERIAL_BEGIN(video_output); STAILQ_FOREACH_SAFE(vo, &sc->vid_outputs, vo_next, vn) { if ((vo->adr & DOD_DEVID_MASK_FULL) != DOD_DEVID_LCD && (vo->adr & DOD_DEVID_MASK) != DOD_DEVID_INTDFP) continue; if ((vo_get_device_status(vo->handle) & DCS_ACTIVE) == 0) continue; level = vo_get_brightness(vo); if (level != -1) vo_set_brightness(vo, level); } ACPI_SERIAL_END(video_output); ACPI_SERIAL_END(video); return (0); } static int acpi_video_shutdown(device_t dev) { struct acpi_video_softc *sc; sc = device_get_softc(dev); vid_set_switch_policy(sc->handle, DOS_SWITCH_BY_BIOS); return (0); } static void acpi_video_invoke_event_handler(void *context) { EVENTHANDLER_INVOKE(acpi_video_event, (int)(intptr_t)context); } static void acpi_video_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context) { struct acpi_video_softc *sc; struct acpi_video_output *vo, *vo_tmp; ACPI_HANDLE lasthand; UINT32 dcs, dss, dss_p; sc = (struct acpi_video_softc *)context; switch (notify) { case VID_NOTIFY_SWITCHED: dss_p = 0; lasthand = NULL; ACPI_SERIAL_BEGIN(video); ACPI_SERIAL_BEGIN(video_output); STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) { dss = vo_get_graphics_state(vo->handle); dcs = vo_get_device_status(vo->handle); if (!(dcs & DCS_READY)) dss = DSS_INACTIVE; if (((dcs & DCS_ACTIVE) && dss == DSS_INACTIVE) || (!(dcs & DCS_ACTIVE) && dss == DSS_ACTIVE)) { if (lasthand != NULL) vo_set_device_state(lasthand, dss_p); dss_p = dss; lasthand = vo->handle; } } if (lasthand != NULL) vo_set_device_state(lasthand, dss_p|DSS_COMMIT); ACPI_SERIAL_END(video_output); ACPI_SERIAL_END(video); break; case VID_NOTIFY_REPROBE: ACPI_SERIAL_BEGIN(video); STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) vo->handle = NULL; acpi_video_bind_outputs(sc); STAILQ_FOREACH_SAFE(vo, &sc->vid_outputs, vo_next, vo_tmp) { if (vo->handle == NULL) { STAILQ_REMOVE(&sc->vid_outputs, vo, acpi_video_output, vo_next); acpi_video_vo_destroy(vo); } } ACPI_SERIAL_END(video); break; /* Next events should not appear if DOS_SWITCH_BY_OSPM policy is set */ case VID_NOTIFY_CYCLE_OUT: case VID_NOTIFY_NEXT_OUT: case VID_NOTIFY_PREV_OUT: default: device_printf(sc->device, "unknown notify event 0x%x\n", notify); } AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_video_invoke_event_handler, (void *)(uintptr_t)notify); #ifdef EVDEV_SUPPORT ACPI_SERIAL_BEGIN(video_output); acpi_video_push_evdev_event(sc->evdev, notify); ACPI_SERIAL_END(video_output); #endif } static void acpi_video_power_profile(void *context) { int state; struct acpi_video_softc *sc; struct acpi_video_output *vo; sc = context; state = power_profile_get_state(); if (state != POWER_PROFILE_PERFORMANCE && state != POWER_PROFILE_ECONOMY) return; ACPI_SERIAL_BEGIN(video); ACPI_SERIAL_BEGIN(video_output); STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) { if (vo->vo_levels != NULL && vo->vo_brightness == -1) vo_set_brightness(vo, state == POWER_PROFILE_ECONOMY ? vo->vo_economy : vo->vo_fullpower); } ACPI_SERIAL_END(video_output); ACPI_SERIAL_END(video); } static void acpi_video_bind_outputs_subr(ACPI_HANDLE handle, UINT32 adr, void *context) { struct acpi_video_softc *sc; struct acpi_video_output *vo; ACPI_SERIAL_ASSERT(video); sc = context; STAILQ_FOREACH(vo, &sc->vid_outputs, vo_next) { if (vo->adr == adr) { acpi_video_vo_bind(vo, handle); return; } } vo = acpi_video_vo_init(adr); if (vo != NULL) { #ifdef EVDEV_SUPPORT vo->evdev = sc->evdev; #endif acpi_video_vo_bind(vo, handle); STAILQ_INSERT_TAIL(&sc->vid_outputs, vo, vo_next); } } static void acpi_video_bind_outputs(struct acpi_video_softc *sc) { ACPI_SERIAL_ASSERT(video); vid_enum_outputs(sc->handle, acpi_video_bind_outputs_subr, sc); } static struct acpi_video_output * acpi_video_vo_init(UINT32 adr) { struct acpi_video_output *vn, *vo, *vp; int n, x; char name[8], env[32]; const char *type, *desc; struct acpi_video_output_queue *voqh; ACPI_SERIAL_ASSERT(video); switch (adr & DOD_DEVID_MASK) { case DOD_DEVID_MONITOR: if ((adr & DOD_DEVID_MASK_FULL) == DOD_DEVID_LCD) { /* DOD_DEVID_LCD is a common, backward compatible ID */ desc = "Internal/Integrated Digital Flat Panel"; type = "lcd"; voqh = &lcd_units; } else { desc = "VGA CRT or VESA Compatible Analog Monitor"; type = "crt"; voqh = &crt_units; } break; case DOD_DEVID_TV: desc = "TV/HDTV or Analog-Video Monitor"; type = "tv"; voqh = &tv_units; break; case DOD_DEVID_EXT: desc = "External Digital Monitor"; type = "ext"; voqh = &ext_units; break; case DOD_DEVID_INTDFP: desc = "Internal/Integrated Digital Flat Panel"; type = "lcd"; voqh = &lcd_units; break; default: desc = "unknown output"; type = "out"; voqh = &other_units; } n = 0; vp = NULL; STAILQ_FOREACH(vn, voqh, vo_unit.next) { if (vn->vo_unit.num != n) break; vp = vn; n++; } snprintf(name, sizeof(name), "%s%d", type, n); vo = malloc(sizeof(*vo), M_ACPIVIDEO, M_NOWAIT); if (vo != NULL) { vo->handle = NULL; vo->adr = adr; vo->vo_unit.num = n; vo->vo_hasbqc = -1; vo->vo_level = -1; vo->vo_brightness = -1; vo->vo_fullpower = -1; /* TODO: override with tunables */ vo->vo_economy = -1; vo->vo_numlevels = 0; vo->vo_levels = NULL; snprintf(env, sizeof(env), "hw.acpi.video.%s.fullpower", name); if (getenv_int(env, &x)) vo->vo_fullpower = x; snprintf(env, sizeof(env), "hw.acpi.video.%s.economy", name); if (getenv_int(env, &x)) vo->vo_economy = x; sysctl_ctx_init(&vo->vo_sysctl_ctx); if (vp != NULL) STAILQ_INSERT_AFTER(voqh, vp, vo, vo_unit.next); else STAILQ_INSERT_TAIL(voqh, vo, vo_unit.next); if (acpi_video_sysctl_tree != NULL) vo->vo_sysctl_tree = SYSCTL_ADD_NODE(&vo->vo_sysctl_ctx, SYSCTL_CHILDREN(acpi_video_sysctl_tree), OID_AUTO, name, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, desc); if (vo->vo_sysctl_tree != NULL) { SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx, SYSCTL_CHILDREN(vo->vo_sysctl_tree), OID_AUTO, "active", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, vo, 0, acpi_video_vo_active_sysctl, "I", "current activity of this device"); SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx, SYSCTL_CHILDREN(vo->vo_sysctl_tree), OID_AUTO, "brightness", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, vo, 0, acpi_video_vo_bright_sysctl, "I", "current brightness level"); SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx, SYSCTL_CHILDREN(vo->vo_sysctl_tree), OID_AUTO, "fullpower", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, vo, POWER_PROFILE_PERFORMANCE, acpi_video_vo_presets_sysctl, "I", "preset level for full power mode"); SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx, SYSCTL_CHILDREN(vo->vo_sysctl_tree), OID_AUTO, "economy", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, vo, POWER_PROFILE_ECONOMY, acpi_video_vo_presets_sysctl, "I", "preset level for economy mode"); SYSCTL_ADD_PROC(&vo->vo_sysctl_ctx, SYSCTL_CHILDREN(vo->vo_sysctl_tree), OID_AUTO, "levels", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, vo, 0, acpi_video_vo_levels_sysctl, "I", "supported brightness levels"); } else printf("%s: sysctl node creation failed\n", type); } else printf("%s: softc allocation failed\n", type); if (bootverbose) { printf("found %s(%x)", desc, adr & DOD_DEVID_MASK_FULL); printf(", idx#%x", adr & DOD_DEVID_MASK_DISPIDX); printf(", port#%x", (adr & DOD_DEVID_MASK_DISPPORT) >> 4); if (adr & DOD_BIOS) printf(", detectable by BIOS"); if (adr & DOD_NONVGA) printf(" (Non-VGA output device whose power " "is related to the VGA device)"); printf(", head #%d\n", (adr & DOD_HEAD_ID_MASK) >> DOD_HEAD_ID_SHIFT); } return (vo); } static void acpi_video_vo_bind(struct acpi_video_output *vo, ACPI_HANDLE handle) { ACPI_SERIAL_BEGIN(video_output); if (vo->vo_levels != NULL) { AcpiRemoveNotifyHandler(vo->handle, ACPI_DEVICE_NOTIFY, acpi_video_vo_notify_handler); AcpiOsFree(vo->vo_levels); vo->vo_levels = NULL; } vo->handle = handle; vo->vo_numlevels = vo_get_brightness_levels(handle, &vo->vo_levels); if (vo->vo_numlevels >= 2) { if (vo->vo_fullpower == -1 || acpi_video_vo_check_level(vo, vo->vo_fullpower) != 0) { /* XXX - can't deal with rebinding... */ vo->vo_fullpower = vo->vo_levels[BCL_FULLPOWER]; } if (vo->vo_economy == -1 || acpi_video_vo_check_level(vo, vo->vo_economy) != 0) { /* XXX - see above. */ vo->vo_economy = vo->vo_levels[BCL_ECONOMY]; } AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY, acpi_video_vo_notify_handler, vo); } ACPI_SERIAL_END(video_output); } static void acpi_video_vo_destroy(struct acpi_video_output *vo) { struct acpi_video_output_queue *voqh; ACPI_SERIAL_ASSERT(video); if (vo->vo_sysctl_tree != NULL) { vo->vo_sysctl_tree = NULL; sysctl_ctx_free(&vo->vo_sysctl_ctx); } if (vo->vo_levels != NULL) { AcpiRemoveNotifyHandler(vo->handle, ACPI_DEVICE_NOTIFY, acpi_video_vo_notify_handler); AcpiOsFree(vo->vo_levels); } switch (vo->adr & DOD_DEVID_MASK) { case DOD_DEVID_MONITOR: if ((vo->adr & DOD_DEVID_MASK_FULL) == DOD_DEVID_LCD) voqh = &lcd_units; else voqh = &crt_units; break; case DOD_DEVID_TV: voqh = &tv_units; break; case DOD_DEVID_EXT: voqh = &ext_units; break; case DOD_DEVID_INTDFP: voqh = &lcd_units; break; default: voqh = &other_units; } STAILQ_REMOVE(voqh, vo, acpi_video_output, vo_unit.next); free(vo, M_ACPIVIDEO); } static int acpi_video_vo_check_level(struct acpi_video_output *vo, int level) { int i; ACPI_SERIAL_ASSERT(video_output); if (vo->vo_levels == NULL) return (ENODEV); for (i = 0; i < vo->vo_numlevels; i++) if (vo->vo_levels[i] == level) return (0); return (EINVAL); } static void acpi_video_vo_notify_handler(ACPI_HANDLE handle, UINT32 notify, void *context) { struct acpi_video_output *vo; int i, j, level, new_level; vo = context; ACPI_SERIAL_BEGIN(video_output); if (vo->handle != handle) goto out; switch (notify) { case VID_NOTIFY_CYCLE_BRN: if (vo->vo_numlevels <= 3) goto out; /* FALLTHROUGH */ case VID_NOTIFY_INC_BRN: case VID_NOTIFY_DEC_BRN: case VID_NOTIFY_ZERO_BRN: case VID_NOTIFY_DISP_OFF: if (vo->vo_levels == NULL) goto out; level = vo_get_brightness(vo); if (level < 0) goto out; break; default: printf("unknown notify event 0x%x from %s\n", notify, acpi_name(handle)); goto out; } new_level = level; switch (notify) { case VID_NOTIFY_CYCLE_BRN: for (i = 2; i < vo->vo_numlevels; i++) if (vo->vo_levels[i] == level) { new_level = vo->vo_numlevels > i + 1 ? vo->vo_levels[i + 1] : vo->vo_levels[2]; break; } break; case VID_NOTIFY_INC_BRN: case VID_NOTIFY_DEC_BRN: for (i = 0; i < vo->vo_numlevels; i++) { j = vo->vo_levels[i]; if (notify == VID_NOTIFY_INC_BRN) { if (j > level && (j < new_level || level == new_level)) new_level = j; } else { if (j < level && (j > new_level || level == new_level)) new_level = j; } } break; case VID_NOTIFY_ZERO_BRN: for (i = 0; i < vo->vo_numlevels; i++) if (vo->vo_levels[i] == 0) { new_level = 0; break; } break; case VID_NOTIFY_DISP_OFF: acpi_pwr_switch_consumer(handle, ACPI_STATE_D3); break; } if (new_level != level) { vo_set_brightness(vo, new_level); vo->vo_brightness = new_level; } #ifdef EVDEV_SUPPORT acpi_video_push_evdev_event(vo->evdev, notify); #endif out: ACPI_SERIAL_END(video_output); AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_video_invoke_event_handler, (void *)(uintptr_t)notify); } /* ARGSUSED */ static int acpi_video_vo_active_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_video_output *vo; int state, err; vo = (struct acpi_video_output *)arg1; if (vo->handle == NULL) return (ENXIO); ACPI_SERIAL_BEGIN(video_output); state = (vo_get_device_status(vo->handle) & DCS_ACTIVE) ? 1 : 0; err = sysctl_handle_int(oidp, &state, 0, req); if (err != 0 || req->newptr == NULL) goto out; vo_set_device_state(vo->handle, DSS_COMMIT | (state ? DSS_ACTIVE : DSS_INACTIVE)); out: ACPI_SERIAL_END(video_output); return (err); } /* ARGSUSED */ static int acpi_video_vo_bright_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_video_output *vo; int level, preset, err; vo = (struct acpi_video_output *)arg1; ACPI_SERIAL_BEGIN(video_output); if (vo->handle == NULL) { err = ENXIO; goto out; } if (vo->vo_levels == NULL) { err = ENODEV; goto out; } preset = (power_profile_get_state() == POWER_PROFILE_ECONOMY) ? vo->vo_economy : vo->vo_fullpower; level = vo->vo_brightness; if (level == -1) level = preset; err = sysctl_handle_int(oidp, &level, 0, req); if (err != 0 || req->newptr == NULL) goto out; if (level < -1 || level > 100) { err = EINVAL; goto out; } if (level != -1 && (err = acpi_video_vo_check_level(vo, level))) goto out; vo->vo_brightness = level; vo_set_brightness(vo, (level == -1) ? preset : level); out: ACPI_SERIAL_END(video_output); return (err); } static int acpi_video_vo_presets_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_video_output *vo; int i, level, *preset, err; vo = (struct acpi_video_output *)arg1; ACPI_SERIAL_BEGIN(video_output); if (vo->handle == NULL) { err = ENXIO; goto out; } if (vo->vo_levels == NULL) { err = ENODEV; goto out; } preset = (arg2 == POWER_PROFILE_ECONOMY) ? &vo->vo_economy : &vo->vo_fullpower; level = *preset; err = sysctl_handle_int(oidp, &level, 0, req); if (err != 0 || req->newptr == NULL) goto out; if (level < -1 || level > 100) { err = EINVAL; goto out; } if (level == -1) { i = (arg2 == POWER_PROFILE_ECONOMY) ? BCL_ECONOMY : BCL_FULLPOWER; level = vo->vo_levels[i]; } else if ((err = acpi_video_vo_check_level(vo, level)) != 0) goto out; if (vo->vo_brightness == -1 && (power_profile_get_state() == arg2)) vo_set_brightness(vo, level); *preset = level; out: ACPI_SERIAL_END(video_output); return (err); } /* ARGSUSED */ static int acpi_video_vo_levels_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_video_output *vo; int err; vo = (struct acpi_video_output *)arg1; ACPI_SERIAL_BEGIN(video_output); if (vo->vo_levels == NULL) { err = ENODEV; goto out; } if (req->newptr != NULL) { err = EPERM; goto out; } err = sysctl_handle_opaque(oidp, vo->vo_levels, vo->vo_numlevels * sizeof(*vo->vo_levels), req); out: ACPI_SERIAL_END(video_output); return (err); } static void vid_set_switch_policy(ACPI_HANDLE handle, UINT32 policy) { ACPI_STATUS status; status = acpi_SetInteger(handle, "_DOS", policy); if (ACPI_FAILURE(status)) printf("can't evaluate %s._DOS - %s\n", acpi_name(handle), AcpiFormatException(status)); } struct enum_callback_arg { void (*callback)(ACPI_HANDLE, UINT32, void *); void *context; ACPI_OBJECT *dod_pkg; int count; }; static ACPI_STATUS vid_enum_outputs_subr(ACPI_HANDLE handle, UINT32 level __unused, void *context, void **retp __unused) { ACPI_STATUS status; UINT32 adr, val; struct enum_callback_arg *argset; size_t i; ACPI_SERIAL_ASSERT(video); argset = context; status = acpi_GetInteger(handle, "_ADR", &adr); if (ACPI_FAILURE(status)) return (AE_OK); for (i = 0; i < argset->dod_pkg->Package.Count; i++) { if (acpi_PkgInt32(argset->dod_pkg, i, &val) == 0 && (val & DOD_DEVID_MASK_FULL) == (adr & DOD_DEVID_MASK_FULL)) { argset->callback(handle, val, argset->context); argset->count++; } } return (AE_OK); } static int vid_enum_outputs(ACPI_HANDLE handle, void (*callback)(ACPI_HANDLE, UINT32, void *), void *context) { ACPI_STATUS status; ACPI_BUFFER dod_buf; ACPI_OBJECT *res; struct enum_callback_arg argset; ACPI_SERIAL_ASSERT(video); dod_buf.Length = ACPI_ALLOCATE_BUFFER; dod_buf.Pointer = NULL; status = AcpiEvaluateObject(handle, "_DOD", NULL, &dod_buf); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) printf("can't evaluate %s._DOD - %s\n", acpi_name(handle), AcpiFormatException(status)); argset.count = -1; goto out; } res = (ACPI_OBJECT *)dod_buf.Pointer; if (!ACPI_PKG_VALID(res, 1)) { printf("evaluation of %s._DOD makes no sense\n", acpi_name(handle)); argset.count = -1; goto out; } if (callback == NULL) { argset.count = res->Package.Count; goto out; } argset.callback = callback; argset.context = context; argset.dod_pkg = res; argset.count = 0; status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, handle, 1, vid_enum_outputs_subr, NULL, &argset, NULL); if (ACPI_FAILURE(status)) printf("failed walking down %s - %s\n", acpi_name(handle), AcpiFormatException(status)); out: if (dod_buf.Pointer != NULL) AcpiOsFree(dod_buf.Pointer); return (argset.count); } static int vo_get_brightness_levels(ACPI_HANDLE handle, int **levelp) { ACPI_STATUS status; ACPI_BUFFER bcl_buf; ACPI_OBJECT *res; int num, i, n, *levels; bcl_buf.Length = ACPI_ALLOCATE_BUFFER; bcl_buf.Pointer = NULL; status = AcpiEvaluateObject(handle, "_BCL", NULL, &bcl_buf); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) printf("can't evaluate %s._BCL - %s\n", acpi_name(handle), AcpiFormatException(status)); goto out; } res = (ACPI_OBJECT *)bcl_buf.Pointer; if (!ACPI_PKG_VALID(res, 2)) { printf("evaluation of %s._BCL makes no sense\n", acpi_name(handle)); goto out; } num = res->Package.Count; if (num < 2 || levelp == NULL) goto out; levels = AcpiOsAllocate(num * sizeof(*levels)); if (levels == NULL) goto out; for (i = 0, n = 0; i < num; i++) if (acpi_PkgInt32(res, i, &levels[n]) == 0) n++; if (n < 2) { AcpiOsFree(levels); goto out; } *levelp = levels; return (n); out: if (bcl_buf.Pointer != NULL) AcpiOsFree(bcl_buf.Pointer); return (0); } static int vo_get_bqc(struct acpi_video_output *vo, UINT32 *level) { ACPI_STATUS status; switch (vo->vo_hasbqc) { case 1: case -1: status = acpi_GetInteger(vo->handle, "_BQC", level); if (vo->vo_hasbqc == 1) break; vo->vo_hasbqc = status != AE_NOT_FOUND; if (vo->vo_hasbqc == 1) break; /* FALLTHROUGH */ default: KASSERT(vo->vo_hasbqc == 0, ("bad vo_hasbqc state %d", vo->vo_hasbqc)); *level = vo->vo_level; status = AE_OK; } return (status); } static int vo_get_brightness(struct acpi_video_output *vo) { UINT32 level; ACPI_STATUS status; ACPI_SERIAL_ASSERT(video_output); status = vo_get_bqc(vo, &level); if (ACPI_FAILURE(status)) { printf("can't evaluate %s._BQC - %s\n", acpi_name(vo->handle), AcpiFormatException(status)); return (-1); } if (level > 100) return (-1); return (level); } static void vo_set_brightness(struct acpi_video_output *vo, int level) { char notify_buf[16]; ACPI_STATUS status; ACPI_SERIAL_ASSERT(video_output); status = acpi_SetInteger(vo->handle, "_BCM", level); if (ACPI_FAILURE(status)) { printf("can't evaluate %s._BCM - %s\n", acpi_name(vo->handle), AcpiFormatException(status)); } else { vo->vo_level = level; } snprintf(notify_buf, sizeof(notify_buf), "notify=%d", level); devctl_notify("ACPI", "Video", "brightness", notify_buf); } static UINT32 vo_get_device_status(ACPI_HANDLE handle) { UINT32 dcs; ACPI_STATUS status; ACPI_SERIAL_ASSERT(video_output); dcs = 0; status = acpi_GetInteger(handle, "_DCS", &dcs); if (ACPI_FAILURE(status)) { /* * If the method is missing, assume that the device is always * operational. */ if (status != AE_NOT_FOUND) { printf("can't evaluate %s._DCS - %s\n", acpi_name(handle), AcpiFormatException(status)); } else { dcs = 0xff; } } return (dcs); } static UINT32 vo_get_graphics_state(ACPI_HANDLE handle) { UINT32 dgs; ACPI_STATUS status; dgs = 0; status = acpi_GetInteger(handle, "_DGS", &dgs); if (ACPI_FAILURE(status)) { /* * If the method is missing, assume that the device is always * operational. */ if (status != AE_NOT_FOUND) { printf("can't evaluate %s._DGS - %s\n", acpi_name(handle), AcpiFormatException(status)); } else { dgs = 0xff; } } return (dgs); } static void vo_set_device_state(ACPI_HANDLE handle, UINT32 state) { ACPI_STATUS status; ACPI_SERIAL_ASSERT(video_output); status = acpi_SetInteger(handle, "_DSS", state); if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) printf("can't evaluate %s._DSS - %s\n", acpi_name(handle), AcpiFormatException(status)); } diff --git a/sys/dev/agp/agp_i810.c b/sys/dev/agp/agp_i810.c index b63c0aaf634a..9d955745f673 100644 --- a/sys/dev/agp/agp_i810.c +++ b/sys/dev/agp/agp_i810.c @@ -1,2372 +1,2372 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Doug Rabson * Copyright (c) 2000 Ruslan Ermilov * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Konstantin Belousov * under sponsorship from the FreeBSD Foundation. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * Fixes for 830/845G support: David Dawes * 852GM/855GM/865G support added by David Dawes * * This is generic Intel GTT handling code, morphed from the AGP * bridge code. */ #include #if 0 #define KTR_AGP_I810 KTR_DEV #else #define KTR_AGP_I810 0 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DECLARE(M_AGP); struct agp_i810_match; static int agp_i810_check_active(device_t bridge_dev); static int agp_i830_check_active(device_t bridge_dev); static int agp_i915_check_active(device_t bridge_dev); static void agp_82852_set_desc(device_t dev, const struct agp_i810_match *match); static void agp_i810_set_desc(device_t dev, const struct agp_i810_match *match); static void agp_i810_dump_regs(device_t dev); static void agp_i830_dump_regs(device_t dev); static void agp_i855_dump_regs(device_t dev); static void agp_i915_dump_regs(device_t dev); static void agp_i965_dump_regs(device_t dev); static int agp_i810_get_stolen_size(device_t dev); static int agp_i830_get_stolen_size(device_t dev); static int agp_i915_get_stolen_size(device_t dev); static int agp_i810_get_gtt_mappable_entries(device_t dev); static int agp_i830_get_gtt_mappable_entries(device_t dev); static int agp_i915_get_gtt_mappable_entries(device_t dev); static int agp_i810_get_gtt_total_entries(device_t dev); static int agp_i965_get_gtt_total_entries(device_t dev); static int agp_gen5_get_gtt_total_entries(device_t dev); static int agp_i810_install_gatt(device_t dev); static int agp_i830_install_gatt(device_t dev); static int agp_i965_install_gatt(device_t dev); static int agp_g4x_install_gatt(device_t dev); static void agp_i810_deinstall_gatt(device_t dev); static void agp_i830_deinstall_gatt(device_t dev); static void agp_i810_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags); static void agp_i830_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags); static void agp_i915_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags); static void agp_i965_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags); static void agp_g4x_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags); static void agp_i810_write_gtt(device_t dev, u_int index, uint32_t pte); static void agp_i915_write_gtt(device_t dev, u_int index, uint32_t pte); static void agp_i965_write_gtt(device_t dev, u_int index, uint32_t pte); static void agp_g4x_write_gtt(device_t dev, u_int index, uint32_t pte); static u_int32_t agp_i810_read_gtt_pte(device_t dev, u_int index); static u_int32_t agp_i915_read_gtt_pte(device_t dev, u_int index); static u_int32_t agp_i965_read_gtt_pte(device_t dev, u_int index); static u_int32_t agp_g4x_read_gtt_pte(device_t dev, u_int index); static vm_paddr_t agp_i810_read_gtt_pte_paddr(device_t dev, u_int index); static vm_paddr_t agp_i915_read_gtt_pte_paddr(device_t dev, u_int index); static int agp_i810_set_aperture(device_t dev, u_int32_t aperture); static int agp_i830_set_aperture(device_t dev, u_int32_t aperture); static int agp_i915_set_aperture(device_t dev, u_int32_t aperture); static int agp_i810_chipset_flush_setup(device_t dev); static int agp_i915_chipset_flush_setup(device_t dev); static int agp_i965_chipset_flush_setup(device_t dev); static void agp_i810_chipset_flush_teardown(device_t dev); static void agp_i915_chipset_flush_teardown(device_t dev); static void agp_i965_chipset_flush_teardown(device_t dev); static void agp_i810_chipset_flush(device_t dev); static void agp_i830_chipset_flush(device_t dev); static void agp_i915_chipset_flush(device_t dev); enum { CHIP_I810, /* i810/i815 */ CHIP_I830, /* 830M/845G */ CHIP_I855, /* 852GM/855GM/865G */ CHIP_I915, /* 915G/915GM */ CHIP_I965, /* G965 */ CHIP_G33, /* G33/Q33/Q35 */ CHIP_IGD, /* Pineview */ CHIP_G4X, /* G45/Q45 */ }; /* The i810 through i855 have the registers at BAR 1, and the GATT gets * allocated by us. The i915 has registers in BAR 0 and the GATT is at the * start of the stolen memory, and should only be accessed by the OS through * BAR 3. The G965 has registers and GATT in the same BAR (0) -- first 512KB * is registers, second 512KB is GATT. */ static struct resource_spec agp_i810_res_spec[] = { { SYS_RES_MEMORY, AGP_I810_MMADR, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; static struct resource_spec agp_i915_res_spec[] = { { SYS_RES_MEMORY, AGP_I915_MMADR, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_MEMORY, AGP_I915_GTTADR, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; static struct resource_spec agp_i965_res_spec[] = { { SYS_RES_MEMORY, AGP_I965_GTTMMADR, RF_ACTIVE | RF_SHAREABLE }, { SYS_RES_MEMORY, AGP_I965_APBASE, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; struct agp_i810_softc { struct agp_softc agp; u_int32_t initial_aperture; /* aperture size at startup */ struct agp_gatt *gatt; u_int32_t dcache_size; /* i810 only */ u_int32_t stolen; /* number of i830/845 gtt entries for stolen memory */ u_int stolen_size; /* BIOS-reserved graphics memory */ u_int gtt_total_entries; /* Total number of gtt ptes */ u_int gtt_mappable_entries; /* Number of gtt ptes mappable by CPU */ device_t bdev; /* bridge device */ void *argb_cursor; /* contigmalloc area for ARGB cursor */ struct resource *sc_res[2]; const struct agp_i810_match *match; int sc_flush_page_rid; struct resource *sc_flush_page_res; void *sc_flush_page_vaddr; int sc_bios_allocated_flush_page; }; static device_t intel_agp; struct agp_i810_driver { int chiptype; int gen; int busdma_addr_mask_sz; struct resource_spec *res_spec; int (*check_active)(device_t); void (*set_desc)(device_t, const struct agp_i810_match *); void (*dump_regs)(device_t); int (*get_stolen_size)(device_t); int (*get_gtt_total_entries)(device_t); int (*get_gtt_mappable_entries)(device_t); int (*install_gatt)(device_t); void (*deinstall_gatt)(device_t); void (*write_gtt)(device_t, u_int, uint32_t); void (*install_gtt_pte)(device_t, u_int, vm_offset_t, int); u_int32_t (*read_gtt_pte)(device_t, u_int); vm_paddr_t (*read_gtt_pte_paddr)(device_t , u_int); int (*set_aperture)(device_t, u_int32_t); int (*chipset_flush_setup)(device_t); void (*chipset_flush_teardown)(device_t); void (*chipset_flush)(device_t); }; static struct { struct intel_gtt base; } intel_private; static const struct agp_i810_driver agp_i810_i810_driver = { .chiptype = CHIP_I810, .gen = 1, .busdma_addr_mask_sz = 32, .res_spec = agp_i810_res_spec, .check_active = agp_i810_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i810_dump_regs, .get_stolen_size = agp_i810_get_stolen_size, .get_gtt_mappable_entries = agp_i810_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i810_get_gtt_total_entries, .install_gatt = agp_i810_install_gatt, .deinstall_gatt = agp_i810_deinstall_gatt, .write_gtt = agp_i810_write_gtt, .install_gtt_pte = agp_i810_install_gtt_pte, .read_gtt_pte = agp_i810_read_gtt_pte, .read_gtt_pte_paddr = agp_i810_read_gtt_pte_paddr, .set_aperture = agp_i810_set_aperture, .chipset_flush_setup = agp_i810_chipset_flush_setup, .chipset_flush_teardown = agp_i810_chipset_flush_teardown, .chipset_flush = agp_i810_chipset_flush, }; static const struct agp_i810_driver agp_i810_i815_driver = { .chiptype = CHIP_I810, .gen = 2, .busdma_addr_mask_sz = 32, .res_spec = agp_i810_res_spec, .check_active = agp_i810_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i810_dump_regs, .get_stolen_size = agp_i810_get_stolen_size, .get_gtt_mappable_entries = agp_i830_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i810_get_gtt_total_entries, .install_gatt = agp_i810_install_gatt, .deinstall_gatt = agp_i810_deinstall_gatt, .write_gtt = agp_i810_write_gtt, .install_gtt_pte = agp_i810_install_gtt_pte, .read_gtt_pte = agp_i810_read_gtt_pte, .read_gtt_pte_paddr = agp_i810_read_gtt_pte_paddr, .set_aperture = agp_i810_set_aperture, .chipset_flush_setup = agp_i810_chipset_flush_setup, .chipset_flush_teardown = agp_i810_chipset_flush_teardown, .chipset_flush = agp_i830_chipset_flush, }; static const struct agp_i810_driver agp_i810_i830_driver = { .chiptype = CHIP_I830, .gen = 2, .busdma_addr_mask_sz = 32, .res_spec = agp_i810_res_spec, .check_active = agp_i830_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i830_dump_regs, .get_stolen_size = agp_i830_get_stolen_size, .get_gtt_mappable_entries = agp_i830_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i810_get_gtt_total_entries, .install_gatt = agp_i830_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_i810_write_gtt, .install_gtt_pte = agp_i830_install_gtt_pte, .read_gtt_pte = agp_i810_read_gtt_pte, .read_gtt_pte_paddr = agp_i810_read_gtt_pte_paddr, .set_aperture = agp_i830_set_aperture, .chipset_flush_setup = agp_i810_chipset_flush_setup, .chipset_flush_teardown = agp_i810_chipset_flush_teardown, .chipset_flush = agp_i830_chipset_flush, }; static const struct agp_i810_driver agp_i810_i855_driver = { .chiptype = CHIP_I855, .gen = 2, .busdma_addr_mask_sz = 32, .res_spec = agp_i810_res_spec, .check_active = agp_i830_check_active, .set_desc = agp_82852_set_desc, .dump_regs = agp_i855_dump_regs, .get_stolen_size = agp_i915_get_stolen_size, .get_gtt_mappable_entries = agp_i915_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i810_get_gtt_total_entries, .install_gatt = agp_i830_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_i810_write_gtt, .install_gtt_pte = agp_i830_install_gtt_pte, .read_gtt_pte = agp_i810_read_gtt_pte, .read_gtt_pte_paddr = agp_i810_read_gtt_pte_paddr, .set_aperture = agp_i830_set_aperture, .chipset_flush_setup = agp_i810_chipset_flush_setup, .chipset_flush_teardown = agp_i810_chipset_flush_teardown, .chipset_flush = agp_i830_chipset_flush, }; static const struct agp_i810_driver agp_i810_i865_driver = { .chiptype = CHIP_I855, .gen = 2, .busdma_addr_mask_sz = 32, .res_spec = agp_i810_res_spec, .check_active = agp_i830_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i855_dump_regs, .get_stolen_size = agp_i915_get_stolen_size, .get_gtt_mappable_entries = agp_i915_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i810_get_gtt_total_entries, .install_gatt = agp_i830_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_i810_write_gtt, .install_gtt_pte = agp_i830_install_gtt_pte, .read_gtt_pte = agp_i810_read_gtt_pte, .read_gtt_pte_paddr = agp_i810_read_gtt_pte_paddr, .set_aperture = agp_i915_set_aperture, .chipset_flush_setup = agp_i810_chipset_flush_setup, .chipset_flush_teardown = agp_i810_chipset_flush_teardown, .chipset_flush = agp_i830_chipset_flush, }; static const struct agp_i810_driver agp_i810_i915_driver = { .chiptype = CHIP_I915, .gen = 3, .busdma_addr_mask_sz = 32, .res_spec = agp_i915_res_spec, .check_active = agp_i915_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i915_dump_regs, .get_stolen_size = agp_i915_get_stolen_size, .get_gtt_mappable_entries = agp_i915_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i810_get_gtt_total_entries, .install_gatt = agp_i830_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_i915_write_gtt, .install_gtt_pte = agp_i915_install_gtt_pte, .read_gtt_pte = agp_i915_read_gtt_pte, .read_gtt_pte_paddr = agp_i915_read_gtt_pte_paddr, .set_aperture = agp_i915_set_aperture, .chipset_flush_setup = agp_i915_chipset_flush_setup, .chipset_flush_teardown = agp_i915_chipset_flush_teardown, .chipset_flush = agp_i915_chipset_flush, }; static const struct agp_i810_driver agp_i810_g33_driver = { .chiptype = CHIP_G33, .gen = 3, .busdma_addr_mask_sz = 36, .res_spec = agp_i915_res_spec, .check_active = agp_i915_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i965_dump_regs, .get_stolen_size = agp_i915_get_stolen_size, .get_gtt_mappable_entries = agp_i915_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i965_get_gtt_total_entries, .install_gatt = agp_i830_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_i915_write_gtt, .install_gtt_pte = agp_i915_install_gtt_pte, .read_gtt_pte = agp_i915_read_gtt_pte, .read_gtt_pte_paddr = agp_i915_read_gtt_pte_paddr, .set_aperture = agp_i915_set_aperture, .chipset_flush_setup = agp_i965_chipset_flush_setup, .chipset_flush_teardown = agp_i965_chipset_flush_teardown, .chipset_flush = agp_i915_chipset_flush, }; static const struct agp_i810_driver agp_i810_igd_driver = { .chiptype = CHIP_IGD, .gen = 3, .busdma_addr_mask_sz = 36, .res_spec = agp_i915_res_spec, .check_active = agp_i915_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i915_dump_regs, .get_stolen_size = agp_i915_get_stolen_size, .get_gtt_mappable_entries = agp_i915_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i965_get_gtt_total_entries, .install_gatt = agp_i830_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_i915_write_gtt, .install_gtt_pte = agp_i915_install_gtt_pte, .read_gtt_pte = agp_i915_read_gtt_pte, .read_gtt_pte_paddr = agp_i915_read_gtt_pte_paddr, .set_aperture = agp_i915_set_aperture, .chipset_flush_setup = agp_i965_chipset_flush_setup, .chipset_flush_teardown = agp_i965_chipset_flush_teardown, .chipset_flush = agp_i915_chipset_flush, }; static const struct agp_i810_driver agp_i810_g965_driver = { .chiptype = CHIP_I965, .gen = 4, .busdma_addr_mask_sz = 36, .res_spec = agp_i965_res_spec, .check_active = agp_i915_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i965_dump_regs, .get_stolen_size = agp_i915_get_stolen_size, .get_gtt_mappable_entries = agp_i915_get_gtt_mappable_entries, .get_gtt_total_entries = agp_i965_get_gtt_total_entries, .install_gatt = agp_i965_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_i965_write_gtt, .install_gtt_pte = agp_i965_install_gtt_pte, .read_gtt_pte = agp_i965_read_gtt_pte, .read_gtt_pte_paddr = agp_i915_read_gtt_pte_paddr, .set_aperture = agp_i915_set_aperture, .chipset_flush_setup = agp_i965_chipset_flush_setup, .chipset_flush_teardown = agp_i965_chipset_flush_teardown, .chipset_flush = agp_i915_chipset_flush, }; static const struct agp_i810_driver agp_i810_g4x_driver = { .chiptype = CHIP_G4X, .gen = 5, .busdma_addr_mask_sz = 36, .res_spec = agp_i965_res_spec, .check_active = agp_i915_check_active, .set_desc = agp_i810_set_desc, .dump_regs = agp_i965_dump_regs, .get_stolen_size = agp_i915_get_stolen_size, .get_gtt_mappable_entries = agp_i915_get_gtt_mappable_entries, .get_gtt_total_entries = agp_gen5_get_gtt_total_entries, .install_gatt = agp_g4x_install_gatt, .deinstall_gatt = agp_i830_deinstall_gatt, .write_gtt = agp_g4x_write_gtt, .install_gtt_pte = agp_g4x_install_gtt_pte, .read_gtt_pte = agp_g4x_read_gtt_pte, .read_gtt_pte_paddr = agp_i915_read_gtt_pte_paddr, .set_aperture = agp_i915_set_aperture, .chipset_flush_setup = agp_i965_chipset_flush_setup, .chipset_flush_teardown = agp_i965_chipset_flush_teardown, .chipset_flush = agp_i915_chipset_flush, }; /* For adding new devices, devid is the id of the graphics controller * (pci:0:2:0, for example). The placeholder (usually at pci:0:2:1) for the * second head should never be added. The bridge_offset is the offset to * subtract from devid to get the id of the hostb that the device is on. */ static const struct agp_i810_match { int devid; char *name; const struct agp_i810_driver *driver; } agp_i810_matches[] = { { .devid = 0x71218086, .name = "Intel 82810 (i810 GMCH) SVGA controller", .driver = &agp_i810_i810_driver }, { .devid = 0x71238086, .name = "Intel 82810-DC100 (i810-DC100 GMCH) SVGA controller", .driver = &agp_i810_i810_driver }, { .devid = 0x71258086, .name = "Intel 82810E (i810E GMCH) SVGA controller", .driver = &agp_i810_i810_driver }, { .devid = 0x11328086, .name = "Intel 82815 (i815 GMCH) SVGA controller", .driver = &agp_i810_i815_driver }, { .devid = 0x35778086, .name = "Intel 82830M (830M GMCH) SVGA controller", .driver = &agp_i810_i830_driver }, { .devid = 0x25628086, .name = "Intel 82845M (845M GMCH) SVGA controller", .driver = &agp_i810_i830_driver }, { .devid = 0x35828086, .name = "Intel 82852/855GM SVGA controller", .driver = &agp_i810_i855_driver }, { .devid = 0x25728086, .name = "Intel 82865G (865G GMCH) SVGA controller", .driver = &agp_i810_i865_driver }, { .devid = 0x25828086, .name = "Intel 82915G (915G GMCH) SVGA controller", .driver = &agp_i810_i915_driver }, { .devid = 0x258A8086, .name = "Intel E7221 SVGA controller", .driver = &agp_i810_i915_driver }, { .devid = 0x25928086, .name = "Intel 82915GM (915GM GMCH) SVGA controller", .driver = &agp_i810_i915_driver }, { .devid = 0x27728086, .name = "Intel 82945G (945G GMCH) SVGA controller", .driver = &agp_i810_i915_driver }, { .devid = 0x27A28086, .name = "Intel 82945GM (945GM GMCH) SVGA controller", .driver = &agp_i810_i915_driver }, { .devid = 0x27AE8086, .name = "Intel 945GME SVGA controller", .driver = &agp_i810_i915_driver }, { .devid = 0x29728086, .name = "Intel 946GZ SVGA controller", .driver = &agp_i810_g965_driver }, { .devid = 0x29828086, .name = "Intel G965 SVGA controller", .driver = &agp_i810_g965_driver }, { .devid = 0x29928086, .name = "Intel Q965 SVGA controller", .driver = &agp_i810_g965_driver }, { .devid = 0x29A28086, .name = "Intel G965 SVGA controller", .driver = &agp_i810_g965_driver }, { .devid = 0x29B28086, .name = "Intel Q35 SVGA controller", .driver = &agp_i810_g33_driver }, { .devid = 0x29C28086, .name = "Intel G33 SVGA controller", .driver = &agp_i810_g33_driver }, { .devid = 0x29D28086, .name = "Intel Q33 SVGA controller", .driver = &agp_i810_g33_driver }, { .devid = 0xA0018086, .name = "Intel Pineview SVGA controller", .driver = &agp_i810_igd_driver }, { .devid = 0xA0118086, .name = "Intel Pineview (M) SVGA controller", .driver = &agp_i810_igd_driver }, { .devid = 0x2A028086, .name = "Intel GM965 SVGA controller", .driver = &agp_i810_g965_driver }, { .devid = 0x2A128086, .name = "Intel GME965 SVGA controller", .driver = &agp_i810_g965_driver }, { .devid = 0x2A428086, .name = "Intel GM45 SVGA controller", .driver = &agp_i810_g4x_driver }, { .devid = 0x2E028086, .name = "Intel Eaglelake SVGA controller", .driver = &agp_i810_g4x_driver }, { .devid = 0x2E128086, .name = "Intel Q45 SVGA controller", .driver = &agp_i810_g4x_driver }, { .devid = 0x2E228086, .name = "Intel G45 SVGA controller", .driver = &agp_i810_g4x_driver }, { .devid = 0x2E328086, .name = "Intel G41 SVGA controller", .driver = &agp_i810_g4x_driver }, { .devid = 0x00428086, .name = "Intel Ironlake (D) SVGA controller", .driver = &agp_i810_g4x_driver }, { .devid = 0x00468086, .name = "Intel Ironlake (M) SVGA controller", .driver = &agp_i810_g4x_driver }, { .devid = 0, } }; static const struct agp_i810_match* agp_i810_match(device_t dev) { int i, devid; if (pci_get_class(dev) != PCIC_DISPLAY || (pci_get_subclass(dev) != PCIS_DISPLAY_VGA && pci_get_subclass(dev) != PCIS_DISPLAY_OTHER)) return (NULL); devid = pci_get_devid(dev); for (i = 0; agp_i810_matches[i].devid != 0; i++) { if (agp_i810_matches[i].devid == devid) break; } if (agp_i810_matches[i].devid == 0) return (NULL); else return (&agp_i810_matches[i]); } /* * Find bridge device. */ static device_t agp_i810_find_bridge(device_t dev) { return (pci_find_dbsf(0, 0, 0, 0)); } static void agp_i810_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "agp", -1) == NULL && + if (device_find_child(parent, "agp", DEVICE_UNIT_ANY) == NULL && agp_i810_match(parent)) device_add_child(parent, "agp", DEVICE_UNIT_ANY); } static int agp_i810_check_active(device_t bridge_dev) { u_int8_t smram; smram = pci_read_config(bridge_dev, AGP_I810_SMRAM, 1); if ((smram & AGP_I810_SMRAM_GMS) == AGP_I810_SMRAM_GMS_DISABLED) return (ENXIO); return (0); } static int agp_i830_check_active(device_t bridge_dev) { int gcc1; gcc1 = pci_read_config(bridge_dev, AGP_I830_GCC1, 1); if ((gcc1 & AGP_I830_GCC1_DEV2) == AGP_I830_GCC1_DEV2_DISABLED) return (ENXIO); return (0); } static int agp_i915_check_active(device_t bridge_dev) { int deven; deven = pci_read_config(bridge_dev, AGP_I915_DEVEN, 4); if ((deven & AGP_I915_DEVEN_D2F0) == AGP_I915_DEVEN_D2F0_DISABLED) return (ENXIO); return (0); } static void agp_82852_set_desc(device_t dev, const struct agp_i810_match *match) { switch (pci_read_config(dev, AGP_I85X_CAPID, 1)) { case AGP_I855_GME: device_set_desc(dev, "Intel 82855GME (855GME GMCH) SVGA controller"); break; case AGP_I855_GM: device_set_desc(dev, "Intel 82855GM (855GM GMCH) SVGA controller"); break; case AGP_I852_GME: device_set_desc(dev, "Intel 82852GME (852GME GMCH) SVGA controller"); break; case AGP_I852_GM: device_set_desc(dev, "Intel 82852GM (852GM GMCH) SVGA controller"); break; default: device_set_desc(dev, "Intel 8285xM (85xGM GMCH) SVGA controller"); break; } } static void agp_i810_set_desc(device_t dev, const struct agp_i810_match *match) { device_set_desc(dev, match->name); } static int agp_i810_probe(device_t dev) { device_t bdev; const struct agp_i810_match *match; int err; if (resource_disabled("agp", device_get_unit(dev))) return (ENXIO); match = agp_i810_match(dev); if (match == NULL) return (ENXIO); bdev = agp_i810_find_bridge(dev); if (bdev == NULL) { if (bootverbose) printf("I810: can't find bridge device\n"); return (ENXIO); } /* * checking whether internal graphics device has been activated. */ err = match->driver->check_active(bdev); if (err != 0) { if (bootverbose) printf("i810: disabled, not probing\n"); return (err); } match->driver->set_desc(dev, match); return (BUS_PROBE_DEFAULT); } static void agp_i810_dump_regs(device_t dev) { struct agp_i810_softc *sc = device_get_softc(dev); device_printf(dev, "AGP_I810_PGTBL_CTL: %08x\n", bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL)); device_printf(dev, "AGP_I810_MISCC: 0x%04x\n", pci_read_config(sc->bdev, AGP_I810_MISCC, 2)); } static void agp_i830_dump_regs(device_t dev) { struct agp_i810_softc *sc = device_get_softc(dev); device_printf(dev, "AGP_I810_PGTBL_CTL: %08x\n", bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL)); device_printf(dev, "AGP_I830_GCC1: 0x%02x\n", pci_read_config(sc->bdev, AGP_I830_GCC1, 1)); } static void agp_i855_dump_regs(device_t dev) { struct agp_i810_softc *sc = device_get_softc(dev); device_printf(dev, "AGP_I810_PGTBL_CTL: %08x\n", bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL)); device_printf(dev, "AGP_I855_GCC1: 0x%02x\n", pci_read_config(sc->bdev, AGP_I855_GCC1, 1)); } static void agp_i915_dump_regs(device_t dev) { struct agp_i810_softc *sc = device_get_softc(dev); device_printf(dev, "AGP_I810_PGTBL_CTL: %08x\n", bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL)); device_printf(dev, "AGP_I855_GCC1: 0x%02x\n", pci_read_config(sc->bdev, AGP_I855_GCC1, 1)); device_printf(dev, "AGP_I915_MSAC: 0x%02x\n", pci_read_config(sc->bdev, AGP_I915_MSAC, 1)); } static void agp_i965_dump_regs(device_t dev) { struct agp_i810_softc *sc = device_get_softc(dev); device_printf(dev, "AGP_I965_PGTBL_CTL2: %08x\n", bus_read_4(sc->sc_res[0], AGP_I965_PGTBL_CTL2)); device_printf(dev, "AGP_I855_GCC1: 0x%02x\n", pci_read_config(sc->bdev, AGP_I855_GCC1, 1)); device_printf(dev, "AGP_I965_MSAC: 0x%02x\n", pci_read_config(sc->bdev, AGP_I965_MSAC, 1)); } static int agp_i810_get_stolen_size(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); sc->stolen = 0; sc->stolen_size = 0; return (0); } static int agp_i830_get_stolen_size(device_t dev) { struct agp_i810_softc *sc; unsigned int gcc1; sc = device_get_softc(dev); gcc1 = pci_read_config(sc->bdev, AGP_I830_GCC1, 1); switch (gcc1 & AGP_I830_GCC1_GMS) { case AGP_I830_GCC1_GMS_STOLEN_512: sc->stolen = (512 - 132) * 1024 / 4096; sc->stolen_size = 512 * 1024; break; case AGP_I830_GCC1_GMS_STOLEN_1024: sc->stolen = (1024 - 132) * 1024 / 4096; sc->stolen_size = 1024 * 1024; break; case AGP_I830_GCC1_GMS_STOLEN_8192: sc->stolen = (8192 - 132) * 1024 / 4096; sc->stolen_size = 8192 * 1024; break; default: sc->stolen = 0; device_printf(dev, "unknown memory configuration, disabling (GCC1 %x)\n", gcc1); return (EINVAL); } return (0); } static int agp_i915_get_stolen_size(device_t dev) { struct agp_i810_softc *sc; unsigned int gcc1, stolen, gtt_size; sc = device_get_softc(dev); /* * Stolen memory is set up at the beginning of the aperture by * the BIOS, consisting of the GATT followed by 4kb for the * BIOS display. */ switch (sc->match->driver->chiptype) { case CHIP_I855: gtt_size = 128; break; case CHIP_I915: gtt_size = 256; break; case CHIP_I965: switch (bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL) & AGP_I810_PGTBL_SIZE_MASK) { case AGP_I810_PGTBL_SIZE_128KB: gtt_size = 128; break; case AGP_I810_PGTBL_SIZE_256KB: gtt_size = 256; break; case AGP_I810_PGTBL_SIZE_512KB: gtt_size = 512; break; case AGP_I965_PGTBL_SIZE_1MB: gtt_size = 1024; break; case AGP_I965_PGTBL_SIZE_2MB: gtt_size = 2048; break; case AGP_I965_PGTBL_SIZE_1_5MB: gtt_size = 1024 + 512; break; default: device_printf(dev, "Bad PGTBL size\n"); return (EINVAL); } break; case CHIP_G33: gcc1 = pci_read_config(sc->bdev, AGP_I855_GCC1, 2); switch (gcc1 & AGP_G33_MGGC_GGMS_MASK) { case AGP_G33_MGGC_GGMS_SIZE_1M: gtt_size = 1024; break; case AGP_G33_MGGC_GGMS_SIZE_2M: gtt_size = 2048; break; default: device_printf(dev, "Bad PGTBL size\n"); return (EINVAL); } break; case CHIP_IGD: case CHIP_G4X: gtt_size = 0; break; default: device_printf(dev, "Bad chiptype\n"); return (EINVAL); } /* GCC1 is called MGGC on i915+ */ gcc1 = pci_read_config(sc->bdev, AGP_I855_GCC1, 1); switch (gcc1 & AGP_I855_GCC1_GMS) { case AGP_I855_GCC1_GMS_STOLEN_1M: stolen = 1024; break; case AGP_I855_GCC1_GMS_STOLEN_4M: stolen = 4 * 1024; break; case AGP_I855_GCC1_GMS_STOLEN_8M: stolen = 8 * 1024; break; case AGP_I855_GCC1_GMS_STOLEN_16M: stolen = 16 * 1024; break; case AGP_I855_GCC1_GMS_STOLEN_32M: stolen = 32 * 1024; break; case AGP_I915_GCC1_GMS_STOLEN_48M: stolen = sc->match->driver->gen > 2 ? 48 * 1024 : 0; break; case AGP_I915_GCC1_GMS_STOLEN_64M: stolen = sc->match->driver->gen > 2 ? 64 * 1024 : 0; break; case AGP_G33_GCC1_GMS_STOLEN_128M: stolen = sc->match->driver->gen > 2 ? 128 * 1024 : 0; break; case AGP_G33_GCC1_GMS_STOLEN_256M: stolen = sc->match->driver->gen > 2 ? 256 * 1024 : 0; break; case AGP_G4X_GCC1_GMS_STOLEN_96M: if (sc->match->driver->chiptype == CHIP_I965 || sc->match->driver->chiptype == CHIP_G4X) stolen = 96 * 1024; else stolen = 0; break; case AGP_G4X_GCC1_GMS_STOLEN_160M: if (sc->match->driver->chiptype == CHIP_I965 || sc->match->driver->chiptype == CHIP_G4X) stolen = 160 * 1024; else stolen = 0; break; case AGP_G4X_GCC1_GMS_STOLEN_224M: if (sc->match->driver->chiptype == CHIP_I965 || sc->match->driver->chiptype == CHIP_G4X) stolen = 224 * 1024; else stolen = 0; break; case AGP_G4X_GCC1_GMS_STOLEN_352M: if (sc->match->driver->chiptype == CHIP_I965 || sc->match->driver->chiptype == CHIP_G4X) stolen = 352 * 1024; else stolen = 0; break; default: device_printf(dev, "unknown memory configuration, disabling (GCC1 %x)\n", gcc1); return (EINVAL); } gtt_size += 4; sc->stolen_size = stolen * 1024; sc->stolen = (stolen - gtt_size) * 1024 / 4096; return (0); } static int agp_i810_get_gtt_mappable_entries(device_t dev) { struct agp_i810_softc *sc; uint32_t ap; uint16_t miscc; sc = device_get_softc(dev); miscc = pci_read_config(sc->bdev, AGP_I810_MISCC, 2); if ((miscc & AGP_I810_MISCC_WINSIZE) == AGP_I810_MISCC_WINSIZE_32) ap = 32; else ap = 64; sc->gtt_mappable_entries = (ap * 1024 * 1024) >> AGP_PAGE_SHIFT; return (0); } static int agp_i830_get_gtt_mappable_entries(device_t dev) { struct agp_i810_softc *sc; uint32_t ap; uint16_t gmch_ctl; sc = device_get_softc(dev); gmch_ctl = pci_read_config(sc->bdev, AGP_I830_GCC1, 2); if ((gmch_ctl & AGP_I830_GCC1_GMASIZE) == AGP_I830_GCC1_GMASIZE_64) ap = 64; else ap = 128; sc->gtt_mappable_entries = (ap * 1024 * 1024) >> AGP_PAGE_SHIFT; return (0); } static int agp_i915_get_gtt_mappable_entries(device_t dev) { struct agp_i810_softc *sc; uint32_t ap; sc = device_get_softc(dev); ap = AGP_GET_APERTURE(dev); sc->gtt_mappable_entries = ap >> AGP_PAGE_SHIFT; return (0); } static int agp_i810_get_gtt_total_entries(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); sc->gtt_total_entries = sc->gtt_mappable_entries; return (0); } static int agp_i965_get_gtt_total_entries(device_t dev) { struct agp_i810_softc *sc; uint32_t pgetbl_ctl; int error; sc = device_get_softc(dev); error = 0; pgetbl_ctl = bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL); switch (pgetbl_ctl & AGP_I810_PGTBL_SIZE_MASK) { case AGP_I810_PGTBL_SIZE_128KB: sc->gtt_total_entries = 128 * 1024 / 4; break; case AGP_I810_PGTBL_SIZE_256KB: sc->gtt_total_entries = 256 * 1024 / 4; break; case AGP_I810_PGTBL_SIZE_512KB: sc->gtt_total_entries = 512 * 1024 / 4; break; /* GTT pagetable sizes bigger than 512KB are not possible on G33! */ case AGP_I810_PGTBL_SIZE_1MB: sc->gtt_total_entries = 1024 * 1024 / 4; break; case AGP_I810_PGTBL_SIZE_2MB: sc->gtt_total_entries = 2 * 1024 * 1024 / 4; break; case AGP_I810_PGTBL_SIZE_1_5MB: sc->gtt_total_entries = (1024 + 512) * 1024 / 4; break; default: device_printf(dev, "Unknown page table size\n"); error = ENXIO; } return (error); } static void agp_gen5_adjust_pgtbl_size(device_t dev, uint32_t sz) { struct agp_i810_softc *sc; uint32_t pgetbl_ctl, pgetbl_ctl2; sc = device_get_softc(dev); /* Disable per-process page table. */ pgetbl_ctl2 = bus_read_4(sc->sc_res[0], AGP_I965_PGTBL_CTL2); pgetbl_ctl2 &= ~AGP_I810_PGTBL_ENABLED; bus_write_4(sc->sc_res[0], AGP_I965_PGTBL_CTL2, pgetbl_ctl2); /* Write the new ggtt size. */ pgetbl_ctl = bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL); pgetbl_ctl &= ~AGP_I810_PGTBL_SIZE_MASK; pgetbl_ctl |= sz; bus_write_4(sc->sc_res[0], AGP_I810_PGTBL_CTL, pgetbl_ctl); } static int agp_gen5_get_gtt_total_entries(device_t dev) { struct agp_i810_softc *sc; uint16_t gcc1; sc = device_get_softc(dev); gcc1 = pci_read_config(sc->bdev, AGP_I830_GCC1, 2); switch (gcc1 & AGP_G4x_GCC1_SIZE_MASK) { case AGP_G4x_GCC1_SIZE_1M: case AGP_G4x_GCC1_SIZE_VT_1M: agp_gen5_adjust_pgtbl_size(dev, AGP_I810_PGTBL_SIZE_1MB); break; case AGP_G4x_GCC1_SIZE_VT_1_5M: agp_gen5_adjust_pgtbl_size(dev, AGP_I810_PGTBL_SIZE_1_5MB); break; case AGP_G4x_GCC1_SIZE_2M: case AGP_G4x_GCC1_SIZE_VT_2M: agp_gen5_adjust_pgtbl_size(dev, AGP_I810_PGTBL_SIZE_2MB); break; default: device_printf(dev, "Unknown page table size\n"); return (ENXIO); } return (agp_i965_get_gtt_total_entries(dev)); } static int agp_i810_install_gatt(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); /* Some i810s have on-chip memory called dcache. */ if ((bus_read_1(sc->sc_res[0], AGP_I810_DRT) & AGP_I810_DRT_POPULATED) != 0) sc->dcache_size = 4 * 1024 * 1024; else sc->dcache_size = 0; /* According to the specs the gatt on the i810 must be 64k. */ sc->gatt->ag_virtual = kmem_alloc_contig(64 * 1024, M_NOWAIT | M_ZERO, 0, ~0, PAGE_SIZE, 0, VM_MEMATTR_WRITE_COMBINING); if (sc->gatt->ag_virtual == NULL) { if (bootverbose) device_printf(dev, "contiguous allocation failed\n"); return (ENOMEM); } sc->gatt->ag_physical = vtophys((vm_offset_t)sc->gatt->ag_virtual); /* Install the GATT. */ bus_write_4(sc->sc_res[0], AGP_I810_PGTBL_CTL, sc->gatt->ag_physical | 1); return (0); } static void agp_i830_install_gatt_init(struct agp_i810_softc *sc) { uint32_t pgtblctl; /* * The i830 automatically initializes the 128k gatt on boot. * GATT address is already in there, make sure it's enabled. */ pgtblctl = bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL); pgtblctl |= 1; bus_write_4(sc->sc_res[0], AGP_I810_PGTBL_CTL, pgtblctl); sc->gatt->ag_physical = pgtblctl & ~1; } static int agp_i830_install_gatt(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); agp_i830_install_gatt_init(sc); return (0); } static int agp_gen4_install_gatt(device_t dev, const vm_size_t gtt_offset) { struct agp_i810_softc *sc; sc = device_get_softc(dev); pmap_change_attr((vm_offset_t)rman_get_virtual(sc->sc_res[0]) + gtt_offset, rman_get_size(sc->sc_res[0]) - gtt_offset, VM_MEMATTR_WRITE_COMBINING); agp_i830_install_gatt_init(sc); return (0); } static int agp_i965_install_gatt(device_t dev) { return (agp_gen4_install_gatt(dev, 512 * 1024)); } static int agp_g4x_install_gatt(device_t dev) { return (agp_gen4_install_gatt(dev, 2 * 1024 * 1024)); } static int agp_i810_attach(device_t dev) { struct agp_i810_softc *sc; int error; sc = device_get_softc(dev); sc->bdev = agp_i810_find_bridge(dev); if (sc->bdev == NULL) return (ENOENT); sc->match = agp_i810_match(dev); agp_set_aperture_resource(dev, sc->match->driver->gen <= 2 ? AGP_APBASE : AGP_I915_GMADR); error = agp_generic_attach(dev); if (error) return (error); if (ptoa((vm_paddr_t)Maxmem) > (1ULL << sc->match->driver->busdma_addr_mask_sz) - 1) { device_printf(dev, "agp_i810 does not support physical " "memory above %ju.\n", (uintmax_t)(1ULL << sc->match->driver->busdma_addr_mask_sz) - 1); return (ENOENT); } if (bus_alloc_resources(dev, sc->match->driver->res_spec, sc->sc_res)) { agp_generic_detach(dev); return (ENODEV); } sc->initial_aperture = AGP_GET_APERTURE(dev); sc->gatt = malloc(sizeof(struct agp_gatt), M_AGP, M_WAITOK); sc->gatt->ag_entries = AGP_GET_APERTURE(dev) >> AGP_PAGE_SHIFT; if ((error = sc->match->driver->get_stolen_size(dev)) != 0 || (error = sc->match->driver->install_gatt(dev)) != 0 || (error = sc->match->driver->get_gtt_mappable_entries(dev)) != 0 || (error = sc->match->driver->get_gtt_total_entries(dev)) != 0 || (error = sc->match->driver->chipset_flush_setup(dev)) != 0) { bus_release_resources(dev, sc->match->driver->res_spec, sc->sc_res); free(sc->gatt, M_AGP); agp_generic_detach(dev); return (error); } intel_agp = dev; device_printf(dev, "aperture size is %dM", sc->initial_aperture / 1024 / 1024); if (sc->stolen > 0) printf(", detected %dk stolen memory\n", sc->stolen * 4); else printf("\n"); if (bootverbose) { sc->match->driver->dump_regs(dev); device_printf(dev, "Mappable GTT entries: %d\n", sc->gtt_mappable_entries); device_printf(dev, "Total GTT entries: %d\n", sc->gtt_total_entries); } return (0); } static void agp_i810_deinstall_gatt(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); bus_write_4(sc->sc_res[0], AGP_I810_PGTBL_CTL, 0); kmem_free(sc->gatt->ag_virtual, 64 * 1024); } static void agp_i830_deinstall_gatt(device_t dev) { struct agp_i810_softc *sc; unsigned int pgtblctl; sc = device_get_softc(dev); pgtblctl = bus_read_4(sc->sc_res[0], AGP_I810_PGTBL_CTL); pgtblctl &= ~1; bus_write_4(sc->sc_res[0], AGP_I810_PGTBL_CTL, pgtblctl); } static int agp_i810_detach(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); agp_free_cdev(dev); /* Clear the GATT base. */ sc->match->driver->deinstall_gatt(dev); sc->match->driver->chipset_flush_teardown(dev); /* Put the aperture back the way it started. */ AGP_SET_APERTURE(dev, sc->initial_aperture); free(sc->gatt, M_AGP); bus_release_resources(dev, sc->match->driver->res_spec, sc->sc_res); agp_free_res(dev); return (0); } static int agp_i810_resume(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); AGP_SET_APERTURE(dev, sc->initial_aperture); /* Install the GATT. */ bus_write_4(sc->sc_res[0], AGP_I810_PGTBL_CTL, sc->gatt->ag_physical | 1); return (bus_generic_resume(dev)); } /** * Sets the PCI resource size of the aperture on i830-class and below chipsets, * while returning failure on later chipsets when an actual change is * requested. * * This whole function is likely bogus, as the kernel would probably need to * reconfigure the placement of the AGP aperture if a larger size is requested, * which doesn't happen currently. */ static int agp_i810_set_aperture(device_t dev, u_int32_t aperture) { struct agp_i810_softc *sc; u_int16_t miscc; sc = device_get_softc(dev); /* * Double check for sanity. */ if (aperture != 32 * 1024 * 1024 && aperture != 64 * 1024 * 1024) { device_printf(dev, "bad aperture size %d\n", aperture); return (EINVAL); } miscc = pci_read_config(sc->bdev, AGP_I810_MISCC, 2); miscc &= ~AGP_I810_MISCC_WINSIZE; if (aperture == 32 * 1024 * 1024) miscc |= AGP_I810_MISCC_WINSIZE_32; else miscc |= AGP_I810_MISCC_WINSIZE_64; pci_write_config(sc->bdev, AGP_I810_MISCC, miscc, 2); return (0); } static int agp_i830_set_aperture(device_t dev, u_int32_t aperture) { struct agp_i810_softc *sc; u_int16_t gcc1; sc = device_get_softc(dev); if (aperture != 64 * 1024 * 1024 && aperture != 128 * 1024 * 1024) { device_printf(dev, "bad aperture size %d\n", aperture); return (EINVAL); } gcc1 = pci_read_config(sc->bdev, AGP_I830_GCC1, 2); gcc1 &= ~AGP_I830_GCC1_GMASIZE; if (aperture == 64 * 1024 * 1024) gcc1 |= AGP_I830_GCC1_GMASIZE_64; else gcc1 |= AGP_I830_GCC1_GMASIZE_128; pci_write_config(sc->bdev, AGP_I830_GCC1, gcc1, 2); return (0); } static int agp_i915_set_aperture(device_t dev, u_int32_t aperture) { return (agp_generic_set_aperture(dev, aperture)); } static int agp_i810_method_set_aperture(device_t dev, u_int32_t aperture) { struct agp_i810_softc *sc; sc = device_get_softc(dev); return (sc->match->driver->set_aperture(dev, aperture)); } /** * Writes a GTT entry mapping the page at the given offset from the * beginning of the aperture to the given physical address. Setup the * caching mode according to flags. * * For gen 1, 2 and 3, GTT start is located at AGP_I810_GTT offset * from corresponding BAR start. For gen 4, offset is 512KB + * AGP_I810_GTT, for gen 5 and 6 it is 2MB + AGP_I810_GTT. * * Also, the bits of the physical page address above 4GB needs to be * placed into bits 40-32 of PTE. */ static void agp_i810_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags) { uint32_t pte; pte = (u_int32_t)physical | I810_PTE_VALID; if (flags == AGP_DCACHE_MEMORY) pte |= I810_PTE_LOCAL; else if (flags == AGP_USER_CACHED_MEMORY) pte |= I830_PTE_SYSTEM_CACHED; agp_i810_write_gtt(dev, index, pte); } static void agp_i810_write_gtt(device_t dev, u_int index, uint32_t pte) { struct agp_i810_softc *sc; sc = device_get_softc(dev); bus_write_4(sc->sc_res[0], AGP_I810_GTT + index * 4, pte); CTR2(KTR_AGP_I810, "810_pte %x %x", index, pte); } static void agp_i830_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags) { uint32_t pte; pte = (u_int32_t)physical | I810_PTE_VALID; if (flags == AGP_USER_CACHED_MEMORY) pte |= I830_PTE_SYSTEM_CACHED; agp_i810_write_gtt(dev, index, pte); } static void agp_i915_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags) { uint32_t pte; pte = (u_int32_t)physical | I810_PTE_VALID; if (flags == AGP_USER_CACHED_MEMORY) pte |= I830_PTE_SYSTEM_CACHED; pte |= (physical & 0x0000000f00000000ull) >> 28; agp_i915_write_gtt(dev, index, pte); } static void agp_i915_write_gtt(device_t dev, u_int index, uint32_t pte) { struct agp_i810_softc *sc; sc = device_get_softc(dev); bus_write_4(sc->sc_res[1], index * 4, pte); CTR2(KTR_AGP_I810, "915_pte %x %x", index, pte); } static void agp_i965_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags) { uint32_t pte; pte = (u_int32_t)physical | I810_PTE_VALID; if (flags == AGP_USER_CACHED_MEMORY) pte |= I830_PTE_SYSTEM_CACHED; pte |= (physical & 0x0000000f00000000ull) >> 28; agp_i965_write_gtt(dev, index, pte); } static void agp_i965_write_gtt(device_t dev, u_int index, uint32_t pte) { struct agp_i810_softc *sc; sc = device_get_softc(dev); bus_write_4(sc->sc_res[0], index * 4 + (512 * 1024), pte); CTR2(KTR_AGP_I810, "965_pte %x %x", index, pte); } static void agp_g4x_install_gtt_pte(device_t dev, u_int index, vm_offset_t physical, int flags) { uint32_t pte; pte = (u_int32_t)physical | I810_PTE_VALID; if (flags == AGP_USER_CACHED_MEMORY) pte |= I830_PTE_SYSTEM_CACHED; pte |= (physical & 0x0000000f00000000ull) >> 28; agp_g4x_write_gtt(dev, index, pte); } static void agp_g4x_write_gtt(device_t dev, u_int index, uint32_t pte) { struct agp_i810_softc *sc; sc = device_get_softc(dev); bus_write_4(sc->sc_res[0], index * 4 + (2 * 1024 * 1024), pte); CTR2(KTR_AGP_I810, "g4x_pte %x %x", index, pte); } static int agp_i810_bind_page(device_t dev, vm_offset_t offset, vm_offset_t physical) { struct agp_i810_softc *sc = device_get_softc(dev); u_int index; if (offset >= (sc->gatt->ag_entries << AGP_PAGE_SHIFT)) { device_printf(dev, "failed: offset is 0x%08jx, " "shift is %d, entries is %d\n", (intmax_t)offset, AGP_PAGE_SHIFT, sc->gatt->ag_entries); return (EINVAL); } index = offset >> AGP_PAGE_SHIFT; if (sc->stolen != 0 && index < sc->stolen) { device_printf(dev, "trying to bind into stolen memory\n"); return (EINVAL); } sc->match->driver->install_gtt_pte(dev, index, physical, 0); return (0); } static int agp_i810_unbind_page(device_t dev, vm_offset_t offset) { struct agp_i810_softc *sc; u_int index; sc = device_get_softc(dev); if (offset >= (sc->gatt->ag_entries << AGP_PAGE_SHIFT)) return (EINVAL); index = offset >> AGP_PAGE_SHIFT; if (sc->stolen != 0 && index < sc->stolen) { device_printf(dev, "trying to unbind from stolen memory\n"); return (EINVAL); } sc->match->driver->install_gtt_pte(dev, index, 0, 0); return (0); } static u_int32_t agp_i810_read_gtt_pte(device_t dev, u_int index) { struct agp_i810_softc *sc; u_int32_t pte; sc = device_get_softc(dev); pte = bus_read_4(sc->sc_res[0], AGP_I810_GTT + index * 4); return (pte); } static u_int32_t agp_i915_read_gtt_pte(device_t dev, u_int index) { struct agp_i810_softc *sc; u_int32_t pte; sc = device_get_softc(dev); pte = bus_read_4(sc->sc_res[1], index * 4); return (pte); } static u_int32_t agp_i965_read_gtt_pte(device_t dev, u_int index) { struct agp_i810_softc *sc; u_int32_t pte; sc = device_get_softc(dev); pte = bus_read_4(sc->sc_res[0], index * 4 + (512 * 1024)); return (pte); } static u_int32_t agp_g4x_read_gtt_pte(device_t dev, u_int index) { struct agp_i810_softc *sc; u_int32_t pte; sc = device_get_softc(dev); pte = bus_read_4(sc->sc_res[0], index * 4 + (2 * 1024 * 1024)); return (pte); } static vm_paddr_t agp_i810_read_gtt_pte_paddr(device_t dev, u_int index) { struct agp_i810_softc *sc; u_int32_t pte; vm_paddr_t res; sc = device_get_softc(dev); pte = sc->match->driver->read_gtt_pte(dev, index); res = pte & ~PAGE_MASK; return (res); } static vm_paddr_t agp_i915_read_gtt_pte_paddr(device_t dev, u_int index) { struct agp_i810_softc *sc; u_int32_t pte; vm_paddr_t res; sc = device_get_softc(dev); pte = sc->match->driver->read_gtt_pte(dev, index); res = (pte & ~PAGE_MASK) | ((pte & 0xf0) << 28); return (res); } /* * Writing via memory mapped registers already flushes all TLBs. */ static void agp_i810_flush_tlb(device_t dev) { } static int agp_i810_enable(device_t dev, u_int32_t mode) { return (0); } static struct agp_memory * agp_i810_alloc_memory(device_t dev, int type, vm_size_t size) { struct agp_i810_softc *sc; struct agp_memory *mem; vm_page_t m; sc = device_get_softc(dev); if ((size & (AGP_PAGE_SIZE - 1)) != 0 || sc->agp.as_allocated + size > sc->agp.as_maxmem) return (0); if (type == 1) { /* * Mapping local DRAM into GATT. */ if (sc->match->driver->chiptype != CHIP_I810) return (0); if (size != sc->dcache_size) return (0); } else if (type == 2) { /* * Type 2 is the contiguous physical memory type, that hands * back a physical address. This is used for cursors on i810. * Hand back as many single pages with physical as the user * wants, but only allow one larger allocation (ARGB cursor) * for simplicity. */ if (size != AGP_PAGE_SIZE) { if (sc->argb_cursor != NULL) return (0); /* Allocate memory for ARGB cursor, if we can. */ sc->argb_cursor = contigmalloc(size, M_AGP, 0, 0, ~0, PAGE_SIZE, 0); if (sc->argb_cursor == NULL) return (0); } } mem = malloc(sizeof *mem, M_AGP, M_WAITOK); mem->am_id = sc->agp.as_nextid++; mem->am_size = size; mem->am_type = type; if (type != 1 && (type != 2 || size == AGP_PAGE_SIZE)) mem->am_obj = vm_object_allocate(OBJT_SWAP, atop(round_page(size))); else mem->am_obj = 0; if (type == 2) { if (size == AGP_PAGE_SIZE) { /* * Allocate and wire down the page now so that we can * get its physical address. */ VM_OBJECT_WLOCK(mem->am_obj); m = vm_page_grab(mem->am_obj, 0, VM_ALLOC_NOBUSY | VM_ALLOC_WIRED | VM_ALLOC_ZERO); VM_OBJECT_WUNLOCK(mem->am_obj); mem->am_physical = VM_PAGE_TO_PHYS(m); } else { /* Our allocation is already nicely wired down for us. * Just grab the physical address. */ mem->am_physical = vtophys(sc->argb_cursor); } } else mem->am_physical = 0; mem->am_offset = 0; mem->am_is_bound = 0; TAILQ_INSERT_TAIL(&sc->agp.as_memory, mem, am_link); sc->agp.as_allocated += size; return (mem); } static int agp_i810_free_memory(device_t dev, struct agp_memory *mem) { struct agp_i810_softc *sc; vm_page_t m; if (mem->am_is_bound) return (EBUSY); sc = device_get_softc(dev); if (mem->am_type == 2) { if (mem->am_size == AGP_PAGE_SIZE) { /* * Unwire the page which we wired in alloc_memory. */ VM_OBJECT_WLOCK(mem->am_obj); m = vm_page_lookup(mem->am_obj, 0); vm_page_unwire(m, PQ_INACTIVE); VM_OBJECT_WUNLOCK(mem->am_obj); } else { free(sc->argb_cursor, M_AGP); sc->argb_cursor = NULL; } } sc->agp.as_allocated -= mem->am_size; TAILQ_REMOVE(&sc->agp.as_memory, mem, am_link); if (mem->am_obj) vm_object_deallocate(mem->am_obj); free(mem, M_AGP); return (0); } static int agp_i810_bind_memory(device_t dev, struct agp_memory *mem, vm_offset_t offset) { struct agp_i810_softc *sc; vm_offset_t i; /* Do some sanity checks first. */ if ((offset & (AGP_PAGE_SIZE - 1)) != 0 || offset + mem->am_size > AGP_GET_APERTURE(dev)) { device_printf(dev, "binding memory at bad offset %#x\n", (int)offset); return (EINVAL); } sc = device_get_softc(dev); if (mem->am_type == 2 && mem->am_size != AGP_PAGE_SIZE) { mtx_lock(&sc->agp.as_lock); if (mem->am_is_bound) { mtx_unlock(&sc->agp.as_lock); return (EINVAL); } /* The memory's already wired down, just stick it in the GTT. */ for (i = 0; i < mem->am_size; i += AGP_PAGE_SIZE) { sc->match->driver->install_gtt_pte(dev, (offset + i) >> AGP_PAGE_SHIFT, mem->am_physical + i, 0); } mem->am_offset = offset; mem->am_is_bound = 1; mtx_unlock(&sc->agp.as_lock); return (0); } if (mem->am_type != 1) return (agp_generic_bind_memory(dev, mem, offset)); /* * Mapping local DRAM into GATT. */ if (sc->match->driver->chiptype != CHIP_I810) return (EINVAL); for (i = 0; i < mem->am_size; i += AGP_PAGE_SIZE) bus_write_4(sc->sc_res[0], AGP_I810_GTT + (i >> AGP_PAGE_SHIFT) * 4, i | 3); return (0); } static int agp_i810_unbind_memory(device_t dev, struct agp_memory *mem) { struct agp_i810_softc *sc; vm_offset_t i; sc = device_get_softc(dev); if (mem->am_type == 2 && mem->am_size != AGP_PAGE_SIZE) { mtx_lock(&sc->agp.as_lock); if (!mem->am_is_bound) { mtx_unlock(&sc->agp.as_lock); return (EINVAL); } for (i = 0; i < mem->am_size; i += AGP_PAGE_SIZE) { sc->match->driver->install_gtt_pte(dev, (mem->am_offset + i) >> AGP_PAGE_SHIFT, 0, 0); } mem->am_is_bound = 0; mtx_unlock(&sc->agp.as_lock); return (0); } if (mem->am_type != 1) return (agp_generic_unbind_memory(dev, mem)); if (sc->match->driver->chiptype != CHIP_I810) return (EINVAL); for (i = 0; i < mem->am_size; i += AGP_PAGE_SIZE) { sc->match->driver->install_gtt_pte(dev, i >> AGP_PAGE_SHIFT, 0, 0); } return (0); } static device_method_t agp_i810_methods[] = { /* Device interface */ DEVMETHOD(device_identify, agp_i810_identify), DEVMETHOD(device_probe, agp_i810_probe), DEVMETHOD(device_attach, agp_i810_attach), DEVMETHOD(device_detach, agp_i810_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, agp_i810_resume), /* AGP interface */ DEVMETHOD(agp_get_aperture, agp_generic_get_aperture), DEVMETHOD(agp_set_aperture, agp_i810_method_set_aperture), DEVMETHOD(agp_bind_page, agp_i810_bind_page), DEVMETHOD(agp_unbind_page, agp_i810_unbind_page), DEVMETHOD(agp_flush_tlb, agp_i810_flush_tlb), DEVMETHOD(agp_enable, agp_i810_enable), DEVMETHOD(agp_alloc_memory, agp_i810_alloc_memory), DEVMETHOD(agp_free_memory, agp_i810_free_memory), DEVMETHOD(agp_bind_memory, agp_i810_bind_memory), DEVMETHOD(agp_unbind_memory, agp_i810_unbind_memory), DEVMETHOD(agp_chipset_flush, agp_intel_gtt_chipset_flush), { 0, 0 } }; static driver_t agp_i810_driver = { "agp", agp_i810_methods, sizeof(struct agp_i810_softc), }; DRIVER_MODULE(agp_i810, vgapci, agp_i810_driver, 0, 0); MODULE_DEPEND(agp_i810, agp, 1, 1, 1); MODULE_DEPEND(agp_i810, pci, 1, 1, 1); void agp_intel_gtt_clear_range(device_t dev, u_int first_entry, u_int num_entries) { struct agp_i810_softc *sc; u_int i; sc = device_get_softc(dev); for (i = 0; i < num_entries; i++) sc->match->driver->install_gtt_pte(dev, first_entry + i, VM_PAGE_TO_PHYS(bogus_page), 0); sc->match->driver->read_gtt_pte(dev, first_entry + num_entries - 1); } void agp_intel_gtt_insert_pages(device_t dev, u_int first_entry, u_int num_entries, vm_page_t *pages, u_int flags) { struct agp_i810_softc *sc; u_int i; sc = device_get_softc(dev); for (i = 0; i < num_entries; i++) { MPASS(pages[i]->valid == VM_PAGE_BITS_ALL); MPASS(pages[i]->ref_count > 0); sc->match->driver->install_gtt_pte(dev, first_entry + i, VM_PAGE_TO_PHYS(pages[i]), flags); } sc->match->driver->read_gtt_pte(dev, first_entry + num_entries - 1); } struct intel_gtt agp_intel_gtt_get(device_t dev) { struct agp_i810_softc *sc; struct intel_gtt res; sc = device_get_softc(dev); res.stolen_size = sc->stolen_size; res.gtt_total_entries = sc->gtt_total_entries; res.gtt_mappable_entries = sc->gtt_mappable_entries; res.do_idle_maps = 0; res.scratch_page_dma = VM_PAGE_TO_PHYS(bogus_page); if (sc->agp.as_aperture != NULL) res.gma_bus_addr = rman_get_start(sc->agp.as_aperture); else res.gma_bus_addr = 0; return (res); } static int agp_i810_chipset_flush_setup(device_t dev) { return (0); } static void agp_i810_chipset_flush_teardown(device_t dev) { /* Nothing to do. */ } static void agp_i810_chipset_flush(device_t dev) { /* Nothing to do. */ } static void agp_i830_chipset_flush(device_t dev) { struct agp_i810_softc *sc; uint32_t hic; int i; sc = device_get_softc(dev); pmap_invalidate_cache(); hic = bus_read_4(sc->sc_res[0], AGP_I830_HIC); bus_write_4(sc->sc_res[0], AGP_I830_HIC, hic | (1U << 31)); for (i = 0; i < 20000 /* 1 sec */; i++) { hic = bus_read_4(sc->sc_res[0], AGP_I830_HIC); if ((hic & (1U << 31)) == 0) break; DELAY(50); } } static int agp_i915_chipset_flush_alloc_page(device_t dev, uint64_t start, uint64_t end) { struct agp_i810_softc *sc; device_t vga; sc = device_get_softc(dev); vga = device_get_parent(dev); sc->sc_flush_page_rid = 100; sc->sc_flush_page_res = BUS_ALLOC_RESOURCE(device_get_parent(vga), dev, SYS_RES_MEMORY, &sc->sc_flush_page_rid, start, end, PAGE_SIZE, RF_ACTIVE); if (sc->sc_flush_page_res == NULL) { device_printf(dev, "Failed to allocate flush page at 0x%jx\n", (uintmax_t)start); return (EINVAL); } sc->sc_flush_page_vaddr = rman_get_virtual(sc->sc_flush_page_res); if (bootverbose) { device_printf(dev, "Allocated flush page phys 0x%jx virt %p\n", (uintmax_t)rman_get_start(sc->sc_flush_page_res), sc->sc_flush_page_vaddr); } return (0); } static void agp_i915_chipset_flush_free_page(device_t dev) { struct agp_i810_softc *sc; device_t vga; sc = device_get_softc(dev); vga = device_get_parent(dev); if (sc->sc_flush_page_res == NULL) return; BUS_DEACTIVATE_RESOURCE(device_get_parent(vga), dev, sc->sc_flush_page_res); BUS_RELEASE_RESOURCE(device_get_parent(vga), dev, sc->sc_flush_page_res); } static int agp_i915_chipset_flush_setup(device_t dev) { struct agp_i810_softc *sc; uint32_t temp; int error; sc = device_get_softc(dev); temp = pci_read_config(sc->bdev, AGP_I915_IFPADDR, 4); if ((temp & 1) != 0) { temp &= ~1; if (bootverbose) device_printf(dev, "Found already configured flush page at 0x%jx\n", (uintmax_t)temp); sc->sc_bios_allocated_flush_page = 1; /* * In the case BIOS initialized the flush pointer (?) * register, expect that BIOS also set up the resource * for the page. */ error = agp_i915_chipset_flush_alloc_page(dev, temp, temp + PAGE_SIZE - 1); if (error != 0) return (error); } else { sc->sc_bios_allocated_flush_page = 0; error = agp_i915_chipset_flush_alloc_page(dev, 0, 0xffffffff); if (error != 0) return (error); temp = rman_get_start(sc->sc_flush_page_res); pci_write_config(sc->bdev, AGP_I915_IFPADDR, temp | 1, 4); } return (0); } static void agp_i915_chipset_flush_teardown(device_t dev) { struct agp_i810_softc *sc; uint32_t temp; sc = device_get_softc(dev); if (sc->sc_flush_page_res == NULL) return; if (!sc->sc_bios_allocated_flush_page) { temp = pci_read_config(sc->bdev, AGP_I915_IFPADDR, 4); temp &= ~1; pci_write_config(sc->bdev, AGP_I915_IFPADDR, temp, 4); } agp_i915_chipset_flush_free_page(dev); } static int agp_i965_chipset_flush_setup(device_t dev) { struct agp_i810_softc *sc; uint64_t temp; uint32_t temp_hi, temp_lo; int error; sc = device_get_softc(dev); temp_hi = pci_read_config(sc->bdev, AGP_I965_IFPADDR + 4, 4); temp_lo = pci_read_config(sc->bdev, AGP_I965_IFPADDR, 4); if ((temp_lo & 1) != 0) { temp = ((uint64_t)temp_hi << 32) | (temp_lo & ~1); if (bootverbose) device_printf(dev, "Found already configured flush page at 0x%jx\n", (uintmax_t)temp); sc->sc_bios_allocated_flush_page = 1; /* * In the case BIOS initialized the flush pointer (?) * register, expect that BIOS also set up the resource * for the page. */ error = agp_i915_chipset_flush_alloc_page(dev, temp, temp + PAGE_SIZE - 1); if (error != 0) return (error); } else { sc->sc_bios_allocated_flush_page = 0; error = agp_i915_chipset_flush_alloc_page(dev, 0, ~0); if (error != 0) return (error); temp = rman_get_start(sc->sc_flush_page_res); pci_write_config(sc->bdev, AGP_I965_IFPADDR + 4, (temp >> 32) & UINT32_MAX, 4); pci_write_config(sc->bdev, AGP_I965_IFPADDR, (temp & UINT32_MAX) | 1, 4); } return (0); } static void agp_i965_chipset_flush_teardown(device_t dev) { struct agp_i810_softc *sc; uint32_t temp_lo; sc = device_get_softc(dev); if (sc->sc_flush_page_res == NULL) return; if (!sc->sc_bios_allocated_flush_page) { temp_lo = pci_read_config(sc->bdev, AGP_I965_IFPADDR, 4); temp_lo &= ~1; pci_write_config(sc->bdev, AGP_I965_IFPADDR, temp_lo, 4); } agp_i915_chipset_flush_free_page(dev); } static void agp_i915_chipset_flush(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); *(uint32_t *)sc->sc_flush_page_vaddr = 1; } int agp_intel_gtt_chipset_flush(device_t dev) { struct agp_i810_softc *sc; sc = device_get_softc(dev); sc->match->driver->chipset_flush(dev); return (0); } void agp_intel_gtt_unmap_memory(device_t dev, struct sglist *sg_list) { } int agp_intel_gtt_map_memory(device_t dev, vm_page_t *pages, u_int num_entries, struct sglist **sg_list) { #if 0 struct agp_i810_softc *sc; #endif struct sglist *sg; int i; #if 0 int error; bus_dma_tag_t dmat; #endif if (*sg_list != NULL) return (0); #if 0 sc = device_get_softc(dev); #endif sg = sglist_alloc(num_entries, M_WAITOK /* XXXKIB */); for (i = 0; i < num_entries; i++) { sg->sg_segs[i].ss_paddr = VM_PAGE_TO_PHYS(pages[i]); sg->sg_segs[i].ss_len = PAGE_SIZE; } #if 0 error = bus_dma_tag_create(bus_get_dma_tag(dev), 1 /* alignment */, 0 /* boundary */, 1ULL << sc->match->busdma_addr_mask_sz /* lowaddr */, BUS_SPACE_MAXADDR /* highaddr */, NULL /* filtfunc */, NULL /* filtfuncarg */, BUS_SPACE_MAXADDR /* maxsize */, BUS_SPACE_UNRESTRICTED /* nsegments */, BUS_SPACE_MAXADDR /* maxsegsz */, 0 /* flags */, NULL /* lockfunc */, NULL /* lockfuncarg */, &dmat); if (error != 0) { sglist_free(sg); return (error); } /* XXXKIB */ #endif *sg_list = sg; return (0); } static void agp_intel_gtt_install_pte(device_t dev, u_int index, vm_paddr_t addr, u_int flags) { struct agp_i810_softc *sc; sc = device_get_softc(dev); sc->match->driver->install_gtt_pte(dev, index, addr, flags); } void agp_intel_gtt_insert_sg_entries(device_t dev, struct sglist *sg_list, u_int first_entry, u_int flags) { struct agp_i810_softc *sc; vm_paddr_t spaddr; size_t slen; u_int i, j; sc = device_get_softc(dev); for (i = j = 0; j < sg_list->sg_nseg; j++) { spaddr = sg_list->sg_segs[i].ss_paddr; slen = sg_list->sg_segs[i].ss_len; for (; slen > 0; i++) { sc->match->driver->install_gtt_pte(dev, first_entry + i, spaddr, flags); spaddr += AGP_PAGE_SIZE; slen -= AGP_PAGE_SIZE; } } sc->match->driver->read_gtt_pte(dev, first_entry + i - 1); } void intel_gtt_clear_range(u_int first_entry, u_int num_entries) { agp_intel_gtt_clear_range(intel_agp, first_entry, num_entries); } void intel_gtt_insert_pages(u_int first_entry, u_int num_entries, vm_page_t *pages, u_int flags) { agp_intel_gtt_insert_pages(intel_agp, first_entry, num_entries, pages, flags); } struct intel_gtt * intel_gtt_get(void) { intel_private.base = agp_intel_gtt_get(intel_agp); return (&intel_private.base); } int intel_gtt_chipset_flush(void) { return (agp_intel_gtt_chipset_flush(intel_agp)); } void intel_gtt_unmap_memory(struct sglist *sg_list) { agp_intel_gtt_unmap_memory(intel_agp, sg_list); } int intel_gtt_map_memory(vm_page_t *pages, u_int num_entries, struct sglist **sg_list) { return (agp_intel_gtt_map_memory(intel_agp, pages, num_entries, sg_list)); } void intel_gtt_insert_sg_entries(struct sglist *sg_list, u_int first_entry, u_int flags) { agp_intel_gtt_insert_sg_entries(intel_agp, sg_list, first_entry, flags); } void intel_gtt_install_pte(u_int index, vm_paddr_t addr, u_int flags) { agp_intel_gtt_install_pte(intel_agp, index, addr, flags); } device_t intel_gtt_get_bridge_device(void) { struct agp_i810_softc *sc; sc = device_get_softc(intel_agp); return (sc->bdev); } vm_paddr_t intel_gtt_read_pte_paddr(u_int entry) { struct agp_i810_softc *sc; sc = device_get_softc(intel_agp); return (sc->match->driver->read_gtt_pte_paddr(intel_agp, entry)); } u_int32_t intel_gtt_read_pte(u_int entry) { struct agp_i810_softc *sc; sc = device_get_softc(intel_agp); return (sc->match->driver->read_gtt_pte(intel_agp, entry)); } void intel_gtt_write(u_int entry, uint32_t val) { struct agp_i810_softc *sc; sc = device_get_softc(intel_agp); return (sc->match->driver->write_gtt(intel_agp, entry, val)); } diff --git a/sys/dev/amdsbwd/amdsbwd.c b/sys/dev/amdsbwd/amdsbwd.c index 122fa2d58277..d817a7b1364e 100644 --- a/sys/dev/amdsbwd/amdsbwd.c +++ b/sys/dev/amdsbwd/amdsbwd.c @@ -1,592 +1,593 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Andriy Gapon * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * This is a driver for watchdog timer present in AMD SB600/SB7xx/SB8xx * southbridges. * Please see the following specifications for the descriptions of the * registers and flags: * - AMD SB600 Register Reference Guide, Public Version, Rev. 3.03 (SB600 RRG) * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/46155_sb600_rrg_pub_3.03.pdf * - AMD SB700/710/750 Register Reference Guide (RRG) * http://developer.amd.com/assets/43009_sb7xx_rrg_pub_1.00.pdf * - AMD SB700/710/750 Register Programming Requirements (RPR) * http://developer.amd.com/assets/42413_sb7xx_rpr_pub_1.00.pdf * - AMD SB800-Series Southbridges Register Reference Guide (RRG) * http://support.amd.com/us/Embedded_TechDocs/45482.pdf * Please see the following for Watchdog Resource Table specification: * - Watchdog Timer Hardware Requirements for Windows Server 2003 (WDRT) * http://www.microsoft.com/whdc/system/sysinternals/watchdog.mspx * AMD SB600/SB7xx/SB8xx watchdog hardware seems to conform to the above * specifications, but the table hasn't been spotted in the wild yet. */ #include #include "opt_amdsbwd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Registers in the Watchdog IO space. * See SB7xx RRG 2.3.4, WDRT. */ #define AMDSB_WD_CTRL 0x00 #define AMDSB_WD_RUN 0x01 #define AMDSB_WD_FIRED 0x02 #define AMDSB_WD_SHUTDOWN 0x04 #define AMDSB_WD_DISABLE 0x08 #define AMDSB_WD_RESERVED 0x70 #define AMDSB_WD_RELOAD 0x80 #define AMDSB_WD_COUNT 0x04 #define AMDSB_WD_COUNT_MASK 0xffff #define AMDSB_WDIO_REG_WIDTH 4 #define amdsbwd_verbose_printf(dev, ...) \ do { \ if (bootverbose) \ device_printf(dev, __VA_ARGS__);\ } while (0) struct amdsbwd_softc { device_t dev; eventhandler_tag ev_tag; struct resource *res_ctrl; struct resource *res_count; int rid_ctrl; int rid_count; int ms_per_tick; int max_ticks; int active; unsigned int timeout; }; static void amdsbwd_identify(driver_t *driver, device_t parent); static int amdsbwd_probe(device_t dev); static int amdsbwd_attach(device_t dev); static int amdsbwd_detach(device_t dev); static int amdsbwd_suspend(device_t dev); static int amdsbwd_resume(device_t dev); static device_method_t amdsbwd_methods[] = { DEVMETHOD(device_identify, amdsbwd_identify), DEVMETHOD(device_probe, amdsbwd_probe), DEVMETHOD(device_attach, amdsbwd_attach), DEVMETHOD(device_detach, amdsbwd_detach), DEVMETHOD(device_suspend, amdsbwd_suspend), DEVMETHOD(device_resume, amdsbwd_resume), #if 0 DEVMETHOD(device_shutdown, amdsbwd_detach), #endif DEVMETHOD_END }; static driver_t amdsbwd_driver = { "amdsbwd", amdsbwd_methods, sizeof(struct amdsbwd_softc) }; DRIVER_MODULE(amdsbwd, isa, amdsbwd_driver, NULL, NULL); static uint8_t pmio_read(struct resource *res, uint8_t reg) { bus_write_1(res, 0, reg); /* Index */ return (bus_read_1(res, 1)); /* Data */ } static void pmio_write(struct resource *res, uint8_t reg, uint8_t val) { bus_write_1(res, 0, reg); /* Index */ bus_write_1(res, 1, val); /* Data */ } static uint32_t wdctrl_read(struct amdsbwd_softc *sc) { return (bus_read_4(sc->res_ctrl, 0)); } static void wdctrl_write(struct amdsbwd_softc *sc, uint32_t val) { bus_write_4(sc->res_ctrl, 0, val); } static __unused uint32_t wdcount_read(struct amdsbwd_softc *sc) { return (bus_read_4(sc->res_count, 0)); } static void wdcount_write(struct amdsbwd_softc *sc, uint32_t val) { bus_write_4(sc->res_count, 0, val); } static void amdsbwd_tmr_enable(struct amdsbwd_softc *sc) { uint32_t val; val = wdctrl_read(sc); val |= AMDSB_WD_RUN; wdctrl_write(sc, val); sc->active = 1; amdsbwd_verbose_printf(sc->dev, "timer enabled\n"); } static void amdsbwd_tmr_disable(struct amdsbwd_softc *sc) { uint32_t val; val = wdctrl_read(sc); val &= ~AMDSB_WD_RUN; wdctrl_write(sc, val); sc->active = 0; amdsbwd_verbose_printf(sc->dev, "timer disabled\n"); } static void amdsbwd_tmr_reload(struct amdsbwd_softc *sc) { uint32_t val; val = wdctrl_read(sc); val |= AMDSB_WD_RELOAD; wdctrl_write(sc, val); } static void amdsbwd_tmr_set(struct amdsbwd_softc *sc, uint16_t timeout) { timeout &= AMDSB_WD_COUNT_MASK; wdcount_write(sc, timeout); sc->timeout = timeout; amdsbwd_verbose_printf(sc->dev, "timeout set to %u ticks\n", timeout); } static void amdsbwd_event(void *arg, unsigned int cmd, int *error) { struct amdsbwd_softc *sc = arg; uint64_t timeout; if (cmd != 0) { timeout = 0; cmd &= WD_INTERVAL; if (cmd >= WD_TO_1MS) { timeout = (uint64_t)1 << (cmd - WD_TO_1MS); timeout = timeout / sc->ms_per_tick; } /* For a too short timeout use 1 tick. */ if (timeout == 0) timeout = 1; /* For a too long timeout stop the timer. */ if (timeout > sc->max_ticks) timeout = 0; } else { timeout = 0; } if (timeout != 0) { if (timeout != sc->timeout) amdsbwd_tmr_set(sc, timeout); if (!sc->active) amdsbwd_tmr_enable(sc); amdsbwd_tmr_reload(sc); *error = 0; } else { if (sc->active) amdsbwd_tmr_disable(sc); } } static void amdsbwd_identify(driver_t *driver, device_t parent) { device_t child; device_t smb_dev; if (resource_disabled("amdsbwd", 0)) return; - if (device_find_child(parent, "amdsbwd", -1) != NULL) + if (device_find_child(parent, "amdsbwd", DEVICE_UNIT_ANY) != NULL) return; /* * Try to identify SB600/SB7xx by PCI Device ID of SMBus device * that should be present at bus 0, device 20, function 0. */ smb_dev = pci_find_bsf(0, 20, 0); if (smb_dev == NULL) return; if (pci_get_devid(smb_dev) != AMDSB_SMBUS_DEVID && pci_get_devid(smb_dev) != AMDFCH_SMBUS_DEVID && pci_get_devid(smb_dev) != AMDCZ_SMBUS_DEVID && pci_get_devid(smb_dev) != HYGONCZ_SMBUS_DEVID) return; - child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "amdsbwd", DEVICE_UNIT_ANY); + child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "amdsbwd", + DEVICE_UNIT_ANY); if (child == NULL) device_printf(parent, "add amdsbwd child failed\n"); } static void amdsbwd_probe_sb7xx(device_t dev, struct resource *pmres, uint32_t *addr) { uint8_t val; int i; /* Report cause of previous reset for user's convenience. */ val = pmio_read(pmres, AMDSB_PM_RESET_STATUS0); if (val != 0) amdsbwd_verbose_printf(dev, "ResetStatus0 = %#04x\n", val); val = pmio_read(pmres, AMDSB_PM_RESET_STATUS1); if (val != 0) amdsbwd_verbose_printf(dev, "ResetStatus1 = %#04x\n", val); if ((val & AMDSB_WD_RST_STS) != 0) device_printf(dev, "Previous Reset was caused by Watchdog\n"); /* Find base address of memory mapped WDT registers. */ for (*addr = 0, i = 0; i < 4; i++) { *addr <<= 8; *addr |= pmio_read(pmres, AMDSB_PM_WDT_BASE_MSB - i); } *addr &= ~0x07u; /* Set watchdog timer tick to 1s. */ val = pmio_read(pmres, AMDSB_PM_WDT_CTRL); val &= ~AMDSB_WDT_RES_MASK; val |= AMDSB_WDT_RES_1S; pmio_write(pmres, AMDSB_PM_WDT_CTRL, val); /* Enable watchdog device (in stopped state). */ val = pmio_read(pmres, AMDSB_PM_WDT_CTRL); val &= ~AMDSB_WDT_DISABLE; pmio_write(pmres, AMDSB_PM_WDT_CTRL, val); /* * XXX TODO: Ensure that watchdog decode is enabled * (register 0x41, bit 3). */ device_set_desc(dev, "AMD SB600/SB7xx Watchdog Timer"); } static void amdsbwd_probe_sb8xx(device_t dev, struct resource *pmres, uint32_t *addr) { uint32_t val; int i; /* Report cause of previous reset for user's convenience. */ val = pmio_read(pmres, AMDSB8_PM_RESET_CTRL); if ((val & AMDSB8_RST_STS_DIS) != 0) { val &= ~AMDSB8_RST_STS_DIS; pmio_write(pmres, AMDSB8_PM_RESET_CTRL, val); } val = 0; for (i = 3; i >= 0; i--) { val <<= 8; val |= pmio_read(pmres, AMDSB8_PM_RESET_STATUS + i); } if (val != 0) amdsbwd_verbose_printf(dev, "ResetStatus = 0x%08x\n", val); if ((val & AMDSB8_WD_RST_STS) != 0) device_printf(dev, "Previous Reset was caused by Watchdog\n"); /* Find base address of memory mapped WDT registers. */ for (*addr = 0, i = 0; i < 4; i++) { *addr <<= 8; *addr |= pmio_read(pmres, AMDSB8_PM_WDT_EN + 3 - i); } *addr &= ~0x07u; /* Set watchdog timer tick to 1s. */ val = pmio_read(pmres, AMDSB8_PM_WDT_CTRL); val &= ~AMDSB8_WDT_RES_MASK; val |= AMDSB8_WDT_1HZ; pmio_write(pmres, AMDSB8_PM_WDT_CTRL, val); #ifdef AMDSBWD_DEBUG val = pmio_read(pmres, AMDSB8_PM_WDT_CTRL); amdsbwd_verbose_printf(dev, "AMDSB8_PM_WDT_CTRL value = %#04x\n", val); #endif /* * Enable watchdog device (in stopped state) * and decoding of its address. */ val = pmio_read(pmres, AMDSB8_PM_WDT_EN); val &= ~AMDSB8_WDT_DISABLE; val |= AMDSB8_WDT_DEC_EN; pmio_write(pmres, AMDSB8_PM_WDT_EN, val); #ifdef AMDSBWD_DEBUG val = pmio_read(pmres, AMDSB8_PM_WDT_EN); device_printf(dev, "AMDSB8_PM_WDT_EN value = %#04x\n", val); #endif device_set_desc(dev, "AMD SB8xx/SB9xx/Axx Watchdog Timer"); } static void amdsbwd_probe_fch41(device_t dev, struct resource *pmres, uint32_t *addr) { uint8_t val; /* * Enable decoding of watchdog MMIO address. */ val = pmio_read(pmres, AMDFCH41_PM_DECODE_EN0); val |= AMDFCH41_WDT_EN; pmio_write(pmres, AMDFCH41_PM_DECODE_EN0, val); #ifdef AMDSBWD_DEBUG val = pmio_read(pmres, AMDFCH41_PM_DECODE_EN0); device_printf(dev, "AMDFCH41_PM_DECODE_EN0 value = %#04x\n", val); #endif val = pmio_read(pmres, AMDFCH41_PM_ISA_CTRL); if ((val & AMDFCH41_MMIO_EN) != 0) { /* Fixed offset for the watchdog within ACPI MMIO range. */ amdsbwd_verbose_printf(dev, "ACPI MMIO range is enabled\n"); *addr = AMDFCH41_MMIO_ADDR + AMDFCH41_MMIO_WDT_OFF; } else { /* Special fixed MMIO range for the watchdog. */ *addr = AMDFCH41_WDT_FIXED_ADDR; } /* * Set watchdog timer tick to 1s and * enable the watchdog device (in stopped state). */ val = pmio_read(pmres, AMDFCH41_PM_DECODE_EN3); val &= ~AMDFCH41_WDT_RES_MASK; val |= AMDFCH41_WDT_RES_1S; val &= ~AMDFCH41_WDT_EN_MASK; val |= AMDFCH41_WDT_ENABLE; pmio_write(pmres, AMDFCH41_PM_DECODE_EN3, val); #ifdef AMDSBWD_DEBUG val = pmio_read(pmres, AMDFCH41_PM_DECODE_EN3); amdsbwd_verbose_printf(dev, "AMDFCH41_PM_DECODE_EN3 value = %#04x\n", val); #endif device_set_descf(dev, "%s FCH Rev 41h+ Watchdog Timer", cpu_vendor_id == CPU_VENDOR_HYGON ? "Hygon" : "AMD"); } static int amdsbwd_probe(device_t dev) { struct resource *res; device_t smb_dev; uint32_t addr; int rid; int rc; uint32_t devid; uint8_t revid; /* Do not claim some ISA PnP device by accident. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); rc = bus_set_resource(dev, SYS_RES_IOPORT, 0, AMDSB_PMIO_INDEX, AMDSB_PMIO_WIDTH); if (rc != 0) { device_printf(dev, "bus_set_resource for IO failed\n"); return (ENXIO); } rid = 0; res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE | RF_SHAREABLE); if (res == NULL) { device_printf(dev, "bus_alloc_resource for IO failed\n"); return (ENXIO); } smb_dev = pci_find_bsf(0, 20, 0); KASSERT(smb_dev != NULL, ("can't find SMBus PCI device\n")); devid = pci_get_devid(smb_dev); revid = pci_get_revid(smb_dev); if (devid == AMDSB_SMBUS_DEVID && revid < AMDSB8_SMBUS_REVID) amdsbwd_probe_sb7xx(dev, res, &addr); else if (devid == AMDSB_SMBUS_DEVID || (devid == AMDFCH_SMBUS_DEVID && revid < AMDFCH41_SMBUS_REVID) || (devid == AMDCZ_SMBUS_DEVID && revid < AMDCZ49_SMBUS_REVID)) amdsbwd_probe_sb8xx(dev, res, &addr); else amdsbwd_probe_fch41(dev, res, &addr); bus_release_resource(dev, SYS_RES_IOPORT, rid, res); bus_delete_resource(dev, SYS_RES_IOPORT, rid); amdsbwd_verbose_printf(dev, "memory base address = %#010x\n", addr); rc = bus_set_resource(dev, SYS_RES_MEMORY, 0, addr + AMDSB_WD_CTRL, AMDSB_WDIO_REG_WIDTH); if (rc != 0) { device_printf(dev, "bus_set_resource for control failed\n"); return (ENXIO); } rc = bus_set_resource(dev, SYS_RES_MEMORY, 1, addr + AMDSB_WD_COUNT, AMDSB_WDIO_REG_WIDTH); if (rc != 0) { device_printf(dev, "bus_set_resource for count failed\n"); return (ENXIO); } return (0); } static int amdsbwd_attach_sb(device_t dev, struct amdsbwd_softc *sc) { sc->max_ticks = UINT16_MAX; sc->rid_ctrl = 0; sc->rid_count = 1; sc->ms_per_tick = 1000; sc->res_ctrl = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid_ctrl, RF_ACTIVE); if (sc->res_ctrl == NULL) { device_printf(dev, "bus_alloc_resource for ctrl failed\n"); return (ENXIO); } sc->res_count = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid_count, RF_ACTIVE); if (sc->res_count == NULL) { device_printf(dev, "bus_alloc_resource for count failed\n"); return (ENXIO); } return (0); } static int amdsbwd_attach(device_t dev) { struct amdsbwd_softc *sc; int rc; sc = device_get_softc(dev); sc->dev = dev; rc = amdsbwd_attach_sb(dev, sc); if (rc != 0) goto fail; #ifdef AMDSBWD_DEBUG device_printf(dev, "wd ctrl = %#04x\n", wdctrl_read(sc)); device_printf(dev, "wd count = %#04x\n", wdcount_read(sc)); #endif /* Setup initial state of Watchdog Control. */ wdctrl_write(sc, AMDSB_WD_FIRED); if (wdctrl_read(sc) & AMDSB_WD_DISABLE) { device_printf(dev, "watchdog hardware is disabled\n"); goto fail; } sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, amdsbwd_event, sc, EVENTHANDLER_PRI_ANY); return (0); fail: amdsbwd_detach(dev); return (ENXIO); } static int amdsbwd_detach(device_t dev) { struct amdsbwd_softc *sc; sc = device_get_softc(dev); if (sc->ev_tag != NULL) EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); if (sc->active) amdsbwd_tmr_disable(sc); if (sc->res_ctrl != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->rid_ctrl, sc->res_ctrl); if (sc->res_count != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->rid_count, sc->res_count); return (0); } static int amdsbwd_suspend(device_t dev) { struct amdsbwd_softc *sc; uint32_t val; sc = device_get_softc(dev); val = wdctrl_read(sc); val &= ~AMDSB_WD_RUN; wdctrl_write(sc, val); return (0); } static int amdsbwd_resume(device_t dev) { struct amdsbwd_softc *sc; sc = device_get_softc(dev); wdctrl_write(sc, AMDSB_WD_FIRED); if (sc->active) { amdsbwd_tmr_set(sc, sc->timeout); amdsbwd_tmr_enable(sc); amdsbwd_tmr_reload(sc); } return (0); } diff --git a/sys/dev/amdsmn/amdsmn.c b/sys/dev/amdsmn/amdsmn.c index 803491e9b0f5..d19103738ec6 100644 --- a/sys/dev/amdsmn/amdsmn.c +++ b/sys/dev/amdsmn/amdsmn.c @@ -1,298 +1,298 @@ /*- * Copyright (c) 2017-2020 Conrad Meyer * 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, 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. */ /* * Driver for the AMD Family 15h, 17h, 19h, 1Ah CPU System Management Network. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define F15H_SMN_ADDR_REG 0xb8 #define F15H_SMN_DATA_REG 0xbc #define F17H_SMN_ADDR_REG 0x60 #define F17H_SMN_DATA_REG 0x64 #define PCI_DEVICE_ID_AMD_15H_M60H_ROOT 0x1576 #define PCI_DEVICE_ID_AMD_17H_ROOT 0x1450 #define PCI_DEVICE_ID_AMD_17H_M10H_ROOT 0x15d0 #define PCI_DEVICE_ID_AMD_17H_M30H_ROOT 0x1480 /* Also M70H, F19H M00H/M20H */ #define PCI_DEVICE_ID_AMD_17H_M60H_ROOT 0x1630 /* Also F19H M50H */ #define PCI_DEVICE_ID_AMD_19H_M10H_ROOT 0x14a4 #define PCI_DEVICE_ID_AMD_19H_M40H_ROOT 0x14b5 #define PCI_DEVICE_ID_AMD_19H_M60H_ROOT 0x14d8 /* Also F1AH M40H */ #define PCI_DEVICE_ID_AMD_19H_M70H_ROOT 0x14e8 #define PCI_DEVICE_ID_AMD_1AH_M00H_ROOT 0x153a #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 struct pciid; struct amdsmn_softc { struct mtx smn_lock; const struct pciid *smn_pciid; }; static const struct pciid { uint16_t amdsmn_vendorid; uint16_t amdsmn_deviceid; uint8_t amdsmn_addr_reg; uint8_t amdsmn_data_reg; } amdsmn_ids[] = { { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_15H_M60H_ROOT, .amdsmn_addr_reg = F15H_SMN_ADDR_REG, .amdsmn_data_reg = F15H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_17H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_17H_M10H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_17H_M30H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_17H_M60H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_19H_M10H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_19H_M40H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_19H_M60H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_19H_M70H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_1AH_M00H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_1AH_M20H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, { .amdsmn_vendorid = CPU_VENDOR_AMD, .amdsmn_deviceid = PCI_DEVICE_ID_AMD_1AH_M60H_ROOT, .amdsmn_addr_reg = F17H_SMN_ADDR_REG, .amdsmn_data_reg = F17H_SMN_DATA_REG, }, }; /* * Device methods. */ static void amdsmn_identify(driver_t *driver, device_t parent); static int amdsmn_probe(device_t dev); static int amdsmn_attach(device_t dev); static int amdsmn_detach(device_t dev); static device_method_t amdsmn_methods[] = { /* Device interface */ DEVMETHOD(device_identify, amdsmn_identify), DEVMETHOD(device_probe, amdsmn_probe), DEVMETHOD(device_attach, amdsmn_attach), DEVMETHOD(device_detach, amdsmn_detach), DEVMETHOD_END }; static driver_t amdsmn_driver = { "amdsmn", amdsmn_methods, sizeof(struct amdsmn_softc), }; DRIVER_MODULE(amdsmn, hostb, amdsmn_driver, NULL, NULL); MODULE_VERSION(amdsmn, 1); MODULE_PNP_INFO("U16:vendor;U16:device", pci, amdsmn, amdsmn_ids, nitems(amdsmn_ids)); static bool amdsmn_match(device_t parent, const struct pciid **pciid_out) { uint16_t vendor, device; size_t i; vendor = pci_get_vendor(parent); device = pci_get_device(parent); for (i = 0; i < nitems(amdsmn_ids); i++) { if (vendor == amdsmn_ids[i].amdsmn_vendorid && device == amdsmn_ids[i].amdsmn_deviceid) { if (pciid_out != NULL) *pciid_out = &amdsmn_ids[i]; return (true); } } return (false); } static void amdsmn_identify(driver_t *driver, device_t parent) { device_t child; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "amdsmn", -1) != NULL) + if (device_find_child(parent, "amdsmn", DEVICE_UNIT_ANY) != NULL) return; if (!amdsmn_match(parent, NULL)) return; child = device_add_child(parent, "amdsmn", DEVICE_UNIT_ANY); if (child == NULL) device_printf(parent, "add amdsmn child failed\n"); } static int amdsmn_probe(device_t dev) { uint32_t family; if (resource_disabled("amdsmn", 0)) return (ENXIO); if (!amdsmn_match(device_get_parent(dev), NULL)) return (ENXIO); family = CPUID_TO_FAMILY(cpu_id); switch (family) { case 0x15: case 0x17: case 0x19: case 0x1a: break; default: return (ENXIO); } device_set_descf(dev, "AMD Family %02Xh System Management Network", family); return (BUS_PROBE_GENERIC); } static int amdsmn_attach(device_t dev) { struct amdsmn_softc *sc = device_get_softc(dev); if (!amdsmn_match(device_get_parent(dev), &sc->smn_pciid)) return (ENXIO); mtx_init(&sc->smn_lock, "SMN mtx", "SMN", MTX_DEF); return (0); } int amdsmn_detach(device_t dev) { struct amdsmn_softc *sc = device_get_softc(dev); mtx_destroy(&sc->smn_lock); return (0); } int amdsmn_read(device_t dev, uint32_t addr, uint32_t *value) { struct amdsmn_softc *sc = device_get_softc(dev); device_t parent; parent = device_get_parent(dev); mtx_lock(&sc->smn_lock); pci_write_config(parent, sc->smn_pciid->amdsmn_addr_reg, addr, 4); *value = pci_read_config(parent, sc->smn_pciid->amdsmn_data_reg, 4); mtx_unlock(&sc->smn_lock); return (0); } int amdsmn_write(device_t dev, uint32_t addr, uint32_t value) { struct amdsmn_softc *sc = device_get_softc(dev); device_t parent; parent = device_get_parent(dev); mtx_lock(&sc->smn_lock); pci_write_config(parent, sc->smn_pciid->amdsmn_addr_reg, addr, 4); pci_write_config(parent, sc->smn_pciid->amdsmn_data_reg, value, 4); mtx_unlock(&sc->smn_lock); return (0); } diff --git a/sys/dev/amdtemp/amdtemp.c b/sys/dev/amdtemp/amdtemp.c index 3ce826a2c0ec..79ccdc8c79fb 100644 --- a/sys/dev/amdtemp/amdtemp.c +++ b/sys/dev/amdtemp/amdtemp.c @@ -1,941 +1,941 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2008, 2009 Rui Paulo * Copyright (c) 2009 Norikatsu Shigemura * Copyright (c) 2009-2012 Jung-uk Kim * All rights reserved. * Copyright (c) 2017-2020 Conrad Meyer . 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, 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. */ /* * Driver for the AMD CPU on-die thermal sensors. * Initially based on the k8temp Linux driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef enum { CORE0_SENSOR0, CORE0_SENSOR1, CORE1_SENSOR0, CORE1_SENSOR1, CORE0, CORE1, CCD1, CCD_BASE = CCD1, CCD2, CCD3, CCD4, CCD5, CCD6, CCD7, CCD8, CCD9, CCD10, CCD11, CCD12, CCD_MAX = CCD12, NUM_CCDS = CCD_MAX - CCD_BASE + 1, } amdsensor_t; struct amdtemp_softc { int sc_ncores; int sc_ntemps; int sc_flags; #define AMDTEMP_FLAG_CS_SWAP 0x01 /* ThermSenseCoreSel is inverted. */ #define AMDTEMP_FLAG_CT_10BIT 0x02 /* CurTmp is 10-bit wide. */ #define AMDTEMP_FLAG_ALT_OFFSET 0x04 /* CurTmp starts at -28C. */ int32_t sc_offset; int32_t sc_temp_base; int32_t (*sc_gettemp)(device_t, amdsensor_t); struct sysctl_oid *sc_sysctl_cpu[MAXCPU]; struct intr_config_hook sc_ich; device_t sc_smn; struct mtx sc_lock; }; /* * N.B. The numbers in macro names below are significant and represent CPU * family and model numbers. Do not make up fictitious family or model numbers * when adding support for new devices. */ #define VENDORID_AMD 0x1022 #define DEVICEID_AMD_MISC0F 0x1103 #define DEVICEID_AMD_MISC10 0x1203 #define DEVICEID_AMD_MISC11 0x1303 #define DEVICEID_AMD_MISC14 0x1703 #define DEVICEID_AMD_MISC15 0x1603 #define DEVICEID_AMD_MISC15_M10H 0x1403 #define DEVICEID_AMD_MISC15_M30H 0x141d #define DEVICEID_AMD_MISC15_M60H_ROOT 0x1576 #define DEVICEID_AMD_MISC16 0x1533 #define DEVICEID_AMD_MISC16_M30H 0x1583 #define DEVICEID_AMD_HOSTB17H_ROOT 0x1450 #define DEVICEID_AMD_HOSTB17H_M10H_ROOT 0x15d0 #define DEVICEID_AMD_HOSTB17H_M30H_ROOT 0x1480 /* Also M70H, F19H M00H/M20H */ #define DEVICEID_AMD_HOSTB17H_M60H_ROOT 0x1630 /* Also F19H M50H */ #define DEVICEID_AMD_HOSTB19H_M10H_ROOT 0x14a4 #define DEVICEID_AMD_HOSTB19H_M40H_ROOT 0x14b5 #define DEVICEID_AMD_HOSTB19H_M60H_ROOT 0x14d8 /* Also F1AH M40H */ #define DEVICEID_AMD_HOSTB19H_M70H_ROOT 0x14e8 #define DEVICEID_AMD_HOSTB1AH_M00H_ROOT 0x153a #define DEVICEID_AMD_HOSTB1AH_M20H_ROOT 0x1507 #define DEVICEID_AMD_HOSTB1AH_M60H_ROOT 0x1122 static const struct amdtemp_product { uint16_t amdtemp_vendorid; uint16_t amdtemp_deviceid; /* * 0xFC register is only valid on the D18F3 PCI device; SMN temp * drivers do not attach to that device. */ bool amdtemp_has_cpuid; } amdtemp_products[] = { { VENDORID_AMD, DEVICEID_AMD_MISC0F, true }, { VENDORID_AMD, DEVICEID_AMD_MISC10, true }, { VENDORID_AMD, DEVICEID_AMD_MISC11, true }, { VENDORID_AMD, DEVICEID_AMD_MISC14, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15_M10H, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15_M30H, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15_M60H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_MISC16, true }, { VENDORID_AMD, DEVICEID_AMD_MISC16_M30H, true }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_M10H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_M30H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_M60H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB19H_M10H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB19H_M40H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB19H_M60H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB19H_M70H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB1AH_M00H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB1AH_M20H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB1AH_M60H_ROOT, false }, }; /* * Reported Temperature Control Register, family 0Fh-15h (some models), 16h. */ #define AMDTEMP_REPTMP_CTRL 0xa4 #define AMDTEMP_REPTMP10H_CURTMP_MASK 0x7ff #define AMDTEMP_REPTMP10H_CURTMP_SHIFT 21 #define AMDTEMP_REPTMP10H_TJSEL_MASK 0x3 #define AMDTEMP_REPTMP10H_TJSEL_SHIFT 16 /* * Reported Temperature, Family 15h, M60+ * * Same register bit definitions as other Family 15h CPUs, but access is * indirect via SMN, like Family 17h. */ #define AMDTEMP_15H_M60H_REPTMP_CTRL 0xd8200ca4 /* * Reported Temperature, Family 17h - 1Ah * * According to AMD OSRR for 17H, section 4.2.1, bits 31-21 of this register * provide the current temp. bit 19, when clear, means the temp is reported in * a range 0.."225C" (probable typo for 255C), and when set changes the range * to -49..206C. */ #define AMDTEMP_17H_CUR_TMP 0x59800 #define AMDTEMP_17H_CUR_TMP_RANGE_SEL (1u << 19) /* * Bits 16-17, when set, mean that CUR_TMP is read-write. When it is, the * 49 degree offset should apply as well. This was revealed in a Linux * patch from an AMD employee. */ #define AMDTEMP_17H_CUR_TMP_TJ_SEL ((1u << 17) | (1u << 16)) /* * The following register set was discovered experimentally by Ondrej ฤŒerman * and collaborators, but is not (yet) documented in a PPR/OSRR (other than * the M70H PPR SMN memory map showing [0x59800, +0x314] as allocated to * SMU::THM). It seems plausible and the Linux sensor folks have adopted it. */ #define AMDTEMP_17H_CCD_TMP_BASE 0x59954 #define AMDTEMP_17H_CCD_TMP_VALID (1u << 11) #define AMDTEMP_ZEN4_10H_CCD_TMP_BASE 0x59b00 #define AMDTEMP_ZEN4_CCD_TMP_BASE 0x59b08 /* * AMD temperature range adjustment, in deciKelvins (i.e., 49.0 Celsius). */ #define AMDTEMP_CURTMP_RANGE_ADJUST 490 /* * Thermaltrip Status Register (Family 0Fh only) */ #define AMDTEMP_THERMTP_STAT 0xe4 #define AMDTEMP_TTSR_SELCORE 0x04 #define AMDTEMP_TTSR_SELSENSOR 0x40 /* * DRAM Configuration High Register */ #define AMDTEMP_DRAM_CONF_HIGH 0x94 /* Function 2 */ #define AMDTEMP_DRAM_MODE_DDR3 0x0100 /* * CPU Family/Model Register */ #define AMDTEMP_CPUID 0xfc /* * Device methods. */ static void amdtemp_identify(driver_t *driver, device_t parent); static int amdtemp_probe(device_t dev); static int amdtemp_attach(device_t dev); static void amdtemp_intrhook(void *arg); static int amdtemp_detach(device_t dev); static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor); static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor); static int32_t amdtemp_gettemp15hm60h(device_t dev, amdsensor_t sensor); static int32_t amdtemp_gettemp17h(device_t dev, amdsensor_t sensor); static void amdtemp_probe_ccd_sensors17h(device_t dev, uint32_t model); static void amdtemp_probe_ccd_sensors19h(device_t dev, uint32_t model); static void amdtemp_probe_ccd_sensors1ah(device_t dev, uint32_t model); static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t amdtemp_methods[] = { /* Device interface */ DEVMETHOD(device_identify, amdtemp_identify), DEVMETHOD(device_probe, amdtemp_probe), DEVMETHOD(device_attach, amdtemp_attach), DEVMETHOD(device_detach, amdtemp_detach), DEVMETHOD_END }; static driver_t amdtemp_driver = { "amdtemp", amdtemp_methods, sizeof(struct amdtemp_softc), }; DRIVER_MODULE(amdtemp, hostb, amdtemp_driver, NULL, NULL); MODULE_VERSION(amdtemp, 1); MODULE_DEPEND(amdtemp, amdsmn, 1, 1, 1); MODULE_PNP_INFO("U16:vendor;U16:device", pci, amdtemp, amdtemp_products, nitems(amdtemp_products)); static bool amdtemp_match(device_t dev, const struct amdtemp_product **product_out) { int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < nitems(amdtemp_products); i++) { if (vendor == amdtemp_products[i].amdtemp_vendorid && devid == amdtemp_products[i].amdtemp_deviceid) { if (product_out != NULL) *product_out = &amdtemp_products[i]; return (true); } } return (false); } static void amdtemp_identify(driver_t *driver, device_t parent) { device_t child; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "amdtemp", -1) != NULL) + if (device_find_child(parent, "amdtemp", DEVICE_UNIT_ANY) != NULL) return; if (amdtemp_match(parent, NULL)) { child = device_add_child(parent, "amdtemp", DEVICE_UNIT_ANY); if (child == NULL) device_printf(parent, "add amdtemp child failed\n"); } } static int amdtemp_probe(device_t dev) { uint32_t family, model, stepping; if (resource_disabled("amdtemp", 0)) { if (bootverbose) device_printf(dev, "Resource disabled\n"); return (ENXIO); } if (!amdtemp_match(device_get_parent(dev), NULL)) { if (bootverbose) device_printf(dev, "amdtemp_match() failed\n"); return (ENXIO); } family = CPUID_TO_FAMILY(cpu_id); model = CPUID_TO_MODEL(cpu_id); stepping = CPUID_TO_STEPPING(cpu_id); switch (family) { case 0x0f: if ((model == 0x04 && stepping == 0) || (model == 0x05 && stepping <= 1)) { if (bootverbose) device_printf(dev, "Unsupported (Family=%02Xh, Model=%02Xh, Stepping=%02Xh)\n", family, model, stepping); return (ENXIO); } break; case 0x10: case 0x11: case 0x12: case 0x14: case 0x15: case 0x16: case 0x17: case 0x19: case 0x1a: break; default: return (ENXIO); } device_set_descf(dev, "AMD Family %02Xh CPU On-Die Thermal Sensors", family); return (BUS_PROBE_GENERIC); } static int amdtemp_attach(device_t dev) { char tn[32]; u_int regs[4]; const struct amdtemp_product *product; struct amdtemp_softc *sc; struct sysctl_ctx_list *sysctlctx; struct sysctl_oid *sysctlnode; uint32_t cpuid, family, model; u_int bid; int erratum319, unit; bool needsmn; sc = device_get_softc(dev); erratum319 = 0; needsmn = false; if (!amdtemp_match(device_get_parent(dev), &product)) return (ENXIO); cpuid = cpu_id; family = CPUID_TO_FAMILY(cpuid); model = CPUID_TO_MODEL(cpuid); /* * This checks for the byzantine condition of running a heterogenous * revision multi-socket system where the attach thread is potentially * probing a remote socket's PCI device. * * Currently, such scenarios are unsupported on models using the SMN * (because on those models, amdtemp(4) attaches to a different PCI * device than the one that contains AMDTEMP_CPUID). * * The ancient 0x0F family of devices only supports this register from * models 40h+. */ if (product->amdtemp_has_cpuid && (family > 0x0f || (family == 0x0f && model >= 0x40))) { cpuid = pci_read_config(device_get_parent(dev), AMDTEMP_CPUID, 4); family = CPUID_TO_FAMILY(cpuid); model = CPUID_TO_MODEL(cpuid); } switch (family) { case 0x0f: /* * Thermaltrip Status Register * * - ThermSenseCoreSel * * Revision F & G: 0 - Core1, 1 - Core0 * Other: 0 - Core0, 1 - Core1 * * - CurTmp * * Revision G: bits 23-14 * Other: bits 23-16 * * XXX According to the BKDG, CurTmp, ThermSenseSel and * ThermSenseCoreSel bits were introduced in Revision F * but CurTmp seems working fine as early as Revision C. * However, it is not clear whether ThermSenseSel and/or * ThermSenseCoreSel work in undocumented cases as well. * In fact, the Linux driver suggests it may not work but * we just assume it does until we find otherwise. * * XXX According to Linux, CurTmp starts at -28C on * Socket AM2 Revision G processors, which is not * documented anywhere. */ if (model >= 0x40) sc->sc_flags |= AMDTEMP_FLAG_CS_SWAP; if (model >= 0x60 && model != 0xc1) { do_cpuid(0x80000001, regs); bid = (regs[1] >> 9) & 0x1f; switch (model) { case 0x68: /* Socket S1g1 */ case 0x6c: case 0x7c: break; case 0x6b: /* Socket AM2 and ASB1 (2 cores) */ if (bid != 0x0b && bid != 0x0c) sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; break; case 0x6f: /* Socket AM2 and ASB1 (1 core) */ case 0x7f: if (bid != 0x07 && bid != 0x09 && bid != 0x0c) sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; break; default: sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; } sc->sc_flags |= AMDTEMP_FLAG_CT_10BIT; } /* * There are two sensors per core. */ sc->sc_ntemps = 2; sc->sc_gettemp = amdtemp_gettemp0f; break; case 0x10: /* * Erratum 319 Inaccurate Temperature Measurement * * http://support.amd.com/us/Processor_TechDocs/41322.pdf */ do_cpuid(0x80000001, regs); switch ((regs[1] >> 28) & 0xf) { case 0: /* Socket F */ erratum319 = 1; break; case 1: /* Socket AM2+ or AM3 */ if ((pci_cfgregread(pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev), 2, AMDTEMP_DRAM_CONF_HIGH, 2) & AMDTEMP_DRAM_MODE_DDR3) != 0 || model > 0x04 || (model == 0x04 && (cpuid & CPUID_STEPPING) >= 3)) break; /* XXX 00100F42h (RB-C2) exists in both formats. */ erratum319 = 1; break; } /* FALLTHROUGH */ case 0x11: case 0x12: case 0x14: case 0x15: case 0x16: sc->sc_ntemps = 1; /* * Some later (60h+) models of family 15h use a similar SMN * network as family 17h. (However, the register index differs * from 17h and the decoding matches other 10h-15h models, * which differ from 17h.) */ if (family == 0x15 && model >= 0x60) { sc->sc_gettemp = amdtemp_gettemp15hm60h; needsmn = true; } else sc->sc_gettemp = amdtemp_gettemp; break; case 0x17: case 0x19: case 0x1a: sc->sc_ntemps = 1; sc->sc_gettemp = amdtemp_gettemp17h; needsmn = true; break; default: device_printf(dev, "Bogus family %02Xh\n", family); return (ENXIO); } if (needsmn) { sc->sc_smn = device_find_child( device_get_parent(dev), "amdsmn", -1); if (sc->sc_smn == NULL) { if (bootverbose) device_printf(dev, "No amdsmn(4) device found\n"); return (ENXIO); } } /* Find number of cores per package. */ sc->sc_ncores = (amd_feature2 & AMDID2_CMP) != 0 ? (cpu_procinfo2 & AMDID_CMP_CORES) + 1 : 1; if (sc->sc_ncores > MAXCPU) return (ENXIO); mtx_init(&sc->sc_lock, "amdtemp", NULL, MTX_DEF); if (erratum319) device_printf(dev, "Erratum 319: temperature measurement may be inaccurate\n"); if (bootverbose) device_printf(dev, "Found %d cores and %d sensors\n", sc->sc_ncores, sc->sc_ntemps > 1 ? sc->sc_ntemps * sc->sc_ncores : 1); /* * dev.amdtemp.N tree. */ unit = device_get_unit(dev); snprintf(tn, sizeof(tn), "dev.amdtemp.%d.sensor_offset", unit); TUNABLE_INT_FETCH(tn, &sc->sc_offset); sysctlctx = device_get_sysctl_ctx(dev); SYSCTL_ADD_INT(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor_offset", CTLFLAG_RW, &sc->sc_offset, 0, "Temperature sensor offset"); sysctlnode = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "core0", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Core 0"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE0_SENSOR0, amdtemp_sysctl, "IK", "Core 0 / Sensor 0 temperature"); sc->sc_temp_base = AMDTEMP_17H_CCD_TMP_BASE; if (family == 0x17) amdtemp_probe_ccd_sensors17h(dev, model); else if (family == 0x19) amdtemp_probe_ccd_sensors19h(dev, model); else if (family == 0x1a) amdtemp_probe_ccd_sensors1ah(dev, model); else if (sc->sc_ntemps > 1) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE0_SENSOR1, amdtemp_sysctl, "IK", "Core 0 / Sensor 1 temperature"); if (sc->sc_ncores > 1) { sysctlnode = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "core1", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Core 1"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE1_SENSOR0, amdtemp_sysctl, "IK", "Core 1 / Sensor 0 temperature"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE1_SENSOR1, amdtemp_sysctl, "IK", "Core 1 / Sensor 1 temperature"); } } /* * Try to create dev.cpu sysctl entries and setup intrhook function. * This is needed because the cpu driver may be loaded late on boot, * after us. */ amdtemp_intrhook(dev); sc->sc_ich.ich_func = amdtemp_intrhook; sc->sc_ich.ich_arg = dev; if (config_intrhook_establish(&sc->sc_ich) != 0) { device_printf(dev, "config_intrhook_establish failed!\n"); return (ENXIO); } return (0); } void amdtemp_intrhook(void *arg) { struct amdtemp_softc *sc; struct sysctl_ctx_list *sysctlctx; device_t dev = (device_t)arg; device_t acpi, cpu, nexus; amdsensor_t sensor; int i; sc = device_get_softc(dev); /* * dev.cpu.N.temperature. */ nexus = device_find_child(root_bus, "nexus", 0); acpi = device_find_child(nexus, "acpi", 0); for (i = 0; i < sc->sc_ncores; i++) { if (sc->sc_sysctl_cpu[i] != NULL) continue; cpu = device_find_child(acpi, "cpu", device_get_unit(dev) * sc->sc_ncores + i); if (cpu != NULL) { sysctlctx = device_get_sysctl_ctx(cpu); sensor = sc->sc_ntemps > 1 ? (i == 0 ? CORE0 : CORE1) : CORE0_SENSOR0; sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, sensor, amdtemp_sysctl, "IK", "Current temparature"); } } if (sc->sc_ich.ich_arg != NULL) config_intrhook_disestablish(&sc->sc_ich); } int amdtemp_detach(device_t dev) { struct amdtemp_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_ncores; i++) if (sc->sc_sysctl_cpu[i] != NULL) sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0); /* NewBus removes the dev.amdtemp.N tree by itself. */ mtx_destroy(&sc->sc_lock); return (0); } static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t)arg1; struct amdtemp_softc *sc = device_get_softc(dev); amdsensor_t sensor = (amdsensor_t)arg2; int32_t auxtemp[2], temp; int error; switch (sensor) { case CORE0: auxtemp[0] = sc->sc_gettemp(dev, CORE0_SENSOR0); auxtemp[1] = sc->sc_gettemp(dev, CORE0_SENSOR1); temp = imax(auxtemp[0], auxtemp[1]); break; case CORE1: auxtemp[0] = sc->sc_gettemp(dev, CORE1_SENSOR0); auxtemp[1] = sc->sc_gettemp(dev, CORE1_SENSOR1); temp = imax(auxtemp[0], auxtemp[1]); break; default: temp = sc->sc_gettemp(dev, sensor); break; } error = sysctl_handle_int(oidp, &temp, 0, req); return (error); } #define AMDTEMP_ZERO_C_TO_K 2731 static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t mask, offset, temp; mtx_lock(&sc->sc_lock); /* Set Sensor/Core selector. */ temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 1); temp &= ~(AMDTEMP_TTSR_SELCORE | AMDTEMP_TTSR_SELSENSOR); switch (sensor) { case CORE0_SENSOR1: temp |= AMDTEMP_TTSR_SELSENSOR; /* FALLTHROUGH */ case CORE0_SENSOR0: case CORE0: if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) != 0) temp |= AMDTEMP_TTSR_SELCORE; break; case CORE1_SENSOR1: temp |= AMDTEMP_TTSR_SELSENSOR; /* FALLTHROUGH */ case CORE1_SENSOR0: case CORE1: if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) == 0) temp |= AMDTEMP_TTSR_SELCORE; break; default: __assert_unreachable(); } pci_write_config(dev, AMDTEMP_THERMTP_STAT, temp, 1); mask = (sc->sc_flags & AMDTEMP_FLAG_CT_10BIT) != 0 ? 0x3ff : 0x3fc; offset = (sc->sc_flags & AMDTEMP_FLAG_ALT_OFFSET) != 0 ? 28 : 49; temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 4); temp = ((temp >> 14) & mask) * 5 / 2; temp += AMDTEMP_ZERO_C_TO_K + (sc->sc_offset - offset) * 10; mtx_unlock(&sc->sc_lock); return (temp); } static uint32_t amdtemp_decode_fam10h_to_17h(int32_t sc_offset, uint32_t val, bool minus49) { uint32_t temp; /* Convert raw register subfield units (0.125C) to units of 0.1C. */ temp = (val & AMDTEMP_REPTMP10H_CURTMP_MASK) * 5 / 4; if (minus49) temp -= AMDTEMP_CURTMP_RANGE_ADJUST; temp += AMDTEMP_ZERO_C_TO_K + sc_offset * 10; return (temp); } static uint32_t amdtemp_decode_fam10h_to_16h(int32_t sc_offset, uint32_t val) { bool minus49; /* * On Family 15h and higher, if CurTmpTjSel is 11b, the range is * adjusted down by 49.0 degrees Celsius. (This adjustment is not * documented in BKDGs prior to family 15h model 00h.) */ minus49 = (CPUID_TO_FAMILY(cpu_id) >= 0x15 && ((val >> AMDTEMP_REPTMP10H_TJSEL_SHIFT) & AMDTEMP_REPTMP10H_TJSEL_MASK) == 0x3); return (amdtemp_decode_fam10h_to_17h(sc_offset, val >> AMDTEMP_REPTMP10H_CURTMP_SHIFT, minus49)); } static uint32_t amdtemp_decode_fam17h_tctl(int32_t sc_offset, uint32_t val) { bool minus49; minus49 = ((val & AMDTEMP_17H_CUR_TMP_RANGE_SEL) != 0) || ((val & AMDTEMP_17H_CUR_TMP_TJ_SEL) == AMDTEMP_17H_CUR_TMP_TJ_SEL); return (amdtemp_decode_fam10h_to_17h(sc_offset, val >> AMDTEMP_REPTMP10H_CURTMP_SHIFT, minus49)); } static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t temp; temp = pci_read_config(dev, AMDTEMP_REPTMP_CTRL, 4); return (amdtemp_decode_fam10h_to_16h(sc->sc_offset, temp)); } static int32_t amdtemp_gettemp15hm60h(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t val; int error __diagused; error = amdsmn_read(sc->sc_smn, AMDTEMP_15H_M60H_REPTMP_CTRL, &val); KASSERT(error == 0, ("amdsmn_read")); return (amdtemp_decode_fam10h_to_16h(sc->sc_offset, val)); } static int32_t amdtemp_gettemp17h(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t val; int error __diagused; switch (sensor) { case CORE0_SENSOR0: /* Tctl */ error = amdsmn_read(sc->sc_smn, AMDTEMP_17H_CUR_TMP, &val); KASSERT(error == 0, ("amdsmn_read")); return (amdtemp_decode_fam17h_tctl(sc->sc_offset, val)); case CCD_BASE ... CCD_MAX: /* Tccd */ error = amdsmn_read(sc->sc_smn, sc->sc_temp_base + (((int)sensor - CCD_BASE) * sizeof(val)), &val); KASSERT(error == 0, ("amdsmn_read2")); KASSERT((val & AMDTEMP_17H_CCD_TMP_VALID) != 0, ("sensor %d: not valid", (int)sensor)); return (amdtemp_decode_fam10h_to_17h(sc->sc_offset, val, true)); default: __assert_unreachable(); } } static void amdtemp_probe_ccd_sensors(device_t dev, uint32_t maxreg) { char sensor_name[16], sensor_descr[32]; struct amdtemp_softc *sc; uint32_t i, val; int error; sc = device_get_softc(dev); for (i = 0; i < maxreg; i++) { error = amdsmn_read(sc->sc_smn, sc->sc_temp_base + (i * sizeof(val)), &val); if (error != 0) continue; if ((val & AMDTEMP_17H_CCD_TMP_VALID) == 0) continue; snprintf(sensor_name, sizeof(sensor_name), "ccd%u", i); snprintf(sensor_descr, sizeof(sensor_descr), "CCD %u temperature (Tccd%u)", i, i); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, sensor_name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CCD_BASE + i, amdtemp_sysctl, "IK", sensor_descr); } } static void amdtemp_probe_ccd_sensors17h(device_t dev, uint32_t model) { uint32_t maxreg; switch (model) { case 0x00 ... 0x2f: /* Zen1, Zen+ */ maxreg = 4; break; case 0x30 ... 0x3f: /* Zen2 TR (Castle Peak)/EPYC (Rome) */ case 0x60 ... 0x7f: /* Zen2 Ryzen (Renoir APU, Matisse) */ case 0x90 ... 0x9f: /* Zen2 Ryzen (Van Gogh APU) */ maxreg = 8; _Static_assert((int)NUM_CCDS >= 8, ""); break; default: device_printf(dev, "Unrecognized Family 17h Model: %02Xh\n", model); return; } amdtemp_probe_ccd_sensors(dev, maxreg); } static void amdtemp_probe_ccd_sensors19h(device_t dev, uint32_t model) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t maxreg; switch (model) { case 0x00 ... 0x0f: /* Zen3 EPYC "Milan" */ case 0x20 ... 0x2f: /* Zen3 Ryzen "Vermeer" */ case 0x50 ... 0x5f: /* Zen3 Ryzen "Cezanne" */ maxreg = 8; _Static_assert((int)NUM_CCDS >= 8, ""); break; case 0x10 ... 0x1f: /* Zen4 EPYC "Genoa" */ sc->sc_temp_base = AMDTEMP_ZEN4_10H_CCD_TMP_BASE; maxreg = 12; _Static_assert((int)NUM_CCDS >= 12, ""); break; case 0x40 ... 0x4f: /* Zen3+ Ryzen "Rembrandt" */ case 0x60 ... 0x6f: /* Zen4 Ryzen "Raphael" */ case 0x70 ... 0x7f: /* Zen4 Ryzen "Phoenix" */ sc->sc_temp_base = AMDTEMP_ZEN4_CCD_TMP_BASE; maxreg = 8; _Static_assert((int)NUM_CCDS >= 8, ""); break; default: device_printf(dev, "Unrecognized Family 19h Model: %02Xh\n", model); return; } amdtemp_probe_ccd_sensors(dev, maxreg); } static void amdtemp_probe_ccd_sensors1ah(device_t dev, uint32_t model) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t maxreg; switch (model) { case 0x00 ... 0x2f: /* Zen5 EPYC "Turin" */ case 0x40 ... 0x4f: /* Zen5 Ryzen "Granite Ridge" */ case 0x60 ... 0x7f: /* ??? */ sc->sc_temp_base = AMDTEMP_ZEN4_CCD_TMP_BASE; maxreg = 8; _Static_assert((int)NUM_CCDS >= 8, ""); break; default: device_printf(dev, "Unrecognized Family 1Ah Model: %02Xh\n", model); return; } amdtemp_probe_ccd_sensors(dev, maxreg); } diff --git a/sys/dev/atopcase/atopcase.c b/sys/dev/atopcase/atopcase.c index 9e64b389c9e3..8dc81046e47e 100644 --- a/sys/dev/atopcase/atopcase.c +++ b/sys/dev/atopcase/atopcase.c @@ -1,723 +1,723 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021-2023 Val Packett * Copyright (c) 2023 Vladimir Kondratyev * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include "opt_hid.h" #include "opt_spi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR atopcase_debug #include #include #include #include #include "spibus_if.h" #include "atopcase_reg.h" #include "atopcase_var.h" #define ATOPCASE_IN_KDB() (SCHEDULER_STOPPED() || kdb_active) #define ATOPCASE_IN_POLLING_MODE(sc) \ (((sc)->sc_gpe_bit == 0 && ((sc)->sc_irq_ih == NULL)) || cold ||\ ATOPCASE_IN_KDB()) #define ATOPCASE_WAKEUP(sc, chan) do { \ if (!ATOPCASE_IN_POLLING_MODE(sc)) { \ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wakeup: %p\n", chan); \ wakeup(chan); \ } \ } while (0) #define ATOPCASE_SPI_PAUSE() DELAY(100) #define ATOPCASE_SPI_NO_SLEEP_FLAG(sc) \ ((sc)->sc_irq_ih != NULL ? SPI_FLAG_NO_SLEEP : 0) /* Tunables */ static SYSCTL_NODE(_hw_hid, OID_AUTO, atopcase, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Apple MacBook Topcase HID driver"); #ifdef HID_DEBUG enum atopcase_log_level atopcase_debug = ATOPCASE_LLEVEL_DISABLED; SYSCTL_INT(_hw_hid_atopcase, OID_AUTO, debug, CTLFLAG_RWTUN, &atopcase_debug, ATOPCASE_LLEVEL_DISABLED, "atopcase log level"); #endif /* !HID_DEBUG */ static const uint8_t booted[] = { 0xa0, 0x80, 0x00, 0x00 }; static const uint8_t status_ok[] = { 0xac, 0x27, 0x68, 0xd5 }; static inline struct atopcase_child * atopcase_get_child_by_device(struct atopcase_softc *sc, uint8_t device) { switch (device) { case ATOPCASE_DEV_KBRD: return (&sc->sc_kb); case ATOPCASE_DEV_TPAD: return (&sc->sc_tp); default: return (NULL); } } static int atopcase_receive_status(struct atopcase_softc *sc) { struct spi_command cmd = SPI_COMMAND_INITIALIZER; uint8_t dummy_buffer[4] = { 0 }; uint8_t status_buffer[4] = { 0 }; int err; cmd.tx_cmd = dummy_buffer; cmd.tx_cmd_sz = sizeof(dummy_buffer); cmd.rx_cmd = status_buffer; cmd.rx_cmd_sz = sizeof(status_buffer); cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc); err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd); ATOPCASE_SPI_PAUSE(); if (err) { device_printf(sc->sc_dev, "SPI error: %d\n", err); return (err); } DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Status: %*D\n", 4, status_buffer, " "); if (memcmp(status_buffer, status_ok, sizeof(status_ok)) == 0) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Wrote command\n"); ATOPCASE_WAKEUP(sc, sc->sc_dev); } else { device_printf(sc->sc_dev, "Failed to write command\n"); return (EIO); } return (0); } static int atopcase_process_message(struct atopcase_softc *sc, uint8_t device, void *msg, uint16_t msg_len) { struct atopcase_header *hdr = msg; struct atopcase_child *ac; void *payload; uint16_t pl_len, crc; payload = (uint8_t *)msg + sizeof(*hdr); pl_len = le16toh(hdr->len); if (pl_len + sizeof(*hdr) + sizeof(crc) != msg_len) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "message with length overflow\n"); return (EIO); } crc = le16toh(*(uint16_t *)((uint8_t *)payload + pl_len)); if (crc != crc16(0, msg, msg_len - sizeof(crc))) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "message with failed checksum\n"); return (EIO); } #define CPOFF(dst, len, off) do { \ unsigned _len = le16toh(len); \ unsigned _off = le16toh(off); \ if (pl_len >= _len + _off) { \ memcpy(dst, (uint8_t*)payload + _off, MIN(_len, sizeof(dst)));\ (dst)[MIN(_len, sizeof(dst) - 1)] = '\0'; \ }} while (0); if ((ac = atopcase_get_child_by_device(sc, device)) != NULL && hdr->type == ATOPCASE_MSG_TYPE_REPORT(device)) { if (ac->open) ac->intr_handler(ac->intr_ctx, payload, pl_len); } else if (device == ATOPCASE_DEV_INFO && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE) && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) { struct atopcase_iface_info_payload *iface = payload; CPOFF(ac->name, iface->name_len, iface->name_off); DPRINTF("Interface #%d name: %s\n", ac->device, ac->name); } else if (device == ATOPCASE_DEV_INFO && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR) && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) { memcpy(ac->rdesc, payload, pl_len); ac->rdesc_len = ac->hw.rdescsize = pl_len; DPRINTF("%s HID report descriptor: %*D\n", ac->name, (int) ac->hw.rdescsize, ac->rdesc, " "); } else if (device == ATOPCASE_DEV_INFO && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE) && hdr->type_arg == ATOPCASE_INFO_DEVICE) { struct atopcase_device_info_payload *dev = payload; sc->sc_vid = le16toh(dev->vid); sc->sc_pid = le16toh(dev->pid); sc->sc_ver = le16toh(dev->ver); CPOFF(sc->sc_vendor, dev->vendor_len, dev->vendor_off); CPOFF(sc->sc_product, dev->product_len, dev->product_off); CPOFF(sc->sc_serial, dev->serial_len, dev->serial_off); if (bootverbose) { device_printf(sc->sc_dev, "Device info descriptor:\n"); printf(" Vendor: %s\n", sc->sc_vendor); printf(" Product: %s\n", sc->sc_product); printf(" Serial: %s\n", sc->sc_serial); } } return (0); } int atopcase_receive_packet(struct atopcase_softc *sc) { struct atopcase_packet pkt = { 0 }; struct spi_command cmd = SPI_COMMAND_INITIALIZER; void *msg; int err; uint16_t length, remaining, offset, msg_len; bzero(&sc->sc_junk, sizeof(struct atopcase_packet)); cmd.tx_cmd = &sc->sc_junk; cmd.tx_cmd_sz = sizeof(struct atopcase_packet); cmd.rx_cmd = &pkt; cmd.rx_cmd_sz = sizeof(struct atopcase_packet); cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc); err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd); ATOPCASE_SPI_PAUSE(); if (err) { device_printf(sc->sc_dev, "SPI error: %d\n", err); return (err); } DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Response: %*D\n", 256, &pkt, " "); if (le16toh(pkt.checksum) != crc16(0, &pkt, sizeof(pkt) - 2)) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "packet with failed checksum\n"); return (EIO); } /* * When we poll and nothing has arrived we get a particular packet * starting with '80 11 00 01' */ if (pkt.direction == ATOPCASE_DIR_NOTHING) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "'Nothing' packet: %*D\n", 4, &pkt, " "); return (EAGAIN); } if (pkt.direction != ATOPCASE_DIR_READ && pkt.direction != ATOPCASE_DIR_WRITE) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "unknown message direction 0x%x\n", pkt.direction); return (EIO); } length = le16toh(pkt.length); remaining = le16toh(pkt.remaining); offset = le16toh(pkt.offset); if (length > sizeof(pkt.data)) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "packet with length overflow: %u\n", length); return (EIO); } if (pkt.direction == ATOPCASE_DIR_READ && pkt.device == ATOPCASE_DEV_INFO && length == sizeof(booted) && memcmp(pkt.data, booted, length) == 0) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "GPE boot packet\n"); sc->sc_booted = true; ATOPCASE_WAKEUP(sc, sc); return (0); } /* handle multi-packet messages */ if (remaining != 0 || offset != 0) { if (offset != sc->sc_msg_len) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Unexpected offset (got %u, expected %u)\n", offset, sc->sc_msg_len); sc->sc_msg_len = 0; return (EIO); } if ((size_t)remaining + length + offset > sizeof(sc->sc_msg)) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Message with length overflow: %zu\n", (size_t)remaining + length + offset); sc->sc_msg_len = 0; return (EIO); } memcpy(sc->sc_msg + offset, &pkt.data, length); sc->sc_msg_len += length; if (remaining != 0) return (0); msg = sc->sc_msg; msg_len = sc->sc_msg_len; } else { msg = pkt.data; msg_len = length; } sc->sc_msg_len = 0; err = atopcase_process_message(sc, pkt.device, msg, msg_len); if (err == 0 && pkt.direction == ATOPCASE_DIR_WRITE) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Write ack\n"); ATOPCASE_WAKEUP(sc, sc); } return (err); } static int atopcase_send(struct atopcase_softc *sc, struct atopcase_packet *pkt) { struct spi_command cmd = SPI_COMMAND_INITIALIZER; int err, retries; cmd.tx_cmd = pkt; cmd.tx_cmd_sz = sizeof(struct atopcase_packet); cmd.rx_cmd = &sc->sc_junk; cmd.rx_cmd_sz = sizeof(struct atopcase_packet); cmd.flags = SPI_FLAG_KEEP_CS | ATOPCASE_SPI_NO_SLEEP_FLAG(sc); DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Request: %*D\n", (int)sizeof(struct atopcase_packet), cmd.tx_cmd, " "); if (!ATOPCASE_IN_POLLING_MODE(sc)) { if (sc->sc_irq_ih != NULL) mtx_lock(&sc->sc_mtx); else sx_xlock(&sc->sc_sx); } sc->sc_wait_for_status = true; err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd); ATOPCASE_SPI_PAUSE(); if (!ATOPCASE_IN_POLLING_MODE(sc)) { if (sc->sc_irq_ih != NULL) mtx_unlock(&sc->sc_mtx); else sx_xunlock(&sc->sc_sx); } if (err != 0) { device_printf(sc->sc_dev, "SPI error: %d\n", err); goto exit; } if (ATOPCASE_IN_POLLING_MODE(sc)) { err = atopcase_receive_status(sc); } else { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc->sc_dev); err = tsleep(sc->sc_dev, 0, "atcstat", hz / 10); } sc->sc_wait_for_status = false; if (err != 0) { DPRINTF("Write status read failed: %d\n", err); goto exit; } if (ATOPCASE_IN_POLLING_MODE(sc)) { /* Backlight setting may require a lot of time */ retries = 20; while ((err = atopcase_receive_packet(sc)) == EAGAIN && --retries != 0) DELAY(1000); } else { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc); err = tsleep(sc, 0, "atcack", hz / 10); } if (err != 0) DPRINTF("Write ack read failed: %d\n", err); exit: if (err == EWOULDBLOCK) err = EIO; return (err); } static void atopcase_create_message(struct atopcase_packet *pkt, uint8_t device, uint16_t type, uint8_t type_arg, const void *payload, uint8_t len, uint16_t resp_len) { struct atopcase_header *hdr = (struct atopcase_header *)pkt->data; uint16_t msg_checksum; static uint8_t seq_no; KASSERT(len <= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header), ("outgoing msg must be 1 packet")); bzero(pkt, sizeof(struct atopcase_packet)); pkt->direction = ATOPCASE_DIR_WRITE; pkt->device = device; pkt->length = htole16(sizeof(*hdr) + len + 2); hdr->type = htole16(type); hdr->type_arg = type_arg; hdr->seq_no = seq_no++; hdr->resp_len = htole16((resp_len == 0) ? len : resp_len); hdr->len = htole16(len); memcpy(pkt->data + sizeof(*hdr), payload, len); msg_checksum = htole16(crc16(0, pkt->data, pkt->length - 2)); memcpy(pkt->data + sizeof(*hdr) + len, &msg_checksum, 2); pkt->checksum = htole16(crc16(0, (uint8_t*)pkt, sizeof(*pkt) - 2)); return; } static int atopcase_request_desc(struct atopcase_softc *sc, uint16_t type, uint8_t device) { atopcase_create_message( &sc->sc_buf, ATOPCASE_DEV_INFO, type, device, NULL, 0, 0x200); return (atopcase_send(sc, &sc->sc_buf)); } int atopcase_intr(struct atopcase_softc *sc) { int err; DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Interrupt event\n"); if (sc->sc_wait_for_status) { err = atopcase_receive_status(sc); sc->sc_wait_for_status = false; } else err = atopcase_receive_packet(sc); return (err); } static int atopcase_add_child(struct atopcase_softc *sc, struct atopcase_child *ac, uint8_t device) { device_t hidbus; int err = 0; ac->device = device; /* fill device info */ strlcpy(ac->hw.name, "Apple MacBook", sizeof(ac->hw.name)); ac->hw.idBus = BUS_SPI; ac->hw.idVendor = sc->sc_vid; ac->hw.idProduct = sc->sc_pid; ac->hw.idVersion = sc->sc_ver; strlcpy(ac->hw.idPnP, sc->sc_hid, sizeof(ac->hw.idPnP)); strlcpy(ac->hw.serial, sc->sc_serial, sizeof(ac->hw.serial)); /* * HID write and set_report methods executed on Apple SPI topcase * hardware do the same request on SPI layer. Set HQ_NOWRITE quirk to * force hidmap to convert writes to set_reports. That makes HID bus * write handler unnecessary and reduces code duplication. */ hid_add_dynamic_quirk(&ac->hw, HQ_NOWRITE); DPRINTF("Get the interface #%d descriptor\n", device); err = atopcase_request_desc(sc, ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE), device); if (err) { device_printf(sc->sc_dev, "can't receive iface descriptor\n"); goto exit; } DPRINTF("Get the \"%s\" HID report descriptor\n", ac->name); err = atopcase_request_desc(sc, ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR), device); if (err) { device_printf(sc->sc_dev, "can't receive report descriptor\n"); goto exit; } - hidbus = device_add_child(sc->sc_dev, "hidbus", -1); + hidbus = device_add_child(sc->sc_dev, "hidbus", DEVICE_UNIT_ANY); if (hidbus == NULL) { device_printf(sc->sc_dev, "can't add child\n"); err = ENOMEM; goto exit; } device_set_ivars(hidbus, &ac->hw); ac->hidbus = hidbus; exit: return (err); } int atopcase_init(struct atopcase_softc *sc) { int err; /* Wait until we know we're getting reasonable responses */ if(!sc->sc_booted && tsleep(sc, 0, "atcboot", hz / 20) != 0) { device_printf(sc->sc_dev, "can't establish communication\n"); err = EIO; goto err; } /* * Management device may send a message on first boot after power off. * Let interrupt handler to read and discard it. */ DELAY(2000); DPRINTF("Get the device descriptor\n"); err = atopcase_request_desc(sc, ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE), ATOPCASE_INFO_DEVICE); if (err) { device_printf(sc->sc_dev, "can't receive device descriptor\n"); goto err; } err = atopcase_add_child(sc, &sc->sc_kb, ATOPCASE_DEV_KBRD); if (err != 0) goto err; err = atopcase_add_child(sc, &sc->sc_tp, ATOPCASE_DEV_TPAD); if (err != 0) goto err; /* TODO: skip on 2015 models where it's controlled by asmc */ sc->sc_backlight = backlight_register("atopcase", sc->sc_dev); if (!sc->sc_backlight) { device_printf(sc->sc_dev, "can't register backlight\n"); err = ENOMEM; } if (sc->sc_tq != NULL) taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task, hz / 120); bus_attach_children(sc->sc_dev); return (0); err: return (err); } int atopcase_destroy(struct atopcase_softc *sc) { int err; err = bus_generic_detach(sc->sc_dev); if (err) return (err); if (sc->sc_backlight) backlight_destroy(sc->sc_backlight); return (0); } static struct atopcase_child * atopcase_get_child_by_hidbus(device_t child) { device_t parent = device_get_parent(child); struct atopcase_softc *sc = device_get_softc(parent); if (child == sc->sc_kb.hidbus) return (&sc->sc_kb); if (child == sc->sc_tp.hidbus) return (&sc->sc_tp); panic("unknown child"); } void atopcase_intr_setup(device_t dev, device_t child, hid_intr_t intr, void *context, struct hid_rdesc_info *rdesc) { struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (intr == NULL) return; rdesc->rdsize = ATOPCASE_MSG_SIZE - sizeof(struct atopcase_header) - 2; rdesc->grsize = 0; rdesc->srsize = ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2; rdesc->wrsize = 0; ac->intr_handler = intr; ac->intr_ctx = context; } void atopcase_intr_unsetup(device_t dev, device_t child) { } int atopcase_intr_start(device_t dev, device_t child) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_lock(&sc->sc_mtx); else sx_xlock(&sc->sc_sx); ac->open = true; if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xunlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_unlock(&sc->sc_mtx); else sx_xunlock(&sc->sc_sx); return (0); } int atopcase_intr_stop(device_t dev, device_t child) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_lock(&sc->sc_mtx); else sx_xlock(&sc->sc_sx); ac->open = false; if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xunlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_unlock(&sc->sc_mtx); else sx_xunlock(&sc->sc_sx); return (0); } void atopcase_intr_poll(device_t dev, device_t child) { struct atopcase_softc *sc = device_get_softc(dev); (void)atopcase_receive_packet(sc); } int atopcase_get_rdesc(device_t dev, device_t child, void *buf, hid_size_t len) { struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (ac->rdesc_len != len) return (ENXIO); memcpy(buf, ac->rdesc, len); return (0); } int atopcase_set_report(device_t dev, device_t child, const void *buf, hid_size_t len, uint8_t type __unused, uint8_t id) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); int err; if (len >= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2) return (EINVAL); DPRINTF("%s HID command SET_REPORT %d (len %d): %*D\n", ac->name, id, len, len, buf, " "); if (!ATOPCASE_IN_KDB()) sx_xlock(&sc->sc_write_sx); atopcase_create_message(&sc->sc_buf, ac->device, ATOPCASE_MSG_TYPE_SET_REPORT(ac->device, id), 0, buf, len, 0); err = atopcase_send(sc, &sc->sc_buf); if (!ATOPCASE_IN_KDB()) sx_xunlock(&sc->sc_write_sx); return (err); } int atopcase_backlight_update_status(device_t dev, struct backlight_props *props) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_bl_payload payload = { 0 }; payload.report_id = ATOPCASE_BKL_REPORT_ID; payload.device = ATOPCASE_DEV_KBRD; /* * Hardware range is 32-255 for visible backlight, * convert from percentages */ payload.level = (props->brightness == 0) ? 0 : (32 + (223 * props->brightness / 100)); payload.status = (payload.level > 0) ? 0x01F4 : 0x1; return (atopcase_set_report(dev, sc->sc_kb.hidbus, &payload, sizeof(payload), HID_OUTPUT_REPORT, ATOPCASE_BKL_REPORT_ID)); } int atopcase_backlight_get_status(device_t dev, struct backlight_props *props) { struct atopcase_softc *sc = device_get_softc(dev); props->brightness = sc->sc_backlight_level; props->nlevels = 0; return (0); } int atopcase_backlight_get_info(device_t dev, struct backlight_info *info) { info->type = BACKLIGHT_TYPE_KEYBOARD; strlcpy(info->name, "Apple MacBook Keyboard", BACKLIGHTMAXNAMELENGTH); return (0); } diff --git a/sys/dev/bhnd/bhnd_subr.c b/sys/dev/bhnd/bhnd_subr.c index 1c456ed1cddf..4818fffd5659 100644 --- a/sys/dev/bhnd/bhnd_subr.c +++ b/sys/dev/bhnd/bhnd_subr.c @@ -1,2320 +1,2320 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015-2016 Landon Fuller * Copyright (c) 2017 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Landon Fuller * under sponsorship from the FreeBSD Foundation. * * 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, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include #include #include #include #include #include #include #include #include #include "nvram/bhnd_nvram.h" #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhnd_nvram_map.h" #include "bhndreg.h" #include "bhndvar.h" #include "bhnd_private.h" static void bhnd_service_registry_free_entry( struct bhnd_service_entry *entry); static int compare_ascending_probe_order(const void *lhs, const void *rhs); static int compare_descending_probe_order(const void *lhs, const void *rhs); /* BHND core device description table. */ static const struct bhnd_core_desc { uint16_t vendor; uint16_t device; bhnd_devclass_t class; const char *desc; } bhnd_core_descs[] = { #define BHND_CDESC(_mfg, _cid, _cls, _desc) \ { BHND_MFGID_ ## _mfg, BHND_COREID_ ## _cid, \ BHND_DEVCLASS_ ## _cls, _desc } BHND_CDESC(BCM, CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, ILINE20, OTHER, "iLine20 HPNA"), BHND_CDESC(BCM, SRAM, RAM, "SRAM"), BHND_CDESC(BCM, SDRAM, RAM, "SDRAM"), BHND_CDESC(BCM, PCI, PCI, "PCI Bridge"), BHND_CDESC(BCM, MIPS, CPU, "BMIPS CPU"), BHND_CDESC(BCM, ENET, ENET_MAC, "Fast Ethernet MAC"), BHND_CDESC(BCM, V90_CODEC, SOFTMODEM, "V.90 SoftModem Codec"), BHND_CDESC(BCM, USB, USB_DUAL, "USB 1.1 Device/Host Controller"), BHND_CDESC(BCM, ADSL, OTHER, "ADSL Core"), BHND_CDESC(BCM, ILINE100, OTHER, "iLine100 HPNA"), BHND_CDESC(BCM, IPSEC, OTHER, "IPsec Accelerator"), BHND_CDESC(BCM, UTOPIA, OTHER, "UTOPIA ATM Core"), BHND_CDESC(BCM, PCMCIA, PCCARD, "PCMCIA Bridge"), BHND_CDESC(BCM, SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, MEMC, MEMC, "MEMC SDRAM Controller"), BHND_CDESC(BCM, OFDM, OTHER, "OFDM PHY"), BHND_CDESC(BCM, EXTIF, OTHER, "External Interface"), BHND_CDESC(BCM, D11, WLAN, "802.11 MAC/PHY/Radio"), BHND_CDESC(BCM, APHY, WLAN_PHY, "802.11a PHY"), BHND_CDESC(BCM, BPHY, WLAN_PHY, "802.11b PHY"), BHND_CDESC(BCM, GPHY, WLAN_PHY, "802.11g PHY"), BHND_CDESC(BCM, MIPS33, CPU, "BMIPS33 CPU"), BHND_CDESC(BCM, USB11H, USB_HOST, "USB 1.1 Host Controller"), BHND_CDESC(BCM, USB11D, USB_DEV, "USB 1.1 Device Controller"), BHND_CDESC(BCM, USB20H, USB_HOST, "USB 2.0 Host Controller"), BHND_CDESC(BCM, USB20D, USB_DEV, "USB 2.0 Device Controller"), BHND_CDESC(BCM, SDIOH, OTHER, "SDIO Host Controller"), BHND_CDESC(BCM, ROBO, OTHER, "RoboSwitch"), BHND_CDESC(BCM, ATA100, OTHER, "Parallel ATA Controller"), BHND_CDESC(BCM, SATAXOR, OTHER, "SATA DMA/XOR Controller"), BHND_CDESC(BCM, GIGETH, ENET_MAC, "Gigabit Ethernet MAC"), BHND_CDESC(BCM, PCIE, PCIE, "PCIe Bridge"), BHND_CDESC(BCM, NPHY, WLAN_PHY, "802.11n 2x2 PHY"), BHND_CDESC(BCM, SRAMC, MEMC, "SRAM Controller"), BHND_CDESC(BCM, MINIMAC, OTHER, "MINI MAC/PHY"), BHND_CDESC(BCM, ARM11, CPU, "ARM1176 CPU"), BHND_CDESC(BCM, ARM7S, CPU, "ARM7TDMI-S CPU"), BHND_CDESC(BCM, LPPHY, WLAN_PHY, "802.11a/b/g PHY"), BHND_CDESC(BCM, PMU, PMU, "PMU"), BHND_CDESC(BCM, SSNPHY, WLAN_PHY, "802.11n Single-Stream PHY"), BHND_CDESC(BCM, SDIOD, OTHER, "SDIO Device Core"), BHND_CDESC(BCM, ARMCM3, CPU, "ARM Cortex-M3 CPU"), BHND_CDESC(BCM, HTPHY, WLAN_PHY, "802.11n 4x4 PHY"), BHND_CDESC(MIPS,MIPS74K, CPU, "MIPS74k CPU"), BHND_CDESC(BCM, GMAC, ENET_MAC, "Gigabit MAC core"), BHND_CDESC(BCM, DMEMC, MEMC, "DDR1/DDR2 Memory Controller"), BHND_CDESC(BCM, PCIERC, OTHER, "PCIe Root Complex"), BHND_CDESC(BCM, OCP, SOC_BRIDGE, "OCP to OCP Bridge"), BHND_CDESC(BCM, SC, OTHER, "Shared Common Core"), BHND_CDESC(BCM, AHB, SOC_BRIDGE, "OCP to AHB Bridge"), BHND_CDESC(BCM, SPIH, OTHER, "SPI Host Controller"), BHND_CDESC(BCM, I2S, OTHER, "I2S Digital Audio Interface"), BHND_CDESC(BCM, DMEMS, MEMC, "SDR/DDR1 Memory Controller"), BHND_CDESC(BCM, UBUS_SHIM, OTHER, "BCM6362/UBUS WLAN SHIM"), BHND_CDESC(BCM, PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(ARM, APB_BRIDGE, SOC_BRIDGE, "BP135 AMBA3 AXI to APB Bridge"), BHND_CDESC(ARM, PL301, SOC_ROUTER, "PL301 AMBA3 Interconnect"), BHND_CDESC(ARM, EROM, EROM, "PL366 Device Enumeration ROM"), BHND_CDESC(ARM, OOB_ROUTER, OTHER, "PL367 OOB Interrupt Router"), BHND_CDESC(ARM, AXI_UNMAPPED, OTHER, "Unmapped Address Ranges"), BHND_CDESC(BCM, 4706_CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, NS_PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(BCM, NS_DMA, OTHER, "DMA engine"), BHND_CDESC(BCM, NS_SDIO, OTHER, "SDIO 3.0 Host Controller"), BHND_CDESC(BCM, NS_USB20H, USB_HOST, "USB 2.0 Host Controller"), BHND_CDESC(BCM, NS_USB30H, USB_HOST, "USB 3.0 Host Controller"), BHND_CDESC(BCM, NS_A9JTAG, OTHER, "ARM Cortex A9 JTAG Interface"), BHND_CDESC(BCM, NS_DDR23_MEMC, MEMC, "Denali DDR2/DD3 Memory Controller"), BHND_CDESC(BCM, NS_ROM, NVRAM, "System ROM"), BHND_CDESC(BCM, NS_NAND, NVRAM, "NAND Flash Controller"), BHND_CDESC(BCM, NS_QSPI, NVRAM, "QSPI Flash Controller"), BHND_CDESC(BCM, NS_CC_B, CC_B, "ChipCommon B Auxiliary I/O Controller"), BHND_CDESC(BCM, 4706_SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, IHOST_ARMCA9, CPU, "ARM Cortex A9 CPU"), BHND_CDESC(BCM, 4706_GMAC_CMN, ENET, "Gigabit MAC (Common)"), BHND_CDESC(BCM, 4706_GMAC, ENET_MAC, "Gigabit MAC"), BHND_CDESC(BCM, AMEMC, MEMC, "Denali DDR1/DDR2 Memory Controller"), #undef BHND_CDESC /* Derived from inspection of the BCM4331 cores that provide PrimeCell * IDs. Due to lack of documentation, the surmised device name/purpose * provided here may be incorrect. */ { BHND_MFGID_ARM, BHND_PRIMEID_EROM, BHND_DEVCLASS_OTHER, "PL364 Device Enumeration ROM" }, { BHND_MFGID_ARM, BHND_PRIMEID_SWRAP, BHND_DEVCLASS_OTHER, "PL368 Device Management Interface" }, { BHND_MFGID_ARM, BHND_PRIMEID_MWRAP, BHND_DEVCLASS_OTHER, "PL369 Device Management Interface" }, { 0, 0, 0, NULL } }; static const struct bhnd_device_quirk bhnd_chipc_clkctl_quirks[]; static const struct bhnd_device_quirk bhnd_pcmcia_clkctl_quirks[]; /** * Device table entries for core-specific CLKCTL quirk lookup. */ static const struct bhnd_device bhnd_clkctl_devices[] = { BHND_DEVICE(BCM, CC, NULL, bhnd_chipc_clkctl_quirks), BHND_DEVICE(BCM, PCMCIA, NULL, bhnd_pcmcia_clkctl_quirks), BHND_DEVICE_END, }; /** ChipCommon CLKCTL quirks */ static const struct bhnd_device_quirk bhnd_chipc_clkctl_quirks[] = { /* HTAVAIL/ALPAVAIL are bitswapped in chipc's CLKCTL */ BHND_CHIP_QUIRK(4328, HWREV_ANY, BHND_CLKCTL_QUIRK_CCS0), BHND_CHIP_QUIRK(5354, HWREV_ANY, BHND_CLKCTL_QUIRK_CCS0), BHND_DEVICE_QUIRK_END }; /** PCMCIA CLKCTL quirks */ static const struct bhnd_device_quirk bhnd_pcmcia_clkctl_quirks[] = { /* HTAVAIL/ALPAVAIL are bitswapped in pcmcia's CLKCTL */ BHND_CHIP_QUIRK(4328, HWREV_ANY, BHND_CLKCTL_QUIRK_CCS0), BHND_CHIP_QUIRK(5354, HWREV_ANY, BHND_CLKCTL_QUIRK_CCS0), BHND_DEVICE_QUIRK_END }; /** * Return the name for a given JEP106 manufacturer ID. * * @param vendor A JEP106 Manufacturer ID, including the non-standard ARM 4-bit * JEP106 continuation code. */ const char * bhnd_vendor_name(uint16_t vendor) { switch (vendor) { case BHND_MFGID_ARM: return "ARM"; case BHND_MFGID_BCM: return "Broadcom"; case BHND_MFGID_MIPS: return "MIPS"; default: return "unknown"; } } /** * Return the name of a port type. * * @param port_type The port type to look up. */ const char * bhnd_port_type_name(bhnd_port_type port_type) { switch (port_type) { case BHND_PORT_DEVICE: return ("device"); case BHND_PORT_BRIDGE: return ("bridge"); case BHND_PORT_AGENT: return ("agent"); default: return "unknown"; } } /** * Return the name of an NVRAM source. * * @param nvram_src The NVRAM source type to look up. */ const char * bhnd_nvram_src_name(bhnd_nvram_src nvram_src) { switch (nvram_src) { case BHND_NVRAM_SRC_FLASH: return ("flash"); case BHND_NVRAM_SRC_OTP: return ("OTP"); case BHND_NVRAM_SRC_SPROM: return ("SPROM"); case BHND_NVRAM_SRC_UNKNOWN: return ("none"); default: return ("unknown"); } } static const struct bhnd_core_desc * bhnd_find_core_desc(uint16_t vendor, uint16_t device) { for (u_int i = 0; bhnd_core_descs[i].desc != NULL; i++) { if (bhnd_core_descs[i].vendor != vendor) continue; if (bhnd_core_descs[i].device != device) continue; return (&bhnd_core_descs[i]); } return (NULL); } /** * Return a human-readable name for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID. * @param device The core identifier. */ const char * bhnd_find_core_name(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return ("unknown"); return desc->desc; } /** * Return the device class for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID. * @param device The core identifier. */ bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return (BHND_DEVCLASS_OTHER); return desc->class; } /** * Return a human-readable name for a BHND core. * * @param ci The core's info record. */ const char * bhnd_core_name(const struct bhnd_core_info *ci) { return bhnd_find_core_name(ci->vendor, ci->device); } /** * Return the device class for a BHND core. * * @param ci The core's info record. */ bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci) { return bhnd_find_core_class(ci->vendor, ci->device); } /** * Write a human readable name representation of the given * BHND_CHIPID_* constant to @p buffer. * * @param buffer Output buffer, or NULL to compute the required size. * @param size Capacity of @p buffer, in bytes. * @param chip_id Chip ID to be formatted. * * @return The required number of bytes on success, or a negative integer on * failure. No more than @p size-1 characters be written, with the @p size'th * set to '\0'. * * @sa BHND_CHIPID_MAX_NAMELEN */ int bhnd_format_chip_id(char *buffer, size_t size, uint16_t chip_id) { /* All hex formatted IDs are within the range of 0x4000-0x9C3F (40000-1) */ if (chip_id >= 0x4000 && chip_id <= 0x9C3F) return (snprintf(buffer, size, "BCM%hX", chip_id)); else return (snprintf(buffer, size, "BCM%hu", chip_id)); } /** * Return a core info record populated from a bhnd-attached @p dev. * * @param dev A bhnd device. * * @return A core info record for @p dev. */ struct bhnd_core_info bhnd_get_core_info(device_t dev) { return (struct bhnd_core_info) { .vendor = bhnd_get_vendor(dev), .device = bhnd_get_device(dev), .hwrev = bhnd_get_hwrev(dev), .core_idx = bhnd_get_core_index(dev), .unit = bhnd_get_core_unit(dev) }; } /** * Find a @p class child device with @p unit on @p bus. * * @param bus The bhnd-compatible bus to be searched. * @param class The device class to match on. * @param unit The core unit number; specify -1 to return the first match * regardless of unit number. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_bus_find_child(device_t bus, bhnd_devclass_t class, int unit) { struct bhnd_core_match md = { BHND_MATCH_CORE_CLASS(class), BHND_MATCH_CORE_UNIT(unit) }; if (unit == -1) md.m.match.core_unit = 0; return bhnd_bus_match_child(bus, &md); } /** * Find the first child device on @p bus that matches @p desc. * * @param bus The bhnd-compatible bus to be searched. * @param desc A match descriptor. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_bus_match_child(device_t bus, const struct bhnd_core_match *desc) { device_t *devlistp; device_t match; int devcnt; int error; error = device_get_children(bus, &devlistp, &devcnt); if (error != 0) return (NULL); match = NULL; for (int i = 0; i < devcnt; i++) { struct bhnd_core_info ci = bhnd_get_core_info(devlistp[i]); if (bhnd_core_matches(&ci, desc)) { match = devlistp[i]; goto done; } } done: free(devlistp, M_TEMP); return match; } /** * Retrieve an ordered list of all device instances currently connected to * @p bus, returning a pointer to the array in @p devlistp and the count * in @p ndevs. * * The memory allocated for the table must be freed via * bhnd_bus_free_children(). * * @param bus The bhnd-compatible bus to be queried. * @param[out] devlist The array of devices. * @param[out] devcount The number of devices in @p devlistp * @param order The order in which devices will be returned * in @p devlist. * * @retval 0 success * @retval non-zero if an error occurs, a regular unix error code will * be returned. */ int bhnd_bus_get_children(device_t bus, device_t **devlist, int *devcount, bhnd_device_order order) { int error; /* Fetch device array */ if ((error = device_get_children(bus, devlist, devcount))) return (error); /* Perform requested sorting */ if ((error = bhnd_sort_devices(*devlist, *devcount, order))) { bhnd_bus_free_children(*devlist); return (error); } return (0); } /** * Free any memory allocated in a previous call to bhnd_bus_get_children(). * * @param devlist The device array returned by bhnd_bus_get_children(). */ void bhnd_bus_free_children(device_t *devlist) { free(devlist, M_TEMP); } /** * Perform in-place sorting of an array of bhnd device instances. * * @param devlist An array of bhnd devices. * @param devcount The number of devices in @p devs. * @param order The sort order to be used. * * @retval 0 success * @retval EINVAL if the sort order is unknown. */ int bhnd_sort_devices(device_t *devlist, size_t devcount, bhnd_device_order order) { int (*compare)(const void *, const void *); switch (order) { case BHND_DEVICE_ORDER_ATTACH: compare = compare_ascending_probe_order; break; case BHND_DEVICE_ORDER_DETACH: compare = compare_descending_probe_order; break; default: printf("unknown sort order: %d\n", order); return (EINVAL); } qsort(devlist, devcount, sizeof(*devlist), compare); return (0); } /* * Ascending comparison of bhnd device's probe order. */ static int compare_ascending_probe_order(const void *lhs, const void *rhs) { device_t ldev, rdev; int lorder, rorder; ldev = (*(const device_t *) lhs); rdev = (*(const device_t *) rhs); lorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(ldev), ldev); rorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(rdev), rdev); if (lorder < rorder) { return (-1); } else if (lorder > rorder) { return (1); } else { return (0); } } /* * Descending comparison of bhnd device's probe order. */ static int compare_descending_probe_order(const void *lhs, const void *rhs) { return (compare_ascending_probe_order(rhs, lhs)); } /** * Call device_probe_and_attach() for each of the bhnd bus device's * children, in bhnd attach order. * * @param bus The bhnd-compatible bus for which all children should be probed * and attached. */ int bhnd_bus_probe_children(device_t bus) { device_t *devs; int ndevs; int error; /* Fetch children in attach order */ error = bhnd_bus_get_children(bus, &devs, &ndevs, BHND_DEVICE_ORDER_ATTACH); if (error) return (error); /* Probe and attach all children */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; device_probe_and_attach(child); } bhnd_bus_free_children(devs); return (0); } /** * Walk up the bhnd device hierarchy to locate the root device * to which the bhndb bridge is attached. * * This can be used from within bhnd host bridge drivers to locate the * actual upstream host device. * * @param dev A bhnd device. * @param bus_class The expected bus (e.g. "pci") to which the bridge root * should be attached. * * @retval device_t if a matching parent device is found. * @retval NULL if @p dev is not attached via a bhndb bus. * @retval NULL if no parent device is attached via @p bus_class. */ device_t bhnd_find_bridge_root(device_t dev, devclass_t bus_class) { devclass_t bhndb_class; device_t parent; KASSERT(device_get_devclass(device_get_parent(dev)) == devclass_find("bhnd"), ("%s not a bhnd device", device_get_nameunit(dev))); bhndb_class = devclass_find("bhndb"); /* Walk the device tree until we hit a bridge */ parent = dev; while ((parent = device_get_parent(parent)) != NULL) { if (device_get_devclass(parent) == bhndb_class) break; } /* No bridge? */ if (parent == NULL) return (NULL); /* Search for a parent attached to the expected bus class */ while ((parent = device_get_parent(parent)) != NULL) { device_t bus; bus = device_get_parent(parent); if (bus != NULL && device_get_devclass(bus) == bus_class) return (parent); } /* Not found */ return (NULL); } /** * Find the first core in @p cores that matches @p desc. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_match_core(const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc) { for (u_int i = 0; i < num_cores; i++) { if (bhnd_core_matches(&cores[i], desc)) return &cores[i]; } return (NULL); } /** * Find the first core in @p cores with the given @p class. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param class The device class to match on. * * @retval non-NULL if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_find_core(const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class) { struct bhnd_core_match md = { BHND_MATCH_CORE_CLASS(class) }; return bhnd_match_core(cores, num_cores, &md); } /** * Create an equality match descriptor for @p core. * * @param core The core info to be matched on. * * @return an equality match descriptor for @p core. */ struct bhnd_core_match bhnd_core_get_match_desc(const struct bhnd_core_info *core) { return ((struct bhnd_core_match) { BHND_MATCH_CORE_VENDOR(core->vendor), BHND_MATCH_CORE_ID(core->device), BHND_MATCH_CORE_REV(HWREV_EQ(core->hwrev)), BHND_MATCH_CORE_CLASS(bhnd_core_class(core)), BHND_MATCH_CORE_IDX(core->core_idx), BHND_MATCH_CORE_UNIT(core->unit) }); } /** * Return true if the @p lhs is equal to @p rhs. * * @param lhs The first bhnd core descriptor to compare. * @param rhs The second bhnd core descriptor to compare. * * @retval true if @p lhs is equal to @p rhs * @retval false if @p lhs is not equal to @p rhs */ bool bhnd_cores_equal(const struct bhnd_core_info *lhs, const struct bhnd_core_info *rhs) { struct bhnd_core_match md; /* Use an equality match descriptor to perform the comparison */ md = bhnd_core_get_match_desc(rhs); return (bhnd_core_matches(lhs, &md)); } /** * Return true if the @p core matches @p desc. * * @param core A bhnd core descriptor. * @param desc A match descriptor to compare against @p core. * * @retval true if @p core matches @p match. * @retval false if @p core does not match @p match. */ bool bhnd_core_matches(const struct bhnd_core_info *core, const struct bhnd_core_match *desc) { if (desc->m.match.core_vendor && desc->core_vendor != core->vendor) return (false); if (desc->m.match.core_id && desc->core_id != core->device) return (false); if (desc->m.match.core_unit && desc->core_unit != core->unit) return (false); if (desc->m.match.core_rev && !bhnd_hwrev_matches(core->hwrev, &desc->core_rev)) return (false); if (desc->m.match.core_idx && desc->core_idx != core->core_idx) return (false); if (desc->m.match.core_class && desc->core_class != bhnd_core_class(core)) return (false); return true; } /** * Return true if the @p chip matches @p desc. * * @param chip A bhnd chip identifier. * @param desc A match descriptor to compare against @p chip. * * @retval true if @p chip matches @p match. * @retval false if @p chip does not match @p match. */ bool bhnd_chip_matches(const struct bhnd_chipid *chip, const struct bhnd_chip_match *desc) { if (desc->m.match.chip_id && chip->chip_id != desc->chip_id) return (false); if (desc->m.match.chip_pkg && chip->chip_pkg != desc->chip_pkg) return (false); if (desc->m.match.chip_rev && !bhnd_hwrev_matches(chip->chip_rev, &desc->chip_rev)) return (false); if (desc->m.match.chip_type && chip->chip_type != desc->chip_type) return (false); return (true); } /** * Return true if the @p board matches @p desc. * * @param board The bhnd board info. * @param desc A match descriptor to compare against @p board. * * @retval true if @p chip matches @p match. * @retval false if @p chip does not match @p match. */ bool bhnd_board_matches(const struct bhnd_board_info *board, const struct bhnd_board_match *desc) { if (desc->m.match.board_srom_rev && !bhnd_hwrev_matches(board->board_srom_rev, &desc->board_srom_rev)) return (false); if (desc->m.match.board_vendor && board->board_vendor != desc->board_vendor) return (false); if (desc->m.match.board_type && board->board_type != desc->board_type) return (false); if (desc->m.match.board_devid && board->board_devid != desc->board_devid) return (false); if (desc->m.match.board_rev && !bhnd_hwrev_matches(board->board_rev, &desc->board_rev)) return (false); return (true); } /** * Return true if the @p hwrev matches @p desc. * * @param hwrev A bhnd hardware revision. * @param desc A match descriptor to compare against @p core. * * @retval true if @p hwrev matches @p match. * @retval false if @p hwrev does not match @p match. */ bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc) { if (desc->start != BHND_HWREV_INVALID && desc->start > hwrev) return false; if (desc->end != BHND_HWREV_INVALID && desc->end < hwrev) return false; return true; } /** * Return true if the @p dev matches @p desc. * * @param dev A bhnd device. * @param desc A match descriptor to compare against @p dev. * * @retval true if @p dev matches @p match. * @retval false if @p dev does not match @p match. */ bool bhnd_device_matches(device_t dev, const struct bhnd_device_match *desc) { struct bhnd_core_info core; const struct bhnd_chipid *chip; struct bhnd_board_info board; device_t parent; int error; /* Construct individual match descriptors */ struct bhnd_core_match m_core = { _BHND_CORE_MATCH_COPY(desc) }; struct bhnd_chip_match m_chip = { _BHND_CHIP_MATCH_COPY(desc) }; struct bhnd_board_match m_board = { _BHND_BOARD_MATCH_COPY(desc) }; /* Fetch and match core info */ if (m_core.m.match_flags) { /* Only applicable to bhnd-attached cores */ parent = device_get_parent(dev); if (device_get_devclass(parent) != devclass_find("bhnd")) { device_printf(dev, "attempting to match core " "attributes against non-core device\n"); return (false); } core = bhnd_get_core_info(dev); if (!bhnd_core_matches(&core, &m_core)) return (false); } /* Fetch and match chip info */ if (m_chip.m.match_flags) { chip = bhnd_get_chipid(dev); if (!bhnd_chip_matches(chip, &m_chip)) return (false); } /* Fetch and match board info. * * This is not available until after NVRAM is up; earlier device * matches should not include board requirements */ if (m_board.m.match_flags) { if ((error = bhnd_read_board_info(dev, &board))) { device_printf(dev, "failed to read required board info " "during device matching: %d\n", error); return (false); } if (!bhnd_board_matches(&board, &m_board)) return (false); } /* All matched */ return (true); } /** * Search @p table for an entry matching @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * @param entry_size The @p table entry size, in bytes. * * @retval non-NULL the first matching device, if any. * @retval NULL if no matching device is found in @p table. */ const struct bhnd_device * bhnd_device_lookup(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *entry; device_t hostb, parent; bhnd_attach_type attach_type; uint32_t dflags; parent = device_get_parent(dev); hostb = bhnd_bus_find_hostb_device(parent); attach_type = bhnd_get_attach_type(dev); for (entry = table; !BHND_DEVICE_IS_END(entry); entry = (const struct bhnd_device *) ((const char *) entry + entry_size)) { /* match core info */ if (!bhnd_device_matches(dev, &entry->core)) continue; /* match device flags */ dflags = entry->device_flags; /* hostb implies BHND_ATTACH_ADAPTER requirement */ if (dflags & BHND_DF_HOSTB) dflags |= BHND_DF_ADAPTER; if (dflags & BHND_DF_ADAPTER) if (attach_type != BHND_ATTACH_ADAPTER) continue; if (dflags & BHND_DF_HOSTB) if (dev != hostb) continue; if (dflags & BHND_DF_SOC) if (attach_type != BHND_ATTACH_NATIVE) continue; /* device found */ return (entry); } /* not found */ return (NULL); } /** * Scan the device @p table for all quirk flags applicable to @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * @param entry_size The @p table entry size, in bytes. * * @return all matching quirk flags. */ uint32_t bhnd_device_quirks(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *dent; const struct bhnd_device_quirk *qent, *qtable; uint32_t quirks; /* Locate the device entry */ if ((dent = bhnd_device_lookup(dev, table, entry_size)) == NULL) return (0); /* Quirks table is optional */ qtable = dent->quirks_table; if (qtable == NULL) return (0); /* Collect matching device quirk entries */ quirks = 0; for (qent = qtable; !BHND_DEVICE_QUIRK_IS_END(qent); qent++) { if (bhnd_device_matches(dev, &qent->desc)) quirks |= qent->quirks; } return (quirks); } /** * Allocate bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device requesting ownership of the resources. * @param rs A standard bus resource specification. This will be updated * with the allocated resource's RIDs. * @param res On success, the allocated bhnd resources. * * @retval 0 success * @retval non-zero if allocation of any non-RF_OPTIONAL resource fails, * all allocated resources will be released and a regular * unix error code will be returned. */ int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res) { /* Initialize output array */ for (u_int i = 0; rs[i].type != -1; i++) res[i] = NULL; for (u_int i = 0; rs[i].type != -1; i++) { res[i] = bhnd_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); /* Clean up all allocations on failure */ if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bhnd_release_resources(dev, rs, res); return (ENXIO); } } return (0); } /** * Release bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device that owns the resources. * @param rs A standard bus resource specification previously initialized * by @p bhnd_alloc_resources. * @param res The bhnd resources to be released. */ void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res) { for (u_int i = 0; rs[i].type != -1; i++) { if (res[i] == NULL) continue; bhnd_release_resource(dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } /** * Allocate and return a new per-core PMU clock control/status (clkctl) * instance for @p dev. * * @param dev The bhnd(4) core device mapped by @p r. * @param pmu_dev The bhnd(4) PMU device, implmenting the bhnd_pmu_if * interface. The caller is responsible for ensuring that * this reference remains valid for the lifetime of the * returned clkctl instance. * @param r A resource mapping the core's clock control register * (see BHND_CLK_CTL_ST). The caller is responsible for * ensuring that this resource remains valid for the * lifetime of the returned clkctl instance. * @param offset The offset to the clock control register within @p r. * @param max_latency The PMU's maximum state transition latency in * microseconds; this upper bound will be used to busy-wait * on PMU state transitions. * * @retval non-NULL success * @retval NULL if allocation fails. * */ struct bhnd_core_clkctl * bhnd_alloc_core_clkctl(device_t dev, device_t pmu_dev, struct bhnd_resource *r, bus_size_t offset, u_int max_latency) { struct bhnd_core_clkctl *clkctl; clkctl = malloc(sizeof(*clkctl), M_BHND, M_ZERO | M_NOWAIT); if (clkctl == NULL) return (NULL); clkctl->cc_dev = dev; clkctl->cc_pmu_dev = pmu_dev; clkctl->cc_res = r; clkctl->cc_res_offset = offset; clkctl->cc_max_latency = max_latency; clkctl->cc_quirks = bhnd_device_quirks(dev, bhnd_clkctl_devices, sizeof(bhnd_clkctl_devices[0])); BHND_CLKCTL_LOCK_INIT(clkctl); return (clkctl); } /** * Free a clkctl instance previously allocated via bhnd_alloc_core_clkctl(). * * @param clkctl The clkctl instance to be freed. */ void bhnd_free_core_clkctl(struct bhnd_core_clkctl *clkctl) { BHND_CLKCTL_LOCK_DESTROY(clkctl); free(clkctl, M_BHND); } /** * Wait for the per-core clock status to be equal to @p value after * applying @p mask, timing out after the maximum transition latency is reached. * * @param clkctl Per-core clkctl state to be queryied. * @param value Value to wait for. * @param mask Mask to apply prior to value comparison. * * @retval 0 success * @retval ETIMEDOUT if the PMU's maximum transition delay is reached before * the clock status matches @p value and @p mask. */ int bhnd_core_clkctl_wait(struct bhnd_core_clkctl *clkctl, uint32_t value, uint32_t mask) { uint32_t clkst; BHND_CLKCTL_LOCK_ASSERT(clkctl, MA_OWNED); /* Bitswapped HTAVAIL/ALPAVAIL work-around */ if (clkctl->cc_quirks & BHND_CLKCTL_QUIRK_CCS0) { uint32_t fmask, fval; fmask = mask & ~(BHND_CCS_HTAVAIL | BHND_CCS_ALPAVAIL); fval = value & ~(BHND_CCS_HTAVAIL | BHND_CCS_ALPAVAIL); if (mask & BHND_CCS_HTAVAIL) fmask |= BHND_CCS0_HTAVAIL; if (value & BHND_CCS_HTAVAIL) fval |= BHND_CCS0_HTAVAIL; if (mask & BHND_CCS_ALPAVAIL) fmask |= BHND_CCS0_ALPAVAIL; if (value & BHND_CCS_ALPAVAIL) fval |= BHND_CCS0_ALPAVAIL; mask = fmask; value = fval; } for (u_int i = 0; i < clkctl->cc_max_latency; i += 10) { clkst = bhnd_bus_read_4(clkctl->cc_res, clkctl->cc_res_offset); if ((clkst & mask) == (value & mask)) return (0); DELAY(10); } device_printf(clkctl->cc_dev, "clkst wait timeout (value=%#x, " "mask=%#x)\n", value, mask); return (ETIMEDOUT); } /** * Read an NVRAM variable's NUL-terminated string value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p len bytes. On * success, the NUL-terminated string value will be * written to this buffer. This argment may be NULL if * the value is not desired. * @param len The maximum capacity of @p buf. * @param[out] rlen On success, will be set to the actual size of * the requested value (including NUL termination). This * argment may be NULL if the size is not desired. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too * small to hold the requested value. * @retval EFTYPE If the variable data cannot be coerced to a valid * string representation. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_str(device_t dev, const char *name, char *buf, size_t len, size_t *rlen) { size_t larg; int error; larg = len; error = bhnd_nvram_getvar(dev, name, buf, &larg, BHND_NVRAM_TYPE_STRING); if (rlen != NULL) *rlen = larg; return (error); } /** * Read an NVRAM variable's unsigned integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * @param width The output integer type width (1, 2, or * 4 bytes). * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) an * unsigned representation of the given @p width. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint(device_t dev, const char *name, void *value, int width) { bhnd_nvram_type type; size_t len; switch (width) { case 1: type = BHND_NVRAM_TYPE_UINT8; break; case 2: type = BHND_NVRAM_TYPE_UINT16; break; case 4: type = BHND_NVRAM_TYPE_UINT32; break; default: device_printf(dev, "unsupported NVRAM integer width: %d\n", width); return (EINVAL); } len = width; return (bhnd_nvram_getvar(dev, name, value, &len, type)); } /** * Read an NVRAM variable's unsigned 8-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) uint8_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint8(device_t dev, const char *name, uint8_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's unsigned 16-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * uint16_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint16(device_t dev, const char *name, uint16_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's unsigned 32-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * uint32_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint32(device_t dev, const char *name, uint32_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * @param width The output integer type width (1, 2, or * 4 bytes). * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) an * signed representation of the given @p width. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int(device_t dev, const char *name, void *value, int width) { bhnd_nvram_type type; size_t len; switch (width) { case 1: type = BHND_NVRAM_TYPE_INT8; break; case 2: type = BHND_NVRAM_TYPE_INT16; break; case 4: type = BHND_NVRAM_TYPE_INT32; break; default: device_printf(dev, "unsupported NVRAM integer width: %d\n", width); return (EINVAL); } len = width; return (bhnd_nvram_getvar(dev, name, value, &len, type)); } /** * Read an NVRAM variable's signed 8-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) int8_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int8(device_t dev, const char *name, int8_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed 16-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * int16_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int16(device_t dev, const char *name, int16_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed 32-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * int32_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int32(device_t dev, const char *name, int32_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's array value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p size bytes. * On success, the requested value will be written * to this buffer. * @param[in,out] size The required number of bytes to write to * @p buf. * @param type The desired array element data representation. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENXIO If less than @p size bytes are available. * @retval ENOMEM If a buffer of @p size is too small to hold the * requested value. * @retval EFTYPE If the variable data cannot be coerced to a * a valid instance of @p type. * @retval ERANGE If value coercion would overflow (or underflow) a * representation of @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_array(device_t dev, const char *name, void *buf, size_t size, bhnd_nvram_type type) { size_t nbytes; int error; /* Attempt read */ nbytes = size; if ((error = bhnd_nvram_getvar(dev, name, buf, &nbytes, type))) return (error); /* Verify that the expected number of bytes were fetched */ if (nbytes < size) return (ENXIO); return (0); } /** * Initialize a service provider registry. * * @param bsr The service registry to initialize. * * @retval 0 success * @retval non-zero if an error occurs initializing the service registry, * a regular unix error code will be returned. */ int bhnd_service_registry_init(struct bhnd_service_registry *bsr) { STAILQ_INIT(&bsr->entries); mtx_init(&bsr->lock, "bhnd_service_registry lock", NULL, MTX_DEF); return (0); } /** * Release all resources held by @p bsr. * * @param bsr A service registry instance previously successfully * initialized via bhnd_service_registry_init(). * * @retval 0 success * @retval EBUSY if active references to service providers registered * with @p bsr exist. */ int bhnd_service_registry_fini(struct bhnd_service_registry *bsr) { struct bhnd_service_entry *entry, *enext; /* Remove everthing we can */ mtx_lock(&bsr->lock); STAILQ_FOREACH_SAFE(entry, &bsr->entries, link, enext) { if (entry->refs > 0) continue; STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); free(entry, M_BHND); } if (!STAILQ_EMPTY(&bsr->entries)) { mtx_unlock(&bsr->lock); return (EBUSY); } mtx_unlock(&bsr->lock); mtx_destroy(&bsr->lock); return (0); } /** * Register a @p provider for the given @p service. * * @param bsr Service registry to be modified. * @param provider Service provider to register. * @param service Service for which @p provider will be registered. * @param flags Service provider flags (see BHND_SPF_*). * * @retval 0 success * @retval EEXIST if an entry for @p service already exists. * @retval EINVAL if @p service is BHND_SERVICE_ANY. * @retval non-zero if registering @p provider otherwise fails, a regular * unix error code will be returned. */ int bhnd_service_registry_add(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service, uint32_t flags) { struct bhnd_service_entry *entry; if (service == BHND_SERVICE_ANY) return (EINVAL); mtx_lock(&bsr->lock); /* Is a service provider already registered? */ STAILQ_FOREACH(entry, &bsr->entries, link) { if (entry->service == service) { mtx_unlock(&bsr->lock); return (EEXIST); } } /* Initialize and insert our new entry */ entry = malloc(sizeof(*entry), M_BHND, M_NOWAIT); if (entry == NULL) { mtx_unlock(&bsr->lock); return (ENOMEM); } entry->provider = provider; entry->service = service; entry->flags = flags; refcount_init(&entry->refs, 0); STAILQ_INSERT_HEAD(&bsr->entries, entry, link); mtx_unlock(&bsr->lock); return (0); } /** * Free an unreferenced registry entry. * * @param entry The entry to be deallocated. */ static void bhnd_service_registry_free_entry(struct bhnd_service_entry *entry) { KASSERT(entry->refs == 0, ("provider has active references")); free(entry, M_BHND); } /** * Attempt to remove the @p service provider registration for @p provider. * * @param bsr The service registry to be modified. * @param provider The service provider to be deregistered. * @param service The service for which @p provider will be deregistered, * or BHND_SERVICE_ANY to remove all service * registrations for @p provider. * * @retval 0 success * @retval EBUSY if active references to @p provider exist; see * bhnd_service_registry_retain() and * bhnd_service_registry_release(). */ int bhnd_service_registry_remove(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service) { struct bhnd_service_entry *entry, *enext; mtx_lock(&bsr->lock); #define BHND_PROV_MATCH(_e) \ ((_e)->provider == provider && \ (service == BHND_SERVICE_ANY || (_e)->service == service)) /* Validate matching provider entries before making any * modifications */ STAILQ_FOREACH(entry, &bsr->entries, link) { /* Skip non-matching entries */ if (!BHND_PROV_MATCH(entry)) continue; /* Entry is in use? */ if (entry->refs > 0) { mtx_unlock(&bsr->lock); return (EBUSY); } } /* We can now safely remove matching entries */ STAILQ_FOREACH_SAFE(entry, &bsr->entries, link, enext) { /* Skip non-matching entries */ if (!BHND_PROV_MATCH(entry)) continue; /* Remove from list */ STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); /* Free provider entry */ bhnd_service_registry_free_entry(entry); } #undef BHND_PROV_MATCH mtx_unlock(&bsr->lock); return (0); } /** * Retain and return a reference to a registered @p service provider, if any. * * @param bsr The service registry to be queried. * @param service The service for which a provider should be returned. * * On success, the caller assumes ownership the returned provider, and * is responsible for releasing this reference via * bhnd_service_registry_release(). * * @retval device_t success * @retval NULL if no provider is registered for @p service. */ device_t bhnd_service_registry_retain(struct bhnd_service_registry *bsr, bhnd_service_t service) { struct bhnd_service_entry *entry; mtx_lock(&bsr->lock); STAILQ_FOREACH(entry, &bsr->entries, link) { if (entry->service != service) continue; /* With a live refcount, entry is gauranteed to remain alive * after we release our lock */ refcount_acquire(&entry->refs); mtx_unlock(&bsr->lock); return (entry->provider); } mtx_unlock(&bsr->lock); /* Not found */ return (NULL); } /** * Release a reference to a service provider previously returned by * bhnd_service_registry_retain(). * * If this is the last reference to an inherited service provider registration * (see BHND_SPF_INHERITED), the registration will also be removed, and * true will be returned. * * @param bsr The service registry from which @p provider * was returned. * @param provider The provider to be released. * @param service The service for which @p provider was previously * retained. * @retval true The inherited service provider registration was removed; * the caller should release its own reference to the * provider. * @retval false The service provider was not inherited, or active * references to the provider remain. * * @see BHND_SPF_INHERITED */ bool bhnd_service_registry_release(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service) { struct bhnd_service_entry *entry; /* Exclusive lock, as we need to prevent any new references to the * entry from being taken if it's to be removed */ mtx_lock(&bsr->lock); STAILQ_FOREACH(entry, &bsr->entries, link) { bool removed; if (entry->provider != provider) continue; if (entry->service != service) continue; if (refcount_release(&entry->refs) && (entry->flags & BHND_SPF_INHERITED)) { /* If an inherited entry is no longer actively * referenced, remove the local registration and inform * the caller. */ STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); bhnd_service_registry_free_entry(entry); removed = true; } else { removed = false; } mtx_unlock(&bsr->lock); return (removed); } /* Caller owns a reference, but no such provider is registered? */ panic("invalid service provider reference"); } /** * Using the bhnd(4) bus-level core information and a custom core name, * populate @p dev's device description. * * @param dev A bhnd-bus attached device. * @param dev_name The core's name (e.g. "SDIO Device Core"). */ void bhnd_set_custom_core_desc(device_t dev, const char *dev_name) { const char *vendor_name; vendor_name = bhnd_get_vendor_name(dev); device_set_descf(dev, "%s %s, rev %hhu", vendor_name, dev_name, bhnd_get_hwrev(dev)); } /** * Using the bhnd(4) bus-level core information, populate @p dev's device * description. * * @param dev A bhnd-bus attached device. */ void bhnd_set_default_core_desc(device_t dev) { bhnd_set_custom_core_desc(dev, bhnd_get_device_name(dev)); } /** * Using the bhnd @p chip_id, populate the bhnd(4) bus @p dev's device * description. * * @param dev A bhnd-bus attached device. * @param chip_id The chip identification. */ void bhnd_set_default_bus_desc(device_t dev, const struct bhnd_chipid *chip_id) { const char *bus_name; char chip_name[BHND_CHIPID_MAX_NAMELEN]; /* Determine chip type's bus name */ switch (chip_id->chip_type) { case BHND_CHIPTYPE_SIBA: bus_name = "SIBA bus"; break; case BHND_CHIPTYPE_BCMA: case BHND_CHIPTYPE_BCMA_ALT: bus_name = "BCMA bus"; break; case BHND_CHIPTYPE_UBUS: bus_name = "UBUS bus"; break; default: bus_name = "Unknown Type"; break; } /* Format chip name */ bhnd_format_chip_id(chip_name, sizeof(chip_name), chip_id->chip_id); /* Format and set device description */ device_set_descf(dev, "%s %s", chip_name, bus_name); } /** * Helper function for implementing BHND_BUS_REGISTER_PROVIDER(). * * This implementation delegates the request to the BHND_BUS_REGISTER_PROVIDER() * method on the parent of @p dev. If no parent exists, the implementation * will return an error. */ int bhnd_bus_generic_register_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_REGISTER_PROVIDER(parent, child, provider, service)); } return (ENXIO); } /** * Helper function for implementing BHND_BUS_DEREGISTER_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will panic. */ int bhnd_bus_generic_deregister_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_DEREGISTER_PROVIDER(parent, child, provider, service)); } panic("missing BHND_BUS_DEREGISTER_PROVIDER()"); } /** * Helper function for implementing BHND_BUS_RETAIN_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will return NULL. */ device_t bhnd_bus_generic_retain_provider(device_t dev, device_t child, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_RETAIN_PROVIDER(parent, child, service)); } return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will panic. */ void bhnd_bus_generic_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_RELEASE_PROVIDER(parent, child, provider, service)); } panic("missing BHND_BUS_RELEASE_PROVIDER()"); } /** * Helper function for implementing BHND_BUS_REGISTER_PROVIDER(). * * This implementation uses the bhnd_service_registry_add() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry to edit. */ int bhnd_bus_generic_sr_register_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); return (bhnd_service_registry_add(bsr, provider, service, 0)); } /** * Helper function for implementing BHND_BUS_DEREGISTER_PROVIDER(). * * This implementation uses the bhnd_service_registry_remove() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry to edit. */ int bhnd_bus_generic_sr_deregister_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); return (bhnd_service_registry_remove(bsr, provider, service)); } /** * Helper function for implementing BHND_BUS_RETAIN_PROVIDER(). * * This implementation uses the bhnd_service_registry_retain() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry. * * If a local provider for the service is not available, and a parent device is * available, this implementation will attempt to fetch and locally register * a service provider reference from the parent of @p dev. */ device_t bhnd_bus_generic_sr_retain_provider(device_t dev, device_t child, bhnd_service_t service) { struct bhnd_service_registry *bsr; device_t parent, provider; int error; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); /* * Attempt to fetch a service provider reference from either the local * service registry, or if not found, from our parent. * * If we fetch a provider from our parent, we register the provider * with the local service registry to prevent conflicting local * registrations from being added. */ while (1) { /* Check the local service registry first */ provider = bhnd_service_registry_retain(bsr, service); if (provider != NULL) return (provider); /* Otherwise, try to delegate to our parent (if any) */ if ((parent = device_get_parent(dev)) == NULL) return (NULL); provider = BHND_BUS_RETAIN_PROVIDER(parent, dev, service); if (provider == NULL) return (NULL); /* Register the inherited service registration with the local * registry */ error = bhnd_service_registry_add(bsr, provider, service, BHND_SPF_INHERITED); if (error) { BHND_BUS_RELEASE_PROVIDER(parent, dev, provider, service); if (error == EEXIST) { /* A valid service provider was registered * concurrently; retry fetching from the local * registry */ continue; } device_printf(dev, "failed to register service " "provider: %d\n", error); return (NULL); } } } /** * Helper function for implementing BHND_BUS_RELEASE_PROVIDER(). * * This implementation uses the bhnd_service_registry_release() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry. */ void bhnd_bus_generic_sr_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); /* Release the provider reference; if the refcount hits zero on an * inherited reference, true will be returned, and we need to drop * our own bus reference to the provider */ if (!bhnd_service_registry_release(bsr, provider, service)) return; /* Drop our reference to the borrowed provider */ BHND_BUS_RELEASE_PROVIDER(device_get_parent(dev), dev, provider, service); } /** * Helper function for implementing BHND_BUS_IS_HW_DISABLED(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_IS_HW_DISABLED() method on the parent of @p dev. * * If no parent device is available (i.e. on a the bus root), the hardware * is assumed to be usable and false is returned. */ bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), child)); return (false); } /** * Helper function for implementing BHND_BUS_GET_CHIPID(). * * This implementation delegates the request to the BHND_BUS_GET_CHIPID() * method on the parent of @p dev. If no parent exists, the implementation * will panic. */ const struct bhnd_chipid * bhnd_bus_generic_get_chipid(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_GET_CHIPID(device_get_parent(dev), child)); panic("missing BHND_BUS_GET_CHIPID()"); } /** * Helper function for implementing BHND_BUS_GET_DMA_TRANSLATION(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_GET_DMA_TRANSLATION() method on the parent of @p dev. * * If no parent device is available, this implementation will panic. */ int bhnd_bus_generic_get_dma_translation(device_t dev, device_t child, u_int width, uint32_t flags, bus_dma_tag_t *dmat, struct bhnd_dma_translation *translation) { if (device_get_parent(dev) != NULL) { return (BHND_BUS_GET_DMA_TRANSLATION(device_get_parent(dev), child, width, flags, dmat, translation)); } panic("missing BHND_BUS_GET_DMA_TRANSLATION()"); } /* nvram board_info population macros for bhnd_bus_generic_read_board_info() */ #define BHND_GV(_dest, _name) \ bhnd_nvram_getvar_uint(child, BHND_NVAR_ ## _name, &_dest, \ sizeof(_dest)) #define REQ_BHND_GV(_dest, _name) do { \ if ((error = BHND_GV(_dest, _name))) { \ device_printf(dev, \ "error reading " __STRING(_name) ": %d\n", error); \ return (error); \ } \ } while(0) #define OPT_BHND_GV(_dest, _name, _default) do { \ if ((error = BHND_GV(_dest, _name))) { \ if (error != ENOENT) { \ device_printf(dev, \ "error reading " \ __STRING(_name) ": %d\n", error); \ return (error); \ } \ _dest = _default; \ } \ } while(0) /** * Helper function for implementing BHND_BUS_READ_BOARDINFO(). * * This implementation populates @p info with information from NVRAM, * defaulting board_vendor and board_type fields to 0 if the * requested variables cannot be found. * * This behavior is correct for most SoCs, but must be overridden on * bridged (PCI, PCMCIA, etc) devices to produce a complete bhnd_board_info * result. */ int bhnd_bus_generic_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { int error; OPT_BHND_GV(info->board_vendor, BOARDVENDOR, 0); OPT_BHND_GV(info->board_type, BOARDTYPE, 0); /* srom >= 2 */ OPT_BHND_GV(info->board_devid, DEVID, 0); /* srom >= 8 */ REQ_BHND_GV(info->board_rev, BOARDREV); OPT_BHND_GV(info->board_srom_rev,SROMREV, 0); /* missing in some SoC NVRAM */ REQ_BHND_GV(info->board_flags, BOARDFLAGS); OPT_BHND_GV(info->board_flags2, BOARDFLAGS2, 0); /* srom >= 4 */ OPT_BHND_GV(info->board_flags3, BOARDFLAGS3, 0); /* srom >= 11 */ return (0); } #undef BHND_GV #undef BHND_GV_REQ #undef BHND_GV_OPT /** * Helper function for implementing BHND_BUS_GET_NVRAM_VAR(). * * This implementation searches @p dev for a usable NVRAM child device. * * If no usable child device is found on @p dev, the request is delegated to * the BHND_BUS_GET_NVRAM_VAR() method on the parent of @p dev. */ int bhnd_bus_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { device_t nvram; device_t parent; bus_topo_assert(); /* Look for a directly-attached NVRAM child */ - if ((nvram = device_find_child(dev, "bhnd_nvram", -1)) != NULL) + if ((nvram = device_find_child(dev, "bhnd_nvram", DEVICE_UNIT_ANY)) != NULL) return BHND_NVRAM_GETVAR(nvram, name, buf, size, type); /* Try to delegate to parent */ if ((parent = device_get_parent(dev)) == NULL) return (ENODEV); return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), child, name, buf, size, type)); } /** * Helper function for implementing BHND_BUS_ALLOC_RESOURCE(). * * This implementation of BHND_BUS_ALLOC_RESOURCE() delegates allocation * of the underlying resource to BUS_ALLOC_RESOURCE(), and activation * to @p dev's BHND_BUS_ACTIVATE_RESOURCE(). */ struct bhnd_resource * bhnd_bus_generic_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhnd_resource *br; struct resource *res; int error; br = NULL; res = NULL; /* Allocate the real bus resource (without activating it) */ res = BUS_ALLOC_RESOURCE(dev, child, type, rid, start, end, count, (flags & ~RF_ACTIVE)); if (res == NULL) return (NULL); /* Allocate our bhnd resource wrapper. */ br = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (br == NULL) goto failed; br->direct = false; br->res = res; /* Attempt activation */ if (flags & RF_ACTIVE) { error = BHND_BUS_ACTIVATE_RESOURCE(dev, child, type, *rid, br); if (error) goto failed; } return (br); failed: if (res != NULL) BUS_RELEASE_RESOURCE(dev, child, res); free(br, M_BHND); return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_RESOURCE(). * * This implementation of BHND_BUS_RELEASE_RESOURCE() delegates release of * the backing resource to BUS_RELEASE_RESOURCE(). */ int bhnd_bus_generic_release_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; if ((error = BUS_RELEASE_RESOURCE(dev, child, r->res))) return (error); free(r, M_BHND); return (0); } /** * Helper function for implementing BHND_BUS_ACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() first calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. * * If this fails, and if @p dev is the direct parent of @p child, standard * resource activation is attempted via bus_activate_resource(). This enables * direct use of the bhnd(4) resource APIs on devices that may not be attached * to a parent bhnd bus or bridge. */ int bhnd_bus_generic_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; bool passthrough; passthrough = (device_get_parent(child) != dev); /* Try to delegate to the parent */ if (device_get_parent(dev) != NULL) { error = BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r); } else { error = ENODEV; } /* If bhnd(4) activation has failed and we're the child's direct * parent, try falling back on standard resource activation. */ if (error && !passthrough) { error = bus_activate_resource(child, type, rid, r->res); if (!error) r->direct = true; } return (error); } /** * Helper function for implementing BHND_BUS_DEACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() simply calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bhnd_bus_generic_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { if (device_get_parent(dev) != NULL) return (BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r)); return (EINVAL); } /** * Helper function for implementing BHND_BUS_GET_INTR_DOMAIN(). * * This implementation simply returns the address of nearest bhnd(4) bus, * which may be @p dev; this behavior may be incompatible with FDT/OFW targets. */ uintptr_t bhnd_bus_generic_get_intr_domain(device_t dev, device_t child, bool self) { return ((uintptr_t)dev); } diff --git a/sys/dev/bhnd/cores/chipc/chipc_spi.c b/sys/dev/bhnd/cores/chipc/chipc_spi.c index e89d128fa441..290933e5ef25 100644 --- a/sys/dev/bhnd/cores/chipc/chipc_spi.c +++ b/sys/dev/bhnd/cores/chipc/chipc_spi.c @@ -1,275 +1,277 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2016 Michael Zhilin * Copyright (c) 2016 Landon Fuller * 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, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include #include #include #include #include #include #include #include #include #include #include "bhnd_chipc_if.h" #include "spibus_if.h" #include "chipcreg.h" #include "chipcvar.h" #include "chipc_slicer.h" #include "chipc_spi.h" static int chipc_spi_probe(device_t dev); static int chipc_spi_attach(device_t dev); static int chipc_spi_detach(device_t dev); static int chipc_spi_transfer(device_t dev, device_t child, struct spi_command *cmd); static int chipc_spi_txrx(struct chipc_spi_softc *sc, uint8_t in, uint8_t* out); static int chipc_spi_wait(struct chipc_spi_softc *sc); static int chipc_spi_probe(device_t dev) { device_set_desc(dev, "Broadcom ChipCommon SPI"); return (BUS_PROBE_NOWILDCARD); } static int chipc_spi_attach(device_t dev) { struct chipc_spi_softc *sc; struct chipc_caps *ccaps; device_t flash_dev; device_t spibus; const char *flash_name; int error; sc = device_get_softc(dev); /* Allocate SPI controller registers */ sc->sc_rid = 1; sc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_rid, RF_ACTIVE); if (sc->sc_res == NULL) { device_printf(dev, "failed to allocate device registers\n"); return (ENXIO); } /* Allocate flash shadow region */ sc->sc_flash_rid = 0; sc->sc_flash_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_flash_rid, RF_ACTIVE); if (sc->sc_flash_res == NULL) { device_printf(dev, "failed to allocate flash region\n"); error = ENXIO; goto failed; } /* * Add flash device * * XXX: This should be replaced with a DEVICE_IDENTIFY implementation * in chipc-specific subclasses of the mx25l and at45d drivers. */ - if ((spibus = device_add_child(dev, "spibus", -1)) == NULL) { + if ((spibus = device_add_child(dev, "spibus", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "failed to add spibus\n"); error = ENXIO; goto failed; } /* Let spibus perform full attach before we try to call * BUS_ADD_CHILD() */ bus_attach_children(dev); /* Determine flash type and add the flash child */ ccaps = BHND_CHIPC_GET_CAPS(device_get_parent(dev)); flash_name = chipc_sflash_device_name(ccaps->flash_type); if (flash_name != NULL) { - flash_dev = BUS_ADD_CHILD(spibus, 0, flash_name, DEVICE_UNIT_ANY); + flash_dev = BUS_ADD_CHILD(spibus, 0, flash_name, + DEVICE_UNIT_ANY); if (flash_dev == NULL) { device_printf(dev, "failed to add %s\n", flash_name); error = ENXIO; goto failed; } chipc_register_slicer(ccaps->flash_type); if ((error = device_probe_and_attach(flash_dev))) { device_printf(dev, "failed to attach %s: %d\n", flash_name, error); goto failed; } } return (0); failed: device_delete_children(dev); if (sc->sc_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid, sc->sc_res); if (sc->sc_flash_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_flash_rid, sc->sc_flash_res); return (error); } static int chipc_spi_detach(device_t dev) { struct chipc_spi_softc *sc; int error; sc = device_get_softc(dev); if ((error = bus_generic_detach(dev))) return (error); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid, sc->sc_res); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_flash_rid, sc->sc_flash_res); return (0); } static int chipc_spi_wait(struct chipc_spi_softc *sc) { int i; for (i = CHIPC_SPI_MAXTRIES; i > 0; i--) if (!(SPI_READ(sc, CHIPC_SPI_FLASHCTL) & CHIPC_SPI_FLASHCTL_START)) break; if (i > 0) return (0); BHND_WARN_DEV(sc->sc_dev, "busy: CTL=0x%x DATA=0x%x", SPI_READ(sc, CHIPC_SPI_FLASHCTL), SPI_READ(sc, CHIPC_SPI_FLASHDATA)); return (-1); } static int chipc_spi_txrx(struct chipc_spi_softc *sc, uint8_t out, uint8_t* in) { uint32_t ctl; ctl = CHIPC_SPI_FLASHCTL_START | CHIPC_SPI_FLASHCTL_CSACTIVE | out; SPI_BARRIER_WRITE(sc); SPI_WRITE(sc, CHIPC_SPI_FLASHCTL, ctl); SPI_BARRIER_WRITE(sc); if (chipc_spi_wait(sc)) return (-1); *in = SPI_READ(sc, CHIPC_SPI_FLASHDATA) & 0xff; return (0); } static int chipc_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct chipc_spi_softc *sc; uint8_t *buf_in; uint8_t *buf_out; int i; sc = device_get_softc(dev); KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, ("TX/RX command sizes should be equal")); KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, ("TX/RX data sizes should be equal")); if (cmd->tx_cmd_sz == 0) { BHND_DEBUG_DEV(child, "size of command is ZERO"); return (EIO); } SPI_BARRIER_WRITE(sc); SPI_WRITE(sc, CHIPC_SPI_FLASHADDR, 0); SPI_BARRIER_WRITE(sc); /* * Transfer command */ buf_out = (uint8_t *)cmd->tx_cmd; buf_in = (uint8_t *)cmd->rx_cmd; for (i = 0; i < cmd->tx_cmd_sz; i++) if (chipc_spi_txrx(sc, buf_out[i], &(buf_in[i]))) return (EIO); /* * Receive/transmit data */ buf_out = (uint8_t *)cmd->tx_data; buf_in = (uint8_t *)cmd->rx_data; for (i = 0; i < cmd->tx_data_sz; i++) if (chipc_spi_txrx(sc, buf_out[i], &(buf_in[i]))) return (EIO); /* * Clear CS bit and whole control register */ SPI_BARRIER_WRITE(sc); SPI_WRITE(sc, CHIPC_SPI_FLASHCTL, 0); SPI_BARRIER_WRITE(sc); return (0); } static device_method_t chipc_spi_methods[] = { DEVMETHOD(device_probe, chipc_spi_probe), DEVMETHOD(device_attach, chipc_spi_attach), DEVMETHOD(device_detach, chipc_spi_detach), /* SPI */ DEVMETHOD(spibus_transfer, chipc_spi_transfer), DEVMETHOD_END }; static driver_t chipc_spi_driver = { "spi", chipc_spi_methods, sizeof(struct chipc_spi_softc), }; DRIVER_MODULE(chipc_spi, bhnd_chipc, chipc_spi_driver, 0, 0); diff --git a/sys/dev/bwn/if_bwn_pci.c b/sys/dev/bwn/if_bwn_pci.c index a81284158443..a64c53acf40f 100644 --- a/sys/dev/bwn/if_bwn_pci.c +++ b/sys/dev/bwn/if_bwn_pci.c @@ -1,292 +1,292 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015-2016 Landon Fuller * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_bwn.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include "bhndb_bus_if.h" #include "if_bwn_pcivar.h" /* If non-zero, enable attachment of BWN_QUIRK_UNTESTED devices */ static int attach_untested = 0; TUNABLE_INT("hw.bwn_pci.attach_untested", &attach_untested); /* SIBA Devices */ static const struct bwn_pci_device siba_devices[] = { BWN_BCM_DEV(BCM4306_D11A, "BCM4306 802.11a", BWN_QUIRK_WLAN_DUALCORE|BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4306_D11G, "BCM4306 802.11b/g", BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4306_D11G_ID2, "BCM4306 802.11b/g", BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4306_D11DUAL, "BCM4306 802.11a/b/g", BWN_QUIRK_SOFTMODEM_UNPOPULATED), BWN_BCM_DEV(BCM4307, "BCM4307 802.11b", 0), BWN_BCM_DEV(BCM4311_D11G, "BCM4311 802.11b/g", 0), BWN_BCM_DEV(BCM4311_D11DUAL, "BCM4311 802.11a/b/g", 0), BWN_BCM_DEV(BCM4311_D11A, "BCM4311 802.11a", BWN_QUIRK_UNTESTED|BWN_QUIRK_WLAN_DUALCORE), BWN_BCM_DEV(BCM4318_D11G, "BCM4318 802.11b/g", 0), BWN_BCM_DEV(BCM4318_D11DUAL, "BCM4318 802.11a/b/g", 0), BWN_BCM_DEV(BCM4318_D11A, "BCM4318 802.11a", BWN_QUIRK_UNTESTED|BWN_QUIRK_WLAN_DUALCORE), BWN_BCM_DEV(BCM4321_D11N, "BCM4321 802.11n Dual-Band", BWN_QUIRK_USBH_UNPOPULATED), BWN_BCM_DEV(BCM4321_D11N2G, "BCM4321 802.11n 2GHz", BWN_QUIRK_USBH_UNPOPULATED), BWN_BCM_DEV(BCM4321_D11N5G, "BCM4321 802.11n 5GHz", BWN_QUIRK_UNTESTED|BWN_QUIRK_USBH_UNPOPULATED), BWN_BCM_DEV(BCM4322_D11N, "BCM4322 802.11n Dual-Band", 0), BWN_BCM_DEV(BCM4322_D11N2G, "BCM4322 802.11n 2GHz", BWN_QUIRK_UNTESTED), BWN_BCM_DEV(BCM4322_D11N5G, "BCM4322 802.11n 5GHz", BWN_QUIRK_UNTESTED), BWN_BCM_DEV(BCM4328_D11G, "BCM4328/4312 802.11g", 0), { 0, 0, NULL, 0 } }; /** BCMA Devices */ static const struct bwn_pci_device bcma_devices[] = { BWN_BCM_DEV(BCM4331_D11N, "BCM4331 802.11n Dual-Band", 0), BWN_BCM_DEV(BCM4331_D11N2G, "BCM4331 802.11n 2GHz", 0), BWN_BCM_DEV(BCM4331_D11N5G, "BCM4331 802.11n 5GHz", 0), BWN_BCM_DEV(BCM43224_D11N, "BCM43224 802.11n Dual-Band", 0), BWN_BCM_DEV(BCM43224_D11N_ID_VEN1, "BCM43224 802.11n Dual-Band",0), BWN_BCM_DEV(BCM43225_D11N2G, "BCM43225 802.11n 2GHz", 0), { 0, 0, NULL, 0} }; /** Device configuration table */ static const struct bwn_pci_devcfg bwn_pci_devcfgs[] = { /* SIBA devices */ { .bridge_hwcfg = &bhndb_pci_siba_generic_hwcfg, .bridge_hwtable = bhndb_pci_generic_hw_table, .bridge_hwprio = bhndb_siba_priority_table, .devices = siba_devices }, /* BCMA devices */ { .bridge_hwcfg = &bhndb_pci_bcma_generic_hwcfg, .bridge_hwtable = bhndb_pci_generic_hw_table, .bridge_hwprio = bhndb_bcma_priority_table, .devices = bcma_devices }, { NULL, NULL, NULL } }; /** Search the device configuration table for an entry matching @p dev. */ static int bwn_pci_find_devcfg(device_t dev, const struct bwn_pci_devcfg **cfg, const struct bwn_pci_device **device) { const struct bwn_pci_devcfg *dvc; const struct bwn_pci_device *dv; for (dvc = bwn_pci_devcfgs; dvc->devices != NULL; dvc++) { for (dv = dvc->devices; dv->device != 0; dv++) { if (pci_get_vendor(dev) == dv->vendor && pci_get_device(dev) == dv->device) { if (cfg != NULL) *cfg = dvc; if (device != NULL) *device = dv; return (0); } } } return (ENOENT); } static int bwn_pci_probe(device_t dev) { const struct bwn_pci_device *ident; if (bwn_pci_find_devcfg(dev, NULL, &ident)) return (ENXIO); /* Skip untested devices */ if (ident->quirks & BWN_QUIRK_UNTESTED && !attach_untested) return (ENXIO); device_set_desc(dev, ident->desc); return (BUS_PROBE_DEFAULT); } static int bwn_pci_attach(device_t dev) { struct bwn_pci_softc *sc; const struct bwn_pci_device *ident; int error; sc = device_get_softc(dev); sc->dev = dev; /* Find our hardware config */ if (bwn_pci_find_devcfg(dev, &sc->devcfg, &ident)) return (ENXIO); /* Save quirk flags */ sc->quirks = ident->quirks; /* Attach bridge device */ - if ((error = bhndb_attach_bridge(dev, &sc->bhndb_dev, -1))) + if ((error = bhndb_attach_bridge(dev, &sc->bhndb_dev, DEVICE_UNIT_ANY))) return (ENXIO); /* Success */ return (0); } static void bwn_pci_probe_nomatch(device_t dev, device_t child) { const char *name; name = device_get_name(child); if (name == NULL) name = "unknown device"; device_printf(dev, "<%s> (no driver attached)\n", name); } static const struct bhndb_hwcfg * bwn_pci_get_generic_hwcfg(device_t dev, device_t child) { struct bwn_pci_softc *sc = device_get_softc(dev); return (sc->devcfg->bridge_hwcfg); } static const struct bhndb_hw * bwn_pci_get_bhndb_hwtable(device_t dev, device_t child) { struct bwn_pci_softc *sc = device_get_softc(dev); return (sc->devcfg->bridge_hwtable); } static const struct bhndb_hw_priority * bwn_pci_get_bhndb_hwprio(device_t dev, device_t child) { struct bwn_pci_softc *sc = device_get_softc(dev); return (sc->devcfg->bridge_hwprio); } static bool bwn_pci_is_core_disabled(device_t dev, device_t child, struct bhnd_core_info *core) { struct bwn_pci_softc *sc; sc = device_get_softc(dev); switch (bhnd_core_class(core)) { case BHND_DEVCLASS_WLAN: if (core->unit > 0 && !(sc->quirks & BWN_QUIRK_WLAN_DUALCORE)) return (true); return (false); case BHND_DEVCLASS_ENET: case BHND_DEVCLASS_ENET_MAC: case BHND_DEVCLASS_ENET_PHY: return ((sc->quirks & BWN_QUIRK_ENET_HW_UNPOPULATED) != 0); case BHND_DEVCLASS_USB_HOST: return ((sc->quirks & BWN_QUIRK_USBH_UNPOPULATED) != 0); case BHND_DEVCLASS_SOFTMODEM: return ((sc->quirks & BWN_QUIRK_SOFTMODEM_UNPOPULATED) != 0); default: return (false); } } static device_method_t bwn_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bwn_pci_probe), DEVMETHOD(device_attach, bwn_pci_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_probe_nomatch, bwn_pci_probe_nomatch), /* BHNDB_BUS Interface */ DEVMETHOD(bhndb_bus_get_generic_hwcfg, bwn_pci_get_generic_hwcfg), DEVMETHOD(bhndb_bus_get_hardware_table, bwn_pci_get_bhndb_hwtable), DEVMETHOD(bhndb_bus_get_hardware_prio, bwn_pci_get_bhndb_hwprio), DEVMETHOD(bhndb_bus_is_core_disabled, bwn_pci_is_core_disabled), DEVMETHOD_END }; DEFINE_CLASS_0(bwn_pci, bwn_pci_driver, bwn_pci_methods, sizeof(struct bwn_pci_softc)); DRIVER_MODULE_ORDERED(bwn_pci, pci, bwn_pci_driver, NULL, NULL, SI_ORDER_ANY); MODULE_PNP_INFO("U16:vendor;U16:device;D:#", pci, bwn_siba, siba_devices, nitems(siba_devices) - 1); MODULE_PNP_INFO("U16:vendor;U16:device;D:#", pci, bwn_bcma, bcma_devices, nitems(bcma_devices) - 1); DRIVER_MODULE(bhndb, bwn_pci, bhndb_pci_driver, NULL, NULL); MODULE_DEPEND(bwn_pci, bwn, 1, 1, 1); MODULE_DEPEND(bwn_pci, bhnd, 1, 1, 1); MODULE_DEPEND(bwn_pci, bhndb, 1, 1, 1); MODULE_DEPEND(bwn_pci, bhndb_pci, 1, 1, 1); MODULE_DEPEND(bwn_pci, bcma_bhndb, 1, 1, 1); MODULE_DEPEND(bwn_pci, siba_bhndb, 1, 1, 1); MODULE_VERSION(bwn_pci, 1); diff --git a/sys/dev/chromebook_platform/chromebook_platform.c b/sys/dev/chromebook_platform/chromebook_platform.c index 935685d86955..f873338a3ba6 100644 --- a/sys/dev/chromebook_platform/chromebook_platform.c +++ b/sys/dev/chromebook_platform/chromebook_platform.c @@ -1,96 +1,96 @@ /*- * Copyright (c) 2016 The FreeBSD Project. * 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, 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 COPYRIGHT HOLDERS AND CONTRIBUTORS * ``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 * COPYRIGHT HOLDERS OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include /* * Driver that attaches I2C devices. */ static struct { uint32_t pci_id; const char *name; uint8_t addr; } slaves[] = { { 0x9c628086, "isl", 0x88 }, { 0x9c618086, "cyapa", 0xce }, }; static void chromebook_i2c_identify(driver_t *driver, device_t bus) { device_t controller; device_t child; int i; /* * A stopgap approach to preserve the status quo. * A more intelligent approach is required to correctly * identify a machine model and hardware available on it. * For instance, DMI could be used. * See http://lxr.free-electrons.com/source/drivers/platform/chrome/chromeos_laptop.c */ controller = device_get_parent(bus); if (strcmp(device_get_name(controller), "ig4iic") != 0) return; for (i = 0; i < nitems(slaves); i++) { - if (device_find_child(bus, slaves[i].name, -1) != NULL) + if (device_find_child(bus, slaves[i].name, DEVICE_UNIT_ANY) != NULL) continue; if (slaves[i].pci_id != pci_get_devid(controller)) continue; child = BUS_ADD_CHILD(bus, 0, slaves[i].name, DEVICE_UNIT_ANY); if (child != NULL) iicbus_set_addr(child, slaves[i].addr); } } static device_method_t chromebook_i2c_methods[] = { DEVMETHOD(device_identify, chromebook_i2c_identify), { 0, 0 } }; static driver_t chromebook_i2c_driver = { "chromebook_i2c", chromebook_i2c_methods, 0 /* no softc */ }; DRIVER_MODULE(chromebook_i2c, iicbus, chromebook_i2c_driver, 0, 0); MODULE_VERSION(chromebook_i2c, 1); diff --git a/sys/dev/coretemp/coretemp.c b/sys/dev/coretemp/coretemp.c index 4a7e4e7834d8..df1dcff83639 100644 --- a/sys/dev/coretemp/coretemp.c +++ b/sys/dev/coretemp/coretemp.c @@ -1,440 +1,440 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007, 2008 Rui Paulo * 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, 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. */ /* * Device driver for Intel's On Die thermal sensor via MSR. * First introduced in Intel's Core line of processors. */ #include #include #include #include #include #include #include #include /* for curthread */ #include #include #include #include #include #include #include #define TZ_ZEROC 2731 #define THERM_CRITICAL_STATUS_LOG 0x20 #define THERM_CRITICAL_STATUS 0x10 #define THERM_STATUS_LOG 0x02 #define THERM_STATUS 0x01 #define THERM_STATUS_TEMP_SHIFT 16 #define THERM_STATUS_TEMP_MASK 0x7f #define THERM_STATUS_RES_SHIFT 27 #define THERM_STATUS_RES_MASK 0x0f #define THERM_STATUS_VALID_SHIFT 31 #define THERM_STATUS_VALID_MASK 0x01 struct coretemp_softc { device_t sc_dev; int sc_tjmax; unsigned int sc_throttle_log; }; /* * Device methods. */ static void coretemp_identify(driver_t *driver, device_t parent); static int coretemp_probe(device_t dev); static int coretemp_attach(device_t dev); static int coretemp_detach(device_t dev); static uint64_t coretemp_get_thermal_msr(int cpu); static void coretemp_clear_thermal_msr(int cpu); static int coretemp_get_val_sysctl(SYSCTL_HANDLER_ARGS); static int coretemp_throttle_log_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t coretemp_methods[] = { /* Device interface */ DEVMETHOD(device_identify, coretemp_identify), DEVMETHOD(device_probe, coretemp_probe), DEVMETHOD(device_attach, coretemp_attach), DEVMETHOD(device_detach, coretemp_detach), DEVMETHOD_END }; static driver_t coretemp_driver = { "coretemp", coretemp_methods, sizeof(struct coretemp_softc), }; enum therm_info { CORETEMP_TEMP, CORETEMP_DELTA, CORETEMP_RESOLUTION, CORETEMP_TJMAX, }; DRIVER_MODULE(coretemp, cpu, coretemp_driver, NULL, NULL); static void coretemp_identify(driver_t *driver, device_t parent) { device_t child; u_int regs[4]; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "coretemp", -1) != NULL) + if (device_find_child(parent, "coretemp", DEVICE_UNIT_ANY) != NULL) return; /* Check that CPUID 0x06 is supported and the vendor is Intel.*/ if (cpu_high < 6 || cpu_vendor_id != CPU_VENDOR_INTEL) return; /* * CPUID 0x06 returns 1 if the processor has on-die thermal * sensors. EBX[0:3] contains the number of sensors. */ do_cpuid(0x06, regs); if ((regs[0] & 0x1) != 1) return; /* * We add a child for each CPU since settings must be performed * on each CPU in the SMP case. */ child = device_add_child(parent, "coretemp", device_get_unit(parent)); if (child == NULL) device_printf(parent, "add coretemp child failed\n"); } static int coretemp_probe(device_t dev) { if (resource_disabled("coretemp", 0)) return (ENXIO); device_set_desc(dev, "CPU On-Die Thermal Sensors"); if (!bootverbose && device_get_unit(dev) != 0) device_quiet(dev); return (BUS_PROBE_GENERIC); } static int coretemp_attach(device_t dev) { struct coretemp_softc *sc = device_get_softc(dev); device_t pdev; uint64_t msr; int cpu_model, cpu_stepping; int ret, tjtarget; struct sysctl_oid *oid; struct sysctl_ctx_list *ctx; sc->sc_dev = dev; pdev = device_get_parent(dev); cpu_model = CPUID_TO_MODEL(cpu_id); cpu_stepping = CPUID_TO_STEPPING(cpu_id); /* * Some CPUs, namely the PIII, don't have thermal sensors, but * report them when the CPUID check is performed in * coretemp_identify(). This leads to a later GPF when the sensor * is queried via a MSR, so we stop here. */ if (cpu_model < 0xe) return (ENXIO); #if 0 /* * XXXrpaulo: I have this CPU model and when it returns from C3 * coretemp continues to function properly. */ /* * Check for errata AE18. * "Processor Digital Thermal Sensor (DTS) Readout stops * updating upon returning from C3/C4 state." * * Adapted from the Linux coretemp driver. */ if (cpu_model == 0xe && cpu_stepping < 0xc) { msr = rdmsr(MSR_BIOS_SIGN); msr = msr >> 32; if (msr < 0x39) { device_printf(dev, "not supported (Intel errata " "AE18), try updating your BIOS\n"); return (ENXIO); } } #endif /* * Use 100C as the initial value. */ sc->sc_tjmax = 100; if ((cpu_model == 0xf && cpu_stepping >= 2) || cpu_model == 0xe) { /* * On some Core 2 CPUs, there's an undocumented MSR that * can tell us if Tj(max) is 100 or 85. * * The if-clause for CPUs having the MSR_IA32_EXT_CONFIG was adapted * from the Linux coretemp driver. */ msr = rdmsr(MSR_IA32_EXT_CONFIG); if (msr & (1 << 30)) sc->sc_tjmax = 85; } else if (cpu_model == 0x17) { switch (cpu_stepping) { case 0x6: /* Mobile Core 2 Duo */ sc->sc_tjmax = 105; break; default: /* Unknown stepping */ break; } } else if (cpu_model == 0x1c) { switch (cpu_stepping) { case 0xa: /* 45nm Atom D400, N400 and D500 series */ sc->sc_tjmax = 100; break; default: sc->sc_tjmax = 90; break; } } else { /* * Attempt to get Tj(max) from MSR IA32_TEMPERATURE_TARGET. * * This method is described in Intel white paper "CPU * Monitoring With DTS/PECI". (#322683) */ ret = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &msr); if (ret == 0) { tjtarget = (msr >> 16) & 0xff; /* * On earlier generation of processors, the value * obtained from IA32_TEMPERATURE_TARGET register is * an offset that needs to be summed with a model * specific base. It is however not clear what * these numbers are, with the publicly available * documents from Intel. * * For now, we consider [70, 110]C range, as * described in #322683, as "reasonable" and accept * these values whenever the MSR is available for * read, regardless the CPU model. */ if (tjtarget >= 70 && tjtarget <= 110) sc->sc_tjmax = tjtarget; else device_printf(dev, "Tj(target) value %d " "does not seem right.\n", tjtarget); } else device_printf(dev, "Can not get Tj(target) " "from your CPU, using 100C.\n"); } if (bootverbose) device_printf(dev, "Setting TjMax=%d\n", sc->sc_tjmax); ctx = device_get_sysctl_ctx(dev); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "coretemp", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Per-CPU thermal information"); /* * Add the MIBs to dev.cpu.N and dev.cpu.N.coretemp. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TEMP, coretemp_get_val_sysctl, "IK", "Current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "delta", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_DELTA, coretemp_get_val_sysctl, "I", "Delta between TCC activation and current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "resolution", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_RESOLUTION, coretemp_get_val_sysctl, "I", "Resolution of CPU thermal sensor"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "tjmax", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TJMAX, coretemp_get_val_sysctl, "IK", "TCC activation temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "throttle_log", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, 0, coretemp_throttle_log_sysctl, "I", "Set to 1 if the thermal sensor has tripped"); return (0); } static int coretemp_detach(device_t dev) { return (0); } struct coretemp_args { u_int msr; uint64_t val; }; /* * The digital temperature reading is located at bit 16 * of MSR_THERM_STATUS. * * There is a bit on that MSR that indicates whether the * temperature is valid or not. * * The temperature is computed by subtracting the temperature * reading by Tj(max). */ static uint64_t coretemp_get_thermal_msr(int cpu) { uint64_t res; x86_msr_op(MSR_THERM_STATUS, MSR_OP_RENDEZVOUS_ONE | MSR_OP_READ | MSR_OP_CPUID(cpu), 0, &res); return (res); } static void coretemp_clear_thermal_msr(int cpu) { x86_msr_op(MSR_THERM_STATUS, MSR_OP_RENDEZVOUS_ONE | MSR_OP_WRITE | MSR_OP_CPUID(cpu), 0, NULL); } static int coretemp_get_val_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; uint64_t msr; int val, tmp; struct coretemp_softc *sc; enum therm_info type; char stemp[16]; dev = (device_t) arg1; msr = coretemp_get_thermal_msr(device_get_unit(dev)); sc = device_get_softc(dev); type = arg2; if (((msr >> THERM_STATUS_VALID_SHIFT) & THERM_STATUS_VALID_MASK) != 1) { val = -1; } else { switch (type) { case CORETEMP_TEMP: tmp = (msr >> THERM_STATUS_TEMP_SHIFT) & THERM_STATUS_TEMP_MASK; val = (sc->sc_tjmax - tmp) * 10 + TZ_ZEROC; break; case CORETEMP_DELTA: val = (msr >> THERM_STATUS_TEMP_SHIFT) & THERM_STATUS_TEMP_MASK; break; case CORETEMP_RESOLUTION: val = (msr >> THERM_STATUS_RES_SHIFT) & THERM_STATUS_RES_MASK; break; case CORETEMP_TJMAX: val = sc->sc_tjmax * 10 + TZ_ZEROC; break; } } if (msr & THERM_STATUS_LOG) { coretemp_clear_thermal_msr(device_get_unit(dev)); sc->sc_throttle_log = 1; /* * Check for Critical Temperature Status and Critical * Temperature Log. It doesn't really matter if the * current temperature is invalid because the "Critical * Temperature Log" bit will tell us if the Critical * Temperature has * been reached in past. It's not * directly related to the current temperature. * * If we reach a critical level, allow devctl(4) * to catch this and shutdown the system. */ if (msr & THERM_CRITICAL_STATUS) { tmp = (msr >> THERM_STATUS_TEMP_SHIFT) & THERM_STATUS_TEMP_MASK; tmp = (sc->sc_tjmax - tmp) * 10 + TZ_ZEROC; device_printf(dev, "critical temperature detected, " "suggest system shutdown\n"); snprintf(stemp, sizeof(stemp), "%d", tmp); devctl_notify("coretemp", "Thermal", stemp, "notify=0xcc"); } } return (sysctl_handle_int(oidp, &val, 0, req)); } static int coretemp_throttle_log_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; uint64_t msr; int error, val; struct coretemp_softc *sc; dev = (device_t) arg1; msr = coretemp_get_thermal_msr(device_get_unit(dev)); sc = device_get_softc(dev); if (msr & THERM_STATUS_LOG) { coretemp_clear_thermal_msr(device_get_unit(dev)); sc->sc_throttle_log = 1; } val = sc->sc_throttle_log; error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr) return (error); else if (val != 0) return (EINVAL); coretemp_clear_thermal_msr(device_get_unit(dev)); sc->sc_throttle_log = 0; return (0); } diff --git a/sys/dev/cpufreq/cpufreq_dt.c b/sys/dev/cpufreq/cpufreq_dt.c index e35a8ec73ef4..b212b08e9a83 100644 --- a/sys/dev/cpufreq/cpufreq_dt.c +++ b/sys/dev/cpufreq/cpufreq_dt.c @@ -1,627 +1,627 @@ /*- * Copyright (c) 2018 Emmanuel Vadot * Copyright (c) 2016 Jared McNeill * * 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, 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. */ /* * Generic DT based cpufreq driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #if 0 #define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg); #else #define DPRINTF(dev, msg...) #endif enum opp_version { OPP_V1 = 1, OPP_V2, }; struct cpufreq_dt_opp { uint64_t freq; uint32_t uvolt_target; uint32_t uvolt_min; uint32_t uvolt_max; uint32_t uamps; uint32_t clk_latency; bool turbo_mode; bool opp_suspend; }; #define CPUFREQ_DT_HAVE_REGULATOR(sc) ((sc)->reg != NULL) struct cpufreq_dt_softc { device_t dev; clk_t clk; regulator_t reg; struct cpufreq_dt_opp *opp; ssize_t nopp; int cpu; cpuset_t cpus; }; static void cpufreq_dt_notify(device_t dev, uint64_t freq) { struct cpufreq_dt_softc *sc; struct pcpu *pc; int cpu; sc = device_get_softc(dev); CPU_FOREACH(cpu) { if (CPU_ISSET(cpu, &sc->cpus)) { pc = pcpu_find(cpu); pc->pc_clock = freq; } } } static const struct cpufreq_dt_opp * cpufreq_dt_find_opp(device_t dev, uint64_t freq) { struct cpufreq_dt_softc *sc; uint64_t diff, best_diff; ssize_t n, best_n; sc = device_get_softc(dev); diff = 0; best_diff = ~0; DPRINTF(dev, "Looking for freq %ju\n", freq); for (n = 0; n < sc->nopp; n++) { diff = abs64((int64_t)sc->opp[n].freq - (int64_t)freq); DPRINTF(dev, "Testing %ju, diff is %ju\n", sc->opp[n].freq, diff); if (diff < best_diff) { best_diff = diff; best_n = n; DPRINTF(dev, "%ju is best for now\n", sc->opp[n].freq); } } DPRINTF(dev, "Will use %ju\n", sc->opp[best_n].freq); return (&sc->opp[best_n]); } static void cpufreq_dt_opp_to_setting(device_t dev, const struct cpufreq_dt_opp *opp, struct cf_setting *set) { memset(set, 0, sizeof(*set)); set->freq = opp->freq / 1000000; set->volts = opp->uvolt_target / 1000; set->power = CPUFREQ_VAL_UNKNOWN; set->lat = opp->clk_latency; set->dev = dev; } static int cpufreq_dt_get(device_t dev, struct cf_setting *set) { struct cpufreq_dt_softc *sc; const struct cpufreq_dt_opp *opp; uint64_t freq; sc = device_get_softc(dev); DPRINTF(dev, "cpufreq_dt_get\n"); if (clk_get_freq(sc->clk, &freq) != 0) return (ENXIO); opp = cpufreq_dt_find_opp(dev, freq); if (opp == NULL) { device_printf(dev, "Can't find the current freq in opp\n"); return (ENOENT); } cpufreq_dt_opp_to_setting(dev, opp, set); DPRINTF(dev, "Current freq %dMhz\n", set->freq); return (0); } static int cpufreq_dt_set(device_t dev, const struct cf_setting *set) { struct cpufreq_dt_softc *sc; const struct cpufreq_dt_opp *opp, *copp; uint64_t freq; int uvolt, error; sc = device_get_softc(dev); DPRINTF(dev, "Working on cpu %d\n", sc->cpu); DPRINTF(dev, "We have %d cpu on this dev\n", CPU_COUNT(&sc->cpus)); if (!CPU_ISSET(sc->cpu, &sc->cpus)) { DPRINTF(dev, "Not for this CPU\n"); return (0); } if (clk_get_freq(sc->clk, &freq) != 0) { device_printf(dev, "Can't get current clk freq\n"); return (ENXIO); } /* * Only do the regulator work if it's required. */ if (CPUFREQ_DT_HAVE_REGULATOR(sc)) { /* Try to get current valtage by using regulator first. */ error = regulator_get_voltage(sc->reg, &uvolt); if (error != 0) { /* * Try oppoints table as backup way. However, * this is insufficient because the actual processor * frequency may not be in the table. PLL frequency * granularity can be different that granularity of * oppoint table. */ copp = cpufreq_dt_find_opp(sc->dev, freq); if (copp == NULL) { device_printf(dev, "Can't find the current freq in opp\n"); return (ENOENT); } uvolt = copp->uvolt_target; } } else uvolt = 0; opp = cpufreq_dt_find_opp(sc->dev, set->freq * 1000000); if (opp == NULL) { device_printf(dev, "Couldn't find an opp for this freq\n"); return (EINVAL); } DPRINTF(sc->dev, "Current freq %ju, uvolt: %d\n", freq, uvolt); DPRINTF(sc->dev, "Target freq %ju, , uvolt: %d\n", opp->freq, opp->uvolt_target); if (CPUFREQ_DT_HAVE_REGULATOR(sc) && (uvolt < opp->uvolt_target)) { DPRINTF(dev, "Changing regulator from %u to %u\n", uvolt, opp->uvolt_target); error = regulator_set_voltage(sc->reg, opp->uvolt_min, opp->uvolt_max); if (error != 0) { DPRINTF(dev, "Failed, backout\n"); return (ENXIO); } } DPRINTF(dev, "Setting clk to %ju\n", opp->freq); error = clk_set_freq(sc->clk, opp->freq, CLK_SET_ROUND_DOWN); if (error != 0) { DPRINTF(dev, "Failed, backout\n"); /* Restore previous voltage (best effort) */ if (CPUFREQ_DT_HAVE_REGULATOR(sc)) error = regulator_set_voltage(sc->reg, copp->uvolt_min, copp->uvolt_max); return (ENXIO); } if (CPUFREQ_DT_HAVE_REGULATOR(sc) && (uvolt > opp->uvolt_target)) { DPRINTF(dev, "Changing regulator from %u to %u\n", uvolt, opp->uvolt_target); error = regulator_set_voltage(sc->reg, opp->uvolt_min, opp->uvolt_max); if (error != 0) { DPRINTF(dev, "Failed to switch regulator to %d\n", opp->uvolt_target); /* Restore previous CPU frequency (best effort) */ (void)clk_set_freq(sc->clk, copp->freq, 0); return (ENXIO); } } if (clk_get_freq(sc->clk, &freq) == 0) cpufreq_dt_notify(dev, freq); return (0); } static int cpufreq_dt_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static int cpufreq_dt_settings(device_t dev, struct cf_setting *sets, int *count) { struct cpufreq_dt_softc *sc; ssize_t n; DPRINTF(dev, "cpufreq_dt_settings\n"); if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); if (*count < sc->nopp) { *count = (int)sc->nopp; return (E2BIG); } for (n = 0; n < sc->nopp; n++) cpufreq_dt_opp_to_setting(dev, &sc->opp[n], &sets[n]); *count = (int)sc->nopp; return (0); } static void cpufreq_dt_identify(driver_t *driver, device_t parent) { phandle_t node; /* Properties must be listed under node /cpus/cpu@0 */ node = ofw_bus_get_node(parent); /* The cpu@0 node must have the following properties */ if (!OF_hasprop(node, "clocks")) return; if (!OF_hasprop(node, "operating-points") && !OF_hasprop(node, "operating-points-v2")) return; - if (device_find_child(parent, "cpufreq_dt", -1) != NULL) + if (device_find_child(parent, "cpufreq_dt", DEVICE_UNIT_ANY) != NULL) return; if (BUS_ADD_CHILD(parent, 0, "cpufreq_dt", device_get_unit(parent)) == NULL) device_printf(parent, "add cpufreq_dt child failed\n"); } static int cpufreq_dt_probe(device_t dev) { phandle_t node; node = ofw_bus_get_node(device_get_parent(dev)); /* * Note - supply isn't required here for probe; we'll check * it out in more detail during attach. */ if (!OF_hasprop(node, "clocks")) return (ENXIO); if (!OF_hasprop(node, "operating-points") && !OF_hasprop(node, "operating-points-v2")) return (ENXIO); device_set_desc(dev, "Generic cpufreq driver"); return (BUS_PROBE_GENERIC); } static int cpufreq_dt_oppv1_parse(struct cpufreq_dt_softc *sc, phandle_t node) { uint32_t *opp, lat; ssize_t n; sc->nopp = OF_getencprop_alloc_multi(node, "operating-points", sizeof(uint32_t) * 2, (void **)&opp); if (sc->nopp == -1) return (ENXIO); if (OF_getencprop(node, "clock-latency", &lat, sizeof(lat)) == -1) lat = CPUFREQ_VAL_UNKNOWN; sc->opp = malloc(sizeof(*sc->opp) * sc->nopp, M_DEVBUF, M_WAITOK); for (n = 0; n < sc->nopp; n++) { sc->opp[n].freq = opp[n * 2 + 0] * 1000; sc->opp[n].uvolt_min = opp[n * 2 + 1]; sc->opp[n].uvolt_max = sc->opp[n].uvolt_min; sc->opp[n].uvolt_target = sc->opp[n].uvolt_min; sc->opp[n].clk_latency = lat; if (bootverbose) device_printf(sc->dev, "%ju.%03ju MHz, %u uV\n", sc->opp[n].freq / 1000000, sc->opp[n].freq % 1000000, sc->opp[n].uvolt_target); } free(opp, M_OFWPROP); return (0); } static int cpufreq_dt_oppv2_parse(struct cpufreq_dt_softc *sc, phandle_t node) { phandle_t opp, opp_table, opp_xref; pcell_t cell[2]; uint32_t *volts, lat; int nvolt, i; /* * operating-points-v2 does not require the voltage entries * and a regulator. So, it's OK if they're not there. */ if (OF_getencprop(node, "operating-points-v2", &opp_xref, sizeof(opp_xref)) == -1) { device_printf(sc->dev, "Cannot get xref to oppv2 table\n"); return (ENXIO); } opp_table = OF_node_from_xref(opp_xref); if (opp_table == opp_xref) return (ENXIO); if (!OF_hasprop(opp_table, "opp-shared") && mp_ncpus > 1) { device_printf(sc->dev, "Only opp-shared is supported\n"); return (ENXIO); } for (opp = OF_child(opp_table); opp > 0; opp = OF_peer(opp)) sc->nopp += 1; sc->opp = malloc(sizeof(*sc->opp) * sc->nopp, M_DEVBUF, M_WAITOK); for (i = 0, opp_table = OF_child(opp_table); opp_table > 0; opp_table = OF_peer(opp_table), i++) { /* opp-hz is a required property */ if (OF_getencprop(opp_table, "opp-hz", cell, sizeof(cell)) == -1) continue; sc->opp[i].freq = cell[0]; sc->opp[i].freq <<= 32; sc->opp[i].freq |= cell[1]; if (OF_getencprop(opp_table, "clock-latency", &lat, sizeof(lat)) == -1) sc->opp[i].clk_latency = CPUFREQ_VAL_UNKNOWN; else sc->opp[i].clk_latency = (int)lat; if (OF_hasprop(opp_table, "turbo-mode")) sc->opp[i].turbo_mode = true; if (OF_hasprop(opp_table, "opp-suspend")) sc->opp[i].opp_suspend = true; if (CPUFREQ_DT_HAVE_REGULATOR(sc)) { nvolt = OF_getencprop_alloc_multi(opp_table, "opp-microvolt", sizeof(*volts), (void **)&volts); if (nvolt == 1) { sc->opp[i].uvolt_target = volts[0]; sc->opp[i].uvolt_min = volts[0]; sc->opp[i].uvolt_max = volts[0]; } else if (nvolt == 3) { sc->opp[i].uvolt_target = volts[0]; sc->opp[i].uvolt_min = volts[1]; sc->opp[i].uvolt_max = volts[2]; } else { device_printf(sc->dev, "Wrong count of opp-microvolt property\n"); OF_prop_free(volts); free(sc->opp, M_DEVBUF); return (ENXIO); } OF_prop_free(volts); } else { /* No regulator required; don't add anything */ sc->opp[i].uvolt_target = 0; sc->opp[i].uvolt_min = 0; sc->opp[i].uvolt_max = 0; } if (bootverbose) device_printf(sc->dev, "%ju.%03ju Mhz (%u uV)\n", sc->opp[i].freq / 1000000, sc->opp[i].freq % 1000000, sc->opp[i].uvolt_target); } return (0); } static int cpufreq_dt_attach(device_t dev) { struct cpufreq_dt_softc *sc; phandle_t node; phandle_t cnode, opp, copp; int cpu; uint64_t freq; int rv = 0; char device_type[16]; enum opp_version version; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(device_get_parent(dev)); sc->cpu = device_get_unit(device_get_parent(dev)); sc->reg = NULL; DPRINTF(dev, "cpu=%d\n", sc->cpu); if (sc->cpu >= mp_ncpus) { device_printf(dev, "Not attaching as cpu is not present\n"); rv = ENXIO; goto error; } /* * Cache if we have the regulator supply but don't error out * quite yet. If it's operating-points-v2 then regulator * and voltage entries are optional. */ if (regulator_get_by_ofw_property(dev, node, "cpu-supply", &sc->reg) == 0) device_printf(dev, "Found cpu-supply\n"); else if (regulator_get_by_ofw_property(dev, node, "cpu0-supply", &sc->reg) == 0) device_printf(dev, "Found cpu0-supply\n"); /* * Determine which operating mode we're in. Error out if we expect * a regulator but we're not getting it. */ if (OF_hasprop(node, "operating-points")) version = OPP_V1; else if (OF_hasprop(node, "operating-points-v2")) version = OPP_V2; else { device_printf(dev, "didn't find a valid operating-points or v2 node\n"); rv = ENXIO; goto error; } /* * Now, we only enforce needing a regulator for v1. */ if ((version == OPP_V1) && !CPUFREQ_DT_HAVE_REGULATOR(sc)) { device_printf(dev, "no regulator for %s\n", ofw_bus_get_name(device_get_parent(dev))); rv = ENXIO; goto error; } if (clk_get_by_ofw_index(dev, node, 0, &sc->clk) != 0) { device_printf(dev, "no clock for %s\n", ofw_bus_get_name(device_get_parent(dev))); rv = ENXIO; goto error; } if (version == OPP_V1) { rv = cpufreq_dt_oppv1_parse(sc, node); if (rv != 0) { device_printf(dev, "Failed to parse opp-v1 table\n"); goto error; } OF_getencprop(node, "operating-points", &opp, sizeof(opp)); } else if (version == OPP_V2) { rv = cpufreq_dt_oppv2_parse(sc, node); if (rv != 0) { device_printf(dev, "Failed to parse opp-v2 table\n"); goto error; } OF_getencprop(node, "operating-points-v2", &opp, sizeof(opp)); } else { device_printf(dev, "operating points version is incorrect\n"); goto error; } /* * Find all CPUs that share the same opp table */ CPU_ZERO(&sc->cpus); cnode = OF_parent(node); for (cpu = 0, cnode = OF_child(cnode); cnode > 0; cnode = OF_peer(cnode)) { if (OF_getprop(cnode, "device_type", device_type, sizeof(device_type)) <= 0) continue; if (strcmp(device_type, "cpu") != 0) continue; if (cpu == sc->cpu) { DPRINTF(dev, "Skipping our cpu\n"); CPU_SET(cpu, &sc->cpus); cpu++; continue; } DPRINTF(dev, "Testing CPU %d\n", cpu); copp = -1; if (version == OPP_V1) OF_getencprop(cnode, "operating-points", &copp, sizeof(copp)); else if (version == OPP_V2) OF_getencprop(cnode, "operating-points-v2", &copp, sizeof(copp)); if (opp == copp) { DPRINTF(dev, "CPU %d is using the same opp as this one (%d)\n", cpu, sc->cpu); CPU_SET(cpu, &sc->cpus); } cpu++; } if (clk_get_freq(sc->clk, &freq) == 0) cpufreq_dt_notify(dev, freq); cpufreq_register(dev); return (0); error: if (CPUFREQ_DT_HAVE_REGULATOR(sc)) regulator_release(sc->reg); return (rv); } static device_method_t cpufreq_dt_methods[] = { /* Device interface */ DEVMETHOD(device_identify, cpufreq_dt_identify), DEVMETHOD(device_probe, cpufreq_dt_probe), DEVMETHOD(device_attach, cpufreq_dt_attach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_get, cpufreq_dt_get), DEVMETHOD(cpufreq_drv_set, cpufreq_dt_set), DEVMETHOD(cpufreq_drv_type, cpufreq_dt_type), DEVMETHOD(cpufreq_drv_settings, cpufreq_dt_settings), DEVMETHOD_END }; static driver_t cpufreq_dt_driver = { "cpufreq_dt", cpufreq_dt_methods, sizeof(struct cpufreq_dt_softc), }; DRIVER_MODULE(cpufreq_dt, cpu, cpufreq_dt_driver, 0, 0); MODULE_VERSION(cpufreq_dt, 1); diff --git a/sys/dev/cpufreq/ichss.c b/sys/dev/cpufreq/ichss.c index f1ec62ed6d8a..6c30bbb9700d 100644 --- a/sys/dev/cpufreq/ichss.c +++ b/sys/dev/cpufreq/ichss.c @@ -1,405 +1,407 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004-2005 Nate Lawson (SDG) * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" /* * The SpeedStep ICH feature is a chipset-initiated voltage and frequency * transition available on the ICH2M, 3M, and 4M. It is different from * the newer Pentium-M SpeedStep feature. It offers only two levels of * frequency/voltage. Often, the BIOS will select one of the levels via * SMM code during the power-on process (i.e., choose a lower level if the * system is off AC power.) */ struct ichss_softc { device_t dev; int bm_rid; /* Bus-mastering control (PM2REG). */ struct resource *bm_reg; int ctrl_rid; /* Control/status register. */ struct resource *ctrl_reg; struct cf_setting sets[2]; /* Only two settings. */ }; /* Supported PCI IDs. */ #define PCI_VENDOR_INTEL 0x8086 #define PCI_DEV_82801BA 0x244c /* ICH2M */ #define PCI_DEV_82801CA 0x248c /* ICH3M */ #define PCI_DEV_82801DB 0x24cc /* ICH4M */ #define PCI_DEV_82815_MC 0x1130 /* Unsupported/buggy part */ /* PCI config registers for finding PMBASE and enabling SpeedStep. */ #define ICHSS_PMBASE_OFFSET 0x40 #define ICHSS_PMCFG_OFFSET 0xa0 /* Values and masks. */ #define ICHSS_ENABLE (1<<3) /* Enable SpeedStep control. */ #define ICHSS_IO_REG 0x1 /* Access register via I/O space. */ #define ICHSS_PMBASE_MASK 0xff80 /* PMBASE address bits. */ #define ICHSS_CTRL_BIT 0x1 /* 0 is high speed, 1 is low. */ #define ICHSS_BM_DISABLE 0x1 /* Offsets from PMBASE for various registers. */ #define ICHSS_BM_OFFSET 0x20 #define ICHSS_CTRL_OFFSET 0x50 #define ICH_GET_REG(reg) \ (bus_space_read_1(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0)) #define ICH_SET_REG(reg, val) \ (bus_space_write_1(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0, (val))) static void ichss_identify(driver_t *driver, device_t parent); static int ichss_probe(device_t dev); static int ichss_attach(device_t dev); static int ichss_detach(device_t dev); static int ichss_settings(device_t dev, struct cf_setting *sets, int *count); static int ichss_set(device_t dev, const struct cf_setting *set); static int ichss_get(device_t dev, struct cf_setting *set); static int ichss_type(device_t dev, int *type); static device_method_t ichss_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ichss_identify), DEVMETHOD(device_probe, ichss_probe), DEVMETHOD(device_attach, ichss_attach), DEVMETHOD(device_detach, ichss_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, ichss_set), DEVMETHOD(cpufreq_drv_get, ichss_get), DEVMETHOD(cpufreq_drv_type, ichss_type), DEVMETHOD(cpufreq_drv_settings, ichss_settings), DEVMETHOD_END }; static driver_t ichss_driver = { "ichss", ichss_methods, sizeof(struct ichss_softc) }; DRIVER_MODULE(ichss, cpu, ichss_driver, 0, 0); static device_t ich_device; #if 0 #define DPRINT(x...) printf(x) #else #define DPRINT(x...) #endif static void ichss_identify(driver_t *driver, device_t parent) { device_t child; uint32_t pmbase; if (resource_disabled("ichss", 0)) return; /* * It appears that ICH SpeedStep only requires a single CPU to * set the value (since the chipset is shared by all CPUs.) * Thus, we only add a child to cpu 0. */ if (device_get_unit(parent) != 0) return; /* Avoid duplicates. */ - if (device_find_child(parent, "ichss", -1)) + if (device_find_child(parent, "ichss", DEVICE_UNIT_ANY)) return; /* * ICH2/3/4-M I/O Controller Hub is at bus 0, slot 1F, function 0. * E.g. see Section 6.1 "PCI Devices and Functions" and table 6.1 of * Intel(r) 82801BA I/O Controller Hub 2 (ICH2) and Intel(r) 82801BAM * I/O Controller Hub 2 Mobile (ICH2-M). */ ich_device = pci_find_bsf(0, 0x1f, 0); if (ich_device == NULL || pci_get_vendor(ich_device) != PCI_VENDOR_INTEL || (pci_get_device(ich_device) != PCI_DEV_82801BA && pci_get_device(ich_device) != PCI_DEV_82801CA && pci_get_device(ich_device) != PCI_DEV_82801DB)) return; /* * Certain systems with ICH2 and an Intel 82815_MC host bridge * where the host bridge's revision is < 5 lockup if SpeedStep * is used. */ if (pci_get_device(ich_device) == PCI_DEV_82801BA) { device_t hostb; hostb = pci_find_bsf(0, 0, 0); if (hostb != NULL && pci_get_vendor(hostb) == PCI_VENDOR_INTEL && pci_get_device(hostb) == PCI_DEV_82815_MC && pci_get_revid(hostb) < 5) return; } /* Find the PMBASE register from our PCI config header. */ pmbase = pci_read_config(ich_device, ICHSS_PMBASE_OFFSET, sizeof(pmbase)); if ((pmbase & ICHSS_IO_REG) == 0) { printf("ichss: invalid PMBASE memory type\n"); return; } pmbase &= ICHSS_PMBASE_MASK; if (pmbase == 0) { printf("ichss: invalid zero PMBASE address\n"); return; } DPRINT("ichss: PMBASE is %#x\n", pmbase); child = BUS_ADD_CHILD(parent, 20, "ichss", 0); if (child == NULL) { device_printf(parent, "add SpeedStep child failed\n"); return; } /* Add the bus master arbitration and control registers. */ bus_set_resource(child, SYS_RES_IOPORT, 0, pmbase + ICHSS_BM_OFFSET, 1); bus_set_resource(child, SYS_RES_IOPORT, 1, pmbase + ICHSS_CTRL_OFFSET, 1); } static int ichss_probe(device_t dev) { device_t est_dev, perf_dev; int error, type; /* * If the ACPI perf driver has attached and is not just offering * info, let it manage things. Also, if Enhanced SpeedStep is * available, don't attach. */ - perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1); + perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", + DEVICE_UNIT_ANY); if (perf_dev && device_is_attached(perf_dev)) { error = CPUFREQ_DRV_TYPE(perf_dev, &type); if (error == 0 && (type & CPUFREQ_FLAG_INFO_ONLY) == 0) return (ENXIO); } - est_dev = device_find_child(device_get_parent(dev), "est", -1); + est_dev = device_find_child(device_get_parent(dev), "est", + DEVICE_UNIT_ANY); if (est_dev && device_is_attached(est_dev)) return (ENXIO); device_set_desc(dev, "SpeedStep ICH"); return (-1000); } static int ichss_attach(device_t dev) { struct ichss_softc *sc; uint16_t ss_en; sc = device_get_softc(dev); sc->dev = dev; sc->bm_rid = 0; sc->bm_reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->bm_rid, RF_ACTIVE); if (sc->bm_reg == NULL) { device_printf(dev, "failed to alloc BM arb register\n"); return (ENXIO); } sc->ctrl_rid = 1; sc->ctrl_reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->ctrl_rid, RF_ACTIVE); if (sc->ctrl_reg == NULL) { device_printf(dev, "failed to alloc control register\n"); bus_release_resource(dev, SYS_RES_IOPORT, sc->bm_rid, sc->bm_reg); return (ENXIO); } /* Activate SpeedStep control if not already enabled. */ ss_en = pci_read_config(ich_device, ICHSS_PMCFG_OFFSET, sizeof(ss_en)); if ((ss_en & ICHSS_ENABLE) == 0) { device_printf(dev, "enabling SpeedStep support\n"); pci_write_config(ich_device, ICHSS_PMCFG_OFFSET, ss_en | ICHSS_ENABLE, sizeof(ss_en)); } /* Setup some defaults for our exported settings. */ sc->sets[0].freq = CPUFREQ_VAL_UNKNOWN; sc->sets[0].volts = CPUFREQ_VAL_UNKNOWN; sc->sets[0].power = CPUFREQ_VAL_UNKNOWN; sc->sets[0].lat = 1000; sc->sets[0].dev = dev; sc->sets[1] = sc->sets[0]; cpufreq_register(dev); return (0); } static int ichss_detach(device_t dev) { /* TODO: teardown BM and CTRL registers. */ return (ENXIO); } static int ichss_settings(device_t dev, struct cf_setting *sets, int *count) { struct ichss_softc *sc; struct cf_setting set; int first, i; if (sets == NULL || count == NULL) return (EINVAL); if (*count < 2) { *count = 2; return (E2BIG); } sc = device_get_softc(dev); /* * Estimate frequencies for both levels, temporarily switching to * the other one if we haven't calibrated it yet. */ ichss_get(dev, &set); for (i = 0; i < 2; i++) { if (sc->sets[i].freq == CPUFREQ_VAL_UNKNOWN) { first = (i == 0) ? 1 : 0; ichss_set(dev, &sc->sets[i]); ichss_set(dev, &sc->sets[first]); } } bcopy(sc->sets, sets, sizeof(sc->sets)); *count = 2; return (0); } static int ichss_set(device_t dev, const struct cf_setting *set) { struct ichss_softc *sc; uint8_t bmval, new_val, old_val, req_val; uint64_t rate; register_t regs; /* Look up appropriate bit value based on frequency. */ sc = device_get_softc(dev); if (CPUFREQ_CMP(set->freq, sc->sets[0].freq)) req_val = 0; else if (CPUFREQ_CMP(set->freq, sc->sets[1].freq)) req_val = ICHSS_CTRL_BIT; else return (EINVAL); DPRINT("ichss: requested setting %d\n", req_val); /* Disable interrupts and get the other register contents. */ regs = intr_disable(); old_val = ICH_GET_REG(sc->ctrl_reg) & ~ICHSS_CTRL_BIT; /* * Disable bus master arbitration, write the new value to the control * register, and then re-enable bus master arbitration. */ bmval = ICH_GET_REG(sc->bm_reg) | ICHSS_BM_DISABLE; ICH_SET_REG(sc->bm_reg, bmval); ICH_SET_REG(sc->ctrl_reg, old_val | req_val); ICH_SET_REG(sc->bm_reg, bmval & ~ICHSS_BM_DISABLE); /* Get the new value and re-enable interrupts. */ new_val = ICH_GET_REG(sc->ctrl_reg); intr_restore(regs); /* Check if the desired state was indeed selected. */ if (req_val != (new_val & ICHSS_CTRL_BIT)) { device_printf(sc->dev, "transition to %d failed\n", req_val); return (ENXIO); } /* Re-initialize our cycle counter if we don't know this new state. */ if (sc->sets[req_val].freq == CPUFREQ_VAL_UNKNOWN) { cpu_est_clockrate(0, &rate); sc->sets[req_val].freq = rate / 1000000; DPRINT("ichss: set calibrated new rate of %d\n", sc->sets[req_val].freq); } return (0); } static int ichss_get(device_t dev, struct cf_setting *set) { struct ichss_softc *sc; uint64_t rate; uint8_t state; sc = device_get_softc(dev); state = ICH_GET_REG(sc->ctrl_reg) & ICHSS_CTRL_BIT; /* If we haven't changed settings yet, estimate the current value. */ if (sc->sets[state].freq == CPUFREQ_VAL_UNKNOWN) { cpu_est_clockrate(0, &rate); sc->sets[state].freq = rate / 1000000; DPRINT("ichss: get calibrated new rate of %d\n", sc->sets[state].freq); } *set = sc->sets[state]; return (0); } static int ichss_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } diff --git a/sys/dev/cxgb/cxgb_main.c b/sys/dev/cxgb/cxgb_main.c index 882d1c6cc4a4..616a2ecc1a37 100644 --- a/sys/dev/cxgb/cxgb_main.c +++ b/sys/dev/cxgb/cxgb_main.c @@ -1,3603 +1,3604 @@ /************************************************************************** SPDX-License-Identifier: BSD-2-Clause Copyright (c) 2007-2009, Chelsio Inc. 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, this list of conditions and the following disclaimer. 2. Neither the name of the Chelsio Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. ***************************************************************************/ #include #include "opt_inet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PRIV_SUPPORTED #include #endif static int cxgb_setup_interrupts(adapter_t *); static void cxgb_teardown_interrupts(adapter_t *); static void cxgb_init(void *); static int cxgb_init_locked(struct port_info *); static int cxgb_uninit_locked(struct port_info *); static int cxgb_uninit_synchronized(struct port_info *); static int cxgb_ioctl(if_t, unsigned long, caddr_t); static int cxgb_media_change(if_t); static int cxgb_ifm_type(int); static void cxgb_build_medialist(struct port_info *); static void cxgb_media_status(if_t, struct ifmediareq *); static uint64_t cxgb_get_counter(if_t, ift_counter); static int setup_sge_qsets(adapter_t *); static void cxgb_async_intr(void *); static void cxgb_tick_handler(void *, int); static void cxgb_tick(void *); static void link_check_callout(void *); static void check_link_status(void *, int); static void setup_rss(adapter_t *sc); static int alloc_filters(struct adapter *); static int setup_hw_filters(struct adapter *); static int set_filter(struct adapter *, int, const struct filter_info *); static inline void mk_set_tcb_field(struct cpl_set_tcb_field *, unsigned int, unsigned int, u64, u64); static inline void set_tcb_field_ulp(struct cpl_set_tcb_field *, unsigned int, unsigned int, u64, u64); #ifdef TCP_OFFLOAD static int cpl_not_handled(struct sge_qset *, struct rsp_desc *, struct mbuf *); #endif /* Attachment glue for the PCI controller end of the device. Each port of * the device is attached separately, as defined later. */ static int cxgb_controller_probe(device_t); static int cxgb_controller_attach(device_t); static int cxgb_controller_detach(device_t); static void cxgb_free(struct adapter *); static __inline void reg_block_dump(struct adapter *ap, uint8_t *buf, unsigned int start, unsigned int end); static void cxgb_get_regs(adapter_t *sc, struct ch_ifconf_regs *regs, uint8_t *buf); static int cxgb_get_regs_len(void); static void touch_bars(device_t dev); static void cxgb_update_mac_settings(struct port_info *p); #ifdef TCP_OFFLOAD static int toe_capability(struct port_info *, int); #endif /* Table for probing the cards. The desc field isn't actually used */ struct cxgb_ident { uint16_t vendor; uint16_t device; int index; char *desc; } cxgb_identifiers[] = { {PCI_VENDOR_ID_CHELSIO, 0x0020, 0, "PE9000"}, {PCI_VENDOR_ID_CHELSIO, 0x0021, 1, "T302E"}, {PCI_VENDOR_ID_CHELSIO, 0x0022, 2, "T310E"}, {PCI_VENDOR_ID_CHELSIO, 0x0023, 3, "T320X"}, {PCI_VENDOR_ID_CHELSIO, 0x0024, 1, "T302X"}, {PCI_VENDOR_ID_CHELSIO, 0x0025, 3, "T320E"}, {PCI_VENDOR_ID_CHELSIO, 0x0026, 2, "T310X"}, {PCI_VENDOR_ID_CHELSIO, 0x0030, 2, "T3B10"}, {PCI_VENDOR_ID_CHELSIO, 0x0031, 3, "T3B20"}, {PCI_VENDOR_ID_CHELSIO, 0x0032, 1, "T3B02"}, {PCI_VENDOR_ID_CHELSIO, 0x0033, 4, "T3B04"}, {PCI_VENDOR_ID_CHELSIO, 0x0035, 6, "T3C10"}, {PCI_VENDOR_ID_CHELSIO, 0x0036, 3, "S320E-CR"}, {PCI_VENDOR_ID_CHELSIO, 0x0037, 7, "N320E-G2"}, {0, 0, 0, NULL} }; static device_method_t cxgb_controller_methods[] = { DEVMETHOD(device_probe, cxgb_controller_probe), DEVMETHOD(device_attach, cxgb_controller_attach), DEVMETHOD(device_detach, cxgb_controller_detach), DEVMETHOD_END }; static driver_t cxgb_controller_driver = { "cxgbc", cxgb_controller_methods, sizeof(struct adapter) }; static int cxgbc_mod_event(module_t, int, void *); DRIVER_MODULE(cxgbc, pci, cxgb_controller_driver, cxgbc_mod_event, NULL); MODULE_PNP_INFO("U16:vendor;U16:device", pci, cxgbc, cxgb_identifiers, nitems(cxgb_identifiers) - 1); MODULE_VERSION(cxgbc, 1); MODULE_DEPEND(cxgbc, firmware, 1, 1, 1); /* * Attachment glue for the ports. Attachment is done directly to the * controller device. */ static int cxgb_port_probe(device_t); static int cxgb_port_attach(device_t); static int cxgb_port_detach(device_t); static device_method_t cxgb_port_methods[] = { DEVMETHOD(device_probe, cxgb_port_probe), DEVMETHOD(device_attach, cxgb_port_attach), DEVMETHOD(device_detach, cxgb_port_detach), { 0, 0 } }; static driver_t cxgb_port_driver = { "cxgb", cxgb_port_methods, 0 }; static d_ioctl_t cxgb_extension_ioctl; static d_open_t cxgb_extension_open; static d_close_t cxgb_extension_close; static struct cdevsw cxgb_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = cxgb_extension_open, .d_close = cxgb_extension_close, .d_ioctl = cxgb_extension_ioctl, .d_name = "cxgb", }; DRIVER_MODULE(cxgb, cxgbc, cxgb_port_driver, 0, 0); MODULE_VERSION(cxgb, 1); DEBUGNET_DEFINE(cxgb); static struct mtx t3_list_lock; static SLIST_HEAD(, adapter) t3_list; #ifdef TCP_OFFLOAD static struct mtx t3_uld_list_lock; static SLIST_HEAD(, uld_info) t3_uld_list; #endif /* * The driver uses the best interrupt scheme available on a platform in the * order MSI-X, MSI, legacy pin interrupts. This parameter determines which * of these schemes the driver may consider as follows: * * msi = 2: choose from among all three options * msi = 1 : only consider MSI and pin interrupts * msi = 0: force pin interrupts */ static int msi_allowed = 2; SYSCTL_NODE(_hw, OID_AUTO, cxgb, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "CXGB driver parameters"); SYSCTL_INT(_hw_cxgb, OID_AUTO, msi_allowed, CTLFLAG_RDTUN, &msi_allowed, 0, "MSI-X, MSI, INTx selector"); /* * The driver uses an auto-queue algorithm by default. * To disable it and force a single queue-set per port, use multiq = 0 */ static int multiq = 1; SYSCTL_INT(_hw_cxgb, OID_AUTO, multiq, CTLFLAG_RDTUN, &multiq, 0, "use min(ncpus/ports, 8) queue-sets per port"); /* * By default the driver will not update the firmware unless * it was compiled against a newer version * */ static int force_fw_update = 0; SYSCTL_INT(_hw_cxgb, OID_AUTO, force_fw_update, CTLFLAG_RDTUN, &force_fw_update, 0, "update firmware even if up to date"); int cxgb_use_16k_clusters = -1; SYSCTL_INT(_hw_cxgb, OID_AUTO, use_16k_clusters, CTLFLAG_RDTUN, &cxgb_use_16k_clusters, 0, "use 16kB clusters for the jumbo queue "); static int nfilters = -1; SYSCTL_INT(_hw_cxgb, OID_AUTO, nfilters, CTLFLAG_RDTUN, &nfilters, 0, "max number of entries in the filter table"); enum { MAX_TXQ_ENTRIES = 16384, MAX_CTRL_TXQ_ENTRIES = 1024, MAX_RSPQ_ENTRIES = 16384, MAX_RX_BUFFERS = 16384, MAX_RX_JUMBO_BUFFERS = 16384, MIN_TXQ_ENTRIES = 4, MIN_CTRL_TXQ_ENTRIES = 4, MIN_RSPQ_ENTRIES = 32, MIN_FL_ENTRIES = 32, MIN_FL_JUMBO_ENTRIES = 32 }; struct filter_info { u32 sip; u32 sip_mask; u32 dip; u16 sport; u16 dport; u32 vlan:12; u32 vlan_prio:3; u32 mac_hit:1; u32 mac_idx:4; u32 mac_vld:1; u32 pkt_type:2; u32 report_filter_id:1; u32 pass:1; u32 rss:1; u32 qset:3; u32 locked:1; u32 valid:1; }; enum { FILTER_NO_VLAN_PRI = 7 }; #define EEPROM_MAGIC 0x38E2F10C #define PORT_MASK ((1 << MAX_NPORTS) - 1) static int set_eeprom(struct port_info *pi, const uint8_t *data, int len, int offset); static __inline char t3rev2char(struct adapter *adapter) { char rev = 'z'; switch(adapter->params.rev) { case T3_REV_A: rev = 'a'; break; case T3_REV_B: case T3_REV_B2: rev = 'b'; break; case T3_REV_C: rev = 'c'; break; } return rev; } static struct cxgb_ident * cxgb_get_ident(device_t dev) { struct cxgb_ident *id; for (id = cxgb_identifiers; id->desc != NULL; id++) { if ((id->vendor == pci_get_vendor(dev)) && (id->device == pci_get_device(dev))) { return (id); } } return (NULL); } static const struct adapter_info * cxgb_get_adapter_info(device_t dev) { struct cxgb_ident *id; const struct adapter_info *ai; id = cxgb_get_ident(dev); if (id == NULL) return (NULL); ai = t3_get_adapter_info(id->index); return (ai); } static int cxgb_controller_probe(device_t dev) { const struct adapter_info *ai; const char *ports; int nports; ai = cxgb_get_adapter_info(dev); if (ai == NULL) return (ENXIO); nports = ai->nports0 + ai->nports1; if (nports == 1) ports = "port"; else ports = "ports"; device_set_descf(dev, "%s, %d %s", ai->desc, nports, ports); return (BUS_PROBE_DEFAULT); } #define FW_FNAME "cxgb_t3fw" #define TPEEPROM_NAME "cxgb_t3%c_tp_eeprom" #define TPSRAM_NAME "cxgb_t3%c_protocol_sram" static int upgrade_fw(adapter_t *sc) { const struct firmware *fw; int status; u32 vers; if ((fw = firmware_get(FW_FNAME)) == NULL) { device_printf(sc->dev, "Could not find firmware image %s\n", FW_FNAME); return (ENOENT); } else device_printf(sc->dev, "installing firmware on card\n"); status = t3_load_fw(sc, (const uint8_t *)fw->data, fw->datasize); if (status != 0) { device_printf(sc->dev, "failed to install firmware: %d\n", status); } else { t3_get_fw_version(sc, &vers); snprintf(&sc->fw_version[0], sizeof(sc->fw_version), "%d.%d.%d", G_FW_VERSION_MAJOR(vers), G_FW_VERSION_MINOR(vers), G_FW_VERSION_MICRO(vers)); } firmware_put(fw, FIRMWARE_UNLOAD); return (status); } /* * The cxgb_controller_attach function is responsible for the initial * bringup of the device. Its responsibilities include: * * 1. Determine if the device supports MSI or MSI-X. * 2. Allocate bus resources so that we can access the Base Address Register * 3. Create and initialize mutexes for the controller and its control * logic such as SGE and MDIO. * 4. Call hardware specific setup routine for the adapter as a whole. * 5. Allocate the BAR for doing MSI-X. * 6. Setup the line interrupt iff MSI-X is not supported. * 7. Create the driver's taskq. * 8. Start one task queue service thread. * 9. Check if the firmware and SRAM are up-to-date. They will be * auto-updated later (before FULL_INIT_DONE), if required. * 10. Create a child device for each MAC (port) * 11. Initialize T3 private state. * 12. Trigger the LED * 13. Setup offload iff supported. * 14. Reset/restart the tick callout. * 15. Attach sysctls * * NOTE: Any modification or deviation from this list MUST be reflected in * the above comment. Failure to do so will result in problems on various * error conditions including link flapping. */ static int cxgb_controller_attach(device_t dev) { device_t child; const struct adapter_info *ai; struct adapter *sc; int i, error = 0; uint32_t vers; int port_qsets = 1; int msi_needed, reg; sc = device_get_softc(dev); sc->dev = dev; sc->msi_count = 0; ai = cxgb_get_adapter_info(dev); snprintf(sc->lockbuf, ADAPTER_LOCK_NAME_LEN, "cxgb controller lock %d", device_get_unit(dev)); ADAPTER_LOCK_INIT(sc, sc->lockbuf); snprintf(sc->reglockbuf, ADAPTER_LOCK_NAME_LEN, "SGE reg lock %d", device_get_unit(dev)); snprintf(sc->mdiolockbuf, ADAPTER_LOCK_NAME_LEN, "cxgb mdio lock %d", device_get_unit(dev)); snprintf(sc->elmerlockbuf, ADAPTER_LOCK_NAME_LEN, "cxgb elmer lock %d", device_get_unit(dev)); MTX_INIT(&sc->sge.reg_lock, sc->reglockbuf, NULL, MTX_SPIN); MTX_INIT(&sc->mdio_lock, sc->mdiolockbuf, NULL, MTX_DEF); MTX_INIT(&sc->elmer_lock, sc->elmerlockbuf, NULL, MTX_DEF); mtx_lock(&t3_list_lock); SLIST_INSERT_HEAD(&t3_list, sc, link); mtx_unlock(&t3_list_lock); /* find the PCIe link width and set max read request to 4KB*/ if (pci_find_cap(dev, PCIY_EXPRESS, ®) == 0) { uint16_t lnk; lnk = pci_read_config(dev, reg + PCIER_LINK_STA, 2); sc->link_width = (lnk & PCIEM_LINK_STA_WIDTH) >> 4; if (sc->link_width < 8 && (ai->caps & SUPPORTED_10000baseT_Full)) { device_printf(sc->dev, "PCIe x%d Link, expect reduced performance\n", sc->link_width); } pci_set_max_read_req(dev, 4096); } touch_bars(dev); pci_enable_busmaster(dev); /* * Allocate the registers and make them available to the driver. * The registers that we care about for NIC mode are in BAR 0 */ sc->regs_rid = PCIR_BAR(0); if ((sc->regs_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regs_rid, RF_ACTIVE)) == NULL) { device_printf(dev, "Cannot allocate BAR region 0\n"); error = ENXIO; goto out; } sc->bt = rman_get_bustag(sc->regs_res); sc->bh = rman_get_bushandle(sc->regs_res); sc->mmio_len = rman_get_size(sc->regs_res); for (i = 0; i < MAX_NPORTS; i++) sc->port[i].adapter = sc; if (t3_prep_adapter(sc, ai, 1) < 0) { printf("prep adapter failed\n"); error = ENODEV; goto out; } sc->udbs_rid = PCIR_BAR(2); sc->udbs_res = NULL; if (is_offload(sc) && ((sc->udbs_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->udbs_rid, RF_ACTIVE)) == NULL)) { device_printf(dev, "Cannot allocate BAR region 1\n"); error = ENXIO; goto out; } /* Allocate the BAR for doing MSI-X. If it succeeds, try to allocate * enough messages for the queue sets. If that fails, try falling * back to MSI. If that fails, then try falling back to the legacy * interrupt pin model. */ sc->msix_regs_rid = 0x20; if ((msi_allowed >= 2) && (sc->msix_regs_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->msix_regs_rid, RF_ACTIVE)) != NULL) { if (multiq) port_qsets = min(SGE_QSETS/sc->params.nports, mp_ncpus); msi_needed = sc->msi_count = sc->params.nports * port_qsets + 1; if (pci_msix_count(dev) == 0 || (error = pci_alloc_msix(dev, &sc->msi_count)) != 0 || sc->msi_count != msi_needed) { device_printf(dev, "alloc msix failed - " "msi_count=%d, msi_needed=%d, err=%d; " "will try MSI\n", sc->msi_count, msi_needed, error); sc->msi_count = 0; port_qsets = 1; pci_release_msi(dev); bus_release_resource(dev, SYS_RES_MEMORY, sc->msix_regs_rid, sc->msix_regs_res); sc->msix_regs_res = NULL; } else { sc->flags |= USING_MSIX; sc->cxgb_intr = cxgb_async_intr; device_printf(dev, "using MSI-X interrupts (%u vectors)\n", sc->msi_count); } } if ((msi_allowed >= 1) && (sc->msi_count == 0)) { sc->msi_count = 1; if ((error = pci_alloc_msi(dev, &sc->msi_count)) != 0) { device_printf(dev, "alloc msi failed - " "err=%d; will try INTx\n", error); sc->msi_count = 0; port_qsets = 1; pci_release_msi(dev); } else { sc->flags |= USING_MSI; sc->cxgb_intr = t3_intr_msi; device_printf(dev, "using MSI interrupts\n"); } } if (sc->msi_count == 0) { device_printf(dev, "using line interrupts\n"); sc->cxgb_intr = t3b_intr; } /* Create a private taskqueue thread for handling driver events */ sc->tq = taskqueue_create("cxgb_taskq", M_NOWAIT, taskqueue_thread_enqueue, &sc->tq); if (sc->tq == NULL) { device_printf(dev, "failed to allocate controller task queue\n"); goto out; } taskqueue_start_threads(&sc->tq, 1, PI_NET, "%s taskq", device_get_nameunit(dev)); TASK_INIT(&sc->tick_task, 0, cxgb_tick_handler, sc); /* Create a periodic callout for checking adapter status */ callout_init(&sc->cxgb_tick_ch, 1); if (t3_check_fw_version(sc) < 0 || force_fw_update) { /* * Warn user that a firmware update will be attempted in init. */ device_printf(dev, "firmware needs to be updated to version %d.%d.%d\n", FW_VERSION_MAJOR, FW_VERSION_MINOR, FW_VERSION_MICRO); sc->flags &= ~FW_UPTODATE; } else { sc->flags |= FW_UPTODATE; } if (t3_check_tpsram_version(sc) < 0) { /* * Warn user that a firmware update will be attempted in init. */ device_printf(dev, "SRAM needs to be updated to version %c-%d.%d.%d\n", t3rev2char(sc), TP_VERSION_MAJOR, TP_VERSION_MINOR, TP_VERSION_MICRO); sc->flags &= ~TPS_UPTODATE; } else { sc->flags |= TPS_UPTODATE; } /* * Create a child device for each MAC. The ethernet attachment * will be done in these children. */ for (i = 0; i < (sc)->params.nports; i++) { struct port_info *pi; - if ((child = device_add_child(dev, "cxgb", -1)) == NULL) { + if ((child = device_add_child(dev, "cxgb", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "failed to add child port\n"); error = EINVAL; goto out; } pi = &sc->port[i]; pi->adapter = sc; pi->nqsets = port_qsets; pi->first_qset = i*port_qsets; pi->port_id = i; pi->tx_chan = i >= ai->nports0; pi->txpkt_intf = pi->tx_chan ? 2 * (i - ai->nports0) + 1 : 2 * i; sc->rxpkt_map[pi->txpkt_intf] = i; sc->port[i].tx_chan = i >= ai->nports0; sc->portdev[i] = child; device_set_softc(child, pi); } bus_attach_children(dev); /* initialize sge private state */ t3_sge_init_adapter(sc); t3_led_ready(sc); error = t3_get_fw_version(sc, &vers); if (error) goto out; snprintf(&sc->fw_version[0], sizeof(sc->fw_version), "%d.%d.%d", G_FW_VERSION_MAJOR(vers), G_FW_VERSION_MINOR(vers), G_FW_VERSION_MICRO(vers)); device_set_descf(dev, "%s %sNIC\t E/C: %s S/N: %s", ai->desc, is_offload(sc) ? "R" : "", sc->params.vpd.ec, sc->params.vpd.sn); snprintf(&sc->port_types[0], sizeof(sc->port_types), "%x%x%x%x", sc->params.vpd.port_type[0], sc->params.vpd.port_type[1], sc->params.vpd.port_type[2], sc->params.vpd.port_type[3]); device_printf(sc->dev, "Firmware Version %s\n", &sc->fw_version[0]); callout_reset(&sc->cxgb_tick_ch, hz, cxgb_tick, sc); t3_add_attach_sysctls(sc); #ifdef TCP_OFFLOAD for (i = 0; i < NUM_CPL_HANDLERS; i++) sc->cpl_handler[i] = cpl_not_handled; #endif t3_intr_clear(sc); error = cxgb_setup_interrupts(sc); out: if (error) cxgb_free(sc); return (error); } /* * The cxgb_controller_detach routine is called with the device is * unloaded from the system. */ static int cxgb_controller_detach(device_t dev) { struct adapter *sc; sc = device_get_softc(dev); cxgb_free(sc); return (0); } /* * The cxgb_free() is called by the cxgb_controller_detach() routine * to tear down the structures that were built up in * cxgb_controller_attach(), and should be the final piece of work * done when fully unloading the driver. * * * 1. Shutting down the threads started by the cxgb_controller_attach() * routine. * 2. Stopping the lower level device and all callouts (cxgb_down_locked()). * 3. Detaching all of the port devices created during the * cxgb_controller_attach() routine. * 4. Removing the device children created via cxgb_controller_attach(). * 5. Releasing PCI resources associated with the device. * 6. Turning off the offload support, iff it was turned on. * 7. Destroying the mutexes created in cxgb_controller_attach(). * */ static void cxgb_free(struct adapter *sc) { int i, nqsets = 0; ADAPTER_LOCK(sc); sc->flags |= CXGB_SHUTDOWN; ADAPTER_UNLOCK(sc); /* * Make sure all child devices are gone. */ bus_detach_children(sc->dev); for (i = 0; i < (sc)->params.nports; i++) { if (sc->portdev[i] && device_delete_child(sc->dev, sc->portdev[i]) != 0) device_printf(sc->dev, "failed to delete child port\n"); nqsets += sc->port[i].nqsets; } /* * At this point, it is as if cxgb_port_detach has run on all ports, and * cxgb_down has run on the adapter. All interrupts have been silenced, * all open devices have been closed. */ KASSERT(sc->open_device_map == 0, ("%s: device(s) still open (%x)", __func__, sc->open_device_map)); for (i = 0; i < sc->params.nports; i++) { KASSERT(sc->port[i].ifp == NULL, ("%s: port %i undead!", __func__, i)); } /* * Finish off the adapter's callouts. */ callout_drain(&sc->cxgb_tick_ch); callout_drain(&sc->sge_timer_ch); /* * Release resources grabbed under FULL_INIT_DONE by cxgb_up. The * sysctls are cleaned up by the kernel linker. */ if (sc->flags & FULL_INIT_DONE) { t3_free_sge_resources(sc, nqsets); sc->flags &= ~FULL_INIT_DONE; } /* * Release all interrupt resources. */ cxgb_teardown_interrupts(sc); if (sc->flags & (USING_MSI | USING_MSIX)) { device_printf(sc->dev, "releasing msi message(s)\n"); pci_release_msi(sc->dev); } else { device_printf(sc->dev, "no msi message to release\n"); } if (sc->msix_regs_res != NULL) { bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->msix_regs_rid, sc->msix_regs_res); } /* * Free the adapter's taskqueue. */ if (sc->tq != NULL) { taskqueue_free(sc->tq); sc->tq = NULL; } free(sc->filters, M_DEVBUF); t3_sge_free(sc); if (sc->udbs_res != NULL) bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->udbs_rid, sc->udbs_res); if (sc->regs_res != NULL) bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->regs_rid, sc->regs_res); MTX_DESTROY(&sc->mdio_lock); MTX_DESTROY(&sc->sge.reg_lock); MTX_DESTROY(&sc->elmer_lock); mtx_lock(&t3_list_lock); SLIST_REMOVE(&t3_list, sc, adapter, link); mtx_unlock(&t3_list_lock); ADAPTER_LOCK_DEINIT(sc); } /** * setup_sge_qsets - configure SGE Tx/Rx/response queues * @sc: the controller softc * * Determines how many sets of SGE queues to use and initializes them. * We support multiple queue sets per port if we have MSI-X, otherwise * just one queue set per port. */ static int setup_sge_qsets(adapter_t *sc) { int i, j, err, irq_idx = 0, qset_idx = 0; u_int ntxq = SGE_TXQ_PER_SET; if ((err = t3_sge_alloc(sc)) != 0) { device_printf(sc->dev, "t3_sge_alloc returned %d\n", err); return (err); } if (sc->params.rev > 0 && !(sc->flags & USING_MSI)) irq_idx = -1; for (i = 0; i < (sc)->params.nports; i++) { struct port_info *pi = &sc->port[i]; for (j = 0; j < pi->nqsets; j++, qset_idx++) { err = t3_sge_alloc_qset(sc, qset_idx, (sc)->params.nports, (sc->flags & USING_MSIX) ? qset_idx + 1 : irq_idx, &sc->params.sge.qset[qset_idx], ntxq, pi); if (err) { t3_free_sge_resources(sc, qset_idx); device_printf(sc->dev, "t3_sge_alloc_qset failed with %d\n", err); return (err); } } } sc->nqsets = qset_idx; return (0); } static void cxgb_teardown_interrupts(adapter_t *sc) { int i; for (i = 0; i < SGE_QSETS; i++) { if (sc->msix_intr_tag[i] == NULL) { /* Should have been setup fully or not at all */ KASSERT(sc->msix_irq_res[i] == NULL && sc->msix_irq_rid[i] == 0, ("%s: half-done interrupt (%d).", __func__, i)); continue; } bus_teardown_intr(sc->dev, sc->msix_irq_res[i], sc->msix_intr_tag[i]); bus_release_resource(sc->dev, SYS_RES_IRQ, sc->msix_irq_rid[i], sc->msix_irq_res[i]); sc->msix_irq_res[i] = sc->msix_intr_tag[i] = NULL; sc->msix_irq_rid[i] = 0; } if (sc->intr_tag) { KASSERT(sc->irq_res != NULL, ("%s: half-done interrupt.", __func__)); bus_teardown_intr(sc->dev, sc->irq_res, sc->intr_tag); bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); sc->irq_res = sc->intr_tag = NULL; sc->irq_rid = 0; } } static int cxgb_setup_interrupts(adapter_t *sc) { struct resource *res; void *tag; int i, rid, err, intr_flag = sc->flags & (USING_MSI | USING_MSIX); sc->irq_rid = intr_flag ? 1 : 0; sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irq_rid, RF_SHAREABLE | RF_ACTIVE); if (sc->irq_res == NULL) { device_printf(sc->dev, "Cannot allocate interrupt (%x, %u)\n", intr_flag, sc->irq_rid); err = EINVAL; sc->irq_rid = 0; } else { err = bus_setup_intr(sc->dev, sc->irq_res, INTR_MPSAFE | INTR_TYPE_NET, NULL, sc->cxgb_intr, sc, &sc->intr_tag); if (err) { device_printf(sc->dev, "Cannot set up interrupt (%x, %u, %d)\n", intr_flag, sc->irq_rid, err); bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); sc->irq_res = sc->intr_tag = NULL; sc->irq_rid = 0; } } /* That's all for INTx or MSI */ if (!(intr_flag & USING_MSIX) || err) return (err); bus_describe_intr(sc->dev, sc->irq_res, sc->intr_tag, "err"); for (i = 0; i < sc->msi_count - 1; i++) { rid = i + 2; res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (res == NULL) { device_printf(sc->dev, "Cannot allocate interrupt " "for message %d\n", rid); err = EINVAL; break; } err = bus_setup_intr(sc->dev, res, INTR_MPSAFE | INTR_TYPE_NET, NULL, t3_intr_msix, &sc->sge.qs[i], &tag); if (err) { device_printf(sc->dev, "Cannot set up interrupt " "for message %d (%d)\n", rid, err); bus_release_resource(sc->dev, SYS_RES_IRQ, rid, res); break; } sc->msix_irq_rid[i] = rid; sc->msix_irq_res[i] = res; sc->msix_intr_tag[i] = tag; bus_describe_intr(sc->dev, res, tag, "qs%d", i); } if (err) cxgb_teardown_interrupts(sc); return (err); } static int cxgb_port_probe(device_t dev) { struct port_info *p; const char *desc; p = device_get_softc(dev); desc = p->phy.desc; device_set_descf(dev, "Port %d %s", p->port_id, desc); return (0); } static int cxgb_makedev(struct port_info *pi) { pi->port_cdev = make_dev(&cxgb_cdevsw, if_getdunit(pi->ifp), UID_ROOT, GID_WHEEL, 0600, "%s", if_name(pi->ifp)); if (pi->port_cdev == NULL) return (ENOMEM); pi->port_cdev->si_drv1 = (void *)pi; return (0); } #define CXGB_CAP (IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_MTU | IFCAP_HWCSUM | \ IFCAP_VLAN_HWCSUM | IFCAP_TSO | IFCAP_JUMBO_MTU | IFCAP_LRO | \ IFCAP_VLAN_HWTSO | IFCAP_LINKSTATE | IFCAP_HWCSUM_IPV6) #define CXGB_CAP_ENABLE CXGB_CAP static int cxgb_port_attach(device_t dev) { struct port_info *p; if_t ifp; int err; struct adapter *sc; p = device_get_softc(dev); sc = p->adapter; snprintf(p->lockbuf, PORT_NAME_LEN, "cxgb port lock %d:%d", device_get_unit(device_get_parent(dev)), p->port_id); PORT_LOCK_INIT(p, p->lockbuf); callout_init(&p->link_check_ch, 1); TASK_INIT(&p->link_check_task, 0, check_link_status, p); /* Allocate an ifnet object and set it up */ ifp = p->ifp = if_alloc(IFT_ETHER); if_initname(ifp, device_get_name(dev), device_get_unit(dev)); if_setinitfn(ifp, cxgb_init); if_setsoftc(ifp, p); if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); if_setioctlfn(ifp, cxgb_ioctl); if_settransmitfn(ifp, cxgb_transmit); if_setqflushfn(ifp, cxgb_qflush); if_setgetcounterfn(ifp, cxgb_get_counter); if_setcapabilities(ifp, CXGB_CAP); #ifdef TCP_OFFLOAD if (is_offload(sc)) if_setcapabilitiesbit(ifp, IFCAP_TOE4, 0); #endif if_setcapenable(ifp, CXGB_CAP_ENABLE); if_sethwassist(ifp, CSUM_TCP | CSUM_UDP | CSUM_IP | CSUM_TSO | CSUM_UDP_IPV6 | CSUM_TCP_IPV6); if_sethwtsomax(ifp, IP_MAXPACKET); if_sethwtsomaxsegcount(ifp, 36); if_sethwtsomaxsegsize(ifp, 65536); /* * Disable TSO on 4-port - it isn't supported by the firmware. */ if (sc->params.nports > 2) { if_setcapabilitiesbit(ifp, 0, IFCAP_TSO | IFCAP_VLAN_HWTSO); if_setcapenablebit(ifp, 0, IFCAP_TSO | IFCAP_VLAN_HWTSO); if_sethwassistbits(ifp, 0, CSUM_TSO); } /* Create a list of media supported by this port */ ifmedia_init(&p->media, IFM_IMASK, cxgb_media_change, cxgb_media_status); cxgb_build_medialist(p); ether_ifattach(ifp, p->hw_addr); /* Attach driver debugnet methods. */ DEBUGNET_SET(ifp, cxgb); #ifdef DEFAULT_JUMBO if (sc->params.nports <= 2) if_setmtu(ifp, ETHERMTU_JUMBO); #endif if ((err = cxgb_makedev(p)) != 0) { printf("makedev failed %d\n", err); return (err); } t3_sge_init_port(p); return (err); } /* * cxgb_port_detach() is called via the device_detach methods when * cxgb_free() calls the bus_detach_children. It is responsible for * removing the device from the view of the kernel, i.e. from all * interfaces lists etc. This routine is only called when the driver is * being unloaded, not when the link goes down. */ static int cxgb_port_detach(device_t dev) { struct port_info *p; struct adapter *sc; int i; p = device_get_softc(dev); sc = p->adapter; /* Tell cxgb_ioctl and if_init that the port is going away */ ADAPTER_LOCK(sc); SET_DOOMED(p); wakeup(&sc->flags); while (IS_BUSY(sc)) mtx_sleep(&sc->flags, &sc->lock, 0, "cxgbdtch", 0); SET_BUSY(sc); ADAPTER_UNLOCK(sc); if (p->port_cdev != NULL) destroy_dev(p->port_cdev); cxgb_uninit_synchronized(p); ether_ifdetach(p->ifp); for (i = p->first_qset; i < p->first_qset + p->nqsets; i++) { struct sge_qset *qs = &sc->sge.qs[i]; struct sge_txq *txq = &qs->txq[TXQ_ETH]; callout_drain(&txq->txq_watchdog); callout_drain(&txq->txq_timer); } PORT_LOCK_DEINIT(p); if_free(p->ifp); p->ifp = NULL; ADAPTER_LOCK(sc); CLR_BUSY(sc); wakeup_one(&sc->flags); ADAPTER_UNLOCK(sc); return (0); } void t3_fatal_err(struct adapter *sc) { u_int fw_status[4]; if (sc->flags & FULL_INIT_DONE) { t3_sge_stop(sc); t3_write_reg(sc, A_XGM_TX_CTRL, 0); t3_write_reg(sc, A_XGM_RX_CTRL, 0); t3_write_reg(sc, XGM_REG(A_XGM_TX_CTRL, 1), 0); t3_write_reg(sc, XGM_REG(A_XGM_RX_CTRL, 1), 0); t3_intr_disable(sc); } device_printf(sc->dev,"encountered fatal error, operation suspended\n"); if (!t3_cim_ctl_blk_read(sc, 0xa0, 4, fw_status)) device_printf(sc->dev, "FW_ status: 0x%x, 0x%x, 0x%x, 0x%x\n", fw_status[0], fw_status[1], fw_status[2], fw_status[3]); } int t3_os_find_pci_capability(adapter_t *sc, int cap) { int rc, reg = 0; rc = pci_find_cap(sc->dev, cap, ®); return (rc == 0 ? reg : 0); } int t3_os_pci_save_state(struct adapter *sc) { pci_save_state(sc->dev); return (0); } int t3_os_pci_restore_state(struct adapter *sc) { pci_restore_state(sc->dev); return (0); } /** * t3_os_link_changed - handle link status changes * @sc: the adapter associated with the link change * @port_id: the port index whose link status has changed * @link_status: the new status of the link * @speed: the new speed setting * @duplex: the new duplex setting * @fc: the new flow-control setting * * This is the OS-dependent handler for link status changes. The OS * neutral handler takes care of most of the processing for these events, * then calls this handler for any OS-specific processing. */ void t3_os_link_changed(adapter_t *adapter, int port_id, int link_status, int speed, int duplex, int fc, int mac_was_reset) { struct port_info *pi = &adapter->port[port_id]; if_t ifp = pi->ifp; /* no race with detach, so ifp should always be good */ KASSERT(ifp, ("%s: if detached.", __func__)); /* Reapply mac settings if they were lost due to a reset */ if (mac_was_reset) { PORT_LOCK(pi); cxgb_update_mac_settings(pi); PORT_UNLOCK(pi); } if (link_status) { if_setbaudrate(ifp, IF_Mbps(speed)); if_link_state_change(ifp, LINK_STATE_UP); } else if_link_state_change(ifp, LINK_STATE_DOWN); } /** * t3_os_phymod_changed - handle PHY module changes * @phy: the PHY reporting the module change * @mod_type: new module type * * This is the OS-dependent handler for PHY module changes. It is * invoked when a PHY module is removed or inserted for any OS-specific * processing. */ void t3_os_phymod_changed(struct adapter *adap, int port_id) { static const char *mod_str[] = { NULL, "SR", "LR", "LRM", "TWINAX", "TWINAX-L", "unknown" }; struct port_info *pi = &adap->port[port_id]; int mod = pi->phy.modtype; if (mod != pi->media.ifm_cur->ifm_data) cxgb_build_medialist(pi); if (mod == phy_modtype_none) if_printf(pi->ifp, "PHY module unplugged\n"); else { KASSERT(mod < ARRAY_SIZE(mod_str), ("invalid PHY module type %d", mod)); if_printf(pi->ifp, "%s PHY module inserted\n", mod_str[mod]); } } void t3_os_set_hw_addr(adapter_t *adapter, int port_idx, u8 hw_addr[]) { /* * The ifnet might not be allocated before this gets called, * as this is called early on in attach by t3_prep_adapter * save the address off in the port structure */ if (cxgb_debug) printf("set_hw_addr on idx %d addr %6D\n", port_idx, hw_addr, ":"); bcopy(hw_addr, adapter->port[port_idx].hw_addr, ETHER_ADDR_LEN); } /* * Programs the XGMAC based on the settings in the ifnet. These settings * include MTU, MAC address, mcast addresses, etc. */ static void cxgb_update_mac_settings(struct port_info *p) { if_t ifp = p->ifp; struct t3_rx_mode rm; struct cmac *mac = &p->mac; int mtu, hwtagging; PORT_LOCK_ASSERT_OWNED(p); bcopy(if_getlladdr(ifp), p->hw_addr, ETHER_ADDR_LEN); mtu = if_getmtu(ifp); if (if_getcapenable(ifp) & IFCAP_VLAN_MTU) mtu += ETHER_VLAN_ENCAP_LEN; hwtagging = (if_getcapenable(ifp) & IFCAP_VLAN_HWTAGGING) != 0; t3_mac_set_mtu(mac, mtu); t3_set_vlan_accel(p->adapter, 1 << p->tx_chan, hwtagging); t3_mac_set_address(mac, 0, p->hw_addr); t3_init_rx_mode(&rm, p); t3_mac_set_rx_mode(mac, &rm); } static int await_mgmt_replies(struct adapter *adap, unsigned long init_cnt, unsigned long n) { int attempts = 5; while (adap->sge.qs[0].rspq.offload_pkts < init_cnt + n) { if (!--attempts) return (ETIMEDOUT); t3_os_sleep(10); } return 0; } static int init_tp_parity(struct adapter *adap) { int i; struct mbuf *m; struct cpl_set_tcb_field *greq; unsigned long cnt = adap->sge.qs[0].rspq.offload_pkts; t3_tp_set_offload_mode(adap, 1); for (i = 0; i < 16; i++) { struct cpl_smt_write_req *req; m = m_gethdr(M_WAITOK, MT_DATA); req = mtod(m, struct cpl_smt_write_req *); m->m_len = m->m_pkthdr.len = sizeof(*req); memset(req, 0, sizeof(*req)); req->wr.wrh_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD)); OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SMT_WRITE_REQ, i)); req->iff = i; t3_mgmt_tx(adap, m); } for (i = 0; i < 2048; i++) { struct cpl_l2t_write_req *req; m = m_gethdr(M_WAITOK, MT_DATA); req = mtod(m, struct cpl_l2t_write_req *); m->m_len = m->m_pkthdr.len = sizeof(*req); memset(req, 0, sizeof(*req)); req->wr.wrh_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD)); OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_L2T_WRITE_REQ, i)); req->params = htonl(V_L2T_W_IDX(i)); t3_mgmt_tx(adap, m); } for (i = 0; i < 2048; i++) { struct cpl_rte_write_req *req; m = m_gethdr(M_WAITOK, MT_DATA); req = mtod(m, struct cpl_rte_write_req *); m->m_len = m->m_pkthdr.len = sizeof(*req); memset(req, 0, sizeof(*req)); req->wr.wrh_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD)); OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_RTE_WRITE_REQ, i)); req->l2t_idx = htonl(V_L2T_W_IDX(i)); t3_mgmt_tx(adap, m); } m = m_gethdr(M_WAITOK, MT_DATA); greq = mtod(m, struct cpl_set_tcb_field *); m->m_len = m->m_pkthdr.len = sizeof(*greq); memset(greq, 0, sizeof(*greq)); greq->wr.wrh_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD)); OPCODE_TID(greq) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, 0)); greq->mask = htobe64(1); t3_mgmt_tx(adap, m); i = await_mgmt_replies(adap, cnt, 16 + 2048 + 2048 + 1); t3_tp_set_offload_mode(adap, 0); return (i); } /** * setup_rss - configure Receive Side Steering (per-queue connection demux) * @adap: the adapter * * Sets up RSS to distribute packets to multiple receive queues. We * configure the RSS CPU lookup table to distribute to the number of HW * receive queues, and the response queue lookup table to narrow that * down to the response queues actually configured for each port. * We always configure the RSS mapping for two ports since the mapping * table has plenty of entries. */ static void setup_rss(adapter_t *adap) { int i; u_int nq[2]; uint8_t cpus[SGE_QSETS + 1]; uint16_t rspq_map[RSS_TABLE_SIZE]; for (i = 0; i < SGE_QSETS; ++i) cpus[i] = i; cpus[SGE_QSETS] = 0xff; nq[0] = nq[1] = 0; for_each_port(adap, i) { const struct port_info *pi = adap2pinfo(adap, i); nq[pi->tx_chan] += pi->nqsets; } for (i = 0; i < RSS_TABLE_SIZE / 2; ++i) { rspq_map[i] = nq[0] ? i % nq[0] : 0; rspq_map[i + RSS_TABLE_SIZE / 2] = nq[1] ? i % nq[1] + nq[0] : 0; } /* Calculate the reverse RSS map table */ for (i = 0; i < SGE_QSETS; ++i) adap->rrss_map[i] = 0xff; for (i = 0; i < RSS_TABLE_SIZE; ++i) if (adap->rrss_map[rspq_map[i]] == 0xff) adap->rrss_map[rspq_map[i]] = i; t3_config_rss(adap, F_RQFEEDBACKENABLE | F_TNLLKPEN | F_TNLMAPEN | F_TNLPRTEN | F_TNL2TUPEN | F_TNL4TUPEN | F_OFDMAPEN | F_RRCPLMAPEN | V_RRCPLCPUSIZE(6) | F_HASHTOEPLITZ, cpus, rspq_map); } static void send_pktsched_cmd(struct adapter *adap, int sched, int qidx, int lo, int hi, int port) { struct mbuf *m; struct mngt_pktsched_wr *req; m = m_gethdr(M_NOWAIT, MT_DATA); if (m) { req = mtod(m, struct mngt_pktsched_wr *); req->wr.wrh_hi = htonl(V_WR_OP(FW_WROPCODE_MNGT)); req->mngt_opcode = FW_MNGTOPCODE_PKTSCHED_SET; req->sched = sched; req->idx = qidx; req->min = lo; req->max = hi; req->binding = port; m->m_len = m->m_pkthdr.len = sizeof(*req); t3_mgmt_tx(adap, m); } } static void bind_qsets(adapter_t *sc) { int i, j; for (i = 0; i < (sc)->params.nports; ++i) { const struct port_info *pi = adap2pinfo(sc, i); for (j = 0; j < pi->nqsets; ++j) { send_pktsched_cmd(sc, 1, pi->first_qset + j, -1, -1, pi->tx_chan); } } } static void update_tpeeprom(struct adapter *adap) { const struct firmware *tpeeprom; uint32_t version; unsigned int major, minor; int ret, len; char rev, name[32]; t3_seeprom_read(adap, TP_SRAM_OFFSET, &version); major = G_TP_VERSION_MAJOR(version); minor = G_TP_VERSION_MINOR(version); if (major == TP_VERSION_MAJOR && minor == TP_VERSION_MINOR) return; rev = t3rev2char(adap); snprintf(name, sizeof(name), TPEEPROM_NAME, rev); tpeeprom = firmware_get(name); if (tpeeprom == NULL) { device_printf(adap->dev, "could not load TP EEPROM: unable to load %s\n", name); return; } len = tpeeprom->datasize - 4; ret = t3_check_tpsram(adap, tpeeprom->data, tpeeprom->datasize); if (ret) goto release_tpeeprom; if (len != TP_SRAM_LEN) { device_printf(adap->dev, "%s length is wrong len=%d expected=%d\n", name, len, TP_SRAM_LEN); return; } ret = set_eeprom(&adap->port[0], tpeeprom->data, tpeeprom->datasize, TP_SRAM_OFFSET); if (!ret) { device_printf(adap->dev, "Protocol SRAM image updated in EEPROM to %d.%d.%d\n", TP_VERSION_MAJOR, TP_VERSION_MINOR, TP_VERSION_MICRO); } else device_printf(adap->dev, "Protocol SRAM image update in EEPROM failed\n"); release_tpeeprom: firmware_put(tpeeprom, FIRMWARE_UNLOAD); return; } static int update_tpsram(struct adapter *adap) { const struct firmware *tpsram; int ret; char rev, name[32]; rev = t3rev2char(adap); snprintf(name, sizeof(name), TPSRAM_NAME, rev); update_tpeeprom(adap); tpsram = firmware_get(name); if (tpsram == NULL){ device_printf(adap->dev, "could not load TP SRAM\n"); return (EINVAL); } else device_printf(adap->dev, "updating TP SRAM\n"); ret = t3_check_tpsram(adap, tpsram->data, tpsram->datasize); if (ret) goto release_tpsram; ret = t3_set_proto_sram(adap, tpsram->data); if (ret) device_printf(adap->dev, "loading protocol SRAM failed\n"); release_tpsram: firmware_put(tpsram, FIRMWARE_UNLOAD); return ret; } /** * cxgb_up - enable the adapter * @adap: adapter being enabled * * Called when the first port is enabled, this function performs the * actions necessary to make an adapter operational, such as completing * the initialization of HW modules, and enabling interrupts. */ static int cxgb_up(struct adapter *sc) { int err = 0; unsigned int mxf = t3_mc5_size(&sc->mc5) - MC5_MIN_TIDS; KASSERT(sc->open_device_map == 0, ("%s: device(s) already open (%x)", __func__, sc->open_device_map)); if ((sc->flags & FULL_INIT_DONE) == 0) { ADAPTER_LOCK_ASSERT_NOTOWNED(sc); if ((sc->flags & FW_UPTODATE) == 0) if ((err = upgrade_fw(sc))) goto out; if ((sc->flags & TPS_UPTODATE) == 0) if ((err = update_tpsram(sc))) goto out; if (is_offload(sc) && nfilters != 0) { sc->params.mc5.nservers = 0; if (nfilters < 0) sc->params.mc5.nfilters = mxf; else sc->params.mc5.nfilters = min(nfilters, mxf); } err = t3_init_hw(sc, 0); if (err) goto out; t3_set_reg_field(sc, A_TP_PARA_REG5, 0, F_RXDDPOFFINIT); t3_write_reg(sc, A_ULPRX_TDDP_PSZ, V_HPZ0(PAGE_SHIFT - 12)); err = setup_sge_qsets(sc); if (err) goto out; alloc_filters(sc); setup_rss(sc); t3_add_configured_sysctls(sc); sc->flags |= FULL_INIT_DONE; } t3_intr_clear(sc); t3_sge_start(sc); t3_intr_enable(sc); if (sc->params.rev >= T3_REV_C && !(sc->flags & TP_PARITY_INIT) && is_offload(sc) && init_tp_parity(sc) == 0) sc->flags |= TP_PARITY_INIT; if (sc->flags & TP_PARITY_INIT) { t3_write_reg(sc, A_TP_INT_CAUSE, F_CMCACHEPERR | F_ARPLUTPERR); t3_write_reg(sc, A_TP_INT_ENABLE, 0x7fbfffff); } if (!(sc->flags & QUEUES_BOUND)) { bind_qsets(sc); setup_hw_filters(sc); sc->flags |= QUEUES_BOUND; } t3_sge_reset_adapter(sc); out: return (err); } /* * Called when the last open device is closed. Does NOT undo all of cxgb_up's * work. Specifically, the resources grabbed under FULL_INIT_DONE are released * during controller_detach, not here. */ static void cxgb_down(struct adapter *sc) { t3_sge_stop(sc); t3_intr_disable(sc); } /* * if_init for cxgb ports. */ static void cxgb_init(void *arg) { struct port_info *p = arg; struct adapter *sc = p->adapter; ADAPTER_LOCK(sc); cxgb_init_locked(p); /* releases adapter lock */ ADAPTER_LOCK_ASSERT_NOTOWNED(sc); } static int cxgb_init_locked(struct port_info *p) { struct adapter *sc = p->adapter; if_t ifp = p->ifp; struct cmac *mac = &p->mac; int i, rc = 0, may_sleep = 0, gave_up_lock = 0; ADAPTER_LOCK_ASSERT_OWNED(sc); while (!IS_DOOMED(p) && IS_BUSY(sc)) { gave_up_lock = 1; if (mtx_sleep(&sc->flags, &sc->lock, PCATCH, "cxgbinit", 0)) { rc = EINTR; goto done; } } if (IS_DOOMED(p)) { rc = ENXIO; goto done; } KASSERT(!IS_BUSY(sc), ("%s: controller busy.", __func__)); /* * The code that runs during one-time adapter initialization can sleep * so it's important not to hold any locks across it. */ may_sleep = sc->flags & FULL_INIT_DONE ? 0 : 1; if (may_sleep) { SET_BUSY(sc); gave_up_lock = 1; ADAPTER_UNLOCK(sc); } if (sc->open_device_map == 0 && ((rc = cxgb_up(sc)) != 0)) goto done; PORT_LOCK(p); if (isset(&sc->open_device_map, p->port_id) && (if_getdrvflags(ifp) & IFF_DRV_RUNNING)) { PORT_UNLOCK(p); goto done; } t3_port_intr_enable(sc, p->port_id); if (!mac->multiport) t3_mac_init(mac); cxgb_update_mac_settings(p); t3_link_start(&p->phy, mac, &p->link_config); t3_mac_enable(mac, MAC_DIRECTION_RX | MAC_DIRECTION_TX); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE); PORT_UNLOCK(p); for (i = p->first_qset; i < p->first_qset + p->nqsets; i++) { struct sge_qset *qs = &sc->sge.qs[i]; struct sge_txq *txq = &qs->txq[TXQ_ETH]; callout_reset_on(&txq->txq_watchdog, hz, cxgb_tx_watchdog, qs, txq->txq_watchdog.c_cpu); } /* all ok */ setbit(&sc->open_device_map, p->port_id); callout_reset(&p->link_check_ch, p->phy.caps & SUPPORTED_LINK_IRQ ? hz * 3 : hz / 4, link_check_callout, p); done: if (may_sleep) { ADAPTER_LOCK(sc); KASSERT(IS_BUSY(sc), ("%s: controller not busy.", __func__)); CLR_BUSY(sc); } if (gave_up_lock) wakeup_one(&sc->flags); ADAPTER_UNLOCK(sc); return (rc); } static int cxgb_uninit_locked(struct port_info *p) { struct adapter *sc = p->adapter; int rc; ADAPTER_LOCK_ASSERT_OWNED(sc); while (!IS_DOOMED(p) && IS_BUSY(sc)) { if (mtx_sleep(&sc->flags, &sc->lock, PCATCH, "cxgbunin", 0)) { rc = EINTR; goto done; } } if (IS_DOOMED(p)) { rc = ENXIO; goto done; } KASSERT(!IS_BUSY(sc), ("%s: controller busy.", __func__)); SET_BUSY(sc); ADAPTER_UNLOCK(sc); rc = cxgb_uninit_synchronized(p); ADAPTER_LOCK(sc); KASSERT(IS_BUSY(sc), ("%s: controller not busy.", __func__)); CLR_BUSY(sc); wakeup_one(&sc->flags); done: ADAPTER_UNLOCK(sc); return (rc); } /* * Called on "ifconfig down", and from port_detach */ static int cxgb_uninit_synchronized(struct port_info *pi) { struct adapter *sc = pi->adapter; if_t ifp = pi->ifp; /* * taskqueue_drain may cause a deadlock if the adapter lock is held. */ ADAPTER_LOCK_ASSERT_NOTOWNED(sc); /* * Clear this port's bit from the open device map, and then drain all * the tasks that can access/manipulate this port's port_info or ifp. * We disable this port's interrupts here and so the slow/ext * interrupt tasks won't be enqueued. The tick task will continue to * be enqueued every second but the runs after this drain will not see * this port in the open device map. * * A well behaved task must take open_device_map into account and ignore * ports that are not open. */ clrbit(&sc->open_device_map, pi->port_id); t3_port_intr_disable(sc, pi->port_id); taskqueue_drain(sc->tq, &sc->slow_intr_task); taskqueue_drain(sc->tq, &sc->tick_task); callout_drain(&pi->link_check_ch); taskqueue_drain(sc->tq, &pi->link_check_task); PORT_LOCK(pi); if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING | IFF_DRV_OACTIVE); /* disable pause frames */ t3_set_reg_field(sc, A_XGM_TX_CFG + pi->mac.offset, F_TXPAUSEEN, 0); /* Reset RX FIFO HWM */ t3_set_reg_field(sc, A_XGM_RXFIFO_CFG + pi->mac.offset, V_RXFIFOPAUSEHWM(M_RXFIFOPAUSEHWM), 0); DELAY(100 * 1000); /* Wait for TXFIFO empty */ t3_wait_op_done(sc, A_XGM_TXFIFO_CFG + pi->mac.offset, F_TXFIFO_EMPTY, 1, 20, 5); DELAY(100 * 1000); t3_mac_disable(&pi->mac, MAC_DIRECTION_RX); pi->phy.ops->power_down(&pi->phy, 1); PORT_UNLOCK(pi); pi->link_config.link_ok = 0; t3_os_link_changed(sc, pi->port_id, 0, 0, 0, 0, 0); if (sc->open_device_map == 0) cxgb_down(pi->adapter); return (0); } /* * Mark lro enabled or disabled in all qsets for this port */ static int cxgb_set_lro(struct port_info *p, int enabled) { int i; struct adapter *adp = p->adapter; struct sge_qset *q; for (i = 0; i < p->nqsets; i++) { q = &adp->sge.qs[p->first_qset + i]; q->lro.enabled = (enabled != 0); } return (0); } static int cxgb_ioctl(if_t ifp, unsigned long command, caddr_t data) { struct port_info *p = if_getsoftc(ifp); struct adapter *sc = p->adapter; struct ifreq *ifr = (struct ifreq *)data; int flags, error = 0, mtu; uint32_t mask; switch (command) { case SIOCSIFMTU: ADAPTER_LOCK(sc); error = IS_DOOMED(p) ? ENXIO : (IS_BUSY(sc) ? EBUSY : 0); if (error) { fail: ADAPTER_UNLOCK(sc); return (error); } mtu = ifr->ifr_mtu; if ((mtu < ETHERMIN) || (mtu > ETHERMTU_JUMBO)) { error = EINVAL; } else { if_setmtu(ifp, mtu); PORT_LOCK(p); cxgb_update_mac_settings(p); PORT_UNLOCK(p); } ADAPTER_UNLOCK(sc); break; case SIOCSIFFLAGS: ADAPTER_LOCK(sc); if (IS_DOOMED(p)) { error = ENXIO; goto fail; } if (if_getflags(ifp) & IFF_UP) { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { flags = p->if_flags; if (((if_getflags(ifp) ^ flags) & IFF_PROMISC) || ((if_getflags(ifp) ^ flags) & IFF_ALLMULTI)) { if (IS_BUSY(sc)) { error = EBUSY; goto fail; } PORT_LOCK(p); cxgb_update_mac_settings(p); PORT_UNLOCK(p); } ADAPTER_UNLOCK(sc); } else error = cxgb_init_locked(p); p->if_flags = if_getflags(ifp); } else if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) error = cxgb_uninit_locked(p); else ADAPTER_UNLOCK(sc); ADAPTER_LOCK_ASSERT_NOTOWNED(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: ADAPTER_LOCK(sc); error = IS_DOOMED(p) ? ENXIO : (IS_BUSY(sc) ? EBUSY : 0); if (error) goto fail; if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { PORT_LOCK(p); cxgb_update_mac_settings(p); PORT_UNLOCK(p); } ADAPTER_UNLOCK(sc); break; case SIOCSIFCAP: ADAPTER_LOCK(sc); error = IS_DOOMED(p) ? ENXIO : (IS_BUSY(sc) ? EBUSY : 0); if (error) goto fail; mask = ifr->ifr_reqcap ^ if_getcapenable(ifp); if (mask & IFCAP_TXCSUM) { if_togglecapenable(ifp, IFCAP_TXCSUM); if_togglehwassist(ifp, CSUM_TCP | CSUM_UDP | CSUM_IP); if (IFCAP_TSO4 & if_getcapenable(ifp) && !(IFCAP_TXCSUM & if_getcapenable(ifp))) { mask &= ~IFCAP_TSO4; if_setcapenablebit(ifp, 0, IFCAP_TSO4); if_printf(ifp, "tso4 disabled due to -txcsum.\n"); } } if (mask & IFCAP_TXCSUM_IPV6) { if_togglecapenable(ifp, IFCAP_TXCSUM_IPV6); if_togglehwassist(ifp, CSUM_UDP_IPV6 | CSUM_TCP_IPV6); if (IFCAP_TSO6 & if_getcapenable(ifp) && !(IFCAP_TXCSUM_IPV6 & if_getcapenable(ifp))) { mask &= ~IFCAP_TSO6; if_setcapenablebit(ifp, 0, IFCAP_TSO6); if_printf(ifp, "tso6 disabled due to -txcsum6.\n"); } } if (mask & IFCAP_RXCSUM) if_togglecapenable(ifp, IFCAP_RXCSUM); if (mask & IFCAP_RXCSUM_IPV6) if_togglecapenable(ifp, IFCAP_RXCSUM_IPV6); /* * Note that we leave CSUM_TSO alone (it is always set). The * kernel takes both IFCAP_TSOx and CSUM_TSO into account before * sending a TSO request our way, so it's sufficient to toggle * IFCAP_TSOx only. */ if (mask & IFCAP_TSO4) { if (!(IFCAP_TSO4 & if_getcapenable(ifp)) && !(IFCAP_TXCSUM & if_getcapenable(ifp))) { if_printf(ifp, "enable txcsum first.\n"); error = EAGAIN; goto fail; } if_togglecapenable(ifp, IFCAP_TSO4); } if (mask & IFCAP_TSO6) { if (!(IFCAP_TSO6 & if_getcapenable(ifp)) && !(IFCAP_TXCSUM_IPV6 & if_getcapenable(ifp))) { if_printf(ifp, "enable txcsum6 first.\n"); error = EAGAIN; goto fail; } if_togglecapenable(ifp, IFCAP_TSO6); } if (mask & IFCAP_LRO) { if_togglecapenable(ifp, IFCAP_LRO); /* Safe to do this even if cxgb_up not called yet */ cxgb_set_lro(p, if_getcapenable(ifp) & IFCAP_LRO); } #ifdef TCP_OFFLOAD if (mask & IFCAP_TOE4) { int enable = (if_getcapenable(ifp) ^ mask) & IFCAP_TOE4; error = toe_capability(p, enable); if (error == 0) if_togglecapenable(ifp, mask); } #endif if (mask & IFCAP_VLAN_HWTAGGING) { if_togglecapenable(ifp, IFCAP_VLAN_HWTAGGING); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { PORT_LOCK(p); cxgb_update_mac_settings(p); PORT_UNLOCK(p); } } if (mask & IFCAP_VLAN_MTU) { if_togglecapenable(ifp, IFCAP_VLAN_MTU); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { PORT_LOCK(p); cxgb_update_mac_settings(p); PORT_UNLOCK(p); } } if (mask & IFCAP_VLAN_HWTSO) if_togglecapenable(ifp, IFCAP_VLAN_HWTSO); if (mask & IFCAP_VLAN_HWCSUM) if_togglecapenable(ifp, IFCAP_VLAN_HWCSUM); #ifdef VLAN_CAPABILITIES VLAN_CAPABILITIES(ifp); #endif ADAPTER_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &p->media, command); break; default: error = ether_ioctl(ifp, command, data); } return (error); } static int cxgb_media_change(if_t ifp) { return (EOPNOTSUPP); } /* * Translates phy->modtype to the correct Ethernet media subtype. */ static int cxgb_ifm_type(int mod) { switch (mod) { case phy_modtype_sr: return (IFM_10G_SR); case phy_modtype_lr: return (IFM_10G_LR); case phy_modtype_lrm: return (IFM_10G_LRM); case phy_modtype_twinax: return (IFM_10G_TWINAX); case phy_modtype_twinax_long: return (IFM_10G_TWINAX_LONG); case phy_modtype_none: return (IFM_NONE); case phy_modtype_unknown: return (IFM_UNKNOWN); } KASSERT(0, ("%s: modtype %d unknown", __func__, mod)); return (IFM_UNKNOWN); } /* * Rebuilds the ifmedia list for this port, and sets the current media. */ static void cxgb_build_medialist(struct port_info *p) { struct cphy *phy = &p->phy; struct ifmedia *media = &p->media; int mod = phy->modtype; int m = IFM_ETHER | IFM_FDX; PORT_LOCK(p); ifmedia_removeall(media); if (phy->caps & SUPPORTED_TP && phy->caps & SUPPORTED_Autoneg) { /* Copper (RJ45) */ if (phy->caps & SUPPORTED_10000baseT_Full) ifmedia_add(media, m | IFM_10G_T, mod, NULL); if (phy->caps & SUPPORTED_1000baseT_Full) ifmedia_add(media, m | IFM_1000_T, mod, NULL); if (phy->caps & SUPPORTED_100baseT_Full) ifmedia_add(media, m | IFM_100_TX, mod, NULL); if (phy->caps & SUPPORTED_10baseT_Full) ifmedia_add(media, m | IFM_10_T, mod, NULL); ifmedia_add(media, IFM_ETHER | IFM_AUTO, mod, NULL); ifmedia_set(media, IFM_ETHER | IFM_AUTO); } else if (phy->caps & SUPPORTED_TP) { /* Copper (CX4) */ KASSERT(phy->caps & SUPPORTED_10000baseT_Full, ("%s: unexpected cap 0x%x", __func__, phy->caps)); ifmedia_add(media, m | IFM_10G_CX4, mod, NULL); ifmedia_set(media, m | IFM_10G_CX4); } else if (phy->caps & SUPPORTED_FIBRE && phy->caps & SUPPORTED_10000baseT_Full) { /* 10G optical (but includes SFP+ twinax) */ m |= cxgb_ifm_type(mod); if (IFM_SUBTYPE(m) == IFM_NONE) m &= ~IFM_FDX; ifmedia_add(media, m, mod, NULL); ifmedia_set(media, m); } else if (phy->caps & SUPPORTED_FIBRE && phy->caps & SUPPORTED_1000baseT_Full) { /* 1G optical */ /* XXX: Lie and claim to be SX, could actually be any 1G-X */ ifmedia_add(media, m | IFM_1000_SX, mod, NULL); ifmedia_set(media, m | IFM_1000_SX); } else { KASSERT(0, ("%s: don't know how to handle 0x%x.", __func__, phy->caps)); } PORT_UNLOCK(p); } static void cxgb_media_status(if_t ifp, struct ifmediareq *ifmr) { struct port_info *p = if_getsoftc(ifp); struct ifmedia_entry *cur = p->media.ifm_cur; int speed = p->link_config.speed; if (cur->ifm_data != p->phy.modtype) { cxgb_build_medialist(p); cur = p->media.ifm_cur; } ifmr->ifm_status = IFM_AVALID; if (!p->link_config.link_ok) return; ifmr->ifm_status |= IFM_ACTIVE; /* * active and current will differ iff current media is autoselect. That * can happen only for copper RJ45. */ if (IFM_SUBTYPE(cur->ifm_media) != IFM_AUTO) return; KASSERT(p->phy.caps & SUPPORTED_TP && p->phy.caps & SUPPORTED_Autoneg, ("%s: unexpected PHY caps 0x%x", __func__, p->phy.caps)); ifmr->ifm_active = IFM_ETHER | IFM_FDX; if (speed == SPEED_10000) ifmr->ifm_active |= IFM_10G_T; else if (speed == SPEED_1000) ifmr->ifm_active |= IFM_1000_T; else if (speed == SPEED_100) ifmr->ifm_active |= IFM_100_TX; else if (speed == SPEED_10) ifmr->ifm_active |= IFM_10_T; else KASSERT(0, ("%s: link up but speed unknown (%u)", __func__, speed)); } static uint64_t cxgb_get_counter(if_t ifp, ift_counter c) { struct port_info *pi = if_getsoftc(ifp); struct adapter *sc = pi->adapter; struct cmac *mac = &pi->mac; struct mac_stats *mstats = &mac->stats; cxgb_refresh_stats(pi); switch (c) { case IFCOUNTER_IPACKETS: return (mstats->rx_frames); case IFCOUNTER_IERRORS: return (mstats->rx_jabber + mstats->rx_data_errs + mstats->rx_sequence_errs + mstats->rx_runt + mstats->rx_too_long + mstats->rx_mac_internal_errs + mstats->rx_short + mstats->rx_fcs_errs); case IFCOUNTER_OPACKETS: return (mstats->tx_frames); case IFCOUNTER_OERRORS: return (mstats->tx_excess_collisions + mstats->tx_underrun + mstats->tx_len_errs + mstats->tx_mac_internal_errs + mstats->tx_excess_deferral + mstats->tx_fcs_errs); case IFCOUNTER_COLLISIONS: return (mstats->tx_total_collisions); case IFCOUNTER_IBYTES: return (mstats->rx_octets); case IFCOUNTER_OBYTES: return (mstats->tx_octets); case IFCOUNTER_IMCASTS: return (mstats->rx_mcast_frames); case IFCOUNTER_OMCASTS: return (mstats->tx_mcast_frames); case IFCOUNTER_IQDROPS: return (mstats->rx_cong_drops); case IFCOUNTER_OQDROPS: { int i; uint64_t drops; drops = 0; if (sc->flags & FULL_INIT_DONE) { for (i = pi->first_qset; i < pi->first_qset + pi->nqsets; i++) drops += sc->sge.qs[i].txq[TXQ_ETH].txq_mr->br_drops; } return (drops); } default: return (if_get_counter_default(ifp, c)); } } static void cxgb_async_intr(void *data) { adapter_t *sc = data; t3_write_reg(sc, A_PL_INT_ENABLE0, 0); (void) t3_read_reg(sc, A_PL_INT_ENABLE0); taskqueue_enqueue(sc->tq, &sc->slow_intr_task); } static void link_check_callout(void *arg) { struct port_info *pi = arg; struct adapter *sc = pi->adapter; if (!isset(&sc->open_device_map, pi->port_id)) return; taskqueue_enqueue(sc->tq, &pi->link_check_task); } static void check_link_status(void *arg, int pending) { struct port_info *pi = arg; struct adapter *sc = pi->adapter; if (!isset(&sc->open_device_map, pi->port_id)) return; t3_link_changed(sc, pi->port_id); if (pi->link_fault || !(pi->phy.caps & SUPPORTED_LINK_IRQ) || pi->link_config.link_ok == 0) callout_reset(&pi->link_check_ch, hz, link_check_callout, pi); } void t3_os_link_intr(struct port_info *pi) { /* * Schedule a link check in the near future. If the link is flapping * rapidly we'll keep resetting the callout and delaying the check until * things stabilize a bit. */ callout_reset(&pi->link_check_ch, hz / 4, link_check_callout, pi); } static void check_t3b2_mac(struct adapter *sc) { int i; if (sc->flags & CXGB_SHUTDOWN) return; for_each_port(sc, i) { struct port_info *p = &sc->port[i]; int status; #ifdef INVARIANTS if_t ifp = p->ifp; #endif if (!isset(&sc->open_device_map, p->port_id) || p->link_fault || !p->link_config.link_ok) continue; KASSERT(if_getdrvflags(ifp) & IFF_DRV_RUNNING, ("%s: state mismatch (drv_flags %x, device_map %x)", __func__, if_getdrvflags(ifp), sc->open_device_map)); PORT_LOCK(p); status = t3b2_mac_watchdog_task(&p->mac); if (status == 1) p->mac.stats.num_toggled++; else if (status == 2) { struct cmac *mac = &p->mac; cxgb_update_mac_settings(p); t3_link_start(&p->phy, mac, &p->link_config); t3_mac_enable(mac, MAC_DIRECTION_RX | MAC_DIRECTION_TX); t3_port_intr_enable(sc, p->port_id); p->mac.stats.num_resets++; } PORT_UNLOCK(p); } } static void cxgb_tick(void *arg) { adapter_t *sc = (adapter_t *)arg; if (sc->flags & CXGB_SHUTDOWN) return; taskqueue_enqueue(sc->tq, &sc->tick_task); callout_reset(&sc->cxgb_tick_ch, hz, cxgb_tick, sc); } void cxgb_refresh_stats(struct port_info *pi) { struct timeval tv; const struct timeval interval = {0, 250000}; /* 250ms */ getmicrotime(&tv); timevalsub(&tv, &interval); if (timevalcmp(&tv, &pi->last_refreshed, <)) return; PORT_LOCK(pi); t3_mac_update_stats(&pi->mac); PORT_UNLOCK(pi); getmicrotime(&pi->last_refreshed); } static void cxgb_tick_handler(void *arg, int count) { adapter_t *sc = (adapter_t *)arg; const struct adapter_params *p = &sc->params; int i; uint32_t cause, reset; if (sc->flags & CXGB_SHUTDOWN || !(sc->flags & FULL_INIT_DONE)) return; if (p->rev == T3_REV_B2 && p->nports < 4 && sc->open_device_map) check_t3b2_mac(sc); cause = t3_read_reg(sc, A_SG_INT_CAUSE) & (F_RSPQSTARVE | F_FLEMPTY); if (cause) { struct sge_qset *qs = &sc->sge.qs[0]; uint32_t mask, v; v = t3_read_reg(sc, A_SG_RSPQ_FL_STATUS) & ~0xff00; mask = 1; for (i = 0; i < SGE_QSETS; i++) { if (v & mask) qs[i].rspq.starved++; mask <<= 1; } mask <<= SGE_QSETS; /* skip RSPQXDISABLED */ for (i = 0; i < SGE_QSETS * 2; i++) { if (v & mask) { qs[i / 2].fl[i % 2].empty++; } mask <<= 1; } /* clear */ t3_write_reg(sc, A_SG_RSPQ_FL_STATUS, v); t3_write_reg(sc, A_SG_INT_CAUSE, cause); } for (i = 0; i < sc->params.nports; i++) { struct port_info *pi = &sc->port[i]; struct cmac *mac = &pi->mac; if (!isset(&sc->open_device_map, pi->port_id)) continue; cxgb_refresh_stats(pi); if (mac->multiport) continue; /* Count rx fifo overflows, once per second */ cause = t3_read_reg(sc, A_XGM_INT_CAUSE + mac->offset); reset = 0; if (cause & F_RXFIFO_OVERFLOW) { mac->stats.rx_fifo_ovfl++; reset |= F_RXFIFO_OVERFLOW; } t3_write_reg(sc, A_XGM_INT_CAUSE + mac->offset, reset); } } static void touch_bars(device_t dev) { /* * Don't enable yet */ #if !defined(__LP64__) && 0 u32 v; pci_read_config_dword(pdev, PCI_BASE_ADDRESS_1, &v); pci_write_config_dword(pdev, PCI_BASE_ADDRESS_1, v); pci_read_config_dword(pdev, PCI_BASE_ADDRESS_3, &v); pci_write_config_dword(pdev, PCI_BASE_ADDRESS_3, v); pci_read_config_dword(pdev, PCI_BASE_ADDRESS_5, &v); pci_write_config_dword(pdev, PCI_BASE_ADDRESS_5, v); #endif } static int set_eeprom(struct port_info *pi, const uint8_t *data, int len, int offset) { uint8_t *buf; int err = 0; u32 aligned_offset, aligned_len, *p; struct adapter *adapter = pi->adapter; aligned_offset = offset & ~3; aligned_len = (len + (offset & 3) + 3) & ~3; if (aligned_offset != offset || aligned_len != len) { buf = malloc(aligned_len, M_DEVBUF, M_WAITOK | M_ZERO); err = t3_seeprom_read(adapter, aligned_offset, (u32 *)buf); if (!err && aligned_len > 4) err = t3_seeprom_read(adapter, aligned_offset + aligned_len - 4, (u32 *)&buf[aligned_len - 4]); if (err) goto out; memcpy(buf + (offset & 3), data, len); } else buf = (uint8_t *)(uintptr_t)data; err = t3_seeprom_wp(adapter, 0); if (err) goto out; for (p = (u32 *)buf; !err && aligned_len; aligned_len -= 4, p++) { err = t3_seeprom_write(adapter, aligned_offset, *p); aligned_offset += 4; } if (!err) err = t3_seeprom_wp(adapter, 1); out: if (buf != data) free(buf, M_DEVBUF); return err; } static int in_range(int val, int lo, int hi) { return val < 0 || (val <= hi && val >= lo); } static int cxgb_extension_open(struct cdev *dev, int flags, int fmp, struct thread *td) { return (0); } static int cxgb_extension_close(struct cdev *dev, int flags, int fmt, struct thread *td) { return (0); } static int cxgb_extension_ioctl(struct cdev *dev, unsigned long cmd, caddr_t data, int fflag, struct thread *td) { int mmd, error = 0; struct port_info *pi = dev->si_drv1; adapter_t *sc = pi->adapter; #ifdef PRIV_SUPPORTED if (priv_check(td, PRIV_DRIVER)) { if (cxgb_debug) printf("user does not have access to privileged ioctls\n"); return (EPERM); } #else if (suser(td)) { if (cxgb_debug) printf("user does not have access to privileged ioctls\n"); return (EPERM); } #endif switch (cmd) { case CHELSIO_GET_MIIREG: { uint32_t val; struct cphy *phy = &pi->phy; struct ch_mii_data *mid = (struct ch_mii_data *)data; if (!phy->mdio_read) return (EOPNOTSUPP); if (is_10G(sc)) { mmd = mid->phy_id >> 8; if (!mmd) mmd = MDIO_DEV_PCS; else if (mmd > MDIO_DEV_VEND2) return (EINVAL); error = phy->mdio_read(sc, mid->phy_id & 0x1f, mmd, mid->reg_num, &val); } else error = phy->mdio_read(sc, mid->phy_id & 0x1f, 0, mid->reg_num & 0x1f, &val); if (error == 0) mid->val_out = val; break; } case CHELSIO_SET_MIIREG: { struct cphy *phy = &pi->phy; struct ch_mii_data *mid = (struct ch_mii_data *)data; if (!phy->mdio_write) return (EOPNOTSUPP); if (is_10G(sc)) { mmd = mid->phy_id >> 8; if (!mmd) mmd = MDIO_DEV_PCS; else if (mmd > MDIO_DEV_VEND2) return (EINVAL); error = phy->mdio_write(sc, mid->phy_id & 0x1f, mmd, mid->reg_num, mid->val_in); } else error = phy->mdio_write(sc, mid->phy_id & 0x1f, 0, mid->reg_num & 0x1f, mid->val_in); break; } case CHELSIO_SETREG: { struct ch_reg *edata = (struct ch_reg *)data; if ((edata->addr & 0x3) != 0 || edata->addr >= sc->mmio_len) return (EFAULT); t3_write_reg(sc, edata->addr, edata->val); break; } case CHELSIO_GETREG: { struct ch_reg *edata = (struct ch_reg *)data; if ((edata->addr & 0x3) != 0 || edata->addr >= sc->mmio_len) return (EFAULT); edata->val = t3_read_reg(sc, edata->addr); break; } case CHELSIO_GET_SGE_CONTEXT: { struct ch_cntxt *ecntxt = (struct ch_cntxt *)data; mtx_lock_spin(&sc->sge.reg_lock); switch (ecntxt->cntxt_type) { case CNTXT_TYPE_EGRESS: error = -t3_sge_read_ecntxt(sc, ecntxt->cntxt_id, ecntxt->data); break; case CNTXT_TYPE_FL: error = -t3_sge_read_fl(sc, ecntxt->cntxt_id, ecntxt->data); break; case CNTXT_TYPE_RSP: error = -t3_sge_read_rspq(sc, ecntxt->cntxt_id, ecntxt->data); break; case CNTXT_TYPE_CQ: error = -t3_sge_read_cq(sc, ecntxt->cntxt_id, ecntxt->data); break; default: error = EINVAL; break; } mtx_unlock_spin(&sc->sge.reg_lock); break; } case CHELSIO_GET_SGE_DESC: { struct ch_desc *edesc = (struct ch_desc *)data; int ret; if (edesc->queue_num >= SGE_QSETS * 6) return (EINVAL); ret = t3_get_desc(&sc->sge.qs[edesc->queue_num / 6], edesc->queue_num % 6, edesc->idx, edesc->data); if (ret < 0) return (EINVAL); edesc->size = ret; break; } case CHELSIO_GET_QSET_PARAMS: { struct qset_params *q; struct ch_qset_params *t = (struct ch_qset_params *)data; int q1 = pi->first_qset; int nqsets = pi->nqsets; int i; if (t->qset_idx >= nqsets) return EINVAL; i = q1 + t->qset_idx; q = &sc->params.sge.qset[i]; t->rspq_size = q->rspq_size; t->txq_size[0] = q->txq_size[0]; t->txq_size[1] = q->txq_size[1]; t->txq_size[2] = q->txq_size[2]; t->fl_size[0] = q->fl_size; t->fl_size[1] = q->jumbo_size; t->polling = q->polling; t->lro = q->lro; t->intr_lat = q->coalesce_usecs; t->cong_thres = q->cong_thres; t->qnum = i; if ((sc->flags & FULL_INIT_DONE) == 0) t->vector = 0; else if (sc->flags & USING_MSIX) t->vector = rman_get_start(sc->msix_irq_res[i]); else t->vector = rman_get_start(sc->irq_res); break; } case CHELSIO_GET_QSET_NUM: { struct ch_reg *edata = (struct ch_reg *)data; edata->val = pi->nqsets; break; } case CHELSIO_LOAD_FW: { uint8_t *fw_data; uint32_t vers; struct ch_mem_range *t = (struct ch_mem_range *)data; /* * You're allowed to load a firmware only before FULL_INIT_DONE * * FW_UPTODATE is also set so the rest of the initialization * will not overwrite what was loaded here. This gives you the * flexibility to load any firmware (and maybe shoot yourself in * the foot). */ ADAPTER_LOCK(sc); if (sc->open_device_map || sc->flags & FULL_INIT_DONE) { ADAPTER_UNLOCK(sc); return (EBUSY); } fw_data = malloc(t->len, M_DEVBUF, M_NOWAIT); if (!fw_data) error = ENOMEM; else error = copyin(t->buf, fw_data, t->len); if (!error) error = -t3_load_fw(sc, fw_data, t->len); if (t3_get_fw_version(sc, &vers) == 0) { snprintf(&sc->fw_version[0], sizeof(sc->fw_version), "%d.%d.%d", G_FW_VERSION_MAJOR(vers), G_FW_VERSION_MINOR(vers), G_FW_VERSION_MICRO(vers)); } if (!error) sc->flags |= FW_UPTODATE; free(fw_data, M_DEVBUF); ADAPTER_UNLOCK(sc); break; } case CHELSIO_LOAD_BOOT: { uint8_t *boot_data; struct ch_mem_range *t = (struct ch_mem_range *)data; boot_data = malloc(t->len, M_DEVBUF, M_NOWAIT); if (!boot_data) return ENOMEM; error = copyin(t->buf, boot_data, t->len); if (!error) error = -t3_load_boot(sc, boot_data, t->len); free(boot_data, M_DEVBUF); break; } case CHELSIO_GET_PM: { struct ch_pm *m = (struct ch_pm *)data; struct tp_params *p = &sc->params.tp; if (!is_offload(sc)) return (EOPNOTSUPP); m->tx_pg_sz = p->tx_pg_size; m->tx_num_pg = p->tx_num_pgs; m->rx_pg_sz = p->rx_pg_size; m->rx_num_pg = p->rx_num_pgs; m->pm_total = p->pmtx_size + p->chan_rx_size * p->nchan; break; } case CHELSIO_SET_PM: { struct ch_pm *m = (struct ch_pm *)data; struct tp_params *p = &sc->params.tp; if (!is_offload(sc)) return (EOPNOTSUPP); if (sc->flags & FULL_INIT_DONE) return (EBUSY); if (!m->rx_pg_sz || (m->rx_pg_sz & (m->rx_pg_sz - 1)) || !m->tx_pg_sz || (m->tx_pg_sz & (m->tx_pg_sz - 1))) return (EINVAL); /* not power of 2 */ if (!(m->rx_pg_sz & 0x14000)) return (EINVAL); /* not 16KB or 64KB */ if (!(m->tx_pg_sz & 0x1554000)) return (EINVAL); if (m->tx_num_pg == -1) m->tx_num_pg = p->tx_num_pgs; if (m->rx_num_pg == -1) m->rx_num_pg = p->rx_num_pgs; if (m->tx_num_pg % 24 || m->rx_num_pg % 24) return (EINVAL); if (m->rx_num_pg * m->rx_pg_sz > p->chan_rx_size || m->tx_num_pg * m->tx_pg_sz > p->chan_tx_size) return (EINVAL); p->rx_pg_size = m->rx_pg_sz; p->tx_pg_size = m->tx_pg_sz; p->rx_num_pgs = m->rx_num_pg; p->tx_num_pgs = m->tx_num_pg; break; } case CHELSIO_SETMTUTAB: { struct ch_mtus *m = (struct ch_mtus *)data; int i; if (!is_offload(sc)) return (EOPNOTSUPP); if (offload_running(sc)) return (EBUSY); if (m->nmtus != NMTUS) return (EINVAL); if (m->mtus[0] < 81) /* accommodate SACK */ return (EINVAL); /* * MTUs must be in ascending order */ for (i = 1; i < NMTUS; ++i) if (m->mtus[i] < m->mtus[i - 1]) return (EINVAL); memcpy(sc->params.mtus, m->mtus, sizeof(sc->params.mtus)); break; } case CHELSIO_GETMTUTAB: { struct ch_mtus *m = (struct ch_mtus *)data; if (!is_offload(sc)) return (EOPNOTSUPP); memcpy(m->mtus, sc->params.mtus, sizeof(m->mtus)); m->nmtus = NMTUS; break; } case CHELSIO_GET_MEM: { struct ch_mem_range *t = (struct ch_mem_range *)data; struct mc7 *mem; uint8_t *useraddr; u64 buf[32]; /* * Use these to avoid modifying len/addr in the return * struct */ uint32_t len = t->len, addr = t->addr; if (!is_offload(sc)) return (EOPNOTSUPP); if (!(sc->flags & FULL_INIT_DONE)) return (EIO); /* need the memory controllers */ if ((addr & 0x7) || (len & 0x7)) return (EINVAL); if (t->mem_id == MEM_CM) mem = &sc->cm; else if (t->mem_id == MEM_PMRX) mem = &sc->pmrx; else if (t->mem_id == MEM_PMTX) mem = &sc->pmtx; else return (EINVAL); /* * Version scheme: * bits 0..9: chip version * bits 10..15: chip revision */ t->version = 3 | (sc->params.rev << 10); /* * Read 256 bytes at a time as len can be large and we don't * want to use huge intermediate buffers. */ useraddr = (uint8_t *)t->buf; while (len) { unsigned int chunk = min(len, sizeof(buf)); error = t3_mc7_bd_read(mem, addr / 8, chunk / 8, buf); if (error) return (-error); if (copyout(buf, useraddr, chunk)) return (EFAULT); useraddr += chunk; addr += chunk; len -= chunk; } break; } case CHELSIO_READ_TCAM_WORD: { struct ch_tcam_word *t = (struct ch_tcam_word *)data; if (!is_offload(sc)) return (EOPNOTSUPP); if (!(sc->flags & FULL_INIT_DONE)) return (EIO); /* need MC5 */ return -t3_read_mc5_range(&sc->mc5, t->addr, 1, t->buf); break; } case CHELSIO_SET_TRACE_FILTER: { struct ch_trace *t = (struct ch_trace *)data; const struct trace_params *tp; tp = (const struct trace_params *)&t->sip; if (t->config_tx) t3_config_trace_filter(sc, tp, 0, t->invert_match, t->trace_tx); if (t->config_rx) t3_config_trace_filter(sc, tp, 1, t->invert_match, t->trace_rx); break; } case CHELSIO_SET_PKTSCHED: { struct ch_pktsched_params *p = (struct ch_pktsched_params *)data; if (sc->open_device_map == 0) return (EAGAIN); send_pktsched_cmd(sc, p->sched, p->idx, p->min, p->max, p->binding); break; } case CHELSIO_IFCONF_GETREGS: { struct ch_ifconf_regs *regs = (struct ch_ifconf_regs *)data; int reglen = cxgb_get_regs_len(); uint8_t *buf = malloc(reglen, M_DEVBUF, M_NOWAIT); if (buf == NULL) { return (ENOMEM); } if (regs->len > reglen) regs->len = reglen; else if (regs->len < reglen) error = ENOBUFS; if (!error) { cxgb_get_regs(sc, regs, buf); error = copyout(buf, regs->data, reglen); } free(buf, M_DEVBUF); break; } case CHELSIO_SET_HW_SCHED: { struct ch_hw_sched *t = (struct ch_hw_sched *)data; unsigned int ticks_per_usec = core_ticks_per_usec(sc); if ((sc->flags & FULL_INIT_DONE) == 0) return (EAGAIN); /* need TP to be initialized */ if (t->sched >= NTX_SCHED || !in_range(t->mode, 0, 1) || !in_range(t->channel, 0, 1) || !in_range(t->kbps, 0, 10000000) || !in_range(t->class_ipg, 0, 10000 * 65535 / ticks_per_usec) || !in_range(t->flow_ipg, 0, dack_ticks_to_usec(sc, 0x7ff))) return (EINVAL); if (t->kbps >= 0) { error = t3_config_sched(sc, t->kbps, t->sched); if (error < 0) return (-error); } if (t->class_ipg >= 0) t3_set_sched_ipg(sc, t->sched, t->class_ipg); if (t->flow_ipg >= 0) { t->flow_ipg *= 1000; /* us -> ns */ t3_set_pace_tbl(sc, &t->flow_ipg, t->sched, 1); } if (t->mode >= 0) { int bit = 1 << (S_TX_MOD_TIMER_MODE + t->sched); t3_set_reg_field(sc, A_TP_TX_MOD_QUEUE_REQ_MAP, bit, t->mode ? bit : 0); } if (t->channel >= 0) t3_set_reg_field(sc, A_TP_TX_MOD_QUEUE_REQ_MAP, 1 << t->sched, t->channel << t->sched); break; } case CHELSIO_GET_EEPROM: { int i; struct ch_eeprom *e = (struct ch_eeprom *)data; uint8_t *buf; if (e->offset & 3 || e->offset >= EEPROMSIZE || e->len > EEPROMSIZE || e->offset + e->len > EEPROMSIZE) { return (EINVAL); } buf = malloc(EEPROMSIZE, M_DEVBUF, M_NOWAIT); if (buf == NULL) { return (ENOMEM); } e->magic = EEPROM_MAGIC; for (i = e->offset & ~3; !error && i < e->offset + e->len; i += 4) error = -t3_seeprom_read(sc, i, (uint32_t *)&buf[i]); if (!error) error = copyout(buf + e->offset, e->data, e->len); free(buf, M_DEVBUF); break; } case CHELSIO_CLEAR_STATS: { if (!(sc->flags & FULL_INIT_DONE)) return EAGAIN; PORT_LOCK(pi); t3_mac_update_stats(&pi->mac); memset(&pi->mac.stats, 0, sizeof(pi->mac.stats)); PORT_UNLOCK(pi); break; } case CHELSIO_GET_UP_LA: { struct ch_up_la *la = (struct ch_up_la *)data; uint8_t *buf = malloc(LA_BUFSIZE, M_DEVBUF, M_NOWAIT); if (buf == NULL) { return (ENOMEM); } if (la->bufsize < LA_BUFSIZE) error = ENOBUFS; if (!error) error = -t3_get_up_la(sc, &la->stopped, &la->idx, &la->bufsize, buf); if (!error) error = copyout(buf, la->data, la->bufsize); free(buf, M_DEVBUF); break; } case CHELSIO_GET_UP_IOQS: { struct ch_up_ioqs *ioqs = (struct ch_up_ioqs *)data; uint8_t *buf = malloc(IOQS_BUFSIZE, M_DEVBUF, M_NOWAIT); uint32_t *v; if (buf == NULL) { return (ENOMEM); } if (ioqs->bufsize < IOQS_BUFSIZE) error = ENOBUFS; if (!error) error = -t3_get_up_ioqs(sc, &ioqs->bufsize, buf); if (!error) { v = (uint32_t *)buf; ioqs->ioq_rx_enable = *v++; ioqs->ioq_tx_enable = *v++; ioqs->ioq_rx_status = *v++; ioqs->ioq_tx_status = *v++; error = copyout(v, ioqs->data, ioqs->bufsize); } free(buf, M_DEVBUF); break; } case CHELSIO_SET_FILTER: { struct ch_filter *f = (struct ch_filter *)data; struct filter_info *p; unsigned int nfilters = sc->params.mc5.nfilters; if (!is_offload(sc)) return (EOPNOTSUPP); /* No TCAM */ if (!(sc->flags & FULL_INIT_DONE)) return (EAGAIN); /* mc5 not setup yet */ if (nfilters == 0) return (EBUSY); /* TOE will use TCAM */ /* sanity checks */ if (f->filter_id >= nfilters || (f->val.dip && f->mask.dip != 0xffffffff) || (f->val.sport && f->mask.sport != 0xffff) || (f->val.dport && f->mask.dport != 0xffff) || (f->val.vlan && f->mask.vlan != 0xfff) || (f->val.vlan_prio && f->mask.vlan_prio != FILTER_NO_VLAN_PRI) || (f->mac_addr_idx != 0xffff && f->mac_addr_idx > 15) || f->qset >= SGE_QSETS || sc->rrss_map[f->qset] >= RSS_TABLE_SIZE) return (EINVAL); /* Was allocated with M_WAITOK */ KASSERT(sc->filters, ("filter table NULL\n")); p = &sc->filters[f->filter_id]; if (p->locked) return (EPERM); bzero(p, sizeof(*p)); p->sip = f->val.sip; p->sip_mask = f->mask.sip; p->dip = f->val.dip; p->sport = f->val.sport; p->dport = f->val.dport; p->vlan = f->mask.vlan ? f->val.vlan : 0xfff; p->vlan_prio = f->mask.vlan_prio ? (f->val.vlan_prio & 6) : FILTER_NO_VLAN_PRI; p->mac_hit = f->mac_hit; p->mac_vld = f->mac_addr_idx != 0xffff; p->mac_idx = f->mac_addr_idx; p->pkt_type = f->proto; p->report_filter_id = f->want_filter_id; p->pass = f->pass; p->rss = f->rss; p->qset = f->qset; error = set_filter(sc, f->filter_id, p); if (error == 0) p->valid = 1; break; } case CHELSIO_DEL_FILTER: { struct ch_filter *f = (struct ch_filter *)data; struct filter_info *p; unsigned int nfilters = sc->params.mc5.nfilters; if (!is_offload(sc)) return (EOPNOTSUPP); if (!(sc->flags & FULL_INIT_DONE)) return (EAGAIN); if (nfilters == 0 || sc->filters == NULL) return (EINVAL); if (f->filter_id >= nfilters) return (EINVAL); p = &sc->filters[f->filter_id]; if (p->locked) return (EPERM); if (!p->valid) return (EFAULT); /* Read "Bad address" as "Bad index" */ bzero(p, sizeof(*p)); p->sip = p->sip_mask = 0xffffffff; p->vlan = 0xfff; p->vlan_prio = FILTER_NO_VLAN_PRI; p->pkt_type = 1; error = set_filter(sc, f->filter_id, p); break; } case CHELSIO_GET_FILTER: { struct ch_filter *f = (struct ch_filter *)data; struct filter_info *p; unsigned int i, nfilters = sc->params.mc5.nfilters; if (!is_offload(sc)) return (EOPNOTSUPP); if (!(sc->flags & FULL_INIT_DONE)) return (EAGAIN); if (nfilters == 0 || sc->filters == NULL) return (EINVAL); i = f->filter_id == 0xffffffff ? 0 : f->filter_id + 1; for (; i < nfilters; i++) { p = &sc->filters[i]; if (!p->valid) continue; bzero(f, sizeof(*f)); f->filter_id = i; f->val.sip = p->sip; f->mask.sip = p->sip_mask; f->val.dip = p->dip; f->mask.dip = p->dip ? 0xffffffff : 0; f->val.sport = p->sport; f->mask.sport = p->sport ? 0xffff : 0; f->val.dport = p->dport; f->mask.dport = p->dport ? 0xffff : 0; f->val.vlan = p->vlan == 0xfff ? 0 : p->vlan; f->mask.vlan = p->vlan == 0xfff ? 0 : 0xfff; f->val.vlan_prio = p->vlan_prio == FILTER_NO_VLAN_PRI ? 0 : p->vlan_prio; f->mask.vlan_prio = p->vlan_prio == FILTER_NO_VLAN_PRI ? 0 : FILTER_NO_VLAN_PRI; f->mac_hit = p->mac_hit; f->mac_addr_idx = p->mac_vld ? p->mac_idx : 0xffff; f->proto = p->pkt_type; f->want_filter_id = p->report_filter_id; f->pass = p->pass; f->rss = p->rss; f->qset = p->qset; break; } if (i == nfilters) f->filter_id = 0xffffffff; break; } default: return (EOPNOTSUPP); break; } return (error); } static __inline void reg_block_dump(struct adapter *ap, uint8_t *buf, unsigned int start, unsigned int end) { uint32_t *p = (uint32_t *)(buf + start); for ( ; start <= end; start += sizeof(uint32_t)) *p++ = t3_read_reg(ap, start); } #define T3_REGMAP_SIZE (3 * 1024) static int cxgb_get_regs_len(void) { return T3_REGMAP_SIZE; } static void cxgb_get_regs(adapter_t *sc, struct ch_ifconf_regs *regs, uint8_t *buf) { /* * Version scheme: * bits 0..9: chip version * bits 10..15: chip revision * bit 31: set for PCIe cards */ regs->version = 3 | (sc->params.rev << 10) | (is_pcie(sc) << 31); /* * We skip the MAC statistics registers because they are clear-on-read. * Also reading multi-register stats would need to synchronize with the * periodic mac stats accumulation. Hard to justify the complexity. */ memset(buf, 0, cxgb_get_regs_len()); reg_block_dump(sc, buf, 0, A_SG_RSPQ_CREDIT_RETURN); reg_block_dump(sc, buf, A_SG_HI_DRB_HI_THRSH, A_ULPRX_PBL_ULIMIT); reg_block_dump(sc, buf, A_ULPTX_CONFIG, A_MPS_INT_CAUSE); reg_block_dump(sc, buf, A_CPL_SWITCH_CNTRL, A_CPL_MAP_TBL_DATA); reg_block_dump(sc, buf, A_SMB_GLOBAL_TIME_CFG, A_XGM_SERDES_STAT3); reg_block_dump(sc, buf, A_XGM_SERDES_STATUS0, XGM_REG(A_XGM_SERDES_STAT3, 1)); reg_block_dump(sc, buf, XGM_REG(A_XGM_SERDES_STATUS0, 1), XGM_REG(A_XGM_RX_SPI4_SOP_EOP_CNT, 1)); } static int alloc_filters(struct adapter *sc) { struct filter_info *p; unsigned int nfilters = sc->params.mc5.nfilters; if (nfilters == 0) return (0); p = malloc(sizeof(*p) * nfilters, M_DEVBUF, M_WAITOK | M_ZERO); sc->filters = p; p = &sc->filters[nfilters - 1]; p->vlan = 0xfff; p->vlan_prio = FILTER_NO_VLAN_PRI; p->pass = p->rss = p->valid = p->locked = 1; return (0); } static int setup_hw_filters(struct adapter *sc) { int i, rc; unsigned int nfilters = sc->params.mc5.nfilters; if (!sc->filters) return (0); t3_enable_filters(sc); for (i = rc = 0; i < nfilters && !rc; i++) { if (sc->filters[i].locked) rc = set_filter(sc, i, &sc->filters[i]); } return (rc); } static int set_filter(struct adapter *sc, int id, const struct filter_info *f) { int len; struct mbuf *m; struct ulp_txpkt *txpkt; struct work_request_hdr *wr; struct cpl_pass_open_req *oreq; struct cpl_set_tcb_field *sreq; len = sizeof(*wr) + sizeof(*oreq) + 2 * sizeof(*sreq); KASSERT(len <= MHLEN, ("filter request too big for an mbuf")); id += t3_mc5_size(&sc->mc5) - sc->params.mc5.nroutes - sc->params.mc5.nfilters; m = m_gethdr(M_WAITOK, MT_DATA); m->m_len = m->m_pkthdr.len = len; bzero(mtod(m, char *), len); wr = mtod(m, struct work_request_hdr *); wr->wrh_hi = htonl(V_WR_OP(FW_WROPCODE_BYPASS) | F_WR_ATOMIC); oreq = (struct cpl_pass_open_req *)(wr + 1); txpkt = (struct ulp_txpkt *)oreq; txpkt->cmd_dest = htonl(V_ULPTX_CMD(ULP_TXPKT)); txpkt->len = htonl(V_ULPTX_NFLITS(sizeof(*oreq) / 8)); OPCODE_TID(oreq) = htonl(MK_OPCODE_TID(CPL_PASS_OPEN_REQ, id)); oreq->local_port = htons(f->dport); oreq->peer_port = htons(f->sport); oreq->local_ip = htonl(f->dip); oreq->peer_ip = htonl(f->sip); oreq->peer_netmask = htonl(f->sip_mask); oreq->opt0h = 0; oreq->opt0l = htonl(F_NO_OFFLOAD); oreq->opt1 = htonl(V_MAC_MATCH_VALID(f->mac_vld) | V_CONN_POLICY(CPL_CONN_POLICY_FILTER) | V_VLAN_PRI(f->vlan_prio >> 1) | V_VLAN_PRI_VALID(f->vlan_prio != FILTER_NO_VLAN_PRI) | V_PKT_TYPE(f->pkt_type) | V_OPT1_VLAN(f->vlan) | V_MAC_MATCH(f->mac_idx | (f->mac_hit << 4))); sreq = (struct cpl_set_tcb_field *)(oreq + 1); set_tcb_field_ulp(sreq, id, 1, 0x1800808000ULL, (f->report_filter_id << 15) | (1 << 23) | ((u64)f->pass << 35) | ((u64)!f->rss << 36)); set_tcb_field_ulp(sreq + 1, id, 0, 0xffffffff, (2 << 19) | 1); t3_mgmt_tx(sc, m); if (f->pass && !f->rss) { len = sizeof(*sreq); m = m_gethdr(M_WAITOK, MT_DATA); m->m_len = m->m_pkthdr.len = len; bzero(mtod(m, char *), len); sreq = mtod(m, struct cpl_set_tcb_field *); sreq->wr.wrh_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD)); mk_set_tcb_field(sreq, id, 25, 0x3f80000, (u64)sc->rrss_map[f->qset] << 19); t3_mgmt_tx(sc, m); } return 0; } static inline void mk_set_tcb_field(struct cpl_set_tcb_field *req, unsigned int tid, unsigned int word, u64 mask, u64 val) { OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, tid)); req->reply = V_NO_REPLY(1); req->cpu_idx = 0; req->word = htons(word); req->mask = htobe64(mask); req->val = htobe64(val); } static inline void set_tcb_field_ulp(struct cpl_set_tcb_field *req, unsigned int tid, unsigned int word, u64 mask, u64 val) { struct ulp_txpkt *txpkt = (struct ulp_txpkt *)req; txpkt->cmd_dest = htonl(V_ULPTX_CMD(ULP_TXPKT)); txpkt->len = htonl(V_ULPTX_NFLITS(sizeof(*req) / 8)); mk_set_tcb_field(req, tid, word, mask, val); } void t3_iterate(void (*func)(struct adapter *, void *), void *arg) { struct adapter *sc; mtx_lock(&t3_list_lock); SLIST_FOREACH(sc, &t3_list, link) { /* * func should not make any assumptions about what state sc is * in - the only guarantee is that sc->sc_lock is a valid lock. */ func(sc, arg); } mtx_unlock(&t3_list_lock); } #ifdef TCP_OFFLOAD static int toe_capability(struct port_info *pi, int enable) { int rc; struct adapter *sc = pi->adapter; ADAPTER_LOCK_ASSERT_OWNED(sc); if (!is_offload(sc)) return (ENODEV); if (enable) { if (!(sc->flags & FULL_INIT_DONE)) { log(LOG_WARNING, "You must enable a cxgb interface first\n"); return (EAGAIN); } if (isset(&sc->offload_map, pi->port_id)) return (0); if (!(sc->flags & TOM_INIT_DONE)) { rc = t3_activate_uld(sc, ULD_TOM); if (rc == EAGAIN) { log(LOG_WARNING, "You must kldload t3_tom.ko before trying " "to enable TOE on a cxgb interface.\n"); } if (rc != 0) return (rc); KASSERT(sc->tom_softc != NULL, ("%s: TOM activated but softc NULL", __func__)); KASSERT(sc->flags & TOM_INIT_DONE, ("%s: TOM activated but flag not set", __func__)); } setbit(&sc->offload_map, pi->port_id); /* * XXX: Temporary code to allow iWARP to be enabled when TOE is * enabled on any port. Need to figure out how to enable, * disable, load, and unload iWARP cleanly. */ if (!isset(&sc->offload_map, MAX_NPORTS) && t3_activate_uld(sc, ULD_IWARP) == 0) setbit(&sc->offload_map, MAX_NPORTS); } else { if (!isset(&sc->offload_map, pi->port_id)) return (0); KASSERT(sc->flags & TOM_INIT_DONE, ("%s: TOM never initialized?", __func__)); clrbit(&sc->offload_map, pi->port_id); } return (0); } /* * Add an upper layer driver to the global list. */ int t3_register_uld(struct uld_info *ui) { int rc = 0; struct uld_info *u; mtx_lock(&t3_uld_list_lock); SLIST_FOREACH(u, &t3_uld_list, link) { if (u->uld_id == ui->uld_id) { rc = EEXIST; goto done; } } SLIST_INSERT_HEAD(&t3_uld_list, ui, link); ui->refcount = 0; done: mtx_unlock(&t3_uld_list_lock); return (rc); } int t3_unregister_uld(struct uld_info *ui) { int rc = EINVAL; struct uld_info *u; mtx_lock(&t3_uld_list_lock); SLIST_FOREACH(u, &t3_uld_list, link) { if (u == ui) { if (ui->refcount > 0) { rc = EBUSY; goto done; } SLIST_REMOVE(&t3_uld_list, ui, uld_info, link); rc = 0; goto done; } } done: mtx_unlock(&t3_uld_list_lock); return (rc); } int t3_activate_uld(struct adapter *sc, int id) { int rc = EAGAIN; struct uld_info *ui; mtx_lock(&t3_uld_list_lock); SLIST_FOREACH(ui, &t3_uld_list, link) { if (ui->uld_id == id) { rc = ui->activate(sc); if (rc == 0) ui->refcount++; goto done; } } done: mtx_unlock(&t3_uld_list_lock); return (rc); } int t3_deactivate_uld(struct adapter *sc, int id) { int rc = EINVAL; struct uld_info *ui; mtx_lock(&t3_uld_list_lock); SLIST_FOREACH(ui, &t3_uld_list, link) { if (ui->uld_id == id) { rc = ui->deactivate(sc); if (rc == 0) ui->refcount--; goto done; } } done: mtx_unlock(&t3_uld_list_lock); return (rc); } static int cpl_not_handled(struct sge_qset *qs __unused, struct rsp_desc *r __unused, struct mbuf *m) { m_freem(m); return (EDOOFUS); } int t3_register_cpl_handler(struct adapter *sc, int opcode, cpl_handler_t h) { uintptr_t *loc, new; if (opcode >= NUM_CPL_HANDLERS) return (EINVAL); new = h ? (uintptr_t)h : (uintptr_t)cpl_not_handled; loc = (uintptr_t *) &sc->cpl_handler[opcode]; atomic_store_rel_ptr(loc, new); return (0); } #endif static int cxgbc_mod_event(module_t mod, int cmd, void *arg) { int rc = 0; switch (cmd) { case MOD_LOAD: mtx_init(&t3_list_lock, "T3 adapters", 0, MTX_DEF); SLIST_INIT(&t3_list); #ifdef TCP_OFFLOAD mtx_init(&t3_uld_list_lock, "T3 ULDs", 0, MTX_DEF); SLIST_INIT(&t3_uld_list); #endif break; case MOD_UNLOAD: #ifdef TCP_OFFLOAD mtx_lock(&t3_uld_list_lock); if (!SLIST_EMPTY(&t3_uld_list)) { rc = EBUSY; mtx_unlock(&t3_uld_list_lock); break; } mtx_unlock(&t3_uld_list_lock); mtx_destroy(&t3_uld_list_lock); #endif mtx_lock(&t3_list_lock); if (!SLIST_EMPTY(&t3_list)) { rc = EBUSY; mtx_unlock(&t3_list_lock); break; } mtx_unlock(&t3_list_lock); mtx_destroy(&t3_list_lock); break; } return (rc); } #ifdef DEBUGNET static void cxgb_debugnet_init(if_t ifp, int *nrxr, int *ncl, int *clsize) { struct port_info *pi; adapter_t *adap; pi = if_getsoftc(ifp); adap = pi->adapter; ADAPTER_LOCK(adap); *nrxr = adap->nqsets; *ncl = adap->sge.qs[0].fl[1].size; *clsize = adap->sge.qs[0].fl[1].buf_size; ADAPTER_UNLOCK(adap); } static void cxgb_debugnet_event(if_t ifp, enum debugnet_ev event) { struct port_info *pi; struct sge_qset *qs; int i; pi = if_getsoftc(ifp); if (event == DEBUGNET_START) for (i = 0; i < pi->adapter->nqsets; i++) { qs = &pi->adapter->sge.qs[i]; /* Need to reinit after debugnet_mbuf_start(). */ qs->fl[0].zone = zone_pack; qs->fl[1].zone = zone_clust; qs->lro.enabled = 0; } } static int cxgb_debugnet_transmit(if_t ifp, struct mbuf *m) { struct port_info *pi; struct sge_qset *qs; pi = if_getsoftc(ifp); if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return (ENOENT); qs = &pi->adapter->sge.qs[pi->first_qset]; return (cxgb_debugnet_encap(qs, &m)); } static int cxgb_debugnet_poll(if_t ifp, int count) { struct port_info *pi; adapter_t *adap; int i; pi = if_getsoftc(ifp); if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) return (ENOENT); adap = pi->adapter; for (i = 0; i < adap->nqsets; i++) (void)cxgb_debugnet_poll_rx(adap, &adap->sge.qs[i]); (void)cxgb_debugnet_poll_tx(&adap->sge.qs[pi->first_qset]); return (0); } #endif /* DEBUGNET */ diff --git a/sys/dev/cxgbe/crypto/t4_crypto.c b/sys/dev/cxgbe/crypto/t4_crypto.c index 568a948a49f7..2c83b10b13d6 100644 --- a/sys/dev/cxgbe/crypto/t4_crypto.c +++ b/sys/dev/cxgbe/crypto/t4_crypto.c @@ -1,2751 +1,2751 @@ /*- * Copyright (c) 2017 Chelsio Communications, Inc. * Copyright (c) 2021 The FreeBSD Foundation * All rights reserved. * Written by: John Baldwin * * Portions of this software were developed by Ararat River * Consulting, LLC under sponsorship of the FreeBSD Foundation. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include "cryptodev_if.h" #include "common/common.h" #include "crypto/t4_crypto.h" /* * Requests consist of: * * +-------------------------------+ * | struct fw_crypto_lookaside_wr | * +-------------------------------+ * | struct ulp_txpkt | * +-------------------------------+ * | struct ulptx_idata | * +-------------------------------+ * | struct cpl_tx_sec_pdu | * +-------------------------------+ * | struct cpl_tls_tx_scmd_fmt | * +-------------------------------+ * | key context header | * +-------------------------------+ * | AES key | ----- For requests with AES * +-------------------------------+ * | Hash state | ----- For hash-only requests * +-------------------------------+ - * | IPAD (16-byte aligned) | \ * +-------------------------------+ +---- For requests with HMAC * | OPAD (16-byte aligned) | / * +-------------------------------+ - * | GMAC H | ----- For AES-GCM * +-------------------------------+ - * | struct cpl_rx_phys_dsgl | \ * +-------------------------------+ +---- Destination buffer for * | PHYS_DSGL entries | / non-hash-only requests * +-------------------------------+ - * | 16 dummy bytes | ----- Only for HMAC/hash-only requests * +-------------------------------+ * | IV | ----- If immediate IV * +-------------------------------+ * | Payload | ----- If immediate Payload * +-------------------------------+ - * | struct ulptx_sgl | \ * +-------------------------------+ +---- If payload via SGL * | SGL entries | / * +-------------------------------+ - * * Note that the key context must be padded to ensure 16-byte alignment. * For HMAC requests, the key consists of the partial hash of the IPAD * followed by the partial hash of the OPAD. * * Replies consist of: * * +-------------------------------+ * | struct cpl_fw6_pld | * +-------------------------------+ * | hash digest | ----- For HMAC request with * +-------------------------------+ 'hash_size' set in work request * * A 32-bit big-endian error status word is supplied in the last 4 * bytes of data[0] in the CPL_FW6_PLD message. bit 0 indicates a * "MAC" error and bit 1 indicates a "PAD" error. * * The 64-bit 'cookie' field from the fw_crypto_lookaside_wr message * in the request is returned in data[1] of the CPL_FW6_PLD message. * * For block cipher replies, the updated IV is supplied in data[2] and * data[3] of the CPL_FW6_PLD message. * * For hash replies where the work request set 'hash_size' to request * a copy of the hash in the reply, the hash digest is supplied * immediately following the CPL_FW6_PLD message. */ /* * The crypto engine supports a maximum AAD size of 511 bytes. */ #define MAX_AAD_LEN 511 /* * The documentation for CPL_RX_PHYS_DSGL claims a maximum of 32 SG * entries. While the CPL includes a 16-bit length field, the T6 can * sometimes hang if an error occurs while processing a request with a * single DSGL entry larger than 2k. */ #define MAX_RX_PHYS_DSGL_SGE 32 #define DSGL_SGE_MAXLEN 2048 /* * The adapter only supports requests with a total input or output * length of 64k-1 or smaller. Longer requests either result in hung * requests or incorrect results. */ #define MAX_REQUEST_SIZE 65535 static MALLOC_DEFINE(M_CCR, "ccr", "Chelsio T6 crypto"); struct ccr_session_hmac { const struct auth_hash *auth_hash; int hash_len; unsigned int partial_digest_len; unsigned int auth_mode; unsigned int mk_size; char pads[CHCR_HASH_MAX_BLOCK_SIZE_128 * 2]; }; struct ccr_session_gmac { int hash_len; char ghash_h[GMAC_BLOCK_LEN]; }; struct ccr_session_ccm_mac { int hash_len; }; struct ccr_session_cipher { unsigned int cipher_mode; unsigned int key_len; unsigned int iv_len; __be32 key_ctx_hdr; char enckey[CHCR_AES_MAX_KEY_LEN]; char deckey[CHCR_AES_MAX_KEY_LEN]; }; struct ccr_port { struct sge_wrq *txq; struct sge_rxq *rxq; int rx_channel_id; int tx_channel_id; u_int active_sessions; counter_u64_t stats_queued; counter_u64_t stats_completed; }; struct ccr_softc { struct adapter *adapter; device_t dev; uint32_t cid; struct mtx lock; bool detaching; struct ccr_port ports[MAX_NPORTS]; u_int port_mask; int first_rxq_id; /* * Pre-allocate a dummy output buffer for the IV and AAD for * AEAD requests. */ char *iv_aad_buf; struct sglist *sg_iv_aad; /* Statistics. */ counter_u64_t stats_cipher_encrypt; counter_u64_t stats_cipher_decrypt; counter_u64_t stats_hash; counter_u64_t stats_hmac; counter_u64_t stats_eta_encrypt; counter_u64_t stats_eta_decrypt; counter_u64_t stats_gcm_encrypt; counter_u64_t stats_gcm_decrypt; counter_u64_t stats_ccm_encrypt; counter_u64_t stats_ccm_decrypt; counter_u64_t stats_wr_nomem; counter_u64_t stats_inflight; counter_u64_t stats_mac_error; counter_u64_t stats_pad_error; counter_u64_t stats_sglist_error; counter_u64_t stats_process_error; counter_u64_t stats_sw_fallback; struct sysctl_ctx_list ctx; }; struct ccr_session { #ifdef INVARIANTS int pending; #endif enum { HASH, HMAC, CIPHER, ETA, GCM, CCM } mode; struct ccr_softc *sc; struct ccr_port *port; union { struct ccr_session_hmac hmac; struct ccr_session_gmac gmac; struct ccr_session_ccm_mac ccm_mac; }; struct ccr_session_cipher cipher; struct mtx lock; /* * A fallback software session is used for certain GCM/CCM * requests that the hardware can't handle such as requests * with only AAD and no payload. */ crypto_session_t sw_session; /* * Pre-allocate S/G lists used when preparing a work request. * 'sg_input' contains an sglist describing the entire input * buffer for a 'struct cryptop'. 'sg_output' contains an * sglist describing the entire output buffer. 'sg_ulptx' is * used to describe the data the engine should DMA as input * via ULPTX_SGL. 'sg_dsgl' is used to describe the * destination that cipher text and a tag should be written * to. */ struct sglist *sg_input; struct sglist *sg_output; struct sglist *sg_ulptx; struct sglist *sg_dsgl; }; /* * Crypto requests involve two kind of scatter/gather lists. * * Non-hash-only requests require a PHYS_DSGL that describes the * location to store the results of the encryption or decryption * operation. This SGL uses a different format (PHYS_DSGL) and should * exclude the skip bytes at the start of the data as well as any AAD * or IV. For authenticated encryption requests it should include the * destination of the hash or tag. * * The input payload may either be supplied inline as immediate data, * or via a standard ULP_TX SGL. This SGL should include AAD, * ciphertext, and the hash or tag for authenticated decryption * requests. * * These scatter/gather lists can describe different subsets of the * buffers described by the crypto operation. ccr_populate_sglist() * generates a scatter/gather list that covers an entire crypto * operation buffer that is then used to construct the other * scatter/gather lists. */ static int ccr_populate_sglist(struct sglist *sg, struct crypto_buffer *cb) { int error; sglist_reset(sg); switch (cb->cb_type) { case CRYPTO_BUF_MBUF: error = sglist_append_mbuf(sg, cb->cb_mbuf); break; case CRYPTO_BUF_SINGLE_MBUF: error = sglist_append_single_mbuf(sg, cb->cb_mbuf); break; case CRYPTO_BUF_UIO: error = sglist_append_uio(sg, cb->cb_uio); break; case CRYPTO_BUF_CONTIG: error = sglist_append(sg, cb->cb_buf, cb->cb_buf_len); break; case CRYPTO_BUF_VMPAGE: error = sglist_append_vmpages(sg, cb->cb_vm_page, cb->cb_vm_page_offset, cb->cb_vm_page_len); break; default: error = EINVAL; } return (error); } /* * Segments in 'sg' larger than 'maxsegsize' are counted as multiple * segments. */ static int ccr_count_sgl(struct sglist *sg, int maxsegsize) { int i, nsegs; nsegs = 0; for (i = 0; i < sg->sg_nseg; i++) nsegs += howmany(sg->sg_segs[i].ss_len, maxsegsize); return (nsegs); } /* These functions deal with PHYS_DSGL for the reply buffer. */ static inline int ccr_phys_dsgl_len(int nsegs) { int len; len = (nsegs / 8) * sizeof(struct phys_sge_pairs); if ((nsegs % 8) != 0) { len += sizeof(uint16_t) * 8; len += roundup2(nsegs % 8, 2) * sizeof(uint64_t); } return (len); } static void ccr_write_phys_dsgl(struct ccr_session *s, void *dst, int nsegs) { struct sglist *sg; struct cpl_rx_phys_dsgl *cpl; struct phys_sge_pairs *sgl; vm_paddr_t paddr; size_t seglen; u_int i, j; sg = s->sg_dsgl; cpl = dst; cpl->op_to_tid = htobe32(V_CPL_RX_PHYS_DSGL_OPCODE(CPL_RX_PHYS_DSGL) | V_CPL_RX_PHYS_DSGL_ISRDMA(0)); cpl->pcirlxorder_to_noofsgentr = htobe32( V_CPL_RX_PHYS_DSGL_PCIRLXORDER(0) | V_CPL_RX_PHYS_DSGL_PCINOSNOOP(0) | V_CPL_RX_PHYS_DSGL_PCITPHNTENB(0) | V_CPL_RX_PHYS_DSGL_DCAID(0) | V_CPL_RX_PHYS_DSGL_NOOFSGENTR(nsegs)); cpl->rss_hdr_int.opcode = CPL_RX_PHYS_ADDR; cpl->rss_hdr_int.qid = htobe16(s->port->rxq->iq.abs_id); cpl->rss_hdr_int.hash_val = 0; cpl->rss_hdr_int.channel = s->port->rx_channel_id; sgl = (struct phys_sge_pairs *)(cpl + 1); j = 0; for (i = 0; i < sg->sg_nseg; i++) { seglen = sg->sg_segs[i].ss_len; paddr = sg->sg_segs[i].ss_paddr; do { sgl->addr[j] = htobe64(paddr); if (seglen > DSGL_SGE_MAXLEN) { sgl->len[j] = htobe16(DSGL_SGE_MAXLEN); paddr += DSGL_SGE_MAXLEN; seglen -= DSGL_SGE_MAXLEN; } else { sgl->len[j] = htobe16(seglen); seglen = 0; } j++; if (j == 8) { sgl++; j = 0; } } while (seglen != 0); } MPASS(j + 8 * (sgl - (struct phys_sge_pairs *)(cpl + 1)) == nsegs); } /* These functions deal with the ULPTX_SGL for input payload. */ static inline int ccr_ulptx_sgl_len(int nsegs) { u_int n; nsegs--; /* first segment is part of ulptx_sgl */ n = sizeof(struct ulptx_sgl) + 8 * ((3 * nsegs) / 2 + (nsegs & 1)); return (roundup2(n, 16)); } static void ccr_write_ulptx_sgl(struct ccr_session *s, void *dst, int nsegs) { struct ulptx_sgl *usgl; struct sglist *sg; struct sglist_seg *ss; int i; sg = s->sg_ulptx; MPASS(nsegs == sg->sg_nseg); ss = &sg->sg_segs[0]; usgl = dst; usgl->cmd_nsge = htobe32(V_ULPTX_CMD(ULP_TX_SC_DSGL) | V_ULPTX_NSGE(nsegs)); usgl->len0 = htobe32(ss->ss_len); usgl->addr0 = htobe64(ss->ss_paddr); ss++; for (i = 0; i < sg->sg_nseg - 1; i++) { usgl->sge[i / 2].len[i & 1] = htobe32(ss->ss_len); usgl->sge[i / 2].addr[i & 1] = htobe64(ss->ss_paddr); ss++; } } static bool ccr_use_imm_data(u_int transhdr_len, u_int input_len) { if (input_len > CRYPTO_MAX_IMM_TX_PKT_LEN) return (false); if (roundup2(transhdr_len, 16) + roundup2(input_len, 16) > SGE_MAX_WR_LEN) return (false); return (true); } static void ccr_populate_wreq(struct ccr_softc *sc, struct ccr_session *s, struct chcr_wr *crwr, u_int kctx_len, u_int wr_len, u_int imm_len, u_int sgl_len, u_int hash_size, struct cryptop *crp) { u_int cctx_size, idata_len; cctx_size = sizeof(struct _key_ctx) + kctx_len; crwr->wreq.op_to_cctx_size = htobe32( V_FW_CRYPTO_LOOKASIDE_WR_OPCODE(FW_CRYPTO_LOOKASIDE_WR) | V_FW_CRYPTO_LOOKASIDE_WR_COMPL(0) | V_FW_CRYPTO_LOOKASIDE_WR_IMM_LEN(imm_len) | V_FW_CRYPTO_LOOKASIDE_WR_CCTX_LOC(1) | V_FW_CRYPTO_LOOKASIDE_WR_CCTX_SIZE(cctx_size >> 4)); crwr->wreq.len16_pkd = htobe32( V_FW_CRYPTO_LOOKASIDE_WR_LEN16(wr_len / 16)); crwr->wreq.session_id = 0; crwr->wreq.rx_chid_to_rx_q_id = htobe32( V_FW_CRYPTO_LOOKASIDE_WR_RX_CHID(s->port->rx_channel_id) | V_FW_CRYPTO_LOOKASIDE_WR_LCB(0) | V_FW_CRYPTO_LOOKASIDE_WR_PHASH(0) | V_FW_CRYPTO_LOOKASIDE_WR_IV(IV_NOP) | V_FW_CRYPTO_LOOKASIDE_WR_FQIDX(0) | V_FW_CRYPTO_LOOKASIDE_WR_TX_CH(0) | /* unused in firmware */ V_FW_CRYPTO_LOOKASIDE_WR_RX_Q_ID(s->port->rxq->iq.abs_id)); crwr->wreq.key_addr = 0; crwr->wreq.pld_size_hash_size = htobe32( V_FW_CRYPTO_LOOKASIDE_WR_PLD_SIZE(sgl_len) | V_FW_CRYPTO_LOOKASIDE_WR_HASH_SIZE(hash_size)); crwr->wreq.cookie = htobe64((uintptr_t)crp); crwr->ulptx.cmd_dest = htobe32(V_ULPTX_CMD(ULP_TX_PKT) | V_ULP_TXPKT_DATAMODIFY(0) | V_ULP_TXPKT_CHANNELID(s->port->tx_channel_id) | V_ULP_TXPKT_DEST(0) | V_ULP_TXPKT_FID(sc->first_rxq_id) | V_ULP_TXPKT_RO(1)); crwr->ulptx.len = htobe32( ((wr_len - sizeof(struct fw_crypto_lookaside_wr)) / 16)); crwr->sc_imm.cmd_more = htobe32(V_ULPTX_CMD(ULP_TX_SC_IMM) | V_ULP_TX_SC_MORE(sgl_len != 0 ? 1 : 0)); idata_len = wr_len - offsetof(struct chcr_wr, sec_cpl) - sgl_len; if (imm_len % 16 != 0) idata_len -= 16 - imm_len % 16; crwr->sc_imm.len = htobe32(idata_len); } static int ccr_hash(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp) { struct chcr_wr *crwr; struct wrqe *wr; const struct auth_hash *axf; char *dst; u_int hash_size_in_response, kctx_flits, kctx_len, transhdr_len, wr_len; u_int hmac_ctrl, imm_len, iopad_size; int error, sgl_nsegs, sgl_len, use_opad; /* Reject requests with too large of an input buffer. */ if (crp->crp_payload_length > MAX_REQUEST_SIZE) return (EFBIG); axf = s->hmac.auth_hash; if (s->mode == HMAC) { use_opad = 1; hmac_ctrl = SCMD_HMAC_CTRL_NO_TRUNC; } else { use_opad = 0; hmac_ctrl = SCMD_HMAC_CTRL_NOP; } /* PADs must be 128-bit aligned. */ iopad_size = roundup2(s->hmac.partial_digest_len, 16); /* * The 'key' part of the context includes the aligned IPAD and * OPAD. */ kctx_len = iopad_size; if (use_opad) kctx_len += iopad_size; hash_size_in_response = axf->hashsize; transhdr_len = HASH_TRANSHDR_SIZE(kctx_len); if (crp->crp_payload_length == 0) { imm_len = axf->blocksize; sgl_nsegs = 0; sgl_len = 0; } else if (ccr_use_imm_data(transhdr_len, crp->crp_payload_length)) { imm_len = crp->crp_payload_length; sgl_nsegs = 0; sgl_len = 0; } else { imm_len = 0; sglist_reset(s->sg_ulptx); error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); sgl_nsegs = s->sg_ulptx->sg_nseg; sgl_len = ccr_ulptx_sgl_len(sgl_nsegs); } wr_len = roundup2(transhdr_len, 16) + roundup2(imm_len, 16) + sgl_len; if (wr_len > SGE_MAX_WR_LEN) return (EFBIG); wr = alloc_wrqe(wr_len, s->port->txq); if (wr == NULL) { counter_u64_add(sc->stats_wr_nomem, 1); return (ENOMEM); } crwr = wrtod(wr); memset(crwr, 0, wr_len); ccr_populate_wreq(sc, s, crwr, kctx_len, wr_len, imm_len, sgl_len, hash_size_in_response, crp); crwr->sec_cpl.op_ivinsrtofst = htobe32( V_CPL_TX_SEC_PDU_OPCODE(CPL_TX_SEC_PDU) | V_CPL_TX_SEC_PDU_RXCHID(s->port->rx_channel_id) | V_CPL_TX_SEC_PDU_ACKFOLLOWS(0) | V_CPL_TX_SEC_PDU_ULPTXLPBK(1) | V_CPL_TX_SEC_PDU_CPLLEN(2) | V_CPL_TX_SEC_PDU_PLACEHOLDER(0) | V_CPL_TX_SEC_PDU_IVINSRTOFST(0)); crwr->sec_cpl.pldlen = htobe32(crp->crp_payload_length == 0 ? axf->blocksize : crp->crp_payload_length); crwr->sec_cpl.cipherstop_lo_authinsert = htobe32( V_CPL_TX_SEC_PDU_AUTHSTART(1) | V_CPL_TX_SEC_PDU_AUTHSTOP(0)); /* These two flits are actually a CPL_TLS_TX_SCMD_FMT. */ crwr->sec_cpl.seqno_numivs = htobe32( V_SCMD_SEQ_NO_CTRL(0) | V_SCMD_PROTO_VERSION(SCMD_PROTO_VERSION_GENERIC) | V_SCMD_CIPH_MODE(SCMD_CIPH_MODE_NOP) | V_SCMD_AUTH_MODE(s->hmac.auth_mode) | V_SCMD_HMAC_CTRL(hmac_ctrl)); crwr->sec_cpl.ivgen_hdrlen = htobe32( V_SCMD_LAST_FRAG(0) | V_SCMD_MORE_FRAGS(crp->crp_payload_length == 0 ? 1 : 0) | V_SCMD_MAC_ONLY(1)); memcpy(crwr->key_ctx.key, s->hmac.pads, kctx_len); /* XXX: F_KEY_CONTEXT_SALT_PRESENT set, but 'salt' not set. */ kctx_flits = (sizeof(struct _key_ctx) + kctx_len) / 16; crwr->key_ctx.ctx_hdr = htobe32(V_KEY_CONTEXT_CTX_LEN(kctx_flits) | V_KEY_CONTEXT_OPAD_PRESENT(use_opad) | V_KEY_CONTEXT_SALT_PRESENT(1) | V_KEY_CONTEXT_CK_SIZE(CHCR_KEYCTX_NO_KEY) | V_KEY_CONTEXT_MK_SIZE(s->hmac.mk_size) | V_KEY_CONTEXT_VALID(1)); dst = (char *)(crwr + 1) + kctx_len + DUMMY_BYTES; if (crp->crp_payload_length == 0) { dst[0] = 0x80; if (s->mode == HMAC) *(uint64_t *)(dst + axf->blocksize - sizeof(uint64_t)) = htobe64(axf->blocksize << 3); } else if (imm_len != 0) crypto_copydata(crp, crp->crp_payload_start, crp->crp_payload_length, dst); else ccr_write_ulptx_sgl(s, dst, sgl_nsegs); /* XXX: TODO backpressure */ t4_wrq_tx(sc->adapter, wr); return (0); } static int ccr_hash_done(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp, const struct cpl_fw6_pld *cpl, int error) { uint8_t hash[HASH_MAX_LEN]; if (error) return (error); if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) { crypto_copydata(crp, crp->crp_digest_start, s->hmac.hash_len, hash); if (timingsafe_bcmp((cpl + 1), hash, s->hmac.hash_len) != 0) return (EBADMSG); } else crypto_copyback(crp, crp->crp_digest_start, s->hmac.hash_len, (cpl + 1)); return (0); } static int ccr_cipher(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp) { char iv[CHCR_MAX_CRYPTO_IV_LEN]; struct chcr_wr *crwr; struct wrqe *wr; char *dst; u_int kctx_len, key_half, op_type, transhdr_len, wr_len; u_int imm_len, iv_len; int dsgl_nsegs, dsgl_len; int sgl_nsegs, sgl_len; int error; if (s->cipher.key_len == 0 || crp->crp_payload_length == 0) return (EINVAL); if (s->cipher.cipher_mode == SCMD_CIPH_MODE_AES_CBC && (crp->crp_payload_length % AES_BLOCK_LEN) != 0) return (EINVAL); /* Reject requests with too large of an input buffer. */ if (crp->crp_payload_length > MAX_REQUEST_SIZE) return (EFBIG); if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) op_type = CHCR_ENCRYPT_OP; else op_type = CHCR_DECRYPT_OP; sglist_reset(s->sg_dsgl); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = sglist_append_sglist(s->sg_dsgl, s->sg_output, crp->crp_payload_output_start, crp->crp_payload_length); else error = sglist_append_sglist(s->sg_dsgl, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); dsgl_nsegs = ccr_count_sgl(s->sg_dsgl, DSGL_SGE_MAXLEN); if (dsgl_nsegs > MAX_RX_PHYS_DSGL_SGE) return (EFBIG); dsgl_len = ccr_phys_dsgl_len(dsgl_nsegs); /* The 'key' must be 128-bit aligned. */ kctx_len = roundup2(s->cipher.key_len, 16); transhdr_len = CIPHER_TRANSHDR_SIZE(kctx_len, dsgl_len); /* For AES-XTS we send a 16-byte IV in the work request. */ if (s->cipher.cipher_mode == SCMD_CIPH_MODE_AES_XTS) iv_len = AES_BLOCK_LEN; else iv_len = s->cipher.iv_len; if (ccr_use_imm_data(transhdr_len, crp->crp_payload_length + iv_len)) { imm_len = crp->crp_payload_length; sgl_nsegs = 0; sgl_len = 0; } else { imm_len = 0; sglist_reset(s->sg_ulptx); error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); sgl_nsegs = s->sg_ulptx->sg_nseg; sgl_len = ccr_ulptx_sgl_len(sgl_nsegs); } wr_len = roundup2(transhdr_len, 16) + iv_len + roundup2(imm_len, 16) + sgl_len; if (wr_len > SGE_MAX_WR_LEN) return (EFBIG); wr = alloc_wrqe(wr_len, s->port->txq); if (wr == NULL) { counter_u64_add(sc->stats_wr_nomem, 1); return (ENOMEM); } crwr = wrtod(wr); memset(crwr, 0, wr_len); crypto_read_iv(crp, iv); /* Zero the remainder of the IV for AES-XTS. */ memset(iv + s->cipher.iv_len, 0, iv_len - s->cipher.iv_len); ccr_populate_wreq(sc, s, crwr, kctx_len, wr_len, imm_len, sgl_len, 0, crp); crwr->sec_cpl.op_ivinsrtofst = htobe32( V_CPL_TX_SEC_PDU_OPCODE(CPL_TX_SEC_PDU) | V_CPL_TX_SEC_PDU_RXCHID(s->port->rx_channel_id) | V_CPL_TX_SEC_PDU_ACKFOLLOWS(0) | V_CPL_TX_SEC_PDU_ULPTXLPBK(1) | V_CPL_TX_SEC_PDU_CPLLEN(2) | V_CPL_TX_SEC_PDU_PLACEHOLDER(0) | V_CPL_TX_SEC_PDU_IVINSRTOFST(1)); crwr->sec_cpl.pldlen = htobe32(iv_len + crp->crp_payload_length); crwr->sec_cpl.aadstart_cipherstop_hi = htobe32( V_CPL_TX_SEC_PDU_CIPHERSTART(iv_len + 1) | V_CPL_TX_SEC_PDU_CIPHERSTOP_HI(0)); crwr->sec_cpl.cipherstop_lo_authinsert = htobe32( V_CPL_TX_SEC_PDU_CIPHERSTOP_LO(0)); /* These two flits are actually a CPL_TLS_TX_SCMD_FMT. */ crwr->sec_cpl.seqno_numivs = htobe32( V_SCMD_SEQ_NO_CTRL(0) | V_SCMD_PROTO_VERSION(SCMD_PROTO_VERSION_GENERIC) | V_SCMD_ENC_DEC_CTRL(op_type) | V_SCMD_CIPH_MODE(s->cipher.cipher_mode) | V_SCMD_AUTH_MODE(SCMD_AUTH_MODE_NOP) | V_SCMD_HMAC_CTRL(SCMD_HMAC_CTRL_NOP) | V_SCMD_IV_SIZE(iv_len / 2) | V_SCMD_NUM_IVS(0)); crwr->sec_cpl.ivgen_hdrlen = htobe32( V_SCMD_IV_GEN_CTRL(0) | V_SCMD_MORE_FRAGS(0) | V_SCMD_LAST_FRAG(0) | V_SCMD_MAC_ONLY(0) | V_SCMD_AADIVDROP(1) | V_SCMD_HDR_LEN(dsgl_len)); crwr->key_ctx.ctx_hdr = s->cipher.key_ctx_hdr; switch (s->cipher.cipher_mode) { case SCMD_CIPH_MODE_AES_CBC: if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) memcpy(crwr->key_ctx.key, s->cipher.enckey, s->cipher.key_len); else memcpy(crwr->key_ctx.key, s->cipher.deckey, s->cipher.key_len); break; case SCMD_CIPH_MODE_AES_CTR: memcpy(crwr->key_ctx.key, s->cipher.enckey, s->cipher.key_len); break; case SCMD_CIPH_MODE_AES_XTS: key_half = s->cipher.key_len / 2; memcpy(crwr->key_ctx.key, s->cipher.enckey + key_half, key_half); if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) memcpy(crwr->key_ctx.key + key_half, s->cipher.enckey, key_half); else memcpy(crwr->key_ctx.key + key_half, s->cipher.deckey, key_half); break; } dst = (char *)(crwr + 1) + kctx_len; ccr_write_phys_dsgl(s, dst, dsgl_nsegs); dst += sizeof(struct cpl_rx_phys_dsgl) + dsgl_len; memcpy(dst, iv, iv_len); dst += iv_len; if (imm_len != 0) crypto_copydata(crp, crp->crp_payload_start, crp->crp_payload_length, dst); else ccr_write_ulptx_sgl(s, dst, sgl_nsegs); /* XXX: TODO backpressure */ t4_wrq_tx(sc->adapter, wr); explicit_bzero(iv, sizeof(iv)); return (0); } static int ccr_cipher_done(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp, const struct cpl_fw6_pld *cpl, int error) { /* * The updated IV to permit chained requests is at * cpl->data[2], but OCF doesn't permit chained requests. */ return (error); } /* * 'hashsize' is the length of a full digest. 'authsize' is the * requested digest length for this operation which may be less * than 'hashsize'. */ static int ccr_hmac_ctrl(unsigned int hashsize, unsigned int authsize) { if (authsize == 10) return (SCMD_HMAC_CTRL_TRUNC_RFC4366); if (authsize == 12) return (SCMD_HMAC_CTRL_IPSEC_96BIT); if (authsize == hashsize / 2) return (SCMD_HMAC_CTRL_DIV2); return (SCMD_HMAC_CTRL_NO_TRUNC); } static int ccr_eta(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp) { char iv[CHCR_MAX_CRYPTO_IV_LEN]; struct chcr_wr *crwr; struct wrqe *wr; const struct auth_hash *axf; char *dst; u_int kctx_len, key_half, op_type, transhdr_len, wr_len; u_int hash_size_in_response, imm_len, iopad_size, iv_len; u_int aad_start, aad_stop; u_int auth_insert; u_int cipher_start, cipher_stop; u_int hmac_ctrl, input_len; int dsgl_nsegs, dsgl_len; int sgl_nsegs, sgl_len; int error; /* * If there is a need in the future, requests with an empty * payload could be supported as HMAC-only requests. */ if (s->cipher.key_len == 0 || crp->crp_payload_length == 0) return (EINVAL); if (s->cipher.cipher_mode == SCMD_CIPH_MODE_AES_CBC && (crp->crp_payload_length % AES_BLOCK_LEN) != 0) return (EINVAL); /* For AES-XTS we send a 16-byte IV in the work request. */ if (s->cipher.cipher_mode == SCMD_CIPH_MODE_AES_XTS) iv_len = AES_BLOCK_LEN; else iv_len = s->cipher.iv_len; if (crp->crp_aad_length + iv_len > MAX_AAD_LEN) return (EINVAL); axf = s->hmac.auth_hash; hash_size_in_response = s->hmac.hash_len; if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) op_type = CHCR_ENCRYPT_OP; else op_type = CHCR_DECRYPT_OP; /* * The output buffer consists of the cipher text followed by * the hash when encrypting. For decryption it only contains * the plain text. * * Due to a firmware bug, the output buffer must include a * dummy output buffer for the IV and AAD prior to the real * output buffer. */ if (op_type == CHCR_ENCRYPT_OP) { if (iv_len + crp->crp_aad_length + crp->crp_payload_length + hash_size_in_response > MAX_REQUEST_SIZE) return (EFBIG); } else { if (iv_len + crp->crp_aad_length + crp->crp_payload_length > MAX_REQUEST_SIZE) return (EFBIG); } sglist_reset(s->sg_dsgl); error = sglist_append_sglist(s->sg_dsgl, sc->sg_iv_aad, 0, iv_len + crp->crp_aad_length); if (error) return (error); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = sglist_append_sglist(s->sg_dsgl, s->sg_output, crp->crp_payload_output_start, crp->crp_payload_length); else error = sglist_append_sglist(s->sg_dsgl, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); if (op_type == CHCR_ENCRYPT_OP) { if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = sglist_append_sglist(s->sg_dsgl, s->sg_output, crp->crp_digest_start, hash_size_in_response); else error = sglist_append_sglist(s->sg_dsgl, s->sg_input, crp->crp_digest_start, hash_size_in_response); if (error) return (error); } dsgl_nsegs = ccr_count_sgl(s->sg_dsgl, DSGL_SGE_MAXLEN); if (dsgl_nsegs > MAX_RX_PHYS_DSGL_SGE) return (EFBIG); dsgl_len = ccr_phys_dsgl_len(dsgl_nsegs); /* PADs must be 128-bit aligned. */ iopad_size = roundup2(s->hmac.partial_digest_len, 16); /* * The 'key' part of the key context consists of the key followed * by the IPAD and OPAD. */ kctx_len = roundup2(s->cipher.key_len, 16) + iopad_size * 2; transhdr_len = CIPHER_TRANSHDR_SIZE(kctx_len, dsgl_len); /* * The input buffer consists of the IV, any AAD, and then the * cipher/plain text. For decryption requests the hash is * appended after the cipher text. * * The IV is always stored at the start of the input buffer * even though it may be duplicated in the payload. The * crypto engine doesn't work properly if the IV offset points * inside of the AAD region, so a second copy is always * required. */ input_len = crp->crp_aad_length + crp->crp_payload_length; /* * The firmware hangs if sent a request which is a * bit smaller than MAX_REQUEST_SIZE. In particular, the * firmware appears to require 512 - 16 bytes of spare room * along with the size of the hash even if the hash isn't * included in the input buffer. */ if (input_len + roundup2(axf->hashsize, 16) + (512 - 16) > MAX_REQUEST_SIZE) return (EFBIG); if (op_type == CHCR_DECRYPT_OP) input_len += hash_size_in_response; if (ccr_use_imm_data(transhdr_len, iv_len + input_len)) { imm_len = input_len; sgl_nsegs = 0; sgl_len = 0; } else { imm_len = 0; sglist_reset(s->sg_ulptx); if (crp->crp_aad_length != 0) { if (crp->crp_aad != NULL) error = sglist_append(s->sg_ulptx, crp->crp_aad, crp->crp_aad_length); else error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_aad_start, crp->crp_aad_length); if (error) return (error); } error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); if (op_type == CHCR_DECRYPT_OP) { error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_digest_start, hash_size_in_response); if (error) return (error); } sgl_nsegs = s->sg_ulptx->sg_nseg; sgl_len = ccr_ulptx_sgl_len(sgl_nsegs); } /* Any AAD comes after the IV. */ if (crp->crp_aad_length != 0) { aad_start = iv_len + 1; aad_stop = aad_start + crp->crp_aad_length - 1; } else { aad_start = 0; aad_stop = 0; } cipher_start = iv_len + crp->crp_aad_length + 1; if (op_type == CHCR_DECRYPT_OP) cipher_stop = hash_size_in_response; else cipher_stop = 0; if (op_type == CHCR_DECRYPT_OP) auth_insert = hash_size_in_response; else auth_insert = 0; wr_len = roundup2(transhdr_len, 16) + iv_len + roundup2(imm_len, 16) + sgl_len; if (wr_len > SGE_MAX_WR_LEN) return (EFBIG); wr = alloc_wrqe(wr_len, s->port->txq); if (wr == NULL) { counter_u64_add(sc->stats_wr_nomem, 1); return (ENOMEM); } crwr = wrtod(wr); memset(crwr, 0, wr_len); crypto_read_iv(crp, iv); /* Zero the remainder of the IV for AES-XTS. */ memset(iv + s->cipher.iv_len, 0, iv_len - s->cipher.iv_len); ccr_populate_wreq(sc, s, crwr, kctx_len, wr_len, imm_len, sgl_len, op_type == CHCR_DECRYPT_OP ? hash_size_in_response : 0, crp); crwr->sec_cpl.op_ivinsrtofst = htobe32( V_CPL_TX_SEC_PDU_OPCODE(CPL_TX_SEC_PDU) | V_CPL_TX_SEC_PDU_RXCHID(s->port->rx_channel_id) | V_CPL_TX_SEC_PDU_ACKFOLLOWS(0) | V_CPL_TX_SEC_PDU_ULPTXLPBK(1) | V_CPL_TX_SEC_PDU_CPLLEN(2) | V_CPL_TX_SEC_PDU_PLACEHOLDER(0) | V_CPL_TX_SEC_PDU_IVINSRTOFST(1)); crwr->sec_cpl.pldlen = htobe32(iv_len + input_len); crwr->sec_cpl.aadstart_cipherstop_hi = htobe32( V_CPL_TX_SEC_PDU_AADSTART(aad_start) | V_CPL_TX_SEC_PDU_AADSTOP(aad_stop) | V_CPL_TX_SEC_PDU_CIPHERSTART(cipher_start) | V_CPL_TX_SEC_PDU_CIPHERSTOP_HI(cipher_stop >> 4)); crwr->sec_cpl.cipherstop_lo_authinsert = htobe32( V_CPL_TX_SEC_PDU_CIPHERSTOP_LO(cipher_stop & 0xf) | V_CPL_TX_SEC_PDU_AUTHSTART(cipher_start) | V_CPL_TX_SEC_PDU_AUTHSTOP(cipher_stop) | V_CPL_TX_SEC_PDU_AUTHINSERT(auth_insert)); /* These two flits are actually a CPL_TLS_TX_SCMD_FMT. */ hmac_ctrl = ccr_hmac_ctrl(axf->hashsize, hash_size_in_response); crwr->sec_cpl.seqno_numivs = htobe32( V_SCMD_SEQ_NO_CTRL(0) | V_SCMD_PROTO_VERSION(SCMD_PROTO_VERSION_GENERIC) | V_SCMD_ENC_DEC_CTRL(op_type) | V_SCMD_CIPH_AUTH_SEQ_CTRL(op_type == CHCR_ENCRYPT_OP ? 1 : 0) | V_SCMD_CIPH_MODE(s->cipher.cipher_mode) | V_SCMD_AUTH_MODE(s->hmac.auth_mode) | V_SCMD_HMAC_CTRL(hmac_ctrl) | V_SCMD_IV_SIZE(iv_len / 2) | V_SCMD_NUM_IVS(0)); crwr->sec_cpl.ivgen_hdrlen = htobe32( V_SCMD_IV_GEN_CTRL(0) | V_SCMD_MORE_FRAGS(0) | V_SCMD_LAST_FRAG(0) | V_SCMD_MAC_ONLY(0) | V_SCMD_AADIVDROP(0) | V_SCMD_HDR_LEN(dsgl_len)); crwr->key_ctx.ctx_hdr = s->cipher.key_ctx_hdr; switch (s->cipher.cipher_mode) { case SCMD_CIPH_MODE_AES_CBC: if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) memcpy(crwr->key_ctx.key, s->cipher.enckey, s->cipher.key_len); else memcpy(crwr->key_ctx.key, s->cipher.deckey, s->cipher.key_len); break; case SCMD_CIPH_MODE_AES_CTR: memcpy(crwr->key_ctx.key, s->cipher.enckey, s->cipher.key_len); break; case SCMD_CIPH_MODE_AES_XTS: key_half = s->cipher.key_len / 2; memcpy(crwr->key_ctx.key, s->cipher.enckey + key_half, key_half); if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) memcpy(crwr->key_ctx.key + key_half, s->cipher.enckey, key_half); else memcpy(crwr->key_ctx.key + key_half, s->cipher.deckey, key_half); break; } dst = crwr->key_ctx.key + roundup2(s->cipher.key_len, 16); memcpy(dst, s->hmac.pads, iopad_size * 2); dst = (char *)(crwr + 1) + kctx_len; ccr_write_phys_dsgl(s, dst, dsgl_nsegs); dst += sizeof(struct cpl_rx_phys_dsgl) + dsgl_len; memcpy(dst, iv, iv_len); dst += iv_len; if (imm_len != 0) { if (crp->crp_aad_length != 0) { if (crp->crp_aad != NULL) memcpy(dst, crp->crp_aad, crp->crp_aad_length); else crypto_copydata(crp, crp->crp_aad_start, crp->crp_aad_length, dst); dst += crp->crp_aad_length; } crypto_copydata(crp, crp->crp_payload_start, crp->crp_payload_length, dst); dst += crp->crp_payload_length; if (op_type == CHCR_DECRYPT_OP) crypto_copydata(crp, crp->crp_digest_start, hash_size_in_response, dst); } else ccr_write_ulptx_sgl(s, dst, sgl_nsegs); /* XXX: TODO backpressure */ t4_wrq_tx(sc->adapter, wr); explicit_bzero(iv, sizeof(iv)); return (0); } static int ccr_eta_done(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp, const struct cpl_fw6_pld *cpl, int error) { /* * The updated IV to permit chained requests is at * cpl->data[2], but OCF doesn't permit chained requests. */ return (error); } static int ccr_gcm(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp) { char iv[CHCR_MAX_CRYPTO_IV_LEN]; struct chcr_wr *crwr; struct wrqe *wr; char *dst; u_int iv_len, kctx_len, op_type, transhdr_len, wr_len; u_int hash_size_in_response, imm_len; u_int aad_start, aad_stop, cipher_start, cipher_stop, auth_insert; u_int hmac_ctrl, input_len; int dsgl_nsegs, dsgl_len; int sgl_nsegs, sgl_len; int error; if (s->cipher.key_len == 0) return (EINVAL); /* * The crypto engine doesn't handle GCM requests with an empty * payload, so handle those in software instead. */ if (crp->crp_payload_length == 0) return (EMSGSIZE); if (crp->crp_aad_length + AES_BLOCK_LEN > MAX_AAD_LEN) return (EMSGSIZE); hash_size_in_response = s->gmac.hash_len; if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) op_type = CHCR_ENCRYPT_OP; else op_type = CHCR_DECRYPT_OP; iv_len = AES_BLOCK_LEN; /* * GCM requests should always provide an explicit IV. */ if ((crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0) return (EINVAL); /* * The output buffer consists of the cipher text followed by * the tag when encrypting. For decryption it only contains * the plain text. * * Due to a firmware bug, the output buffer must include a * dummy output buffer for the IV and AAD prior to the real * output buffer. */ if (op_type == CHCR_ENCRYPT_OP) { if (iv_len + crp->crp_aad_length + crp->crp_payload_length + hash_size_in_response > MAX_REQUEST_SIZE) return (EFBIG); } else { if (iv_len + crp->crp_aad_length + crp->crp_payload_length > MAX_REQUEST_SIZE) return (EFBIG); } sglist_reset(s->sg_dsgl); error = sglist_append_sglist(s->sg_dsgl, sc->sg_iv_aad, 0, iv_len + crp->crp_aad_length); if (error) return (error); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = sglist_append_sglist(s->sg_dsgl, s->sg_output, crp->crp_payload_output_start, crp->crp_payload_length); else error = sglist_append_sglist(s->sg_dsgl, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); if (op_type == CHCR_ENCRYPT_OP) { if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = sglist_append_sglist(s->sg_dsgl, s->sg_output, crp->crp_digest_start, hash_size_in_response); else error = sglist_append_sglist(s->sg_dsgl, s->sg_input, crp->crp_digest_start, hash_size_in_response); if (error) return (error); } dsgl_nsegs = ccr_count_sgl(s->sg_dsgl, DSGL_SGE_MAXLEN); if (dsgl_nsegs > MAX_RX_PHYS_DSGL_SGE) return (EFBIG); dsgl_len = ccr_phys_dsgl_len(dsgl_nsegs); /* * The 'key' part of the key context consists of the key followed * by the Galois hash key. */ kctx_len = roundup2(s->cipher.key_len, 16) + GMAC_BLOCK_LEN; transhdr_len = CIPHER_TRANSHDR_SIZE(kctx_len, dsgl_len); /* * The input buffer consists of the IV, any AAD, and then the * cipher/plain text. For decryption requests the hash is * appended after the cipher text. * * The IV is always stored at the start of the input buffer * even though it may be duplicated in the payload. The * crypto engine doesn't work properly if the IV offset points * inside of the AAD region, so a second copy is always * required. */ input_len = crp->crp_aad_length + crp->crp_payload_length; if (op_type == CHCR_DECRYPT_OP) input_len += hash_size_in_response; if (input_len > MAX_REQUEST_SIZE) return (EFBIG); if (ccr_use_imm_data(transhdr_len, iv_len + input_len)) { imm_len = input_len; sgl_nsegs = 0; sgl_len = 0; } else { imm_len = 0; sglist_reset(s->sg_ulptx); if (crp->crp_aad_length != 0) { if (crp->crp_aad != NULL) error = sglist_append(s->sg_ulptx, crp->crp_aad, crp->crp_aad_length); else error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_aad_start, crp->crp_aad_length); if (error) return (error); } error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); if (op_type == CHCR_DECRYPT_OP) { error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_digest_start, hash_size_in_response); if (error) return (error); } sgl_nsegs = s->sg_ulptx->sg_nseg; sgl_len = ccr_ulptx_sgl_len(sgl_nsegs); } if (crp->crp_aad_length != 0) { aad_start = iv_len + 1; aad_stop = aad_start + crp->crp_aad_length - 1; } else { aad_start = 0; aad_stop = 0; } cipher_start = iv_len + crp->crp_aad_length + 1; if (op_type == CHCR_DECRYPT_OP) cipher_stop = hash_size_in_response; else cipher_stop = 0; if (op_type == CHCR_DECRYPT_OP) auth_insert = hash_size_in_response; else auth_insert = 0; wr_len = roundup2(transhdr_len, 16) + iv_len + roundup2(imm_len, 16) + sgl_len; if (wr_len > SGE_MAX_WR_LEN) return (EFBIG); wr = alloc_wrqe(wr_len, s->port->txq); if (wr == NULL) { counter_u64_add(sc->stats_wr_nomem, 1); return (ENOMEM); } crwr = wrtod(wr); memset(crwr, 0, wr_len); crypto_read_iv(crp, iv); *(uint32_t *)&iv[12] = htobe32(1); ccr_populate_wreq(sc, s, crwr, kctx_len, wr_len, imm_len, sgl_len, 0, crp); crwr->sec_cpl.op_ivinsrtofst = htobe32( V_CPL_TX_SEC_PDU_OPCODE(CPL_TX_SEC_PDU) | V_CPL_TX_SEC_PDU_RXCHID(s->port->rx_channel_id) | V_CPL_TX_SEC_PDU_ACKFOLLOWS(0) | V_CPL_TX_SEC_PDU_ULPTXLPBK(1) | V_CPL_TX_SEC_PDU_CPLLEN(2) | V_CPL_TX_SEC_PDU_PLACEHOLDER(0) | V_CPL_TX_SEC_PDU_IVINSRTOFST(1)); crwr->sec_cpl.pldlen = htobe32(iv_len + input_len); /* * NB: cipherstop is explicitly set to 0. On encrypt it * should normally be set to 0 anyway. However, for decrypt * the cipher ends before the tag in the ETA case (and * authstop is set to stop before the tag), but for GCM the * cipher still runs to the end of the buffer. Not sure if * this is intentional or a firmware quirk, but it is required * for working tag validation with GCM decryption. */ crwr->sec_cpl.aadstart_cipherstop_hi = htobe32( V_CPL_TX_SEC_PDU_AADSTART(aad_start) | V_CPL_TX_SEC_PDU_AADSTOP(aad_stop) | V_CPL_TX_SEC_PDU_CIPHERSTART(cipher_start) | V_CPL_TX_SEC_PDU_CIPHERSTOP_HI(0)); crwr->sec_cpl.cipherstop_lo_authinsert = htobe32( V_CPL_TX_SEC_PDU_CIPHERSTOP_LO(0) | V_CPL_TX_SEC_PDU_AUTHSTART(cipher_start) | V_CPL_TX_SEC_PDU_AUTHSTOP(cipher_stop) | V_CPL_TX_SEC_PDU_AUTHINSERT(auth_insert)); /* These two flits are actually a CPL_TLS_TX_SCMD_FMT. */ hmac_ctrl = ccr_hmac_ctrl(AES_GMAC_HASH_LEN, hash_size_in_response); crwr->sec_cpl.seqno_numivs = htobe32( V_SCMD_SEQ_NO_CTRL(0) | V_SCMD_PROTO_VERSION(SCMD_PROTO_VERSION_GENERIC) | V_SCMD_ENC_DEC_CTRL(op_type) | V_SCMD_CIPH_AUTH_SEQ_CTRL(op_type == CHCR_ENCRYPT_OP ? 1 : 0) | V_SCMD_CIPH_MODE(SCMD_CIPH_MODE_AES_GCM) | V_SCMD_AUTH_MODE(SCMD_AUTH_MODE_GHASH) | V_SCMD_HMAC_CTRL(hmac_ctrl) | V_SCMD_IV_SIZE(iv_len / 2) | V_SCMD_NUM_IVS(0)); crwr->sec_cpl.ivgen_hdrlen = htobe32( V_SCMD_IV_GEN_CTRL(0) | V_SCMD_MORE_FRAGS(0) | V_SCMD_LAST_FRAG(0) | V_SCMD_MAC_ONLY(0) | V_SCMD_AADIVDROP(0) | V_SCMD_HDR_LEN(dsgl_len)); crwr->key_ctx.ctx_hdr = s->cipher.key_ctx_hdr; memcpy(crwr->key_ctx.key, s->cipher.enckey, s->cipher.key_len); dst = crwr->key_ctx.key + roundup2(s->cipher.key_len, 16); memcpy(dst, s->gmac.ghash_h, GMAC_BLOCK_LEN); dst = (char *)(crwr + 1) + kctx_len; ccr_write_phys_dsgl(s, dst, dsgl_nsegs); dst += sizeof(struct cpl_rx_phys_dsgl) + dsgl_len; memcpy(dst, iv, iv_len); dst += iv_len; if (imm_len != 0) { if (crp->crp_aad_length != 0) { if (crp->crp_aad != NULL) memcpy(dst, crp->crp_aad, crp->crp_aad_length); else crypto_copydata(crp, crp->crp_aad_start, crp->crp_aad_length, dst); dst += crp->crp_aad_length; } crypto_copydata(crp, crp->crp_payload_start, crp->crp_payload_length, dst); dst += crp->crp_payload_length; if (op_type == CHCR_DECRYPT_OP) crypto_copydata(crp, crp->crp_digest_start, hash_size_in_response, dst); } else ccr_write_ulptx_sgl(s, dst, sgl_nsegs); /* XXX: TODO backpressure */ t4_wrq_tx(sc->adapter, wr); explicit_bzero(iv, sizeof(iv)); return (0); } static int ccr_gcm_done(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp, const struct cpl_fw6_pld *cpl, int error) { /* * The updated IV to permit chained requests is at * cpl->data[2], but OCF doesn't permit chained requests. * * Note that the hardware should always verify the GMAC hash. */ return (error); } static int ccr_ccm_hmac_ctrl(unsigned int authsize) { switch (authsize) { case 4: return (SCMD_HMAC_CTRL_PL1); case 6: return (SCMD_HMAC_CTRL_PL2); case 8: return (SCMD_HMAC_CTRL_DIV2); case 10: return (SCMD_HMAC_CTRL_TRUNC_RFC4366); case 12: return (SCMD_HMAC_CTRL_IPSEC_96BIT); case 14: return (SCMD_HMAC_CTRL_PL3); case 16: return (SCMD_HMAC_CTRL_NO_TRUNC); default: __assert_unreachable(); } } static void generate_ccm_b0(struct cryptop *crp, u_int hash_size_in_response, const char *iv, char *b0) { u_int i, payload_len, L; /* NB: L is already set in the first byte of the IV. */ memcpy(b0, iv, CCM_B0_SIZE); L = iv[0] + 1; /* Set length of hash in bits 3 - 5. */ b0[0] |= (((hash_size_in_response - 2) / 2) << 3); /* Store the payload length as a big-endian value. */ payload_len = crp->crp_payload_length; for (i = 0; i < L; i++) { b0[CCM_CBC_BLOCK_LEN - 1 - i] = payload_len; payload_len >>= 8; } /* * If there is AAD in the request, set bit 6 in the flags * field and store the AAD length as a big-endian value at the * start of block 1. This only assumes a 16-bit AAD length * since T6 doesn't support large AAD sizes. */ if (crp->crp_aad_length != 0) { b0[0] |= (1 << 6); *(uint16_t *)(b0 + CCM_B0_SIZE) = htobe16(crp->crp_aad_length); } } static int ccr_ccm(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp) { char iv[CHCR_MAX_CRYPTO_IV_LEN]; const struct crypto_session_params *csp; struct ulptx_idata *idata; struct chcr_wr *crwr; struct wrqe *wr; char *dst; u_int iv_len, kctx_len, op_type, transhdr_len, wr_len; u_int aad_len, b0_len, hash_size_in_response, imm_len; u_int aad_start, aad_stop, cipher_start, cipher_stop, auth_insert; u_int hmac_ctrl, input_len; int dsgl_nsegs, dsgl_len; int sgl_nsegs, sgl_len; int error; csp = crypto_get_params(crp->crp_session); if (s->cipher.key_len == 0) return (EINVAL); /* * The crypto engine doesn't handle CCM requests with an empty * payload, so handle those in software instead. */ if (crp->crp_payload_length == 0) return (EMSGSIZE); /* The length has to fit within the length field in block 0. */ if (crp->crp_payload_length > ccm_max_payload_length(csp)) return (EMSGSIZE); /* * CCM always includes block 0 in the AAD before AAD from the * request. */ b0_len = CCM_B0_SIZE; if (crp->crp_aad_length != 0) b0_len += CCM_AAD_FIELD_SIZE; aad_len = b0_len + crp->crp_aad_length; /* * CCM requests should always provide an explicit IV (really * the nonce). */ if ((crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0) return (EINVAL); /* * The IV in the work request is 16 bytes and not just the * nonce. */ iv_len = AES_BLOCK_LEN; if (iv_len + aad_len > MAX_AAD_LEN) return (EMSGSIZE); hash_size_in_response = s->ccm_mac.hash_len; if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) op_type = CHCR_ENCRYPT_OP; else op_type = CHCR_DECRYPT_OP; /* * The output buffer consists of the cipher text followed by * the tag when encrypting. For decryption it only contains * the plain text. * * Due to a firmware bug, the output buffer must include a * dummy output buffer for the IV and AAD prior to the real * output buffer. */ if (op_type == CHCR_ENCRYPT_OP) { if (iv_len + aad_len + crp->crp_payload_length + hash_size_in_response > MAX_REQUEST_SIZE) return (EFBIG); } else { if (iv_len + aad_len + crp->crp_payload_length > MAX_REQUEST_SIZE) return (EFBIG); } sglist_reset(s->sg_dsgl); error = sglist_append_sglist(s->sg_dsgl, sc->sg_iv_aad, 0, iv_len + aad_len); if (error) return (error); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = sglist_append_sglist(s->sg_dsgl, s->sg_output, crp->crp_payload_output_start, crp->crp_payload_length); else error = sglist_append_sglist(s->sg_dsgl, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); if (op_type == CHCR_ENCRYPT_OP) { if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = sglist_append_sglist(s->sg_dsgl, s->sg_output, crp->crp_digest_start, hash_size_in_response); else error = sglist_append_sglist(s->sg_dsgl, s->sg_input, crp->crp_digest_start, hash_size_in_response); if (error) return (error); } dsgl_nsegs = ccr_count_sgl(s->sg_dsgl, DSGL_SGE_MAXLEN); if (dsgl_nsegs > MAX_RX_PHYS_DSGL_SGE) return (EFBIG); dsgl_len = ccr_phys_dsgl_len(dsgl_nsegs); /* * The 'key' part of the key context consists of two copies of * the AES key. */ kctx_len = roundup2(s->cipher.key_len, 16) * 2; transhdr_len = CIPHER_TRANSHDR_SIZE(kctx_len, dsgl_len); /* * The input buffer consists of the IV, AAD (including block * 0), and then the cipher/plain text. For decryption * requests the hash is appended after the cipher text. * * The IV is always stored at the start of the input buffer * even though it may be duplicated in the payload. The * crypto engine doesn't work properly if the IV offset points * inside of the AAD region, so a second copy is always * required. */ input_len = aad_len + crp->crp_payload_length; if (op_type == CHCR_DECRYPT_OP) input_len += hash_size_in_response; if (input_len > MAX_REQUEST_SIZE) return (EFBIG); if (ccr_use_imm_data(transhdr_len, iv_len + input_len)) { imm_len = input_len; sgl_nsegs = 0; sgl_len = 0; } else { /* Block 0 is passed as immediate data. */ imm_len = b0_len; sglist_reset(s->sg_ulptx); if (crp->crp_aad_length != 0) { if (crp->crp_aad != NULL) error = sglist_append(s->sg_ulptx, crp->crp_aad, crp->crp_aad_length); else error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_aad_start, crp->crp_aad_length); if (error) return (error); } error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_payload_start, crp->crp_payload_length); if (error) return (error); if (op_type == CHCR_DECRYPT_OP) { error = sglist_append_sglist(s->sg_ulptx, s->sg_input, crp->crp_digest_start, hash_size_in_response); if (error) return (error); } sgl_nsegs = s->sg_ulptx->sg_nseg; sgl_len = ccr_ulptx_sgl_len(sgl_nsegs); } aad_start = iv_len + 1; aad_stop = aad_start + aad_len - 1; cipher_start = aad_stop + 1; if (op_type == CHCR_DECRYPT_OP) cipher_stop = hash_size_in_response; else cipher_stop = 0; if (op_type == CHCR_DECRYPT_OP) auth_insert = hash_size_in_response; else auth_insert = 0; wr_len = roundup2(transhdr_len, 16) + iv_len + roundup2(imm_len, 16) + sgl_len; if (wr_len > SGE_MAX_WR_LEN) return (EFBIG); wr = alloc_wrqe(wr_len, s->port->txq); if (wr == NULL) { counter_u64_add(sc->stats_wr_nomem, 1); return (ENOMEM); } crwr = wrtod(wr); memset(crwr, 0, wr_len); /* * Read the nonce from the request. Use the nonce to generate * the full IV with the counter set to 0. */ memset(iv, 0, iv_len); iv[0] = (15 - csp->csp_ivlen) - 1; crypto_read_iv(crp, iv + 1); ccr_populate_wreq(sc, s, crwr, kctx_len, wr_len, imm_len, sgl_len, 0, crp); crwr->sec_cpl.op_ivinsrtofst = htobe32( V_CPL_TX_SEC_PDU_OPCODE(CPL_TX_SEC_PDU) | V_CPL_TX_SEC_PDU_RXCHID(s->port->rx_channel_id) | V_CPL_TX_SEC_PDU_ACKFOLLOWS(0) | V_CPL_TX_SEC_PDU_ULPTXLPBK(1) | V_CPL_TX_SEC_PDU_CPLLEN(2) | V_CPL_TX_SEC_PDU_PLACEHOLDER(0) | V_CPL_TX_SEC_PDU_IVINSRTOFST(1)); crwr->sec_cpl.pldlen = htobe32(iv_len + input_len); /* * NB: cipherstop is explicitly set to 0. See comments above * in ccr_gcm(). */ crwr->sec_cpl.aadstart_cipherstop_hi = htobe32( V_CPL_TX_SEC_PDU_AADSTART(aad_start) | V_CPL_TX_SEC_PDU_AADSTOP(aad_stop) | V_CPL_TX_SEC_PDU_CIPHERSTART(cipher_start) | V_CPL_TX_SEC_PDU_CIPHERSTOP_HI(0)); crwr->sec_cpl.cipherstop_lo_authinsert = htobe32( V_CPL_TX_SEC_PDU_CIPHERSTOP_LO(0) | V_CPL_TX_SEC_PDU_AUTHSTART(cipher_start) | V_CPL_TX_SEC_PDU_AUTHSTOP(cipher_stop) | V_CPL_TX_SEC_PDU_AUTHINSERT(auth_insert)); /* These two flits are actually a CPL_TLS_TX_SCMD_FMT. */ hmac_ctrl = ccr_ccm_hmac_ctrl(hash_size_in_response); crwr->sec_cpl.seqno_numivs = htobe32( V_SCMD_SEQ_NO_CTRL(0) | V_SCMD_PROTO_VERSION(SCMD_PROTO_VERSION_GENERIC) | V_SCMD_ENC_DEC_CTRL(op_type) | V_SCMD_CIPH_AUTH_SEQ_CTRL(op_type == CHCR_ENCRYPT_OP ? 0 : 1) | V_SCMD_CIPH_MODE(SCMD_CIPH_MODE_AES_CCM) | V_SCMD_AUTH_MODE(SCMD_AUTH_MODE_CBCMAC) | V_SCMD_HMAC_CTRL(hmac_ctrl) | V_SCMD_IV_SIZE(iv_len / 2) | V_SCMD_NUM_IVS(0)); crwr->sec_cpl.ivgen_hdrlen = htobe32( V_SCMD_IV_GEN_CTRL(0) | V_SCMD_MORE_FRAGS(0) | V_SCMD_LAST_FRAG(0) | V_SCMD_MAC_ONLY(0) | V_SCMD_AADIVDROP(0) | V_SCMD_HDR_LEN(dsgl_len)); crwr->key_ctx.ctx_hdr = s->cipher.key_ctx_hdr; memcpy(crwr->key_ctx.key, s->cipher.enckey, s->cipher.key_len); memcpy(crwr->key_ctx.key + roundup(s->cipher.key_len, 16), s->cipher.enckey, s->cipher.key_len); dst = (char *)(crwr + 1) + kctx_len; ccr_write_phys_dsgl(s, dst, dsgl_nsegs); dst += sizeof(struct cpl_rx_phys_dsgl) + dsgl_len; memcpy(dst, iv, iv_len); dst += iv_len; generate_ccm_b0(crp, hash_size_in_response, iv, dst); if (sgl_nsegs == 0) { dst += b0_len; if (crp->crp_aad_length != 0) { if (crp->crp_aad != NULL) memcpy(dst, crp->crp_aad, crp->crp_aad_length); else crypto_copydata(crp, crp->crp_aad_start, crp->crp_aad_length, dst); dst += crp->crp_aad_length; } crypto_copydata(crp, crp->crp_payload_start, crp->crp_payload_length, dst); dst += crp->crp_payload_length; if (op_type == CHCR_DECRYPT_OP) crypto_copydata(crp, crp->crp_digest_start, hash_size_in_response, dst); } else { dst += CCM_B0_SIZE; if (b0_len > CCM_B0_SIZE) { /* * If there is AAD, insert padding including a * ULP_TX_SC_NOOP so that the ULP_TX_SC_DSGL * is 16-byte aligned. */ KASSERT(b0_len - CCM_B0_SIZE == CCM_AAD_FIELD_SIZE, ("b0_len mismatch")); memset(dst + CCM_AAD_FIELD_SIZE, 0, 8 - CCM_AAD_FIELD_SIZE); idata = (void *)(dst + 8); idata->cmd_more = htobe32(V_ULPTX_CMD(ULP_TX_SC_NOOP)); idata->len = htobe32(0); dst = (void *)(idata + 1); } ccr_write_ulptx_sgl(s, dst, sgl_nsegs); } /* XXX: TODO backpressure */ t4_wrq_tx(sc->adapter, wr); explicit_bzero(iv, sizeof(iv)); return (0); } static int ccr_ccm_done(struct ccr_softc *sc, struct ccr_session *s, struct cryptop *crp, const struct cpl_fw6_pld *cpl, int error) { /* * The updated IV to permit chained requests is at * cpl->data[2], but OCF doesn't permit chained requests. * * Note that the hardware should always verify the CBC MAC * hash. */ return (error); } /* * Use the software session for requests not supported by the crypto * engine (e.g. CCM and GCM requests with an empty payload). */ static int ccr_soft_done(struct cryptop *crp) { struct cryptop *orig; orig = crp->crp_opaque; orig->crp_etype = crp->crp_etype; crypto_freereq(crp); crypto_done(orig); return (0); } static void ccr_soft(struct ccr_session *s, struct cryptop *crp) { struct cryptop *new; int error; new = crypto_clonereq(crp, s->sw_session, M_NOWAIT); if (new == NULL) { crp->crp_etype = ENOMEM; crypto_done(crp); return; } /* * XXX: This only really needs CRYPTO_ASYNC_ORDERED if the * original request was dispatched that way. There is no way * to know that though since crypto_dispatch_async() discards * the flag for async backends (such as ccr(4)). */ new->crp_opaque = crp; new->crp_callback = ccr_soft_done; error = crypto_dispatch_async(new, CRYPTO_ASYNC_ORDERED); if (error != 0) { crp->crp_etype = error; crypto_done(crp); } } static void ccr_identify(driver_t *driver, device_t parent) { struct adapter *sc; sc = device_get_softc(parent); if (sc->cryptocaps & FW_CAPS_CONFIG_CRYPTO_LOOKASIDE && - device_find_child(parent, "ccr", -1) == NULL) + device_find_child(parent, "ccr", DEVICE_UNIT_ANY) == NULL) device_add_child(parent, "ccr", DEVICE_UNIT_ANY); } static int ccr_probe(device_t dev) { device_set_desc(dev, "Chelsio Crypto Accelerator"); return (BUS_PROBE_DEFAULT); } static void ccr_sysctls(struct ccr_softc *sc) { struct sysctl_ctx_list *ctx = &sc->ctx; struct sysctl_oid *oid, *port_oid; struct sysctl_oid_list *children; char buf[16]; int i; /* * dev.ccr.X. */ oid = device_get_sysctl_tree(sc->dev); children = SYSCTL_CHILDREN(oid); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "port_mask", CTLFLAG_RW, &sc->port_mask, 0, "Mask of enabled ports"); /* * dev.ccr.X.stats. */ oid = SYSCTL_ADD_NODE(ctx, children, OID_AUTO, "stats", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "statistics"); children = SYSCTL_CHILDREN(oid); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "hash", CTLFLAG_RD, &sc->stats_hash, "Hash requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "hmac", CTLFLAG_RD, &sc->stats_hmac, "HMAC requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "cipher_encrypt", CTLFLAG_RD, &sc->stats_cipher_encrypt, "Cipher encryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "cipher_decrypt", CTLFLAG_RD, &sc->stats_cipher_decrypt, "Cipher decryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "eta_encrypt", CTLFLAG_RD, &sc->stats_eta_encrypt, "Combined AES+HMAC encryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "eta_decrypt", CTLFLAG_RD, &sc->stats_eta_decrypt, "Combined AES+HMAC decryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "gcm_encrypt", CTLFLAG_RD, &sc->stats_gcm_encrypt, "AES-GCM encryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "gcm_decrypt", CTLFLAG_RD, &sc->stats_gcm_decrypt, "AES-GCM decryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "ccm_encrypt", CTLFLAG_RD, &sc->stats_ccm_encrypt, "AES-CCM encryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "ccm_decrypt", CTLFLAG_RD, &sc->stats_ccm_decrypt, "AES-CCM decryption requests submitted"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "wr_nomem", CTLFLAG_RD, &sc->stats_wr_nomem, "Work request memory allocation failures"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "inflight", CTLFLAG_RD, &sc->stats_inflight, "Requests currently pending"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "mac_error", CTLFLAG_RD, &sc->stats_mac_error, "MAC errors"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "pad_error", CTLFLAG_RD, &sc->stats_pad_error, "Padding errors"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "sglist_error", CTLFLAG_RD, &sc->stats_sglist_error, "Requests for which DMA mapping failed"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "process_error", CTLFLAG_RD, &sc->stats_process_error, "Requests failed during queueing"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "sw_fallback", CTLFLAG_RD, &sc->stats_sw_fallback, "Requests processed by falling back to software"); /* * dev.ccr.X.stats.port */ port_oid = SYSCTL_ADD_NODE(ctx, children, OID_AUTO, "port", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Per-port statistics"); for (i = 0; i < nitems(sc->ports); i++) { if (sc->ports[i].rxq == NULL) continue; /* * dev.ccr.X.stats.port.Y */ snprintf(buf, sizeof(buf), "%d", i); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(port_oid), OID_AUTO, buf, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, buf); children = SYSCTL_CHILDREN(oid); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "active_sessions", CTLFLAG_RD, &sc->ports[i].active_sessions, 0, "Count of active sessions"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "queued", CTLFLAG_RD, &sc->ports[i].stats_queued, "Requests queued"); SYSCTL_ADD_COUNTER_U64(ctx, children, OID_AUTO, "completed", CTLFLAG_RD, &sc->ports[i].stats_completed, "Requests completed"); } } static void ccr_init_port(struct ccr_softc *sc, int port) { struct port_info *pi; pi = sc->adapter->port[port]; sc->ports[port].txq = &sc->adapter->sge.ctrlq[port]; sc->ports[port].rxq = &sc->adapter->sge.rxq[pi->vi->first_rxq]; sc->ports[port].rx_channel_id = pi->rx_chan; sc->ports[port].tx_channel_id = pi->tx_chan; sc->ports[port].stats_queued = counter_u64_alloc(M_WAITOK); sc->ports[port].stats_completed = counter_u64_alloc(M_WAITOK); _Static_assert(sizeof(sc->port_mask) * NBBY >= MAX_NPORTS - 1, "Too many ports to fit in port_mask"); /* * Completions for crypto requests on port 1 can sometimes * return a stale cookie value due to a firmware bug. Disable * requests on port 1 by default on affected firmware. */ if (sc->adapter->params.fw_vers >= FW_VERSION32(1, 25, 4, 0) || port == 0) sc->port_mask |= 1u << port; } static int ccr_attach(device_t dev) { struct ccr_softc *sc; int32_t cid; int i; sc = device_get_softc(dev); sc->dev = dev; sysctl_ctx_init(&sc->ctx); sc->adapter = device_get_softc(device_get_parent(dev)); for_each_port(sc->adapter, i) { ccr_init_port(sc, i); } cid = crypto_get_driverid(dev, sizeof(struct ccr_session), CRYPTOCAP_F_HARDWARE); if (cid < 0) { device_printf(dev, "could not get crypto driver id\n"); return (ENXIO); } sc->cid = cid; /* * The FID must be the first RXQ for port 0 regardless of * which port is used to service the request. */ sc->first_rxq_id = sc->adapter->sge.rxq[0].iq.abs_id; mtx_init(&sc->lock, "ccr", NULL, MTX_DEF); sc->iv_aad_buf = malloc(MAX_AAD_LEN, M_CCR, M_WAITOK); sc->sg_iv_aad = sglist_build(sc->iv_aad_buf, MAX_AAD_LEN, M_WAITOK); sc->stats_cipher_encrypt = counter_u64_alloc(M_WAITOK); sc->stats_cipher_decrypt = counter_u64_alloc(M_WAITOK); sc->stats_hash = counter_u64_alloc(M_WAITOK); sc->stats_hmac = counter_u64_alloc(M_WAITOK); sc->stats_eta_encrypt = counter_u64_alloc(M_WAITOK); sc->stats_eta_decrypt = counter_u64_alloc(M_WAITOK); sc->stats_gcm_encrypt = counter_u64_alloc(M_WAITOK); sc->stats_gcm_decrypt = counter_u64_alloc(M_WAITOK); sc->stats_ccm_encrypt = counter_u64_alloc(M_WAITOK); sc->stats_ccm_decrypt = counter_u64_alloc(M_WAITOK); sc->stats_wr_nomem = counter_u64_alloc(M_WAITOK); sc->stats_inflight = counter_u64_alloc(M_WAITOK); sc->stats_mac_error = counter_u64_alloc(M_WAITOK); sc->stats_pad_error = counter_u64_alloc(M_WAITOK); sc->stats_sglist_error = counter_u64_alloc(M_WAITOK); sc->stats_process_error = counter_u64_alloc(M_WAITOK); sc->stats_sw_fallback = counter_u64_alloc(M_WAITOK); ccr_sysctls(sc); return (0); } static void ccr_free_port(struct ccr_softc *sc, int port) { counter_u64_free(sc->ports[port].stats_queued); counter_u64_free(sc->ports[port].stats_completed); } static int ccr_detach(device_t dev) { struct ccr_softc *sc; int i; sc = device_get_softc(dev); mtx_lock(&sc->lock); sc->detaching = true; mtx_unlock(&sc->lock); crypto_unregister_all(sc->cid); sysctl_ctx_free(&sc->ctx); mtx_destroy(&sc->lock); counter_u64_free(sc->stats_cipher_encrypt); counter_u64_free(sc->stats_cipher_decrypt); counter_u64_free(sc->stats_hash); counter_u64_free(sc->stats_hmac); counter_u64_free(sc->stats_eta_encrypt); counter_u64_free(sc->stats_eta_decrypt); counter_u64_free(sc->stats_gcm_encrypt); counter_u64_free(sc->stats_gcm_decrypt); counter_u64_free(sc->stats_ccm_encrypt); counter_u64_free(sc->stats_ccm_decrypt); counter_u64_free(sc->stats_wr_nomem); counter_u64_free(sc->stats_inflight); counter_u64_free(sc->stats_mac_error); counter_u64_free(sc->stats_pad_error); counter_u64_free(sc->stats_sglist_error); counter_u64_free(sc->stats_process_error); counter_u64_free(sc->stats_sw_fallback); for_each_port(sc->adapter, i) { ccr_free_port(sc, i); } sglist_free(sc->sg_iv_aad); free(sc->iv_aad_buf, M_CCR); return (0); } static void ccr_init_hash_digest(struct ccr_session *s) { union authctx auth_ctx; const struct auth_hash *axf; axf = s->hmac.auth_hash; axf->Init(&auth_ctx); t4_copy_partial_hash(axf->type, &auth_ctx, s->hmac.pads); } static bool ccr_aes_check_keylen(int alg, int klen) { switch (klen * 8) { case 128: case 192: if (alg == CRYPTO_AES_XTS) return (false); break; case 256: break; case 512: if (alg != CRYPTO_AES_XTS) return (false); break; default: return (false); } return (true); } static void ccr_aes_setkey(struct ccr_session *s, const void *key, int klen) { unsigned int ck_size, iopad_size, kctx_flits, kctx_len, kbits, mk_size; unsigned int opad_present; if (s->cipher.cipher_mode == SCMD_CIPH_MODE_AES_XTS) kbits = (klen / 2) * 8; else kbits = klen * 8; switch (kbits) { case 128: ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_128; break; case 192: ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_192; break; case 256: ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_256; break; default: panic("should not get here"); } s->cipher.key_len = klen; memcpy(s->cipher.enckey, key, s->cipher.key_len); switch (s->cipher.cipher_mode) { case SCMD_CIPH_MODE_AES_CBC: case SCMD_CIPH_MODE_AES_XTS: t4_aes_getdeckey(s->cipher.deckey, key, kbits); break; } kctx_len = roundup2(s->cipher.key_len, 16); switch (s->mode) { case ETA: mk_size = s->hmac.mk_size; opad_present = 1; iopad_size = roundup2(s->hmac.partial_digest_len, 16); kctx_len += iopad_size * 2; break; case GCM: mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_128; opad_present = 0; kctx_len += GMAC_BLOCK_LEN; break; case CCM: switch (kbits) { case 128: mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_128; break; case 192: mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_192; break; case 256: mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_256; break; default: panic("should not get here"); } opad_present = 0; kctx_len *= 2; break; default: mk_size = CHCR_KEYCTX_NO_KEY; opad_present = 0; break; } kctx_flits = (sizeof(struct _key_ctx) + kctx_len) / 16; s->cipher.key_ctx_hdr = htobe32(V_KEY_CONTEXT_CTX_LEN(kctx_flits) | V_KEY_CONTEXT_DUAL_CK(s->cipher.cipher_mode == SCMD_CIPH_MODE_AES_XTS) | V_KEY_CONTEXT_OPAD_PRESENT(opad_present) | V_KEY_CONTEXT_SALT_PRESENT(1) | V_KEY_CONTEXT_CK_SIZE(ck_size) | V_KEY_CONTEXT_MK_SIZE(mk_size) | V_KEY_CONTEXT_VALID(1)); } static bool ccr_auth_supported(const struct crypto_session_params *csp) { switch (csp->csp_auth_alg) { case CRYPTO_SHA1: case CRYPTO_SHA2_224: case CRYPTO_SHA2_256: case CRYPTO_SHA2_384: case CRYPTO_SHA2_512: case CRYPTO_SHA1_HMAC: case CRYPTO_SHA2_224_HMAC: case CRYPTO_SHA2_256_HMAC: case CRYPTO_SHA2_384_HMAC: case CRYPTO_SHA2_512_HMAC: break; default: return (false); } return (true); } static bool ccr_cipher_supported(const struct crypto_session_params *csp) { switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: if (csp->csp_ivlen != AES_BLOCK_LEN) return (false); break; case CRYPTO_AES_ICM: if (csp->csp_ivlen != AES_BLOCK_LEN) return (false); break; case CRYPTO_AES_XTS: if (csp->csp_ivlen != AES_XTS_IV_LEN) return (false); break; default: return (false); } return (ccr_aes_check_keylen(csp->csp_cipher_alg, csp->csp_cipher_klen)); } static int ccr_cipher_mode(const struct crypto_session_params *csp) { switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: return (SCMD_CIPH_MODE_AES_CBC); case CRYPTO_AES_ICM: return (SCMD_CIPH_MODE_AES_CTR); case CRYPTO_AES_NIST_GCM_16: return (SCMD_CIPH_MODE_AES_GCM); case CRYPTO_AES_XTS: return (SCMD_CIPH_MODE_AES_XTS); case CRYPTO_AES_CCM_16: return (SCMD_CIPH_MODE_AES_CCM); default: return (SCMD_CIPH_MODE_NOP); } } static int ccr_probesession(device_t dev, const struct crypto_session_params *csp) { unsigned int cipher_mode; if ((csp->csp_flags & ~(CSP_F_SEPARATE_OUTPUT | CSP_F_SEPARATE_AAD)) != 0) return (EINVAL); switch (csp->csp_mode) { case CSP_MODE_DIGEST: if (!ccr_auth_supported(csp)) return (EINVAL); break; case CSP_MODE_CIPHER: if (!ccr_cipher_supported(csp)) return (EINVAL); break; case CSP_MODE_AEAD: switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: case CRYPTO_AES_CCM_16: break; default: return (EINVAL); } break; case CSP_MODE_ETA: if (!ccr_auth_supported(csp) || !ccr_cipher_supported(csp)) return (EINVAL); break; default: return (EINVAL); } if (csp->csp_cipher_klen != 0) { cipher_mode = ccr_cipher_mode(csp); if (cipher_mode == SCMD_CIPH_MODE_NOP) return (EINVAL); } return (CRYPTODEV_PROBE_HARDWARE); } /* * Select an available port with the lowest number of active sessions. */ static struct ccr_port * ccr_choose_port(struct ccr_softc *sc) { struct ccr_port *best, *p; int i; mtx_assert(&sc->lock, MA_OWNED); best = NULL; for (i = 0; i < nitems(sc->ports); i++) { p = &sc->ports[i]; /* Ignore non-existent ports. */ if (p->rxq == NULL) continue; /* * XXX: Ignore ports whose queues aren't initialized. * This is racy as the rxq can be destroyed by the * associated VI detaching. Eventually ccr should use * dedicated queues. */ if (p->rxq->iq.adapter == NULL || p->txq->adapter == NULL) continue; if ((sc->port_mask & (1u << i)) == 0) continue; if (best == NULL || p->active_sessions < best->active_sessions) best = p; } return (best); } static void ccr_delete_session(struct ccr_session *s) { crypto_freesession(s->sw_session); sglist_free(s->sg_input); sglist_free(s->sg_output); sglist_free(s->sg_ulptx); sglist_free(s->sg_dsgl); mtx_destroy(&s->lock); } static int ccr_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { struct ccr_softc *sc; struct ccr_session *s; const struct auth_hash *auth_hash; unsigned int auth_mode, cipher_mode, mk_size; unsigned int partial_digest_len; int error; switch (csp->csp_auth_alg) { case CRYPTO_SHA1: case CRYPTO_SHA1_HMAC: auth_hash = &auth_hash_hmac_sha1; auth_mode = SCMD_AUTH_MODE_SHA1; mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_160; partial_digest_len = SHA1_HASH_LEN; break; case CRYPTO_SHA2_224: case CRYPTO_SHA2_224_HMAC: auth_hash = &auth_hash_hmac_sha2_224; auth_mode = SCMD_AUTH_MODE_SHA224; mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_256; partial_digest_len = SHA2_256_HASH_LEN; break; case CRYPTO_SHA2_256: case CRYPTO_SHA2_256_HMAC: auth_hash = &auth_hash_hmac_sha2_256; auth_mode = SCMD_AUTH_MODE_SHA256; mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_256; partial_digest_len = SHA2_256_HASH_LEN; break; case CRYPTO_SHA2_384: case CRYPTO_SHA2_384_HMAC: auth_hash = &auth_hash_hmac_sha2_384; auth_mode = SCMD_AUTH_MODE_SHA512_384; mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_512; partial_digest_len = SHA2_512_HASH_LEN; break; case CRYPTO_SHA2_512: case CRYPTO_SHA2_512_HMAC: auth_hash = &auth_hash_hmac_sha2_512; auth_mode = SCMD_AUTH_MODE_SHA512_512; mk_size = CHCR_KEYCTX_MAC_KEY_SIZE_512; partial_digest_len = SHA2_512_HASH_LEN; break; default: auth_hash = NULL; auth_mode = SCMD_AUTH_MODE_NOP; mk_size = 0; partial_digest_len = 0; break; } cipher_mode = ccr_cipher_mode(csp); #ifdef INVARIANTS switch (csp->csp_mode) { case CSP_MODE_CIPHER: if (cipher_mode == SCMD_CIPH_MODE_NOP || cipher_mode == SCMD_CIPH_MODE_AES_GCM || cipher_mode == SCMD_CIPH_MODE_AES_CCM) panic("invalid cipher algo"); break; case CSP_MODE_DIGEST: if (auth_mode == SCMD_AUTH_MODE_NOP) panic("invalid auth algo"); break; case CSP_MODE_AEAD: if (cipher_mode != SCMD_CIPH_MODE_AES_GCM && cipher_mode != SCMD_CIPH_MODE_AES_CCM) panic("invalid aead cipher algo"); if (auth_mode != SCMD_AUTH_MODE_NOP) panic("invalid aead auth aglo"); break; case CSP_MODE_ETA: if (cipher_mode == SCMD_CIPH_MODE_NOP || cipher_mode == SCMD_CIPH_MODE_AES_GCM || cipher_mode == SCMD_CIPH_MODE_AES_CCM) panic("invalid cipher algo"); if (auth_mode == SCMD_AUTH_MODE_NOP) panic("invalid auth algo"); break; default: panic("invalid csp mode"); } #endif s = crypto_get_driver_session(cses); mtx_init(&s->lock, "ccr session", NULL, MTX_DEF); s->sg_input = sglist_alloc(TX_SGL_SEGS, M_NOWAIT); s->sg_output = sglist_alloc(TX_SGL_SEGS, M_NOWAIT); s->sg_ulptx = sglist_alloc(TX_SGL_SEGS, M_NOWAIT); s->sg_dsgl = sglist_alloc(MAX_RX_PHYS_DSGL_SGE, M_NOWAIT); if (s->sg_input == NULL || s->sg_output == NULL || s->sg_ulptx == NULL || s->sg_dsgl == NULL) { ccr_delete_session(s); return (ENOMEM); } if (csp->csp_mode == CSP_MODE_AEAD) { error = crypto_newsession(&s->sw_session, csp, CRYPTOCAP_F_SOFTWARE); if (error) { ccr_delete_session(s); return (error); } } sc = device_get_softc(dev); s->sc = sc; mtx_lock(&sc->lock); if (sc->detaching) { mtx_unlock(&sc->lock); ccr_delete_session(s); return (ENXIO); } s->port = ccr_choose_port(sc); if (s->port == NULL) { mtx_unlock(&sc->lock); ccr_delete_session(s); return (ENXIO); } switch (csp->csp_mode) { case CSP_MODE_AEAD: if (cipher_mode == SCMD_CIPH_MODE_AES_CCM) s->mode = CCM; else s->mode = GCM; break; case CSP_MODE_ETA: s->mode = ETA; break; case CSP_MODE_DIGEST: if (csp->csp_auth_klen != 0) s->mode = HMAC; else s->mode = HASH; break; case CSP_MODE_CIPHER: s->mode = CIPHER; break; } if (s->mode == GCM) { if (csp->csp_auth_mlen == 0) s->gmac.hash_len = AES_GMAC_HASH_LEN; else s->gmac.hash_len = csp->csp_auth_mlen; t4_init_gmac_hash(csp->csp_cipher_key, csp->csp_cipher_klen, s->gmac.ghash_h); } else if (s->mode == CCM) { if (csp->csp_auth_mlen == 0) s->ccm_mac.hash_len = AES_CBC_MAC_HASH_LEN; else s->ccm_mac.hash_len = csp->csp_auth_mlen; } else if (auth_mode != SCMD_AUTH_MODE_NOP) { s->hmac.auth_hash = auth_hash; s->hmac.auth_mode = auth_mode; s->hmac.mk_size = mk_size; s->hmac.partial_digest_len = partial_digest_len; if (csp->csp_auth_mlen == 0) s->hmac.hash_len = auth_hash->hashsize; else s->hmac.hash_len = csp->csp_auth_mlen; if (csp->csp_auth_key != NULL) t4_init_hmac_digest(auth_hash, partial_digest_len, csp->csp_auth_key, csp->csp_auth_klen, s->hmac.pads); else ccr_init_hash_digest(s); } if (cipher_mode != SCMD_CIPH_MODE_NOP) { s->cipher.cipher_mode = cipher_mode; s->cipher.iv_len = csp->csp_ivlen; if (csp->csp_cipher_key != NULL) ccr_aes_setkey(s, csp->csp_cipher_key, csp->csp_cipher_klen); } s->port->active_sessions++; mtx_unlock(&sc->lock); return (0); } static void ccr_freesession(device_t dev, crypto_session_t cses) { struct ccr_softc *sc; struct ccr_session *s; sc = device_get_softc(dev); s = crypto_get_driver_session(cses); #ifdef INVARIANTS if (s->pending != 0) device_printf(dev, "session %p freed with %d pending requests\n", s, s->pending); #endif mtx_lock(&sc->lock); s->port->active_sessions--; mtx_unlock(&sc->lock); ccr_delete_session(s); } static int ccr_process(device_t dev, struct cryptop *crp, int hint) { const struct crypto_session_params *csp; struct ccr_softc *sc; struct ccr_session *s; int error; csp = crypto_get_params(crp->crp_session); s = crypto_get_driver_session(crp->crp_session); sc = device_get_softc(dev); mtx_lock(&s->lock); error = ccr_populate_sglist(s->sg_input, &crp->crp_buf); if (error == 0 && CRYPTO_HAS_OUTPUT_BUFFER(crp)) error = ccr_populate_sglist(s->sg_output, &crp->crp_obuf); if (error) { counter_u64_add(sc->stats_sglist_error, 1); goto out; } switch (s->mode) { case HASH: error = ccr_hash(sc, s, crp); if (error == 0) counter_u64_add(sc->stats_hash, 1); break; case HMAC: if (crp->crp_auth_key != NULL) t4_init_hmac_digest(s->hmac.auth_hash, s->hmac.partial_digest_len, crp->crp_auth_key, csp->csp_auth_klen, s->hmac.pads); error = ccr_hash(sc, s, crp); if (error == 0) counter_u64_add(sc->stats_hmac, 1); break; case CIPHER: if (crp->crp_cipher_key != NULL) ccr_aes_setkey(s, crp->crp_cipher_key, csp->csp_cipher_klen); error = ccr_cipher(sc, s, crp); if (error == 0) { if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) counter_u64_add(sc->stats_cipher_encrypt, 1); else counter_u64_add(sc->stats_cipher_decrypt, 1); } break; case ETA: if (crp->crp_auth_key != NULL) t4_init_hmac_digest(s->hmac.auth_hash, s->hmac.partial_digest_len, crp->crp_auth_key, csp->csp_auth_klen, s->hmac.pads); if (crp->crp_cipher_key != NULL) ccr_aes_setkey(s, crp->crp_cipher_key, csp->csp_cipher_klen); error = ccr_eta(sc, s, crp); if (error == 0) { if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) counter_u64_add(sc->stats_eta_encrypt, 1); else counter_u64_add(sc->stats_eta_decrypt, 1); } break; case GCM: if (crp->crp_cipher_key != NULL) { t4_init_gmac_hash(crp->crp_cipher_key, csp->csp_cipher_klen, s->gmac.ghash_h); ccr_aes_setkey(s, crp->crp_cipher_key, csp->csp_cipher_klen); } error = ccr_gcm(sc, s, crp); if (error == EMSGSIZE || error == EFBIG) { counter_u64_add(sc->stats_sw_fallback, 1); mtx_unlock(&s->lock); ccr_soft(s, crp); return (0); } if (error == 0) { if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) counter_u64_add(sc->stats_gcm_encrypt, 1); else counter_u64_add(sc->stats_gcm_decrypt, 1); } break; case CCM: if (crp->crp_cipher_key != NULL) { ccr_aes_setkey(s, crp->crp_cipher_key, csp->csp_cipher_klen); } error = ccr_ccm(sc, s, crp); if (error == EMSGSIZE || error == EFBIG) { counter_u64_add(sc->stats_sw_fallback, 1); mtx_unlock(&s->lock); ccr_soft(s, crp); return (0); } if (error == 0) { if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) counter_u64_add(sc->stats_ccm_encrypt, 1); else counter_u64_add(sc->stats_ccm_decrypt, 1); } break; } if (error == 0) { #ifdef INVARIANTS s->pending++; #endif counter_u64_add(sc->stats_inflight, 1); counter_u64_add(s->port->stats_queued, 1); } else counter_u64_add(sc->stats_process_error, 1); out: mtx_unlock(&s->lock); if (error) { crp->crp_etype = error; crypto_done(crp); } return (0); } static int do_cpl6_fw_pld(struct sge_iq *iq, const struct rss_header *rss, struct mbuf *m) { struct ccr_softc *sc; struct ccr_session *s; const struct cpl_fw6_pld *cpl; struct cryptop *crp; uint32_t status; int error; if (m != NULL) cpl = mtod(m, const void *); else cpl = (const void *)(rss + 1); crp = (struct cryptop *)(uintptr_t)be64toh(cpl->data[1]); s = crypto_get_driver_session(crp->crp_session); status = be64toh(cpl->data[0]); if (CHK_MAC_ERR_BIT(status) || CHK_PAD_ERR_BIT(status)) error = EBADMSG; else error = 0; sc = s->sc; #ifdef INVARIANTS mtx_lock(&s->lock); s->pending--; mtx_unlock(&s->lock); #endif counter_u64_add(sc->stats_inflight, -1); counter_u64_add(s->port->stats_completed, 1); switch (s->mode) { case HASH: case HMAC: error = ccr_hash_done(sc, s, crp, cpl, error); break; case CIPHER: error = ccr_cipher_done(sc, s, crp, cpl, error); break; case ETA: error = ccr_eta_done(sc, s, crp, cpl, error); break; case GCM: error = ccr_gcm_done(sc, s, crp, cpl, error); break; case CCM: error = ccr_ccm_done(sc, s, crp, cpl, error); break; } if (error == EBADMSG) { if (CHK_MAC_ERR_BIT(status)) counter_u64_add(sc->stats_mac_error, 1); if (CHK_PAD_ERR_BIT(status)) counter_u64_add(sc->stats_pad_error, 1); } crp->crp_etype = error; crypto_done(crp); m_freem(m); return (0); } static int ccr_modevent(module_t mod, int cmd, void *arg) { switch (cmd) { case MOD_LOAD: t4_register_cpl_handler(CPL_FW6_PLD, do_cpl6_fw_pld); return (0); case MOD_UNLOAD: t4_register_cpl_handler(CPL_FW6_PLD, NULL); return (0); default: return (EOPNOTSUPP); } } static device_method_t ccr_methods[] = { DEVMETHOD(device_identify, ccr_identify), DEVMETHOD(device_probe, ccr_probe), DEVMETHOD(device_attach, ccr_attach), DEVMETHOD(device_detach, ccr_detach), DEVMETHOD(cryptodev_probesession, ccr_probesession), DEVMETHOD(cryptodev_newsession, ccr_newsession), DEVMETHOD(cryptodev_freesession, ccr_freesession), DEVMETHOD(cryptodev_process, ccr_process), DEVMETHOD_END }; static driver_t ccr_driver = { "ccr", ccr_methods, sizeof(struct ccr_softc) }; DRIVER_MODULE(ccr, t6nex, ccr_driver, ccr_modevent, NULL); MODULE_VERSION(ccr, 1); MODULE_DEPEND(ccr, crypto, 1, 1, 1); MODULE_DEPEND(ccr, t6nex, 1, 1, 1); diff --git a/sys/dev/dpaa2/dpaa2_mc_acpi.c b/sys/dev/dpaa2/dpaa2_mc_acpi.c index 1042ea56d8cf..55c1c0d5b12e 100644 --- a/sys/dev/dpaa2/dpaa2_mc_acpi.c +++ b/sys/dev/dpaa2/dpaa2_mc_acpi.c @@ -1,396 +1,396 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright ยฉ 2021-2022 Dmitry Salychev * Copyright ยฉ 2021 Bjoern A. Zeeb * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include /* * The DPAA2 Management Complex (MC) Bus Driver (ACPI-based). * * MC is a hardware resource manager which can be found in several NXP * SoCs (LX2160A, for example) and provides an access to the specialized * hardware objects used in network-oriented packet processing applications. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_bus_if.h" #include "pcib_if.h" #include "pci_if.h" #include "dpaa2_mcp.h" #include "dpaa2_mc.h" #include "dpaa2_mc_if.h" #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("DPAA2_MC") struct dpaa2_mac_dev_softc { int uid; uint64_t reg; char managed[64]; char phy_conn_type[64]; char phy_mode[64]; ACPI_HANDLE phy_channel; }; static int dpaa2_mac_dev_probe(device_t dev) { uint64_t reg; ssize_t s; s = device_get_property(dev, "reg", ®, sizeof(reg), DEVICE_PROP_UINT64); if (s == -1) return (ENXIO); device_set_desc(dev, "DPAA2 MAC DEV"); return (BUS_PROBE_DEFAULT); } static int dpaa2_mac_dev_attach(device_t dev) { struct dpaa2_mac_dev_softc *sc; ACPI_HANDLE h; ssize_t s; sc = device_get_softc(dev); h = acpi_get_handle(dev); if (h == NULL) return (ENXIO); s = acpi_GetInteger(h, "_UID", &sc->uid); if (ACPI_FAILURE(s)) { device_printf(dev, "Cannot find '_UID' property: %zd\n", s); return (ENXIO); } s = device_get_property(dev, "reg", &sc->reg, sizeof(sc->reg), DEVICE_PROP_UINT64); if (s == -1) { device_printf(dev, "Cannot find 'reg' property: %zd\n", s); return (ENXIO); } s = device_get_property(dev, "managed", sc->managed, sizeof(sc->managed), DEVICE_PROP_ANY); s = device_get_property(dev, "phy-connection-type", sc->phy_conn_type, sizeof(sc->phy_conn_type), DEVICE_PROP_ANY); s = device_get_property(dev, "phy-mode", sc->phy_mode, sizeof(sc->phy_mode), DEVICE_PROP_ANY); s = device_get_property(dev, "phy-handle", &sc->phy_channel, sizeof(sc->phy_channel), DEVICE_PROP_HANDLE); if (bootverbose) device_printf(dev, "UID %#04x reg %#04jx managed '%s' " "phy-connection-type '%s' phy-mode '%s' phy-handle '%s'\n", sc->uid, sc->reg, sc->managed[0] != '\0' ? sc->managed : "", sc->phy_conn_type[0] != '\0' ? sc->phy_conn_type : "", sc->phy_mode[0] != '\0' ? sc->phy_mode : "", sc->phy_channel != NULL ? acpi_name(sc->phy_channel) : ""); return (0); } static bool dpaa2_mac_dev_match_id(device_t dev, uint32_t id) { struct dpaa2_mac_dev_softc *sc; if (dev == NULL) return (false); sc = device_get_softc(dev); if (sc->uid == id) return (true); return (false); } static device_t dpaa2_mac_dev_get_phy_dev(device_t dev) { struct dpaa2_mac_dev_softc *sc; if (dev == NULL) return (NULL); sc = device_get_softc(dev); if (sc->phy_channel == NULL) return (NULL); return (acpi_get_device(sc->phy_channel)); } static device_method_t dpaa2_mac_dev_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dpaa2_mac_dev_probe), DEVMETHOD(device_attach, dpaa2_mac_dev_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD_END }; DEFINE_CLASS_0(dpaa2_mac_dev, dpaa2_mac_dev_driver, dpaa2_mac_dev_methods, sizeof(struct dpaa2_mac_dev_softc)); DRIVER_MODULE(dpaa2_mac_dev, dpaa2_mc, dpaa2_mac_dev_driver, 0, 0); MODULE_DEPEND(dpaa2_mac_dev, memac_mdio_acpi, 1, 1, 1); /* * Device interface. */ static int dpaa2_mc_acpi_probe(device_t dev) { static char *dpaa2_mc_ids[] = { "NXP0008", NULL }; int rc; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); rc = ACPI_ID_PROBE(device_get_parent(dev), dev, dpaa2_mc_ids, NULL); if (rc <= 0) device_set_desc(dev, "DPAA2 Management Complex"); return (rc); } /* Context for walking PRxx child devices. */ struct dpaa2_mc_acpi_prxx_walk_ctx { device_t dev; int count; int countok; }; static ACPI_STATUS dpaa2_mc_acpi_probe_child(ACPI_HANDLE h, device_t *dev, int level, void *arg) { struct dpaa2_mc_acpi_prxx_walk_ctx *ctx; struct acpi_device *ad; device_t child; uint32_t uid; ctx = (struct dpaa2_mc_acpi_prxx_walk_ctx *)arg; ctx->count++; #if 0 device_printf(ctx->dev, "%s: %s level %d count %d\n", __func__, acpi_name(h), level, ctx->count); #endif if (ACPI_FAILURE(acpi_GetInteger(h, "_UID", &uid))) return (AE_OK); #if 0 if (bootverbose) device_printf(ctx->dev, "%s: Found child Ports _UID %u\n", __func__, uid); #endif /* Technically M_ACPIDEV */ if ((ad = malloc(sizeof(*ad), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) return (AE_OK); - child = device_add_child(ctx->dev, "dpaa2_mac_dev", -1); + child = device_add_child(ctx->dev, "dpaa2_mac_dev", DEVICE_UNIT_ANY); if (child == NULL) { free(ad, M_DEVBUF); return (AE_OK); } ad->ad_handle = h; ad->ad_cls_class = 0xffffff; resource_list_init(&ad->ad_rl); device_set_ivars(child, ad); *dev = child; ctx->countok++; return (AE_OK); } static int dpaa2_mc_acpi_attach(device_t dev) { struct dpaa2_mc_softc *sc; sc = device_get_softc(dev); sc->acpi_based = true; struct dpaa2_mc_acpi_prxx_walk_ctx ctx; ctx.dev = dev; ctx.count = 0; ctx.countok = 0; ACPI_SCAN_CHILDREN(device_get_parent(dev), dev, 2, dpaa2_mc_acpi_probe_child, &ctx); #if 0 device_printf(dev, "Found %d child Ports in ASL, %d ok\n", ctx.count, ctx.countok); #endif return (dpaa2_mc_attach(dev)); } /* * ACPI compat layer. */ static device_t dpaa2_mc_acpi_find_dpaa2_mac_dev(device_t dev, uint32_t id) { int devcount, error, i, len; device_t *devlist, mdev; const char *mdevname; error = device_get_children(dev, &devlist, &devcount); if (error != 0) return (NULL); for (i = 0; i < devcount; i++) { mdev = devlist[i]; mdevname = device_get_name(mdev); if (mdevname != NULL) { len = strlen(mdevname); if (strncmp("dpaa2_mac_dev", mdevname, len) != 0) continue; } else { continue; } if (!device_is_attached(mdev)) continue; if (dpaa2_mac_dev_match_id(mdev, id)) return (mdev); } return (NULL); } static int dpaa2_mc_acpi_get_phy_dev(device_t dev, device_t *phy_dev, uint32_t id) { device_t mdev, pdev; mdev = dpaa2_mc_acpi_find_dpaa2_mac_dev(dev, id); if (mdev == NULL) { device_printf(dev, "%s: error finding dpmac device with id=%u\n", __func__, id); return (ENXIO); } pdev = dpaa2_mac_dev_get_phy_dev(mdev); if (pdev == NULL) { device_printf(dev, "%s: error getting MDIO device for dpamc %s " "(id=%u)\n", __func__, device_get_nameunit(mdev), id); return (ENXIO); } if (phy_dev != NULL) *phy_dev = pdev; return (0); } static ssize_t dpaa2_mc_acpi_get_property(device_t dev, device_t child, const char *propname, void *propvalue, size_t size, device_property_type_t type) { return (bus_generic_get_property(dev, child, propname, propvalue, size, type)); } static int dpaa2_mc_acpi_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { /* * This is special in that it passes "child" as second argument rather * than "dev". acpi_get_handle() in dpaa2_mac_dev_attach() calls the * read on parent(dev), dev and gets us here not to ACPI. Hence we * need to keep child as-is and pass it to our parent which is ACPI. * Only that gives the desired result. */ return (BUS_READ_IVAR(device_get_parent(dev), child, index, result)); } static device_method_t dpaa2_mc_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dpaa2_mc_acpi_probe), DEVMETHOD(device_attach, dpaa2_mc_acpi_attach), DEVMETHOD(device_detach, dpaa2_mc_detach), /* Bus interface */ DEVMETHOD(bus_get_rman, dpaa2_mc_rman), DEVMETHOD(bus_alloc_resource, dpaa2_mc_alloc_resource), DEVMETHOD(bus_adjust_resource, dpaa2_mc_adjust_resource), DEVMETHOD(bus_release_resource, dpaa2_mc_release_resource), DEVMETHOD(bus_activate_resource, dpaa2_mc_activate_resource), DEVMETHOD(bus_deactivate_resource, dpaa2_mc_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), /* Pseudo-PCIB interface */ DEVMETHOD(pcib_alloc_msi, dpaa2_mc_alloc_msi), DEVMETHOD(pcib_release_msi, dpaa2_mc_release_msi), DEVMETHOD(pcib_map_msi, dpaa2_mc_map_msi), DEVMETHOD(pcib_get_id, dpaa2_mc_get_id), /* DPAA2 MC bus interface */ DEVMETHOD(dpaa2_mc_manage_dev, dpaa2_mc_manage_dev), DEVMETHOD(dpaa2_mc_get_free_dev,dpaa2_mc_get_free_dev), DEVMETHOD(dpaa2_mc_get_dev, dpaa2_mc_get_dev), DEVMETHOD(dpaa2_mc_get_shared_dev, dpaa2_mc_get_shared_dev), DEVMETHOD(dpaa2_mc_reserve_dev, dpaa2_mc_reserve_dev), DEVMETHOD(dpaa2_mc_release_dev, dpaa2_mc_release_dev), DEVMETHOD(dpaa2_mc_get_phy_dev, dpaa2_mc_acpi_get_phy_dev), /* ACPI compar layer. */ DEVMETHOD(bus_read_ivar, dpaa2_mc_acpi_read_ivar), DEVMETHOD(bus_get_property, dpaa2_mc_acpi_get_property), DEVMETHOD_END }; DEFINE_CLASS_1(dpaa2_mc, dpaa2_mc_acpi_driver, dpaa2_mc_acpi_methods, sizeof(struct dpaa2_mc_softc), dpaa2_mc_driver); /* Make sure miibus gets procesed first. */ DRIVER_MODULE_ORDERED(dpaa2_mc, acpi, dpaa2_mc_acpi_driver, NULL, NULL, SI_ORDER_ANY); MODULE_DEPEND(dpaa2_mc, memac_mdio_acpi, 1, 1, 1); diff --git a/sys/dev/dpaa2/dpaa2_rc.c b/sys/dev/dpaa2/dpaa2_rc.c index d8e15e388bf5..3cb2fdfeaa2e 100644 --- a/sys/dev/dpaa2/dpaa2_rc.c +++ b/sys/dev/dpaa2/dpaa2_rc.c @@ -1,3552 +1,3552 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright ยฉ 2021-2022 Dmitry Salychev * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include /* * The DPAA2 Resource Container (DPRC) bus driver. * * DPRC holds all the resources and object information that a software context * (kernel, virtual machine, etc.) can access or use. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" #include "pci_if.h" #include "dpaa2_mcp.h" #include "dpaa2_mc.h" #include "dpaa2_ni.h" #include "dpaa2_mc_if.h" #include "dpaa2_cmd_if.h" /* Timeouts to wait for a command response from MC. */ #define CMD_SPIN_TIMEOUT 100u /* us */ #define CMD_SPIN_ATTEMPTS 2000u /* max. 200 ms */ #define TYPE_LEN_MAX 16u #define LABEL_LEN_MAX 16u MALLOC_DEFINE(M_DPAA2_RC, "dpaa2_rc", "DPAA2 Resource Container"); /* Discover and add devices to the resource container. */ static int dpaa2_rc_discover(struct dpaa2_rc_softc *); static int dpaa2_rc_add_child(struct dpaa2_rc_softc *, struct dpaa2_cmd *, struct dpaa2_obj *); static int dpaa2_rc_add_managed_child(struct dpaa2_rc_softc *, struct dpaa2_cmd *, struct dpaa2_obj *); /* Helper routines. */ static int dpaa2_rc_enable_irq(struct dpaa2_mcp *, struct dpaa2_cmd *, uint8_t, bool, uint16_t); static int dpaa2_rc_configure_irq(device_t, device_t, int, uint64_t, uint32_t); static int dpaa2_rc_add_res(device_t, device_t, enum dpaa2_dev_type, int *, int); static int dpaa2_rc_print_type(struct resource_list *, enum dpaa2_dev_type); static struct dpaa2_mcp *dpaa2_rc_select_portal(device_t, device_t); /* Routines to send commands to MC. */ static int dpaa2_rc_exec_cmd(struct dpaa2_mcp *, struct dpaa2_cmd *, uint16_t); static int dpaa2_rc_send_cmd(struct dpaa2_mcp *, struct dpaa2_cmd *); static int dpaa2_rc_wait_for_cmd(struct dpaa2_mcp *, struct dpaa2_cmd *); static int dpaa2_rc_reset_cmd_params(struct dpaa2_cmd *); static int dpaa2_rc_probe(device_t dev) { /* DPRC device will be added by the parent DPRC or MC bus itself. */ device_set_desc(dev, "DPAA2 Resource Container"); return (BUS_PROBE_DEFAULT); } static int dpaa2_rc_detach(device_t dev) { struct dpaa2_devinfo *dinfo; int error; error = bus_generic_detach(dev); if (error) return (error); dinfo = device_get_ivars(dev); if (dinfo->portal) dpaa2_mcp_free_portal(dinfo->portal); if (dinfo) free(dinfo, M_DPAA2_RC); return (0); } static int dpaa2_rc_attach(device_t dev) { device_t pdev; struct dpaa2_mc_softc *mcsc; struct dpaa2_rc_softc *sc; struct dpaa2_devinfo *dinfo = NULL; int error; sc = device_get_softc(dev); sc->dev = dev; sc->unit = device_get_unit(dev); if (sc->unit == 0) { /* Root DPRC should be attached directly to the MC bus. */ pdev = device_get_parent(dev); mcsc = device_get_softc(pdev); KASSERT(strcmp(device_get_name(pdev), "dpaa2_mc") == 0, ("root DPRC should be attached to the MC bus")); /* * Allocate devinfo to let the parent MC bus access ICID of the * DPRC object. */ dinfo = malloc(sizeof(struct dpaa2_devinfo), M_DPAA2_RC, M_WAITOK | M_ZERO); if (!dinfo) { device_printf(dev, "%s: failed to allocate " "dpaa2_devinfo\n", __func__); dpaa2_rc_detach(dev); return (ENXIO); } device_set_ivars(dev, dinfo); dinfo->pdev = pdev; dinfo->dev = dev; dinfo->dtype = DPAA2_DEV_RC; dinfo->portal = NULL; /* Prepare helper portal object to send commands to MC. */ error = dpaa2_mcp_init_portal(&dinfo->portal, mcsc->res[0], &mcsc->map[0], DPAA2_PORTAL_DEF); if (error) { device_printf(dev, "%s: failed to initialize dpaa2_mcp: " "error=%d\n", __func__, error); dpaa2_rc_detach(dev); return (ENXIO); } } else { /* TODO: Child DPRCs aren't supported yet. */ return (ENXIO); } /* Create DPAA2 devices for objects in this container. */ error = dpaa2_rc_discover(sc); if (error) { device_printf(dev, "%s: failed to discover objects in " "container: error=%d\n", __func__, error); dpaa2_rc_detach(dev); return (error); } return (0); } /* * Bus interface. */ static struct resource_list * dpaa2_rc_get_resource_list(device_t rcdev, device_t child) { struct dpaa2_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } static void dpaa2_rc_delete_resource(device_t rcdev, device_t child, int type, int rid) { struct resource_list *rl; struct resource_list_entry *rle; struct dpaa2_devinfo *dinfo; if (device_get_parent(child) != rcdev) return; dinfo = device_get_ivars(child); rl = &dinfo->resources; rle = resource_list_find(rl, type, rid); if (rle == NULL) return; if (rle->res) { if (rman_get_flags(rle->res) & RF_ACTIVE || resource_list_busy(rl, type, rid)) { device_printf(rcdev, "%s: resource still owned by " "child: type=%d, rid=%d, start=%jx\n", __func__, type, rid, rman_get_start(rle->res)); return; } resource_list_unreserve(rl, rcdev, child, type, rid); } resource_list_delete(rl, type, rid); } static struct resource * dpaa2_rc_alloc_multi_resource(device_t rcdev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource_list *rl; struct dpaa2_devinfo *dinfo; dinfo = device_get_ivars(child); rl = &dinfo->resources; /* * By default, software portal interrupts are message-based, that is, * they are issued from QMan using a 4 byte write. * * TODO: However this default behavior can be changed by programming one * or more software portals to issue their interrupts via a * dedicated software portal interrupt wire. * See registers SWP_INTW0_CFG to SWP_INTW3_CFG for details. */ if (type == SYS_RES_IRQ && *rid == 0) return (NULL); return (resource_list_alloc(rl, rcdev, child, type, rid, start, end, count, flags)); } static struct resource * dpaa2_rc_alloc_resource(device_t rcdev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { if (device_get_parent(child) != rcdev) return (BUS_ALLOC_RESOURCE(device_get_parent(rcdev), child, type, rid, start, end, count, flags)); return (dpaa2_rc_alloc_multi_resource(rcdev, child, type, rid, start, end, count, flags)); } static int dpaa2_rc_release_resource(device_t rcdev, device_t child, struct resource *r) { struct resource_list *rl; struct dpaa2_devinfo *dinfo; if (device_get_parent(child) != rcdev) return (BUS_RELEASE_RESOURCE(device_get_parent(rcdev), child, r)); dinfo = device_get_ivars(child); rl = &dinfo->resources; return (resource_list_release(rl, rcdev, child, r)); } static void dpaa2_rc_child_deleted(device_t rcdev, device_t child) { struct dpaa2_devinfo *dinfo; struct resource_list *rl; struct resource_list_entry *rle; dinfo = device_get_ivars(child); rl = &dinfo->resources; /* Free all allocated resources */ STAILQ_FOREACH(rle, rl, link) { if (rle->res) { if (rman_get_flags(rle->res) & RF_ACTIVE || resource_list_busy(rl, rle->type, rle->rid)) { device_printf(child, "%s: resource still owned: " "type=%d, rid=%d, addr=%lx\n", __func__, rle->type, rle->rid, rman_get_start(rle->res)); bus_release_resource(child, rle->type, rle->rid, rle->res); } resource_list_unreserve(rl, rcdev, child, rle->type, rle->rid); } } resource_list_free(rl); if (dinfo) free(dinfo, M_DPAA2_RC); } static void dpaa2_rc_child_detached(device_t rcdev, device_t child) { struct dpaa2_devinfo *dinfo; struct resource_list *rl; dinfo = device_get_ivars(child); rl = &dinfo->resources; if (resource_list_release_active(rl, rcdev, child, SYS_RES_IRQ) != 0) device_printf(child, "%s: leaked IRQ resources!\n", __func__); if (dinfo->msi.msi_alloc != 0) { device_printf(child, "%s: leaked %d MSI vectors!\n", __func__, dinfo->msi.msi_alloc); PCI_RELEASE_MSI(rcdev, child); } if (resource_list_release_active(rl, rcdev, child, SYS_RES_MEMORY) != 0) device_printf(child, "%s: leaked memory resources!\n", __func__); } static int dpaa2_rc_setup_intr(device_t rcdev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { struct dpaa2_devinfo *dinfo; uint64_t addr; uint32_t data; void *cookie; int error, rid; error = bus_generic_setup_intr(rcdev, child, irq, flags, filter, intr, arg, &cookie); if (error) { device_printf(rcdev, "%s: bus_generic_setup_intr() failed: " "error=%d\n", __func__, error); return (error); } /* If this is not a direct child, just bail out. */ if (device_get_parent(child) != rcdev) { *cookiep = cookie; return (0); } rid = rman_get_rid(irq); if (rid == 0) { if (bootverbose) device_printf(rcdev, "%s: cannot setup interrupt with " "rid=0: INTx are not supported by DPAA2 objects " "yet\n", __func__); return (EINVAL); } else { dinfo = device_get_ivars(child); KASSERT(dinfo->msi.msi_alloc > 0, ("No MSI interrupts allocated")); /* * Ask our parent to map the MSI and give us the address and * data register values. If we fail for some reason, teardown * the interrupt handler. */ error = PCIB_MAP_MSI(device_get_parent(rcdev), child, rman_get_start(irq), &addr, &data); if (error) { device_printf(rcdev, "%s: PCIB_MAP_MSI failed: " "error=%d\n", __func__, error); (void)bus_generic_teardown_intr(rcdev, child, irq, cookie); return (error); } /* Configure MSI for this DPAA2 object. */ error = dpaa2_rc_configure_irq(rcdev, child, rid, addr, data); if (error) { device_printf(rcdev, "%s: failed to configure IRQ for " "DPAA2 object: rid=%d, type=%s, unit=%d\n", __func__, rid, dpaa2_ttos(dinfo->dtype), device_get_unit(child)); return (error); } dinfo->msi.msi_handlers++; } *cookiep = cookie; return (0); } static int dpaa2_rc_teardown_intr(device_t rcdev, device_t child, struct resource *irq, void *cookie) { struct resource_list_entry *rle; struct dpaa2_devinfo *dinfo; int error, rid; if (irq == NULL || !(rman_get_flags(irq) & RF_ACTIVE)) return (EINVAL); /* If this isn't a direct child, just bail out */ if (device_get_parent(child) != rcdev) return(bus_generic_teardown_intr(rcdev, child, irq, cookie)); rid = rman_get_rid(irq); if (rid == 0) { if (bootverbose) device_printf(rcdev, "%s: cannot teardown interrupt " "with rid=0: INTx are not supported by DPAA2 " "objects yet\n", __func__); return (EINVAL); } else { dinfo = device_get_ivars(child); rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, rid); if (rle->res != irq) return (EINVAL); dinfo->msi.msi_handlers--; } error = bus_generic_teardown_intr(rcdev, child, irq, cookie); if (rid > 0) KASSERT(error == 0, ("%s: generic teardown failed for MSI", __func__)); return (error); } static int dpaa2_rc_print_child(device_t rcdev, device_t child) { struct dpaa2_devinfo *dinfo = device_get_ivars(child); struct resource_list *rl = &dinfo->resources; int retval = 0; retval += bus_print_child_header(rcdev, child); retval += resource_list_print_type(rl, "port", SYS_RES_IOPORT, "%#jx"); retval += resource_list_print_type(rl, "iomem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); /* Print DPAA2-specific resources. */ retval += dpaa2_rc_print_type(rl, DPAA2_DEV_IO); retval += dpaa2_rc_print_type(rl, DPAA2_DEV_BP); retval += dpaa2_rc_print_type(rl, DPAA2_DEV_CON); retval += dpaa2_rc_print_type(rl, DPAA2_DEV_MCP); retval += printf(" at %s (id=%u)", dpaa2_ttos(dinfo->dtype), dinfo->id); retval += bus_print_child_domain(rcdev, child); retval += bus_print_child_footer(rcdev, child); return (retval); } /* * Pseudo-PCI interface. */ /* * Attempt to allocate *count MSI messages. The actual number allocated is * returned in *count. After this function returns, each message will be * available to the driver as SYS_RES_IRQ resources starting at a rid 1. * * NOTE: Implementation is similar to sys/dev/pci/pci.c. */ static int dpaa2_rc_alloc_msi(device_t rcdev, device_t child, int *count) { struct dpaa2_devinfo *rcinfo = device_get_ivars(rcdev); struct dpaa2_devinfo *dinfo = device_get_ivars(child); int error, actual, i, run, irqs[32]; /* Don't let count == 0 get us into trouble. */ if (*count == 0) return (EINVAL); /* MSI should be allocated by the resource container. */ if (rcinfo->dtype != DPAA2_DEV_RC) return (ENODEV); /* Already have allocated messages? */ if (dinfo->msi.msi_alloc != 0) return (ENXIO); /* Don't ask for more than the device supports. */ actual = min(*count, dinfo->msi.msi_msgnum); /* Don't ask for more than 32 messages. */ actual = min(actual, 32); /* MSI requires power of 2 number of messages. */ if (!powerof2(actual)) return (EINVAL); for (;;) { /* Try to allocate N messages. */ error = PCIB_ALLOC_MSI(device_get_parent(rcdev), child, actual, actual, irqs); if (error == 0) break; if (actual == 1) return (error); /* Try N / 2. */ actual >>= 1; } /* * We now have N actual messages mapped onto SYS_RES_IRQ resources in * the irqs[] array, so add new resources starting at rid 1. */ for (i = 0; i < actual; i++) resource_list_add(&dinfo->resources, SYS_RES_IRQ, i + 1, irqs[i], irqs[i], 1); if (bootverbose) { if (actual == 1) { device_printf(child, "using IRQ %d for MSI\n", irqs[0]); } else { /* * Be fancy and try to print contiguous runs * of IRQ values as ranges. 'run' is true if * we are in a range. */ device_printf(child, "using IRQs %d", irqs[0]); run = 0; for (i = 1; i < actual; i++) { /* Still in a run? */ if (irqs[i] == irqs[i - 1] + 1) { run = 1; continue; } /* Finish previous range. */ if (run) { printf("-%d", irqs[i - 1]); run = 0; } /* Start new range. */ printf(",%d", irqs[i]); } /* Unfinished range? */ if (run) printf("-%d", irqs[actual - 1]); printf(" for MSI\n"); } } /* Update counts of alloc'd messages. */ dinfo->msi.msi_alloc = actual; dinfo->msi.msi_handlers = 0; *count = actual; return (0); } /* * Release the MSI messages associated with this DPAA2 device. * * NOTE: Implementation is similar to sys/dev/pci/pci.c. */ static int dpaa2_rc_release_msi(device_t rcdev, device_t child) { struct dpaa2_devinfo *rcinfo = device_get_ivars(rcdev); struct dpaa2_devinfo *dinfo = device_get_ivars(child); struct resource_list_entry *rle; int i, irqs[32]; /* MSI should be released by the resource container. */ if (rcinfo->dtype != DPAA2_DEV_RC) return (ENODEV); /* Do we have any messages to release? */ if (dinfo->msi.msi_alloc == 0) return (ENODEV); KASSERT(dinfo->msi.msi_alloc <= 32, ("more than 32 alloc'd MSI messages")); /* Make sure none of the resources are allocated. */ if (dinfo->msi.msi_handlers > 0) return (EBUSY); for (i = 0; i < dinfo->msi.msi_alloc; i++) { rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); KASSERT(rle != NULL, ("missing MSI resource")); if (rle->res != NULL) return (EBUSY); irqs[i] = rle->start; } /* Release the messages. */ PCIB_RELEASE_MSI(device_get_parent(rcdev), child, dinfo->msi.msi_alloc, irqs); for (i = 0; i < dinfo->msi.msi_alloc; i++) resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); /* Update alloc count. */ dinfo->msi.msi_alloc = 0; return (0); } /** * @brief Return the maximum number of the MSI supported by this DPAA2 device. */ static int dpaa2_rc_msi_count(device_t rcdev, device_t child) { struct dpaa2_devinfo *dinfo = device_get_ivars(child); return (dinfo->msi.msi_msgnum); } static int dpaa2_rc_get_id(device_t rcdev, device_t child, enum pci_id_type type, uintptr_t *id) { struct dpaa2_devinfo *rcinfo = device_get_ivars(rcdev); if (rcinfo->dtype != DPAA2_DEV_RC) return (ENODEV); return (PCIB_GET_ID(device_get_parent(rcdev), child, type, id)); } /* * DPAA2 MC command interface. */ static int dpaa2_rc_mng_get_version(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t *major, uint32_t *minor, uint32_t *rev) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || major == NULL || minor == NULL || rev == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MNG_GET_VER); if (!error) { *major = cmd->params[0] >> 32; *minor = cmd->params[1] & 0xFFFFFFFF; *rev = cmd->params[0] & 0xFFFFFFFF; } return (error); } static int dpaa2_rc_mng_get_soc_version(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t *pvr, uint32_t *svr) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || pvr == NULL || svr == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MNG_GET_SOC_VER); if (!error) { *pvr = cmd->params[0] >> 32; *svr = cmd->params[0] & 0xFFFFFFFF; } return (error); } static int dpaa2_rc_mng_get_container_id(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t *cont_id) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || cont_id == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MNG_GET_CONT_ID); if (!error) *cont_id = cmd->params[0] & 0xFFFFFFFF; return (error); } static int dpaa2_rc_open(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t cont_id, uint16_t *token) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); struct dpaa2_cmd_header *hdr; int error; if (portal == NULL || cmd == NULL || token == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = cont_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_OPEN); if (!error) { hdr = (struct dpaa2_cmd_header *) &cmd->header; *token = hdr->token; } return (error); } static int dpaa2_rc_close(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_CLOSE)); } static int dpaa2_rc_get_obj_count(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t *obj_count) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || obj_count == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_GET_OBJ_COUNT); if (!error) *obj_count = (uint32_t)(cmd->params[0] >> 32); return (error); } static int dpaa2_rc_get_obj(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t obj_idx, struct dpaa2_obj *obj) { struct __packed dpaa2_obj_resp { uint32_t _reserved1; uint32_t id; uint16_t vendor; uint8_t irq_count; uint8_t reg_count; uint32_t state; uint16_t ver_major; uint16_t ver_minor; uint16_t flags; uint16_t _reserved2; uint8_t type[16]; uint8_t label[16]; } *pobj; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || obj == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = obj_idx; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_GET_OBJ); if (!error) { pobj = (struct dpaa2_obj_resp *) &cmd->params[0]; obj->id = pobj->id; obj->vendor = pobj->vendor; obj->irq_count = pobj->irq_count; obj->reg_count = pobj->reg_count; obj->state = pobj->state; obj->ver_major = pobj->ver_major; obj->ver_minor = pobj->ver_minor; obj->flags = pobj->flags; obj->type = dpaa2_stot((const char *) pobj->type); memcpy(obj->label, pobj->label, sizeof(pobj->label)); } /* Some DPAA2 objects might not be supported by the driver yet. */ if (obj->type == DPAA2_DEV_NOTYPE) error = DPAA2_CMD_STAT_UNKNOWN_OBJ; return (error); } static int dpaa2_rc_get_obj_descriptor(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t obj_id, enum dpaa2_dev_type dtype, struct dpaa2_obj *obj) { struct __packed get_obj_desc_args { uint32_t obj_id; uint32_t _reserved1; uint8_t type[16]; } *args; struct __packed dpaa2_obj_resp { uint32_t _reserved1; uint32_t id; uint16_t vendor; uint8_t irq_count; uint8_t reg_count; uint32_t state; uint16_t ver_major; uint16_t ver_minor; uint16_t flags; uint16_t _reserved2; uint8_t type[16]; uint8_t label[16]; } *pobj; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); const char *type = dpaa2_ttos(dtype); int error; if (portal == NULL || cmd == NULL || obj == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct get_obj_desc_args *) &cmd->params[0]; args->obj_id = obj_id; memcpy(args->type, type, min(strlen(type) + 1, TYPE_LEN_MAX)); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_GET_OBJ_DESC); if (!error) { pobj = (struct dpaa2_obj_resp *) &cmd->params[0]; obj->id = pobj->id; obj->vendor = pobj->vendor; obj->irq_count = pobj->irq_count; obj->reg_count = pobj->reg_count; obj->state = pobj->state; obj->ver_major = pobj->ver_major; obj->ver_minor = pobj->ver_minor; obj->flags = pobj->flags; obj->type = dpaa2_stot((const char *) pobj->type); memcpy(obj->label, pobj->label, sizeof(pobj->label)); } /* Some DPAA2 objects might not be supported by the driver yet. */ if (obj->type == DPAA2_DEV_NOTYPE) error = DPAA2_CMD_STAT_UNKNOWN_OBJ; return (error); } static int dpaa2_rc_get_attributes(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_rc_attr *attr) { struct __packed dpaa2_rc_attr { uint32_t cont_id; uint32_t icid; uint32_t options; uint32_t portal_id; } *pattr; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || attr == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_GET_ATTR); if (!error) { pattr = (struct dpaa2_rc_attr *) &cmd->params[0]; attr->cont_id = pattr->cont_id; attr->portal_id = pattr->portal_id; attr->options = pattr->options; attr->icid = pattr->icid; } return (error); } static int dpaa2_rc_get_obj_region(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t obj_id, uint8_t reg_idx, enum dpaa2_dev_type dtype, struct dpaa2_rc_obj_region *reg) { struct __packed obj_region_args { uint32_t obj_id; uint16_t _reserved1; uint8_t reg_idx; uint8_t _reserved2; uint64_t _reserved3; uint64_t _reserved4; uint8_t type[16]; } *args; struct __packed obj_region { uint64_t _reserved1; uint64_t base_offset; uint32_t size; uint32_t type; uint32_t flags; uint32_t _reserved2; uint64_t base_paddr; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); uint16_t cmdid, api_major, api_minor; const char *type = dpaa2_ttos(dtype); int error; if (portal == NULL || cmd == NULL || reg == NULL) return (DPAA2_CMD_STAT_ERR); /* * If the DPRC object version was not yet cached, cache it now. * Otherwise use the already cached value. */ if (!portal->rc_api_major && !portal->rc_api_minor) { error = DPAA2_CMD_RC_GET_API_VERSION(dev, child, cmd, &api_major, &api_minor); if (error) return (error); portal->rc_api_major = api_major; portal->rc_api_minor = api_minor; } else { api_major = portal->rc_api_major; api_minor = portal->rc_api_minor; } /* TODO: Remove magic numbers. */ if (api_major > 6u || (api_major == 6u && api_minor >= 6u)) /* * MC API version 6.6 changed the size of the MC portals and * software portals to 64K (as implemented by hardware). */ cmdid = CMDID_RC_GET_OBJ_REG_V3; else if (api_major == 6u && api_minor >= 3u) /* * MC API version 6.3 introduced a new field to the region * descriptor: base_address. */ cmdid = CMDID_RC_GET_OBJ_REG_V2; else cmdid = CMDID_RC_GET_OBJ_REG; args = (struct obj_region_args *) &cmd->params[0]; args->obj_id = obj_id; args->reg_idx = reg_idx; memcpy(args->type, type, min(strlen(type) + 1, TYPE_LEN_MAX)); error = dpaa2_rc_exec_cmd(portal, cmd, cmdid); if (!error) { resp = (struct obj_region *) &cmd->params[0]; reg->base_paddr = resp->base_paddr; reg->base_offset = resp->base_offset; reg->size = resp->size; reg->flags = resp->flags; reg->type = resp->type & 0xFu; } return (error); } static int dpaa2_rc_get_api_version(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint16_t *major, uint16_t *minor) { struct __packed rc_api_version { uint16_t major; uint16_t minor; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || major == NULL || minor == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_GET_API_VERSION); if (!error) { resp = (struct rc_api_version *) &cmd->params[0]; *major = resp->major; *minor = resp->minor; } return (error); } static int dpaa2_rc_set_irq_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint8_t enable) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_enable_irq(portal, cmd, irq_idx, enable, CMDID_RC_SET_IRQ_ENABLE)); } static int dpaa2_rc_set_obj_irq(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint64_t addr, uint32_t data, uint32_t irq_usr, uint32_t obj_id, enum dpaa2_dev_type dtype) { struct __packed set_obj_irq_args { uint32_t data; uint8_t irq_idx; uint8_t _reserved1[3]; uint64_t addr; uint32_t irq_usr; uint32_t obj_id; uint8_t type[16]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); const char *type = dpaa2_ttos(dtype); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct set_obj_irq_args *) &cmd->params[0]; args->irq_idx = irq_idx; args->addr = addr; args->data = data; args->irq_usr = irq_usr; args->obj_id = obj_id; memcpy(args->type, type, min(strlen(type) + 1, TYPE_LEN_MAX)); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_SET_OBJ_IRQ)); } static int dpaa2_rc_get_conn(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ep_desc *ep1_desc, struct dpaa2_ep_desc *ep2_desc, uint32_t *link_stat) { struct __packed get_conn_args { uint32_t ep1_id; uint32_t ep1_ifid; uint8_t ep1_type[16]; uint64_t _reserved[4]; } *args; struct __packed get_conn_resp { uint64_t _reserved1[3]; uint32_t ep2_id; uint32_t ep2_ifid; uint8_t ep2_type[16]; uint32_t link_stat; uint32_t _reserved2; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || ep1_desc == NULL || ep2_desc == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct get_conn_args *) &cmd->params[0]; args->ep1_id = ep1_desc->obj_id; args->ep1_ifid = ep1_desc->if_id; /* TODO: Remove magic number. */ strncpy(args->ep1_type, dpaa2_ttos(ep1_desc->type), 16); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_RC_GET_CONN); if (!error) { resp = (struct get_conn_resp *) &cmd->params[0]; ep2_desc->obj_id = resp->ep2_id; ep2_desc->if_id = resp->ep2_ifid; ep2_desc->type = dpaa2_stot((const char *) resp->ep2_type); if (link_stat != NULL) *link_stat = resp->link_stat; } return (error); } static int dpaa2_rc_ni_open(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpni_id, uint16_t *token) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); struct dpaa2_cmd_header *hdr; int error; if (portal == NULL || cmd == NULL || token == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = dpni_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_OPEN); if (!error) { hdr = (struct dpaa2_cmd_header *) &cmd->header; *token = hdr->token; } return (error); } static int dpaa2_rc_ni_close(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_CLOSE)); } static int dpaa2_rc_ni_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_ENABLE)); } static int dpaa2_rc_ni_disable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_DISABLE)); } static int dpaa2_rc_ni_get_api_version(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint16_t *major, uint16_t *minor) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || major == NULL || minor == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_API_VER); if (!error) { *major = cmd->params[0] & 0xFFFFU; *minor = (cmd->params[0] >> 16) & 0xFFFFU; } return (error); } static int dpaa2_rc_ni_reset(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_RESET)); } static int dpaa2_rc_ni_get_attributes(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_attr *attr) { struct __packed ni_attr { uint32_t options; uint8_t num_queues; uint8_t num_rx_tcs; uint8_t mac_entries; uint8_t num_tx_tcs; uint8_t vlan_entries; uint8_t num_channels; uint8_t qos_entries; uint8_t _reserved1; uint16_t fs_entries; uint16_t _reserved2; uint8_t qos_key_size; uint8_t fs_key_size; uint16_t wriop_ver; uint8_t num_cgs; uint8_t _reserved3; uint16_t _reserved4; uint64_t _reserved5[4]; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || attr == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_ATTR); if (!error) { resp = (struct ni_attr *) &cmd->params[0]; attr->options = resp->options; attr->wriop_ver = resp->wriop_ver; attr->entries.fs = resp->fs_entries; attr->entries.mac = resp->mac_entries; attr->entries.vlan = resp->vlan_entries; attr->entries.qos = resp->qos_entries; attr->num.queues = resp->num_queues; attr->num.rx_tcs = resp->num_rx_tcs; attr->num.tx_tcs = resp->num_tx_tcs; attr->num.channels = resp->num_channels; attr->num.cgs = resp->num_cgs; attr->key_size.fs = resp->fs_key_size; attr->key_size.qos = resp->qos_key_size; } return (error); } static int dpaa2_rc_ni_set_buf_layout(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_buf_layout *bl) { struct __packed set_buf_layout_args { uint8_t queue_type; uint8_t _reserved1; uint16_t _reserved2; uint16_t options; uint8_t params; uint8_t _reserved3; uint16_t priv_data_size; uint16_t data_align; uint16_t head_room; uint16_t tail_room; uint64_t _reserved4[5]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || bl == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct set_buf_layout_args *) &cmd->params[0]; args->queue_type = (uint8_t) bl->queue_type; args->options = bl->options; args->params = 0; args->priv_data_size = bl->pd_size; args->data_align = bl->fd_align; args->head_room = bl->head_size; args->tail_room = bl->tail_size; args->params |= bl->pass_timestamp ? 1U : 0U; args->params |= bl->pass_parser_result ? 2U : 0U; args->params |= bl->pass_frame_status ? 4U : 0U; args->params |= bl->pass_sw_opaque ? 8U : 0U; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_BUF_LAYOUT)); } static int dpaa2_rc_ni_get_tx_data_offset(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint16_t *offset) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || offset == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_TX_DATA_OFF); if (!error) *offset = cmd->params[0] & 0xFFFFU; return (error); } static int dpaa2_rc_ni_get_port_mac_addr(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t *mac) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || mac == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_PORT_MAC_ADDR); if (!error) { mac[0] = (cmd->params[0] >> 56) & 0xFFU; mac[1] = (cmd->params[0] >> 48) & 0xFFU; mac[2] = (cmd->params[0] >> 40) & 0xFFU; mac[3] = (cmd->params[0] >> 32) & 0xFFU; mac[4] = (cmd->params[0] >> 24) & 0xFFU; mac[5] = (cmd->params[0] >> 16) & 0xFFU; } return (error); } static int dpaa2_rc_ni_set_prim_mac_addr(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t *mac) { struct __packed set_prim_mac_args { uint8_t _reserved[2]; uint8_t mac[ETHER_ADDR_LEN]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || mac == NULL) return (DPAA2_CMD_STAT_EINVAL); args = (struct set_prim_mac_args *) &cmd->params[0]; for (int i = 1; i <= ETHER_ADDR_LEN; i++) args->mac[i - 1] = mac[ETHER_ADDR_LEN - i]; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_PRIM_MAC_ADDR)); } static int dpaa2_rc_ni_get_prim_mac_addr(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t *mac) { struct __packed get_prim_mac_resp { uint8_t _reserved[2]; uint8_t mac[ETHER_ADDR_LEN]; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || mac == NULL) return (DPAA2_CMD_STAT_EINVAL); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_PRIM_MAC_ADDR); if (!error) { resp = (struct get_prim_mac_resp *) &cmd->params[0]; for (int i = 1; i <= ETHER_ADDR_LEN; i++) mac[ETHER_ADDR_LEN - i] = resp->mac[i - 1]; } return (error); } static int dpaa2_rc_ni_set_link_cfg(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_link_cfg *cfg) { struct __packed link_cfg_args { uint64_t _reserved1; uint32_t rate; uint32_t _reserved2; uint64_t options; uint64_t adv_speeds; uint64_t _reserved3[3]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || cfg == NULL) return (DPAA2_CMD_STAT_EINVAL); args = (struct link_cfg_args *) &cmd->params[0]; args->rate = cfg->rate; args->options = cfg->options; args->adv_speeds = cfg->adv_speeds; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_LINK_CFG)); } static int dpaa2_rc_ni_get_link_cfg(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_link_cfg *cfg) { struct __packed link_cfg_resp { uint64_t _reserved1; uint32_t rate; uint32_t _reserved2; uint64_t options; uint64_t adv_speeds; uint64_t _reserved3[3]; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || cfg == NULL) return (DPAA2_CMD_STAT_EINVAL); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_LINK_CFG); if (!error) { resp = (struct link_cfg_resp *) &cmd->params[0]; cfg->rate = resp->rate; cfg->options = resp->options; cfg->adv_speeds = resp->adv_speeds; } return (error); } static int dpaa2_rc_ni_get_link_state(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_link_state *state) { struct __packed link_state_resp { uint32_t _reserved1; uint32_t flags; uint32_t rate; uint32_t _reserved2; uint64_t options; uint64_t supported; uint64_t advert; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || state == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_LINK_STATE); if (!error) { resp = (struct link_state_resp *) &cmd->params[0]; state->options = resp->options; state->adv_speeds = resp->advert; state->sup_speeds = resp->supported; state->rate = resp->rate; state->link_up = resp->flags & 0x1u ? true : false; state->state_valid = resp->flags & 0x2u ? true : false; } return (error); } static int dpaa2_rc_ni_set_qos_table(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_qos_table *tbl) { struct __packed qos_table_args { uint32_t _reserved1; uint8_t default_tc; uint8_t options; uint16_t _reserved2; uint64_t _reserved[5]; uint64_t kcfg_busaddr; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || tbl == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct qos_table_args *) &cmd->params[0]; args->default_tc = tbl->default_tc; args->kcfg_busaddr = tbl->kcfg_busaddr; args->options |= tbl->discard_on_miss ? 1U : 0U; args->options |= tbl->keep_entries ? 2U : 0U; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_QOS_TABLE)); } static int dpaa2_rc_ni_clear_qos_table(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_CLEAR_QOS_TABLE)); } static int dpaa2_rc_ni_set_pools(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_pools_cfg *cfg) { struct __packed set_pools_args { uint8_t pools_num; uint8_t backup_pool_mask; uint8_t _reserved1; uint8_t pool_as; /* assigning: 0 - QPRI, 1 - QDBIN */ uint32_t bp_obj_id[DPAA2_NI_MAX_POOLS]; uint16_t buf_sz[DPAA2_NI_MAX_POOLS]; uint32_t _reserved2; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || cfg == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_pools_args *) &cmd->params[0]; args->pools_num = cfg->pools_num < DPAA2_NI_MAX_POOLS ? cfg->pools_num : DPAA2_NI_MAX_POOLS; for (uint32_t i = 0; i < args->pools_num; i++) { args->bp_obj_id[i] = cfg->pools[i].bp_obj_id; args->buf_sz[i] = cfg->pools[i].buf_sz; args->backup_pool_mask |= (cfg->pools[i].backup_flag & 1) << i; } return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_POOLS)); } static int dpaa2_rc_ni_set_err_behavior(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_err_cfg *cfg) { struct __packed err_behavior_args { uint32_t err_mask; uint8_t flags; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || cfg == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct err_behavior_args *) &cmd->params[0]; args->err_mask = cfg->err_mask; args->flags |= cfg->set_err_fas ? 0x10u : 0u; args->flags |= ((uint8_t) cfg->action) & 0x0Fu; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_ERR_BEHAVIOR)); } static int dpaa2_rc_ni_get_queue(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_queue_cfg *cfg) { struct __packed get_queue_args { uint8_t queue_type; uint8_t tc; uint8_t idx; uint8_t chan_id; } *args; struct __packed get_queue_resp { uint64_t _reserved1; uint32_t dest_id; uint16_t _reserved2; uint8_t priority; uint8_t flags; uint64_t flc; uint64_t user_ctx; uint32_t fqid; uint16_t qdbin; uint16_t _reserved3; uint8_t cgid; uint8_t _reserved[15]; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || cfg == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct get_queue_args *) &cmd->params[0]; args->queue_type = (uint8_t) cfg->type; args->tc = cfg->tc; args->idx = cfg->idx; args->chan_id = cfg->chan_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_QUEUE); if (!error) { resp = (struct get_queue_resp *) &cmd->params[0]; cfg->dest_id = resp->dest_id; cfg->priority = resp->priority; cfg->flow_ctx = resp->flc; cfg->user_ctx = resp->user_ctx; cfg->fqid = resp->fqid; cfg->qdbin = resp->qdbin; cfg->cgid = resp->cgid; cfg->dest_type = (enum dpaa2_ni_dest_type) resp->flags & 0x0Fu; cfg->cgid_valid = (resp->flags & 0x20u) > 0u ? true : false; cfg->stash_control = (resp->flags & 0x40u) > 0u ? true : false; cfg->hold_active = (resp->flags & 0x80u) > 0u ? true : false; } return (error); } static int dpaa2_rc_ni_set_queue(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_ni_queue_cfg *cfg) { struct __packed set_queue_args { uint8_t queue_type; uint8_t tc; uint8_t idx; uint8_t options; uint32_t _reserved1; uint32_t dest_id; uint16_t _reserved2; uint8_t priority; uint8_t flags; uint64_t flc; uint64_t user_ctx; uint8_t cgid; uint8_t chan_id; uint8_t _reserved[23]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || cfg == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_queue_args *) &cmd->params[0]; args->queue_type = (uint8_t) cfg->type; args->tc = cfg->tc; args->idx = cfg->idx; args->options = cfg->options; args->dest_id = cfg->dest_id; args->priority = cfg->priority; args->flc = cfg->flow_ctx; args->user_ctx = cfg->user_ctx; args->cgid = cfg->cgid; args->chan_id = cfg->chan_id; args->flags |= (uint8_t)(cfg->dest_type & 0x0Fu); args->flags |= cfg->stash_control ? 0x40u : 0u; args->flags |= cfg->hold_active ? 0x80u : 0u; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_QUEUE)); } static int dpaa2_rc_ni_get_qdid(device_t dev, device_t child, struct dpaa2_cmd *cmd, enum dpaa2_ni_queue_type type, uint16_t *qdid) { struct __packed get_qdid_args { uint8_t queue_type; } *args; struct __packed get_qdid_resp { uint16_t qdid; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || qdid == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct get_qdid_args *) &cmd->params[0]; args->queue_type = (uint8_t) type; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_QDID); if (!error) { resp = (struct get_qdid_resp *) &cmd->params[0]; *qdid = resp->qdid; } return (error); } static int dpaa2_rc_ni_add_mac_addr(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t *mac) { struct __packed add_mac_args { uint8_t flags; uint8_t _reserved; uint8_t mac[ETHER_ADDR_LEN]; uint8_t tc_id; uint8_t fq_id; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || mac == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct add_mac_args *) &cmd->params[0]; for (int i = 1; i <= ETHER_ADDR_LEN; i++) args->mac[i - 1] = mac[ETHER_ADDR_LEN - i]; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_ADD_MAC_ADDR)); } static int dpaa2_rc_ni_remove_mac_addr(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t *mac) { struct __packed rem_mac_args { uint16_t _reserved; uint8_t mac[ETHER_ADDR_LEN]; uint64_t _reserved1[6]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || mac == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct rem_mac_args *) &cmd->params[0]; for (int i = 1; i <= ETHER_ADDR_LEN; i++) args->mac[i - 1] = mac[ETHER_ADDR_LEN - i]; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_REMOVE_MAC_ADDR)); } static int dpaa2_rc_ni_clear_mac_filters(device_t dev, device_t child, struct dpaa2_cmd *cmd, bool rm_uni, bool rm_multi) { struct __packed clear_mac_filters_args { uint8_t flags; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct clear_mac_filters_args *) &cmd->params[0]; args->flags |= rm_uni ? 0x1 : 0x0; args->flags |= rm_multi ? 0x2 : 0x0; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_CLEAR_MAC_FILTERS)); } static int dpaa2_rc_ni_set_mfl(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint16_t length) { struct __packed set_mfl_args { uint16_t length; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_mfl_args *) &cmd->params[0]; args->length = length; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_MFL)); } static int dpaa2_rc_ni_set_offload(device_t dev, device_t child, struct dpaa2_cmd *cmd, enum dpaa2_ni_ofl_type ofl_type, bool en) { struct __packed set_ofl_args { uint8_t _reserved[3]; uint8_t ofl_type; uint32_t config; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_ofl_args *) &cmd->params[0]; args->ofl_type = (uint8_t) ofl_type; args->config = en ? 1u : 0u; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_OFFLOAD)); } static int dpaa2_rc_ni_set_irq_mask(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint32_t mask) { struct __packed set_irq_mask_args { uint32_t mask; uint8_t irq_idx; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_irq_mask_args *) &cmd->params[0]; args->mask = mask; args->irq_idx = irq_idx; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_IRQ_MASK)); } static int dpaa2_rc_ni_set_irq_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, bool en) { struct __packed set_irq_enable_args { uint32_t en; uint8_t irq_idx; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_irq_enable_args *) &cmd->params[0]; args->en = en ? 1u : 0u; args->irq_idx = irq_idx; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_IRQ_ENABLE)); } static int dpaa2_rc_ni_get_irq_status(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint32_t *status) { struct __packed get_irq_stat_args { uint32_t status; uint8_t irq_idx; } *args; struct __packed get_irq_stat_resp { uint32_t status; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || status == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct get_irq_stat_args *) &cmd->params[0]; args->status = *status; args->irq_idx = irq_idx; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_IRQ_STATUS); if (!error) { resp = (struct get_irq_stat_resp *) &cmd->params[0]; *status = resp->status; } return (error); } static int dpaa2_rc_ni_set_uni_promisc(device_t dev, device_t child, struct dpaa2_cmd *cmd, bool en) { struct __packed set_uni_promisc_args { uint8_t en; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_uni_promisc_args *) &cmd->params[0]; args->en = en ? 1u : 0u; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_UNI_PROMISC)); } static int dpaa2_rc_ni_set_multi_promisc(device_t dev, device_t child, struct dpaa2_cmd *cmd, bool en) { /* TODO: Implementation is the same as for ni_set_uni_promisc(). */ struct __packed set_multi_promisc_args { uint8_t en; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_multi_promisc_args *) &cmd->params[0]; args->en = en ? 1u : 0u; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_MULTI_PROMISC)); } static int dpaa2_rc_ni_get_statistics(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t page, uint16_t param, uint64_t *cnt) { struct __packed get_statistics_args { uint8_t page; uint16_t param; } *args; struct __packed get_statistics_resp { uint64_t cnt[7]; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || cnt == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct get_statistics_args *) &cmd->params[0]; args->page = page; args->param = param; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_GET_STATISTICS); if (!error) { resp = (struct get_statistics_resp *) &cmd->params[0]; for (int i = 0; i < DPAA2_NI_STAT_COUNTERS; i++) cnt[i] = resp->cnt[i]; } return (error); } static int dpaa2_rc_ni_set_rx_tc_dist(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint16_t dist_size, uint8_t tc, enum dpaa2_ni_dist_mode dist_mode, bus_addr_t key_cfg_buf) { struct __packed set_rx_tc_dist_args { uint16_t dist_size; uint8_t tc; uint8_t ma_dm; /* miss action + dist. mode */ uint32_t _reserved1; uint64_t _reserved2[5]; uint64_t key_cfg_iova; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_rx_tc_dist_args *) &cmd->params[0]; args->dist_size = dist_size; args->tc = tc; args->ma_dm = ((uint8_t) dist_mode) & 0x0Fu; args->key_cfg_iova = key_cfg_buf; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_NI_SET_RX_TC_DIST)); } static int dpaa2_rc_io_open(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpio_id, uint16_t *token) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); struct dpaa2_cmd_header *hdr; int error; if (portal == NULL || cmd == NULL || token == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = dpio_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_OPEN); if (!error) { hdr = (struct dpaa2_cmd_header *) &cmd->header; *token = hdr->token; } return (error); } static int dpaa2_rc_io_close(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_CLOSE)); } static int dpaa2_rc_io_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_ENABLE)); } static int dpaa2_rc_io_disable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_DISABLE)); } static int dpaa2_rc_io_reset(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_RESET)); } static int dpaa2_rc_io_get_attributes(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_io_attr *attr) { struct __packed dpaa2_io_attr { uint32_t id; uint16_t swp_id; uint8_t priors_num; uint8_t chan_mode; uint64_t swp_ce_paddr; uint64_t swp_ci_paddr; uint32_t swp_version; uint32_t _reserved1; uint32_t swp_clk; uint32_t _reserved2[5]; } *pattr; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || attr == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_GET_ATTR); if (!error) { pattr = (struct dpaa2_io_attr *) &cmd->params[0]; attr->swp_ce_paddr = pattr->swp_ce_paddr; attr->swp_ci_paddr = pattr->swp_ci_paddr; attr->swp_version = pattr->swp_version; attr->swp_clk = pattr->swp_clk; attr->id = pattr->id; attr->swp_id = pattr->swp_id; attr->priors_num = pattr->priors_num; attr->chan_mode = (enum dpaa2_io_chan_mode) pattr->chan_mode; } return (error); } static int dpaa2_rc_io_set_irq_mask(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint32_t mask) { /* TODO: Extract similar *_set_irq_mask() into one function. */ struct __packed set_irq_mask_args { uint32_t mask; uint8_t irq_idx; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_irq_mask_args *) &cmd->params[0]; args->mask = mask; args->irq_idx = irq_idx; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_SET_IRQ_MASK)); } static int dpaa2_rc_io_get_irq_status(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint32_t *status) { /* TODO: Extract similar *_get_irq_status() into one function. */ struct __packed get_irq_stat_args { uint32_t status; uint8_t irq_idx; } *args; struct __packed get_irq_stat_resp { uint32_t status; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || status == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct get_irq_stat_args *) &cmd->params[0]; args->status = *status; args->irq_idx = irq_idx; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_GET_IRQ_STATUS); if (!error) { resp = (struct get_irq_stat_resp *) &cmd->params[0]; *status = resp->status; } return (error); } static int dpaa2_rc_io_set_irq_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, bool en) { /* TODO: Extract similar *_set_irq_enable() into one function. */ struct __packed set_irq_enable_args { uint32_t en; uint8_t irq_idx; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_irq_enable_args *) &cmd->params[0]; args->en = en ? 1u : 0u; args->irq_idx = irq_idx; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_SET_IRQ_ENABLE)); } static int dpaa2_rc_io_add_static_dq_chan(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpcon_id, uint8_t *chan_idx) { struct __packed add_static_dq_chan_args { uint32_t dpcon_id; } *args; struct __packed add_static_dq_chan_resp { uint8_t chan_idx; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || chan_idx == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct add_static_dq_chan_args *) &cmd->params[0]; args->dpcon_id = dpcon_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_IO_ADD_STATIC_DQ_CHAN); if (!error) { resp = (struct add_static_dq_chan_resp *) &cmd->params[0]; *chan_idx = resp->chan_idx; } return (error); } static int dpaa2_rc_bp_open(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpbp_id, uint16_t *token) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); struct dpaa2_cmd_header *hdr; int error; if (portal == NULL || cmd == NULL || token == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = dpbp_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_BP_OPEN); if (!error) { hdr = (struct dpaa2_cmd_header *) &cmd->header; *token = hdr->token; } return (error); } static int dpaa2_rc_bp_close(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_BP_CLOSE)); } static int dpaa2_rc_bp_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_BP_ENABLE)); } static int dpaa2_rc_bp_disable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_BP_DISABLE)); } static int dpaa2_rc_bp_reset(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_BP_RESET)); } static int dpaa2_rc_bp_get_attributes(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_bp_attr *attr) { struct __packed dpaa2_bp_attr { uint16_t _reserved1; uint16_t bpid; uint32_t id; } *pattr; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || attr == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_BP_GET_ATTR); if (!error) { pattr = (struct dpaa2_bp_attr *) &cmd->params[0]; attr->id = pattr->id; attr->bpid = pattr->bpid; } return (error); } static int dpaa2_rc_mac_open(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpmac_id, uint16_t *token) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); struct dpaa2_cmd_header *hdr; int error; if (portal == NULL || cmd == NULL || token == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = dpmac_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_OPEN); if (!error) { hdr = (struct dpaa2_cmd_header *) &cmd->header; *token = hdr->token; } return (error); } static int dpaa2_rc_mac_close(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_CLOSE)); } static int dpaa2_rc_mac_reset(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_RESET)); } static int dpaa2_rc_mac_mdio_read(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t phy, uint16_t reg, uint16_t *val) { struct __packed mdio_read_args { uint8_t clause; /* set to 0 by default */ uint8_t phy; uint16_t reg; uint32_t _reserved1; uint64_t _reserved2[6]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || val == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct mdio_read_args *) &cmd->params[0]; args->phy = phy; args->reg = reg; args->clause = 0; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_MDIO_READ); if (!error) *val = cmd->params[0] & 0xFFFF; return (error); } static int dpaa2_rc_mac_mdio_write(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t phy, uint16_t reg, uint16_t val) { struct __packed mdio_write_args { uint8_t clause; /* set to 0 by default */ uint8_t phy; uint16_t reg; uint16_t val; uint16_t _reserved1; uint64_t _reserved2[6]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct mdio_write_args *) &cmd->params[0]; args->phy = phy; args->reg = reg; args->val = val; args->clause = 0; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_MDIO_WRITE)); } static int dpaa2_rc_mac_get_addr(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t *mac) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || mac == NULL) return (DPAA2_CMD_STAT_ERR); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_GET_ADDR); if (!error) { mac[0] = (cmd->params[0] >> 56) & 0xFFU; mac[1] = (cmd->params[0] >> 48) & 0xFFU; mac[2] = (cmd->params[0] >> 40) & 0xFFU; mac[3] = (cmd->params[0] >> 32) & 0xFFU; mac[4] = (cmd->params[0] >> 24) & 0xFFU; mac[5] = (cmd->params[0] >> 16) & 0xFFU; } return (error); } static int dpaa2_rc_mac_get_attributes(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_mac_attr *attr) { struct __packed mac_attr_resp { uint8_t eth_if; uint8_t link_type; uint16_t id; uint32_t max_rate; uint8_t fec_mode; uint8_t ifg_mode; uint8_t ifg_len; uint8_t _reserved1; uint32_t _reserved2; uint8_t sgn_post_pre; uint8_t serdes_cfg_mode; uint8_t eq_amp_red; uint8_t eq_post1q; uint8_t eq_preq; uint8_t eq_type; uint16_t _reserved3; uint64_t _reserved[4]; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || attr == NULL) return (DPAA2_CMD_STAT_EINVAL); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_GET_ATTR); if (!error) { resp = (struct mac_attr_resp *) &cmd->params[0]; attr->id = resp->id; attr->max_rate = resp->max_rate; attr->eth_if = resp->eth_if; attr->link_type = resp->link_type; } return (error); } static int dpaa2_rc_mac_set_link_state(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_mac_link_state *state) { struct __packed mac_set_link_args { uint64_t options; uint32_t rate; uint32_t _reserved1; uint32_t flags; uint32_t _reserved2; uint64_t supported; uint64_t advert; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || state == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct mac_set_link_args *) &cmd->params[0]; args->options = state->options; args->rate = state->rate; args->supported = state->supported; args->advert = state->advert; args->flags |= state->up ? 0x1u : 0u; args->flags |= state->state_valid ? 0x2u : 0u; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_SET_LINK_STATE)); } static int dpaa2_rc_mac_set_irq_mask(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint32_t mask) { /* TODO: Implementation is the same as for ni_set_irq_mask(). */ struct __packed set_irq_mask_args { uint32_t mask; uint8_t irq_idx; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_irq_mask_args *) &cmd->params[0]; args->mask = mask; args->irq_idx = irq_idx; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_SET_IRQ_MASK)); } static int dpaa2_rc_mac_set_irq_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, bool en) { /* TODO: Implementation is the same as for ni_set_irq_enable(). */ struct __packed set_irq_enable_args { uint32_t en; uint8_t irq_idx; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct set_irq_enable_args *) &cmd->params[0]; args->en = en ? 1u : 0u; args->irq_idx = irq_idx; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_SET_IRQ_ENABLE)); } static int dpaa2_rc_mac_get_irq_status(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint8_t irq_idx, uint32_t *status) { /* TODO: Implementation is the same as ni_get_irq_status(). */ struct __packed get_irq_stat_args { uint32_t status; uint8_t irq_idx; } *args; struct __packed get_irq_stat_resp { uint32_t status; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || status == NULL) return (DPAA2_CMD_STAT_EINVAL); dpaa2_rc_reset_cmd_params(cmd); args = (struct get_irq_stat_args *) &cmd->params[0]; args->status = *status; args->irq_idx = irq_idx; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MAC_GET_IRQ_STATUS); if (!error) { resp = (struct get_irq_stat_resp *) &cmd->params[0]; *status = resp->status; } return (error); } static int dpaa2_rc_con_open(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpcon_id, uint16_t *token) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); struct dpaa2_cmd_header *hdr; int error; if (portal == NULL || cmd == NULL || token == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = dpcon_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_CON_OPEN); if (!error) { hdr = (struct dpaa2_cmd_header *) &cmd->header; *token = hdr->token; } return (error); } static int dpaa2_rc_con_close(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_CON_CLOSE)); } static int dpaa2_rc_con_reset(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_CON_RESET)); } static int dpaa2_rc_con_enable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_CON_ENABLE)); } static int dpaa2_rc_con_disable(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_CON_DISABLE)); } static int dpaa2_rc_con_get_attributes(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_con_attr *attr) { struct __packed con_attr_resp { uint32_t id; uint16_t chan_id; uint8_t prior_num; uint8_t _reserved1; uint64_t _reserved2[6]; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || attr == NULL) return (DPAA2_CMD_STAT_EINVAL); error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_CON_GET_ATTR); if (!error) { resp = (struct con_attr_resp *) &cmd->params[0]; attr->id = resp->id; attr->chan_id = resp->chan_id; attr->prior_num = resp->prior_num; } return (error); } static int dpaa2_rc_con_set_notif(device_t dev, device_t child, struct dpaa2_cmd *cmd, struct dpaa2_con_notif_cfg *cfg) { struct __packed set_notif_args { uint32_t dpio_id; uint8_t prior; uint8_t _reserved1; uint16_t _reserved2; uint64_t ctx; uint64_t _reserved3[5]; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL || cfg == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct set_notif_args *) &cmd->params[0]; args->dpio_id = cfg->dpio_id; args->prior = cfg->prior; args->ctx = cfg->qman_ctx; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_CON_SET_NOTIF)); } static int dpaa2_rc_mcp_create(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t portal_id, uint32_t options, uint32_t *dpmcp_id) { struct __packed mcp_create_args { uint32_t portal_id; uint32_t options; uint64_t _reserved[6]; } *args; struct __packed mcp_create_resp { uint32_t dpmcp_id; } *resp; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); int error; if (portal == NULL || cmd == NULL || dpmcp_id == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct mcp_create_args *) &cmd->params[0]; args->portal_id = portal_id; args->options = options; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MCP_CREATE); if (!error) { resp = (struct mcp_create_resp *) &cmd->params[0]; *dpmcp_id = resp->dpmcp_id; } return (error); } static int dpaa2_rc_mcp_destroy(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpmcp_id) { struct __packed mcp_destroy_args { uint32_t dpmcp_id; } *args; struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); args = (struct mcp_destroy_args *) &cmd->params[0]; args->dpmcp_id = dpmcp_id; return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MCP_DESTROY)); } static int dpaa2_rc_mcp_open(device_t dev, device_t child, struct dpaa2_cmd *cmd, uint32_t dpmcp_id, uint16_t *token) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); struct dpaa2_cmd_header *hdr; int error; if (portal == NULL || cmd == NULL || token == NULL) return (DPAA2_CMD_STAT_ERR); cmd->params[0] = dpmcp_id; error = dpaa2_rc_exec_cmd(portal, cmd, CMDID_MCP_OPEN); if (!error) { hdr = (struct dpaa2_cmd_header *) &cmd->header; *token = hdr->token; } return (error); } static int dpaa2_rc_mcp_close(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MCP_CLOSE)); } static int dpaa2_rc_mcp_reset(device_t dev, device_t child, struct dpaa2_cmd *cmd) { struct dpaa2_mcp *portal = dpaa2_rc_select_portal(dev, child); if (portal == NULL || cmd == NULL) return (DPAA2_CMD_STAT_ERR); return (dpaa2_rc_exec_cmd(portal, cmd, CMDID_MCP_RESET)); } /** * @brief Create and add devices for DPAA2 objects in this resource container. */ static int dpaa2_rc_discover(struct dpaa2_rc_softc *sc) { device_t rcdev = sc->dev; device_t child = sc->dev; struct dpaa2_devinfo *rcinfo = device_get_ivars(rcdev); struct dpaa2_cmd cmd; struct dpaa2_rc_attr dprc_attr; struct dpaa2_obj obj; uint32_t major, minor, rev, obj_count; uint16_t rc_token; int rc; DPAA2_CMD_INIT(&cmd); /* Print MC firmware version. */ rc = DPAA2_CMD_MNG_GET_VERSION(rcdev, child, &cmd, &major, &minor, &rev); if (rc) { device_printf(rcdev, "%s: failed to get MC firmware version: " "error=%d\n", __func__, rc); return (ENXIO); } device_printf(rcdev, "MC firmware version: %u.%u.%u\n", major, minor, rev); /* Obtain container ID associated with a given MC portal. */ rc = DPAA2_CMD_MNG_GET_CONTAINER_ID(rcdev, child, &cmd, &sc->cont_id); if (rc) { device_printf(rcdev, "%s: failed to get container id: " "error=%d\n", __func__, rc); return (ENXIO); } if (bootverbose) { device_printf(rcdev, "Resource container ID: %u\n", sc->cont_id); } /* Open the resource container. */ rc = DPAA2_CMD_RC_OPEN(rcdev, child, &cmd, sc->cont_id, &rc_token); if (rc) { device_printf(rcdev, "%s: failed to open container: cont_id=%u, " "error=%d\n", __func__, sc->cont_id, rc); return (ENXIO); } /* Obtain a number of objects in this container. */ rc = DPAA2_CMD_RC_GET_OBJ_COUNT(rcdev, child, &cmd, &obj_count); if (rc) { device_printf(rcdev, "%s: failed to count objects in container: " "cont_id=%u, error=%d\n", __func__, sc->cont_id, rc); (void)DPAA2_CMD_RC_CLOSE(rcdev, child, &cmd); return (ENXIO); } if (bootverbose) { device_printf(rcdev, "Objects in container: %u\n", obj_count); } rc = DPAA2_CMD_RC_GET_ATTRIBUTES(rcdev, child, &cmd, &dprc_attr); if (rc) { device_printf(rcdev, "%s: failed to get attributes of the " "container: cont_id=%u, error=%d\n", __func__, sc->cont_id, rc); DPAA2_CMD_RC_CLOSE(rcdev, child, &cmd); return (ENXIO); } if (bootverbose) { device_printf(rcdev, "Isolation context ID: %u\n", dprc_attr.icid); } if (rcinfo) { rcinfo->id = dprc_attr.cont_id; rcinfo->portal_id = dprc_attr.portal_id; rcinfo->icid = dprc_attr.icid; } /* * Add MC portals before everything else. * TODO: Discover DPAA2 objects on-demand. */ for (uint32_t i = 0; i < obj_count; i++) { rc = DPAA2_CMD_RC_GET_OBJ(rcdev, child, &cmd, i, &obj); if (rc) { continue; /* Skip silently for now. */ } if (obj.type != DPAA2_DEV_MCP) { continue; } dpaa2_rc_add_managed_child(sc, &cmd, &obj); } /* Probe and attach MC portals. */ bus_identify_children(rcdev); bus_attach_children(rcdev); /* Add managed devices (except DPMCPs) to the resource container. */ for (uint32_t i = 0; i < obj_count; i++) { rc = DPAA2_CMD_RC_GET_OBJ(rcdev, child, &cmd, i, &obj); if (rc && bootverbose) { if (rc == DPAA2_CMD_STAT_UNKNOWN_OBJ) { device_printf(rcdev, "%s: skip unsupported " "DPAA2 object: idx=%u\n", __func__, i); continue; } else { device_printf(rcdev, "%s: failed to get " "information about DPAA2 object: idx=%u, " "error=%d\n", __func__, i, rc); continue; } } if (obj.type == DPAA2_DEV_MCP) { continue; /* Already added. */ } dpaa2_rc_add_managed_child(sc, &cmd, &obj); } /* Probe and attach managed devices properly. */ bus_identify_children(rcdev); bus_attach_children(rcdev); /* Add other devices to the resource container. */ for (uint32_t i = 0; i < obj_count; i++) { rc = DPAA2_CMD_RC_GET_OBJ(rcdev, child, &cmd, i, &obj); if (rc == DPAA2_CMD_STAT_UNKNOWN_OBJ && bootverbose) { device_printf(rcdev, "%s: skip unsupported DPAA2 " "object: idx=%u\n", __func__, i); continue; } else if (rc) { device_printf(rcdev, "%s: failed to get object: " "idx=%u, error=%d\n", __func__, i, rc); continue; } dpaa2_rc_add_child(sc, &cmd, &obj); } DPAA2_CMD_RC_CLOSE(rcdev, child, &cmd); /* Probe and attach the rest of devices. */ bus_identify_children(rcdev); bus_attach_children(rcdev); return (0); } /** * @brief Add a new DPAA2 device to the resource container bus. */ static int dpaa2_rc_add_child(struct dpaa2_rc_softc *sc, struct dpaa2_cmd *cmd, struct dpaa2_obj *obj) { device_t rcdev, dev; struct dpaa2_devinfo *rcinfo; struct dpaa2_devinfo *dinfo; struct resource_spec *res_spec; const char *devclass; int dpio_n = 0; /* to limit DPIOs by # of CPUs */ int dpcon_n = 0; /* to limit DPCONs by # of CPUs */ int rid, error; rcdev = sc->dev; rcinfo = device_get_ivars(rcdev); switch (obj->type) { case DPAA2_DEV_NI: devclass = "dpaa2_ni"; res_spec = dpaa2_ni_spec; break; default: return (ENXIO); } /* Add a device for the DPAA2 object. */ - dev = device_add_child(rcdev, devclass, -1); + dev = device_add_child(rcdev, devclass, DEVICE_UNIT_ANY); if (dev == NULL) { device_printf(rcdev, "%s: failed to add a device for DPAA2 " "object: type=%s, id=%u\n", __func__, dpaa2_ttos(obj->type), obj->id); return (ENXIO); } /* Allocate devinfo for a child. */ dinfo = malloc(sizeof(struct dpaa2_devinfo), M_DPAA2_RC, M_WAITOK | M_ZERO); if (!dinfo) { device_printf(rcdev, "%s: failed to allocate dpaa2_devinfo " "for: type=%s, id=%u\n", __func__, dpaa2_ttos(obj->type), obj->id); return (ENXIO); } device_set_ivars(dev, dinfo); dinfo->pdev = rcdev; dinfo->dev = dev; dinfo->id = obj->id; dinfo->dtype = obj->type; dinfo->portal = NULL; /* Children share their parent container's ICID and portal ID. */ dinfo->icid = rcinfo->icid; dinfo->portal_id = rcinfo->portal_id; /* MSI configuration */ dinfo->msi.msi_msgnum = obj->irq_count; dinfo->msi.msi_alloc = 0; dinfo->msi.msi_handlers = 0; /* Initialize a resource list for the child. */ resource_list_init(&dinfo->resources); /* Add DPAA2-specific resources to the resource list. */ for (; res_spec && res_spec->type != -1; res_spec++) { if (res_spec->type < DPAA2_DEV_MC) continue; /* Skip non-DPAA2 resource. */ rid = res_spec->rid; /* Limit DPIOs and DPCONs by number of CPUs. */ if (res_spec->type == DPAA2_DEV_IO && dpio_n >= mp_ncpus) { dpio_n++; continue; } if (res_spec->type == DPAA2_DEV_CON && dpcon_n >= mp_ncpus) { dpcon_n++; continue; } error = dpaa2_rc_add_res(rcdev, dev, res_spec->type, &rid, res_spec->flags); if (error) device_printf(rcdev, "%s: dpaa2_rc_add_res() failed: " "error=%d\n", __func__, error); if (res_spec->type == DPAA2_DEV_IO) dpio_n++; if (res_spec->type == DPAA2_DEV_CON) dpcon_n++; } return (0); } /** * @brief Add a new managed DPAA2 device to the resource container bus. * * There are DPAA2 objects (DPIO, DPBP) which have their own drivers and can be * allocated as resources or associated with the other DPAA2 objects. This * function is supposed to discover such managed objects in the resource * container and add them as children to perform a proper initialization. * * NOTE: It must be called together with bus_identify_children() and * bus_attach_children() before dpaa2_rc_add_child(). */ static int dpaa2_rc_add_managed_child(struct dpaa2_rc_softc *sc, struct dpaa2_cmd *cmd, struct dpaa2_obj *obj) { device_t rcdev, dev, child; struct dpaa2_devinfo *rcinfo, *dinfo; struct dpaa2_rc_obj_region reg; struct resource_spec *res_spec; const char *devclass; uint64_t start, end, count; uint32_t flags = 0; int rid, error; rcdev = sc->dev; child = sc->dev; rcinfo = device_get_ivars(rcdev); switch (obj->type) { case DPAA2_DEV_IO: devclass = "dpaa2_io"; res_spec = dpaa2_io_spec; flags = DPAA2_MC_DEV_ALLOCATABLE | DPAA2_MC_DEV_SHAREABLE; break; case DPAA2_DEV_BP: devclass = "dpaa2_bp"; res_spec = dpaa2_bp_spec; flags = DPAA2_MC_DEV_ALLOCATABLE; break; case DPAA2_DEV_CON: devclass = "dpaa2_con"; res_spec = dpaa2_con_spec; flags = DPAA2_MC_DEV_ALLOCATABLE; break; case DPAA2_DEV_MAC: devclass = "dpaa2_mac"; res_spec = dpaa2_mac_spec; flags = DPAA2_MC_DEV_ASSOCIATED; break; case DPAA2_DEV_MCP: devclass = "dpaa2_mcp"; res_spec = NULL; flags = DPAA2_MC_DEV_ALLOCATABLE | DPAA2_MC_DEV_SHAREABLE; break; default: /* Only managed devices above are supported. */ return (EINVAL); } /* Add a device for the DPAA2 object. */ - dev = device_add_child(rcdev, devclass, -1); + dev = device_add_child(rcdev, devclass, DEVICE_UNIT_ANY); if (dev == NULL) { device_printf(rcdev, "%s: failed to add a device for DPAA2 " "object: type=%s, id=%u\n", __func__, dpaa2_ttos(obj->type), obj->id); return (ENXIO); } /* Allocate devinfo for the child. */ dinfo = malloc(sizeof(struct dpaa2_devinfo), M_DPAA2_RC, M_WAITOK | M_ZERO); if (!dinfo) { device_printf(rcdev, "%s: failed to allocate dpaa2_devinfo " "for: type=%s, id=%u\n", __func__, dpaa2_ttos(obj->type), obj->id); return (ENXIO); } device_set_ivars(dev, dinfo); dinfo->pdev = rcdev; dinfo->dev = dev; dinfo->id = obj->id; dinfo->dtype = obj->type; dinfo->portal = NULL; /* Children share their parent container's ICID and portal ID. */ dinfo->icid = rcinfo->icid; dinfo->portal_id = rcinfo->portal_id; /* MSI configuration */ dinfo->msi.msi_msgnum = obj->irq_count; dinfo->msi.msi_alloc = 0; dinfo->msi.msi_handlers = 0; /* Initialize a resource list for the child. */ resource_list_init(&dinfo->resources); /* Add memory regions to the resource list. */ for (uint8_t i = 0; i < obj->reg_count; i++) { error = DPAA2_CMD_RC_GET_OBJ_REGION(rcdev, child, cmd, obj->id, i, obj->type, ®); if (error) { device_printf(rcdev, "%s: failed to obtain memory " "region for type=%s, id=%u, reg_idx=%u: error=%d\n", __func__, dpaa2_ttos(obj->type), obj->id, i, error); continue; } count = reg.size; start = reg.base_paddr + reg.base_offset; end = reg.base_paddr + reg.base_offset + reg.size - 1; resource_list_add(&dinfo->resources, SYS_RES_MEMORY, i, start, end, count); } /* Add DPAA2-specific resources to the resource list. */ for (; res_spec && res_spec->type != -1; res_spec++) { if (res_spec->type < DPAA2_DEV_MC) continue; /* Skip non-DPAA2 resource. */ rid = res_spec->rid; error = dpaa2_rc_add_res(rcdev, dev, res_spec->type, &rid, res_spec->flags); if (error) device_printf(rcdev, "%s: dpaa2_rc_add_res() failed: " "error=%d\n", __func__, error); } /* Inform MC about a new managed device. */ error = DPAA2_MC_MANAGE_DEV(rcdev, dev, flags); if (error) { device_printf(rcdev, "%s: failed to add a managed DPAA2 device: " "type=%s, id=%u, error=%d\n", __func__, dpaa2_ttos(obj->type), obj->id, error); return (ENXIO); } return (0); } /** * @brief Configure given IRQ using MC command interface. */ static int dpaa2_rc_configure_irq(device_t rcdev, device_t child, int rid, uint64_t addr, uint32_t data) { struct dpaa2_devinfo *rcinfo; struct dpaa2_devinfo *dinfo; struct dpaa2_cmd cmd; uint16_t rc_token; int rc = EINVAL; DPAA2_CMD_INIT(&cmd); if (device_get_parent(child) == rcdev && rid >= 1) { rcinfo = device_get_ivars(rcdev); dinfo = device_get_ivars(child); rc = DPAA2_CMD_RC_OPEN(rcdev, child, &cmd, rcinfo->id, &rc_token); if (rc) { device_printf(rcdev, "%s: failed to open DPRC: " "error=%d\n", __func__, rc); return (ENODEV); } /* Set MSI address and value. */ rc = DPAA2_CMD_RC_SET_OBJ_IRQ(rcdev, child, &cmd, rid - 1, addr, data, rid, dinfo->id, dinfo->dtype); if (rc) { device_printf(rcdev, "%s: failed to setup IRQ: " "rid=%d, addr=%jx, data=%x, error=%d\n", __func__, rid, addr, data, rc); return (ENODEV); } rc = DPAA2_CMD_RC_CLOSE(rcdev, child, &cmd); if (rc) { device_printf(rcdev, "%s: failed to close DPRC: " "error=%d\n", __func__, rc); return (ENODEV); } rc = 0; } return (rc); } /** * @brief General implementation of the MC command to enable IRQ. */ static int dpaa2_rc_enable_irq(struct dpaa2_mcp *mcp, struct dpaa2_cmd *cmd, uint8_t irq_idx, bool enable, uint16_t cmdid) { struct __packed enable_irq_args { uint8_t enable; uint8_t _reserved1; uint16_t _reserved2; uint8_t irq_idx; uint8_t _reserved3; uint16_t _reserved4; uint64_t _reserved5[6]; } *args; if (!mcp || !cmd) return (DPAA2_CMD_STAT_ERR); args = (struct enable_irq_args *) &cmd->params[0]; args->irq_idx = irq_idx; args->enable = enable == 0u ? 0u : 1u; return (dpaa2_rc_exec_cmd(mcp, cmd, cmdid)); } /** * @brief Sends a command to MC and waits for response. */ static int dpaa2_rc_exec_cmd(struct dpaa2_mcp *mcp, struct dpaa2_cmd *cmd, uint16_t cmdid) { struct dpaa2_cmd_header *hdr; uint16_t flags; int error; if (!mcp || !cmd) return (DPAA2_CMD_STAT_ERR); /* Prepare a command for the MC hardware. */ hdr = (struct dpaa2_cmd_header *) &cmd->header; hdr->cmdid = cmdid; hdr->status = DPAA2_CMD_STAT_READY; DPAA2_MCP_LOCK(mcp, &flags); if (flags & DPAA2_PORTAL_DESTROYED) { /* Terminate operation if portal is destroyed. */ DPAA2_MCP_UNLOCK(mcp); return (DPAA2_CMD_STAT_INVALID_STATE); } /* Send a command to MC and wait for the result. */ dpaa2_rc_send_cmd(mcp, cmd); error = dpaa2_rc_wait_for_cmd(mcp, cmd); if (error) { DPAA2_MCP_UNLOCK(mcp); return (DPAA2_CMD_STAT_ERR); } if (hdr->status != DPAA2_CMD_STAT_OK) { DPAA2_MCP_UNLOCK(mcp); return (int)(hdr->status); } DPAA2_MCP_UNLOCK(mcp); return (DPAA2_CMD_STAT_OK); } /** * @brief Writes a command to the MC command portal. */ static int dpaa2_rc_send_cmd(struct dpaa2_mcp *mcp, struct dpaa2_cmd *cmd) { /* Write command parameters. */ for (uint32_t i = 1; i <= DPAA2_CMD_PARAMS_N; i++) bus_write_8(mcp->map, sizeof(uint64_t) * i, cmd->params[i-1]); bus_barrier(mcp->map, 0, sizeof(struct dpaa2_cmd), BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); /* Write command header to trigger execution. */ bus_write_8(mcp->map, 0, cmd->header); return (0); } /** * @brief Polls the MC command portal in order to receive a result of the * command execution. */ static int dpaa2_rc_wait_for_cmd(struct dpaa2_mcp *mcp, struct dpaa2_cmd *cmd) { struct dpaa2_cmd_header *hdr; uint64_t val; uint32_t i; /* Wait for a command execution result from the MC hardware. */ for (i = 1; i <= CMD_SPIN_ATTEMPTS; i++) { val = bus_read_8(mcp->map, 0); hdr = (struct dpaa2_cmd_header *) &val; if (hdr->status != DPAA2_CMD_STAT_READY) { break; } DELAY(CMD_SPIN_TIMEOUT); } if (i > CMD_SPIN_ATTEMPTS) { /* Return an error on expired timeout. */ return (DPAA2_CMD_STAT_TIMEOUT); } else { /* Read command response. */ cmd->header = val; for (i = 1; i <= DPAA2_CMD_PARAMS_N; i++) { cmd->params[i-1] = bus_read_8(mcp->map, i * sizeof(uint64_t)); } } return (DPAA2_CMD_STAT_OK); } /** * @brief Reserve a DPAA2-specific device of the given devtype for the child. */ static int dpaa2_rc_add_res(device_t rcdev, device_t child, enum dpaa2_dev_type devtype, int *rid, int flags) { device_t dpaa2_dev; struct dpaa2_devinfo *dinfo = device_get_ivars(child); struct resource *res; bool shared = false; int error; /* Request a free DPAA2 device of the given type from MC. */ error = DPAA2_MC_GET_FREE_DEV(rcdev, &dpaa2_dev, devtype); if (error && !(flags & RF_SHAREABLE)) { device_printf(rcdev, "%s: failed to obtain a free %s (rid=%d) " "for: %s (id=%u)\n", __func__, dpaa2_ttos(devtype), *rid, dpaa2_ttos(dinfo->dtype), dinfo->id); return (error); } /* Request a shared DPAA2 device of the given type from MC. */ if (error) { error = DPAA2_MC_GET_SHARED_DEV(rcdev, &dpaa2_dev, devtype); if (error) { device_printf(rcdev, "%s: failed to obtain a shared " "%s (rid=%d) for: %s (id=%u)\n", __func__, dpaa2_ttos(devtype), *rid, dpaa2_ttos(dinfo->dtype), dinfo->id); return (error); } shared = true; } /* Add DPAA2 device to the resource list of the child device. */ resource_list_add(&dinfo->resources, devtype, *rid, (rman_res_t) dpaa2_dev, (rman_res_t) dpaa2_dev, 1); /* Reserve a newly added DPAA2 resource. */ res = resource_list_reserve(&dinfo->resources, rcdev, child, devtype, rid, (rman_res_t) dpaa2_dev, (rman_res_t) dpaa2_dev, 1, flags & ~RF_ACTIVE); if (!res) { device_printf(rcdev, "%s: failed to reserve %s (rid=%d) for: %s " "(id=%u)\n", __func__, dpaa2_ttos(devtype), *rid, dpaa2_ttos(dinfo->dtype), dinfo->id); return (EBUSY); } /* Reserve a shared DPAA2 device of the given type. */ if (shared) { error = DPAA2_MC_RESERVE_DEV(rcdev, dpaa2_dev, devtype); if (error) { device_printf(rcdev, "%s: failed to reserve a shared " "%s (rid=%d) for: %s (id=%u)\n", __func__, dpaa2_ttos(devtype), *rid, dpaa2_ttos(dinfo->dtype), dinfo->id); return (error); } } return (0); } static int dpaa2_rc_print_type(struct resource_list *rl, enum dpaa2_dev_type type) { struct dpaa2_devinfo *dinfo; struct resource_list_entry *rle; uint32_t prev_id; int printed = 0, series = 0; int retval = 0; STAILQ_FOREACH(rle, rl, link) { if (rle->type == type) { dinfo = device_get_ivars((device_t) rle->start); if (printed == 0) { retval += printf(" %s (id=", dpaa2_ttos(dinfo->dtype)); } else { if (dinfo->id == prev_id + 1) { if (series == 0) { series = 1; retval += printf("-"); } } else { if (series == 1) { retval += printf("%u", prev_id); series = 0; } retval += printf(","); } } printed++; if (series == 0) retval += printf("%u", dinfo->id); prev_id = dinfo->id; } } if (printed) { if (series == 1) retval += printf("%u", prev_id); retval += printf(")"); } return (retval); } static int dpaa2_rc_reset_cmd_params(struct dpaa2_cmd *cmd) { if (cmd != NULL) { memset(cmd->params, 0, sizeof(cmd->params[0]) * DPAA2_CMD_PARAMS_N); } return (0); } static struct dpaa2_mcp * dpaa2_rc_select_portal(device_t dev, device_t child) { struct dpaa2_devinfo *dinfo = device_get_ivars(dev); struct dpaa2_devinfo *cinfo = device_get_ivars(child); if (cinfo == NULL || dinfo == NULL || dinfo->dtype != DPAA2_DEV_RC) return (NULL); return (cinfo->portal != NULL ? cinfo->portal : dinfo->portal); } static device_method_t dpaa2_rc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dpaa2_rc_probe), DEVMETHOD(device_attach, dpaa2_rc_attach), DEVMETHOD(device_detach, dpaa2_rc_detach), /* Bus interface */ DEVMETHOD(bus_get_resource_list, dpaa2_rc_get_resource_list), DEVMETHOD(bus_delete_resource, dpaa2_rc_delete_resource), DEVMETHOD(bus_alloc_resource, dpaa2_rc_alloc_resource), DEVMETHOD(bus_release_resource, dpaa2_rc_release_resource), DEVMETHOD(bus_child_deleted, dpaa2_rc_child_deleted), DEVMETHOD(bus_child_detached, dpaa2_rc_child_detached), DEVMETHOD(bus_setup_intr, dpaa2_rc_setup_intr), DEVMETHOD(bus_teardown_intr, dpaa2_rc_teardown_intr), DEVMETHOD(bus_print_child, dpaa2_rc_print_child), DEVMETHOD(bus_add_child, device_add_child_ordered), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), /* Pseudo-PCI interface */ DEVMETHOD(pci_alloc_msi, dpaa2_rc_alloc_msi), DEVMETHOD(pci_release_msi, dpaa2_rc_release_msi), DEVMETHOD(pci_msi_count, dpaa2_rc_msi_count), DEVMETHOD(pci_get_id, dpaa2_rc_get_id), /* DPAA2 MC command interface */ DEVMETHOD(dpaa2_cmd_mng_get_version, dpaa2_rc_mng_get_version), DEVMETHOD(dpaa2_cmd_mng_get_soc_version, dpaa2_rc_mng_get_soc_version), DEVMETHOD(dpaa2_cmd_mng_get_container_id, dpaa2_rc_mng_get_container_id), /* DPRC commands */ DEVMETHOD(dpaa2_cmd_rc_open, dpaa2_rc_open), DEVMETHOD(dpaa2_cmd_rc_close, dpaa2_rc_close), DEVMETHOD(dpaa2_cmd_rc_get_obj_count, dpaa2_rc_get_obj_count), DEVMETHOD(dpaa2_cmd_rc_get_obj, dpaa2_rc_get_obj), DEVMETHOD(dpaa2_cmd_rc_get_obj_descriptor, dpaa2_rc_get_obj_descriptor), DEVMETHOD(dpaa2_cmd_rc_get_attributes, dpaa2_rc_get_attributes), DEVMETHOD(dpaa2_cmd_rc_get_obj_region, dpaa2_rc_get_obj_region), DEVMETHOD(dpaa2_cmd_rc_get_api_version, dpaa2_rc_get_api_version), DEVMETHOD(dpaa2_cmd_rc_set_irq_enable, dpaa2_rc_set_irq_enable), DEVMETHOD(dpaa2_cmd_rc_set_obj_irq, dpaa2_rc_set_obj_irq), DEVMETHOD(dpaa2_cmd_rc_get_conn, dpaa2_rc_get_conn), /* DPNI commands */ DEVMETHOD(dpaa2_cmd_ni_open, dpaa2_rc_ni_open), DEVMETHOD(dpaa2_cmd_ni_close, dpaa2_rc_ni_close), DEVMETHOD(dpaa2_cmd_ni_enable, dpaa2_rc_ni_enable), DEVMETHOD(dpaa2_cmd_ni_disable, dpaa2_rc_ni_disable), DEVMETHOD(dpaa2_cmd_ni_get_api_version, dpaa2_rc_ni_get_api_version), DEVMETHOD(dpaa2_cmd_ni_reset, dpaa2_rc_ni_reset), DEVMETHOD(dpaa2_cmd_ni_get_attributes, dpaa2_rc_ni_get_attributes), DEVMETHOD(dpaa2_cmd_ni_set_buf_layout, dpaa2_rc_ni_set_buf_layout), DEVMETHOD(dpaa2_cmd_ni_get_tx_data_off, dpaa2_rc_ni_get_tx_data_offset), DEVMETHOD(dpaa2_cmd_ni_get_port_mac_addr, dpaa2_rc_ni_get_port_mac_addr), DEVMETHOD(dpaa2_cmd_ni_set_prim_mac_addr, dpaa2_rc_ni_set_prim_mac_addr), DEVMETHOD(dpaa2_cmd_ni_get_prim_mac_addr, dpaa2_rc_ni_get_prim_mac_addr), DEVMETHOD(dpaa2_cmd_ni_set_link_cfg, dpaa2_rc_ni_set_link_cfg), DEVMETHOD(dpaa2_cmd_ni_get_link_cfg, dpaa2_rc_ni_get_link_cfg), DEVMETHOD(dpaa2_cmd_ni_get_link_state, dpaa2_rc_ni_get_link_state), DEVMETHOD(dpaa2_cmd_ni_set_qos_table, dpaa2_rc_ni_set_qos_table), DEVMETHOD(dpaa2_cmd_ni_clear_qos_table, dpaa2_rc_ni_clear_qos_table), DEVMETHOD(dpaa2_cmd_ni_set_pools, dpaa2_rc_ni_set_pools), DEVMETHOD(dpaa2_cmd_ni_set_err_behavior,dpaa2_rc_ni_set_err_behavior), DEVMETHOD(dpaa2_cmd_ni_get_queue, dpaa2_rc_ni_get_queue), DEVMETHOD(dpaa2_cmd_ni_set_queue, dpaa2_rc_ni_set_queue), DEVMETHOD(dpaa2_cmd_ni_get_qdid, dpaa2_rc_ni_get_qdid), DEVMETHOD(dpaa2_cmd_ni_add_mac_addr, dpaa2_rc_ni_add_mac_addr), DEVMETHOD(dpaa2_cmd_ni_remove_mac_addr, dpaa2_rc_ni_remove_mac_addr), DEVMETHOD(dpaa2_cmd_ni_clear_mac_filters, dpaa2_rc_ni_clear_mac_filters), DEVMETHOD(dpaa2_cmd_ni_set_mfl, dpaa2_rc_ni_set_mfl), DEVMETHOD(dpaa2_cmd_ni_set_offload, dpaa2_rc_ni_set_offload), DEVMETHOD(dpaa2_cmd_ni_set_irq_mask, dpaa2_rc_ni_set_irq_mask), DEVMETHOD(dpaa2_cmd_ni_set_irq_enable, dpaa2_rc_ni_set_irq_enable), DEVMETHOD(dpaa2_cmd_ni_get_irq_status, dpaa2_rc_ni_get_irq_status), DEVMETHOD(dpaa2_cmd_ni_set_uni_promisc, dpaa2_rc_ni_set_uni_promisc), DEVMETHOD(dpaa2_cmd_ni_set_multi_promisc, dpaa2_rc_ni_set_multi_promisc), DEVMETHOD(dpaa2_cmd_ni_get_statistics, dpaa2_rc_ni_get_statistics), DEVMETHOD(dpaa2_cmd_ni_set_rx_tc_dist, dpaa2_rc_ni_set_rx_tc_dist), /* DPIO commands */ DEVMETHOD(dpaa2_cmd_io_open, dpaa2_rc_io_open), DEVMETHOD(dpaa2_cmd_io_close, dpaa2_rc_io_close), DEVMETHOD(dpaa2_cmd_io_enable, dpaa2_rc_io_enable), DEVMETHOD(dpaa2_cmd_io_disable, dpaa2_rc_io_disable), DEVMETHOD(dpaa2_cmd_io_reset, dpaa2_rc_io_reset), DEVMETHOD(dpaa2_cmd_io_get_attributes, dpaa2_rc_io_get_attributes), DEVMETHOD(dpaa2_cmd_io_set_irq_mask, dpaa2_rc_io_set_irq_mask), DEVMETHOD(dpaa2_cmd_io_get_irq_status, dpaa2_rc_io_get_irq_status), DEVMETHOD(dpaa2_cmd_io_set_irq_enable, dpaa2_rc_io_set_irq_enable), DEVMETHOD(dpaa2_cmd_io_add_static_dq_chan, dpaa2_rc_io_add_static_dq_chan), /* DPBP commands */ DEVMETHOD(dpaa2_cmd_bp_open, dpaa2_rc_bp_open), DEVMETHOD(dpaa2_cmd_bp_close, dpaa2_rc_bp_close), DEVMETHOD(dpaa2_cmd_bp_enable, dpaa2_rc_bp_enable), DEVMETHOD(dpaa2_cmd_bp_disable, dpaa2_rc_bp_disable), DEVMETHOD(dpaa2_cmd_bp_reset, dpaa2_rc_bp_reset), DEVMETHOD(dpaa2_cmd_bp_get_attributes, dpaa2_rc_bp_get_attributes), /* DPMAC commands */ DEVMETHOD(dpaa2_cmd_mac_open, dpaa2_rc_mac_open), DEVMETHOD(dpaa2_cmd_mac_close, dpaa2_rc_mac_close), DEVMETHOD(dpaa2_cmd_mac_reset, dpaa2_rc_mac_reset), DEVMETHOD(dpaa2_cmd_mac_mdio_read, dpaa2_rc_mac_mdio_read), DEVMETHOD(dpaa2_cmd_mac_mdio_write, dpaa2_rc_mac_mdio_write), DEVMETHOD(dpaa2_cmd_mac_get_addr, dpaa2_rc_mac_get_addr), DEVMETHOD(dpaa2_cmd_mac_get_attributes, dpaa2_rc_mac_get_attributes), DEVMETHOD(dpaa2_cmd_mac_set_link_state, dpaa2_rc_mac_set_link_state), DEVMETHOD(dpaa2_cmd_mac_set_irq_mask, dpaa2_rc_mac_set_irq_mask), DEVMETHOD(dpaa2_cmd_mac_set_irq_enable, dpaa2_rc_mac_set_irq_enable), DEVMETHOD(dpaa2_cmd_mac_get_irq_status, dpaa2_rc_mac_get_irq_status), /* DPCON commands */ DEVMETHOD(dpaa2_cmd_con_open, dpaa2_rc_con_open), DEVMETHOD(dpaa2_cmd_con_close, dpaa2_rc_con_close), DEVMETHOD(dpaa2_cmd_con_reset, dpaa2_rc_con_reset), DEVMETHOD(dpaa2_cmd_con_enable, dpaa2_rc_con_enable), DEVMETHOD(dpaa2_cmd_con_disable, dpaa2_rc_con_disable), DEVMETHOD(dpaa2_cmd_con_get_attributes, dpaa2_rc_con_get_attributes), DEVMETHOD(dpaa2_cmd_con_set_notif, dpaa2_rc_con_set_notif), /* DPMCP commands */ DEVMETHOD(dpaa2_cmd_mcp_create, dpaa2_rc_mcp_create), DEVMETHOD(dpaa2_cmd_mcp_destroy, dpaa2_rc_mcp_destroy), DEVMETHOD(dpaa2_cmd_mcp_open, dpaa2_rc_mcp_open), DEVMETHOD(dpaa2_cmd_mcp_close, dpaa2_rc_mcp_close), DEVMETHOD(dpaa2_cmd_mcp_reset, dpaa2_rc_mcp_reset), DEVMETHOD_END }; static driver_t dpaa2_rc_driver = { "dpaa2_rc", dpaa2_rc_methods, sizeof(struct dpaa2_rc_softc), }; /* For root container */ DRIVER_MODULE(dpaa2_rc, dpaa2_mc, dpaa2_rc_driver, 0, 0); /* For child containers */ DRIVER_MODULE(dpaa2_rc, dpaa2_rc, dpaa2_rc_driver, 0, 0); diff --git a/sys/dev/dpaa2/memac_mdio_acpi.c b/sys/dev/dpaa2/memac_mdio_acpi.c index 3a816901815b..dc08715343e0 100644 --- a/sys/dev/dpaa2/memac_mdio_acpi.c +++ b/sys/dev/dpaa2/memac_mdio_acpi.c @@ -1,307 +1,307 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright ยฉ 2021-2022 Bjoern A. Zeeb * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "memac_mdio.h" #include "memac_mdio_if.h" #include "acpi_bus_if.h" #include "miibus_if.h" /* -------------------------------------------------------------------------- */ struct memacphy_softc_acpi { struct memacphy_softc_common scc; int uid; uint64_t phy_channel; char compatible[64]; }; static void memacphy_acpi_miibus_statchg(device_t dev) { struct memacphy_softc_acpi *sc; sc = device_get_softc(dev); memacphy_miibus_statchg(&sc->scc); } static int memacphy_acpi_set_ni_dev(device_t dev, device_t nidev) { struct memacphy_softc_acpi *sc; sc = device_get_softc(dev); return (memacphy_set_ni_dev(&sc->scc, nidev)); } static int memacphy_acpi_get_phy_loc(device_t dev, int *phy_loc) { struct memacphy_softc_acpi *sc; sc = device_get_softc(dev); return (memacphy_get_phy_loc(&sc->scc, phy_loc)); } static int memacphy_acpi_probe(device_t dev) { device_set_desc(dev, "MEMAC PHY (acpi)"); return (BUS_PROBE_DEFAULT); } static int memacphy_acpi_attach(device_t dev) { struct memacphy_softc_acpi *sc; ACPI_HANDLE h; ssize_t s; sc = device_get_softc(dev); sc->scc.dev = dev; h = acpi_get_handle(dev); s = acpi_GetInteger(h, "_UID", &sc->uid); if (ACPI_FAILURE(s)) { device_printf(dev, "Cannot get '_UID' property: %zd\n", s); return (ENXIO); } s = device_get_property(dev, "phy-channel", &sc->phy_channel, sizeof(sc->phy_channel), DEVICE_PROP_UINT64); if (s != -1) sc->scc.phy = sc->phy_channel; else sc->scc.phy = -1; s = device_get_property(dev, "compatible", sc->compatible, sizeof(sc->compatible), DEVICE_PROP_ANY); if (bootverbose) device_printf(dev, "UID %#04x phy-channel %ju compatible '%s' phy %u\n", sc->uid, sc->phy_channel, sc->compatible[0] != '\0' ? sc->compatible : "", sc->scc.phy); if (sc->scc.phy == -1) return (ENXIO); return (0); } static device_method_t memacphy_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, memacphy_acpi_probe), DEVMETHOD(device_attach, memacphy_acpi_attach), DEVMETHOD(device_detach, bus_generic_detach), /* MII interface */ DEVMETHOD(miibus_readreg, memacphy_miibus_readreg), DEVMETHOD(miibus_writereg, memacphy_miibus_writereg), DEVMETHOD(miibus_statchg, memacphy_acpi_miibus_statchg), /* memac */ DEVMETHOD(memac_mdio_set_ni_dev, memacphy_acpi_set_ni_dev), DEVMETHOD(memac_mdio_get_phy_loc, memacphy_acpi_get_phy_loc), DEVMETHOD_END }; DEFINE_CLASS_0(memacphy_acpi, memacphy_acpi_driver, memacphy_acpi_methods, sizeof(struct memacphy_softc_acpi)); EARLY_DRIVER_MODULE(memacphy_acpi, memac_mdio_acpi, memacphy_acpi_driver, 0, 0, BUS_PASS_SUPPORTDEV); DRIVER_MODULE(miibus, memacphy_acpi, miibus_driver, 0, 0); MODULE_DEPEND(memacphy_acpi, miibus, 1, 1, 1); /* -------------------------------------------------------------------------- */ struct memac_mdio_softc_acpi { struct memac_mdio_softc_common scc; }; static int memac_acpi_miibus_readreg(device_t dev, int phy, int reg) { struct memac_mdio_softc_acpi *sc; sc = device_get_softc(dev); return (memac_miibus_readreg(&sc->scc, phy, reg)); } static int memac_acpi_miibus_writereg(device_t dev, int phy, int reg, int data) { struct memac_mdio_softc_acpi *sc; sc = device_get_softc(dev); return (memac_miibus_writereg(&sc->scc, phy, reg, data)); } /* Context for walking PHY child devices. */ struct memac_mdio_walk_ctx { device_t dev; int count; int countok; }; static char *memac_mdio_ids[] = { "NXP0006", NULL }; static int memac_mdio_acpi_probe(device_t dev) { int rc; if (acpi_disabled("fsl_memac_mdio")) return (ENXIO); rc = ACPI_ID_PROBE(device_get_parent(dev), dev, memac_mdio_ids, NULL); if (rc <= 0) device_set_desc(dev, "Freescale XGMAC MDIO Bus"); return (rc); } static ACPI_STATUS memac_mdio_acpi_probe_child(ACPI_HANDLE h, device_t *dev, int level, void *arg) { struct memac_mdio_walk_ctx *ctx; struct acpi_device *ad; device_t child; uint32_t adr; ctx = (struct memac_mdio_walk_ctx *)arg; ctx->count++; if (ACPI_FAILURE(acpi_GetInteger(h, "_ADR", &adr))) return (AE_OK); /* Technically M_ACPIDEV */ if ((ad = malloc(sizeof(*ad), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) return (AE_OK); - child = device_add_child(ctx->dev, "memacphy_acpi", -1); + child = device_add_child(ctx->dev, "memacphy_acpi", DEVICE_UNIT_ANY); if (child == NULL) { free(ad, M_DEVBUF); return (AE_OK); } ad->ad_handle = h; ad->ad_cls_class = 0xffffff; resource_list_init(&ad->ad_rl); device_set_ivars(child, ad); *dev = child; ctx->countok++; return (AE_OK); } static int memac_mdio_acpi_attach(device_t dev) { struct memac_mdio_softc_acpi *sc; struct memac_mdio_walk_ctx ctx; int error; sc = device_get_softc(dev); sc->scc.dev = dev; error = memac_mdio_generic_attach(&sc->scc); if (error != 0) return (error); ctx.dev = dev; ctx.count = 0; ctx.countok = 0; ACPI_SCAN_CHILDREN(device_get_parent(dev), dev, 1, memac_mdio_acpi_probe_child, &ctx); if (ctx.countok > 0) { bus_identify_children(dev); bus_attach_children(dev); } return (0); } static int memac_mdio_acpi_detach(device_t dev) { struct memac_mdio_softc_acpi *sc; sc = device_get_softc(dev); return (memac_mdio_generic_detach(&sc->scc)); } static device_method_t memac_mdio_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, memac_mdio_acpi_probe), DEVMETHOD(device_attach, memac_mdio_acpi_attach), DEVMETHOD(device_detach, memac_mdio_acpi_detach), /* MII interface */ DEVMETHOD(miibus_readreg, memac_acpi_miibus_readreg), DEVMETHOD(miibus_writereg, memac_acpi_miibus_writereg), /* .. */ DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_read_ivar, memac_mdio_read_ivar), DEVMETHOD(bus_get_property, memac_mdio_get_property), DEVMETHOD_END }; DEFINE_CLASS_0(memac_mdio_acpi, memac_mdio_acpi_driver, memac_mdio_acpi_methods, sizeof(struct memac_mdio_softc_acpi)); EARLY_DRIVER_MODULE(memac_mdio_acpi, acpi, memac_mdio_acpi_driver, 0, 0, BUS_PASS_SUPPORTDEV); DRIVER_MODULE(miibus, memac_mdio_acpi, miibus_driver, 0, 0); MODULE_DEPEND(memac_mdio_acpi, miibus, 1, 1, 1); MODULE_VERSION(memac_mdio_acpi, 1); diff --git a/sys/dev/efidev/efirtc.c b/sys/dev/efidev/efirtc.c index a7baff673c1c..69d2c0b1af9f 100644 --- a/sys/dev/efidev/efirtc.c +++ b/sys/dev/efidev/efirtc.c @@ -1,203 +1,203 @@ /*- * Copyright (c) 2017 Andrew Turner * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237 * ("CTSRD"), as part of the DARPA CRASH research programme. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include "clock_if.h" static bool efirtc_zeroes_subseconds; static struct timespec efirtc_resadj; static const u_int us_per_s = 1000000; static const u_int ns_per_s = 1000000000; static const u_int ns_per_us = 1000; static void efirtc_identify(driver_t *driver, device_t parent) { /* Don't add the driver unless we have working runtime services. */ if (efi_rt_ok() != 0) return; - if (device_find_child(parent, "efirtc", -1) != NULL) + if (device_find_child(parent, "efirtc", DEVICE_UNIT_ANY) != NULL) return; - if (BUS_ADD_CHILD(parent, 0, "efirtc", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "efirtc", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int efirtc_probe(device_t dev) { struct efi_tm tm; int error; /* * Check whether we can read the time. This will stop us from attaching * when there is EFI Runtime support but the gettime function is * unimplemented, e.g. on some builds of U-Boot. */ if ((error = efi_get_time(&tm)) != 0) { if (bootverbose) device_printf(dev, "cannot read EFI realtime clock, " "error %d\n", error); return (error); } device_set_desc(dev, "EFI Realtime Clock"); return (BUS_PROBE_DEFAULT); } static int efirtc_attach(device_t dev) { struct efi_tmcap tmcap; long res; int error; bzero(&tmcap, sizeof(tmcap)); if ((error = efi_get_time_capabilities(&tmcap)) != 0) { device_printf(dev, "cannot get EFI time capabilities"); return (error); } /* Translate resolution in Hz to tick length in usec. */ if (tmcap.tc_res == 0) res = us_per_s; /* 0 is insane, assume 1 Hz. */ else if (tmcap.tc_res > us_per_s) res = 1; /* 1us is the best we can represent */ else res = us_per_s / tmcap.tc_res; /* Clock rounding adjustment is 1/2 of resolution, in nsec. */ efirtc_resadj.tv_nsec = (res * ns_per_us) / 2; /* Does the clock zero the subseconds when time is set? */ efirtc_zeroes_subseconds = tmcap.tc_stz; /* * Register. If the clock zeroes out the subseconds when it's set, * schedule the SetTime calls to happen just before top-of-second. */ clock_register_flags(dev, res, CLOCKF_SETTIME_NO_ADJ); if (efirtc_zeroes_subseconds) clock_schedule(dev, ns_per_s - ns_per_us); return (0); } static int efirtc_detach(device_t dev) { clock_unregister(dev); return (0); } static int efirtc_gettime(device_t dev, struct timespec *ts) { struct clocktime ct; struct efi_tm tm; int error; error = efi_get_time(&tm); if (error != 0) return (error); ct.sec = tm.tm_sec; ct.min = tm.tm_min; ct.hour = tm.tm_hour; ct.day = tm.tm_mday; ct.mon = tm.tm_mon; ct.year = tm.tm_year; ct.nsec = tm.tm_nsec; clock_dbgprint_ct(dev, CLOCK_DBG_READ, &ct); return (clock_ct_to_ts(&ct, ts)); } static int efirtc_settime(device_t dev, struct timespec *ts) { struct clocktime ct; struct efi_tm tm; /* * We request a timespec with no resolution-adjustment so that we can * apply it ourselves based on whether or not the clock zeroes the * sub-second part of the time when setting the time. */ ts->tv_sec -= utc_offset(); if (!efirtc_zeroes_subseconds) timespecadd(ts, &efirtc_resadj, ts); clock_ts_to_ct(ts, &ct); clock_dbgprint_ct(dev, CLOCK_DBG_WRITE, &ct); bzero(&tm, sizeof(tm)); tm.tm_sec = ct.sec; tm.tm_min = ct.min; tm.tm_hour = ct.hour; tm.tm_mday = ct.day; tm.tm_mon = ct.mon; tm.tm_year = ct.year; tm.tm_nsec = ct.nsec; return (efi_set_time(&tm)); } static device_method_t efirtc_methods[] = { /* Device interface */ DEVMETHOD(device_identify, efirtc_identify), DEVMETHOD(device_probe, efirtc_probe), DEVMETHOD(device_attach, efirtc_attach), DEVMETHOD(device_detach, efirtc_detach), /* Clock interface */ DEVMETHOD(clock_gettime, efirtc_gettime), DEVMETHOD(clock_settime, efirtc_settime), DEVMETHOD_END }; static driver_t efirtc_driver = { "efirtc", efirtc_methods, 0 }; DRIVER_MODULE(efirtc, nexus, efirtc_driver, 0, 0); MODULE_VERSION(efirtc, 1); MODULE_DEPEND(efirtc, efirt, 1, 1, 1); diff --git a/sys/dev/etherswitch/e6000sw/e6000sw.c b/sys/dev/etherswitch/e6000sw/e6000sw.c index 4ef510b85c70..7e9193f4ba47 100644 --- a/sys/dev/etherswitch/e6000sw/e6000sw.c +++ b/sys/dev/etherswitch/e6000sw/e6000sw.c @@ -1,1834 +1,1834 @@ /*- * Copyright (c) 2015 Semihalf * Copyright (c) 2015 Stormshield * Copyright (c) 2018-2019, Rubicon Communications, LLC (Netgate) * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #else #include #endif #include "e6000swreg.h" #include "etherswitch_if.h" #include "miibus_if.h" #include "mdio_if.h" MALLOC_DECLARE(M_E6000SW); MALLOC_DEFINE(M_E6000SW, "e6000sw", "e6000sw switch"); #define E6000SW_LOCK(_sc) sx_xlock(&(_sc)->sx) #define E6000SW_UNLOCK(_sc) sx_unlock(&(_sc)->sx) #define E6000SW_LOCK_ASSERT(_sc, _what) sx_assert(&(_sc)->sx, (_what)) #define E6000SW_TRYLOCK(_sc) sx_tryxlock(&(_sc)->sx) #define E6000SW_LOCKED(_sc) sx_xlocked(&(_sc)->sx) #define E6000SW_WAITREADY(_sc, _reg, _bit) \ e6000sw_waitready((_sc), REG_GLOBAL, (_reg), (_bit)) #define E6000SW_WAITREADY2(_sc, _reg, _bit) \ e6000sw_waitready((_sc), REG_GLOBAL2, (_reg), (_bit)) #define MDIO_READ(dev, addr, reg) \ MDIO_READREG(device_get_parent(dev), (addr), (reg)) #define MDIO_WRITE(dev, addr, reg, val) \ MDIO_WRITEREG(device_get_parent(dev), (addr), (reg), (val)) typedef struct e6000sw_softc { device_t dev; #ifdef FDT phandle_t node; #endif struct sx sx; if_t ifp[E6000SW_MAX_PORTS]; char *ifname[E6000SW_MAX_PORTS]; device_t miibus[E6000SW_MAX_PORTS]; struct taskqueue *sc_tq; struct timeout_task sc_tt; bool is_shutdown; int vlans[E6000SW_NUM_VLANS]; uint32_t swid; uint32_t vlan_mode; uint32_t cpuports_mask; uint32_t fixed_mask; uint32_t fixed25_mask; uint32_t ports_mask; int phy_base; int sw_addr; int num_ports; } e6000sw_softc_t; static etherswitch_info_t etherswitch_info = { .es_nports = 0, .es_nvlangroups = 0, .es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q, .es_name = "Marvell 6000 series switch" }; static void e6000sw_identify(driver_t *, device_t); static int e6000sw_probe(device_t); #ifdef FDT static int e6000sw_parse_fixed_link(e6000sw_softc_t *, phandle_t, uint32_t); static int e6000sw_parse_ethernet(e6000sw_softc_t *, phandle_t, uint32_t); #endif static int e6000sw_attach(device_t); static int e6000sw_detach(device_t); static int e6000sw_read_xmdio(device_t, int, int, int); static int e6000sw_write_xmdio(device_t, int, int, int, int); static int e6000sw_readphy(device_t, int, int); static int e6000sw_writephy(device_t, int, int, int); static int e6000sw_readphy_locked(device_t, int, int); static int e6000sw_writephy_locked(device_t, int, int, int); static etherswitch_info_t* e6000sw_getinfo(device_t); static int e6000sw_getconf(device_t, etherswitch_conf_t *); static int e6000sw_setconf(device_t, etherswitch_conf_t *); static void e6000sw_lock(device_t); static void e6000sw_unlock(device_t); static int e6000sw_getport(device_t, etherswitch_port_t *); static int e6000sw_setport(device_t, etherswitch_port_t *); static int e6000sw_set_vlan_mode(e6000sw_softc_t *, uint32_t); static int e6000sw_readreg_wrapper(device_t, int); static int e6000sw_writereg_wrapper(device_t, int, int); static int e6000sw_getvgroup_wrapper(device_t, etherswitch_vlangroup_t *); static int e6000sw_setvgroup_wrapper(device_t, etherswitch_vlangroup_t *); static int e6000sw_setvgroup(device_t, etherswitch_vlangroup_t *); static int e6000sw_getvgroup(device_t, etherswitch_vlangroup_t *); static void e6000sw_setup(device_t, e6000sw_softc_t *); static void e6000sw_tick(void *, int); static void e6000sw_set_atustat(device_t, e6000sw_softc_t *, int, int); static int e6000sw_atu_flush(device_t, e6000sw_softc_t *, int); static int e6000sw_vtu_flush(e6000sw_softc_t *); static int e6000sw_vtu_update(e6000sw_softc_t *, int, int, int, int, int); static __inline void e6000sw_writereg(e6000sw_softc_t *, int, int, int); static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *, int, int); static int e6000sw_ifmedia_upd(if_t); static void e6000sw_ifmedia_sts(if_t, struct ifmediareq *); static int e6000sw_atu_mac_table(device_t, e6000sw_softc_t *, struct atu_opt *, int); static int e6000sw_get_pvid(e6000sw_softc_t *, int, int *); static void e6000sw_set_pvid(e6000sw_softc_t *, int, int); static __inline bool e6000sw_is_cpuport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_fixedport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_fixed25port(e6000sw_softc_t *, int); static __inline bool e6000sw_is_phyport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_portenabled(e6000sw_softc_t *, int); static __inline struct mii_data *e6000sw_miiforphy(e6000sw_softc_t *, unsigned int); static device_method_t e6000sw_methods[] = { /* device interface */ DEVMETHOD(device_identify, e6000sw_identify), DEVMETHOD(device_probe, e6000sw_probe), DEVMETHOD(device_attach, e6000sw_attach), DEVMETHOD(device_detach, e6000sw_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* mii interface */ DEVMETHOD(miibus_readreg, e6000sw_readphy), DEVMETHOD(miibus_writereg, e6000sw_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_getinfo, e6000sw_getinfo), DEVMETHOD(etherswitch_getconf, e6000sw_getconf), DEVMETHOD(etherswitch_setconf, e6000sw_setconf), DEVMETHOD(etherswitch_lock, e6000sw_lock), DEVMETHOD(etherswitch_unlock, e6000sw_unlock), DEVMETHOD(etherswitch_getport, e6000sw_getport), DEVMETHOD(etherswitch_setport, e6000sw_setport), DEVMETHOD(etherswitch_readreg, e6000sw_readreg_wrapper), DEVMETHOD(etherswitch_writereg, e6000sw_writereg_wrapper), DEVMETHOD(etherswitch_readphyreg, e6000sw_readphy), DEVMETHOD(etherswitch_writephyreg, e6000sw_writephy), DEVMETHOD(etherswitch_setvgroup, e6000sw_setvgroup_wrapper), DEVMETHOD(etherswitch_getvgroup, e6000sw_getvgroup_wrapper), DEVMETHOD_END }; DEFINE_CLASS_0(e6000sw, e6000sw_driver, e6000sw_methods, sizeof(e6000sw_softc_t)); DRIVER_MODULE(e6000sw, mdio, e6000sw_driver, 0, 0); DRIVER_MODULE(miibus, e6000sw, miibus_driver, 0, 0); DRIVER_MODULE_ORDERED(etherswitch, e6000sw, etherswitch_driver, 0, 0, SI_ORDER_ANY); MODULE_DEPEND(e6000sw, mdio, 1, 1, 1); MODULE_DEPEND(e6000sw, etherswitch, 1, 1, 1); static void e6000sw_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "e6000sw", -1) == NULL) + if (device_find_child(parent, "e6000sw", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "e6000sw", DEVICE_UNIT_ANY); } static int e6000sw_probe(device_t dev) { e6000sw_softc_t *sc; const char *description; #ifdef FDT phandle_t switch_node; #else int is_6190 = 0; int is_6190x = 0; #endif sc = device_get_softc(dev); sc->dev = dev; #ifdef FDT switch_node = ofw_bus_find_compatible(OF_finddevice("/"), "marvell,mv88e6085"); if (switch_node == 0) { switch_node = ofw_bus_find_compatible(OF_finddevice("/"), "marvell,mv88e6190"); if (switch_node == 0) return (ENXIO); /* * Trust DTS and fix the port register offset for the MV88E6190 * detection bellow. */ sc->swid = MV88E6190; } if (bootverbose) device_printf(dev, "Found switch_node: 0x%x\n", switch_node); sc->node = switch_node; if (OF_getencprop(sc->node, "reg", &sc->sw_addr, sizeof(sc->sw_addr)) < 0) return (ENXIO); #else if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "addr", &sc->sw_addr) != 0) return (ENXIO); if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "is6190", &is_6190) != 0) { /* * Check "is8190" to keep backward compatibility with * older setups. */ resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "is8190", &is_6190); } resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "is6190x", &is_6190x); if (is_6190 != 0 && is_6190x != 0) { device_printf(dev, "Cannot configure conflicting variants (6190 / 6190x)\n"); return (ENXIO); } if (is_6190 != 0) sc->swid = MV88E6190; else if (is_6190x != 0) sc->swid = MV88E6190X; #endif if (sc->sw_addr < 0 || sc->sw_addr > 32) return (ENXIO); /* * Create temporary lock, just to satisfy assertions, * when obtaining the switch ID. Destroy immediately afterwards. */ sx_init(&sc->sx, "e6000sw_tmp"); E6000SW_LOCK(sc); sc->swid = e6000sw_readreg(sc, REG_PORT(sc, 0), SWITCH_ID) & 0xfff0; E6000SW_UNLOCK(sc); sx_destroy(&sc->sx); switch (sc->swid) { case MV88E6141: description = "Marvell 88E6141"; sc->phy_base = 0x10; sc->num_ports = 6; break; case MV88E6341: description = "Marvell 88E6341"; sc->phy_base = 0x10; sc->num_ports = 6; break; case MV88E6352: description = "Marvell 88E6352"; sc->num_ports = 7; break; case MV88E6172: description = "Marvell 88E6172"; sc->num_ports = 7; break; case MV88E6176: description = "Marvell 88E6176"; sc->num_ports = 7; break; case MV88E6190: description = "Marvell 88E6190"; sc->num_ports = 11; break; case MV88E6190X: description = "Marvell 88E6190X"; sc->num_ports = 11; break; default: device_printf(dev, "Unrecognized device, id 0x%x.\n", sc->swid); return (ENXIO); } device_set_desc(dev, description); return (BUS_PROBE_DEFAULT); } #ifdef FDT static int e6000sw_parse_fixed_link(e6000sw_softc_t *sc, phandle_t node, uint32_t port) { int speed; phandle_t fixed_link; fixed_link = ofw_bus_find_child(node, "fixed-link"); if (fixed_link != 0) { sc->fixed_mask |= (1 << port); if (OF_getencprop(fixed_link, "speed", &speed, sizeof(speed)) < 0) { device_printf(sc->dev, "Port %d has a fixed-link node without a speed " "property\n", port); return (ENXIO); } if (speed == 2500 && (MVSWITCH(sc, MV88E6141) || MVSWITCH(sc, MV88E6341) || MVSWITCH(sc, MV88E6190) || MVSWITCH(sc, MV88E6190X))) sc->fixed25_mask |= (1 << port); } return (0); } static int e6000sw_parse_ethernet(e6000sw_softc_t *sc, phandle_t port_handle, uint32_t port) { phandle_t switch_eth, switch_eth_handle; if (OF_getencprop(port_handle, "ethernet", (void*)&switch_eth_handle, sizeof(switch_eth_handle)) > 0) { if (switch_eth_handle > 0) { switch_eth = OF_node_from_xref(switch_eth_handle); device_printf(sc->dev, "CPU port at %d\n", port); sc->cpuports_mask |= (1 << port); return (e6000sw_parse_fixed_link(sc, switch_eth, port)); } else device_printf(sc->dev, "Port %d has ethernet property but it points " "to an invalid location\n", port); } return (0); } static int e6000sw_parse_child_fdt(e6000sw_softc_t *sc, phandle_t child, int *pport) { uint32_t port; if (pport == NULL) return (ENXIO); if (OF_getencprop(child, "reg", (void *)&port, sizeof(port)) < 0) return (ENXIO); if (port >= sc->num_ports) return (ENXIO); *pport = port; if (e6000sw_parse_fixed_link(sc, child, port) != 0) return (ENXIO); if (e6000sw_parse_ethernet(sc, child, port) != 0) return (ENXIO); if ((sc->fixed_mask & (1 << port)) != 0) device_printf(sc->dev, "fixed port at %d\n", port); else device_printf(sc->dev, "PHY at port %d\n", port); return (0); } #else static int e6000sw_check_hint_val(device_t dev, int *val, char *fmt, ...) { char *resname; int err, len; va_list ap; len = min(strlen(fmt) * 2, 128); if (len == 0) return (-1); resname = malloc(len, M_E6000SW, M_WAITOK); memset(resname, 0, len); va_start(ap, fmt); vsnprintf(resname, len - 1, fmt, ap); va_end(ap); err = resource_int_value(device_get_name(dev), device_get_unit(dev), resname, val); free(resname, M_E6000SW); return (err); } static int e6000sw_parse_hinted_port(e6000sw_softc_t *sc, int port) { int err, val; err = e6000sw_check_hint_val(sc->dev, &val, "port%ddisabled", port); if (err == 0 && val != 0) return (1); err = e6000sw_check_hint_val(sc->dev, &val, "port%dcpu", port); if (err == 0 && val != 0) { sc->cpuports_mask |= (1 << port); sc->fixed_mask |= (1 << port); if (bootverbose) device_printf(sc->dev, "CPU port at %d\n", port); } err = e6000sw_check_hint_val(sc->dev, &val, "port%dspeed", port); if (err == 0 && val != 0) { sc->fixed_mask |= (1 << port); if (val == 2500) sc->fixed25_mask |= (1 << port); } if (bootverbose) { if ((sc->fixed_mask & (1 << port)) != 0) device_printf(sc->dev, "fixed port at %d\n", port); else device_printf(sc->dev, "PHY at port %d\n", port); } return (0); } #endif static int e6000sw_init_interface(e6000sw_softc_t *sc, int port) { char name[IFNAMSIZ]; snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev)); sc->ifp[port] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[port], sc); if_setflagbits(sc->ifp[port], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX, 0); sc->ifname[port] = malloc(strlen(name) + 1, M_E6000SW, M_NOWAIT); if (sc->ifname[port] == NULL) { if_free(sc->ifp[port]); return (ENOMEM); } memcpy(sc->ifname[port], name, strlen(name) + 1); if_initname(sc->ifp[port], sc->ifname[port], port); return (0); } static int e6000sw_attach_miibus(e6000sw_softc_t *sc, int port) { int err; err = mii_attach(sc->dev, &sc->miibus[port], sc->ifp[port], e6000sw_ifmedia_upd, e6000sw_ifmedia_sts, BMSR_DEFCAPMASK, port + sc->phy_base, MII_OFFSET_ANY, 0); if (err != 0) return (err); return (0); } static void e6000sw_serdes_power(device_t dev, int port, bool sgmii) { uint32_t reg; /* SGMII */ reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_SGMII_CTL); if (sgmii) reg &= ~E6000SW_SERDES_PDOWN; else reg |= E6000SW_SERDES_PDOWN; e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_SGMII_CTL, reg); /* 10GBASE-R/10GBASE-X4/X2 */ reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_PCS_CTL1); if (sgmii) reg |= E6000SW_SERDES_PDOWN; else reg &= ~E6000SW_SERDES_PDOWN; e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_PCS_CTL1, reg); } static int e6000sw_attach(device_t dev) { bool sgmii; e6000sw_softc_t *sc; #ifdef FDT phandle_t child, ports; #endif int err, port; uint32_t reg; err = 0; sc = device_get_softc(dev); /* * According to the Linux source code, all of the Switch IDs we support * are multi_chip capable, and should go into multi-chip mode if the * sw_addr != 0. */ if (MVSWITCH_MULTICHIP(sc)) device_printf(dev, "multi-chip addressing mode (%#x)\n", sc->sw_addr); else device_printf(dev, "single-chip addressing mode\n"); sx_init(&sc->sx, "e6000sw"); E6000SW_LOCK(sc); e6000sw_setup(dev, sc); sc->sc_tq = taskqueue_create("e6000sw_taskq", M_NOWAIT, taskqueue_thread_enqueue, &sc->sc_tq); TIMEOUT_TASK_INIT(sc->sc_tq, &sc->sc_tt, 0, e6000sw_tick, sc); taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", device_get_nameunit(dev)); #ifdef FDT ports = ofw_bus_find_child(sc->node, "ports"); if (ports == 0) { device_printf(dev, "failed to parse DTS: no ports found for " "switch\n"); E6000SW_UNLOCK(sc); return (ENXIO); } for (child = OF_child(ports); child != 0; child = OF_peer(child)) { err = e6000sw_parse_child_fdt(sc, child, &port); if (err != 0) { device_printf(sc->dev, "failed to parse DTS\n"); goto out_fail; } #else for (port = 0; port < sc->num_ports; port++) { err = e6000sw_parse_hinted_port(sc, port); if (err != 0) continue; #endif /* Port is in use. */ sc->ports_mask |= (1 << port); err = e6000sw_init_interface(sc, port); if (err != 0) { device_printf(sc->dev, "failed to init interface\n"); goto out_fail; } if (e6000sw_is_fixedport(sc, port)) { /* Link must be down to change speed force value. */ reg = e6000sw_readreg(sc, REG_PORT(sc, port), PSC_CONTROL); reg &= ~PSC_CONTROL_LINK_UP; reg |= PSC_CONTROL_FORCED_LINK; e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL, reg); /* * Force speed, full-duplex, EEE off and flow-control * on. */ reg &= ~(PSC_CONTROL_SPD2500 | PSC_CONTROL_ALT_SPD | PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON | PSC_CONTROL_FORCED_EEE); if (e6000sw_is_fixed25port(sc, port)) reg |= PSC_CONTROL_SPD2500; else reg |= PSC_CONTROL_SPD1000; if ((MVSWITCH(sc, MV88E6190) || MVSWITCH(sc, MV88E6190X)) && e6000sw_is_fixed25port(sc, port)) reg |= PSC_CONTROL_ALT_SPD; reg |= PSC_CONTROL_FORCED_DPX | PSC_CONTROL_FULLDPX | PSC_CONTROL_FORCED_LINK | PSC_CONTROL_LINK_UP | PSC_CONTROL_FORCED_SPD; if (!MVSWITCH(sc, MV88E6190) && !MVSWITCH(sc, MV88E6190X)) reg |= PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON; if (MVSWITCH(sc, MV88E6141) || MVSWITCH(sc, MV88E6341) || MVSWITCH(sc, MV88E6190) || MVSWITCH(sc, MV88E6190X)) reg |= PSC_CONTROL_FORCED_EEE; e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL, reg); /* Power on the SERDES interfaces. */ if ((MVSWITCH(sc, MV88E6190) || MVSWITCH(sc, MV88E6190X)) && (port == 9 || port == 10)) { if (e6000sw_is_fixed25port(sc, port)) sgmii = false; else sgmii = true; e6000sw_serdes_power(sc->dev, port, sgmii); } } /* Don't attach miibus at CPU/fixed ports */ if (!e6000sw_is_phyport(sc, port)) continue; err = e6000sw_attach_miibus(sc, port); if (err != 0) { device_printf(sc->dev, "failed to attach miibus\n"); goto out_fail; } } etherswitch_info.es_nports = sc->num_ports; /* Default to port vlan. */ e6000sw_set_vlan_mode(sc, ETHERSWITCH_VLAN_PORT); reg = e6000sw_readreg(sc, REG_GLOBAL, SWITCH_GLOBAL_STATUS); if (reg & SWITCH_GLOBAL_STATUS_IR) device_printf(dev, "switch is ready.\n"); E6000SW_UNLOCK(sc); bus_identify_children(dev); bus_attach_children(dev); taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_tt, hz); return (0); out_fail: E6000SW_UNLOCK(sc); e6000sw_detach(dev); return (err); } static int e6000sw_waitready(e6000sw_softc_t *sc, uint32_t phy, uint32_t reg, uint32_t busybit) { int i; for (i = 0; i < E6000SW_RETRIES; i++) { if ((e6000sw_readreg(sc, phy, reg) & busybit) == 0) return (0); DELAY(1); } return (1); } /* XMDIO/Clause 45 access. */ static int e6000sw_read_xmdio(device_t dev, int phy, int devaddr, int devreg) { e6000sw_softc_t *sc; uint32_t reg; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } reg = devaddr & SMI_CMD_REG_ADDR_MASK; reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK; /* Load C45 register address. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_ADDR); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } /* Start C45 read operation. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_READ); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } /* Read C45 data. */ reg = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG); return (reg & PHY_DATA_MASK); } static int e6000sw_write_xmdio(device_t dev, int phy, int devaddr, int devreg, int val) { e6000sw_softc_t *sc; uint32_t reg; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } reg = devaddr & SMI_CMD_REG_ADDR_MASK; reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK; /* Load C45 register address. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_ADDR); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } /* Load data and start the C45 write operation. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_WRITE); return (0); } static int e6000sw_readphy(device_t dev, int phy, int reg) { e6000sw_softc_t *sc; int locked, ret; sc = device_get_softc(dev); locked = E6000SW_LOCKED(sc); if (!locked) E6000SW_LOCK(sc); ret = e6000sw_readphy_locked(dev, phy, reg); if (!locked) E6000SW_UNLOCK(sc); return (ret); } /* * PHY registers are paged. Put page index in reg 22 (accessible from every * page), then access specific register. */ static int e6000sw_readphy_locked(device_t dev, int phy, int reg) { e6000sw_softc_t *sc; uint32_t val; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, SMI_CMD_OP_C22_READ | (reg & SMI_CMD_REG_ADDR_MASK) | ((phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } val = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG); return (val & PHY_DATA_MASK); } static int e6000sw_writephy(device_t dev, int phy, int reg, int data) { e6000sw_softc_t *sc; int locked, ret; sc = device_get_softc(dev); locked = E6000SW_LOCKED(sc); if (!locked) E6000SW_LOCK(sc); ret = e6000sw_writephy_locked(dev, phy, reg, data); if (!locked) E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_writephy_locked(device_t dev, int phy, int reg, int data) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, data & PHY_DATA_MASK); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, SMI_CMD_OP_C22_WRITE | (reg & SMI_CMD_REG_ADDR_MASK) | ((phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); return (0); } static int e6000sw_detach(device_t dev) { int error, phy; e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK(sc); sc->is_shutdown = true; if (sc->sc_tq != NULL) { while (taskqueue_cancel_timeout(sc->sc_tq, &sc->sc_tt, NULL) != 0) taskqueue_drain_timeout(sc->sc_tq, &sc->sc_tt); } E6000SW_UNLOCK(sc); error = bus_generic_detach(dev); if (error != 0) return (error); if (sc->sc_tq != NULL) taskqueue_free(sc->sc_tq); sx_destroy(&sc->sx); for (phy = 0; phy < sc->num_ports; phy++) { if (sc->ifp[phy] != NULL) if_free(sc->ifp[phy]); if (sc->ifname[phy] != NULL) free(sc->ifname[phy], M_E6000SW); } return (0); } static etherswitch_info_t* e6000sw_getinfo(device_t dev) { return (ðerswitch_info); } static int e6000sw_getconf(device_t dev, etherswitch_conf_t *conf) { struct e6000sw_softc *sc; /* Return the VLAN mode. */ sc = device_get_softc(dev); conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; return (0); } static int e6000sw_setconf(device_t dev, etherswitch_conf_t *conf) { struct e6000sw_softc *sc; /* Set the VLAN mode. */ sc = device_get_softc(dev); if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) { E6000SW_LOCK(sc); e6000sw_set_vlan_mode(sc, conf->vlan_mode); E6000SW_UNLOCK(sc); } return (0); } static void e6000sw_lock(device_t dev) { struct e6000sw_softc *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); } static void e6000sw_unlock(device_t dev) { struct e6000sw_softc *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); E6000SW_UNLOCK(sc); } static int e6000sw_getport(device_t dev, etherswitch_port_t *p) { struct mii_data *mii; int err; struct ifmediareq *ifmr; uint32_t reg; e6000sw_softc_t *sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); if (p->es_port >= sc->num_ports || p->es_port < 0) return (EINVAL); if (!e6000sw_is_portenabled(sc, p->es_port)) return (0); E6000SW_LOCK(sc); e6000sw_get_pvid(sc, p->es_port, &p->es_pvid); /* Port flags. */ reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2); if (reg & PORT_CONTROL2_DISC_TAGGED) p->es_flags |= ETHERSWITCH_PORT_DROPTAGGED; if (reg & PORT_CONTROL2_DISC_UNTAGGED) p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED; err = 0; if (e6000sw_is_fixedport(sc, p->es_port)) { if (e6000sw_is_cpuport(sc, p->es_port)) p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr = &p->es_ifmr; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; ifmr->ifm_count = 0; if (e6000sw_is_fixed25port(sc, p->es_port)) ifmr->ifm_active = IFM_2500_T; else ifmr->ifm_active = IFM_1000_T; ifmr->ifm_active |= IFM_ETHER | IFM_FDX; ifmr->ifm_current = ifmr->ifm_active; ifmr->ifm_mask = 0; } else { mii = e6000sw_miiforphy(sc, p->es_port); err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); } E6000SW_UNLOCK(sc); return (err); } static int e6000sw_setport(device_t dev, etherswitch_port_t *p) { e6000sw_softc_t *sc; int err; struct mii_data *mii; uint32_t reg; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); if (p->es_port >= sc->num_ports || p->es_port < 0) return (EINVAL); if (!e6000sw_is_portenabled(sc, p->es_port)) return (0); E6000SW_LOCK(sc); /* Port flags. */ reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2); if (p->es_flags & ETHERSWITCH_PORT_DROPTAGGED) reg |= PORT_CONTROL2_DISC_TAGGED; else reg &= ~PORT_CONTROL2_DISC_TAGGED; if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED) reg |= PORT_CONTROL2_DISC_UNTAGGED; else reg &= ~PORT_CONTROL2_DISC_UNTAGGED; e6000sw_writereg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2, reg); err = 0; if (p->es_pvid != 0) e6000sw_set_pvid(sc, p->es_port, p->es_pvid); if (e6000sw_is_phyport(sc, p->es_port)) { mii = e6000sw_miiforphy(sc, p->es_port); err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCSIFMEDIA); } E6000SW_UNLOCK(sc); return (err); } static __inline void e6000sw_port_vlan_assign(e6000sw_softc_t *sc, int port, uint32_t fid, uint32_t members) { uint32_t reg; reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP); reg &= ~(PORT_MASK(sc) | PORT_VLAN_MAP_FID_MASK); reg |= members & PORT_MASK(sc) & ~(1 << port); reg |= (fid << PORT_VLAN_MAP_FID) & PORT_VLAN_MAP_FID_MASK; e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VLAN_MAP, reg); reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1); reg &= ~PORT_CONTROL1_FID_MASK; reg |= (fid >> 4) & PORT_CONTROL1_FID_MASK; e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL1, reg); } static int e6000sw_init_vlan(struct e6000sw_softc *sc) { int i, port, ret; uint32_t members; /* Disable all ports */ for (port = 0; port < sc->num_ports; port++) { ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, (ret & ~PORT_CONTROL_ENABLE)); } /* Flush VTU. */ e6000sw_vtu_flush(sc); for (port = 0; port < sc->num_ports; port++) { /* Reset the egress and frame mode. */ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL); ret &= ~(PORT_CONTROL_EGRESS | PORT_CONTROL_FRAME); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, ret); /* Set the 802.1q mode. */ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL2); ret &= ~PORT_CONTROL2_DOT1Q; if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) ret |= PORT_CONTROL2_DOT1Q; e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL2, ret); } for (port = 0; port < sc->num_ports; port++) { if (!e6000sw_is_portenabled(sc, port)) continue; ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID); /* Set port priority */ ret &= ~PORT_VID_PRIORITY_MASK; /* Set VID map */ ret &= ~PORT_VID_DEF_VID_MASK; if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) ret |= 1; else ret |= (port + 1); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, ret); } /* Assign the member ports to each origin port. */ for (port = 0; port < sc->num_ports; port++) { members = 0; if (e6000sw_is_portenabled(sc, port)) { for (i = 0; i < sc->num_ports; i++) { if (i == port || !e6000sw_is_portenabled(sc, i)) continue; members |= (1 << i); } } /* Default to FID 0. */ e6000sw_port_vlan_assign(sc, port, 0, members); } /* Reset internal VLAN table. */ for (i = 0; i < nitems(sc->vlans); i++) sc->vlans[i] = 0; /* Create default VLAN (1). */ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { sc->vlans[0] = 1; e6000sw_vtu_update(sc, 0, sc->vlans[0], 1, 0, sc->ports_mask); } /* Enable all ports */ for (port = 0; port < sc->num_ports; port++) { if (!e6000sw_is_portenabled(sc, port)) continue; ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, (ret | PORT_CONTROL_ENABLE)); } return (0); } static int e6000sw_set_vlan_mode(struct e6000sw_softc *sc, uint32_t mode) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); switch (mode) { case ETHERSWITCH_VLAN_PORT: sc->vlan_mode = ETHERSWITCH_VLAN_PORT; etherswitch_info.es_nvlangroups = sc->num_ports; return (e6000sw_init_vlan(sc)); break; case ETHERSWITCH_VLAN_DOT1Q: sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; etherswitch_info.es_nvlangroups = E6000SW_NUM_VLANS; return (e6000sw_init_vlan(sc)); break; default: return (EINVAL); } } /* * Registers in this switch are divided into sections, specified in * documentation. So as to access any of them, section index and reg index * is necessary. etherswitchcfg uses only one variable, so indexes were * compressed into addr_reg: 32 * section_index + reg_index. */ static int e6000sw_readreg_wrapper(device_t dev, int addr_reg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || (addr_reg < (REG_PORT(sc, 0) * 32))) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } return (e6000sw_readreg(device_get_softc(dev), addr_reg / 32, addr_reg % 32)); } static int e6000sw_writereg_wrapper(device_t dev, int addr_reg, int val) { e6000sw_softc_t *sc; sc = device_get_softc(dev); if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || (addr_reg < (REG_PORT(sc, 0) * 32))) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } e6000sw_writereg(device_get_softc(dev), addr_reg / 32, addr_reg % 32, val); return (0); } /* * setvgroup/getvgroup called from etherswitchfcg need to be locked, * while internal calls do not. */ static int e6000sw_setvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_setvgroup(dev, vg); E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_getvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_getvgroup(dev, vg); E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_set_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { uint32_t port; port = vg->es_vlangroup; if (port > sc->num_ports) return (EINVAL); if (vg->es_member_ports != vg->es_untagged_ports) { device_printf(sc->dev, "Tagged ports not supported.\n"); return (EINVAL); } e6000sw_port_vlan_assign(sc, port, 0, vg->es_untagged_ports); vg->es_vid = port | ETHERSWITCH_VID_VALID; return (0); } static int e6000sw_set_dot1q_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { int i, vlan; vlan = vg->es_vid & ETHERSWITCH_VID_MASK; /* Set VLAN to '0' removes it from table. */ if (vlan == 0) { e6000sw_vtu_update(sc, VTU_PURGE, sc->vlans[vg->es_vlangroup], 0, 0, 0); sc->vlans[vg->es_vlangroup] = 0; return (0); } /* Is this VLAN already in table ? */ for (i = 0; i < etherswitch_info.es_nvlangroups; i++) if (i != vg->es_vlangroup && vlan == sc->vlans[i]) return (EINVAL); sc->vlans[vg->es_vlangroup] = vlan; e6000sw_vtu_update(sc, 0, vlan, vg->es_vlangroup + 1, vg->es_member_ports & sc->ports_mask, vg->es_untagged_ports & sc->ports_mask); return (0); } static int e6000sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) return (e6000sw_set_port_vlan(sc, vg)); else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) return (e6000sw_set_dot1q_vlan(sc, vg)); return (EINVAL); } static int e6000sw_get_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { uint32_t port, reg; port = vg->es_vlangroup; if (port > sc->num_ports) return (EINVAL); if (!e6000sw_is_portenabled(sc, port)) { vg->es_vid = port; return (0); } reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP); vg->es_untagged_ports = vg->es_member_ports = reg & PORT_MASK(sc); vg->es_vid = port | ETHERSWITCH_VID_VALID; vg->es_fid = (reg & PORT_VLAN_MAP_FID_MASK) >> PORT_VLAN_MAP_FID; reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1); vg->es_fid |= (reg & PORT_CONTROL1_FID_MASK) << 4; return (0); } static int e6000sw_get_dot1q_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { int i, port; uint32_t reg; vg->es_fid = 0; vg->es_vid = sc->vlans[vg->es_vlangroup]; vg->es_untagged_ports = vg->es_member_ports = 0; if (vg->es_vid == 0) return (0); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "VTU unit is busy, cannot access\n"); return (EBUSY); } e6000sw_writereg(sc, REG_GLOBAL, VTU_VID, vg->es_vid - 1); reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_OPERATION); reg &= ~VTU_OP_MASK; reg |= VTU_GET_NEXT | VTU_BUSY; e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, reg); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "Timeout while reading\n"); return (EBUSY); } reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_VID); if (reg == VTU_VID_MASK || (reg & VTU_VID_VALID) == 0) return (EINVAL); if ((reg & VTU_VID_MASK) != vg->es_vid) return (EINVAL); vg->es_vid |= ETHERSWITCH_VID_VALID; reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_DATA); for (i = 0; i < sc->num_ports; i++) { if (i == VTU_PPREG(sc)) reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_DATA2); port = (reg >> VTU_PORT(sc, i)) & VTU_PORT_MASK; if (port == VTU_PORT_UNTAGGED) { vg->es_untagged_ports |= (1 << i); vg->es_member_ports |= (1 << i); } else if (port == VTU_PORT_TAGGED) vg->es_member_ports |= (1 << i); } return (0); } static int e6000sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) return (e6000sw_get_port_vlan(sc, vg)); else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) return (e6000sw_get_dot1q_vlan(sc, vg)); return (EINVAL); } static __inline struct mii_data* e6000sw_miiforphy(e6000sw_softc_t *sc, unsigned int phy) { device_t mii_dev; if (!e6000sw_is_phyport(sc, phy)) return (NULL); mii_dev = sc->miibus[phy]; if (mii_dev == NULL) return (NULL); if (device_get_state(mii_dev) != DS_ATTACHED) return (NULL); return (device_get_softc(mii_dev)); } static int e6000sw_ifmedia_upd(if_t ifp) { e6000sw_softc_t *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = e6000sw_miiforphy(sc, if_getdunit(ifp)); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void e6000sw_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { e6000sw_softc_t *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = e6000sw_miiforphy(sc, if_getdunit(ifp)); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int e6000sw_smi_waitready(e6000sw_softc_t *sc, int phy) { int i; for (i = 0; i < E6000SW_SMI_TIMEOUT; i++) { if ((MDIO_READ(sc->dev, phy, SMI_CMD) & SMI_CMD_BUSY) == 0) return (0); DELAY(1); } return (1); } static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *sc, int addr, int reg) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!MVSWITCH_MULTICHIP(sc)) return (MDIO_READ(sc->dev, addr, reg) & 0xffff); if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return (0xffff); } MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD, SMI_CMD_OP_C22_READ | (reg & SMI_CMD_REG_ADDR_MASK) | ((addr << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return (0xffff); } return (MDIO_READ(sc->dev, sc->sw_addr, SMI_DATA) & 0xffff); } static __inline void e6000sw_writereg(e6000sw_softc_t *sc, int addr, int reg, int val) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!MVSWITCH_MULTICHIP(sc)) { MDIO_WRITE(sc->dev, addr, reg, val); return; } if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return; } MDIO_WRITE(sc->dev, sc->sw_addr, SMI_DATA, val); MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD, SMI_CMD_OP_C22_WRITE | (reg & SMI_CMD_REG_ADDR_MASK) | ((addr << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); } static __inline bool e6000sw_is_cpuport(e6000sw_softc_t *sc, int port) { return ((sc->cpuports_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_fixedport(e6000sw_softc_t *sc, int port) { return ((sc->fixed_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_fixed25port(e6000sw_softc_t *sc, int port) { return ((sc->fixed25_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_phyport(e6000sw_softc_t *sc, int port) { uint32_t phy_mask; phy_mask = ~(sc->fixed_mask | sc->cpuports_mask); return ((phy_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_portenabled(e6000sw_softc_t *sc, int port) { return ((sc->ports_mask & (1 << port)) ? true : false); } static __inline void e6000sw_set_pvid(e6000sw_softc_t *sc, int port, int pvid) { uint32_t reg; reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID); reg &= ~PORT_VID_DEF_VID_MASK; reg |= (pvid & PORT_VID_DEF_VID_MASK); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, reg); } static __inline int e6000sw_get_pvid(e6000sw_softc_t *sc, int port, int *pvid) { if (pvid == NULL) return (ENXIO); *pvid = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID) & PORT_VID_DEF_VID_MASK; return (0); } /* * Convert port status to ifmedia. */ static void e6000sw_update_ifmedia(uint16_t portstatus, u_int *media_status, u_int *media_active) { *media_active = IFM_ETHER; *media_status = IFM_AVALID; if ((portstatus & PORT_STATUS_LINK_MASK) != 0) *media_status |= IFM_ACTIVE; else { *media_active |= IFM_NONE; return; } switch (portstatus & PORT_STATUS_SPEED_MASK) { case PORT_STATUS_SPEED_10: *media_active |= IFM_10_T; break; case PORT_STATUS_SPEED_100: *media_active |= IFM_100_TX; break; case PORT_STATUS_SPEED_1000: *media_active |= IFM_1000_T; break; } if ((portstatus & PORT_STATUS_DUPLEX_MASK) == 0) *media_active |= IFM_FDX; else *media_active |= IFM_HDX; } static void e6000sw_tick(void *arg, int p __unused) { e6000sw_softc_t *sc; struct mii_data *mii; struct mii_softc *miisc; uint16_t portstatus; int port; sc = arg; E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); if (sc->is_shutdown) { E6000SW_UNLOCK(sc); return; } for (port = 0; port < sc->num_ports; port++) { /* Tick only on PHY ports */ if (!e6000sw_is_portenabled(sc, port) || !e6000sw_is_phyport(sc, port)) continue; mii = e6000sw_miiforphy(sc, port); if (mii == NULL) continue; portstatus = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_STATUS); e6000sw_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { /* * Note: this is sometimes NULL during PHY * enumeration, although that shouldn't be * happening /after/ tick runs. To work * around this whilst the problem is being * debugged, just do a NULL check here and * continue. */ if (mii->mii_media.ifm_cur == NULL) continue; if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; mii_phy_update(miisc, MII_POLLSTAT); } } E6000SW_UNLOCK(sc); taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_tt, hz); } static void e6000sw_setup(device_t dev, e6000sw_softc_t *sc) { uint32_t atu_ctrl; /* Set aging time. */ atu_ctrl = e6000sw_readreg(sc, REG_GLOBAL, ATU_CONTROL); atu_ctrl &= ~ATU_CONTROL_AGETIME_MASK; atu_ctrl |= E6000SW_DEFAULT_AGETIME << ATU_CONTROL_AGETIME; e6000sw_writereg(sc, REG_GLOBAL, ATU_CONTROL, atu_ctrl); /* Send all with specific mac address to cpu port */ e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_2x, MGMT_EN_ALL); e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_0x, MGMT_EN_ALL); /* Disable Remote Management */ e6000sw_writereg(sc, REG_GLOBAL, SWITCH_GLOBAL_CONTROL2, 0); /* Disable loopback filter and flow control messages */ e6000sw_writereg(sc, REG_GLOBAL2, SWITCH_MGMT, SWITCH_MGMT_PRI_MASK | (1 << SWITCH_MGMT_RSVD2CPU) | SWITCH_MGMT_FC_PRI_MASK | (1 << SWITCH_MGMT_FORCEFLOW)); e6000sw_atu_flush(dev, sc, NO_OPERATION); e6000sw_atu_mac_table(dev, sc, NULL, NO_OPERATION); e6000sw_set_atustat(dev, sc, 0, COUNT_ALL); } static void e6000sw_set_atustat(device_t dev, e6000sw_softc_t *sc, int bin, int flag) { e6000sw_readreg(sc, REG_GLOBAL2, ATU_STATS); e6000sw_writereg(sc, REG_GLOBAL2, ATU_STATS, (bin << ATU_STATS_BIN ) | (flag << ATU_STATS_FLAG)); } static int e6000sw_atu_mac_table(device_t dev, e6000sw_softc_t *sc, struct atu_opt *atu, int flag) { uint16_t ret_opt; uint16_t ret_data; if (flag == NO_OPERATION) return (0); else if ((flag & (LOAD_FROM_FIB | PURGE_FROM_FIB | GET_NEXT_IN_FIB | GET_VIOLATION_DATA | CLEAR_VIOLATION_DATA)) == 0) { device_printf(dev, "Wrong Opcode for ATU operation\n"); return (EINVAL); } if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) { device_printf(dev, "ATU unit is busy, cannot access\n"); return (EBUSY); } ret_opt = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); if (flag & LOAD_FROM_FIB) { ret_data = e6000sw_readreg(sc, REG_GLOBAL, ATU_DATA); e6000sw_writereg(sc, REG_GLOBAL2, ATU_DATA, (ret_data & ~ENTRY_STATE)); } e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR01, atu->mac_01); e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR23, atu->mac_23); e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR45, atu->mac_45); e6000sw_writereg(sc, REG_GLOBAL, ATU_FID, atu->fid); e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (ret_opt | ATU_UNIT_BUSY | flag)); if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) device_printf(dev, "Timeout while waiting ATU\n"); else if (flag & GET_NEXT_IN_FIB) { atu->mac_01 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR01); atu->mac_23 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR23); atu->mac_45 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR45); } return (0); } static int e6000sw_atu_flush(device_t dev, e6000sw_softc_t *sc, int flag) { uint32_t reg; if (flag == NO_OPERATION) return (0); if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) { device_printf(dev, "ATU unit is busy, cannot access\n"); return (EBUSY); } reg = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (reg | ATU_UNIT_BUSY | flag)); if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) device_printf(dev, "Timeout while flushing ATU\n"); return (0); } static int e6000sw_vtu_flush(e6000sw_softc_t *sc) { if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "VTU unit is busy, cannot access\n"); return (EBUSY); } e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, VTU_FLUSH | VTU_BUSY); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "Timeout while flushing VTU\n"); return (ETIMEDOUT); } return (0); } static int e6000sw_vtu_update(e6000sw_softc_t *sc, int purge, int vid, int fid, int members, int untagged) { int i, op; uint32_t data[2]; if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "VTU unit is busy, cannot access\n"); return (EBUSY); } *data = (vid & VTU_VID_MASK); if (purge == 0) *data |= VTU_VID_VALID; e6000sw_writereg(sc, REG_GLOBAL, VTU_VID, *data); if (purge == 0) { data[0] = 0; data[1] = 0; for (i = 0; i < sc->num_ports; i++) { if ((untagged & (1 << i)) != 0) data[i / VTU_PPREG(sc)] |= VTU_PORT_UNTAGGED << VTU_PORT(sc, i); else if ((members & (1 << i)) != 0) data[i / VTU_PPREG(sc)] |= VTU_PORT_TAGGED << VTU_PORT(sc, i); else data[i / VTU_PPREG(sc)] |= VTU_PORT_DISCARD << VTU_PORT(sc, i); } e6000sw_writereg(sc, REG_GLOBAL, VTU_DATA, data[0]); e6000sw_writereg(sc, REG_GLOBAL, VTU_DATA2, data[1]); e6000sw_writereg(sc, REG_GLOBAL, VTU_FID, fid & VTU_FID_MASK(sc)); op = VTU_LOAD; } else op = VTU_PURGE; e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, op | VTU_BUSY); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "Timeout while flushing VTU\n"); return (ETIMEDOUT); } return (0); } diff --git a/sys/dev/etherswitch/etherswitch.c b/sys/dev/etherswitch/etherswitch.c index c66918f77174..ba46d8b2299d 100644 --- a/sys/dev/etherswitch/etherswitch.c +++ b/sys/dev/etherswitch/etherswitch.c @@ -1,226 +1,226 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011-2012 Stefan Bethke. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "etherswitch_if.h" struct etherswitch_softc { device_t sc_dev; struct cdev *sc_devnode; }; static int etherswitch_probe(device_t); static int etherswitch_attach(device_t); static int etherswitch_detach(device_t); static void etherswitch_identify(driver_t *driver, device_t parent); static device_method_t etherswitch_methods[] = { /* device interface */ DEVMETHOD(device_identify, etherswitch_identify), DEVMETHOD(device_probe, etherswitch_probe), DEVMETHOD(device_attach, etherswitch_attach), DEVMETHOD(device_detach, etherswitch_detach), DEVMETHOD_END }; driver_t etherswitch_driver = { "etherswitch", etherswitch_methods, sizeof(struct etherswitch_softc), }; static d_ioctl_t etherswitchioctl; static struct cdevsw etherswitch_cdevsw = { .d_version = D_VERSION, .d_flags = D_TRACKCLOSE, .d_ioctl = etherswitchioctl, .d_name = "etherswitch", }; static void etherswitch_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "etherswitch", -1) == NULL) + if (device_find_child(parent, "etherswitch", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "etherswitch", DEVICE_UNIT_ANY); } static int etherswitch_probe(device_t dev) { device_set_desc(dev, "Switch controller"); return (0); } static int etherswitch_attach(device_t dev) { int err; struct etherswitch_softc *sc; struct make_dev_args devargs; sc = device_get_softc(dev); sc->sc_dev = dev; make_dev_args_init(&devargs); devargs.mda_devsw = ðerswitch_cdevsw; devargs.mda_uid = UID_ROOT; devargs.mda_gid = GID_WHEEL; devargs.mda_mode = 0600; devargs.mda_si_drv1 = sc; err = make_dev_s(&devargs, &sc->sc_devnode, "etherswitch%d", device_get_unit(dev)); if (err != 0) { device_printf(dev, "failed to create character device\n"); return (ENXIO); } return (0); } static int etherswitch_detach(device_t dev) { struct etherswitch_softc *sc = (struct etherswitch_softc *)device_get_softc(dev); if (sc->sc_devnode) destroy_dev(sc->sc_devnode); return (0); } static int etherswitchioctl(struct cdev *cdev, u_long cmd, caddr_t data, int flags, struct thread *td) { struct etherswitch_softc *sc = cdev->si_drv1; device_t dev = sc->sc_dev; device_t etherswitch = device_get_parent(dev); etherswitch_conf_t conf; etherswitch_info_t *info; etherswitch_reg_t *reg; etherswitch_phyreg_t *phyreg; etherswitch_portid_t *portid; int error = 0; switch (cmd) { case IOETHERSWITCHGETINFO: info = ETHERSWITCH_GETINFO(etherswitch); bcopy(info, data, sizeof(etherswitch_info_t)); break; case IOETHERSWITCHGETREG: reg = (etherswitch_reg_t *)data; ETHERSWITCH_LOCK(etherswitch); reg->val = ETHERSWITCH_READREG(etherswitch, reg->reg); ETHERSWITCH_UNLOCK(etherswitch); break; case IOETHERSWITCHSETREG: reg = (etherswitch_reg_t *)data; ETHERSWITCH_LOCK(etherswitch); error = ETHERSWITCH_WRITEREG(etherswitch, reg->reg, reg->val); ETHERSWITCH_UNLOCK(etherswitch); break; case IOETHERSWITCHGETPORT: error = ETHERSWITCH_GETPORT(etherswitch, (etherswitch_port_t *)data); break; case IOETHERSWITCHSETPORT: error = ETHERSWITCH_SETPORT(etherswitch, (etherswitch_port_t *)data); break; case IOETHERSWITCHGETVLANGROUP: error = ETHERSWITCH_GETVGROUP(etherswitch, (etherswitch_vlangroup_t *)data); break; case IOETHERSWITCHSETVLANGROUP: error = ETHERSWITCH_SETVGROUP(etherswitch, (etherswitch_vlangroup_t *)data); break; case IOETHERSWITCHGETPHYREG: phyreg = (etherswitch_phyreg_t *)data; phyreg->val = ETHERSWITCH_READPHYREG(etherswitch, phyreg->phy, phyreg->reg); break; case IOETHERSWITCHSETPHYREG: phyreg = (etherswitch_phyreg_t *)data; error = ETHERSWITCH_WRITEPHYREG(etherswitch, phyreg->phy, phyreg->reg, phyreg->val); break; case IOETHERSWITCHGETCONF: bzero(&conf, sizeof(etherswitch_conf_t)); error = ETHERSWITCH_GETCONF(etherswitch, &conf); bcopy(&conf, data, sizeof(etherswitch_conf_t)); break; case IOETHERSWITCHSETCONF: error = ETHERSWITCH_SETCONF(etherswitch, (etherswitch_conf_t *)data); break; case IOETHERSWITCHFLUSHALL: error = ETHERSWITCH_FLUSH_ALL(etherswitch); break; case IOETHERSWITCHFLUSHPORT: portid = (etherswitch_portid_t *)data; error = ETHERSWITCH_FLUSH_PORT(etherswitch, portid->es_port); break; case IOETHERSWITCHGETTABLE: error = ETHERSWITCH_FETCH_TABLE(etherswitch, (void *) data); break; case IOETHERSWITCHGETTABLEENTRY: error = ETHERSWITCH_FETCH_TABLE_ENTRY(etherswitch, (void *) data); break; default: error = ENOTTY; } return (error); } MODULE_VERSION(etherswitch, 1); diff --git a/sys/dev/etherswitch/ip17x/ip17x.c b/sys/dev/etherswitch/ip17x/ip17x.c index c90d46c49857..42d3bf990c0e 100644 --- a/sys/dev/etherswitch/ip17x/ip17x.c +++ b/sys/dev/etherswitch/ip17x/ip17x.c @@ -1,652 +1,652 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Luiz Otavio O Souza. * Copyright (c) 2011-2012 Stefan Bethke. * Copyright (c) 2012 Adrian Chadd. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif #include "mdio_if.h" #include "miibus_if.h" #include "etherswitch_if.h" MALLOC_DECLARE(M_IP17X); MALLOC_DEFINE(M_IP17X, "ip17x", "ip17x data structures"); static void ip17x_tick(void *); static int ip17x_ifmedia_upd(if_t); static void ip17x_ifmedia_sts(if_t, struct ifmediareq *); static void ip17x_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "ip17x", -1) == NULL) + if (device_find_child(parent, "ip17x", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "ip17x", DEVICE_UNIT_ANY); } static int ip17x_probe(device_t dev) { struct ip17x_softc *sc; uint32_t oui, model, phy_id1, phy_id2; #ifdef FDT phandle_t ip17x_node; pcell_t cell; ip17x_node = fdt_find_compatible(OF_finddevice("/"), "icplus,ip17x", 0); if (ip17x_node == 0) return (ENXIO); #endif sc = device_get_softc(dev); /* Read ID from PHY 0. */ phy_id1 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR1); phy_id2 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR2); oui = MII_OUI(phy_id1, phy_id2); model = MII_MODEL(phy_id2); /* We only care about IC+ devices. */ if (oui != IP17X_OUI) { device_printf(dev, "Unsupported IC+ switch. Unknown OUI: %#x\n", oui); return (ENXIO); } switch (model) { case IP17X_IP175A: sc->sc_switchtype = IP17X_SWITCH_IP175A; break; case IP17X_IP175C: sc->sc_switchtype = IP17X_SWITCH_IP175C; break; default: device_printf(dev, "Unsupported IC+ switch model: %#x\n", model); return (ENXIO); } /* IP175D has a specific ID register. */ model = MDIO_READREG(device_get_parent(dev), IP175D_ID_PHY, IP175D_ID_REG); if (model == 0x175d) sc->sc_switchtype = IP17X_SWITCH_IP175D; else { /* IP178 has more PHYs. Try it. */ model = MDIO_READREG(device_get_parent(dev), 5, MII_PHYIDR1); if (phy_id1 == model) sc->sc_switchtype = IP17X_SWITCH_IP178C; } sc->miipoll = 1; #ifdef FDT if ((OF_getencprop(ip17x_node, "mii-poll", &cell, sizeof(cell))) > 0) sc->miipoll = cell ? 1 : 0; #else (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "mii-poll", &sc->miipoll); #endif device_set_desc(dev, "IC+ IP17x switch driver"); return (BUS_PROBE_DEFAULT); } static int ip17x_attach_phys(struct ip17x_softc *sc) { int err, phy, port; char name[IFNAMSIZ]; port = err = 0; /* PHYs need an interface, so we generate a dummy one */ snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); for (phy = 0; phy < MII_NPHY; phy++) { if (((1 << phy) & sc->phymask) == 0) continue; sc->phyport[phy] = port; sc->portphy[port] = phy; sc->ifp[port] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[port], sc); if_setflags(sc->ifp[port], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX); if_initname(sc->ifp[port], name, port); sc->miibus[port] = malloc(sizeof(device_t), M_IP17X, M_WAITOK | M_ZERO); err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port], ip17x_ifmedia_upd, ip17x_ifmedia_sts, \ BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", device_get_nameunit(*sc->miibus[port]), if_name(sc->ifp[port])); if (err != 0) { device_printf(sc->sc_dev, "attaching PHY %d failed\n", phy); break; } sc->info.es_nports = port + 1; if (++port >= sc->numports) break; } return (err); } static int ip17x_attach(device_t dev) { struct ip17x_softc *sc; int err; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "ip17x", NULL, MTX_DEF); strlcpy(sc->info.es_name, device_get_desc(dev), sizeof(sc->info.es_name)); /* XXX Defaults */ sc->phymask = 0x0f; sc->media = 100; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phymask", &sc->phymask); /* Number of vlans supported by the switch. */ sc->info.es_nvlangroups = IP17X_MAX_VLANS; /* Attach the switch related functions. */ if (IP17X_IS_SWITCH(sc, IP175C)) ip175c_attach(sc); else if (IP17X_IS_SWITCH(sc, IP175D)) ip175d_attach(sc); else /* We don't have support to all the models yet :-/ */ return (ENXIO); /* Always attach the cpu port. */ sc->phymask |= (1 << sc->cpuport); sc->ifp = malloc(sizeof(if_t) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); sc->pvid = malloc(sizeof(uint32_t) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); sc->portphy = malloc(sizeof(int) * sc->numports, M_IP17X, M_WAITOK | M_ZERO); /* Initialize the switch. */ sc->hal.ip17x_reset(sc); /* * Attach the PHYs and complete the bus enumeration. */ err = ip17x_attach_phys(sc); if (err != 0) return (err); /* * Set the switch to port based vlans or disabled (if not supported * on this model). */ sc->hal.ip17x_set_vlan_mode(sc, ETHERSWITCH_VLAN_PORT); bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); if (sc->miipoll) { callout_init(&sc->callout_tick, 0); ip17x_tick(sc); } return (0); } static int ip17x_detach(device_t dev) { struct ip17x_softc *sc; int error, i, port; error = bus_generic_detach(dev); if (error != 0) return (error); sc = device_get_softc(dev); if (sc->miipoll) callout_drain(&sc->callout_tick); for (i=0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = sc->phyport[i]; if (sc->ifp[port] != NULL) if_free(sc->ifp[port]); free(sc->miibus[port], M_IP17X); } free(sc->portphy, M_IP17X); free(sc->miibus, M_IP17X); free(sc->pvid, M_IP17X); free(sc->ifp, M_IP17X); /* Reset the switch. */ sc->hal.ip17x_reset(sc); mtx_destroy(&sc->sc_mtx); return (0); } static inline struct mii_data * ip17x_miiforport(struct ip17x_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (device_get_softc(*sc->miibus[port])); } static inline if_t ip17x_ifpforport(struct ip17x_softc *sc, int port) { if (port < 0 || port > sc->numports) return (NULL); return (sc->ifp[port]); } /* * Poll the status for all PHYs. */ static void ip17x_miipollstat(struct ip17x_softc *sc) { struct mii_softc *miisc; struct mii_data *mii; int i, port; IP17X_LOCK_ASSERT(sc, MA_NOTOWNED); for (i = 0; i < MII_NPHY; i++) { if (((1 << i) & sc->phymask) == 0) continue; port = sc->phyport[i]; if ((*sc->miibus[port]) == NULL) continue; mii = device_get_softc(*sc->miibus[port]); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; ukphy_status(miisc); mii_phy_update(miisc, MII_POLLSTAT); } } } static void ip17x_tick(void *arg) { struct ip17x_softc *sc; sc = arg; ip17x_miipollstat(sc); callout_reset(&sc->callout_tick, hz, ip17x_tick, sc); } static void ip17x_lock(device_t dev) { struct ip17x_softc *sc; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_NOTOWNED); IP17X_LOCK(sc); } static void ip17x_unlock(device_t dev) { struct ip17x_softc *sc; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_OWNED); IP17X_UNLOCK(sc); } static etherswitch_info_t * ip17x_getinfo(device_t dev) { struct ip17x_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static int ip17x_getport(device_t dev, etherswitch_port_t *p) { struct ip17x_softc *sc; struct ifmediareq *ifmr; struct mii_data *mii; int err, phy; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); phy = sc->portphy[p->es_port]; /* Retrieve the PVID. */ p->es_pvid = sc->pvid[phy]; /* Port flags. */ if (sc->addtag & (1 << phy)) p->es_flags |= ETHERSWITCH_PORT_ADDTAG; if (sc->striptag & (1 << phy)) p->es_flags |= ETHERSWITCH_PORT_STRIPTAG; ifmr = &p->es_ifmr; /* No media settings ? */ if (p->es_ifmr.ifm_count == 0) return (0); mii = ip17x_miiforport(sc, p->es_port); if (mii == NULL) return (ENXIO); if (phy == sc->cpuport) { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr->ifm_count = 0; if (sc->media == 100) ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_100_TX | IFM_FDX; else ifmr->ifm_current = ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; } else { err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); if (err) return (err); } return (0); } static int ip17x_setport(device_t dev, etherswitch_port_t *p) { struct ip17x_softc *sc; struct ifmedia *ifm; if_t ifp; struct mii_data *mii; int phy; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= sc->numports) return (ENXIO); phy = sc->portphy[p->es_port]; ifp = ip17x_ifpforport(sc, p->es_port); mii = ip17x_miiforport(sc, p->es_port); if (ifp == NULL || mii == NULL) return (ENXIO); /* Port flags. */ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { /* Set the PVID. */ if (p->es_pvid != 0) { if (IP17X_IS_SWITCH(sc, IP175C) && p->es_pvid > IP175C_LAST_VLAN) return (ENXIO); sc->pvid[phy] = p->es_pvid; } /* Mutually exclusive. */ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG && p->es_flags & ETHERSWITCH_PORT_STRIPTAG) return (EINVAL); /* Reset the settings for this port. */ sc->addtag &= ~(1 << phy); sc->striptag &= ~(1 << phy); /* And then set it to the new value. */ if (p->es_flags & ETHERSWITCH_PORT_ADDTAG) sc->addtag |= (1 << phy); if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG) sc->striptag |= (1 << phy); } /* Update the switch configuration. */ if (sc->hal.ip17x_hw_setup(sc)) return (ENXIO); /* Do not allow media changes on CPU port. */ if (phy == sc->cpuport) return (0); /* No media settings ? */ if (p->es_ifmr.ifm_count == 0) return (0); ifm = &mii->mii_media; return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); } static void ip17x_statchg(device_t dev) { DPRINTF(dev, "%s\n", __func__); } static int ip17x_ifmedia_upd(if_t ifp) { struct ip17x_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); DPRINTF(sc->sc_dev, "%s\n", __func__); mii = ip17x_miiforport(sc, if_getdunit(ifp)); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void ip17x_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct ip17x_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); DPRINTF(sc->sc_dev, "%s\n", __func__); mii = ip17x_miiforport(sc, if_getdunit(ifp)); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int ip17x_readreg(device_t dev, int addr) { struct ip17x_softc *sc __diagused; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_OWNED); /* Not supported. */ return (0); } static int ip17x_writereg(device_t dev, int addr, int value) { struct ip17x_softc *sc __diagused; sc = device_get_softc(dev); IP17X_LOCK_ASSERT(sc, MA_OWNED); /* Not supported. */ return (0); } static int ip17x_getconf(device_t dev, etherswitch_conf_t *conf) { struct ip17x_softc *sc; sc = device_get_softc(dev); /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->hal.ip17x_get_vlan_mode(sc); return (0); } static int ip17x_setconf(device_t dev, etherswitch_conf_t *conf) { struct ip17x_softc *sc; sc = device_get_softc(dev); /* Set the VLAN mode. */ if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) sc->hal.ip17x_set_vlan_mode(sc, conf->vlan_mode); return (0); } static device_method_t ip17x_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ip17x_identify), DEVMETHOD(device_probe, ip17x_probe), DEVMETHOD(device_attach, ip17x_attach), DEVMETHOD(device_detach, ip17x_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, ip17x_readphy), DEVMETHOD(miibus_writereg, ip17x_writephy), DEVMETHOD(miibus_statchg, ip17x_statchg), /* MDIO interface */ DEVMETHOD(mdio_readreg, ip17x_readphy), DEVMETHOD(mdio_writereg, ip17x_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_lock, ip17x_lock), DEVMETHOD(etherswitch_unlock, ip17x_unlock), DEVMETHOD(etherswitch_getinfo, ip17x_getinfo), DEVMETHOD(etherswitch_readreg, ip17x_readreg), DEVMETHOD(etherswitch_writereg, ip17x_writereg), DEVMETHOD(etherswitch_readphyreg, ip17x_readphy), DEVMETHOD(etherswitch_writephyreg, ip17x_writephy), DEVMETHOD(etherswitch_getport, ip17x_getport), DEVMETHOD(etherswitch_setport, ip17x_setport), DEVMETHOD(etherswitch_getvgroup, ip17x_getvgroup), DEVMETHOD(etherswitch_setvgroup, ip17x_setvgroup), DEVMETHOD(etherswitch_getconf, ip17x_getconf), DEVMETHOD(etherswitch_setconf, ip17x_setconf), DEVMETHOD_END }; DEFINE_CLASS_0(ip17x, ip17x_driver, ip17x_methods, sizeof(struct ip17x_softc)); DRIVER_MODULE(ip17x, mdio, ip17x_driver, 0, 0); DRIVER_MODULE(miibus, ip17x, miibus_driver, 0, 0); DRIVER_MODULE(etherswitch, ip17x, etherswitch_driver, 0, 0); MODULE_VERSION(ip17x, 1); #ifdef FDT MODULE_DEPEND(ip17x, mdio, 1, 1, 1); /* XXX which versions? */ #else DRIVER_MODULE(mdio, ip17x, mdio_driver, 0, 0); MODULE_DEPEND(ip17x, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(ip17x, etherswitch, 1, 1, 1); /* XXX which versions? */ #endif diff --git a/sys/dev/etherswitch/miiproxy.c b/sys/dev/etherswitch/miiproxy.c index 2af6533d41c8..79342a9e8e03 100644 --- a/sys/dev/etherswitch/miiproxy.c +++ b/sys/dev/etherswitch/miiproxy.c @@ -1,434 +1,434 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011-2012 Stefan Bethke. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdio_if.h" #include "miibus_if.h" MALLOC_DECLARE(M_MIIPROXY); MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures"); driver_t miiproxy_driver; driver_t mdioproxy_driver; struct miiproxy_softc { device_t parent; device_t proxy; device_t mdio; }; struct mdioproxy_softc { }; /* * The rendezvous data structures and functions allow two device endpoints to * match up, so that the proxy endpoint can be associated with a target * endpoint. The proxy has to know the device name of the target that it * wants to associate with, for example through a hint. The rendezvous code * makes no assumptions about the devices that want to meet. */ struct rendezvous_entry; enum rendezvous_op { RENDEZVOUS_ATTACH, RENDEZVOUS_DETACH }; typedef int (*rendezvous_callback_t)(enum rendezvous_op, struct rendezvous_entry *); static SLIST_HEAD(rendezvoushead, rendezvous_entry) rendezvoushead = SLIST_HEAD_INITIALIZER(rendezvoushead); struct rendezvous_endpoint { device_t device; const char *name; rendezvous_callback_t callback; }; struct rendezvous_entry { SLIST_ENTRY(rendezvous_entry) entries; struct rendezvous_endpoint proxy; struct rendezvous_endpoint target; }; /* * Call the callback routines for both the proxy and the target. If either * returns an error, undo the attachment. */ static int rendezvous_attach(struct rendezvous_entry *e, struct rendezvous_endpoint *ep) { int error; error = e->proxy.callback(RENDEZVOUS_ATTACH, e); if (error == 0) { error = e->target.callback(RENDEZVOUS_ATTACH, e); if (error != 0) { e->proxy.callback(RENDEZVOUS_DETACH, e); ep->device = NULL; ep->callback = NULL; } } return (error); } /* * Create an entry for the proxy in the rendezvous list. The name parameter * indicates the name of the device that is the target endpoint for this * rendezvous. The callback will be invoked as soon as the target is * registered: either immediately if the target registered itself earlier, * or once the target registers. Returns ENXIO if the target has not yet * registered. */ static int rendezvous_register_proxy(device_t dev, const char *name, rendezvous_callback_t callback) { struct rendezvous_entry *e; KASSERT(callback != NULL, ("callback must be set")); SLIST_FOREACH(e, &rendezvoushead, entries) { if (strcmp(name, e->target.name) == 0) { /* the target is already attached */ e->proxy.name = device_get_nameunit(dev); e->proxy.device = dev; e->proxy.callback = callback; return (rendezvous_attach(e, &e->proxy)); } } e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO); e->proxy.name = device_get_nameunit(dev); e->proxy.device = dev; e->proxy.callback = callback; e->target.name = name; SLIST_INSERT_HEAD(&rendezvoushead, e, entries); return (ENXIO); } /* * Create an entry in the rendezvous list for the target. * Returns ENXIO if the proxy has not yet registered. */ static int rendezvous_register_target(device_t dev, rendezvous_callback_t callback) { struct rendezvous_entry *e; const char *name; KASSERT(callback != NULL, ("callback must be set")); name = device_get_nameunit(dev); SLIST_FOREACH(e, &rendezvoushead, entries) { if (strcmp(name, e->target.name) == 0) { e->target.device = dev; e->target.callback = callback; return (rendezvous_attach(e, &e->target)); } } e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO); e->target.name = name; e->target.device = dev; e->target.callback = callback; SLIST_INSERT_HEAD(&rendezvoushead, e, entries); return (ENXIO); } /* * Remove the registration for the proxy. */ static int rendezvous_unregister_proxy(device_t dev) { struct rendezvous_entry *e; int error = 0; SLIST_FOREACH(e, &rendezvoushead, entries) { if (e->proxy.device == dev) { if (e->target.device == NULL) { SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries); free(e, M_MIIPROXY); return (0); } else { e->proxy.callback(RENDEZVOUS_DETACH, e); e->target.callback(RENDEZVOUS_DETACH, e); } e->proxy.device = NULL; e->proxy.callback = NULL; return (error); } } return (ENOENT); } /* * Remove the registration for the target. */ static int rendezvous_unregister_target(device_t dev) { struct rendezvous_entry *e; int error = 0; SLIST_FOREACH(e, &rendezvoushead, entries) { if (e->target.device == dev) { if (e->proxy.device == NULL) { SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries); free(e, M_MIIPROXY); return (0); } else { e->proxy.callback(RENDEZVOUS_DETACH, e); e->target.callback(RENDEZVOUS_DETACH, e); } e->target.device = NULL; e->target.callback = NULL; return (error); } } return (ENOENT); } /* * Functions of the proxy that is interposed between the ethernet interface * driver and the miibus device. */ static int miiproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous) { struct miiproxy_softc *sc = device_get_softc(rendezvous->proxy.device); switch (op) { case RENDEZVOUS_ATTACH: sc->mdio = device_get_parent(rendezvous->target.device); break; case RENDEZVOUS_DETACH: sc->mdio = NULL; break; } return (0); } static int miiproxy_probe(device_t dev) { device_set_desc(dev, "MII/MDIO proxy, MII side"); return (BUS_PROBE_SPECIFIC); } static int miiproxy_attach(device_t dev) { /* * The ethernet interface needs to call mii_attach_proxy() to pass * the relevant parameters for rendezvous with the MDIO target. */ bus_attach_children(dev); return (0); } static int miiproxy_detach(device_t dev) { rendezvous_unregister_proxy(dev); bus_generic_detach(dev); return (0); } static int miiproxy_readreg(device_t dev, int phy, int reg) { struct miiproxy_softc *sc = device_get_softc(dev); if (sc->mdio != NULL) return (MDIO_READREG(sc->mdio, phy, reg)); return (-1); } static int miiproxy_writereg(device_t dev, int phy, int reg, int val) { struct miiproxy_softc *sc = device_get_softc(dev); if (sc->mdio != NULL) return (MDIO_WRITEREG(sc->mdio, phy, reg, val)); return (-1); } static void miiproxy_statchg(device_t dev) { MIIBUS_STATCHG(device_get_parent(dev)); } static void miiproxy_linkchg(device_t dev) { MIIBUS_LINKCHG(device_get_parent(dev)); } static void miiproxy_mediainit(device_t dev) { MIIBUS_MEDIAINIT(device_get_parent(dev)); } /* * Functions for the MDIO target device driver. */ static int mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous) { return (0); } static void mdioproxy_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, driver->name, -1) == NULL) { + if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) == NULL) { BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY); } } static int mdioproxy_probe(device_t dev) { device_set_desc(dev, "MII/MDIO proxy, MDIO side"); return (BUS_PROBE_SPECIFIC); } static int mdioproxy_attach(device_t dev) { rendezvous_register_target(dev, mdioproxy_rendezvous_callback); bus_attach_children(dev); return (0); } static int mdioproxy_detach(device_t dev) { rendezvous_unregister_target(dev); bus_generic_detach(dev); return (0); } /* * Attach this proxy in place of miibus. The target MDIO must be attached * already. Returns NULL on error. */ device_t mii_attach_proxy(device_t dev) { struct miiproxy_softc *sc; const char *name; device_t miiproxy; if (resource_string_value(device_get_name(dev), device_get_unit(dev), "mdio", &name) != 0) { if (bootverbose) printf("mii_attach_proxy: not attaching, no mdio" " device hint for %s\n", device_get_nameunit(dev)); return (NULL); } miiproxy = device_add_child(dev, miiproxy_driver.name, DEVICE_UNIT_ANY); bus_attach_children(dev); sc = device_get_softc(miiproxy); sc->parent = dev; sc->proxy = miiproxy; if (rendezvous_register_proxy(miiproxy, name, miiproxy_rendezvous_callback) != 0) { device_printf(dev, "can't attach proxy\n"); return (NULL); } device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio)); return (miiproxy); } static device_method_t miiproxy_methods[] = { /* device interface */ DEVMETHOD(device_probe, miiproxy_probe), DEVMETHOD(device_attach, miiproxy_attach), DEVMETHOD(device_detach, miiproxy_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* MII interface */ DEVMETHOD(miibus_readreg, miiproxy_readreg), DEVMETHOD(miibus_writereg, miiproxy_writereg), DEVMETHOD(miibus_statchg, miiproxy_statchg), DEVMETHOD(miibus_linkchg, miiproxy_linkchg), DEVMETHOD(miibus_mediainit, miiproxy_mediainit), DEVMETHOD_END }; static device_method_t mdioproxy_methods[] = { /* device interface */ DEVMETHOD(device_identify, mdioproxy_identify), DEVMETHOD(device_probe, mdioproxy_probe), DEVMETHOD(device_attach, mdioproxy_attach), DEVMETHOD(device_detach, mdioproxy_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods, sizeof(struct miiproxy_softc)); DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods, sizeof(struct mdioproxy_softc)); DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, 0, 0); DRIVER_MODULE(miibus, miiproxy, miibus_driver, 0, 0); MODULE_VERSION(miiproxy, 1); MODULE_DEPEND(miiproxy, miibus, 1, 1, 1); MODULE_DEPEND(miiproxy, mdio, 1, 1, 1); diff --git a/sys/dev/etherswitch/rtl8366/rtl8366rb.c b/sys/dev/etherswitch/rtl8366/rtl8366rb.c index 079244b2f745..9000061ae138 100644 --- a/sys/dev/etherswitch/rtl8366/rtl8366rb.c +++ b/sys/dev/etherswitch/rtl8366/rtl8366rb.c @@ -1,959 +1,959 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015-2016 Hiroki Mori. * Copyright (c) 2011-2012 Stefan Bethke. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include "opt_etherswitch.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdio_if.h" #include "iicbus_if.h" #include "miibus_if.h" #include "etherswitch_if.h" struct rtl8366rb_softc { struct mtx sc_mtx; /* serialize access to softc */ int smi_acquired; /* serialize access to SMI/I2C bus */ struct mtx callout_mtx; /* serialize callout */ device_t dev; int vid[RTL8366_NUM_VLANS]; char *ifname[RTL8366_NUM_PHYS]; device_t miibus[RTL8366_NUM_PHYS]; if_t ifp[RTL8366_NUM_PHYS]; struct callout callout_tick; etherswitch_info_t info; int chip_type; int phy4cpu; int numphys; }; #define RTL_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define RTL_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define RTL_LOCK_ASSERT(_sc, _what) mtx_assert(&(_s)c->sc_mtx, (_what)) #define RTL_TRYLOCK(_sc) mtx_trylock(&(_sc)->sc_mtx) #define RTL_WAITOK 0 #define RTL_NOWAIT 1 #define RTL_SMI_ACQUIRED 1 #define RTL_SMI_ACQUIRED_ASSERT(_sc) \ KASSERT((_sc)->smi_acquired == RTL_SMI_ACQUIRED, ("smi must be acquired @%s", __FUNCTION__)) #if defined(DEBUG) #define DPRINTF(dev, args...) device_printf(dev, args) #define DEVERR(dev, err, fmt, args...) do { \ if (err != 0) device_printf(dev, fmt, err, args); \ } while (0) #define DEBUG_INCRVAR(var) do { \ var++; \ } while (0) static int callout_blocked = 0; static int iic_select_retries = 0; static int phy_access_retries = 0; static SYSCTL_NODE(_debug, OID_AUTO, rtl8366rb, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "rtl8366rb"); SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, callout_blocked, CTLFLAG_RW, &callout_blocked, 0, "number of times the callout couldn't acquire the bus"); SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, iic_select_retries, CTLFLAG_RW, &iic_select_retries, 0, "number of times the I2C bus selection had to be retried"); SYSCTL_INT(_debug_rtl8366rb, OID_AUTO, phy_access_retries, CTLFLAG_RW, &phy_access_retries, 0, "number of times PHY register access had to be retried"); #else #define DPRINTF(dev, args...) #define DEVERR(dev, err, fmt, args...) #define DEBUG_INCRVAR(var) #endif static int smi_probe(device_t dev); static int smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep); static int smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep); static int smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep); static void rtl8366rb_tick(void *arg); static int rtl8366rb_ifmedia_upd(if_t); static void rtl8366rb_ifmedia_sts(if_t, struct ifmediareq *); static void rtl8366rb_identify(driver_t *driver, device_t parent) { device_t child; struct iicbus_ivar *devi; - if (device_find_child(parent, "rtl8366rb", -1) == NULL) { + if (device_find_child(parent, "rtl8366rb", DEVICE_UNIT_ANY) == NULL) { child = BUS_ADD_CHILD(parent, 0, "rtl8366rb", DEVICE_UNIT_ANY); devi = IICBUS_IVAR(child); devi->addr = RTL8366_IIC_ADDR; } } static int rtl8366rb_probe(device_t dev) { struct rtl8366rb_softc *sc; sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); if (smi_probe(dev) != 0) return (ENXIO); if (sc->chip_type == RTL8366RB) device_set_desc(dev, "RTL8366RB Ethernet Switch Controller"); else device_set_desc(dev, "RTL8366SR Ethernet Switch Controller"); return (BUS_PROBE_DEFAULT); } static void rtl8366rb_init(device_t dev) { struct rtl8366rb_softc *sc; int i; sc = device_get_softc(dev); /* Initialisation for TL-WR1043ND */ #ifdef RTL8366_SOFT_RESET smi_rmw(dev, RTL8366_RCR, RTL8366_RCR_SOFT_RESET, RTL8366_RCR_SOFT_RESET, RTL_WAITOK); #else smi_rmw(dev, RTL8366_RCR, RTL8366_RCR_HARD_RESET, RTL8366_RCR_HARD_RESET, RTL_WAITOK); #endif /* hard reset not return ack */ DELAY(100000); /* Enable 16 VLAN mode */ smi_rmw(dev, RTL8366_SGCR, RTL8366_SGCR_EN_VLAN | RTL8366_SGCR_EN_VLAN_4KTB, RTL8366_SGCR_EN_VLAN, RTL_WAITOK); /* Initialize our vlan table. */ for (i = 0; i <= 1; i++) sc->vid[i] = (i + 1) | ETHERSWITCH_VID_VALID; /* Remove port 0 from VLAN 1. */ smi_rmw(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, 0), (1 << 0), 0, RTL_WAITOK); /* Add port 0 untagged and port 5 tagged to VLAN 2. */ smi_rmw(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, 1), ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_MEMBER_SHIFT) | ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_UNTAG_SHIFT), ((1 << 5 | 1 << 0) << RTL8366_VMCR_MU_MEMBER_SHIFT | ((1 << 0) << RTL8366_VMCR_MU_UNTAG_SHIFT)), RTL_WAITOK); /* Set PVID 2 for port 0. */ smi_rmw(dev, RTL8366_PVCR_REG(0), RTL8366_PVCR_VAL(0, RTL8366_PVCR_PORT_MASK), RTL8366_PVCR_VAL(0, 1), RTL_WAITOK); } static int rtl8366rb_attach(device_t dev) { struct rtl8366rb_softc *sc; uint16_t rev = 0; char name[IFNAMSIZ]; int err = 0; int i; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->sc_mtx, "rtl8366rb", NULL, MTX_DEF); sc->smi_acquired = 0; mtx_init(&sc->callout_mtx, "rtl8366rbcallout", NULL, MTX_DEF); rtl8366rb_init(dev); smi_read(dev, RTL8366_CVCR, &rev, RTL_WAITOK); device_printf(dev, "rev. %d\n", rev & 0x000f); sc->phy4cpu = 0; (void) resource_int_value(device_get_name(dev), device_get_unit(dev), "phy4cpu", &sc->phy4cpu); sc->numphys = sc->phy4cpu ? RTL8366_NUM_PHYS - 1 : RTL8366_NUM_PHYS; sc->info.es_nports = sc->numphys + 1; sc->info.es_nvlangroups = RTL8366_NUM_VLANS; sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q; if (sc->chip_type == RTL8366RB) sprintf(sc->info.es_name, "Realtek RTL8366RB"); else sprintf(sc->info.es_name, "Realtek RTL8366SR"); /* attach miibus and phys */ /* PHYs need an interface, so we generate a dummy one */ for (i = 0; i < sc->numphys; i++) { sc->ifp[i] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[i], sc); if_setflagbits(sc->ifp[i], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX, 0); snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(dev)); sc->ifname[i] = malloc(strlen(name)+1, M_DEVBUF, M_WAITOK); bcopy(name, sc->ifname[i], strlen(name)+1); if_initname(sc->ifp[i], sc->ifname[i], i); err = mii_attach(dev, &sc->miibus[i], sc->ifp[i], rtl8366rb_ifmedia_upd, \ rtl8366rb_ifmedia_sts, BMSR_DEFCAPMASK, \ i, MII_OFFSET_ANY, 0); if (err != 0) { device_printf(dev, "attaching PHY %d failed\n", i); return (err); } } bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); callout_init_mtx(&sc->callout_tick, &sc->callout_mtx, 0); rtl8366rb_tick(sc); return (err); } static int rtl8366rb_detach(device_t dev) { struct rtl8366rb_softc *sc; int error, i; error = bus_generic_detach(dev); if (error != 0) return (error); sc = device_get_softc(dev); for (i=0; i < sc->numphys; i++) { if (sc->ifp[i] != NULL) if_free(sc->ifp[i]); free(sc->ifname[i], M_DEVBUF); } callout_drain(&sc->callout_tick); mtx_destroy(&sc->callout_mtx); mtx_destroy(&sc->sc_mtx); return (0); } static void rtl8366rb_update_ifmedia(int portstatus, u_int *media_status, u_int *media_active) { *media_active = IFM_ETHER; *media_status = IFM_AVALID; if ((portstatus & RTL8366_PLSR_LINK) != 0) *media_status |= IFM_ACTIVE; else { *media_active |= IFM_NONE; return; } switch (portstatus & RTL8366_PLSR_SPEED_MASK) { case RTL8366_PLSR_SPEED_10: *media_active |= IFM_10_T; break; case RTL8366_PLSR_SPEED_100: *media_active |= IFM_100_TX; break; case RTL8366_PLSR_SPEED_1000: *media_active |= IFM_1000_T; break; } if ((portstatus & RTL8366_PLSR_FULLDUPLEX) != 0) *media_active |= IFM_FDX; else *media_active |= IFM_HDX; if ((portstatus & RTL8366_PLSR_TXPAUSE) != 0) *media_active |= IFM_ETH_TXPAUSE; if ((portstatus & RTL8366_PLSR_RXPAUSE) != 0) *media_active |= IFM_ETH_RXPAUSE; } static void rtl833rb_miipollstat(struct rtl8366rb_softc *sc) { int i; struct mii_data *mii; struct mii_softc *miisc; uint16_t value; int portstatus; for (i = 0; i < sc->numphys; i++) { mii = device_get_softc(sc->miibus[i]); if ((i % 2) == 0) { if (smi_read(sc->dev, RTL8366_PLSR_BASE + i/2, &value, RTL_NOWAIT) != 0) { DEBUG_INCRVAR(callout_blocked); return; } portstatus = value & 0xff; } else { portstatus = (value >> 8) & 0xff; } rtl8366rb_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; mii_phy_update(miisc, MII_POLLSTAT); } } } static void rtl8366rb_tick(void *arg) { struct rtl8366rb_softc *sc; sc = arg; rtl833rb_miipollstat(sc); callout_reset(&sc->callout_tick, hz, rtl8366rb_tick, sc); } static int smi_probe(device_t dev) { struct rtl8366rb_softc *sc; device_t iicbus, iicha; int err, i, j; uint16_t chipid; char bytes[2]; int xferd; sc = device_get_softc(dev); iicbus = device_get_parent(dev); iicha = device_get_parent(iicbus); for (i = 0; i < 2; ++i) { iicbus_reset(iicbus, IIC_FASTEST, RTL8366_IIC_ADDR, NULL); for (j=3; j--; ) { IICBUS_STOP(iicha); /* * we go directly to the host adapter because iicbus.c * only issues a stop on a bus that was successfully started. */ } err = iicbus_request_bus(iicbus, dev, IIC_WAIT); if (err != 0) goto out; err = iicbus_start(iicbus, RTL8366_IIC_ADDR | RTL_IICBUS_READ, RTL_IICBUS_TIMEOUT); if (err != 0) goto out; if (i == 0) { bytes[0] = RTL8366RB_CIR & 0xff; bytes[1] = (RTL8366RB_CIR >> 8) & 0xff; } else { bytes[0] = RTL8366SR_CIR & 0xff; bytes[1] = (RTL8366SR_CIR >> 8) & 0xff; } err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT); if (err != 0) goto out; err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0); if (err != 0) goto out; chipid = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff); if (i == 0 && chipid == RTL8366RB_CIR_ID8366RB) { DPRINTF(dev, "chip id 0x%04x\n", chipid); sc->chip_type = RTL8366RB; err = 0; break; } if (i == 1 && chipid == RTL8366SR_CIR_ID8366SR) { DPRINTF(dev, "chip id 0x%04x\n", chipid); sc->chip_type = RTL8366SR; err = 0; break; } if (i == 0) { iicbus_stop(iicbus); iicbus_release_bus(iicbus, dev); } } if (i == 2) err = ENXIO; out: iicbus_stop(iicbus); iicbus_release_bus(iicbus, dev); return (err == 0 ? 0 : ENXIO); } static int smi_acquire(struct rtl8366rb_softc *sc, int sleep) { int r = 0; if (sleep == RTL_WAITOK) RTL_LOCK(sc); else if (RTL_TRYLOCK(sc) == 0) return (EWOULDBLOCK); if (sc->smi_acquired == RTL_SMI_ACQUIRED) r = EBUSY; else { r = iicbus_request_bus(device_get_parent(sc->dev), sc->dev, \ sleep == RTL_WAITOK ? IIC_WAIT : IIC_DONTWAIT); if (r == 0) sc->smi_acquired = RTL_SMI_ACQUIRED; } RTL_UNLOCK(sc); return (r); } static int smi_release(struct rtl8366rb_softc *sc, int sleep) { if (sleep == RTL_WAITOK) RTL_LOCK(sc); else if (RTL_TRYLOCK(sc) == 0) return (EWOULDBLOCK); RTL_SMI_ACQUIRED_ASSERT(sc); iicbus_release_bus(device_get_parent(sc->dev), sc->dev); sc->smi_acquired = 0; RTL_UNLOCK(sc); return (0); } static int smi_select(device_t dev, int op, int sleep) { struct rtl8366rb_softc *sc; int err, i; device_t iicbus; struct iicbus_ivar *devi; int slave; sc = device_get_softc(dev); iicbus = device_get_parent(dev); devi = IICBUS_IVAR(dev); slave = devi->addr; RTL_SMI_ACQUIRED_ASSERT((struct rtl8366rb_softc *)device_get_softc(dev)); if (sc->chip_type == RTL8366SR) { // RTL8366SR work around // this is same work around at probe for (int i=3; i--; ) IICBUS_STOP(device_get_parent(device_get_parent(dev))); } /* * The chip does not use clock stretching when it is busy, * instead ignoring the command. Retry a few times. */ for (i = RTL_IICBUS_RETRIES; i--; ) { err = iicbus_start(iicbus, slave | op, RTL_IICBUS_TIMEOUT); if (err != IIC_ENOACK) break; if (sleep == RTL_WAITOK) { DEBUG_INCRVAR(iic_select_retries); pause("smi_select", RTL_IICBUS_RETRY_SLEEP); } else break; } return (err); } static int smi_read_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t *data, int sleep) { int err; device_t iicbus; char bytes[2]; int xferd; iicbus = device_get_parent(sc->dev); RTL_SMI_ACQUIRED_ASSERT(sc); bytes[0] = addr & 0xff; bytes[1] = (addr >> 8) & 0xff; err = smi_select(sc->dev, RTL_IICBUS_READ, sleep); if (err != 0) goto out; err = iicbus_write(iicbus, bytes, 2, &xferd, RTL_IICBUS_TIMEOUT); if (err != 0) goto out; err = iicbus_read(iicbus, bytes, 2, &xferd, IIC_LAST_READ, 0); if (err != 0) goto out; *data = ((bytes[1] & 0xff) << 8) | (bytes[0] & 0xff); out: iicbus_stop(iicbus); return (err); } static int smi_write_locked(struct rtl8366rb_softc *sc, uint16_t addr, uint16_t data, int sleep) { int err; device_t iicbus; char bytes[4]; int xferd; iicbus = device_get_parent(sc->dev); RTL_SMI_ACQUIRED_ASSERT(sc); bytes[0] = addr & 0xff; bytes[1] = (addr >> 8) & 0xff; bytes[2] = data & 0xff; bytes[3] = (data >> 8) & 0xff; err = smi_select(sc->dev, RTL_IICBUS_WRITE, sleep); if (err == 0) err = iicbus_write(iicbus, bytes, 4, &xferd, RTL_IICBUS_TIMEOUT); iicbus_stop(iicbus); return (err); } static int smi_read(device_t dev, uint16_t addr, uint16_t *data, int sleep) { struct rtl8366rb_softc *sc; int err; sc = device_get_softc(dev); err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); err = smi_read_locked(sc, addr, data, sleep); smi_release(sc, sleep); DEVERR(dev, err, "smi_read()=%d: addr=%04x\n", addr); return (err == 0 ? 0 : EIO); } static int smi_write(device_t dev, uint16_t addr, uint16_t data, int sleep) { struct rtl8366rb_softc *sc; int err; sc = device_get_softc(dev); err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); err = smi_write_locked(sc, addr, data, sleep); smi_release(sc, sleep); DEVERR(dev, err, "smi_write()=%d: addr=%04x\n", addr); return (err == 0 ? 0 : EIO); } static int smi_rmw(device_t dev, uint16_t addr, uint16_t mask, uint16_t data, int sleep) { struct rtl8366rb_softc *sc; int err; uint16_t oldv, newv; sc = device_get_softc(dev); err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); if (err == 0) { err = smi_read_locked(sc, addr, &oldv, sleep); if (err == 0) { newv = oldv & ~mask; newv |= data & mask; if (newv != oldv) err = smi_write_locked(sc, addr, newv, sleep); } } smi_release(sc, sleep); DEVERR(dev, err, "smi_rmw()=%d: addr=%04x\n", addr); return (err == 0 ? 0 : EIO); } static etherswitch_info_t * rtl_getinfo(device_t dev) { struct rtl8366rb_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static int rtl_readreg(device_t dev, int reg) { uint16_t data; data = 0; smi_read(dev, reg, &data, RTL_WAITOK); return (data); } static int rtl_writereg(device_t dev, int reg, int value) { return (smi_write(dev, reg, value, RTL_WAITOK)); } static int rtl_getport(device_t dev, etherswitch_port_t *p) { struct rtl8366rb_softc *sc; struct ifmedia *ifm; struct mii_data *mii; struct ifmediareq *ifmr; uint16_t v; int err, vlangroup; sc = device_get_softc(dev); ifmr = &p->es_ifmr; if (p->es_port < 0 || p->es_port >= (sc->numphys + 1)) return (ENXIO); if (sc->phy4cpu && p->es_port == sc->numphys) { vlangroup = RTL8366_PVCR_GET(p->es_port + 1, rtl_readreg(dev, RTL8366_PVCR_REG(p->es_port + 1))); } else { vlangroup = RTL8366_PVCR_GET(p->es_port, rtl_readreg(dev, RTL8366_PVCR_REG(p->es_port))); } p->es_pvid = sc->vid[vlangroup] & ETHERSWITCH_VID_MASK; if (p->es_port < sc->numphys) { mii = device_get_softc(sc->miibus[p->es_port]); ifm = &mii->mii_media; err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCGIFMEDIA); if (err) return (err); } else { /* fill in fixed values for CPU port */ p->es_flags |= ETHERSWITCH_PORT_CPU; smi_read(dev, RTL8366_PLSR_BASE + (RTL8366_NUM_PHYS)/2, &v, RTL_WAITOK); v = v >> (8 * ((RTL8366_NUM_PHYS) % 2)); rtl8366rb_update_ifmedia(v, &ifmr->ifm_status, &ifmr->ifm_active); ifmr->ifm_current = ifmr->ifm_active; ifmr->ifm_mask = 0; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; /* Return our static media list. */ if (ifmr->ifm_count > 0) { ifmr->ifm_count = 1; ifmr->ifm_ulist[0] = IFM_MAKEWORD(IFM_ETHER, IFM_1000_T, IFM_FDX, 0); } else ifmr->ifm_count = 0; } return (0); } static int rtl_setport(device_t dev, etherswitch_port_t *p) { struct rtl8366rb_softc *sc; int i, err, vlangroup; struct ifmedia *ifm; struct mii_data *mii; int port; sc = device_get_softc(dev); if (p->es_port < 0 || p->es_port >= (sc->numphys + 1)) return (ENXIO); vlangroup = -1; for (i = 0; i < RTL8366_NUM_VLANS; i++) { if ((sc->vid[i] & ETHERSWITCH_VID_MASK) == p->es_pvid) { vlangroup = i; break; } } if (vlangroup == -1) return (ENXIO); if (sc->phy4cpu && p->es_port == sc->numphys) { port = p->es_port + 1; } else { port = p->es_port; } err = smi_rmw(dev, RTL8366_PVCR_REG(port), RTL8366_PVCR_VAL(port, RTL8366_PVCR_PORT_MASK), RTL8366_PVCR_VAL(port, vlangroup), RTL_WAITOK); if (err) return (err); /* CPU Port */ if (p->es_port == sc->numphys) return (0); mii = device_get_softc(sc->miibus[p->es_port]); ifm = &mii->mii_media; err = ifmedia_ioctl(sc->ifp[p->es_port], &p->es_ifr, ifm, SIOCSIFMEDIA); return (err); } static int rtl_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct rtl8366rb_softc *sc; uint16_t vmcr[3]; int i; int member, untagged; sc = device_get_softc(dev); for (i=0; ies_vlangroup)); vg->es_vid = sc->vid[vg->es_vlangroup]; member = RTL8366_VMCR_MEMBER(vmcr); untagged = RTL8366_VMCR_UNTAG(vmcr); if (sc->phy4cpu) { vg->es_member_ports = ((member & 0x20) >> 1) | (member & 0x0f); vg->es_untagged_ports = ((untagged & 0x20) >> 1) | (untagged & 0x0f); } else { vg->es_member_ports = member; vg->es_untagged_ports = untagged; } vg->es_fid = RTL8366_VMCR_FID(vmcr); return (0); } static int rtl_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { struct rtl8366rb_softc *sc; int g; int member, untagged; sc = device_get_softc(dev); g = vg->es_vlangroup; sc->vid[g] = vg->es_vid; /* VLAN group disabled ? */ if (vg->es_member_ports == 0 && vg->es_untagged_ports == 0 && vg->es_vid == 0) return (0); sc->vid[g] |= ETHERSWITCH_VID_VALID; rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_DOT1Q_REG, g), (vg->es_vid << RTL8366_VMCR_DOT1Q_VID_SHIFT) & RTL8366_VMCR_DOT1Q_VID_MASK); if (sc->phy4cpu) { /* add space at phy4 */ member = (vg->es_member_ports & 0x0f) | ((vg->es_member_ports & 0x10) << 1); untagged = (vg->es_untagged_ports & 0x0f) | ((vg->es_untagged_ports & 0x10) << 1); } else { member = vg->es_member_ports; untagged = vg->es_untagged_ports; } if (sc->chip_type == RTL8366RB) { rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, g), ((member << RTL8366_VMCR_MU_MEMBER_SHIFT) & RTL8366_VMCR_MU_MEMBER_MASK) | ((untagged << RTL8366_VMCR_MU_UNTAG_SHIFT) & RTL8366_VMCR_MU_UNTAG_MASK)); rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_FID_REG, g), vg->es_fid); } else { rtl_writereg(dev, RTL8366_VMCR(RTL8366_VMCR_MU_REG, g), ((member << RTL8366_VMCR_MU_MEMBER_SHIFT) & RTL8366_VMCR_MU_MEMBER_MASK) | ((untagged << RTL8366_VMCR_MU_UNTAG_SHIFT) & RTL8366_VMCR_MU_UNTAG_MASK) | ((vg->es_fid << RTL8366_VMCR_FID_FID_SHIFT) & RTL8366_VMCR_FID_FID_MASK)); } return (0); } static int rtl_getconf(device_t dev, etherswitch_conf_t *conf) { /* Return the VLAN mode. */ conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; return (0); } static int rtl_readphy(device_t dev, int phy, int reg) { struct rtl8366rb_softc *sc; uint16_t data; int err, i, sleep; sc = device_get_softc(dev); data = 0; if (phy < 0 || phy >= RTL8366_NUM_PHYS) return (ENXIO); if (reg < 0 || reg >= RTL8366_NUM_PHY_REG) return (ENXIO); sleep = RTL_WAITOK; err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); for (i = RTL_IICBUS_RETRIES; i--; ) { err = smi_write_locked(sc, RTL8366_PACR, RTL8366_PACR_READ, sleep); if (err == 0) err = smi_write_locked(sc, RTL8366_PHYREG(phy, 0, reg), 0, sleep); if (err == 0) { err = smi_read_locked(sc, RTL8366_PADR, &data, sleep); break; } DEBUG_INCRVAR(phy_access_retries); DPRINTF(dev, "rtl_readphy(): chip not responsive, retrying %d more times\n", i); pause("rtl_readphy", RTL_IICBUS_RETRY_SLEEP); } smi_release(sc, sleep); DEVERR(dev, err, "rtl_readphy()=%d: phy=%d.%02x\n", phy, reg); return (data); } static int rtl_writephy(device_t dev, int phy, int reg, int data) { struct rtl8366rb_softc *sc; int err, i, sleep; sc = device_get_softc(dev); if (phy < 0 || phy >= RTL8366_NUM_PHYS) return (ENXIO); if (reg < 0 || reg >= RTL8366_NUM_PHY_REG) return (ENXIO); sleep = RTL_WAITOK; err = smi_acquire(sc, sleep); if (err != 0) return (EBUSY); for (i = RTL_IICBUS_RETRIES; i--; ) { err = smi_write_locked(sc, RTL8366_PACR, RTL8366_PACR_WRITE, sleep); if (err == 0) err = smi_write_locked(sc, RTL8366_PHYREG(phy, 0, reg), data, sleep); if (err == 0) { break; } DEBUG_INCRVAR(phy_access_retries); DPRINTF(dev, "rtl_writephy(): chip not responsive, retrying %d more tiems\n", i); pause("rtl_writephy", RTL_IICBUS_RETRY_SLEEP); } smi_release(sc, sleep); DEVERR(dev, err, "rtl_writephy()=%d: phy=%d.%02x\n", phy, reg); return (err == 0 ? 0 : EIO); } static int rtl8366rb_ifmedia_upd(if_t ifp) { struct rtl8366rb_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus[if_getdunit(ifp)]); mii_mediachg(mii); return (0); } static void rtl8366rb_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { struct rtl8366rb_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus[if_getdunit(ifp)]); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static device_method_t rtl8366rb_methods[] = { /* Device interface */ DEVMETHOD(device_identify, rtl8366rb_identify), DEVMETHOD(device_probe, rtl8366rb_probe), DEVMETHOD(device_attach, rtl8366rb_attach), DEVMETHOD(device_detach, rtl8366rb_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* MII interface */ DEVMETHOD(miibus_readreg, rtl_readphy), DEVMETHOD(miibus_writereg, rtl_writephy), /* MDIO interface */ DEVMETHOD(mdio_readreg, rtl_readphy), DEVMETHOD(mdio_writereg, rtl_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_getconf, rtl_getconf), DEVMETHOD(etherswitch_getinfo, rtl_getinfo), DEVMETHOD(etherswitch_readreg, rtl_readreg), DEVMETHOD(etherswitch_writereg, rtl_writereg), DEVMETHOD(etherswitch_readphyreg, rtl_readphy), DEVMETHOD(etherswitch_writephyreg, rtl_writephy), DEVMETHOD(etherswitch_getport, rtl_getport), DEVMETHOD(etherswitch_setport, rtl_setport), DEVMETHOD(etherswitch_getvgroup, rtl_getvgroup), DEVMETHOD(etherswitch_setvgroup, rtl_setvgroup), DEVMETHOD_END }; DEFINE_CLASS_0(rtl8366rb, rtl8366rb_driver, rtl8366rb_methods, sizeof(struct rtl8366rb_softc)); DRIVER_MODULE(rtl8366rb, iicbus, rtl8366rb_driver, 0, 0); DRIVER_MODULE(miibus, rtl8366rb, miibus_driver, 0, 0); DRIVER_MODULE(mdio, rtl8366rb, mdio_driver, 0, 0); DRIVER_MODULE(etherswitch, rtl8366rb, etherswitch_driver, 0, 0); MODULE_VERSION(rtl8366rb, 1); MODULE_DEPEND(rtl8366rb, iicbus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(rtl8366rb, miibus, 1, 1, 1); /* XXX which versions? */ MODULE_DEPEND(rtl8366rb, etherswitch, 1, 1, 1); /* XXX which versions? */ diff --git a/sys/dev/firewire/sbp.c b/sys/dev/firewire/sbp.c index 2a91f6987e69..be1e60e45e75 100644 --- a/sys/dev/firewire/sbp.c +++ b/sys/dev/firewire/sbp.c @@ -1,2849 +1,2849 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 2003 Hidetoshi Shimokawa * Copyright (c) 1998-2002 Katsushi Kobayashi and Hidetoshi Shimokawa * 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, 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. * 3. All advertising materials mentioning features or use of this software * must display the acknowledgement as bellow: * * This product includes software developed by K. Kobayashi and H. Shimokawa * * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ccb_sdev_ptr spriv_ptr0 #define ccb_sbp_ptr spriv_ptr1 #define SBP_NUM_TARGETS 8 /* MAX 64 */ /* * Scan_bus doesn't work for more than 8 LUNs * because of CAM_SCSI2_MAXLUN in cam_xpt.c */ #define SBP_NUM_LUNS 64 #define SBP_MAXPHYS (128 * 1024) #define SBP_DMA_SIZE PAGE_SIZE #define SBP_LOGIN_SIZE sizeof(struct sbp_login_res) #define SBP_QUEUE_LEN ((SBP_DMA_SIZE - SBP_LOGIN_SIZE) / sizeof(struct sbp_ocb)) #define SBP_NUM_OCB (SBP_QUEUE_LEN * SBP_NUM_TARGETS) /* * STATUS FIFO addressing * bit *----------------------- * 0- 1( 2): 0 (alignment) * 2- 7( 6): target * 8-15( 8): lun * 16-31( 8): reserved * 32-47(16): SBP_BIND_HI * 48-64(16): bus_id, node_id */ #define SBP_BIND_HI 0x1 #define SBP_DEV2ADDR(t, l) \ (((u_int64_t)SBP_BIND_HI << 32) \ | (((l) & 0xff) << 8) \ | (((t) & 0x3f) << 2)) #define SBP_ADDR2TRG(a) (((a) >> 2) & 0x3f) #define SBP_ADDR2LUN(a) (((a) >> 8) & 0xff) #define SBP_INITIATOR 7 static char *orb_fun_name[] = { ORB_FUN_NAMES }; static int debug = 0; static int auto_login = 1; static int max_speed = -1; static int sbp_cold = 1; static int ex_login = 1; static int login_delay = 1000; /* msec */ static int scan_delay = 500; /* msec */ static int use_doorbell = 0; static int sbp_tags = 0; SYSCTL_DECL(_hw_firewire); static SYSCTL_NODE(_hw_firewire, OID_AUTO, sbp, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "SBP-II Subsystem"); SYSCTL_INT(_debug, OID_AUTO, sbp_debug, CTLFLAG_RWTUN, &debug, 0, "SBP debug flag"); SYSCTL_INT(_hw_firewire_sbp, OID_AUTO, auto_login, CTLFLAG_RWTUN, &auto_login, 0, "SBP perform login automatically"); SYSCTL_INT(_hw_firewire_sbp, OID_AUTO, max_speed, CTLFLAG_RWTUN, &max_speed, 0, "SBP transfer max speed"); SYSCTL_INT(_hw_firewire_sbp, OID_AUTO, exclusive_login, CTLFLAG_RWTUN, &ex_login, 0, "SBP enable exclusive login"); SYSCTL_INT(_hw_firewire_sbp, OID_AUTO, login_delay, CTLFLAG_RWTUN, &login_delay, 0, "SBP login delay in msec"); SYSCTL_INT(_hw_firewire_sbp, OID_AUTO, scan_delay, CTLFLAG_RWTUN, &scan_delay, 0, "SBP scan delay in msec"); SYSCTL_INT(_hw_firewire_sbp, OID_AUTO, use_doorbell, CTLFLAG_RWTUN, &use_doorbell, 0, "SBP use doorbell request"); SYSCTL_INT(_hw_firewire_sbp, OID_AUTO, tags, CTLFLAG_RWTUN, &sbp_tags, 0, "SBP tagged queuing support"); #define NEED_RESPONSE 0 #define SBP_SEG_MAX rounddown(0xffff, PAGE_SIZE) #define SBP_IND_MAX howmany(SBP_MAXPHYS, PAGE_SIZE) struct sbp_ocb { STAILQ_ENTRY(sbp_ocb) ocb; union ccb *ccb; bus_addr_t bus_addr; uint32_t orb[8]; #define IND_PTR_OFFSET (8*sizeof(uint32_t)) struct ind_ptr ind_ptr[SBP_IND_MAX]; struct sbp_dev *sdev; int flags; /* XXX should be removed */ bus_dmamap_t dmamap; struct callout timer; }; #define OCB_ACT_MGM 0 #define OCB_ACT_CMD 1 #define OCB_MATCH(o,s) ((o)->bus_addr == ntohl((s)->orb_lo)) struct sbp_dev { #define SBP_DEV_RESET 0 /* accept login */ #define SBP_DEV_LOGIN 1 /* to login */ #if 0 #define SBP_DEV_RECONN 2 /* to reconnect */ #endif #define SBP_DEV_TOATTACH 3 /* to attach */ #define SBP_DEV_PROBE 4 /* scan lun */ #define SBP_DEV_ATTACHED 5 /* in operation */ #define SBP_DEV_DEAD 6 /* unavailable unit */ #define SBP_DEV_RETRY 7 /* unavailable unit */ uint8_t status:4, timeout:4; uint8_t type; uint16_t lun_id; uint16_t freeze; #define ORB_LINK_DEAD (1 << 0) #define VALID_LUN (1 << 1) #define ORB_POINTER_ACTIVE (1 << 2) #define ORB_POINTER_NEED (1 << 3) #define ORB_DOORBELL_ACTIVE (1 << 4) #define ORB_DOORBELL_NEED (1 << 5) #define ORB_SHORTAGE (1 << 6) uint16_t flags; struct cam_path *path; struct sbp_target *target; struct fwdma_alloc dma; struct sbp_login_res *login; struct callout login_callout; struct sbp_ocb *ocb; STAILQ_HEAD(, sbp_ocb) ocbs; STAILQ_HEAD(, sbp_ocb) free_ocbs; struct sbp_ocb *last_ocb; char vendor[32]; char product[32]; char revision[10]; char bustgtlun[32]; }; struct sbp_target { int target_id; int num_lun; struct sbp_dev **luns; struct sbp_softc *sbp; struct fw_device *fwdev; uint32_t mgm_hi, mgm_lo; struct sbp_ocb *mgm_ocb_cur; STAILQ_HEAD(, sbp_ocb) mgm_ocb_queue; struct callout mgm_ocb_timeout; struct callout scan_callout; STAILQ_HEAD(, fw_xfer) xferlist; int n_xfer; }; struct sbp_softc { struct firewire_dev_comm fd; struct cam_sim *sim; struct cam_path *path; struct sbp_target targets[SBP_NUM_TARGETS]; struct fw_bind fwb; bus_dma_tag_t dmat; struct timeval last_busreset; #define SIMQ_FREEZED 1 int flags; struct mtx mtx; }; #define SBP_LOCK(sbp) mtx_lock(&(sbp)->mtx) #define SBP_UNLOCK(sbp) mtx_unlock(&(sbp)->mtx) #define SBP_LOCK_ASSERT(sbp) mtx_assert(&(sbp)->mtx, MA_OWNED) static void sbp_post_explore (void *); static void sbp_recv (struct fw_xfer *); static void sbp_mgm_callback (struct fw_xfer *); #if 0 static void sbp_cmd_callback (struct fw_xfer *); #endif static void sbp_orb_pointer (struct sbp_dev *, struct sbp_ocb *); static void sbp_doorbell(struct sbp_dev *); static void sbp_execute_ocb (void *, bus_dma_segment_t *, int, int); static void sbp_free_ocb (struct sbp_dev *, struct sbp_ocb *); static void sbp_abort_ocb (struct sbp_ocb *, int); static void sbp_abort_all_ocbs (struct sbp_dev *, int); static struct fw_xfer * sbp_write_cmd (struct sbp_dev *, int, int); static struct sbp_ocb * sbp_get_ocb (struct sbp_dev *); static struct sbp_ocb * sbp_enqueue_ocb (struct sbp_dev *, struct sbp_ocb *); static struct sbp_ocb * sbp_dequeue_ocb (struct sbp_dev *, struct sbp_status *); static void sbp_cam_detach_sdev(struct sbp_dev *); static void sbp_free_sdev(struct sbp_dev *); static void sbp_cam_detach_target (struct sbp_target *); static void sbp_free_target (struct sbp_target *); static void sbp_mgm_timeout (void *arg); static void sbp_timeout (void *arg); static void sbp_mgm_orb (struct sbp_dev *, int, struct sbp_ocb *); static MALLOC_DEFINE(M_SBP, "sbp", "SBP-II/FireWire"); /* cam related functions */ static void sbp_action(struct cam_sim *sim, union ccb *ccb); static void sbp_poll(struct cam_sim *sim); static void sbp_cam_scan_lun(struct cam_periph *, union ccb *); static void sbp_cam_scan_target(void *arg); static char *orb_status0[] = { /* 0 */ "No additional information to report", /* 1 */ "Request type not supported", /* 2 */ "Speed not supported", /* 3 */ "Page size not supported", /* 4 */ "Access denied", /* 5 */ "Logical unit not supported", /* 6 */ "Maximum payload too small", /* 7 */ "Reserved for future standardization", /* 8 */ "Resources unavailable", /* 9 */ "Function rejected", /* A */ "Login ID not recognized", /* B */ "Dummy ORB completed", /* C */ "Request aborted", /* FF */ "Unspecified error" #define MAX_ORB_STATUS0 0xd }; static char *orb_status1_object[] = { /* 0 */ "Operation request block (ORB)", /* 1 */ "Data buffer", /* 2 */ "Page table", /* 3 */ "Unable to specify" }; static char *orb_status1_serial_bus_error[] = { /* 0 */ "Missing acknowledge", /* 1 */ "Reserved; not to be used", /* 2 */ "Time-out error", /* 3 */ "Reserved; not to be used", /* 4 */ "Busy retry limit exceeded(X)", /* 5 */ "Busy retry limit exceeded(A)", /* 6 */ "Busy retry limit exceeded(B)", /* 7 */ "Reserved for future standardization", /* 8 */ "Reserved for future standardization", /* 9 */ "Reserved for future standardization", /* A */ "Reserved for future standardization", /* B */ "Tardy retry limit exceeded", /* C */ "Conflict error", /* D */ "Data error", /* E */ "Type error", /* F */ "Address error" }; static void sbp_identify(driver_t *driver, device_t parent) { SBP_DEBUG(0) printf("sbp_identify\n"); END_DEBUG - if (device_find_child(parent, "sbp", -1) == NULL) + if (device_find_child(parent, "sbp", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "sbp", DEVICE_UNIT_ANY); } /* * sbp_probe() */ static int sbp_probe(device_t dev) { SBP_DEBUG(0) printf("sbp_probe\n"); END_DEBUG device_set_desc(dev, "SBP-2/SCSI over FireWire"); #if 0 if (bootverbose) debug = bootverbose; #endif return (0); } /* * Display device characteristics on the console */ static void sbp_show_sdev_info(struct sbp_dev *sdev) { struct fw_device *fwdev; fwdev = sdev->target->fwdev; device_printf(sdev->target->sbp->fd.dev, "%s: %s: ordered:%d type:%d EUI:%08x%08x node:%d " "speed:%d maxrec:%d\n", __func__, sdev->bustgtlun, (sdev->type & 0x40) >> 6, (sdev->type & 0x1f), fwdev->eui.hi, fwdev->eui.lo, fwdev->dst, fwdev->speed, fwdev->maxrec); device_printf(sdev->target->sbp->fd.dev, "%s: %s '%s' '%s' '%s'\n", __func__, sdev->bustgtlun, sdev->vendor, sdev->product, sdev->revision); } static struct { int bus; int target; struct fw_eui64 eui; } wired[] = { /* Bus Target EUI64 */ #if 0 {0, 2, {0x00018ea0, 0x01fd0154}}, /* Logitec HDD */ {0, 0, {0x00018ea6, 0x00100682}}, /* Logitec DVD */ {0, 1, {0x00d03200, 0xa412006a}}, /* Yano HDD */ #endif {-1, -1, {0,0}} }; static int sbp_new_target(struct sbp_softc *sbp, struct fw_device *fwdev) { int bus, i, target=-1; char w[SBP_NUM_TARGETS]; bzero(w, sizeof(w)); bus = device_get_unit(sbp->fd.dev); /* XXX wired-down configuration should be gotten from tunable or device hint */ for (i = 0; wired[i].bus >= 0; i++) { if (wired[i].bus == bus) { w[wired[i].target] = 1; if (wired[i].eui.hi == fwdev->eui.hi && wired[i].eui.lo == fwdev->eui.lo) target = wired[i].target; } } if (target >= 0) { if (target < SBP_NUM_TARGETS && sbp->targets[target].fwdev == NULL) return (target); device_printf(sbp->fd.dev, "target %d is not free for %08x:%08x\n", target, fwdev->eui.hi, fwdev->eui.lo); target = -1; } /* non-wired target */ for (i = 0; i < SBP_NUM_TARGETS; i++) if (sbp->targets[i].fwdev == NULL && w[i] == 0) { target = i; break; } return target; } static void sbp_alloc_lun(struct sbp_target *target) { struct crom_context cc; struct csrreg *reg; struct sbp_dev *sdev, **newluns; struct sbp_softc *sbp; int maxlun, lun, i; sbp = target->sbp; crom_init_context(&cc, target->fwdev->csrrom); /* XXX shoud parse appropriate unit directories only */ maxlun = -1; while (cc.depth >= 0) { reg = crom_search_key(&cc, CROM_LUN); if (reg == NULL) break; lun = reg->val & 0xffff; SBP_DEBUG(0) printf("target %d lun %d found\n", target->target_id, lun); END_DEBUG if (maxlun < lun) maxlun = lun; crom_next(&cc); } if (maxlun < 0) device_printf(target->sbp->fd.dev, "%d no LUN found\n", target->target_id); maxlun++; if (maxlun >= SBP_NUM_LUNS) maxlun = SBP_NUM_LUNS; /* Invalidiate stale devices */ for (lun = 0; lun < target->num_lun; lun++) { sdev = target->luns[lun]; if (sdev == NULL) continue; sdev->flags &= ~VALID_LUN; if (lun >= maxlun) { /* lost device */ sbp_cam_detach_sdev(sdev); sbp_free_sdev(sdev); target->luns[lun] = NULL; } } /* Reallocate */ if (maxlun != target->num_lun) { newluns = (struct sbp_dev **) realloc(target->luns, sizeof(struct sbp_dev *) * maxlun, M_SBP, M_NOWAIT | M_ZERO); if (newluns == NULL) { printf("%s: realloc failed\n", __func__); newluns = target->luns; maxlun = target->num_lun; } /* * We must zero the extended region for the case * realloc() doesn't allocate new buffer. */ if (maxlun > target->num_lun) bzero(&newluns[target->num_lun], sizeof(struct sbp_dev *) * (maxlun - target->num_lun)); target->luns = newluns; target->num_lun = maxlun; } crom_init_context(&cc, target->fwdev->csrrom); while (cc.depth >= 0) { int new = 0; reg = crom_search_key(&cc, CROM_LUN); if (reg == NULL) break; lun = reg->val & 0xffff; if (lun >= SBP_NUM_LUNS) { printf("too large lun %d\n", lun); goto next; } sdev = target->luns[lun]; if (sdev == NULL) { sdev = malloc(sizeof(struct sbp_dev), M_SBP, M_NOWAIT | M_ZERO); if (sdev == NULL) { printf("%s: malloc failed\n", __func__); goto next; } target->luns[lun] = sdev; sdev->lun_id = lun; sdev->target = target; STAILQ_INIT(&sdev->ocbs); callout_init_mtx(&sdev->login_callout, &sbp->mtx, 0); sdev->status = SBP_DEV_RESET; new = 1; snprintf(sdev->bustgtlun, 32, "%s:%d:%d", device_get_nameunit(sdev->target->sbp->fd.dev), sdev->target->target_id, sdev->lun_id); } sdev->flags |= VALID_LUN; sdev->type = (reg->val & 0xff0000) >> 16; if (new == 0) goto next; fwdma_malloc(sbp->fd.fc, /* alignment */ sizeof(uint32_t), SBP_DMA_SIZE, &sdev->dma, BUS_DMA_NOWAIT | BUS_DMA_COHERENT); if (sdev->dma.v_addr == NULL) { printf("%s: dma space allocation failed\n", __func__); free(sdev, M_SBP); target->luns[lun] = NULL; goto next; } sdev->login = (struct sbp_login_res *) sdev->dma.v_addr; sdev->ocb = (struct sbp_ocb *) ((char *)sdev->dma.v_addr + SBP_LOGIN_SIZE); bzero((char *)sdev->ocb, sizeof(struct sbp_ocb) * SBP_QUEUE_LEN); STAILQ_INIT(&sdev->free_ocbs); for (i = 0; i < SBP_QUEUE_LEN; i++) { struct sbp_ocb *ocb; ocb = &sdev->ocb[i]; ocb->bus_addr = sdev->dma.bus_addr + SBP_LOGIN_SIZE + sizeof(struct sbp_ocb) * i + offsetof(struct sbp_ocb, orb[0]); if (bus_dmamap_create(sbp->dmat, 0, &ocb->dmamap)) { printf("sbp_attach: cannot create dmamap\n"); /* XXX */ goto next; } callout_init_mtx(&ocb->timer, &sbp->mtx, 0); SBP_LOCK(sbp); sbp_free_ocb(sdev, ocb); SBP_UNLOCK(sbp); } next: crom_next(&cc); } for (lun = 0; lun < target->num_lun; lun++) { sdev = target->luns[lun]; if (sdev != NULL && (sdev->flags & VALID_LUN) == 0) { sbp_cam_detach_sdev(sdev); sbp_free_sdev(sdev); target->luns[lun] = NULL; } } } static struct sbp_target * sbp_alloc_target(struct sbp_softc *sbp, struct fw_device *fwdev) { int i; struct sbp_target *target; struct crom_context cc; struct csrreg *reg; SBP_DEBUG(1) printf("sbp_alloc_target\n"); END_DEBUG i = sbp_new_target(sbp, fwdev); if (i < 0) { device_printf(sbp->fd.dev, "increase SBP_NUM_TARGETS!\n"); return NULL; } /* new target */ target = &sbp->targets[i]; target->fwdev = fwdev; target->target_id = i; /* XXX we may want to reload mgm port after each bus reset */ /* XXX there might be multiple management agents */ crom_init_context(&cc, target->fwdev->csrrom); reg = crom_search_key(&cc, CROM_MGM); if (reg == NULL || reg->val == 0) { printf("NULL management address\n"); target->fwdev = NULL; return NULL; } target->mgm_hi = 0xffff; target->mgm_lo = 0xf0000000 | (reg->val << 2); target->mgm_ocb_cur = NULL; SBP_DEBUG(1) printf("target:%d mgm_port: %x\n", i, target->mgm_lo); END_DEBUG STAILQ_INIT(&target->xferlist); target->n_xfer = 0; STAILQ_INIT(&target->mgm_ocb_queue); callout_init_mtx(&target->mgm_ocb_timeout, &sbp->mtx, 0); callout_init_mtx(&target->scan_callout, &sbp->mtx, 0); target->luns = NULL; target->num_lun = 0; return target; } static void sbp_probe_lun(struct sbp_dev *sdev) { struct fw_device *fwdev; struct crom_context c, *cc = &c; struct csrreg *reg; bzero(sdev->vendor, sizeof(sdev->vendor)); bzero(sdev->product, sizeof(sdev->product)); fwdev = sdev->target->fwdev; crom_init_context(cc, fwdev->csrrom); /* get vendor string */ crom_search_key(cc, CSRKEY_VENDOR); crom_next(cc); crom_parse_text(cc, sdev->vendor, sizeof(sdev->vendor)); /* skip to the unit directory for SBP-2 */ while ((reg = crom_search_key(cc, CSRKEY_VER)) != NULL) { if (reg->val == CSRVAL_T10SBP2) break; crom_next(cc); } /* get firmware revision */ reg = crom_search_key(cc, CSRKEY_FIRM_VER); if (reg != NULL) snprintf(sdev->revision, sizeof(sdev->revision), "%06x", reg->val); /* get product string */ crom_search_key(cc, CSRKEY_MODEL); crom_next(cc); crom_parse_text(cc, sdev->product, sizeof(sdev->product)); } static void sbp_login_callout(void *arg) { struct sbp_dev *sdev = (struct sbp_dev *)arg; SBP_LOCK_ASSERT(sdev->target->sbp); sbp_mgm_orb(sdev, ORB_FUN_LGI, NULL); } static void sbp_login(struct sbp_dev *sdev) { struct timeval delta; struct timeval t; int ticks = 0; microtime(&delta); timevalsub(&delta, &sdev->target->sbp->last_busreset); t.tv_sec = login_delay / 1000; t.tv_usec = (login_delay % 1000) * 1000; timevalsub(&t, &delta); if (t.tv_sec >= 0 && t.tv_usec > 0) ticks = (t.tv_sec * 1000 + t.tv_usec / 1000) * hz / 1000; SBP_DEBUG(0) printf("%s: sec = %jd usec = %ld ticks = %d\n", __func__, (intmax_t)t.tv_sec, t.tv_usec, ticks); END_DEBUG callout_reset(&sdev->login_callout, ticks, sbp_login_callout, (void *)(sdev)); } #define SBP_FWDEV_ALIVE(fwdev) (((fwdev)->status == FWDEVATTACHED) \ && crom_has_specver((fwdev)->csrrom, CSRVAL_ANSIT10, CSRVAL_T10SBP2)) static void sbp_probe_target(struct sbp_target *target) { struct sbp_softc *sbp = target->sbp; struct sbp_dev *sdev; int i, alive; alive = SBP_FWDEV_ALIVE(target->fwdev); SBP_DEBUG(1) device_printf(sbp->fd.dev, "%s %d%salive\n", __func__, target->target_id, (!alive) ? " not " : ""); END_DEBUG sbp_alloc_lun(target); /* XXX untimeout mgm_ocb and dequeue */ for (i=0; i < target->num_lun; i++) { sdev = target->luns[i]; if (sdev == NULL) continue; if (alive && (sdev->status != SBP_DEV_DEAD)) { if (sdev->path != NULL) { xpt_freeze_devq(sdev->path, 1); sdev->freeze++; } sbp_probe_lun(sdev); sbp_show_sdev_info(sdev); SBP_LOCK(sbp); sbp_abort_all_ocbs(sdev, CAM_SCSI_BUS_RESET); SBP_UNLOCK(sbp); switch (sdev->status) { case SBP_DEV_RESET: /* new or revived target */ if (auto_login) sbp_login(sdev); break; case SBP_DEV_TOATTACH: case SBP_DEV_PROBE: case SBP_DEV_ATTACHED: case SBP_DEV_RETRY: default: sbp_mgm_orb(sdev, ORB_FUN_RCN, NULL); break; } } else { switch (sdev->status) { case SBP_DEV_ATTACHED: SBP_DEBUG(0) /* the device has gone */ device_printf(sbp->fd.dev, "%s: lost target\n", __func__); END_DEBUG if (sdev->path) { xpt_freeze_devq(sdev->path, 1); sdev->freeze++; } sdev->status = SBP_DEV_RETRY; sbp_cam_detach_sdev(sdev); sbp_free_sdev(sdev); target->luns[i] = NULL; break; case SBP_DEV_PROBE: case SBP_DEV_TOATTACH: sdev->status = SBP_DEV_RESET; break; case SBP_DEV_RETRY: case SBP_DEV_RESET: case SBP_DEV_DEAD: break; } } } } static void sbp_post_busreset(void *arg) { struct sbp_softc *sbp; sbp = (struct sbp_softc *)arg; SBP_DEBUG(0) printf("sbp_post_busreset\n"); END_DEBUG SBP_LOCK(sbp); if ((sbp->flags & SIMQ_FREEZED) == 0) { xpt_freeze_simq(sbp->sim, /*count*/1); sbp->flags |= SIMQ_FREEZED; } microtime(&sbp->last_busreset); SBP_UNLOCK(sbp); } static void sbp_post_explore(void *arg) { struct sbp_softc *sbp = (struct sbp_softc *)arg; struct sbp_target *target; struct fw_device *fwdev; int i, alive; SBP_DEBUG(0) printf("sbp_post_explore (sbp_cold=%d)\n", sbp_cold); END_DEBUG /* We need physical access */ if (!firewire_phydma_enable) return; if (sbp_cold > 0) sbp_cold--; SBP_LOCK(sbp); /* Garbage Collection */ for (i = 0; i < SBP_NUM_TARGETS; i++) { target = &sbp->targets[i]; if (target->fwdev == NULL) continue; STAILQ_FOREACH(fwdev, &sbp->fd.fc->devices, link) if (target->fwdev == fwdev) break; if (fwdev == NULL) { /* device has removed in lower driver */ sbp_cam_detach_target(target); sbp_free_target(target); } } /* traverse device list */ STAILQ_FOREACH(fwdev, &sbp->fd.fc->devices, link) { SBP_DEBUG(0) device_printf(sbp->fd.dev,"%s:: EUI:%08x%08x %s attached, state=%d\n", __func__, fwdev->eui.hi, fwdev->eui.lo, (fwdev->status != FWDEVATTACHED) ? "not" : "", fwdev->status); END_DEBUG alive = SBP_FWDEV_ALIVE(fwdev); for (i = 0; i < SBP_NUM_TARGETS; i++) { target = &sbp->targets[i]; if (target->fwdev == fwdev) { /* known target */ break; } } if (i == SBP_NUM_TARGETS) { if (alive) { /* new target */ target = sbp_alloc_target(sbp, fwdev); if (target == NULL) continue; } else { continue; } } /* * It is safe to drop the lock here as the target is already * reserved, so there should be no contenders for it. * And the target is not yet exposed, so there should not be * any other accesses to it. * Finally, the list being iterated is protected somewhere else. */ SBP_UNLOCK(sbp); sbp_probe_target(target); SBP_LOCK(sbp); if (target->num_lun == 0) sbp_free_target(target); } if ((sbp->flags & SIMQ_FREEZED) != 0) { xpt_release_simq(sbp->sim, /*run queue*/TRUE); sbp->flags &= ~SIMQ_FREEZED; } SBP_UNLOCK(sbp); } #if NEED_RESPONSE static void sbp_loginres_callback(struct fw_xfer *xfer) { struct sbp_dev *sdev; sdev = (struct sbp_dev *)xfer->sc; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev,"%s\n", __func__); END_DEBUG /* recycle */ SBP_LOCK(sdev->target->sbp); STAILQ_INSERT_TAIL(&sdev->target->sbp->fwb.xferlist, xfer, link); SBP_UNLOCK(sdev->target->sbp); return; } #endif static __inline void sbp_xfer_free(struct fw_xfer *xfer) { struct sbp_dev *sdev; sdev = (struct sbp_dev *)xfer->sc; fw_xfer_unload(xfer); SBP_LOCK_ASSERT(sdev->target->sbp); STAILQ_INSERT_TAIL(&sdev->target->xferlist, xfer, link); } static void sbp_reset_start_callback(struct fw_xfer *xfer) { struct sbp_dev *tsdev, *sdev = (struct sbp_dev *)xfer->sc; struct sbp_target *target = sdev->target; int i; if (xfer->resp != 0) { device_printf(sdev->target->sbp->fd.dev, "%s: %s failed: resp=%d\n", __func__, sdev->bustgtlun, xfer->resp); } SBP_LOCK(target->sbp); for (i = 0; i < target->num_lun; i++) { tsdev = target->luns[i]; if (tsdev != NULL && tsdev->status == SBP_DEV_LOGIN) sbp_login(tsdev); } SBP_UNLOCK(target->sbp); } static void sbp_reset_start(struct sbp_dev *sdev) { struct fw_xfer *xfer; struct fw_pkt *fp; SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__,sdev->bustgtlun); END_DEBUG xfer = sbp_write_cmd(sdev, FWTCODE_WREQQ, 0); xfer->hand = sbp_reset_start_callback; fp = &xfer->send.hdr; fp->mode.wreqq.dest_hi = 0xffff; fp->mode.wreqq.dest_lo = 0xf0000000 | RESET_START; fp->mode.wreqq.data = htonl(0xf); fw_asyreq(xfer->fc, -1, xfer); } static void sbp_mgm_callback(struct fw_xfer *xfer) { struct sbp_dev *sdev; sdev = (struct sbp_dev *)xfer->sc; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG SBP_LOCK(sdev->target->sbp); sbp_xfer_free(xfer); SBP_UNLOCK(sdev->target->sbp); } static struct sbp_dev * sbp_next_dev(struct sbp_target *target, int lun) { struct sbp_dev **sdevp; int i; for (i = lun, sdevp = &target->luns[lun]; i < target->num_lun; i++, sdevp++) if (*sdevp != NULL && (*sdevp)->status == SBP_DEV_PROBE) return (*sdevp); return (NULL); } #define SCAN_PRI 1 static void sbp_cam_scan_lun(struct cam_periph *periph, union ccb *ccb) { struct sbp_softc *sbp; struct sbp_target *target; struct sbp_dev *sdev; sdev = (struct sbp_dev *) ccb->ccb_h.ccb_sdev_ptr; target = sdev->target; sbp = target->sbp; SBP_LOCK(sbp); SBP_DEBUG(0) device_printf(sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { sdev->status = SBP_DEV_ATTACHED; } else { device_printf(sbp->fd.dev, "%s:%s failed\n", __func__, sdev->bustgtlun); } sdev = sbp_next_dev(target, sdev->lun_id + 1); if (sdev == NULL) { SBP_UNLOCK(sbp); xpt_free_ccb(ccb); return; } /* reuse ccb */ xpt_setup_ccb(&ccb->ccb_h, sdev->path, SCAN_PRI); ccb->ccb_h.ccb_sdev_ptr = sdev; ccb->ccb_h.flags |= CAM_DEV_QFREEZE; SBP_UNLOCK(sbp); xpt_action(ccb); xpt_release_devq(sdev->path, sdev->freeze, TRUE); sdev->freeze = 1; } static void sbp_cam_scan_target(void *arg) { struct sbp_target *target = (struct sbp_target *)arg; struct sbp_dev *sdev; union ccb *ccb; SBP_LOCK_ASSERT(target->sbp); sdev = sbp_next_dev(target, 0); if (sdev == NULL) { printf("sbp_cam_scan_target: nothing to do for target%d\n", target->target_id); return; } SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { printf("sbp_cam_scan_target: xpt_alloc_ccb_nowait() failed\n"); return; } SBP_UNLOCK(target->sbp); xpt_setup_ccb(&ccb->ccb_h, sdev->path, SCAN_PRI); ccb->ccb_h.func_code = XPT_SCAN_LUN; ccb->ccb_h.cbfcnp = sbp_cam_scan_lun; ccb->ccb_h.flags |= CAM_DEV_QFREEZE; ccb->crcn.flags = CAM_FLAG_NONE; ccb->ccb_h.ccb_sdev_ptr = sdev; /* The scan is in progress now. */ xpt_action(ccb); SBP_LOCK(target->sbp); xpt_release_devq(sdev->path, sdev->freeze, TRUE); sdev->freeze = 1; } static __inline void sbp_scan_dev(struct sbp_dev *sdev) { sdev->status = SBP_DEV_PROBE; callout_reset_sbt(&sdev->target->scan_callout, SBT_1MS * scan_delay, 0, sbp_cam_scan_target, (void *)sdev->target, 0); } static void sbp_do_attach(struct fw_xfer *xfer) { struct sbp_dev *sdev; struct sbp_target *target; struct sbp_softc *sbp; sdev = (struct sbp_dev *)xfer->sc; target = sdev->target; sbp = target->sbp; SBP_LOCK(sbp); SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG sbp_xfer_free(xfer); if (sdev->path == NULL) xpt_create_path(&sdev->path, NULL, cam_sim_path(target->sbp->sim), target->target_id, sdev->lun_id); /* * Let CAM scan the bus if we are in the boot process. * XXX xpt_scan_bus cannot detect LUN larger than 0 * if LUN 0 doesn't exist. */ if (sbp_cold > 0) { sdev->status = SBP_DEV_ATTACHED; SBP_UNLOCK(sbp); return; } sbp_scan_dev(sdev); SBP_UNLOCK(sbp); } static void sbp_agent_reset_callback(struct fw_xfer *xfer) { struct sbp_dev *sdev; sdev = (struct sbp_dev *)xfer->sc; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG if (xfer->resp != 0) { device_printf(sdev->target->sbp->fd.dev, "%s:%s resp=%d\n", __func__, sdev->bustgtlun, xfer->resp); } SBP_LOCK(sdev->target->sbp); sbp_xfer_free(xfer); if (sdev->path) { xpt_release_devq(sdev->path, sdev->freeze, TRUE); sdev->freeze = 0; } SBP_UNLOCK(sdev->target->sbp); } static void sbp_agent_reset(struct sbp_dev *sdev) { struct fw_xfer *xfer; struct fw_pkt *fp; SBP_LOCK_ASSERT(sdev->target->sbp); SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG xfer = sbp_write_cmd(sdev, FWTCODE_WREQQ, 0x04); if (xfer == NULL) return; if (sdev->status == SBP_DEV_ATTACHED || sdev->status == SBP_DEV_PROBE) xfer->hand = sbp_agent_reset_callback; else xfer->hand = sbp_do_attach; fp = &xfer->send.hdr; fp->mode.wreqq.data = htonl(0xf); fw_asyreq(xfer->fc, -1, xfer); sbp_abort_all_ocbs(sdev, CAM_BDR_SENT); } static void sbp_busy_timeout_callback(struct fw_xfer *xfer) { struct sbp_dev *sdev; sdev = (struct sbp_dev *)xfer->sc; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG SBP_LOCK(sdev->target->sbp); sbp_xfer_free(xfer); sbp_agent_reset(sdev); SBP_UNLOCK(sdev->target->sbp); } static void sbp_busy_timeout(struct sbp_dev *sdev) { struct fw_pkt *fp; struct fw_xfer *xfer; SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG xfer = sbp_write_cmd(sdev, FWTCODE_WREQQ, 0); xfer->hand = sbp_busy_timeout_callback; fp = &xfer->send.hdr; fp->mode.wreqq.dest_hi = 0xffff; fp->mode.wreqq.dest_lo = 0xf0000000 | BUSY_TIMEOUT; fp->mode.wreqq.data = htonl((1 << (13 + 12)) | 0xf); fw_asyreq(xfer->fc, -1, xfer); } static void sbp_orb_pointer_callback(struct fw_xfer *xfer) { struct sbp_dev *sdev; sdev = (struct sbp_dev *)xfer->sc; SBP_DEBUG(2) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG if (xfer->resp != 0) { /* XXX */ printf("%s: xfer->resp = %d\n", __func__, xfer->resp); } SBP_LOCK(sdev->target->sbp); sbp_xfer_free(xfer); sdev->flags &= ~ORB_POINTER_ACTIVE; if ((sdev->flags & ORB_POINTER_NEED) != 0) { struct sbp_ocb *ocb; sdev->flags &= ~ORB_POINTER_NEED; ocb = STAILQ_FIRST(&sdev->ocbs); if (ocb != NULL) sbp_orb_pointer(sdev, ocb); } SBP_UNLOCK(sdev->target->sbp); return; } static void sbp_orb_pointer(struct sbp_dev *sdev, struct sbp_ocb *ocb) { struct fw_xfer *xfer; struct fw_pkt *fp; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s 0x%08x\n", __func__, sdev->bustgtlun, (uint32_t)ocb->bus_addr); END_DEBUG SBP_LOCK_ASSERT(sdev->target->sbp); if ((sdev->flags & ORB_POINTER_ACTIVE) != 0) { SBP_DEBUG(0) printf("%s: orb pointer active\n", __func__); END_DEBUG sdev->flags |= ORB_POINTER_NEED; return; } sdev->flags |= ORB_POINTER_ACTIVE; xfer = sbp_write_cmd(sdev, FWTCODE_WREQB, 0x08); if (xfer == NULL) return; xfer->hand = sbp_orb_pointer_callback; fp = &xfer->send.hdr; fp->mode.wreqb.len = 8; fp->mode.wreqb.extcode = 0; xfer->send.payload[0] = htonl(((sdev->target->sbp->fd.fc->nodeid | FWLOCALBUS) << 16)); xfer->send.payload[1] = htonl((uint32_t)ocb->bus_addr); if (fw_asyreq(xfer->fc, -1, xfer) != 0) { sbp_xfer_free(xfer); ocb->ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ocb->ccb); } } static void sbp_doorbell_callback(struct fw_xfer *xfer) { struct sbp_dev *sdev; sdev = (struct sbp_dev *)xfer->sc; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG if (xfer->resp != 0) { /* XXX */ device_printf(sdev->target->sbp->fd.dev, "%s: xfer->resp = %d\n", __func__, xfer->resp); } SBP_LOCK(sdev->target->sbp); sbp_xfer_free(xfer); sdev->flags &= ~ORB_DOORBELL_ACTIVE; if ((sdev->flags & ORB_DOORBELL_NEED) != 0) { sdev->flags &= ~ORB_DOORBELL_NEED; sbp_doorbell(sdev); } SBP_UNLOCK(sdev->target->sbp); } static void sbp_doorbell(struct sbp_dev *sdev) { struct fw_xfer *xfer; struct fw_pkt *fp; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG if ((sdev->flags & ORB_DOORBELL_ACTIVE) != 0) { sdev->flags |= ORB_DOORBELL_NEED; return; } sdev->flags |= ORB_DOORBELL_ACTIVE; xfer = sbp_write_cmd(sdev, FWTCODE_WREQQ, 0x10); if (xfer == NULL) return; xfer->hand = sbp_doorbell_callback; fp = &xfer->send.hdr; fp->mode.wreqq.data = htonl(0xf); fw_asyreq(xfer->fc, -1, xfer); } static struct fw_xfer * sbp_write_cmd(struct sbp_dev *sdev, int tcode, int offset) { struct fw_xfer *xfer; struct fw_pkt *fp; struct sbp_target *target; int new = 0; SBP_LOCK_ASSERT(sdev->target->sbp); target = sdev->target; xfer = STAILQ_FIRST(&target->xferlist); if (xfer == NULL) { if (target->n_xfer > 5 /* XXX */) { printf("sbp: no more xfer for this target\n"); return (NULL); } xfer = fw_xfer_alloc_buf(M_SBP, 8, 0); if (xfer == NULL) { printf("sbp: fw_xfer_alloc_buf failed\n"); return NULL; } target->n_xfer++; if (debug) printf("sbp: alloc %d xfer\n", target->n_xfer); new = 1; } else { STAILQ_REMOVE_HEAD(&target->xferlist, link); } if (new) { xfer->recv.pay_len = 0; xfer->send.spd = min(sdev->target->fwdev->speed, max_speed); xfer->fc = sdev->target->sbp->fd.fc; } if (tcode == FWTCODE_WREQB) xfer->send.pay_len = 8; else xfer->send.pay_len = 0; xfer->sc = (caddr_t)sdev; fp = &xfer->send.hdr; fp->mode.wreqq.dest_hi = sdev->login->cmd_hi; fp->mode.wreqq.dest_lo = sdev->login->cmd_lo + offset; fp->mode.wreqq.tlrt = 0; fp->mode.wreqq.tcode = tcode; fp->mode.wreqq.pri = 0; fp->mode.wreqq.dst = FWLOCALBUS | sdev->target->fwdev->dst; return xfer; } static void sbp_mgm_orb(struct sbp_dev *sdev, int func, struct sbp_ocb *aocb) { struct fw_xfer *xfer; struct fw_pkt *fp; struct sbp_ocb *ocb; struct sbp_target *target; int nid; target = sdev->target; nid = target->sbp->fd.fc->nodeid | FWLOCALBUS; SBP_LOCK_ASSERT(target->sbp); if (func == ORB_FUN_RUNQUEUE) { ocb = STAILQ_FIRST(&target->mgm_ocb_queue); if (target->mgm_ocb_cur != NULL || ocb == NULL) { return; } STAILQ_REMOVE_HEAD(&target->mgm_ocb_queue, ocb); goto start; } if ((ocb = sbp_get_ocb(sdev)) == NULL) { /* XXX */ return; } ocb->flags = OCB_ACT_MGM; ocb->sdev = sdev; bzero((void *)ocb->orb, sizeof(ocb->orb)); ocb->orb[6] = htonl((nid << 16) | SBP_BIND_HI); ocb->orb[7] = htonl(SBP_DEV2ADDR(target->target_id, sdev->lun_id)); SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s %s\n", __func__,sdev->bustgtlun, orb_fun_name[(func >> 16) & 0xf]); END_DEBUG switch (func) { case ORB_FUN_LGI: ocb->orb[0] = ocb->orb[1] = 0; /* password */ ocb->orb[2] = htonl(nid << 16); ocb->orb[3] = htonl(sdev->dma.bus_addr); ocb->orb[4] = htonl(ORB_NOTIFY | sdev->lun_id); if (ex_login) ocb->orb[4] |= htonl(ORB_EXV); ocb->orb[5] = htonl(SBP_LOGIN_SIZE); fwdma_sync(&sdev->dma, BUS_DMASYNC_PREREAD); break; case ORB_FUN_ATA: ocb->orb[0] = htonl((0 << 16) | 0); ocb->orb[1] = htonl(aocb->bus_addr & 0xffffffff); /* fall through */ case ORB_FUN_RCN: case ORB_FUN_LGO: case ORB_FUN_LUR: case ORB_FUN_RST: case ORB_FUN_ATS: ocb->orb[4] = htonl(ORB_NOTIFY | func | sdev->login->id); break; } if (target->mgm_ocb_cur != NULL) { /* there is a standing ORB */ STAILQ_INSERT_TAIL(&sdev->target->mgm_ocb_queue, ocb, ocb); return; } start: target->mgm_ocb_cur = ocb; callout_reset(&target->mgm_ocb_timeout, 5 * hz, sbp_mgm_timeout, (caddr_t)ocb); xfer = sbp_write_cmd(sdev, FWTCODE_WREQB, 0); if (xfer == NULL) { return; } xfer->hand = sbp_mgm_callback; fp = &xfer->send.hdr; fp->mode.wreqb.dest_hi = sdev->target->mgm_hi; fp->mode.wreqb.dest_lo = sdev->target->mgm_lo; fp->mode.wreqb.len = 8; fp->mode.wreqb.extcode = 0; xfer->send.payload[0] = htonl(nid << 16); xfer->send.payload[1] = htonl(ocb->bus_addr & 0xffffffff); fw_asyreq(xfer->fc, -1, xfer); } static void sbp_print_scsi_cmd(struct sbp_ocb *ocb) { struct ccb_scsiio *csio; csio = &ocb->ccb->csio; printf("%s:%d:%jx XPT_SCSI_IO: " "cmd: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x" ", flags: 0x%02x, " "%db cmd/%db data/%db sense\n", device_get_nameunit(ocb->sdev->target->sbp->fd.dev), ocb->ccb->ccb_h.target_id, (uintmax_t)ocb->ccb->ccb_h.target_lun, csio->cdb_io.cdb_bytes[0], csio->cdb_io.cdb_bytes[1], csio->cdb_io.cdb_bytes[2], csio->cdb_io.cdb_bytes[3], csio->cdb_io.cdb_bytes[4], csio->cdb_io.cdb_bytes[5], csio->cdb_io.cdb_bytes[6], csio->cdb_io.cdb_bytes[7], csio->cdb_io.cdb_bytes[8], csio->cdb_io.cdb_bytes[9], ocb->ccb->ccb_h.flags & CAM_DIR_MASK, csio->cdb_len, csio->dxfer_len, csio->sense_len); } static void sbp_scsi_status(struct sbp_status *sbp_status, struct sbp_ocb *ocb) { struct sbp_cmd_status *sbp_cmd_status; struct scsi_sense_data_fixed *sense; sbp_cmd_status = (struct sbp_cmd_status *)sbp_status->data; sense = (struct scsi_sense_data_fixed *)&ocb->ccb->csio.sense_data; SBP_DEBUG(0) sbp_print_scsi_cmd(ocb); /* XXX need decode status */ printf("%s: SCSI status %x sfmt %x valid %x key %x code %x qlfr %x len %d\n", ocb->sdev->bustgtlun, sbp_cmd_status->status, sbp_cmd_status->sfmt, sbp_cmd_status->valid, sbp_cmd_status->s_key, sbp_cmd_status->s_code, sbp_cmd_status->s_qlfr, sbp_status->len); END_DEBUG switch (sbp_cmd_status->status) { case SCSI_STATUS_CHECK_COND: case SCSI_STATUS_BUSY: case SCSI_STATUS_CMD_TERMINATED: if (sbp_cmd_status->sfmt == SBP_SFMT_CURR) { sense->error_code = SSD_CURRENT_ERROR; } else { sense->error_code = SSD_DEFERRED_ERROR; } if (sbp_cmd_status->valid) sense->error_code |= SSD_ERRCODE_VALID; sense->flags = sbp_cmd_status->s_key; if (sbp_cmd_status->mark) sense->flags |= SSD_FILEMARK; if (sbp_cmd_status->eom) sense->flags |= SSD_EOM; if (sbp_cmd_status->ill_len) sense->flags |= SSD_ILI; bcopy(&sbp_cmd_status->info, &sense->info[0], 4); if (sbp_status->len <= 1) /* XXX not scsi status. shouldn't be happened */ sense->extra_len = 0; else if (sbp_status->len <= 4) /* add_sense_code(_qual), info, cmd_spec_info */ sense->extra_len = 6; else /* fru, sense_key_spec */ sense->extra_len = 10; bcopy(&sbp_cmd_status->cdb, &sense->cmd_spec_info[0], 4); sense->add_sense_code = sbp_cmd_status->s_code; sense->add_sense_code_qual = sbp_cmd_status->s_qlfr; sense->fru = sbp_cmd_status->fru; bcopy(&sbp_cmd_status->s_keydep[0], &sense->sense_key_spec[0], 3); ocb->ccb->csio.scsi_status = sbp_cmd_status->status; ocb->ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; /* { uint8_t j, *tmp; tmp = sense; for (j = 0; j < 32; j += 8) { printf("sense %02x%02x %02x%02x %02x%02x %02x%02x\n", tmp[j], tmp[j + 1], tmp[j + 2], tmp[j + 3], tmp[j + 4], tmp[j + 5], tmp[j + 6], tmp[j + 7]); } } */ break; default: device_printf(ocb->sdev->target->sbp->fd.dev, "%s:%s unknown scsi status 0x%x\n", __func__, ocb->sdev->bustgtlun, sbp_cmd_status->status); } } static void sbp_fix_inq_data(struct sbp_ocb *ocb) { union ccb *ccb; struct sbp_dev *sdev; struct scsi_inquiry_data *inq; ccb = ocb->ccb; sdev = ocb->sdev; if (ccb->csio.cdb_io.cdb_bytes[1] & SI_EVPD) return; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s\n", __func__, sdev->bustgtlun); END_DEBUG inq = (struct scsi_inquiry_data *) ccb->csio.data_ptr; switch (SID_TYPE(inq)) { case T_DIRECT: #if 0 /* * XXX Convert Direct Access device to RBC. * I've never seen FireWire DA devices which support READ_6. */ if (SID_TYPE(inq) == T_DIRECT) inq->device |= T_RBC; /* T_DIRECT == 0 */ #endif /* fall through */ case T_RBC: /* * Override vendor/product/revision information. * Some devices sometimes return strange strings. */ #if 1 bcopy(sdev->vendor, inq->vendor, sizeof(inq->vendor)); bcopy(sdev->product, inq->product, sizeof(inq->product)); bcopy(sdev->revision + 2, inq->revision, sizeof(inq->revision)); #endif break; } /* * Force to enable/disable tagged queuing. * XXX CAM also checks SCP_QUEUE_DQUE flag in the control mode page. */ if (sbp_tags > 0) inq->flags |= SID_CmdQue; else if (sbp_tags < 0) inq->flags &= ~SID_CmdQue; } static void sbp_recv1(struct fw_xfer *xfer) { struct fw_pkt *rfp; #if NEED_RESPONSE struct fw_pkt *sfp; #endif struct sbp_softc *sbp; struct sbp_dev *sdev; struct sbp_ocb *ocb; struct sbp_login_res *login_res = NULL; struct sbp_status *sbp_status; struct sbp_target *target; int orb_fun, status_valid0, status_valid, t, l, reset_agent = 0; uint32_t addr; /* uint32_t *ld; ld = xfer->recv.buf; printf("sbp %x %d %d %08x %08x %08x %08x\n", xfer->resp, xfer->recv.len, xfer->recv.off, ntohl(ld[0]), ntohl(ld[1]), ntohl(ld[2]), ntohl(ld[3])); printf("sbp %08x %08x %08x %08x\n", ntohl(ld[4]), ntohl(ld[5]), ntohl(ld[6]), ntohl(ld[7])); printf("sbp %08x %08x %08x %08x\n", ntohl(ld[8]), ntohl(ld[9]), ntohl(ld[10]), ntohl(ld[11])); */ sbp = (struct sbp_softc *)xfer->sc; SBP_LOCK_ASSERT(sbp); if (xfer->resp != 0) { printf("sbp_recv: xfer->resp = %d\n", xfer->resp); goto done0; } if (xfer->recv.payload == NULL) { printf("sbp_recv: xfer->recv.payload == NULL\n"); goto done0; } rfp = &xfer->recv.hdr; if (rfp->mode.wreqb.tcode != FWTCODE_WREQB) { printf("sbp_recv: tcode = %d\n", rfp->mode.wreqb.tcode); goto done0; } sbp_status = (struct sbp_status *)xfer->recv.payload; addr = rfp->mode.wreqb.dest_lo; SBP_DEBUG(2) printf("received address 0x%x\n", addr); END_DEBUG t = SBP_ADDR2TRG(addr); if (t >= SBP_NUM_TARGETS) { device_printf(sbp->fd.dev, "sbp_recv1: invalid target %d\n", t); goto done0; } target = &sbp->targets[t]; l = SBP_ADDR2LUN(addr); if (l >= target->num_lun || target->luns[l] == NULL) { device_printf(sbp->fd.dev, "sbp_recv1: invalid lun %d (target=%d)\n", l, t); goto done0; } sdev = target->luns[l]; ocb = NULL; switch (sbp_status->src) { case 0: case 1: /* check mgm_ocb_cur first */ ocb = target->mgm_ocb_cur; if (ocb != NULL) { if (OCB_MATCH(ocb, sbp_status)) { callout_stop(&target->mgm_ocb_timeout); target->mgm_ocb_cur = NULL; break; } } ocb = sbp_dequeue_ocb(sdev, sbp_status); if (ocb == NULL) { device_printf(sdev->target->sbp->fd.dev, "%s:%s No ocb(%x) on the queue\n", __func__,sdev->bustgtlun, ntohl(sbp_status->orb_lo)); } break; case 2: /* unsolicit */ device_printf(sdev->target->sbp->fd.dev, "%s:%s unsolicit status received\n", __func__, sdev->bustgtlun); break; default: device_printf(sdev->target->sbp->fd.dev, "%s:%s unknown sbp_status->src\n", __func__, sdev->bustgtlun); } status_valid0 = (sbp_status->src < 2 && sbp_status->resp == ORB_RES_CMPL && sbp_status->dead == 0); status_valid = (status_valid0 && sbp_status->status == 0); if (!status_valid0 || debug > 2) { int status; SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s ORB status src:%x resp:%x dead:%x" " len:%x stat:%x orb:%x%08x\n", __func__, sdev->bustgtlun, sbp_status->src, sbp_status->resp, sbp_status->dead, sbp_status->len, sbp_status->status, ntohs(sbp_status->orb_hi), ntohl(sbp_status->orb_lo)); END_DEBUG device_printf(sdev->target->sbp->fd.dev, "%s\n", sdev->bustgtlun); status = sbp_status->status; switch (sbp_status->resp) { case 0: if (status > MAX_ORB_STATUS0) printf("%s\n", orb_status0[MAX_ORB_STATUS0]); else printf("%s\n", orb_status0[status]); break; case 1: printf("Obj: %s, Error: %s\n", orb_status1_object[(status >> 6) & 3], orb_status1_serial_bus_error[status & 0xf]); break; case 2: printf("Illegal request\n"); break; case 3: printf("Vendor dependent\n"); break; default: printf("unknown respose code %d\n", sbp_status->resp); } } /* we have to reset the fetch agent if it's dead */ if (sbp_status->dead) { if (sdev->path) { xpt_freeze_devq(sdev->path, 1); sdev->freeze++; } reset_agent = 1; } if (ocb == NULL) goto done; switch (ntohl(ocb->orb[4]) & ORB_FMT_MSK) { case ORB_FMT_NOP: break; case ORB_FMT_VED: break; case ORB_FMT_STD: switch (ocb->flags) { case OCB_ACT_MGM: orb_fun = ntohl(ocb->orb[4]) & ORB_FUN_MSK; reset_agent = 0; switch (orb_fun) { case ORB_FUN_LGI: fwdma_sync(&sdev->dma, BUS_DMASYNC_POSTREAD); login_res = sdev->login; login_res->len = ntohs(login_res->len); login_res->id = ntohs(login_res->id); login_res->cmd_hi = ntohs(login_res->cmd_hi); login_res->cmd_lo = ntohl(login_res->cmd_lo); if (status_valid) { SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s login: len %d, ID %d, cmd %08x%08x, recon_hold %d\n", __func__, sdev->bustgtlun, login_res->len, login_res->id, login_res->cmd_hi, login_res->cmd_lo, ntohs(login_res->recon_hold)); END_DEBUG sbp_busy_timeout(sdev); } else { /* forgot logout? */ device_printf(sdev->target->sbp->fd.dev, "%s:%s login failed\n", __func__, sdev->bustgtlun); sdev->status = SBP_DEV_RESET; } break; case ORB_FUN_RCN: login_res = sdev->login; if (status_valid) { SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s reconnect: len %d, ID %d, cmd %08x%08x\n", __func__, sdev->bustgtlun, login_res->len, login_res->id, login_res->cmd_hi, login_res->cmd_lo); END_DEBUG if (sdev->status == SBP_DEV_ATTACHED) sbp_scan_dev(sdev); else sbp_agent_reset(sdev); } else { /* reconnection hold time exceed? */ SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s reconnect failed\n", __func__, sdev->bustgtlun); END_DEBUG sbp_login(sdev); } break; case ORB_FUN_LGO: sdev->status = SBP_DEV_RESET; break; case ORB_FUN_RST: sbp_busy_timeout(sdev); break; case ORB_FUN_LUR: case ORB_FUN_ATA: case ORB_FUN_ATS: sbp_agent_reset(sdev); break; default: device_printf(sdev->target->sbp->fd.dev, "%s:%s unknown function %d\n", __func__, sdev->bustgtlun, orb_fun); break; } sbp_mgm_orb(sdev, ORB_FUN_RUNQUEUE, NULL); break; case OCB_ACT_CMD: sdev->timeout = 0; if (ocb->ccb != NULL) { union ccb *ccb; ccb = ocb->ccb; if (sbp_status->len > 1) { sbp_scsi_status(sbp_status, ocb); } else { if (sbp_status->resp != ORB_RES_CMPL) { ccb->ccb_h.status = CAM_REQ_CMP_ERR; } else { ccb->ccb_h.status = CAM_REQ_CMP; } } /* fix up inq data */ if (ccb->csio.cdb_io.cdb_bytes[0] == INQUIRY) sbp_fix_inq_data(ocb); xpt_done(ccb); } break; default: break; } } if (!use_doorbell) sbp_free_ocb(sdev, ocb); done: if (reset_agent) sbp_agent_reset(sdev); done0: xfer->recv.pay_len = SBP_RECV_LEN; /* The received packet is usually small enough to be stored within * the buffer. In that case, the controller return ack_complete and * no respose is necessary. * * XXX fwohci.c and firewire.c should inform event_code such as * ack_complete or ack_pending to upper driver. */ #if NEED_RESPONSE xfer->send.off = 0; sfp = (struct fw_pkt *)xfer->send.buf; sfp->mode.wres.dst = rfp->mode.wreqb.src; xfer->dst = sfp->mode.wres.dst; xfer->spd = min(sdev->target->fwdev->speed, max_speed); xfer->hand = sbp_loginres_callback; sfp->mode.wres.tlrt = rfp->mode.wreqb.tlrt; sfp->mode.wres.tcode = FWTCODE_WRES; sfp->mode.wres.rtcode = 0; sfp->mode.wres.pri = 0; fw_asyreq(xfer->fc, -1, xfer); #else /* recycle */ STAILQ_INSERT_TAIL(&sbp->fwb.xferlist, xfer, link); #endif } static void sbp_recv(struct fw_xfer *xfer) { struct sbp_softc *sbp; sbp = (struct sbp_softc *)xfer->sc; SBP_LOCK(sbp); sbp_recv1(xfer); SBP_UNLOCK(sbp); } /* * sbp_attach() */ static int sbp_attach(device_t dev) { struct sbp_softc *sbp; struct cam_devq *devq; struct firewire_comm *fc; int i, error; if (DFLTPHYS > SBP_MAXPHYS) device_printf(dev, "Warning, DFLTPHYS(%dKB) is larger than " "SBP_MAXPHYS(%dKB).\n", DFLTPHYS / 1024, SBP_MAXPHYS / 1024); if (!firewire_phydma_enable) device_printf(dev, "Warning, hw.firewire.phydma_enable must be 1 " "for SBP over FireWire.\n"); SBP_DEBUG(0) printf("sbp_attach (cold=%d)\n", cold); END_DEBUG if (cold) sbp_cold++; sbp = device_get_softc(dev); sbp->fd.dev = dev; sbp->fd.fc = fc = device_get_ivars(dev); mtx_init(&sbp->mtx, "sbp", NULL, MTX_DEF); if (max_speed < 0) max_speed = fc->speed; error = bus_dma_tag_create(/*parent*/fc->dmat, /* XXX shoud be 4 for sane backend? */ /*alignment*/1, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/0x100000, /*nsegments*/SBP_IND_MAX, /*maxsegsz*/SBP_SEG_MAX, /*flags*/BUS_DMA_ALLOCNOW, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&sbp->mtx, &sbp->dmat); if (error != 0) { printf("sbp_attach: Could not allocate DMA tag " "- error %d\n", error); return (ENOMEM); } devq = cam_simq_alloc(/*maxopenings*/SBP_NUM_OCB); if (devq == NULL) return (ENXIO); for (i = 0; i < SBP_NUM_TARGETS; i++) { sbp->targets[i].fwdev = NULL; sbp->targets[i].luns = NULL; sbp->targets[i].sbp = sbp; } sbp->sim = cam_sim_alloc(sbp_action, sbp_poll, "sbp", sbp, device_get_unit(dev), &sbp->mtx, /*untagged*/ 1, /*tagged*/ SBP_QUEUE_LEN - 1, devq); if (sbp->sim == NULL) { cam_simq_free(devq); return (ENXIO); } SBP_LOCK(sbp); if (xpt_bus_register(sbp->sim, dev, /*bus*/0) != CAM_SUCCESS) goto fail; if (xpt_create_path(&sbp->path, NULL, cam_sim_path(sbp->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_bus_deregister(cam_sim_path(sbp->sim)); goto fail; } SBP_UNLOCK(sbp); /* We reserve 16 bit space (4 bytes X 64 targets X 256 luns) */ sbp->fwb.start = ((u_int64_t)SBP_BIND_HI << 32) | SBP_DEV2ADDR(0, 0); sbp->fwb.end = sbp->fwb.start + 0xffff; /* pre-allocate xfer */ STAILQ_INIT(&sbp->fwb.xferlist); fw_xferlist_add(&sbp->fwb.xferlist, M_SBP, /*send*/ 0, /*recv*/ SBP_RECV_LEN, SBP_NUM_OCB/2, fc, (void *)sbp, sbp_recv); fw_bindadd(fc, &sbp->fwb); sbp->fd.post_busreset = sbp_post_busreset; sbp->fd.post_explore = sbp_post_explore; if (fc->status != -1) { sbp_post_busreset(sbp); sbp_post_explore(sbp); } SBP_LOCK(sbp); xpt_async(AC_BUS_RESET, sbp->path, /*arg*/ NULL); SBP_UNLOCK(sbp); return (0); fail: SBP_UNLOCK(sbp); cam_sim_free(sbp->sim, /*free_devq*/TRUE); return (ENXIO); } static int sbp_logout_all(struct sbp_softc *sbp) { struct sbp_target *target; struct sbp_dev *sdev; int i, j; SBP_DEBUG(0) printf("sbp_logout_all\n"); END_DEBUG SBP_LOCK_ASSERT(sbp); for (i = 0; i < SBP_NUM_TARGETS; i++) { target = &sbp->targets[i]; if (target->luns == NULL) continue; for (j = 0; j < target->num_lun; j++) { sdev = target->luns[j]; if (sdev == NULL) continue; callout_stop(&sdev->login_callout); if (sdev->status >= SBP_DEV_TOATTACH && sdev->status <= SBP_DEV_ATTACHED) sbp_mgm_orb(sdev, ORB_FUN_LGO, NULL); } } return 0; } static int sbp_shutdown(device_t dev) { struct sbp_softc *sbp = ((struct sbp_softc *)device_get_softc(dev)); SBP_LOCK(sbp); sbp_logout_all(sbp); SBP_UNLOCK(sbp); return (0); } static void sbp_free_sdev(struct sbp_dev *sdev) { struct sbp_softc *sbp; int i; if (sdev == NULL) return; sbp = sdev->target->sbp; SBP_UNLOCK(sbp); callout_drain(&sdev->login_callout); for (i = 0; i < SBP_QUEUE_LEN; i++) { callout_drain(&sdev->ocb[i].timer); bus_dmamap_destroy(sbp->dmat, sdev->ocb[i].dmamap); } fwdma_free(sbp->fd.fc, &sdev->dma); free(sdev, M_SBP); SBP_LOCK(sbp); } static void sbp_free_target(struct sbp_target *target) { struct sbp_softc *sbp; struct fw_xfer *xfer, *next; int i; if (target->luns == NULL) return; sbp = target->sbp; SBP_LOCK_ASSERT(sbp); SBP_UNLOCK(sbp); callout_drain(&target->mgm_ocb_timeout); callout_drain(&target->scan_callout); SBP_LOCK(sbp); for (i = 0; i < target->num_lun; i++) sbp_free_sdev(target->luns[i]); STAILQ_FOREACH_SAFE(xfer, &target->xferlist, link, next) { fw_xfer_free_buf(xfer); } STAILQ_INIT(&target->xferlist); free(target->luns, M_SBP); target->num_lun = 0; target->luns = NULL; target->fwdev = NULL; } static int sbp_detach(device_t dev) { struct sbp_softc *sbp = ((struct sbp_softc *)device_get_softc(dev)); struct firewire_comm *fc = sbp->fd.fc; int i; SBP_DEBUG(0) printf("sbp_detach\n"); END_DEBUG SBP_LOCK(sbp); for (i = 0; i < SBP_NUM_TARGETS; i++) sbp_cam_detach_target(&sbp->targets[i]); xpt_async(AC_LOST_DEVICE, sbp->path, NULL); xpt_free_path(sbp->path); xpt_bus_deregister(cam_sim_path(sbp->sim)); cam_sim_free(sbp->sim, /*free_devq*/ TRUE); sbp_logout_all(sbp); SBP_UNLOCK(sbp); /* XXX wait for logout completion */ pause("sbpdtc", hz/2); SBP_LOCK(sbp); for (i = 0; i < SBP_NUM_TARGETS; i++) sbp_free_target(&sbp->targets[i]); SBP_UNLOCK(sbp); fw_bindremove(fc, &sbp->fwb); fw_xferlist_remove(&sbp->fwb.xferlist); bus_dma_tag_destroy(sbp->dmat); mtx_destroy(&sbp->mtx); return (0); } static void sbp_cam_detach_sdev(struct sbp_dev *sdev) { if (sdev == NULL) return; if (sdev->status == SBP_DEV_DEAD) return; if (sdev->status == SBP_DEV_RESET) return; SBP_LOCK_ASSERT(sdev->target->sbp); sbp_abort_all_ocbs(sdev, CAM_DEV_NOT_THERE); if (sdev->path) { xpt_release_devq(sdev->path, sdev->freeze, TRUE); sdev->freeze = 0; xpt_async(AC_LOST_DEVICE, sdev->path, NULL); xpt_free_path(sdev->path); sdev->path = NULL; } } static void sbp_cam_detach_target(struct sbp_target *target) { int i; SBP_LOCK_ASSERT(target->sbp); if (target->luns != NULL) { SBP_DEBUG(0) printf("sbp_detach_target %d\n", target->target_id); END_DEBUG callout_stop(&target->scan_callout); for (i = 0; i < target->num_lun; i++) sbp_cam_detach_sdev(target->luns[i]); } } static void sbp_target_reset(struct sbp_dev *sdev, int method) { int i; struct sbp_target *target = sdev->target; struct sbp_dev *tsdev; SBP_LOCK_ASSERT(target->sbp); for (i = 0; i < target->num_lun; i++) { tsdev = target->luns[i]; if (tsdev == NULL) continue; if (tsdev->status == SBP_DEV_DEAD) continue; if (tsdev->status == SBP_DEV_RESET) continue; xpt_freeze_devq(tsdev->path, 1); tsdev->freeze++; sbp_abort_all_ocbs(tsdev, CAM_CMD_TIMEOUT); if (method == 2) tsdev->status = SBP_DEV_LOGIN; } switch (method) { case 1: printf("target reset\n"); sbp_mgm_orb(sdev, ORB_FUN_RST, NULL); break; case 2: printf("reset start\n"); sbp_reset_start(sdev); break; } } static void sbp_mgm_timeout(void *arg) { struct sbp_ocb *ocb = (struct sbp_ocb *)arg; struct sbp_dev *sdev = ocb->sdev; struct sbp_target *target = sdev->target; SBP_LOCK_ASSERT(target->sbp); device_printf(sdev->target->sbp->fd.dev, "%s:%s request timeout(mgm orb:0x%08x)\n", __func__, sdev->bustgtlun, (uint32_t)ocb->bus_addr); target->mgm_ocb_cur = NULL; sbp_free_ocb(sdev, ocb); #if 0 /* XXX */ printf("run next request\n"); sbp_mgm_orb(sdev, ORB_FUN_RUNQUEUE, NULL); #endif device_printf(sdev->target->sbp->fd.dev, "%s:%s reset start\n", __func__, sdev->bustgtlun); sbp_reset_start(sdev); } static void sbp_timeout(void *arg) { struct sbp_ocb *ocb = (struct sbp_ocb *)arg; struct sbp_dev *sdev = ocb->sdev; device_printf(sdev->target->sbp->fd.dev, "%s:%s request timeout(cmd orb:0x%08x) ... ", __func__, sdev->bustgtlun, (uint32_t)ocb->bus_addr); SBP_LOCK_ASSERT(sdev->target->sbp); sdev->timeout++; switch (sdev->timeout) { case 1: printf("agent reset\n"); xpt_freeze_devq(sdev->path, 1); sdev->freeze++; sbp_abort_all_ocbs(sdev, CAM_CMD_TIMEOUT); sbp_agent_reset(sdev); break; case 2: case 3: sbp_target_reset(sdev, sdev->timeout - 1); break; #if 0 default: /* XXX give up */ sbp_cam_detach_target(target); if (target->luns != NULL) free(target->luns, M_SBP); target->num_lun = 0; target->luns = NULL; target->fwdev = NULL; #endif } } static void sbp_action(struct cam_sim *sim, union ccb *ccb) { struct sbp_softc *sbp = cam_sim_softc(sim); struct sbp_target *target = NULL; struct sbp_dev *sdev = NULL; if (sbp != NULL) SBP_LOCK_ASSERT(sbp); /* target:lun -> sdev mapping */ if (sbp != NULL && ccb->ccb_h.target_id != CAM_TARGET_WILDCARD && ccb->ccb_h.target_id < SBP_NUM_TARGETS) { target = &sbp->targets[ccb->ccb_h.target_id]; if (target->fwdev != NULL && ccb->ccb_h.target_lun != CAM_LUN_WILDCARD && ccb->ccb_h.target_lun < target->num_lun) { sdev = target->luns[ccb->ccb_h.target_lun]; if (sdev != NULL && sdev->status != SBP_DEV_ATTACHED && sdev->status != SBP_DEV_PROBE) sdev = NULL; } } SBP_DEBUG(1) if (sdev == NULL) printf("invalid target %d lun %jx\n", ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); END_DEBUG switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: case XPT_RESET_DEV: case XPT_GET_TRAN_SETTINGS: case XPT_SET_TRAN_SETTINGS: case XPT_CALC_GEOMETRY: if (sdev == NULL) { SBP_DEBUG(1) printf("%s:%d:%jx:func_code 0x%04x: " "Invalid target (target needed)\n", device_get_nameunit(sbp->fd.dev), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, ccb->ccb_h.func_code); END_DEBUG ccb->ccb_h.status = CAM_DEV_NOT_THERE; xpt_done(ccb); return; } break; case XPT_PATH_INQ: case XPT_NOOP: /* The opcodes sometimes aimed at a target (sc is valid), * sometimes aimed at the SIM (sc is invalid and target is * CAM_TARGET_WILDCARD) */ if (sbp == NULL && ccb->ccb_h.target_id != CAM_TARGET_WILDCARD) { SBP_DEBUG(0) printf("%s:%d:%jx func_code 0x%04x: " "Invalid target (no wildcard)\n", device_get_nameunit(sbp->fd.dev), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, ccb->ccb_h.func_code); END_DEBUG ccb->ccb_h.status = CAM_DEV_NOT_THERE; xpt_done(ccb); return; } break; default: /* XXX Hm, we should check the input parameters */ break; } switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: { struct ccb_scsiio *csio; struct sbp_ocb *ocb; int speed; void *cdb; csio = &ccb->csio; mtx_assert(sim->mtx, MA_OWNED); SBP_DEBUG(2) printf("%s:%d:%jx XPT_SCSI_IO: " "cmd: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x" ", flags: 0x%02x, " "%db cmd/%db data/%db sense\n", device_get_nameunit(sbp->fd.dev), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, csio->cdb_io.cdb_bytes[0], csio->cdb_io.cdb_bytes[1], csio->cdb_io.cdb_bytes[2], csio->cdb_io.cdb_bytes[3], csio->cdb_io.cdb_bytes[4], csio->cdb_io.cdb_bytes[5], csio->cdb_io.cdb_bytes[6], csio->cdb_io.cdb_bytes[7], csio->cdb_io.cdb_bytes[8], csio->cdb_io.cdb_bytes[9], ccb->ccb_h.flags & CAM_DIR_MASK, csio->cdb_len, csio->dxfer_len, csio->sense_len); END_DEBUG if (sdev == NULL) { ccb->ccb_h.status = CAM_DEV_NOT_THERE; xpt_done(ccb); return; } if (csio->cdb_len > sizeof(ocb->orb) - 5 * sizeof(uint32_t)) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); return; } #if 0 /* if we are in probe stage, pass only probe commands */ if (sdev->status == SBP_DEV_PROBE) { char *name; name = xpt_path_periph(ccb->ccb_h.path)->periph_name; printf("probe stage, periph name: %s\n", name); if (strcmp(name, "probe") != 0) { ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); return; } } #endif if ((ocb = sbp_get_ocb(sdev)) == NULL) { ccb->ccb_h.status = CAM_RESRC_UNAVAIL; if (sdev->freeze == 0) { xpt_freeze_devq(sdev->path, 1); sdev->freeze++; } xpt_done(ccb); return; } ocb->flags = OCB_ACT_CMD; ocb->sdev = sdev; ocb->ccb = ccb; ccb->ccb_h.ccb_sdev_ptr = sdev; ocb->orb[0] = htonl(1U << 31); ocb->orb[1] = 0; ocb->orb[2] = htonl(((sbp->fd.fc->nodeid | FWLOCALBUS) << 16)); ocb->orb[3] = htonl(ocb->bus_addr + IND_PTR_OFFSET); speed = min(target->fwdev->speed, max_speed); ocb->orb[4] = htonl(ORB_NOTIFY | ORB_CMD_SPD(speed) | ORB_CMD_MAXP(speed + 7)); if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { ocb->orb[4] |= htonl(ORB_CMD_IN); } if (csio->ccb_h.flags & CAM_CDB_POINTER) cdb = (void *)csio->cdb_io.cdb_ptr; else cdb = (void *)&csio->cdb_io.cdb_bytes; bcopy(cdb, (void *)&ocb->orb[5], csio->cdb_len); /* printf("ORB %08x %08x %08x %08x\n", ntohl(ocb->orb[0]), ntohl(ocb->orb[1]), ntohl(ocb->orb[2]), ntohl(ocb->orb[3])); printf("ORB %08x %08x %08x %08x\n", ntohl(ocb->orb[4]), ntohl(ocb->orb[5]), ntohl(ocb->orb[6]), ntohl(ocb->orb[7])); */ if (ccb->csio.dxfer_len > 0) { int error; error = bus_dmamap_load_ccb(/*dma tag*/sbp->dmat, /*dma map*/ocb->dmamap, ccb, sbp_execute_ocb, ocb, /*flags*/0); if (error) printf("sbp: bus_dmamap_load error %d\n", error); } else sbp_execute_ocb(ocb, NULL, 0, 0); break; } case XPT_CALC_GEOMETRY: { struct ccb_calc_geometry *ccg; ccg = &ccb->ccg; if (ccg->block_size == 0) { printf("sbp_action: block_size is 0.\n"); ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } SBP_DEBUG(1) printf("%s:%d:%d:%jx:XPT_CALC_GEOMETRY: " "Volume size = %jd\n", device_get_nameunit(sbp->fd.dev), cam_sim_path(sbp->sim), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun, (uintmax_t)ccg->volume_size); END_DEBUG cam_calc_geometry(ccg, /*extended*/1); xpt_done(ccb); break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ { SBP_DEBUG(1) printf("%s:%d:XPT_RESET_BUS: \n", device_get_nameunit(sbp->fd.dev), cam_sim_path(sbp->sim)); END_DEBUG ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; SBP_DEBUG(1) printf("%s:%d:%jx XPT_PATH_INQ:.\n", device_get_nameunit(sbp->fd.dev), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); END_DEBUG cpi->version_num = 1; /* XXX??? */ cpi->hba_inquiry = PI_TAG_ABLE; cpi->target_sprt = 0; cpi->hba_misc = PIM_NOBUSRESET | PIM_NO_6_BYTE; cpi->hba_eng_cnt = 0; cpi->max_target = SBP_NUM_TARGETS - 1; cpi->max_lun = SBP_NUM_LUNS - 1; cpi->initiator_id = SBP_INITIATOR; cpi->bus_id = sim->bus_id; cpi->base_transfer_speed = 400 * 1000 / 8; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "SBP", HBA_IDLEN); strlcpy(cpi->dev_name, sim->sim_name, DEV_IDLEN); cpi->unit_number = sim->unit_number; cpi->transport = XPORT_SPI; /* XX should have a FireWire */ cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; /* should have a FireWire */ cts->transport_version = 2; spi->valid = CTS_SPI_VALID_DISC; spi->flags = CTS_SPI_FLAGS_DISC_ENB; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; SBP_DEBUG(1) printf("%s:%d:%jx XPT_GET_TRAN_SETTINGS:.\n", device_get_nameunit(sbp->fd.dev), ccb->ccb_h.target_id, (uintmax_t)ccb->ccb_h.target_lun); END_DEBUG cts->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_ABORT: ccb->ccb_h.status = CAM_UA_ABORT; xpt_done(ccb); break; case XPT_SET_TRAN_SETTINGS: /* XXX */ default: ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } return; } static void sbp_execute_ocb(void *arg, bus_dma_segment_t *segments, int seg, int error) { int i; struct sbp_ocb *ocb; struct sbp_ocb *prev; bus_dma_segment_t *s; if (error) printf("sbp_execute_ocb: error=%d\n", error); ocb = (struct sbp_ocb *)arg; SBP_DEBUG(2) printf("sbp_execute_ocb: seg %d", seg); for (i = 0; i < seg; i++) printf(", %jx:%jd", (uintmax_t)segments[i].ds_addr, (uintmax_t)segments[i].ds_len); printf("\n"); END_DEBUG if (seg == 1) { /* direct pointer */ s = &segments[0]; if (s->ds_len > SBP_SEG_MAX) panic("ds_len > SBP_SEG_MAX, fix busdma code"); ocb->orb[3] = htonl(s->ds_addr); ocb->orb[4] |= htonl(s->ds_len); } else if (seg > 1) { /* page table */ for (i = 0; i < seg; i++) { s = &segments[i]; SBP_DEBUG(0) /* XXX LSI Logic "< 16 byte" bug might be hit */ if (s->ds_len < 16) printf("sbp_execute_ocb: warning, " "segment length(%zd) is less than 16." "(seg=%d/%d)\n", (size_t)s->ds_len, i + 1, seg); END_DEBUG if (s->ds_len > SBP_SEG_MAX) panic("ds_len > SBP_SEG_MAX, fix busdma code"); ocb->ind_ptr[i].hi = htonl(s->ds_len << 16); ocb->ind_ptr[i].lo = htonl(s->ds_addr); } ocb->orb[4] |= htonl(ORB_CMD_PTBL | seg); } if (seg > 0) bus_dmamap_sync(ocb->sdev->target->sbp->dmat, ocb->dmamap, (ntohl(ocb->orb[4]) & ORB_CMD_IN) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); prev = sbp_enqueue_ocb(ocb->sdev, ocb); fwdma_sync(&ocb->sdev->dma, BUS_DMASYNC_PREWRITE); if (use_doorbell) { if (prev == NULL) { if (ocb->sdev->last_ocb != NULL) sbp_doorbell(ocb->sdev); else sbp_orb_pointer(ocb->sdev, ocb); } } else { if (prev == NULL || (ocb->sdev->flags & ORB_LINK_DEAD) != 0) { ocb->sdev->flags &= ~ORB_LINK_DEAD; sbp_orb_pointer(ocb->sdev, ocb); } } } static void sbp_poll(struct cam_sim *sim) { struct sbp_softc *sbp; struct firewire_comm *fc; sbp = cam_sim_softc(sim); fc = sbp->fd.fc; fc->poll(fc, 0, -1); return; } static struct sbp_ocb * sbp_dequeue_ocb(struct sbp_dev *sdev, struct sbp_status *sbp_status) { struct sbp_ocb *ocb; struct sbp_ocb *next; int order = 0; SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s 0x%08x src %d\n", __func__, sdev->bustgtlun, ntohl(sbp_status->orb_lo), sbp_status->src); END_DEBUG SBP_LOCK_ASSERT(sdev->target->sbp); STAILQ_FOREACH_SAFE(ocb, &sdev->ocbs, ocb, next) { if (OCB_MATCH(ocb, sbp_status)) { /* found */ STAILQ_REMOVE(&sdev->ocbs, ocb, sbp_ocb, ocb); if (ocb->ccb != NULL) callout_stop(&ocb->timer); if (ntohl(ocb->orb[4]) & 0xffff) { bus_dmamap_sync(sdev->target->sbp->dmat, ocb->dmamap, (ntohl(ocb->orb[4]) & ORB_CMD_IN) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sdev->target->sbp->dmat, ocb->dmamap); } if (!use_doorbell) { if (sbp_status->src == SRC_NO_NEXT) { if (next != NULL) sbp_orb_pointer(sdev, next); else if (order > 0) { /* * Unordered execution * We need to send pointer for * next ORB */ sdev->flags |= ORB_LINK_DEAD; } } } else { /* * XXX this is not correct for unordered * execution. */ if (sdev->last_ocb != NULL) { sbp_free_ocb(sdev, sdev->last_ocb); } sdev->last_ocb = ocb; if (next != NULL && sbp_status->src == SRC_NO_NEXT) sbp_doorbell(sdev); } break; } else order++; } SBP_DEBUG(0) if (ocb && order > 0) { device_printf(sdev->target->sbp->fd.dev, "%s:%s unordered execution order:%d\n", __func__, sdev->bustgtlun, order); } END_DEBUG return (ocb); } static struct sbp_ocb * sbp_enqueue_ocb(struct sbp_dev *sdev, struct sbp_ocb *ocb) { struct sbp_ocb *prev, *prev2; SBP_LOCK_ASSERT(sdev->target->sbp); SBP_DEBUG(1) device_printf(sdev->target->sbp->fd.dev, "%s:%s 0x%08jx\n", __func__, sdev->bustgtlun, (uintmax_t)ocb->bus_addr); END_DEBUG prev2 = prev = STAILQ_LAST(&sdev->ocbs, sbp_ocb, ocb); STAILQ_INSERT_TAIL(&sdev->ocbs, ocb, ocb); if (ocb->ccb != NULL) { callout_reset_sbt(&ocb->timer, SBT_1MS * ocb->ccb->ccb_h.timeout, 0, sbp_timeout, ocb, 0); } if (use_doorbell && prev == NULL) prev2 = sdev->last_ocb; if (prev2 != NULL && (ocb->sdev->flags & ORB_LINK_DEAD) == 0) { SBP_DEBUG(1) printf("linking chain 0x%jx -> 0x%jx\n", (uintmax_t)prev2->bus_addr, (uintmax_t)ocb->bus_addr); END_DEBUG /* * Suppress compiler optimization so that orb[1] must be written first. * XXX We may need an explicit memory barrier for other architectures * other than i386/amd64. */ *(volatile uint32_t *)&prev2->orb[1] = htonl(ocb->bus_addr); *(volatile uint32_t *)&prev2->orb[0] = 0; } return prev; } static struct sbp_ocb * sbp_get_ocb(struct sbp_dev *sdev) { struct sbp_ocb *ocb; SBP_LOCK_ASSERT(sdev->target->sbp); ocb = STAILQ_FIRST(&sdev->free_ocbs); if (ocb == NULL) { sdev->flags |= ORB_SHORTAGE; printf("ocb shortage!!!\n"); return NULL; } STAILQ_REMOVE_HEAD(&sdev->free_ocbs, ocb); ocb->ccb = NULL; return (ocb); } static void sbp_free_ocb(struct sbp_dev *sdev, struct sbp_ocb *ocb) { ocb->flags = 0; ocb->ccb = NULL; SBP_LOCK_ASSERT(sdev->target->sbp); STAILQ_INSERT_TAIL(&sdev->free_ocbs, ocb, ocb); if ((sdev->flags & ORB_SHORTAGE) != 0) { int count; sdev->flags &= ~ORB_SHORTAGE; count = sdev->freeze; sdev->freeze = 0; xpt_release_devq(sdev->path, count, TRUE); } } static void sbp_abort_ocb(struct sbp_ocb *ocb, int status) { struct sbp_dev *sdev; sdev = ocb->sdev; SBP_LOCK_ASSERT(sdev->target->sbp); SBP_DEBUG(0) device_printf(sdev->target->sbp->fd.dev, "%s:%s 0x%jx\n", __func__, sdev->bustgtlun, (uintmax_t)ocb->bus_addr); END_DEBUG SBP_DEBUG(1) if (ocb->ccb != NULL) sbp_print_scsi_cmd(ocb); END_DEBUG if (ntohl(ocb->orb[4]) & 0xffff) { bus_dmamap_sync(sdev->target->sbp->dmat, ocb->dmamap, (ntohl(ocb->orb[4]) & ORB_CMD_IN) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sdev->target->sbp->dmat, ocb->dmamap); } if (ocb->ccb != NULL) { callout_stop(&ocb->timer); ocb->ccb->ccb_h.status = status; xpt_done(ocb->ccb); } sbp_free_ocb(sdev, ocb); } static void sbp_abort_all_ocbs(struct sbp_dev *sdev, int status) { struct sbp_ocb *ocb, *next; STAILQ_HEAD(, sbp_ocb) temp; STAILQ_INIT(&temp); SBP_LOCK_ASSERT(sdev->target->sbp); STAILQ_CONCAT(&temp, &sdev->ocbs); STAILQ_INIT(&sdev->ocbs); STAILQ_FOREACH_SAFE(ocb, &temp, ocb, next) { sbp_abort_ocb(ocb, status); } if (sdev->last_ocb != NULL) { sbp_free_ocb(sdev, sdev->last_ocb); sdev->last_ocb = NULL; } } static device_method_t sbp_methods[] = { /* device interface */ DEVMETHOD(device_identify, sbp_identify), DEVMETHOD(device_probe, sbp_probe), DEVMETHOD(device_attach, sbp_attach), DEVMETHOD(device_detach, sbp_detach), DEVMETHOD(device_shutdown, sbp_shutdown), { 0, 0 } }; static driver_t sbp_driver = { "sbp", sbp_methods, sizeof(struct sbp_softc), }; DRIVER_MODULE(sbp, firewire, sbp_driver, 0, 0); MODULE_VERSION(sbp, 1); MODULE_DEPEND(sbp, firewire, 1, 1, 1); MODULE_DEPEND(sbp, cam, 1, 1, 1); diff --git a/sys/dev/glxiic/glxiic.c b/sys/dev/glxiic/glxiic.c index ddaa77b6b73c..ef0a0e111339 100644 --- a/sys/dev/glxiic/glxiic.c +++ b/sys/dev/glxiic/glxiic.c @@ -1,1077 +1,1078 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Henrik Brix Andersen * 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, 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. */ #include /* * AMD Geode LX CS5536 System Management Bus controller. * * Although AMD refers to this device as an SMBus controller, it * really is an I2C controller (It lacks SMBus ALERT# and Alert * Response support). * * The driver is implemented as an interrupt-driven state machine, * supporting both master and slave mode. */ #include #include #include #include #include #include #include #include #ifdef GLXIIC_DEBUG #include #endif #include #include #include #include #include #include #include #include "iicbus_if.h" /* CS5536 PCI-ISA ID. */ #define GLXIIC_CS5536_DEV_ID 0x20901022 /* MSRs. */ #define GLXIIC_MSR_PIC_YSEL_HIGH 0x51400021 /* Bus speeds. */ #define GLXIIC_SLOW 0x0258 /* 10 kHz. */ #define GLXIIC_FAST 0x0078 /* 50 kHz. */ #define GLXIIC_FASTEST 0x003c /* 100 kHz. */ /* Default bus activity timeout in milliseconds. */ #define GLXIIC_DEFAULT_TIMEOUT 35 /* GPIO register offsets. */ #define GLXIIC_GPIOL_OUT_AUX1_SEL 0x10 #define GLXIIC_GPIOL_IN_AUX1_SEL 0x34 /* GPIO 14 (SMB_CLK) and 15 (SMB_DATA) bitmasks. */ #define GLXIIC_GPIO_14_15_ENABLE 0x0000c000 #define GLXIIC_GPIO_14_15_DISABLE 0xc0000000 /* SMB register offsets. */ #define GLXIIC_SMB_SDA 0x00 #define GLXIIC_SMB_STS 0x01 #define GLXIIC_SMB_STS_SLVSTP_BIT (1 << 7) #define GLXIIC_SMB_STS_SDAST_BIT (1 << 6) #define GLXIIC_SMB_STS_BER_BIT (1 << 5) #define GLXIIC_SMB_STS_NEGACK_BIT (1 << 4) #define GLXIIC_SMB_STS_STASTR_BIT (1 << 3) #define GLXIIC_SMB_STS_NMATCH_BIT (1 << 2) #define GLXIIC_SMB_STS_MASTER_BIT (1 << 1) #define GLXIIC_SMB_STS_XMIT_BIT (1 << 0) #define GLXIIC_SMB_CTRL_STS 0x02 #define GLXIIC_SMB_CTRL_STS_TGSCL_BIT (1 << 5) #define GLXIIC_SMB_CTRL_STS_TSDA_BIT (1 << 4) #define GLXIIC_SMB_CTRL_STS_GCMTCH_BIT (1 << 3) #define GLXIIC_SMB_CTRL_STS_MATCH_BIT (1 << 2) #define GLXIIC_SMB_CTRL_STS_BB_BIT (1 << 1) #define GLXIIC_SMB_CTRL_STS_BUSY_BIT (1 << 0) #define GLXIIC_SMB_CTRL1 0x03 #define GLXIIC_SMB_CTRL1_STASTRE_BIT (1 << 7) #define GLXIIC_SMB_CTRL1_NMINTE_BIT (1 << 6) #define GLXIIC_SMB_CTRL1_GCMEN_BIT (1 << 5) #define GLXIIC_SMB_CTRL1_ACK_BIT (1 << 4) #define GLXIIC_SMB_CTRL1_INTEN_BIT (1 << 2) #define GLXIIC_SMB_CTRL1_STOP_BIT (1 << 1) #define GLXIIC_SMB_CTRL1_START_BIT (1 << 0) #define GLXIIC_SMB_ADDR 0x04 #define GLXIIC_SMB_ADDR_SAEN_BIT (1 << 7) #define GLXIIC_SMB_CTRL2 0x05 #define GLXIIC_SMB_CTRL2_EN_BIT (1 << 0) #define GLXIIC_SMB_CTRL3 0x06 typedef enum { GLXIIC_STATE_IDLE, GLXIIC_STATE_SLAVE_TX, GLXIIC_STATE_SLAVE_RX, GLXIIC_STATE_MASTER_ADDR, GLXIIC_STATE_MASTER_TX, GLXIIC_STATE_MASTER_RX, GLXIIC_STATE_MASTER_STOP, GLXIIC_STATE_MAX, } glxiic_state_t; struct glxiic_softc { device_t dev; /* Myself. */ device_t iicbus; /* IIC bus. */ struct mtx mtx; /* Lock. */ glxiic_state_t state; /* Driver state. */ struct callout callout; /* Driver state timeout callout. */ int timeout; /* Driver state timeout (ms). */ int smb_rid; /* SMB controller resource ID. */ struct resource *smb_res; /* SMB controller resource. */ int gpio_rid; /* GPIO resource ID. */ struct resource *gpio_res; /* GPIO resource. */ int irq_rid; /* IRQ resource ID. */ struct resource *irq_res; /* IRQ resource. */ void *irq_handler; /* IRQ handler cookie. */ int old_irq; /* IRQ mapped by board firmware. */ struct iic_msg *msg; /* Current master mode message. */ uint32_t nmsgs; /* Number of messages remaining. */ uint8_t *data; /* Current master mode data byte. */ uint16_t ndata; /* Number of data bytes remaining. */ int error; /* Last master mode error. */ uint8_t addr; /* Own address. */ uint16_t sclfrq; /* Bus frequency. */ }; #ifdef GLXIIC_DEBUG #define GLXIIC_DEBUG_LOG(fmt, args...) \ log(LOG_DEBUG, "%s: " fmt "\n" , __func__ , ## args) #else #define GLXIIC_DEBUG_LOG(fmt, args...) #endif #define GLXIIC_SCLFRQ(n) ((n << 1)) #define GLXIIC_SMBADDR(n) ((n >> 1)) #define GLXIIC_SMB_IRQ_TO_MAP(n) ((n << 16)) #define GLXIIC_MAP_TO_SMB_IRQ(n) ((n >> 16) & 0xf) #define GLXIIC_LOCK(_sc) mtx_lock(&_sc->mtx) #define GLXIIC_UNLOCK(_sc) mtx_unlock(&_sc->mtx) #define GLXIIC_LOCK_INIT(_sc) \ mtx_init(&_sc->mtx, device_get_nameunit(_sc->dev), "glxiic", MTX_DEF) #define GLXIIC_SLEEP(_sc) \ mtx_sleep(_sc, &_sc->mtx, IICPRI, "glxiic", 0) #define GLXIIC_WAKEUP(_sc) wakeup(_sc); #define GLXIIC_LOCK_DESTROY(_sc) mtx_destroy(&_sc->mtx); #define GLXIIC_ASSERT_LOCKED(_sc) mtx_assert(&_sc->mtx, MA_OWNED); typedef int (glxiic_state_callback_t)(struct glxiic_softc *sc, uint8_t status); static glxiic_state_callback_t glxiic_state_idle_callback; static glxiic_state_callback_t glxiic_state_slave_tx_callback; static glxiic_state_callback_t glxiic_state_slave_rx_callback; static glxiic_state_callback_t glxiic_state_master_addr_callback; static glxiic_state_callback_t glxiic_state_master_tx_callback; static glxiic_state_callback_t glxiic_state_master_rx_callback; static glxiic_state_callback_t glxiic_state_master_stop_callback; struct glxiic_state_table_entry { glxiic_state_callback_t *callback; boolean_t master; }; typedef struct glxiic_state_table_entry glxiic_state_table_entry_t; static glxiic_state_table_entry_t glxiic_state_table[GLXIIC_STATE_MAX] = { [GLXIIC_STATE_IDLE] = { .callback = &glxiic_state_idle_callback, .master = FALSE, }, [GLXIIC_STATE_SLAVE_TX] = { .callback = &glxiic_state_slave_tx_callback, .master = FALSE, }, [GLXIIC_STATE_SLAVE_RX] = { .callback = &glxiic_state_slave_rx_callback, .master = FALSE, }, [GLXIIC_STATE_MASTER_ADDR] = { .callback = &glxiic_state_master_addr_callback, .master = TRUE, }, [GLXIIC_STATE_MASTER_TX] = { .callback = &glxiic_state_master_tx_callback, .master = TRUE, }, [GLXIIC_STATE_MASTER_RX] = { .callback = &glxiic_state_master_rx_callback, .master = TRUE, }, [GLXIIC_STATE_MASTER_STOP] = { .callback = &glxiic_state_master_stop_callback, .master = TRUE, }, }; static void glxiic_identify(driver_t *driver, device_t parent); static int glxiic_probe(device_t dev); static int glxiic_attach(device_t dev); static int glxiic_detach(device_t dev); static uint8_t glxiic_read_status_locked(struct glxiic_softc *sc); static void glxiic_stop_locked(struct glxiic_softc *sc); static void glxiic_timeout(void *arg); static void glxiic_start_timeout_locked(struct glxiic_softc *sc); static void glxiic_set_state_locked(struct glxiic_softc *sc, glxiic_state_t state); static int glxiic_handle_slave_match_locked(struct glxiic_softc *sc, uint8_t status); static void glxiic_intr(void *arg); static int glxiic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr); static int glxiic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs); static void glxiic_smb_map_interrupt(int irq); static void glxiic_gpio_enable(struct glxiic_softc *sc); static void glxiic_gpio_disable(struct glxiic_softc *sc); static void glxiic_smb_enable(struct glxiic_softc *sc, uint8_t speed, uint8_t addr); static void glxiic_smb_disable(struct glxiic_softc *sc); static device_method_t glxiic_methods[] = { DEVMETHOD(device_identify, glxiic_identify), DEVMETHOD(device_probe, glxiic_probe), DEVMETHOD(device_attach, glxiic_attach), DEVMETHOD(device_detach, glxiic_detach), DEVMETHOD(iicbus_reset, glxiic_reset), DEVMETHOD(iicbus_transfer, glxiic_transfer), DEVMETHOD(iicbus_callback, iicbus_null_callback), { 0, 0 } }; static driver_t glxiic_driver = { "glxiic", glxiic_methods, sizeof(struct glxiic_softc), }; DRIVER_MODULE(glxiic, isab, glxiic_driver, 0, 0); DRIVER_MODULE(iicbus, glxiic, iicbus_driver, 0, 0); MODULE_DEPEND(glxiic, iicbus, 1, 1, 1); static void glxiic_identify(driver_t *driver, device_t parent) { /* Prevent child from being added more than once. */ - if (device_find_child(parent, driver->name, -1) != NULL) + if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL) return; if (pci_get_devid(parent) == GLXIIC_CS5536_DEV_ID) { - if (device_add_child(parent, driver->name, -1) == NULL) + if (device_add_child(parent, driver->name, DEVICE_UNIT_ANY) == NULL) device_printf(parent, "Could not add glxiic child\n"); } } static int glxiic_probe(device_t dev) { if (resource_disabled("glxiic", device_get_unit(dev))) return (ENXIO); device_set_desc(dev, "AMD Geode CS5536 SMBus controller"); return (BUS_PROBE_DEFAULT); } static int glxiic_attach(device_t dev) { struct glxiic_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; int error, irq, unit; uint32_t irq_map; sc = device_get_softc(dev); sc->dev = dev; sc->state = GLXIIC_STATE_IDLE; error = 0; GLXIIC_LOCK_INIT(sc); callout_init_mtx(&sc->callout, &sc->mtx, 0); sc->smb_rid = PCIR_BAR(0); sc->smb_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->smb_rid, RF_ACTIVE); if (sc->smb_res == NULL) { device_printf(dev, "Could not allocate SMBus I/O port\n"); error = ENXIO; goto out; } sc->gpio_rid = PCIR_BAR(1); sc->gpio_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->gpio_rid, RF_SHAREABLE | RF_ACTIVE); if (sc->gpio_res == NULL) { device_printf(dev, "Could not allocate GPIO I/O port\n"); error = ENXIO; goto out; } /* Ensure the controller is not enabled by firmware. */ glxiic_smb_disable(sc); /* Read the existing IRQ map. */ irq_map = rdmsr(GLXIIC_MSR_PIC_YSEL_HIGH); sc->old_irq = GLXIIC_MAP_TO_SMB_IRQ(irq_map); unit = device_get_unit(dev); if (resource_int_value("glxiic", unit, "irq", &irq) == 0) { if (irq < 1 || irq > 15) { device_printf(dev, "Bad value %d for glxiic.%d.irq\n", irq, unit); error = ENXIO; goto out; } if (bootverbose) device_printf(dev, "Using irq %d set by hint\n", irq); } else if (sc->old_irq != 0) { if (bootverbose) device_printf(dev, "Using irq %d set by firmware\n", irq); irq = sc->old_irq; } else { device_printf(dev, "No irq mapped by firmware"); printf(" and no glxiic.%d.irq hint provided\n", unit); error = ENXIO; goto out; } /* Map the SMBus interrupt to the requested legacy IRQ. */ glxiic_smb_map_interrupt(irq); sc->irq_rid = 0; sc->irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irq_rid, irq, irq, 1, RF_SHAREABLE | RF_ACTIVE); if (sc->irq_res == NULL) { device_printf(dev, "Could not allocate IRQ %d\n", irq); error = ENXIO; goto out; } error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, glxiic_intr, sc, &(sc->irq_handler)); if (error != 0) { device_printf(dev, "Could not setup IRQ handler\n"); error = ENXIO; goto out; } - if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL) { + if ((sc->iicbus = device_add_child(dev, "iicbus", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "Could not allocate iicbus instance\n"); error = ENXIO; goto out; } ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); sc->timeout = GLXIIC_DEFAULT_TIMEOUT; SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "timeout", CTLFLAG_RWTUN, &sc->timeout, 0, "activity timeout in ms"); glxiic_gpio_enable(sc); glxiic_smb_enable(sc, IIC_FASTEST, 0); /* Probe and attach the iicbus when interrupts are available. */ bus_delayed_attach_children(dev); out: if (error != 0) { callout_drain(&sc->callout); if (sc->iicbus != NULL) device_delete_child(dev, sc->iicbus); if (sc->smb_res != NULL) { glxiic_smb_disable(sc); bus_release_resource(dev, SYS_RES_IOPORT, sc->smb_rid, sc->smb_res); } if (sc->gpio_res != NULL) { glxiic_gpio_disable(sc); bus_release_resource(dev, SYS_RES_IOPORT, sc->gpio_rid, sc->gpio_res); } if (sc->irq_handler != NULL) bus_teardown_intr(dev, sc->irq_res, sc->irq_handler); if (sc->irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); /* Restore the old SMBus interrupt mapping. */ glxiic_smb_map_interrupt(sc->old_irq); GLXIIC_LOCK_DESTROY(sc); } return (error); } static int glxiic_detach(device_t dev) { struct glxiic_softc *sc; int error; sc = device_get_softc(dev); error = bus_generic_detach(dev); if (error != 0) return (error); callout_drain(&sc->callout); if (sc->smb_res != NULL) { glxiic_smb_disable(sc); bus_release_resource(dev, SYS_RES_IOPORT, sc->smb_rid, sc->smb_res); } if (sc->gpio_res != NULL) { glxiic_gpio_disable(sc); bus_release_resource(dev, SYS_RES_IOPORT, sc->gpio_rid, sc->gpio_res); } if (sc->irq_handler != NULL) bus_teardown_intr(dev, sc->irq_res, sc->irq_handler); if (sc->irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); /* Restore the old SMBus interrupt mapping. */ glxiic_smb_map_interrupt(sc->old_irq); GLXIIC_LOCK_DESTROY(sc); return (0); } static uint8_t glxiic_read_status_locked(struct glxiic_softc *sc) { uint8_t status; GLXIIC_ASSERT_LOCKED(sc); status = bus_read_1(sc->smb_res, GLXIIC_SMB_STS); /* Clear all status flags except SDAST and STASTR after reading. */ bus_write_1(sc->smb_res, GLXIIC_SMB_STS, (GLXIIC_SMB_STS_SLVSTP_BIT | GLXIIC_SMB_STS_BER_BIT | GLXIIC_SMB_STS_NEGACK_BIT | GLXIIC_SMB_STS_NMATCH_BIT)); return (status); } static void glxiic_stop_locked(struct glxiic_softc *sc) { uint8_t status, ctrl1; GLXIIC_ASSERT_LOCKED(sc); status = glxiic_read_status_locked(sc); ctrl1 = bus_read_1(sc->smb_res, GLXIIC_SMB_CTRL1); bus_write_1(sc->smb_res, GLXIIC_SMB_CTRL1, ctrl1 | GLXIIC_SMB_CTRL1_STOP_BIT); /* * Perform a dummy read of SDA in master receive mode to clear * SDAST if set. */ if ((status & GLXIIC_SMB_STS_XMIT_BIT) == 0 && (status & GLXIIC_SMB_STS_SDAST_BIT) != 0) bus_read_1(sc->smb_res, GLXIIC_SMB_SDA); /* Check stall after start bit and clear if needed */ if ((status & GLXIIC_SMB_STS_STASTR_BIT) != 0) { bus_write_1(sc->smb_res, GLXIIC_SMB_STS, GLXIIC_SMB_STS_STASTR_BIT); } } static void glxiic_timeout(void *arg) { struct glxiic_softc *sc; uint8_t error; sc = (struct glxiic_softc *)arg; GLXIIC_DEBUG_LOG("timeout in state %d", sc->state); if (glxiic_state_table[sc->state].master) { sc->error = IIC_ETIMEOUT; GLXIIC_WAKEUP(sc); } else { error = IIC_ETIMEOUT; iicbus_intr(sc->iicbus, INTR_ERROR, &error); } glxiic_smb_disable(sc); glxiic_smb_enable(sc, IIC_UNKNOWN, sc->addr); glxiic_set_state_locked(sc, GLXIIC_STATE_IDLE); } static void glxiic_start_timeout_locked(struct glxiic_softc *sc) { GLXIIC_ASSERT_LOCKED(sc); callout_reset_sbt(&sc->callout, SBT_1MS * sc->timeout, 0, glxiic_timeout, sc, 0); } static void glxiic_set_state_locked(struct glxiic_softc *sc, glxiic_state_t state) { GLXIIC_ASSERT_LOCKED(sc); if (state == GLXIIC_STATE_IDLE) callout_stop(&sc->callout); else if (sc->timeout > 0) glxiic_start_timeout_locked(sc); sc->state = state; } static int glxiic_handle_slave_match_locked(struct glxiic_softc *sc, uint8_t status) { uint8_t ctrl_sts, addr; GLXIIC_ASSERT_LOCKED(sc); ctrl_sts = bus_read_1(sc->smb_res, GLXIIC_SMB_CTRL_STS); if ((ctrl_sts & GLXIIC_SMB_CTRL_STS_MATCH_BIT) != 0) { if ((status & GLXIIC_SMB_STS_XMIT_BIT) != 0) { addr = sc->addr | LSB; glxiic_set_state_locked(sc, GLXIIC_STATE_SLAVE_TX); } else { addr = sc->addr & ~LSB; glxiic_set_state_locked(sc, GLXIIC_STATE_SLAVE_RX); } iicbus_intr(sc->iicbus, INTR_START, &addr); } else if ((ctrl_sts & GLXIIC_SMB_CTRL_STS_GCMTCH_BIT) != 0) { addr = 0; glxiic_set_state_locked(sc, GLXIIC_STATE_SLAVE_RX); iicbus_intr(sc->iicbus, INTR_GENERAL, &addr); } else { GLXIIC_DEBUG_LOG("unknown slave match"); return (IIC_ESTATUS); } return (IIC_NOERR); } static int glxiic_state_idle_callback(struct glxiic_softc *sc, uint8_t status) { GLXIIC_ASSERT_LOCKED(sc); if ((status & GLXIIC_SMB_STS_BER_BIT) != 0) { GLXIIC_DEBUG_LOG("bus error in idle"); return (IIC_EBUSERR); } if ((status & GLXIIC_SMB_STS_NMATCH_BIT) != 0) { return (glxiic_handle_slave_match_locked(sc, status)); } return (IIC_NOERR); } static int glxiic_state_slave_tx_callback(struct glxiic_softc *sc, uint8_t status) { uint8_t data; GLXIIC_ASSERT_LOCKED(sc); if ((status & GLXIIC_SMB_STS_BER_BIT) != 0) { GLXIIC_DEBUG_LOG("bus error in slave tx"); return (IIC_EBUSERR); } if ((status & GLXIIC_SMB_STS_SLVSTP_BIT) != 0) { iicbus_intr(sc->iicbus, INTR_STOP, NULL); glxiic_set_state_locked(sc, GLXIIC_STATE_IDLE); return (IIC_NOERR); } if ((status & GLXIIC_SMB_STS_NEGACK_BIT) != 0) { iicbus_intr(sc->iicbus, INTR_NOACK, NULL); return (IIC_NOERR); } if ((status & GLXIIC_SMB_STS_NMATCH_BIT) != 0) { /* Handle repeated start in slave mode. */ return (glxiic_handle_slave_match_locked(sc, status)); } if ((status & GLXIIC_SMB_STS_SDAST_BIT) == 0) { GLXIIC_DEBUG_LOG("not awaiting data in slave tx"); return (IIC_ESTATUS); } iicbus_intr(sc->iicbus, INTR_TRANSMIT, &data); bus_write_1(sc->smb_res, GLXIIC_SMB_SDA, data); glxiic_start_timeout_locked(sc); return (IIC_NOERR); } static int glxiic_state_slave_rx_callback(struct glxiic_softc *sc, uint8_t status) { uint8_t data; GLXIIC_ASSERT_LOCKED(sc); if ((status & GLXIIC_SMB_STS_BER_BIT) != 0) { GLXIIC_DEBUG_LOG("bus error in slave rx"); return (IIC_EBUSERR); } if ((status & GLXIIC_SMB_STS_SLVSTP_BIT) != 0) { iicbus_intr(sc->iicbus, INTR_STOP, NULL); glxiic_set_state_locked(sc, GLXIIC_STATE_IDLE); return (IIC_NOERR); } if ((status & GLXIIC_SMB_STS_NMATCH_BIT) != 0) { /* Handle repeated start in slave mode. */ return (glxiic_handle_slave_match_locked(sc, status)); } if ((status & GLXIIC_SMB_STS_SDAST_BIT) == 0) { GLXIIC_DEBUG_LOG("no pending data in slave rx"); return (IIC_ESTATUS); } data = bus_read_1(sc->smb_res, GLXIIC_SMB_SDA); iicbus_intr(sc->iicbus, INTR_RECEIVE, &data); glxiic_start_timeout_locked(sc); return (IIC_NOERR); } static int glxiic_state_master_addr_callback(struct glxiic_softc *sc, uint8_t status) { uint8_t slave; uint8_t ctrl1; GLXIIC_ASSERT_LOCKED(sc); if ((status & GLXIIC_SMB_STS_BER_BIT) != 0) { GLXIIC_DEBUG_LOG("bus error after master start"); return (IIC_EBUSERR); } if ((status & GLXIIC_SMB_STS_MASTER_BIT) == 0) { GLXIIC_DEBUG_LOG("not bus master after master start"); return (IIC_ESTATUS); } if ((status & GLXIIC_SMB_STS_SDAST_BIT) == 0) { GLXIIC_DEBUG_LOG("not awaiting address in master addr"); return (IIC_ESTATUS); } if ((sc->msg->flags & IIC_M_RD) != 0) { slave = sc->msg->slave | LSB; glxiic_set_state_locked(sc, GLXIIC_STATE_MASTER_RX); } else { slave = sc->msg->slave & ~LSB; glxiic_set_state_locked(sc, GLXIIC_STATE_MASTER_TX); } sc->data = sc->msg->buf; sc->ndata = sc->msg->len; /* Handle address-only transfer. */ if (sc->ndata == 0) glxiic_set_state_locked(sc, GLXIIC_STATE_MASTER_STOP); bus_write_1(sc->smb_res, GLXIIC_SMB_SDA, slave); if ((sc->msg->flags & IIC_M_RD) != 0 && sc->ndata == 1) { /* Last byte from slave, set NACK. */ ctrl1 = bus_read_1(sc->smb_res, GLXIIC_SMB_CTRL1); bus_write_1(sc->smb_res, GLXIIC_SMB_CTRL1, ctrl1 | GLXIIC_SMB_CTRL1_ACK_BIT); } return (IIC_NOERR); } static int glxiic_state_master_tx_callback(struct glxiic_softc *sc, uint8_t status) { GLXIIC_ASSERT_LOCKED(sc); if ((status & GLXIIC_SMB_STS_BER_BIT) != 0) { GLXIIC_DEBUG_LOG("bus error in master tx"); return (IIC_EBUSERR); } if ((status & GLXIIC_SMB_STS_MASTER_BIT) == 0) { GLXIIC_DEBUG_LOG("not bus master in master tx"); return (IIC_ESTATUS); } if ((status & GLXIIC_SMB_STS_NEGACK_BIT) != 0) { GLXIIC_DEBUG_LOG("slave nack in master tx"); return (IIC_ENOACK); } if ((status & GLXIIC_SMB_STS_STASTR_BIT) != 0) { bus_write_1(sc->smb_res, GLXIIC_SMB_STS, GLXIIC_SMB_STS_STASTR_BIT); } if ((status & GLXIIC_SMB_STS_SDAST_BIT) == 0) { GLXIIC_DEBUG_LOG("not awaiting data in master tx"); return (IIC_ESTATUS); } bus_write_1(sc->smb_res, GLXIIC_SMB_SDA, *sc->data++); if (--sc->ndata == 0) glxiic_set_state_locked(sc, GLXIIC_STATE_MASTER_STOP); else glxiic_start_timeout_locked(sc); return (IIC_NOERR); } static int glxiic_state_master_rx_callback(struct glxiic_softc *sc, uint8_t status) { uint8_t ctrl1; GLXIIC_ASSERT_LOCKED(sc); if ((status & GLXIIC_SMB_STS_BER_BIT) != 0) { GLXIIC_DEBUG_LOG("bus error in master rx"); return (IIC_EBUSERR); } if ((status & GLXIIC_SMB_STS_MASTER_BIT) == 0) { GLXIIC_DEBUG_LOG("not bus master in master rx"); return (IIC_ESTATUS); } if ((status & GLXIIC_SMB_STS_NEGACK_BIT) != 0) { GLXIIC_DEBUG_LOG("slave nack in rx"); return (IIC_ENOACK); } if ((status & GLXIIC_SMB_STS_STASTR_BIT) != 0) { /* Bus is stalled, clear and wait for data. */ bus_write_1(sc->smb_res, GLXIIC_SMB_STS, GLXIIC_SMB_STS_STASTR_BIT); return (IIC_NOERR); } if ((status & GLXIIC_SMB_STS_SDAST_BIT) == 0) { GLXIIC_DEBUG_LOG("no pending data in master rx"); return (IIC_ESTATUS); } *sc->data++ = bus_read_1(sc->smb_res, GLXIIC_SMB_SDA); if (--sc->ndata == 0) { /* Proceed with stop on reading last byte. */ glxiic_set_state_locked(sc, GLXIIC_STATE_MASTER_STOP); return (glxiic_state_table[sc->state].callback(sc, status)); } if (sc->ndata == 1) { /* Last byte from slave, set NACK. */ ctrl1 = bus_read_1(sc->smb_res, GLXIIC_SMB_CTRL1); bus_write_1(sc->smb_res, GLXIIC_SMB_CTRL1, ctrl1 | GLXIIC_SMB_CTRL1_ACK_BIT); } glxiic_start_timeout_locked(sc); return (IIC_NOERR); } static int glxiic_state_master_stop_callback(struct glxiic_softc *sc, uint8_t status) { uint8_t ctrl1; GLXIIC_ASSERT_LOCKED(sc); if ((status & GLXIIC_SMB_STS_BER_BIT) != 0) { GLXIIC_DEBUG_LOG("bus error in master stop"); return (IIC_EBUSERR); } if ((status & GLXIIC_SMB_STS_MASTER_BIT) == 0) { GLXIIC_DEBUG_LOG("not bus master in master stop"); return (IIC_ESTATUS); } if ((status & GLXIIC_SMB_STS_NEGACK_BIT) != 0) { GLXIIC_DEBUG_LOG("slave nack in master stop"); return (IIC_ENOACK); } if (--sc->nmsgs > 0) { /* Start transfer of next message. */ if ((sc->msg->flags & IIC_M_NOSTOP) == 0) { glxiic_stop_locked(sc); } ctrl1 = bus_read_1(sc->smb_res, GLXIIC_SMB_CTRL1); bus_write_1(sc->smb_res, GLXIIC_SMB_CTRL1, ctrl1 | GLXIIC_SMB_CTRL1_START_BIT); glxiic_set_state_locked(sc, GLXIIC_STATE_MASTER_ADDR); sc->msg++; } else { /* Last message. */ glxiic_stop_locked(sc); glxiic_set_state_locked(sc, GLXIIC_STATE_IDLE); sc->error = IIC_NOERR; GLXIIC_WAKEUP(sc); } return (IIC_NOERR); } static void glxiic_intr(void *arg) { struct glxiic_softc *sc; int error; uint8_t status, data; sc = (struct glxiic_softc *)arg; GLXIIC_LOCK(sc); status = glxiic_read_status_locked(sc); /* Check if this interrupt originated from the SMBus. */ if ((status & ~(GLXIIC_SMB_STS_MASTER_BIT | GLXIIC_SMB_STS_XMIT_BIT)) != 0) { error = glxiic_state_table[sc->state].callback(sc, status); if (error != IIC_NOERR) { if (glxiic_state_table[sc->state].master) { glxiic_stop_locked(sc); glxiic_set_state_locked(sc, GLXIIC_STATE_IDLE); sc->error = error; GLXIIC_WAKEUP(sc); } else { data = error & 0xff; iicbus_intr(sc->iicbus, INTR_ERROR, &data); glxiic_set_state_locked(sc, GLXIIC_STATE_IDLE); } } } GLXIIC_UNLOCK(sc); } static int glxiic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct glxiic_softc *sc; sc = device_get_softc(dev); GLXIIC_LOCK(sc); if (oldaddr != NULL) *oldaddr = sc->addr; sc->addr = addr; /* A disable/enable cycle resets the controller. */ glxiic_smb_disable(sc); glxiic_smb_enable(sc, speed, addr); if (glxiic_state_table[sc->state].master) { sc->error = IIC_ESTATUS; GLXIIC_WAKEUP(sc); } glxiic_set_state_locked(sc, GLXIIC_STATE_IDLE); GLXIIC_UNLOCK(sc); return (IIC_NOERR); } static int glxiic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct glxiic_softc *sc; int error; uint8_t ctrl1; sc = device_get_softc(dev); GLXIIC_LOCK(sc); if (sc->state != GLXIIC_STATE_IDLE) { error = IIC_EBUSBSY; goto out; } sc->msg = msgs; sc->nmsgs = nmsgs; glxiic_set_state_locked(sc, GLXIIC_STATE_MASTER_ADDR); /* Set start bit and let glxiic_intr() handle the transfer. */ ctrl1 = bus_read_1(sc->smb_res, GLXIIC_SMB_CTRL1); bus_write_1(sc->smb_res, GLXIIC_SMB_CTRL1, ctrl1 | GLXIIC_SMB_CTRL1_START_BIT); GLXIIC_SLEEP(sc); error = sc->error; out: GLXIIC_UNLOCK(sc); return (error); } static void glxiic_smb_map_interrupt(int irq) { uint32_t irq_map; int old_irq; /* Protect the read-modify-write operation. */ critical_enter(); irq_map = rdmsr(GLXIIC_MSR_PIC_YSEL_HIGH); old_irq = GLXIIC_MAP_TO_SMB_IRQ(irq_map); if (irq != old_irq) { irq_map &= ~GLXIIC_SMB_IRQ_TO_MAP(old_irq); irq_map |= GLXIIC_SMB_IRQ_TO_MAP(irq); wrmsr(GLXIIC_MSR_PIC_YSEL_HIGH, irq_map); } critical_exit(); } static void glxiic_gpio_enable(struct glxiic_softc *sc) { bus_write_4(sc->gpio_res, GLXIIC_GPIOL_IN_AUX1_SEL, GLXIIC_GPIO_14_15_ENABLE); bus_write_4(sc->gpio_res, GLXIIC_GPIOL_OUT_AUX1_SEL, GLXIIC_GPIO_14_15_ENABLE); } static void glxiic_gpio_disable(struct glxiic_softc *sc) { bus_write_4(sc->gpio_res, GLXIIC_GPIOL_OUT_AUX1_SEL, GLXIIC_GPIO_14_15_DISABLE); bus_write_4(sc->gpio_res, GLXIIC_GPIOL_IN_AUX1_SEL, GLXIIC_GPIO_14_15_DISABLE); } static void glxiic_smb_enable(struct glxiic_softc *sc, uint8_t speed, uint8_t addr) { uint8_t ctrl1; ctrl1 = 0; switch (speed) { case IIC_SLOW: sc->sclfrq = GLXIIC_SLOW; break; case IIC_FAST: sc->sclfrq = GLXIIC_FAST; break; case IIC_FASTEST: sc->sclfrq = GLXIIC_FASTEST; break; case IIC_UNKNOWN: default: /* Reuse last frequency. */ break; } /* Set bus speed and enable controller. */ bus_write_2(sc->smb_res, GLXIIC_SMB_CTRL2, GLXIIC_SCLFRQ(sc->sclfrq) | GLXIIC_SMB_CTRL2_EN_BIT); if (addr != 0) { /* Enable new match and global call match interrupts. */ ctrl1 |= GLXIIC_SMB_CTRL1_NMINTE_BIT | GLXIIC_SMB_CTRL1_GCMEN_BIT; bus_write_1(sc->smb_res, GLXIIC_SMB_ADDR, GLXIIC_SMB_ADDR_SAEN_BIT | GLXIIC_SMBADDR(addr)); } else { bus_write_1(sc->smb_res, GLXIIC_SMB_ADDR, 0); } /* Enable stall after start and interrupt. */ bus_write_1(sc->smb_res, GLXIIC_SMB_CTRL1, ctrl1 | GLXIIC_SMB_CTRL1_STASTRE_BIT | GLXIIC_SMB_CTRL1_INTEN_BIT); } static void glxiic_smb_disable(struct glxiic_softc *sc) { uint16_t sclfrq; sclfrq = bus_read_2(sc->smb_res, GLXIIC_SMB_CTRL2); bus_write_2(sc->smb_res, GLXIIC_SMB_CTRL2, sclfrq & ~GLXIIC_SMB_CTRL2_EN_BIT); } diff --git a/sys/dev/gpio/gpiobus.c b/sys/dev/gpio/gpiobus.c index e053adacf457..2e2618805e7b 100644 --- a/sys/dev/gpio/gpiobus.c +++ b/sys/dev/gpio/gpiobus.c @@ -1,1070 +1,1070 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Oleksandr Tymoshenko * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #ifdef INTRNG #include #endif #include #include #include #include #include #include "gpiobus_if.h" #undef GPIOBUS_DEBUG #ifdef GPIOBUS_DEBUG #define dprintf printf #else #define dprintf(x, arg...) #endif static void gpiobus_print_pins(struct gpiobus_ivar *, struct sbuf *); static int gpiobus_parse_pins(struct gpiobus_softc *, device_t, int); static int gpiobus_probe(device_t); static int gpiobus_suspend(device_t); static int gpiobus_resume(device_t); static void gpiobus_probe_nomatch(device_t, device_t); static int gpiobus_print_child(device_t, device_t); static int gpiobus_child_location(device_t, device_t, struct sbuf *); static device_t gpiobus_add_child(device_t, u_int, const char *, int); static void gpiobus_hinted_child(device_t, const char *, int); /* * GPIOBUS interface */ static int gpiobus_acquire_bus(device_t, device_t, int); static void gpiobus_release_bus(device_t, device_t); static int gpiobus_pin_setflags(device_t, device_t, uint32_t, uint32_t); static int gpiobus_pin_getflags(device_t, device_t, uint32_t, uint32_t*); static int gpiobus_pin_getcaps(device_t, device_t, uint32_t, uint32_t*); static int gpiobus_pin_set(device_t, device_t, uint32_t, unsigned int); static int gpiobus_pin_get(device_t, device_t, uint32_t, unsigned int*); static int gpiobus_pin_toggle(device_t, device_t, uint32_t); /* * gpiobus_pin flags * The flags in struct gpiobus_pin are not related to the flags used by the * low-level controller driver in struct gpio_pin. Currently, only pins * acquired via FDT data have gpiobus_pin.flags set, sourced from the flags in * the FDT properties. In theory, these flags are defined per-platform. In * practice they are always the flags from the dt-bindings/gpio/gpio.h file. * The only one of those flags we currently support is for handling active-low * pins, so we just define that flag here instead of including a GPL'd header. */ #define GPIO_ACTIVE_LOW 1 /* * XXX -> Move me to better place - gpio_subr.c? * Also, this function must be changed when interrupt configuration * data will be moved into struct resource. */ #ifdef INTRNG struct resource * gpio_alloc_intr_resource(device_t consumer_dev, int *rid, u_int alloc_flags, gpio_pin_t pin, uint32_t intr_mode) { u_int irq; struct intr_map_data_gpio *gpio_data; struct resource *res; gpio_data = (struct intr_map_data_gpio *)intr_alloc_map_data( INTR_MAP_DATA_GPIO, sizeof(*gpio_data), M_WAITOK | M_ZERO); gpio_data->gpio_pin_num = pin->pin; gpio_data->gpio_pin_flags = pin->flags; gpio_data->gpio_intr_mode = intr_mode; irq = intr_map_irq(pin->dev, 0, (struct intr_map_data *)gpio_data); res = bus_alloc_resource(consumer_dev, SYS_RES_IRQ, rid, irq, irq, 1, alloc_flags); if (res == NULL) { intr_free_intr_map_data((struct intr_map_data *)gpio_data); return (NULL); } rman_set_virtual(res, gpio_data); return (res); } #else struct resource * gpio_alloc_intr_resource(device_t consumer_dev, int *rid, u_int alloc_flags, gpio_pin_t pin, uint32_t intr_mode) { return (NULL); } #endif int gpio_check_flags(uint32_t caps, uint32_t flags) { /* Filter unwanted flags. */ flags &= caps; /* Cannot mix input/output together. */ if (flags & GPIO_PIN_INPUT && flags & GPIO_PIN_OUTPUT) return (EINVAL); /* Cannot mix pull-up/pull-down together. */ if (flags & GPIO_PIN_PULLUP && flags & GPIO_PIN_PULLDOWN) return (EINVAL); /* Cannot mix output and interrupt flags together */ if (flags & GPIO_PIN_OUTPUT && flags & GPIO_INTR_MASK) return (EINVAL); /* Only one interrupt flag can be defined at once */ if ((flags & GPIO_INTR_MASK) & ((flags & GPIO_INTR_MASK) - 1)) return (EINVAL); /* The interrupt attached flag cannot be set */ if (flags & GPIO_INTR_ATTACHED) return (EINVAL); return (0); } int gpio_pin_get_by_bus_pinnum(device_t busdev, uint32_t pinnum, gpio_pin_t *ppin) { gpio_pin_t pin; int err; err = gpiobus_acquire_pin(busdev, pinnum); if (err != 0) return (EBUSY); pin = malloc(sizeof(*pin), M_DEVBUF, M_WAITOK | M_ZERO); pin->dev = device_get_parent(busdev); pin->pin = pinnum; pin->flags = 0; *ppin = pin; return (0); } int gpio_pin_get_by_child_index(device_t childdev, uint32_t idx, gpio_pin_t *ppin) { struct gpiobus_ivar *devi; devi = GPIOBUS_IVAR(childdev); if (idx >= devi->npins) return (EINVAL); return (gpio_pin_get_by_bus_pinnum(device_get_parent(childdev), devi->pins[idx], ppin)); } int gpio_pin_getcaps(gpio_pin_t pin, uint32_t *caps) { KASSERT(pin != NULL, ("GPIO pin is NULL.")); KASSERT(pin->dev != NULL, ("GPIO pin device is NULL.")); return (GPIO_PIN_GETCAPS(pin->dev, pin->pin, caps)); } int gpio_pin_is_active(gpio_pin_t pin, bool *active) { int rv; uint32_t tmp; KASSERT(pin != NULL, ("GPIO pin is NULL.")); KASSERT(pin->dev != NULL, ("GPIO pin device is NULL.")); rv = GPIO_PIN_GET(pin->dev, pin->pin, &tmp); if (rv != 0) { return (rv); } if (pin->flags & GPIO_ACTIVE_LOW) *active = tmp == 0; else *active = tmp != 0; return (0); } void gpio_pin_release(gpio_pin_t gpio) { device_t busdev; if (gpio == NULL) return; KASSERT(gpio->dev != NULL, ("GPIO pin device is NULL.")); busdev = GPIO_GET_BUS(gpio->dev); if (busdev != NULL) gpiobus_release_pin(busdev, gpio->pin); free(gpio, M_DEVBUF); } int gpio_pin_set_active(gpio_pin_t pin, bool active) { int rv; uint32_t tmp; if (pin->flags & GPIO_ACTIVE_LOW) tmp = active ? 0 : 1; else tmp = active ? 1 : 0; KASSERT(pin != NULL, ("GPIO pin is NULL.")); KASSERT(pin->dev != NULL, ("GPIO pin device is NULL.")); rv = GPIO_PIN_SET(pin->dev, pin->pin, tmp); return (rv); } int gpio_pin_setflags(gpio_pin_t pin, uint32_t flags) { int rv; KASSERT(pin != NULL, ("GPIO pin is NULL.")); KASSERT(pin->dev != NULL, ("GPIO pin device is NULL.")); rv = GPIO_PIN_SETFLAGS(pin->dev, pin->pin, flags); return (rv); } static void gpiobus_print_pins(struct gpiobus_ivar *devi, struct sbuf *sb) { int i, range_start, range_stop, need_coma; if (devi->npins == 0) return; need_coma = 0; range_start = range_stop = devi->pins[0]; for (i = 1; i < devi->npins; i++) { if (devi->pins[i] != (range_stop + 1)) { if (need_coma) sbuf_cat(sb, ","); if (range_start != range_stop) sbuf_printf(sb, "%d-%d", range_start, range_stop); else sbuf_printf(sb, "%d", range_start); range_start = range_stop = devi->pins[i]; need_coma = 1; } else range_stop++; } if (need_coma) sbuf_cat(sb, ","); if (range_start != range_stop) sbuf_printf(sb, "%d-%d", range_start, range_stop); else sbuf_printf(sb, "%d", range_start); } device_t gpiobus_attach_bus(device_t dev) { device_t busdev; busdev = device_add_child(dev, "gpiobus", DEVICE_UNIT_ANY); if (busdev == NULL) return (NULL); - if (device_add_child(dev, "gpioc", -1) == NULL) { + if (device_add_child(dev, "gpioc", DEVICE_UNIT_ANY) == NULL) { device_delete_child(dev, busdev); return (NULL); } #ifdef FDT ofw_gpiobus_register_provider(dev); #endif bus_attach_children(dev); return (busdev); } int gpiobus_detach_bus(device_t dev) { #ifdef FDT ofw_gpiobus_unregister_provider(dev); #endif return (bus_generic_detach(dev)); } int gpiobus_init_softc(device_t dev) { struct gpiobus_softc *sc; sc = GPIOBUS_SOFTC(dev); sc->sc_busdev = dev; sc->sc_dev = device_get_parent(dev); sc->sc_intr_rman.rm_type = RMAN_ARRAY; sc->sc_intr_rman.rm_descr = "GPIO Interrupts"; if (rman_init(&sc->sc_intr_rman) != 0 || rman_manage_region(&sc->sc_intr_rman, 0, ~0) != 0) panic("%s: failed to set up rman.", __func__); if (GPIO_PIN_MAX(sc->sc_dev, &sc->sc_npins) != 0) return (ENXIO); KASSERT(sc->sc_npins >= 0, ("GPIO device with no pins")); /* Pins = GPIO_PIN_MAX() + 1 */ sc->sc_npins++; sc->sc_pins = malloc(sizeof(*sc->sc_pins) * sc->sc_npins, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_pins == NULL) return (ENOMEM); /* Initialize the bus lock. */ GPIOBUS_LOCK_INIT(sc); return (0); } int gpiobus_alloc_ivars(struct gpiobus_ivar *devi) { /* Allocate pins and flags memory. */ devi->pins = malloc(sizeof(uint32_t) * devi->npins, M_DEVBUF, M_NOWAIT | M_ZERO); if (devi->pins == NULL) return (ENOMEM); return (0); } void gpiobus_free_ivars(struct gpiobus_ivar *devi) { if (devi->pins) { free(devi->pins, M_DEVBUF); devi->pins = NULL; } devi->npins = 0; } int gpiobus_acquire_pin(device_t bus, uint32_t pin) { struct gpiobus_softc *sc; sc = device_get_softc(bus); /* Consistency check. */ if (pin >= sc->sc_npins) { device_printf(bus, "invalid pin %d, max: %d\n", pin, sc->sc_npins - 1); return (-1); } /* Mark pin as mapped and give warning if it's already mapped. */ if (sc->sc_pins[pin].mapped) { device_printf(bus, "warning: pin %d is already mapped\n", pin); return (-1); } sc->sc_pins[pin].mapped = 1; return (0); } /* Release mapped pin */ int gpiobus_release_pin(device_t bus, uint32_t pin) { struct gpiobus_softc *sc; sc = device_get_softc(bus); /* Consistency check. */ if (pin >= sc->sc_npins) { device_printf(bus, "invalid pin %d, max=%d\n", pin, sc->sc_npins - 1); return (-1); } if (!sc->sc_pins[pin].mapped) { device_printf(bus, "pin %d is not mapped\n", pin); return (-1); } sc->sc_pins[pin].mapped = 0; return (0); } static int gpiobus_acquire_child_pins(device_t dev, device_t child) { struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); int i; for (i = 0; i < devi->npins; i++) { /* Reserve the GPIO pin. */ if (gpiobus_acquire_pin(dev, devi->pins[i]) != 0) { device_printf(child, "cannot acquire pin %d\n", devi->pins[i]); while (--i >= 0) { (void)gpiobus_release_pin(dev, devi->pins[i]); } gpiobus_free_ivars(devi); return (EBUSY); } } for (i = 0; i < devi->npins; i++) { /* Use the child name as pin name. */ GPIOBUS_PIN_SETNAME(dev, devi->pins[i], device_get_nameunit(child)); } return (0); } static int gpiobus_parse_pins(struct gpiobus_softc *sc, device_t child, int mask) { struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); int i, npins; npins = 0; for (i = 0; i < 32; i++) { if (mask & (1 << i)) npins++; } if (npins == 0) { device_printf(child, "empty pin mask\n"); return (EINVAL); } devi->npins = npins; if (gpiobus_alloc_ivars(devi) != 0) { device_printf(child, "cannot allocate device ivars\n"); return (EINVAL); } npins = 0; for (i = 0; i < 32; i++) { if ((mask & (1 << i)) == 0) continue; devi->pins[npins++] = i; } return (0); } static int gpiobus_parse_pin_list(struct gpiobus_softc *sc, device_t child, const char *pins) { struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); const char *p; char *endp; unsigned long pin; int i, npins; npins = 0; p = pins; for (;;) { pin = strtoul(p, &endp, 0); if (endp == p) break; npins++; if (*endp == '\0') break; p = endp + 1; } if (*endp != '\0') { device_printf(child, "garbage in the pin list: %s\n", endp); return (EINVAL); } if (npins == 0) { device_printf(child, "empty pin list\n"); return (EINVAL); } devi->npins = npins; if (gpiobus_alloc_ivars(devi) != 0) { device_printf(child, "cannot allocate device ivars\n"); return (EINVAL); } i = 0; p = pins; for (;;) { pin = strtoul(p, &endp, 0); devi->pins[i] = pin; if (*endp == '\0') break; i++; p = endp + 1; } return (0); } static int gpiobus_probe(device_t dev) { device_set_desc(dev, "GPIO bus"); return (BUS_PROBE_GENERIC); } int gpiobus_attach(device_t dev) { int err; err = gpiobus_init_softc(dev); if (err != 0) return (err); /* * Get parent's pins and mark them as unmapped */ bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); return (0); } /* * Since this is not a self-enumerating bus, and since we always add * children in attach, we have to always delete children here. */ int gpiobus_detach(device_t dev) { struct gpiobus_softc *sc; int i, err; sc = GPIOBUS_SOFTC(dev); KASSERT(mtx_initialized(&sc->sc_mtx), ("gpiobus mutex not initialized")); GPIOBUS_LOCK_DESTROY(sc); if ((err = bus_detach_children(dev)) != 0) return (err); rman_fini(&sc->sc_intr_rman); if (sc->sc_pins) { for (i = 0; i < sc->sc_npins; i++) { if (sc->sc_pins[i].name != NULL) free(sc->sc_pins[i].name, M_DEVBUF); sc->sc_pins[i].name = NULL; } free(sc->sc_pins, M_DEVBUF); sc->sc_pins = NULL; } return (0); } static int gpiobus_suspend(device_t dev) { return (bus_generic_suspend(dev)); } static int gpiobus_resume(device_t dev) { return (bus_generic_resume(dev)); } static void gpiobus_probe_nomatch(device_t dev, device_t child) { char pins[128]; struct sbuf sb; struct gpiobus_ivar *devi; devi = GPIOBUS_IVAR(child); sbuf_new(&sb, pins, sizeof(pins), SBUF_FIXEDLEN); gpiobus_print_pins(devi, &sb); sbuf_finish(&sb); device_printf(dev, " at pin%s %s", devi->npins > 1 ? "s" : "", sbuf_data(&sb)); resource_list_print_type(&devi->rl, "irq", SYS_RES_IRQ, "%jd"); printf("\n"); } static int gpiobus_print_child(device_t dev, device_t child) { char pins[128]; struct sbuf sb; int retval = 0; struct gpiobus_ivar *devi; devi = GPIOBUS_IVAR(child); retval += bus_print_child_header(dev, child); if (devi->npins > 0) { if (devi->npins > 1) retval += printf(" at pins "); else retval += printf(" at pin "); sbuf_new(&sb, pins, sizeof(pins), SBUF_FIXEDLEN); gpiobus_print_pins(devi, &sb); sbuf_finish(&sb); retval += printf("%s", sbuf_data(&sb)); } resource_list_print_type(&devi->rl, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } static int gpiobus_child_location(device_t bus, device_t child, struct sbuf *sb) { struct gpiobus_ivar *devi; devi = GPIOBUS_IVAR(child); sbuf_printf(sb, "pins="); gpiobus_print_pins(devi, sb); return (0); } static device_t gpiobus_add_child(device_t dev, u_int order, const char *name, int unit) { device_t child; struct gpiobus_ivar *devi; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (child); devi = malloc(sizeof(struct gpiobus_ivar), M_DEVBUF, M_NOWAIT | M_ZERO); if (devi == NULL) { device_delete_child(dev, child); return (NULL); } resource_list_init(&devi->rl); device_set_ivars(child, devi); return (child); } static void gpiobus_child_deleted(device_t dev, device_t child) { struct gpiobus_ivar *devi; devi = GPIOBUS_IVAR(child); if (devi == NULL) return; gpiobus_free_ivars(devi); resource_list_free(&devi->rl); free(devi, M_DEVBUF); } static int gpiobus_rescan(device_t dev) { /* * Re-scan is supposed to remove and add children, but if someone has * deleted the hints for a child we attached earlier, we have no easy * way to handle that. So this just attaches new children for whom new * hints or drivers have arrived since we last tried. */ bus_enumerate_hinted_children(dev); bus_attach_children(dev); return (0); } static void gpiobus_hinted_child(device_t bus, const char *dname, int dunit) { struct gpiobus_softc *sc = GPIOBUS_SOFTC(bus); device_t child; const char *pins; int irq, pinmask; if (device_find_child(bus, dname, dunit) != NULL) { return; } child = BUS_ADD_CHILD(bus, 0, dname, dunit); if (resource_int_value(dname, dunit, "pins", &pinmask) == 0) { if (gpiobus_parse_pins(sc, child, pinmask)) { device_delete_child(bus, child); return; } } else if (resource_string_value(dname, dunit, "pin_list", &pins) == 0) { if (gpiobus_parse_pin_list(sc, child, pins)) { device_delete_child(bus, child); return; } } if (resource_int_value(dname, dunit, "irq", &irq) == 0) { if (bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1) != 0) device_printf(bus, "warning: bus_set_resource() failed\n"); } } int gpiobus_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct gpiobus_ivar *devi; devi = GPIOBUS_IVAR(child); switch (which) { case GPIOBUS_IVAR_NPINS: *result = devi->npins; break; case GPIOBUS_IVAR_PINS: /* Children do not ever need to directly examine this. */ return (ENOTSUP); default: return (ENOENT); } return (0); } static int gpiobus_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct gpiobus_ivar *devi; const uint32_t *ptr; int i; devi = GPIOBUS_IVAR(child); switch (which) { case GPIOBUS_IVAR_NPINS: /* GPIO ivars are set once. */ if (devi->npins != 0) { return (EBUSY); } devi->npins = value; if (gpiobus_alloc_ivars(devi) != 0) { device_printf(child, "cannot allocate device ivars\n"); devi->npins = 0; return (ENOMEM); } break; case GPIOBUS_IVAR_PINS: ptr = (const uint32_t *)value; for (i = 0; i < devi->npins; i++) devi->pins[i] = ptr[i]; if (gpiobus_acquire_child_pins(dev, child) != 0) return (EBUSY); break; default: return (ENOENT); } return (0); } static struct rman * gpiobus_get_rman(device_t bus, int type, u_int flags) { struct gpiobus_softc *sc; sc = device_get_softc(bus); switch (type) { case SYS_RES_IRQ: return (&sc->sc_intr_rman); default: return (NULL); } } static struct resource * gpiobus_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource_list *rl; struct resource_list_entry *rle; int isdefault; isdefault = (RMAN_IS_DEFAULT_RANGE(start, end) && count == 1); if (isdefault) { rl = BUS_GET_RESOURCE_LIST(bus, child); if (rl == NULL) return (NULL); rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); start = rle->start; count = rle->count; end = rle->end; } return (bus_generic_rman_alloc_resource(bus, child, type, rid, start, end, count, flags)); } static struct resource_list * gpiobus_get_resource_list(device_t bus __unused, device_t child) { struct gpiobus_ivar *ivar; ivar = GPIOBUS_IVAR(child); return (&ivar->rl); } static int gpiobus_acquire_bus(device_t busdev, device_t child, int how) { struct gpiobus_softc *sc; sc = device_get_softc(busdev); GPIOBUS_ASSERT_UNLOCKED(sc); GPIOBUS_LOCK(sc); if (sc->sc_owner != NULL) { if (sc->sc_owner == child) panic("%s: %s still owns the bus.", device_get_nameunit(busdev), device_get_nameunit(child)); if (how == GPIOBUS_DONTWAIT) { GPIOBUS_UNLOCK(sc); return (EWOULDBLOCK); } while (sc->sc_owner != NULL) mtx_sleep(sc, &sc->sc_mtx, 0, "gpiobuswait", 0); } sc->sc_owner = child; GPIOBUS_UNLOCK(sc); return (0); } static void gpiobus_release_bus(device_t busdev, device_t child) { struct gpiobus_softc *sc; sc = device_get_softc(busdev); GPIOBUS_ASSERT_UNLOCKED(sc); GPIOBUS_LOCK(sc); if (sc->sc_owner == NULL) panic("%s: %s releasing unowned bus.", device_get_nameunit(busdev), device_get_nameunit(child)); if (sc->sc_owner != child) panic("%s: %s trying to release bus owned by %s", device_get_nameunit(busdev), device_get_nameunit(child), device_get_nameunit(sc->sc_owner)); sc->sc_owner = NULL; wakeup(sc); GPIOBUS_UNLOCK(sc); } static int gpiobus_pin_setflags(device_t dev, device_t child, uint32_t pin, uint32_t flags) { struct gpiobus_softc *sc = GPIOBUS_SOFTC(dev); struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); uint32_t caps; if (pin >= devi->npins) return (EINVAL); if (GPIO_PIN_GETCAPS(sc->sc_dev, devi->pins[pin], &caps) != 0) return (EINVAL); if (gpio_check_flags(caps, flags) != 0) return (EINVAL); return (GPIO_PIN_SETFLAGS(sc->sc_dev, devi->pins[pin], flags)); } static int gpiobus_pin_getflags(device_t dev, device_t child, uint32_t pin, uint32_t *flags) { struct gpiobus_softc *sc = GPIOBUS_SOFTC(dev); struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); if (pin >= devi->npins) return (EINVAL); return GPIO_PIN_GETFLAGS(sc->sc_dev, devi->pins[pin], flags); } static int gpiobus_pin_getcaps(device_t dev, device_t child, uint32_t pin, uint32_t *caps) { struct gpiobus_softc *sc = GPIOBUS_SOFTC(dev); struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); if (pin >= devi->npins) return (EINVAL); return GPIO_PIN_GETCAPS(sc->sc_dev, devi->pins[pin], caps); } static int gpiobus_pin_set(device_t dev, device_t child, uint32_t pin, unsigned int value) { struct gpiobus_softc *sc = GPIOBUS_SOFTC(dev); struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); if (pin >= devi->npins) return (EINVAL); return GPIO_PIN_SET(sc->sc_dev, devi->pins[pin], value); } static int gpiobus_pin_get(device_t dev, device_t child, uint32_t pin, unsigned int *value) { struct gpiobus_softc *sc = GPIOBUS_SOFTC(dev); struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); if (pin >= devi->npins) return (EINVAL); return GPIO_PIN_GET(sc->sc_dev, devi->pins[pin], value); } static int gpiobus_pin_toggle(device_t dev, device_t child, uint32_t pin) { struct gpiobus_softc *sc = GPIOBUS_SOFTC(dev); struct gpiobus_ivar *devi = GPIOBUS_IVAR(child); if (pin >= devi->npins) return (EINVAL); return GPIO_PIN_TOGGLE(sc->sc_dev, devi->pins[pin]); } static int gpiobus_pin_getname(device_t dev, uint32_t pin, char *name) { struct gpiobus_softc *sc; sc = GPIOBUS_SOFTC(dev); if (pin > sc->sc_npins) return (EINVAL); /* Did we have a name for this pin ? */ if (sc->sc_pins[pin].name != NULL) { memcpy(name, sc->sc_pins[pin].name, GPIOMAXNAME); return (0); } /* Return the default pin name. */ return (GPIO_PIN_GETNAME(device_get_parent(dev), pin, name)); } static int gpiobus_pin_setname(device_t dev, uint32_t pin, const char *name) { struct gpiobus_softc *sc; sc = GPIOBUS_SOFTC(dev); if (pin > sc->sc_npins) return (EINVAL); if (name == NULL) return (EINVAL); /* Save the pin name. */ if (sc->sc_pins[pin].name == NULL) sc->sc_pins[pin].name = malloc(GPIOMAXNAME, M_DEVBUF, M_WAITOK | M_ZERO); strlcpy(sc->sc_pins[pin].name, name, GPIOMAXNAME); return (0); } static device_method_t gpiobus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpiobus_probe), DEVMETHOD(device_attach, gpiobus_attach), DEVMETHOD(device_detach, gpiobus_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, gpiobus_suspend), DEVMETHOD(device_resume, gpiobus_resume), /* Bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_config_intr, bus_generic_config_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_alloc_resource, gpiobus_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_rman_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_rman_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_rman_deactivate_resource), DEVMETHOD(bus_get_resource_list, gpiobus_get_resource_list), DEVMETHOD(bus_get_rman, gpiobus_get_rman), DEVMETHOD(bus_add_child, gpiobus_add_child), DEVMETHOD(bus_child_deleted, gpiobus_child_deleted), DEVMETHOD(bus_rescan, gpiobus_rescan), DEVMETHOD(bus_probe_nomatch, gpiobus_probe_nomatch), DEVMETHOD(bus_print_child, gpiobus_print_child), DEVMETHOD(bus_child_location, gpiobus_child_location), DEVMETHOD(bus_hinted_child, gpiobus_hinted_child), DEVMETHOD(bus_read_ivar, gpiobus_read_ivar), DEVMETHOD(bus_write_ivar, gpiobus_write_ivar), /* GPIO protocol */ DEVMETHOD(gpiobus_acquire_bus, gpiobus_acquire_bus), DEVMETHOD(gpiobus_release_bus, gpiobus_release_bus), DEVMETHOD(gpiobus_pin_getflags, gpiobus_pin_getflags), DEVMETHOD(gpiobus_pin_getcaps, gpiobus_pin_getcaps), DEVMETHOD(gpiobus_pin_setflags, gpiobus_pin_setflags), DEVMETHOD(gpiobus_pin_get, gpiobus_pin_get), DEVMETHOD(gpiobus_pin_set, gpiobus_pin_set), DEVMETHOD(gpiobus_pin_toggle, gpiobus_pin_toggle), DEVMETHOD(gpiobus_pin_getname, gpiobus_pin_getname), DEVMETHOD(gpiobus_pin_setname, gpiobus_pin_setname), DEVMETHOD_END }; driver_t gpiobus_driver = { "gpiobus", gpiobus_methods, sizeof(struct gpiobus_softc) }; EARLY_DRIVER_MODULE(gpiobus, gpio, gpiobus_driver, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(gpiobus, 1); diff --git a/sys/dev/hid/hidraw.c b/sys/dev/hid/hidraw.c index 9b6f83d34d08..06f70070f61b 100644 --- a/sys/dev/hid/hidraw.c +++ b/sys/dev/hid/hidraw.c @@ -1,1061 +1,1061 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * Copyright (c) 2020, 2025 Vladimir Kondratyev * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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, 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION OR CONTRIBUTORS * 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. */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include #include "opt_hid.h" #include #ifdef COMPAT_FREEBSD32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR hidraw_debug #include #include #include #ifdef HID_DEBUG static int hidraw_debug = 0; static SYSCTL_NODE(_hw_hid, OID_AUTO, hidraw, CTLFLAG_RW, 0, "HID raw interface"); SYSCTL_INT(_hw_hid_hidraw, OID_AUTO, debug, CTLFLAG_RWTUN, &hidraw_debug, 0, "Debug level"); #endif #define HIDRAW_INDEX 0xFF /* Arbitrary high value */ #define HIDRAW_LOCAL_BUFSIZE 64 /* Size of on-stack buffer. */ #define HIDRAW_LOCAL_ALLOC(local_buf, size) \ (sizeof(local_buf) > (size) ? (local_buf) : \ malloc((size), M_DEVBUF, M_ZERO | M_WAITOK)) #define HIDRAW_LOCAL_FREE(local_buf, buf) \ if ((local_buf) != (buf)) { \ free((buf), M_DEVBUF); \ } struct hidraw_softc { device_t sc_dev; /* base device */ struct mtx sc_mtx; /* hidbus private mutex */ struct hid_rdesc_info *sc_rdesc; const struct hid_device_info *sc_hw; uint8_t *sc_q; hid_size_t *sc_qlen; int sc_head; int sc_tail; int sc_sleepcnt; struct selinfo sc_rsel; struct proc *sc_async; /* process that wants SIGIO */ struct { /* driver state */ bool open:1; /* device is open */ bool aslp:1; /* waiting for device data in read() */ bool sel:1; /* waiting for device data in poll() */ bool quiet:1; /* Ignore input data */ bool immed:1; /* return read data immediately */ bool uhid:1; /* driver switched in to uhid mode */ bool lock:1; /* input queue sleepable lock */ bool flush:1; /* do not wait for data in read() */ } sc_state; int sc_fflags; /* access mode for open lifetime */ struct cdev *dev; }; #ifdef COMPAT_FREEBSD32 struct hidraw_gen_descriptor32 { uint32_t hgd_data; /* void * */ uint16_t hgd_lang_id; uint16_t hgd_maxlen; uint16_t hgd_actlen; uint16_t hgd_offset; uint8_t hgd_config_index; uint8_t hgd_string_index; uint8_t hgd_iface_index; uint8_t hgd_altif_index; uint8_t hgd_endpt_index; uint8_t hgd_report_type; uint8_t reserved[8]; }; #define HIDRAW_GET_REPORT_DESC32 \ _IOC_NEWTYPE(HIDRAW_GET_REPORT_DESC, struct hidraw_gen_descriptor32) #define HIDRAW_GET_REPORT32 \ _IOC_NEWTYPE(HIDRAW_GET_REPORT, struct hidraw_gen_descriptor32) #define HIDRAW_SET_REPORT_DESC32 \ _IOC_NEWTYPE(HIDRAW_SET_REPORT_DESC, struct hidraw_gen_descriptor32) #define HIDRAW_SET_REPORT32 \ _IOC_NEWTYPE(HIDRAW_SET_REPORT, struct hidraw_gen_descriptor32) #endif static d_open_t hidraw_open; static d_read_t hidraw_read; static d_write_t hidraw_write; static d_ioctl_t hidraw_ioctl; static d_poll_t hidraw_poll; static d_kqfilter_t hidraw_kqfilter; static d_priv_dtor_t hidraw_dtor; static struct cdevsw hidraw_cdevsw = { .d_version = D_VERSION, .d_open = hidraw_open, .d_read = hidraw_read, .d_write = hidraw_write, .d_ioctl = hidraw_ioctl, .d_poll = hidraw_poll, .d_kqfilter = hidraw_kqfilter, .d_name = "hidraw", }; static hid_intr_t hidraw_intr; static device_identify_t hidraw_identify; static device_probe_t hidraw_probe; static device_attach_t hidraw_attach; static device_detach_t hidraw_detach; static int hidraw_kqread(struct knote *, long); static void hidraw_kqdetach(struct knote *); static void hidraw_notify(struct hidraw_softc *); static const struct filterops hidraw_filterops_read = { .f_isfd = 1, .f_detach = hidraw_kqdetach, .f_event = hidraw_kqread, }; static void hidraw_identify(driver_t *driver, device_t parent) { device_t child; - if (device_find_child(parent, "hidraw", -1) == NULL) { + if (device_find_child(parent, "hidraw", DEVICE_UNIT_ANY) == NULL) { child = BUS_ADD_CHILD(parent, 0, "hidraw", device_get_unit(parent)); if (child != NULL) hidbus_set_index(child, HIDRAW_INDEX); } } static int hidraw_probe(device_t self) { if (hidbus_get_index(self) != HIDRAW_INDEX) return (ENXIO); hidbus_set_desc(self, "Raw HID Device"); return (BUS_PROBE_GENERIC); } static int hidraw_attach(device_t self) { struct hidraw_softc *sc = device_get_softc(self); struct make_dev_args mda; int error; sc->sc_dev = self; sc->sc_rdesc = hidbus_get_rdesc_info(self); sc->sc_hw = hid_get_device_info(self); /* Hidraw mode does not require report descriptor to work */ if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) device_printf(self, "no report descriptor\n"); mtx_init(&sc->sc_mtx, "hidraw lock", NULL, MTX_DEF); knlist_init_mtx(&sc->sc_rsel.si_note, &sc->sc_mtx); make_dev_args_init(&mda); mda.mda_flags = MAKEDEV_WAITOK; mda.mda_devsw = &hidraw_cdevsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_OPERATOR; mda.mda_mode = 0600; mda.mda_si_drv1 = sc; error = make_dev_s(&mda, &sc->dev, "hidraw%d", device_get_unit(self)); if (error) { device_printf(self, "Can not create character device\n"); hidraw_detach(self); return (error); } #ifdef HIDRAW_MAKE_UHID_ALIAS (void)make_dev_alias(sc->dev, "uhid%d", device_get_unit(self)); #endif hidbus_set_lock(self, &sc->sc_mtx); hidbus_set_intr(self, hidraw_intr, sc); return (0); } static int hidraw_detach(device_t self) { struct hidraw_softc *sc = device_get_softc(self); DPRINTF("sc=%p\n", sc); if (sc->dev != NULL) { mtx_lock(&sc->sc_mtx); sc->dev->si_drv1 = NULL; /* Wake everyone */ hidraw_notify(sc); mtx_unlock(&sc->sc_mtx); destroy_dev(sc->dev); } knlist_clear(&sc->sc_rsel.si_note, 0); knlist_destroy(&sc->sc_rsel.si_note); seldrain(&sc->sc_rsel); mtx_destroy(&sc->sc_mtx); return (0); } void hidraw_intr(void *context, void *buf, hid_size_t len) { struct hidraw_softc *sc = context; int next; DPRINTFN(5, "len=%d\n", len); DPRINTFN(5, "data = %*D\n", len, buf, " "); next = (sc->sc_tail + 1) % HIDRAW_BUFFER_SIZE; if (sc->sc_state.quiet || next == sc->sc_head) return; bcopy(buf, sc->sc_q + sc->sc_tail * sc->sc_rdesc->rdsize, len); /* Make sure we don't process old data */ if (len < sc->sc_rdesc->rdsize) bzero(sc->sc_q + sc->sc_tail * sc->sc_rdesc->rdsize + len, sc->sc_rdesc->isize - len); sc->sc_qlen[sc->sc_tail] = len; sc->sc_tail = next; hidraw_notify(sc); } static inline int hidraw_lock_queue(struct hidraw_softc *sc, bool flush) { int error = 0; mtx_assert(&sc->sc_mtx, MA_OWNED); if (flush) sc->sc_state.flush = true; ++sc->sc_sleepcnt; while (sc->sc_state.lock && error == 0) { /* Flush is requested. Wakeup all readers and forbid sleeps */ if (flush && sc->sc_state.aslp) { sc->sc_state.aslp = false; DPRINTFN(5, "waking %p\n", &sc->sc_q); wakeup(&sc->sc_q); } error = mtx_sleep(&sc->sc_sleepcnt, &sc->sc_mtx, PZERO | PCATCH, "hidrawio", 0); } --sc->sc_sleepcnt; if (flush) sc->sc_state.flush = false; if (error == 0) sc->sc_state.lock = true; return (error); } static inline void hidraw_unlock_queue(struct hidraw_softc *sc) { mtx_assert(&sc->sc_mtx, MA_OWNED); KASSERT(sc->sc_state.lock, ("input buffer is not locked")); if (sc->sc_sleepcnt != 0) wakeup_one(&sc->sc_sleepcnt); sc->sc_state.lock = false; } static int hidraw_open(struct cdev *dev, int flag, int mode, struct thread *td) { struct hidraw_softc *sc; int error; sc = dev->si_drv1; if (sc == NULL) return (ENXIO); DPRINTF("sc=%p\n", sc); mtx_lock(&sc->sc_mtx); if (sc->sc_state.open) { mtx_unlock(&sc->sc_mtx); return (EBUSY); } sc->sc_state.open = true; mtx_unlock(&sc->sc_mtx); error = devfs_set_cdevpriv(sc, hidraw_dtor); if (error != 0) { mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); return (error); } sc->sc_q = malloc(sc->sc_rdesc->rdsize * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); sc->sc_qlen = malloc(sizeof(hid_size_t) * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); /* Set up interrupt pipe. */ sc->sc_state.immed = false; sc->sc_async = 0; sc->sc_state.uhid = false; /* hidraw mode is default */ sc->sc_state.quiet = false; sc->sc_head = sc->sc_tail = 0; sc->sc_fflags = flag; hid_intr_start(sc->sc_dev); return (0); } static void hidraw_dtor(void *data) { struct hidraw_softc *sc = data; DPRINTF("sc=%p\n", sc); /* Disable interrupts. */ hid_intr_stop(sc->sc_dev); sc->sc_tail = sc->sc_head = 0; sc->sc_async = 0; free(sc->sc_q, M_DEVBUF); free(sc->sc_qlen, M_DEVBUF); sc->sc_q = NULL; mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); } static int hidraw_read(struct cdev *dev, struct uio *uio, int flag) { struct hidraw_softc *sc; size_t length; int error; DPRINTFN(1, "\n"); sc = dev->si_drv1; if (sc == NULL) return (EIO); mtx_lock(&sc->sc_mtx); error = dev->si_drv1 == NULL ? EIO : hidraw_lock_queue(sc, false); if (error != 0) { mtx_unlock(&sc->sc_mtx); return (error); } if (sc->sc_state.immed) { mtx_unlock(&sc->sc_mtx); DPRINTFN(1, "immed\n"); error = hid_get_report(sc->sc_dev, sc->sc_q, sc->sc_rdesc->isize, NULL, HID_INPUT_REPORT, sc->sc_rdesc->iid); if (error == 0) error = uiomove(sc->sc_q, sc->sc_rdesc->isize, uio); mtx_lock(&sc->sc_mtx); goto exit; } while (sc->sc_tail == sc->sc_head && !sc->sc_state.flush) { if (flag & O_NONBLOCK) { error = EWOULDBLOCK; goto exit; } sc->sc_state.aslp = true; DPRINTFN(5, "sleep on %p\n", &sc->sc_q); error = mtx_sleep(&sc->sc_q, &sc->sc_mtx, PZERO | PCATCH, "hidrawrd", 0); DPRINTFN(5, "woke, error=%d\n", error); if (dev->si_drv1 == NULL) error = EIO; if (error) { sc->sc_state.aslp = false; goto exit; } } while (sc->sc_tail != sc->sc_head && uio->uio_resid > 0) { length = min(uio->uio_resid, sc->sc_state.uhid ? sc->sc_rdesc->isize : sc->sc_qlen[sc->sc_head]); mtx_unlock(&sc->sc_mtx); /* Copy the data to the user process. */ DPRINTFN(5, "got %lu chars\n", (u_long)length); error = uiomove(sc->sc_q + sc->sc_head * sc->sc_rdesc->rdsize, length, uio); mtx_lock(&sc->sc_mtx); if (error != 0) goto exit; /* Remove a small chunk from the input queue. */ sc->sc_head = (sc->sc_head + 1) % HIDRAW_BUFFER_SIZE; /* * In uhid mode transfer as many chunks as possible. Hidraw * packets are transferred one by one due to different length. */ if (!sc->sc_state.uhid) goto exit; } exit: hidraw_unlock_queue(sc); mtx_unlock(&sc->sc_mtx); return (error); } static int hidraw_write(struct cdev *dev, struct uio *uio, int flag) { uint8_t local_buf[HIDRAW_LOCAL_BUFSIZE], *buf; struct hidraw_softc *sc; int error; int size; size_t buf_offset; uint8_t id = 0; DPRINTFN(1, "\n"); sc = dev->si_drv1; if (sc == NULL) return (EIO); if (sc->sc_rdesc->osize == 0) return (EOPNOTSUPP); buf_offset = 0; if (sc->sc_state.uhid) { size = sc->sc_rdesc->osize; if (uio->uio_resid != size) return (EINVAL); } else { size = uio->uio_resid; if (size < 2) return (EINVAL); /* Strip leading 0 if the device doesnt use numbered reports */ error = uiomove(&id, 1, uio); if (error) return (error); if (id != 0) buf_offset++; else size--; /* Check if underlying driver could process this request */ if (size > sc->sc_rdesc->wrsize) return (ENOBUFS); } buf = HIDRAW_LOCAL_ALLOC(local_buf, size); buf[0] = id; error = uiomove(buf + buf_offset, uio->uio_resid, uio); if (error == 0) error = hid_write(sc->sc_dev, buf, size); HIDRAW_LOCAL_FREE(local_buf, buf); return (error); } #ifdef COMPAT_FREEBSD32 static void update_hgd32(const struct hidraw_gen_descriptor *hgd, struct hidraw_gen_descriptor32 *hgd32) { /* Don't update hgd_data pointer */ CP(*hgd, *hgd32, hgd_lang_id); CP(*hgd, *hgd32, hgd_maxlen); CP(*hgd, *hgd32, hgd_actlen); CP(*hgd, *hgd32, hgd_offset); CP(*hgd, *hgd32, hgd_config_index); CP(*hgd, *hgd32, hgd_string_index); CP(*hgd, *hgd32, hgd_iface_index); CP(*hgd, *hgd32, hgd_altif_index); CP(*hgd, *hgd32, hgd_endpt_index); CP(*hgd, *hgd32, hgd_report_type); /* Don't update reserved */ } #endif static int hidraw_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { uint8_t local_buf[HIDRAW_LOCAL_BUFSIZE]; #ifdef COMPAT_FREEBSD32 struct hidraw_gen_descriptor local_hgd; struct hidraw_gen_descriptor32 *hgd32 = NULL; #endif void *buf; struct hidraw_softc *sc; struct hidraw_device_info *hdi; struct hidraw_gen_descriptor *hgd; struct hidraw_report_descriptor *hrd; struct hidraw_devinfo *hd; const char *devname; uint32_t size; hid_size_t actsize; int id, len; int error = 0; uint8_t reptype; DPRINTFN(2, "cmd=%lx\n", cmd); sc = dev->si_drv1; if (sc == NULL) return (EIO); hgd = (struct hidraw_gen_descriptor *)addr; #ifdef COMPAT_FREEBSD32 switch (cmd) { case HIDRAW_GET_REPORT_DESC32: case HIDRAW_GET_REPORT32: case HIDRAW_SET_REPORT_DESC32: case HIDRAW_SET_REPORT32: cmd = _IOC_NEWTYPE(cmd, struct hidraw_gen_descriptor); hgd32 = (struct hidraw_gen_descriptor32 *)addr; hgd = &local_hgd; PTRIN_CP(*hgd32, *hgd, hgd_data); CP(*hgd32, *hgd, hgd_lang_id); CP(*hgd32, *hgd, hgd_maxlen); CP(*hgd32, *hgd, hgd_actlen); CP(*hgd32, *hgd, hgd_offset); CP(*hgd32, *hgd, hgd_config_index); CP(*hgd32, *hgd, hgd_string_index); CP(*hgd32, *hgd, hgd_iface_index); CP(*hgd32, *hgd, hgd_altif_index); CP(*hgd32, *hgd, hgd_endpt_index); CP(*hgd32, *hgd, hgd_report_type); /* Don't copy reserved */ break; } #endif /* fixed-length ioctls handling */ switch (cmd) { case FIONBIO: /* All handled in the upper FS layer. */ return (0); case FIOASYNC: mtx_lock(&sc->sc_mtx); if (*(int *)addr) { if (sc->sc_async == NULL) { sc->sc_async = td->td_proc; DPRINTF("FIOASYNC %p\n", sc->sc_async); } else error = EBUSY; } else sc->sc_async = NULL; mtx_unlock(&sc->sc_mtx); return (error); /* XXX this is not the most general solution. */ case TIOCSPGRP: mtx_lock(&sc->sc_mtx); if (sc->sc_async == NULL) error = EINVAL; else if (*(int *)addr != sc->sc_async->p_pgid) error = EPERM; mtx_unlock(&sc->sc_mtx); return (error); case HIDRAW_GET_REPORT_DESC: if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) return (EOPNOTSUPP); mtx_lock(&sc->sc_mtx); sc->sc_state.uhid = true; mtx_unlock(&sc->sc_mtx); if (sc->sc_rdesc->len > hgd->hgd_maxlen) { size = hgd->hgd_maxlen; } else { size = sc->sc_rdesc->len; } hgd->hgd_actlen = size; #ifdef COMPAT_FREEBSD32 if (hgd32 != NULL) update_hgd32(hgd, hgd32); #endif if (hgd->hgd_data == NULL) return (0); /* descriptor length only */ return (copyout(sc->sc_rdesc->data, hgd->hgd_data, size)); case HIDRAW_SET_REPORT_DESC: if (!(sc->sc_fflags & FWRITE)) return (EPERM); /* check privileges */ error = priv_check(curthread, PRIV_DRIVER); if (error) return (error); /* Stop interrupts and clear input report buffer */ mtx_lock(&sc->sc_mtx); sc->sc_tail = sc->sc_head = 0; error = hidraw_lock_queue(sc, true); if (error == 0) sc->sc_state.quiet = true; mtx_unlock(&sc->sc_mtx); if (error != 0) return (error); buf = HIDRAW_LOCAL_ALLOC(local_buf, hgd->hgd_maxlen); error = copyin(hgd->hgd_data, buf, hgd->hgd_maxlen); if (error == 0) { bus_topo_lock(); error = hid_set_report_descr(sc->sc_dev, buf, hgd->hgd_maxlen); bus_topo_unlock(); } HIDRAW_LOCAL_FREE(local_buf, buf); /* Realloc hidraw input queue */ if (error == 0) sc->sc_q = realloc(sc->sc_q, sc->sc_rdesc->rdsize * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); /* Start interrupts again */ mtx_lock(&sc->sc_mtx); sc->sc_state.quiet = false; hidraw_unlock_queue(sc); mtx_unlock(&sc->sc_mtx); return (error); case HIDRAW_SET_IMMED: if (!(sc->sc_fflags & FREAD)) return (EPERM); if (*(int *)addr) { /* XXX should read into ibuf, but does it matter? */ size = sc->sc_rdesc->isize; buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = hid_get_report(sc->sc_dev, buf, size, NULL, HID_INPUT_REPORT, sc->sc_rdesc->iid); HIDRAW_LOCAL_FREE(local_buf, buf); if (error) return (EOPNOTSUPP); mtx_lock(&sc->sc_mtx); sc->sc_state.immed = true; mtx_unlock(&sc->sc_mtx); } else { mtx_lock(&sc->sc_mtx); sc->sc_state.immed = false; mtx_unlock(&sc->sc_mtx); } return (0); case HIDRAW_GET_REPORT: if (!(sc->sc_fflags & FREAD)) return (EPERM); switch (hgd->hgd_report_type) { case HID_INPUT_REPORT: size = sc->sc_rdesc->isize; id = sc->sc_rdesc->iid; break; case HID_OUTPUT_REPORT: size = sc->sc_rdesc->osize; id = sc->sc_rdesc->oid; break; case HID_FEATURE_REPORT: size = sc->sc_rdesc->fsize; id = sc->sc_rdesc->fid; break; default: return (EINVAL); } if (id != 0) { error = copyin(hgd->hgd_data, &id, 1); if (error != 0) return (error); } size = MIN(hgd->hgd_maxlen, size); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); actsize = 0; error = hid_get_report(sc->sc_dev, buf, size, &actsize, hgd->hgd_report_type, id); if (!error) error = copyout(buf, hgd->hgd_data, actsize); HIDRAW_LOCAL_FREE(local_buf, buf); hgd->hgd_actlen = actsize; #ifdef COMPAT_FREEBSD32 if (hgd32 != NULL) update_hgd32(hgd, hgd32); #endif return (error); case HIDRAW_SET_REPORT: if (!(sc->sc_fflags & FWRITE)) return (EPERM); switch (hgd->hgd_report_type) { case HID_INPUT_REPORT: size = sc->sc_rdesc->isize; id = sc->sc_rdesc->iid; break; case HID_OUTPUT_REPORT: size = sc->sc_rdesc->osize; id = sc->sc_rdesc->oid; break; case HID_FEATURE_REPORT: size = sc->sc_rdesc->fsize; id = sc->sc_rdesc->fid; break; default: return (EINVAL); } size = MIN(hgd->hgd_maxlen, size); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = copyin(hgd->hgd_data, buf, size); if (error == 0) { if (id != 0) id = *(uint8_t *)buf; error = hid_set_report(sc->sc_dev, buf, size, hgd->hgd_report_type, id); } HIDRAW_LOCAL_FREE(local_buf, buf); return (error); case HIDRAW_GET_REPORT_ID: *(int *)addr = 0; /* XXX: we only support reportid 0? */ return (0); case HIDRAW_GET_DEVICEINFO: hdi = (struct hidraw_device_info *)addr; bzero(hdi, sizeof(struct hidraw_device_info)); hdi->hdi_product = sc->sc_hw->idProduct; hdi->hdi_vendor = sc->sc_hw->idVendor; hdi->hdi_version = sc->sc_hw->idVersion; hdi->hdi_bustype = sc->sc_hw->idBus; strlcpy(hdi->hdi_name, sc->sc_hw->name, sizeof(hdi->hdi_name)); strlcpy(hdi->hdi_phys, device_get_nameunit(sc->sc_dev), sizeof(hdi->hdi_phys)); strlcpy(hdi->hdi_uniq, sc->sc_hw->serial, sizeof(hdi->hdi_uniq)); snprintf(hdi->hdi_release, sizeof(hdi->hdi_release), "%x.%02x", sc->sc_hw->idVersion >> 8, sc->sc_hw->idVersion & 0xff); return(0); case HIDIOCGRDESCSIZE: *(int *)addr = sc->sc_hw->rdescsize; return (0); case HIDIOCGRDESC: hrd = *(struct hidraw_report_descriptor **)addr; error = copyin(&hrd->size, &size, sizeof(uint32_t)); if (error) return (error); /* * HID_MAX_DESCRIPTOR_SIZE-1 is a limit of report descriptor * size in current Linux implementation. */ if (size >= HID_MAX_DESCRIPTOR_SIZE) return (EINVAL); mtx_lock(&sc->sc_mtx); sc->sc_state.uhid = false; mtx_unlock(&sc->sc_mtx); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = hid_get_rdesc(sc->sc_dev, buf, size); if (error == 0) { size = MIN(size, sc->sc_rdesc->len); error = copyout(buf, hrd->value, size); } HIDRAW_LOCAL_FREE(local_buf, buf); return (error); case HIDIOCGRAWINFO: hd = (struct hidraw_devinfo *)addr; hd->bustype = sc->sc_hw->idBus; hd->vendor = sc->sc_hw->idVendor; hd->product = sc->sc_hw->idProduct; return (0); } /* variable-length ioctls handling */ len = IOCPARM_LEN(cmd); switch (IOCBASECMD(cmd)) { case HIDIOCGRAWNAME(0): strlcpy(addr, sc->sc_hw->name, len); td->td_retval[0] = min(strlen(sc->sc_hw->name) + 1, len); return (0); case HIDIOCGRAWPHYS(0): devname = device_get_nameunit(sc->sc_dev); strlcpy(addr, devname, len); td->td_retval[0] = min(strlen(devname) + 1, len); return (0); case HIDIOCSFEATURE(0): case HIDIOCSINPUT(0): case HIDIOCSOUTPUT(0): if (!(sc->sc_fflags & FWRITE)) return (EPERM); if (len < 2) return (EINVAL); id = *(uint8_t *)addr; if (id == 0) { addr = (uint8_t *)addr + 1; len--; } switch (IOCBASECMD(cmd)) { case HIDIOCSFEATURE(0): reptype = HID_FEATURE_REPORT; break; case HIDIOCSINPUT(0): reptype = HID_INPUT_REPORT; break; case HIDIOCSOUTPUT(0): reptype = HID_OUTPUT_REPORT; break; default: panic("Invalid report type"); } error = hid_set_report(sc->sc_dev, addr, len, reptype, id); if (error == 0) td->td_retval[0] = IOCPARM_LEN(cmd); return (error); case HIDIOCGFEATURE(0): case HIDIOCGINPUT(0): case HIDIOCGOUTPUT(0): if (!(sc->sc_fflags & FREAD)) return (EPERM); if (len < 2) return (EINVAL); id = *(uint8_t *)addr; if (id == 0) { addr = (uint8_t *)addr + 1; len--; } switch (IOCBASECMD(cmd)) { case HIDIOCGFEATURE(0): reptype = HID_FEATURE_REPORT; break; case HIDIOCGINPUT(0): reptype = HID_INPUT_REPORT; break; case HIDIOCGOUTPUT(0): reptype = HID_OUTPUT_REPORT; break; default: panic("Invalid report type"); } error = hid_get_report(sc->sc_dev, addr, len, &actsize, reptype, id); if (error == 0) { if (id == 0) actsize++; td->td_retval[0] = actsize; } return (error); case HIDIOCGRAWUNIQ(0): strlcpy(addr, sc->sc_hw->serial, len); td->td_retval[0] = min(strlen(sc->sc_hw->serial) + 1, len); return (0); } return (EINVAL); } static int hidraw_poll(struct cdev *dev, int events, struct thread *td) { struct hidraw_softc *sc; int revents = 0; sc = dev->si_drv1; if (sc == NULL) return (POLLHUP); if (events & (POLLOUT | POLLWRNORM) && (sc->sc_fflags & FWRITE)) revents |= events & (POLLOUT | POLLWRNORM); if (events & (POLLIN | POLLRDNORM) && (sc->sc_fflags & FREAD)) { mtx_lock(&sc->sc_mtx); if (sc->sc_head != sc->sc_tail) revents |= events & (POLLIN | POLLRDNORM); else { sc->sc_state.sel = true; selrecord(td, &sc->sc_rsel); } mtx_unlock(&sc->sc_mtx); } return (revents); } static int hidraw_kqfilter(struct cdev *dev, struct knote *kn) { struct hidraw_softc *sc; sc = dev->si_drv1; if (sc == NULL) return (ENXIO); switch(kn->kn_filter) { case EVFILT_READ: if (sc->sc_fflags & FREAD) { kn->kn_fop = &hidraw_filterops_read; break; } /* FALLTHROUGH */ default: return(EINVAL); } kn->kn_hook = sc; knlist_add(&sc->sc_rsel.si_note, kn, 0); return (0); } static int hidraw_kqread(struct knote *kn, long hint) { struct hidraw_softc *sc; int ret; sc = kn->kn_hook; mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->dev->si_drv1 == NULL) { kn->kn_flags |= EV_EOF; ret = 1; } else ret = (sc->sc_head != sc->sc_tail) ? 1 : 0; return (ret); } static void hidraw_kqdetach(struct knote *kn) { struct hidraw_softc *sc; sc = kn->kn_hook; knlist_remove(&sc->sc_rsel.si_note, kn, 0); } static void hidraw_notify(struct hidraw_softc *sc) { mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->sc_state.aslp) { sc->sc_state.aslp = false; DPRINTFN(5, "waking %p\n", &sc->sc_q); wakeup(&sc->sc_q); } if (sc->sc_state.sel) { sc->sc_state.sel = false; selwakeuppri(&sc->sc_rsel, PZERO); } if (sc->sc_async != NULL) { DPRINTFN(3, "sending SIGIO %p\n", sc->sc_async); PROC_LOCK(sc->sc_async); kern_psignal(sc->sc_async, SIGIO); PROC_UNLOCK(sc->sc_async); } KNOTE_LOCKED(&sc->sc_rsel.si_note, 0); } static device_method_t hidraw_methods[] = { /* Device interface */ DEVMETHOD(device_identify, hidraw_identify), DEVMETHOD(device_probe, hidraw_probe), DEVMETHOD(device_attach, hidraw_attach), DEVMETHOD(device_detach, hidraw_detach), DEVMETHOD_END }; static driver_t hidraw_driver = { "hidraw", hidraw_methods, sizeof(struct hidraw_softc) }; DRIVER_MODULE(hidraw, hidbus, hidraw_driver, NULL, NULL); MODULE_DEPEND(hidraw, hidbus, 1, 1, 1); MODULE_DEPEND(hidraw, hid, 1, 1, 1); MODULE_VERSION(hidraw, 1); diff --git a/sys/dev/hyperv/input/hv_hid.c b/sys/dev/hyperv/input/hv_hid.c index a26c46184442..ec68581d63a8 100644 --- a/sys/dev/hyperv/input/hv_hid.c +++ b/sys/dev/hyperv/input/hv_hid.c @@ -1,563 +1,563 @@ /*- * Copyright (c) 2017 Microsoft Corp. * Copyright (c) 2023 Yuri * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hid_if.h" #include "vmbus_if.h" #define HV_HID_VER_MAJOR 2 #define HV_HID_VER_MINOR 0 #define HV_HID_VER (HV_HID_VER_MINOR | (HV_HID_VER_MAJOR) << 16) #define HV_BUFSIZ (4 * PAGE_SIZE) #define HV_HID_RINGBUFF_SEND_SZ (10 * PAGE_SIZE) #define HV_HID_RINGBUFF_RECV_SZ (10 * PAGE_SIZE) typedef struct { device_t dev; struct mtx mtx; /* vmbus */ struct vmbus_channel *hs_chan; struct vmbus_xact_ctx *hs_xact_ctx; uint8_t *buf; int buflen; /* hid */ struct hid_device_info hdi; hid_intr_t *intr; bool intr_on; void *intr_ctx; uint8_t *rdesc; } hv_hid_sc; typedef enum { SH_PROTO_REQ, SH_PROTO_RESP, SH_DEVINFO, SH_DEVINFO_ACK, SH_INPUT_REPORT, } sh_msg_type; typedef struct { sh_msg_type type; uint32_t size; } __packed sh_msg_hdr; typedef struct { sh_msg_hdr hdr; char data[]; } __packed sh_msg; typedef struct { sh_msg_hdr hdr; uint32_t ver; } __packed sh_proto_req; typedef struct { sh_msg_hdr hdr; uint32_t ver; uint32_t app; } __packed sh_proto_resp; typedef struct { u_int size; u_short vendor; u_short product; u_short version; u_short reserved[11]; } __packed sh_devinfo; /* Copied from linux/hid.h */ typedef struct { uint8_t bDescriptorType; uint16_t wDescriptorLength; } __packed sh_hcdesc; typedef struct { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdHID; uint8_t bCountryCode; uint8_t bNumDescriptors; sh_hcdesc hcdesc[1]; } __packed sh_hdesc; typedef struct { sh_msg_hdr hdr; sh_devinfo devinfo; sh_hdesc hdesc; } __packed sh_devinfo_resp; typedef struct { sh_msg_hdr hdr; uint8_t rsvd; } __packed sh_devinfo_ack; typedef struct { sh_msg_hdr hdr; char buffer[]; } __packed sh_input_report; typedef enum { HV_HID_MSG_INVALID, HV_HID_MSG_DATA, } hv_hid_msg_type; typedef struct { hv_hid_msg_type type; uint32_t size; char data[]; } hv_hid_pmsg; typedef struct { hv_hid_msg_type type; uint32_t size; union { sh_msg msg; sh_proto_req req; sh_proto_resp resp; sh_devinfo_resp dresp; sh_devinfo_ack ack; sh_input_report irep; }; } hv_hid_msg; #define HV_HID_REQ_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_req)) #define HV_HID_RESP_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_resp)) #define HV_HID_ACK_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_devinfo_ack)) /* Somewhat arbitrary, enough to get the devinfo response */ #define HV_HID_REQ_MAX 256 #define HV_HID_RESP_MAX 256 static const struct vmbus_ic_desc vmbus_hid_descs[] = { { .ic_guid = { .hv_guid = { 0x9e, 0xb6, 0xa8, 0xcf, 0x4a, 0x5b, 0xc0, 0x4c, 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a} }, .ic_desc = "Hyper-V HID device" }, VMBUS_IC_DESC_END }; /* TODO: add GUID support to devmatch(8) to export vmbus_hid_descs directly */ const struct { char *guid; } vmbus_hid_descs_pnp[] = {{ "cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a" }}; static int hv_hid_attach(device_t dev); static int hv_hid_detach(device_t dev); static int hv_hid_connect_vsp(hv_hid_sc *sc) { struct vmbus_xact *xact; hv_hid_msg *req; const hv_hid_msg *resp; size_t resplen; int ret; xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_REQ_SZ); if (xact == NULL) { device_printf(sc->dev, "no xact for init"); return (ENODEV); } req = vmbus_xact_req_data(xact); req->type = HV_HID_MSG_DATA; req->size = sizeof(sh_proto_req); req->req.hdr.type = SH_PROTO_REQ; req->req.hdr.size = sizeof(u_int); req->req.ver = HV_HID_VER; vmbus_xact_activate(xact); ret = vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, VMBUS_CHANPKT_FLAG_RC, req, HV_HID_REQ_SZ, (uint64_t)(uintptr_t)xact); if (ret != 0) { device_printf(sc->dev, "failed to send proto req\n"); vmbus_xact_deactivate(xact); return (ret); } resp = vmbus_chan_xact_wait(sc->hs_chan, xact, &resplen, true); if (resplen != HV_HID_RESP_SZ || !resp->resp.app) { device_printf(sc->dev, "proto req failed\n"); ret = ENODEV; } vmbus_xact_put(xact); return (ret); } static void hv_hid_receive(hv_hid_sc *sc, struct vmbus_chanpkt_hdr *pkt) { const hv_hid_msg *msg; sh_msg_type msg_type; uint32_t msg_len; void *rdesc; msg = VMBUS_CHANPKT_CONST_DATA(pkt); msg_len = VMBUS_CHANPKT_DATALEN(pkt); if (msg->type != HV_HID_MSG_DATA) return; if (msg_len <= sizeof(hv_hid_pmsg)) { device_printf(sc->dev, "invalid packet length\n"); return; } msg_type = msg->msg.hdr.type; switch (msg_type) { case SH_PROTO_RESP: { struct vmbus_xact_ctx *xact_ctx; xact_ctx = sc->hs_xact_ctx; if (xact_ctx != NULL) { vmbus_xact_ctx_wakeup(xact_ctx, VMBUS_CHANPKT_CONST_DATA(pkt), VMBUS_CHANPKT_DATALEN(pkt)); } break; } case SH_DEVINFO: { struct vmbus_xact *xact; struct hid_device_info *hdi; hv_hid_msg ack; const sh_devinfo *devinfo; const sh_hdesc *hdesc; /* Send ack */ ack.type = HV_HID_MSG_DATA; ack.size = sizeof(sh_devinfo_ack); ack.ack.hdr.type = SH_DEVINFO_ACK; ack.ack.hdr.size = 1; ack.ack.rsvd = 0; xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_ACK_SZ); if (xact == NULL) break; vmbus_xact_activate(xact); (void) vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, 0, &ack, HV_HID_ACK_SZ, (uint64_t)(uintptr_t)xact); vmbus_xact_deactivate(xact); vmbus_xact_put(xact); /* Check for resume from hibernation */ if (sc->rdesc != NULL) break; /* Parse devinfo response */ devinfo = &msg->dresp.devinfo; hdesc = &msg->dresp.hdesc; if (hdesc->bLength == 0) break; hdi = &sc->hdi; memset(hdi, 0, sizeof(*hdi)); hdi->rdescsize = le16toh(hdesc->hcdesc[0].wDescriptorLength); if (hdi->rdescsize == 0) break; strlcpy(hdi->name, "Hyper-V", sizeof(hdi->name)); hdi->idBus = BUS_VIRTUAL; hdi->idVendor = le16toh(devinfo->vendor); hdi->idProduct = le16toh(devinfo->product); hdi->idVersion = le16toh(devinfo->version); /* Save rdesc copy */ rdesc = malloc(hdi->rdescsize, M_DEVBUF, M_WAITOK | M_ZERO); memcpy(rdesc, (const uint8_t *)hdesc + hdesc->bLength, hdi->rdescsize); mtx_lock(&sc->mtx); sc->rdesc = rdesc; wakeup(sc); mtx_unlock(&sc->mtx); break; } case SH_INPUT_REPORT: { mtx_lock(&sc->mtx); if (sc->intr != NULL && sc->intr_on) sc->intr(sc->intr_ctx, __DECONST(void *, msg->irep.buffer), msg->irep.hdr.size); mtx_unlock(&sc->mtx); break; } default: break; } } static void hv_hid_read_channel(struct vmbus_channel *channel, void *ctx) { hv_hid_sc *sc; uint8_t *buf; int buflen; int ret; sc = ctx; buf = sc->buf; buflen = sc->buflen; for (;;) { struct vmbus_chanpkt_hdr *pkt; int rcvd; pkt = (struct vmbus_chanpkt_hdr *)buf; rcvd = buflen; ret = vmbus_chan_recv_pkt(channel, pkt, &rcvd); if (__predict_false(ret == ENOBUFS)) { buflen = sc->buflen * 2; while (buflen < rcvd) buflen *= 2; buf = malloc(buflen, M_DEVBUF, M_WAITOK | M_ZERO); device_printf(sc->dev, "expand recvbuf %d -> %d\n", sc->buflen, buflen); free(sc->buf, M_DEVBUF); sc->buf = buf; sc->buflen = buflen; continue; } else if (__predict_false(ret == EAGAIN)) { /* No more channel packets; done! */ break; } KASSERT(ret == 0, ("vmbus_chan_recv_pkt failed: %d", ret)); switch (pkt->cph_type) { case VMBUS_CHANPKT_TYPE_COMP: case VMBUS_CHANPKT_TYPE_RXBUF: device_printf(sc->dev, "unhandled event: %d\n", pkt->cph_type); break; case VMBUS_CHANPKT_TYPE_INBAND: hv_hid_receive(sc, pkt); break; default: device_printf(sc->dev, "unknown event: %d\n", pkt->cph_type); break; } } } static int hv_hid_probe(device_t dev) { device_t bus; const struct vmbus_ic_desc *d; if (resource_disabled(device_get_name(dev), 0)) return (ENXIO); bus = device_get_parent(dev); for (d = vmbus_hid_descs; d->ic_desc != NULL; ++d) { if (VMBUS_PROBE_GUID(bus, dev, &d->ic_guid) == 0) { device_set_desc(dev, d->ic_desc); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int hv_hid_attach(device_t dev) { device_t child; hv_hid_sc *sc; int ret; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mtx, "hvhid lock", NULL, MTX_DEF); sc->hs_chan = vmbus_get_channel(dev); sc->hs_xact_ctx = vmbus_xact_ctx_create(bus_get_dma_tag(dev), HV_HID_REQ_MAX, HV_HID_RESP_MAX, 0); if (sc->hs_xact_ctx == NULL) { ret = ENOMEM; goto out; } sc->buflen = HV_BUFSIZ; sc->buf = malloc(sc->buflen, M_DEVBUF, M_WAITOK | M_ZERO); vmbus_chan_set_readbatch(sc->hs_chan, false); ret = vmbus_chan_open(sc->hs_chan, HV_HID_RINGBUFF_SEND_SZ, HV_HID_RINGBUFF_RECV_SZ, NULL, 0, hv_hid_read_channel, sc); if (ret != 0) goto out; ret = hv_hid_connect_vsp(sc); if (ret != 0) goto out; /* Wait until we have devinfo (or arbitrary timeout of 3s) */ mtx_lock(&sc->mtx); if (sc->rdesc == NULL) ret = mtx_sleep(sc, &sc->mtx, 0, "hvhid", hz * 3); mtx_unlock(&sc->mtx); if (ret != 0) { ret = ENODEV; goto out; } - child = device_add_child(sc->dev, "hidbus", -1); + child = device_add_child(sc->dev, "hidbus", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(sc->dev, "failed to add hidbus\n"); ret = ENOMEM; goto out; } device_set_ivars(child, &sc->hdi); bus_attach_children(dev); out: if (ret != 0) hv_hid_detach(dev); return (ret); } static int hv_hid_detach(device_t dev) { hv_hid_sc *sc; int ret; sc = device_get_softc(dev); ret = bus_generic_detach(dev); if (ret != 0) return (ret); if (sc->hs_xact_ctx != NULL) vmbus_xact_ctx_destroy(sc->hs_xact_ctx); vmbus_chan_close(vmbus_get_channel(dev)); free(sc->buf, M_DEVBUF); free(sc->rdesc, M_DEVBUF); mtx_destroy(&sc->mtx); return (0); } static void hv_hid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, void *ctx, struct hid_rdesc_info *rdesc) { hv_hid_sc *sc; if (intr == NULL) return; sc = device_get_softc(dev); sc->intr = intr; sc->intr_on = false; sc->intr_ctx = ctx; rdesc->rdsize = rdesc->isize; } static void hv_hid_intr_unsetup(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); sc->intr = NULL; sc->intr_on = false; sc->intr_ctx = NULL; } static int hv_hid_intr_start(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); sc->intr_on = true; mtx_unlock(&sc->mtx); return (0); } static int hv_hid_intr_stop(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); sc->intr_on = false; mtx_unlock(&sc->mtx); return (0); } static int hv_hid_get_rdesc(device_t dev, device_t child __unused, void *buf, hid_size_t len) { hv_hid_sc *sc; sc = device_get_softc(dev); if (len < sc->hdi.rdescsize) return (EMSGSIZE); memcpy(buf, sc->rdesc, len); return (0); } static device_method_t hv_hid_methods[] = { DEVMETHOD(device_probe, hv_hid_probe), DEVMETHOD(device_attach, hv_hid_attach), DEVMETHOD(device_detach, hv_hid_detach), DEVMETHOD(hid_intr_setup, hv_hid_intr_setup), DEVMETHOD(hid_intr_unsetup, hv_hid_intr_unsetup), DEVMETHOD(hid_intr_start, hv_hid_intr_start), DEVMETHOD(hid_intr_stop, hv_hid_intr_stop), DEVMETHOD(hid_get_rdesc, hv_hid_get_rdesc), DEVMETHOD_END, }; static driver_t hv_hid_driver = { .name = "hvhid", .methods = hv_hid_methods, .size = sizeof(hv_hid_sc), }; DRIVER_MODULE(hv_hid, vmbus, hv_hid_driver, NULL, NULL); MODULE_VERSION(hv_hid, 1); MODULE_DEPEND(hv_hid, hidbus, 1, 1, 1); MODULE_DEPEND(hv_hid, hms, 1, 1, 1); MODULE_DEPEND(hv_hid, vmbus, 1, 1, 1); MODULE_PNP_INFO("Z:classid", vmbus, hv_hid, vmbus_hid_descs_pnp, nitems(vmbus_hid_descs_pnp)); diff --git a/sys/dev/hyperv/vmbus/vmbus_et.c b/sys/dev/hyperv/vmbus/vmbus_et.c index 21b1cd9e4e39..33eb94daacd3 100644 --- a/sys/dev/hyperv/vmbus/vmbus_et.c +++ b/sys/dev/hyperv/vmbus/vmbus_et.c @@ -1,201 +1,201 @@ /*- * Copyright (c) 2015,2016-2017 Microsoft Corp. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #if defined(__aarch64__) #include #else #include #endif #include #include #include #define VMBUS_ET_NAME "hvet" #define MSR_HV_STIMER0_CFG_SINT \ ((((uint64_t)VMBUS_SINT_TIMER) << MSR_HV_STIMER_CFG_SINT_SHIFT) & \ MSR_HV_STIMER_CFG_SINT_MASK) /* * Additionally required feature: * - SynIC is needed for interrupt generation. */ #define CPUID_HV_ET_MASK (CPUID_HV_MSR_SYNIC | \ CPUID_HV_MSR_SYNTIMER) static void vmbus_et_identify(driver_t *, device_t); static int vmbus_et_probe(device_t); static int vmbus_et_attach(device_t); static int vmbus_et_detach(device_t); static int vmbus_et_start(struct eventtimer *, sbintime_t, sbintime_t); static struct eventtimer vmbus_et; static device_method_t vmbus_et_methods[] = { DEVMETHOD(device_identify, vmbus_et_identify), DEVMETHOD(device_probe, vmbus_et_probe), DEVMETHOD(device_attach, vmbus_et_attach), DEVMETHOD(device_detach, vmbus_et_detach), DEVMETHOD_END }; static driver_t vmbus_et_driver = { VMBUS_ET_NAME, vmbus_et_methods, 0 }; DRIVER_MODULE(hv_et, vmbus, vmbus_et_driver, NULL, NULL); MODULE_VERSION(hv_et, 1); static __inline uint64_t hyperv_sbintime2count(sbintime_t time) { struct timespec val; val = sbttots(time); return (val.tv_sec * HYPERV_TIMER_FREQ) + (val.tv_nsec / HYPERV_TIMER_NS_FACTOR); } static int vmbus_et_start(struct eventtimer *et __unused, sbintime_t first, sbintime_t period __unused) { uint64_t current; current = hyperv_tc64(); current += hyperv_sbintime2count(first); wrmsr(MSR_HV_STIMER0_COUNT, current); return (0); } void vmbus_et_intr(struct trapframe *frame) { struct trapframe *oldframe; struct thread *td; if (vmbus_et.et_active) { td = curthread; td->td_intr_nesting_level++; oldframe = td->td_intr_frame; td->td_intr_frame = frame; vmbus_et.et_event_cb(&vmbus_et, vmbus_et.et_arg); td->td_intr_frame = oldframe; td->td_intr_nesting_level--; } } static void vmbus_et_identify(driver_t *driver, device_t parent) { if (device_get_unit(parent) != 0 || - device_find_child(parent, VMBUS_ET_NAME, -1) != NULL || + device_find_child(parent, VMBUS_ET_NAME, DEVICE_UNIT_ANY) != NULL || (hyperv_features & CPUID_HV_ET_MASK) != CPUID_HV_ET_MASK || hyperv_tc64 == NULL) return; device_add_child(parent, VMBUS_ET_NAME, DEVICE_UNIT_ANY); } static int vmbus_et_probe(device_t dev) { if (resource_disabled(VMBUS_ET_NAME, 0)) return (ENXIO); device_set_desc(dev, "Hyper-V event timer"); return (BUS_PROBE_NOWILDCARD); } static void vmbus_et_config(void *arg __unused) { /* * Make sure that STIMER0 is really disabled before writing * to STIMER0_CONFIG. * * "Writing to the configuration register of a timer that * is already enabled may result in undefined behaviour." */ for (;;) { uint64_t val; /* Stop counting, and this also implies disabling STIMER0 */ wrmsr(MSR_HV_STIMER0_COUNT, 0); val = rdmsr(MSR_HV_STIMER0_CONFIG); if ((val & MSR_HV_STIMER_CFG_ENABLE) == 0) break; cpu_spinwait(); } wrmsr(MSR_HV_STIMER0_CONFIG, MSR_HV_STIMER_CFG_AUTOEN | MSR_HV_STIMER0_CFG_SINT); } static int vmbus_et_attach(device_t dev) { /* TODO: use independent IDT vector */ vmbus_et.et_name = "Hyper-V"; vmbus_et.et_flags = ET_FLAGS_ONESHOT | ET_FLAGS_PERCPU; vmbus_et.et_quality = 1000; vmbus_et.et_frequency = HYPERV_TIMER_FREQ; vmbus_et.et_min_period = (0x00000001ULL << 32) / HYPERV_TIMER_FREQ; vmbus_et.et_max_period = (0xfffffffeULL << 32) / HYPERV_TIMER_FREQ; vmbus_et.et_start = vmbus_et_start; /* * Delay a bit to make sure that hyperv_tc64 will not return 0, * since writing 0 to STIMER0_COUNT will disable STIMER0. */ DELAY(100); smp_rendezvous(NULL, vmbus_et_config, NULL, NULL); return (et_register(&vmbus_et)); } static int vmbus_et_detach(device_t dev) { return (et_deregister(&vmbus_et)); } diff --git a/sys/dev/ichsmb/ichsmb.c b/sys/dev/ichsmb/ichsmb.c index 28503b9e574d..c5e9e2f1b9ed 100644 --- a/sys/dev/ichsmb/ichsmb.c +++ b/sys/dev/ichsmb/ichsmb.c @@ -1,705 +1,706 @@ /*- * ichsmb.c * * Author: Archie Cobbs * Copyright (c) 2000 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include /* * Support for the SMBus controller logical device which is part of the * Intel 81801AA (ICH) and 81801AB (ICH0) I/O controller hub chips. * * This driver assumes that the generic SMBus code will ensure that * at most one process at a time calls into the SMBus methods below. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Enable debugging by defining ICHSMB_DEBUG to a non-zero value. */ #define ICHSMB_DEBUG 0 #if ICHSMB_DEBUG != 0 #define DBG(fmt, args...) \ do { printf("%s: " fmt, __func__ , ## args); } while (0) #else #define DBG(fmt, args...) do { } while (0) #endif /* * Our child device driver name */ #define DRIVER_SMBUS "smbus" /* * Internal functions */ static int ichsmb_wait(sc_p sc); /******************************************************************** BUS-INDEPENDENT BUS METHODS ********************************************************************/ /* * Handle probe-time duties that are independent of the bus * our device lives on. */ int ichsmb_probe(device_t dev) { return (BUS_PROBE_DEFAULT); } /* * Handle attach-time duties that are independent of the bus * our device lives on. */ int ichsmb_attach(device_t dev) { const sc_p sc = device_get_softc(dev); int error; /* Create mutex */ mtx_init(&sc->mutex, device_get_nameunit(dev), "ichsmb", MTX_DEF); /* Add child: an instance of the "smbus" device */ - if ((sc->smb = device_add_child(dev, DRIVER_SMBUS, -1)) == NULL) { + if ((sc->smb = device_add_child(dev, DRIVER_SMBUS, + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "no \"%s\" child found\n", DRIVER_SMBUS); error = ENXIO; goto fail; } /* Clear interrupt conditions */ bus_write_1(sc->io_res, ICH_HST_STA, 0xff); /* Set up interrupt handler */ error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, ichsmb_device_intr, sc, &sc->irq_handle); if (error != 0) { device_printf(dev, "can't setup irq\n"); goto fail; } /* Attach children when interrupts are available */ bus_delayed_attach_children(dev); return (0); fail: mtx_destroy(&sc->mutex); return (error); } /******************************************************************** SMBUS METHODS ********************************************************************/ int ichsmb_callback(device_t dev, int index, void *data) { int smb_error = 0; DBG("index=%d how=%d\n", index, data ? *(int *)data : -1); switch (index) { case SMB_REQUEST_BUS: break; case SMB_RELEASE_BUS: break; default: smb_error = SMB_EABORT; /* XXX */ break; } DBG("smb_error=%d\n", smb_error); return (smb_error); } int ichsmb_quick(device_t dev, u_char slave, int how) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x how=%d\n", slave, how); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); switch (how) { case SMB_QREAD: case SMB_QWRITE: mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_QUICK; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | (how == SMB_QREAD ? ICH_XMIT_SLVA_READ : ICH_XMIT_SLVA_WRITE)); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); smb_error = ichsmb_wait(sc); mtx_unlock(&sc->mutex); break; default: smb_error = SMB_ENOTSUPP; } DBG("smb_error=%d\n", smb_error); return (smb_error); } int ichsmb_sendb(device_t dev, u_char slave, char byte) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x byte=0x%02x\n", slave, (u_char)byte); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_BYTE; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_WRITE); bus_write_1(sc->io_res, ICH_HST_CMD, byte); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); smb_error = ichsmb_wait(sc); mtx_unlock(&sc->mutex); DBG("smb_error=%d\n", smb_error); return (smb_error); } int ichsmb_recvb(device_t dev, u_char slave, char *byte) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x\n", slave); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_BYTE; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_READ); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); if ((smb_error = ichsmb_wait(sc)) == SMB_ENOERR) *byte = bus_read_1(sc->io_res, ICH_D0); mtx_unlock(&sc->mutex); DBG("smb_error=%d byte=0x%02x\n", smb_error, (u_char)*byte); return (smb_error); } int ichsmb_writeb(device_t dev, u_char slave, char cmd, char byte) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x cmd=0x%02x byte=0x%02x\n", slave, (u_char)cmd, (u_char)byte); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_BYTE_DATA; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_WRITE); bus_write_1(sc->io_res, ICH_HST_CMD, cmd); bus_write_1(sc->io_res, ICH_D0, byte); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); smb_error = ichsmb_wait(sc); mtx_unlock(&sc->mutex); DBG("smb_error=%d\n", smb_error); return (smb_error); } int ichsmb_writew(device_t dev, u_char slave, char cmd, short word) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x cmd=0x%02x word=0x%04x\n", slave, (u_char)cmd, (u_int16_t)word); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_WORD_DATA; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_WRITE); bus_write_1(sc->io_res, ICH_HST_CMD, cmd); bus_write_1(sc->io_res, ICH_D0, word & 0xff); bus_write_1(sc->io_res, ICH_D1, word >> 8); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); smb_error = ichsmb_wait(sc); mtx_unlock(&sc->mutex); DBG("smb_error=%d\n", smb_error); return (smb_error); } int ichsmb_readb(device_t dev, u_char slave, char cmd, char *byte) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x cmd=0x%02x\n", slave, (u_char)cmd); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_BYTE_DATA; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_READ); bus_write_1(sc->io_res, ICH_HST_CMD, cmd); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); if ((smb_error = ichsmb_wait(sc)) == SMB_ENOERR) *byte = bus_read_1(sc->io_res, ICH_D0); mtx_unlock(&sc->mutex); DBG("smb_error=%d byte=0x%02x\n", smb_error, (u_char)*byte); return (smb_error); } int ichsmb_readw(device_t dev, u_char slave, char cmd, short *word) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x cmd=0x%02x\n", slave, (u_char)cmd); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_WORD_DATA; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_READ); bus_write_1(sc->io_res, ICH_HST_CMD, cmd); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); if ((smb_error = ichsmb_wait(sc)) == SMB_ENOERR) { *word = (bus_read_1(sc->io_res, ICH_D0) & 0xff) | (bus_read_1(sc->io_res, ICH_D1) << 8); } mtx_unlock(&sc->mutex); DBG("smb_error=%d word=0x%04x\n", smb_error, (u_int16_t)*word); return (smb_error); } int ichsmb_pcall(device_t dev, u_char slave, char cmd, short sdata, short *rdata) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x cmd=0x%02x sdata=0x%04x\n", slave, (u_char)cmd, (u_int16_t)sdata); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_PROC_CALL; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_WRITE); bus_write_1(sc->io_res, ICH_HST_CMD, cmd); bus_write_1(sc->io_res, ICH_D0, sdata & 0xff); bus_write_1(sc->io_res, ICH_D1, sdata >> 8); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); if ((smb_error = ichsmb_wait(sc)) == SMB_ENOERR) { *rdata = (bus_read_1(sc->io_res, ICH_D0) & 0xff) | (bus_read_1(sc->io_res, ICH_D1) << 8); } mtx_unlock(&sc->mutex); DBG("smb_error=%d rdata=0x%04x\n", smb_error, (u_int16_t)*rdata); return (smb_error); } int ichsmb_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x cmd=0x%02x count=%d\n", slave, (u_char)cmd, count); #if ICHSMB_DEBUG #define DISP(ch) (((ch) < 0x20 || (ch) >= 0x7e) ? '.' : (ch)) { u_char *p; for (p = (u_char *)buf; p - (u_char *)buf < 32; p += 8) { DBG("%02x: %02x %02x %02x %02x %02x %02x %02x %02x" " %c%c%c%c%c%c%c%c", (p - (u_char *)buf), p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], DISP(p[0]), DISP(p[1]), DISP(p[2]), DISP(p[3]), DISP(p[4]), DISP(p[5]), DISP(p[6]), DISP(p[7])); } } #undef DISP #endif KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); if (count < 1 || count > 32) return (SMB_EINVAL); bcopy(buf, sc->block_data, count); sc->block_count = count; sc->block_index = 1; /* buf[0] is written here */ sc->block_write = true; mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_BLOCK; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_WRITE); bus_write_1(sc->io_res, ICH_HST_CMD, cmd); bus_write_1(sc->io_res, ICH_D0, count); bus_write_1(sc->io_res, ICH_BLOCK_DB, buf[0]); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); smb_error = ichsmb_wait(sc); mtx_unlock(&sc->mutex); DBG("smb_error=%d\n", smb_error); return (smb_error); } int ichsmb_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf) { const sc_p sc = device_get_softc(dev); int smb_error; DBG("slave=0x%02x cmd=0x%02x\n", slave, (u_char)cmd); KASSERT(sc->ich_cmd == -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); bzero(sc->block_data, sizeof(sc->block_data)); sc->block_count = 0; sc->block_index = 0; sc->block_write = false; mtx_lock(&sc->mutex); sc->ich_cmd = ICH_HST_CNT_SMB_CMD_BLOCK; bus_write_1(sc->io_res, ICH_XMIT_SLVA, slave | ICH_XMIT_SLVA_READ); bus_write_1(sc->io_res, ICH_HST_CMD, cmd); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd); if ((smb_error = ichsmb_wait(sc)) == SMB_ENOERR) { bcopy(sc->block_data, buf, sc->block_count); *count = sc->block_count; } mtx_unlock(&sc->mutex); DBG("smb_error=%d\n", smb_error); #if ICHSMB_DEBUG #define DISP(ch) (((ch) < 0x20 || (ch) >= 0x7e) ? '.' : (ch)) { u_char *p; for (p = (u_char *)buf; p - (u_char *)buf < 32; p += 8) { DBG("%02x: %02x %02x %02x %02x %02x %02x %02x %02x" " %c%c%c%c%c%c%c%c", (p - (u_char *)buf), p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], DISP(p[0]), DISP(p[1]), DISP(p[2]), DISP(p[3]), DISP(p[4]), DISP(p[5]), DISP(p[6]), DISP(p[7])); } } #undef DISP #endif return (smb_error); } /******************************************************************** OTHER FUNCTIONS ********************************************************************/ /* * This table describes what interrupts we should ever expect to * see after each ICH command, not including the SMBALERT interrupt. */ static const u_int8_t ichsmb_state_irqs[] = { /* quick */ (ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR), /* byte */ (ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR), /* byte data */ (ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR), /* word data */ (ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR), /* process call */ (ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR), /* block */ (ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR | ICH_HST_STA_BYTE_DONE_STS), /* i2c read (not used) */ (ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR | ICH_HST_STA_BYTE_DONE_STS) }; /* * Interrupt handler. This handler is bus-independent. Note that our * interrupt may be shared, so we must handle "false" interrupts. */ void ichsmb_device_intr(void *cookie) { const sc_p sc = cookie; const device_t dev = sc->dev; const int maxloops = 16; u_int8_t status; u_int8_t ok_bits; int cmd_index; int count; mtx_lock(&sc->mutex); for (count = 0; count < maxloops; count++) { /* Get and reset status bits */ status = bus_read_1(sc->io_res, ICH_HST_STA); #if ICHSMB_DEBUG if ((status & ~(ICH_HST_STA_INUSE_STS | ICH_HST_STA_HOST_BUSY)) || count > 0) { DBG("%d stat=0x%02x\n", count, status); } #endif status &= ~(ICH_HST_STA_INUSE_STS | ICH_HST_STA_HOST_BUSY | ICH_HST_STA_SMBALERT_STS); if (status == 0) break; /* Check for unexpected interrupt */ ok_bits = ICH_HST_STA_SMBALERT_STS; if (sc->killed) { sc->killed = 0; ok_bits |= ICH_HST_STA_FAILED; bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_INTREN); } if (sc->ich_cmd != -1) { cmd_index = sc->ich_cmd >> 2; KASSERT(cmd_index < sizeof(ichsmb_state_irqs), ("%s: ich_cmd=%d", device_get_nameunit(dev), sc->ich_cmd)); ok_bits |= ichsmb_state_irqs[cmd_index]; } if ((status & ~ok_bits) != 0) { device_printf(dev, "irq 0x%02x during 0x%02x\n", status, sc->ich_cmd); bus_write_1(sc->io_res, ICH_HST_STA, (status & ~ok_bits)); continue; } /* Check for killed / aborted command */ if (status & ICH_HST_STA_FAILED) { sc->smb_error = SMB_EABORT; goto finished; } /* Check for bus error */ if (status & ICH_HST_STA_BUS_ERR) { sc->smb_error = SMB_ECOLLI; /* XXX SMB_EBUSERR? */ goto finished; } /* Check for device error */ if (status & ICH_HST_STA_DEV_ERR) { sc->smb_error = SMB_ENOACK; /* or SMB_ETIMEOUT? */ goto finished; } /* Check for byte completion in block transfer */ if (status & ICH_HST_STA_BYTE_DONE_STS) { if (sc->block_write) { if (sc->block_index < sc->block_count) { /* Write next byte */ bus_write_1(sc->io_res, ICH_BLOCK_DB, sc->block_data[sc->block_index++]); } } else { /* First interrupt, get the count also */ if (sc->block_index == 0) { sc->block_count = bus_read_1( sc->io_res, ICH_D0); if (sc->block_count < 1 || sc->block_count > 32) { device_printf(dev, "block read " "wrong length: %d\n", sc->block_count); bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_KILL | ICH_HST_CNT_INTREN); sc->block_count = 0; sc->killed = true; } } /* Get next byte, if any */ if (sc->block_index < sc->block_count) { /* Read next byte */ sc->block_data[sc->block_index++] = bus_read_1(sc->io_res, ICH_BLOCK_DB); /* * Set "LAST_BYTE" bit before reading * the last byte of block data */ if (sc->block_index == sc->block_count - 1) { bus_write_1(sc->io_res, ICH_HST_CNT, ICH_HST_CNT_LAST_BYTE | ICH_HST_CNT_INTREN | sc->ich_cmd); } } } } /* Check command completion */ if (status & ICH_HST_STA_INTR) { sc->smb_error = SMB_ENOERR; finished: sc->ich_cmd = -1; bus_write_1(sc->io_res, ICH_HST_STA, status); wakeup(sc); break; } /* Clear status bits and try again */ bus_write_1(sc->io_res, ICH_HST_STA, status); } mtx_unlock(&sc->mutex); /* Too many loops? */ if (count == maxloops) { device_printf(dev, "interrupt loop, status=0x%02x\n", bus_read_1(sc->io_res, ICH_HST_STA)); } } /* * Wait for command completion. Assumes mutex is held. * Returns an SMB_* error code. */ static int ichsmb_wait(sc_p sc) { const device_t dev = sc->dev; int error, smb_error; KASSERT(sc->ich_cmd != -1, ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd)); mtx_assert(&sc->mutex, MA_OWNED); error = msleep(sc, &sc->mutex, PZERO, "ichsmb", hz / 4); DBG("msleep -> %d\n", error); switch (error) { case 0: smb_error = sc->smb_error; break; case EWOULDBLOCK: device_printf(dev, "device timeout, status=0x%02x\n", bus_read_1(sc->io_res, ICH_HST_STA)); sc->ich_cmd = -1; smb_error = SMB_ETIMEOUT; break; default: smb_error = SMB_EABORT; break; } return (smb_error); } /* * Release resources associated with device. */ void ichsmb_release_resources(sc_p sc) { const device_t dev = sc->dev; if (sc->irq_handle != NULL) { bus_teardown_intr(dev, sc->irq_res, sc->irq_handle); sc->irq_handle = NULL; } if (sc->irq_res != NULL) { bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); sc->irq_res = NULL; } if (sc->io_res != NULL) { bus_release_resource(dev, SYS_RES_IOPORT, sc->io_rid, sc->io_res); sc->io_res = NULL; } } int ichsmb_detach(device_t dev) { const sc_p sc = device_get_softc(dev); int error; error = bus_generic_detach(dev); if (error) return (error); ichsmb_release_resources(sc); mtx_destroy(&sc->mutex); return 0; } DRIVER_MODULE(smbus, ichsmb, smbus_driver, 0, 0); diff --git a/sys/dev/iicbus/controller/cadence/cdnc_i2c.c b/sys/dev/iicbus/controller/cadence/cdnc_i2c.c index ad18ff39961a..2e7950dab9c1 100644 --- a/sys/dev/iicbus/controller/cadence/cdnc_i2c.c +++ b/sys/dev/iicbus/controller/cadence/cdnc_i2c.c @@ -1,702 +1,702 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019-2020 Thomas Skibo * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* Cadence / Zynq i2c driver. * * Reference: Zynq-7000 All Programmable SoC Technical Reference Manual. * (v1.12.2) July 1, 2018. Xilinx doc UG585. I2C Controller is documented * in Chapter 20. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #ifdef I2CDEBUG #define DPRINTF(...) do { printf(__VA_ARGS__); } while (0) #else #define DPRINTF(...) do { } while (0) #endif #if 0 #define HWTYPE_CDNS_R1P10 1 #endif #define HWTYPE_CDNS_R1P14 2 static struct ofw_compat_data compat_data[] = { #if 0 {"cdns,i2c-r1p10", HWTYPE_CDNS_R1P10}, #endif {"cdns,i2c-r1p14", HWTYPE_CDNS_R1P14}, {NULL, 0} }; struct cdnc_i2c_softc { device_t dev; device_t iicbus; struct mtx sc_mtx; struct resource *mem_res; struct resource *irq_res; void *intrhandle; uint16_t cfg_reg_shadow; uint16_t istat; clk_t ref_clk; uint32_t ref_clock_freq; uint32_t i2c_clock_freq; int hwtype; int hold; /* sysctls */ unsigned int i2c_clk_real_freq; unsigned int interrupts; unsigned int timeout_ints; }; #define I2C_SC_LOCK(sc) mtx_lock(&(sc)->sc_mtx) #define I2C_SC_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) #define I2C_SC_LOCK_INIT(sc) \ mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->dev), NULL, MTX_DEF) #define I2C_SC_LOCK_DESTROY(sc) mtx_destroy(&(sc)->sc_mtx) #define I2C_SC_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) #define RD2(sc, off) (bus_read_2((sc)->mem_res, (off))) #define WR2(sc, off, val) (bus_write_2((sc)->mem_res, (off), (val))) #define RD1(sc, off) (bus_read_1((sc)->mem_res, (off))) #define WR1(sc, off, val) (bus_write_1((sc)->mem_res, (off), (val))) /* Cadence I2C controller device registers. */ #define CDNC_I2C_CR 0x0000 /* Config register. */ #define CDNC_I2C_CR_DIV_A_MASK (3 << 14) #define CDNC_I2C_CR_DIV_A_SHIFT 14 #define CDNC_I2C_CR_DIV_A(a) ((a) << 14) #define CDNC_I2C_CR_DIV_A_MAX 3 #define CDNC_I2C_CR_DIV_B_MASK (0x3f << 8) #define CDNC_I2C_CR_DIV_B_SHIFT 8 #define CDNC_I2C_CR_DIV_B(b) ((b) << 8) #define CDNC_I2C_CR_DIV_B_MAX 63 #define CDNC_I2C_CR_CLR_FIFO (1 << 6) #define CDNC_I2C_CR_SLVMON_MODE (1 << 5) #define CDNC_I2C_CR_HOLD (1 << 4) #define CDNC_I2C_CR_ACKEN (1 << 3) #define CDNC_I2C_CR_NEA (1 << 2) #define CDNC_I2C_CR_MAST (1 << 1) #define CDNC_I2C_CR_RNW (1 << 0) #define CDNC_I2C_SR 0x0004 /* Status register. */ #define CDNC_I2C_SR_BUS_ACTIVE (1 << 8) #define CDNC_I2C_SR_RX_OVF (1 << 7) #define CDNC_I2C_SR_TX_VALID (1 << 6) #define CDNC_I2C_SR_RX_VALID (1 << 5) #define CDNC_I2C_SR_RXRW (1 << 3) #define CDNC_I2C_ADDR 0x0008 /* i2c address register. */ #define CDNC_I2C_DATA 0x000C /* i2c data register. */ #define CDNC_I2C_ISR 0x0010 /* Int status register. */ #define CDNC_I2C_ISR_ARB_LOST (1 << 9) #define CDNC_I2C_ISR_RX_UNDF (1 << 7) #define CDNC_I2C_ISR_TX_OVF (1 << 6) #define CDNC_I2C_ISR_RX_OVF (1 << 5) #define CDNC_I2C_ISR_SLV_RDY (1 << 4) #define CDNC_I2C_ISR_XFER_TMOUT (1 << 3) #define CDNC_I2C_ISR_XFER_NACK (1 << 2) #define CDNC_I2C_ISR_XFER_DATA (1 << 1) #define CDNC_I2C_ISR_XFER_DONE (1 << 0) #define CDNC_I2C_ISR_ALL 0x2ff #define CDNC_I2C_TRANS_SIZE 0x0014 /* Transfer size. */ #define CDNC_I2C_PAUSE 0x0018 /* Slv Monitor Pause reg. */ #define CDNC_I2C_TIME_OUT 0x001C /* Time-out register. */ #define CDNC_I2C_TIME_OUT_MIN 31 #define CDNC_I2C_TIME_OUT_MAX 255 #define CDNC_I2C_IMR 0x0020 /* Int mask register. */ #define CDNC_I2C_IER 0x0024 /* Int enable register. */ #define CDNC_I2C_IDR 0x0028 /* Int disable register. */ #define CDNC_I2C_FIFO_SIZE 16 #define CDNC_I2C_DEFAULT_I2C_CLOCK 400000 /* 400Khz default */ #define CDNC_I2C_ISR_ERRS (CDNC_I2C_ISR_ARB_LOST | CDNC_I2C_ISR_RX_UNDF | \ CDNC_I2C_ISR_TX_OVF | CDNC_I2C_ISR_RX_OVF | CDNC_I2C_ISR_XFER_TMOUT | \ CDNC_I2C_ISR_XFER_NACK) /* Configure clock dividers. */ static int cdnc_i2c_set_freq(struct cdnc_i2c_softc *sc) { uint32_t div_a, div_b, err, clk_out; uint32_t best_div_a, best_div_b, best_err; best_div_a = 0; best_div_b = 0; best_err = ~0U; /* * The i2c controller has a two-stage clock divider to create * the "clock enable" signal used to sample the incoming SCL and * SDA signals. The Clock Enable signal is divided by 22 to create * the outgoing SCL signal. * * Try all div_a values and pick best match. */ for (div_a = 0; div_a <= CDNC_I2C_CR_DIV_A_MAX; div_a++) { div_b = sc->ref_clock_freq / (22 * sc->i2c_clock_freq * (div_a + 1)); if (div_b > CDNC_I2C_CR_DIV_B_MAX) continue; clk_out = sc->ref_clock_freq / (22 * (div_a + 1) * (div_b + 1)); err = clk_out > sc->i2c_clock_freq ? clk_out - sc->i2c_clock_freq : sc->i2c_clock_freq - clk_out; if (err < best_err) { best_err = err; best_div_a = div_a; best_div_b = div_b; } } if (best_err == ~0U) { device_printf(sc->dev, "cannot configure clock divider.\n"); return (EINVAL); /* out of range */ } clk_out = sc->ref_clock_freq / (22 * (best_div_a + 1) * (best_div_b + 1)); DPRINTF("%s: ref_clock_freq=%d i2c_clock_freq=%d\n", __func__, sc->ref_clock_freq, sc->i2c_clock_freq); DPRINTF("%s: div_a=%d div_b=%d real-freq=%d\n", __func__, best_div_a, best_div_b, clk_out); sc->cfg_reg_shadow &= ~(CDNC_I2C_CR_DIV_A_MASK | CDNC_I2C_CR_DIV_B_MASK); sc->cfg_reg_shadow |= CDNC_I2C_CR_DIV_A(best_div_a) | CDNC_I2C_CR_DIV_B(best_div_b); WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow); sc->i2c_clk_real_freq = clk_out; return (0); } /* Initialize hardware. */ static int cdnc_i2c_init_hw(struct cdnc_i2c_softc *sc) { /* Reset config register and clear FIFO. */ sc->cfg_reg_shadow = 0; WR2(sc, CDNC_I2C_CR, CDNC_I2C_CR_CLR_FIFO); sc->hold = 0; /* Clear and disable all interrupts. */ WR2(sc, CDNC_I2C_ISR, CDNC_I2C_ISR_ALL); WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_ALL); /* Max out bogus time-out register. */ WR1(sc, CDNC_I2C_TIME_OUT, CDNC_I2C_TIME_OUT_MAX); /* Set up clock dividers. */ return (cdnc_i2c_set_freq(sc)); } static int cdnc_i2c_errs(struct cdnc_i2c_softc *sc, uint16_t istat) { DPRINTF("%s: istat=0x%x\n", __func__, istat); /* XXX: clean up after errors. */ /* Reset config register and clear FIFO. */ sc->cfg_reg_shadow &= CDNC_I2C_CR_DIV_A_MASK | CDNC_I2C_CR_DIV_B_MASK; WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow | CDNC_I2C_CR_CLR_FIFO); sc->hold = 0; if (istat & CDNC_I2C_ISR_XFER_TMOUT) return (IIC_ETIMEOUT); else if (istat & CDNC_I2C_ISR_RX_UNDF) return (IIC_EUNDERFLOW); else if (istat & (CDNC_I2C_ISR_RX_OVF | CDNC_I2C_ISR_TX_OVF)) return (IIC_EOVERFLOW); else if (istat & CDNC_I2C_ISR_XFER_NACK) return (IIC_ENOACK); else if (istat & CDNC_I2C_ISR_ARB_LOST) return (IIC_EBUSERR); /* XXX: ???? */ else /* Should not happen */ return (IIC_NOERR); } static int cdnc_i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct cdnc_i2c_softc *sc = device_get_softc(dev); int error; DPRINTF("%s: speed=%d addr=0x%x\n", __func__, speed, addr); I2C_SC_LOCK(sc); sc->i2c_clock_freq = IICBUS_GET_FREQUENCY(sc->iicbus, speed); error = cdnc_i2c_init_hw(sc); I2C_SC_UNLOCK(sc); return (error ? IIC_ENOTSUPP : IIC_NOERR); } static void cdnc_i2c_intr(void *arg) { struct cdnc_i2c_softc *sc = (struct cdnc_i2c_softc *)arg; uint16_t status; I2C_SC_LOCK(sc); sc->interrupts++; /* Read active interrupts. */ status = RD2(sc, CDNC_I2C_ISR) & ~RD2(sc, CDNC_I2C_IMR); /* Clear interrupts. */ WR2(sc, CDNC_I2C_ISR, status); if (status & CDNC_I2C_ISR_XFER_TMOUT) sc->timeout_ints++; sc->istat |= status; if (status) wakeup(sc); I2C_SC_UNLOCK(sc); } static int cdnc_i2c_xfer_rd(struct cdnc_i2c_softc *sc, struct iic_msg *msg) { int error = IIC_NOERR; uint16_t flags = msg->flags; uint16_t len = msg->len; int idx = 0, nbytes, last, first = 1; uint16_t statr; DPRINTF("%s: flags=0x%x len=%d\n", __func__, flags, len); #if 0 if (sc->hwtype == HWTYPE_CDNS_R1P10 && (flags & IIC_M_NOSTOP)) return (IIC_ENOTSUPP); #endif I2C_SC_ASSERT_LOCKED(sc); /* Program config register. */ sc->cfg_reg_shadow &= CDNC_I2C_CR_DIV_A_MASK | CDNC_I2C_CR_DIV_B_MASK; sc->cfg_reg_shadow |= CDNC_I2C_CR_HOLD | CDNC_I2C_CR_ACKEN | CDNC_I2C_CR_NEA | CDNC_I2C_CR_MAST | CDNC_I2C_CR_RNW; WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow | CDNC_I2C_CR_CLR_FIFO); sc->hold = 1; while (len > 0) { nbytes = MIN(CDNC_I2C_FIFO_SIZE - 2, len); WR1(sc, CDNC_I2C_TRANS_SIZE, nbytes); last = nbytes == len && !(flags & IIC_M_NOSTOP); if (last) { /* Clear HOLD bit on last transfer. */ sc->cfg_reg_shadow &= ~CDNC_I2C_CR_HOLD; WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow); sc->hold = 0; } /* Writing slv address for a start or repeated start. */ if (first && !(flags & IIC_M_NOSTART)) WR2(sc, CDNC_I2C_ADDR, msg->slave >> 1); first = 0; /* Enable FIFO interrupts and wait. */ if (last) WR2(sc, CDNC_I2C_IER, CDNC_I2C_ISR_XFER_DONE | CDNC_I2C_ISR_ERRS); else WR2(sc, CDNC_I2C_IER, CDNC_I2C_ISR_XFER_DATA | CDNC_I2C_ISR_ERRS); error = mtx_sleep(sc, &sc->sc_mtx, 0, "cdi2c", hz); /* Disable FIFO interrupts. */ WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_XFER_DATA | CDNC_I2C_ISR_XFER_DONE | CDNC_I2C_ISR_ERRS); if (error == EWOULDBLOCK) error = cdnc_i2c_errs(sc, CDNC_I2C_ISR_XFER_TMOUT); else if (sc->istat & CDNC_I2C_ISR_ERRS) error = cdnc_i2c_errs(sc, sc->istat); sc->istat = 0; if (error != IIC_NOERR) break; /* Read nbytes from FIFO. */ while (nbytes-- > 0) { statr = RD2(sc, CDNC_I2C_SR); if (!(statr & CDNC_I2C_SR_RX_VALID)) { printf("%s: RX FIFO underflow?\n", __func__); break; } msg->buf[idx++] = RD2(sc, CDNC_I2C_DATA); len--; } } return (error); } static int cdnc_i2c_xfer_wr(struct cdnc_i2c_softc *sc, struct iic_msg *msg) { int error = IIC_NOERR; uint16_t flags = msg->flags; uint16_t len = msg->len; int idx = 0, nbytes, last, first = 1; DPRINTF("%s: flags=0x%x len=%d\n", __func__, flags, len); I2C_SC_ASSERT_LOCKED(sc); /* Program config register. */ sc->cfg_reg_shadow &= CDNC_I2C_CR_DIV_A_MASK | CDNC_I2C_CR_DIV_B_MASK; sc->cfg_reg_shadow |= CDNC_I2C_CR_HOLD | CDNC_I2C_CR_ACKEN | CDNC_I2C_CR_NEA | CDNC_I2C_CR_MAST; WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow | CDNC_I2C_CR_CLR_FIFO); sc->hold = 1; while (len > 0) { /* Put as much data into fifo as you can. */ nbytes = MIN(len, CDNC_I2C_FIFO_SIZE - RD1(sc, CDNC_I2C_TRANS_SIZE) - 1); len -= nbytes; while (nbytes-- > 0) WR2(sc, CDNC_I2C_DATA, msg->buf[idx++]); last = len == 0 && !(flags & IIC_M_NOSTOP); if (last) { /* Clear HOLD bit on last transfer. */ sc->cfg_reg_shadow &= ~CDNC_I2C_CR_HOLD; WR2(sc, CDNC_I2C_CR, sc->cfg_reg_shadow); sc->hold = 0; } /* Perform START if this is start or repeated start. */ if (first && !(flags & IIC_M_NOSTART)) WR2(sc, CDNC_I2C_ADDR, msg->slave >> 1); first = 0; /* Enable FIFO interrupts. */ WR2(sc, CDNC_I2C_IER, CDNC_I2C_ISR_XFER_DONE | CDNC_I2C_ISR_ERRS); /* Wait for end of data transfer. */ error = mtx_sleep(sc, &sc->sc_mtx, 0, "cdi2c", hz); /* Disable FIFO interrupts. */ WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_XFER_DONE | CDNC_I2C_ISR_ERRS); if (error == EWOULDBLOCK) error = cdnc_i2c_errs(sc, CDNC_I2C_ISR_XFER_TMOUT); else if (sc->istat & CDNC_I2C_ISR_ERRS) error = cdnc_i2c_errs(sc, sc->istat); sc->istat = 0; if (error) break; } return (error); } static int cdnc_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct cdnc_i2c_softc *sc = device_get_softc(dev); int i, error = IIC_NOERR; DPRINTF("%s: nmsgs=%d\n", __func__, nmsgs); I2C_SC_LOCK(sc); for (i = 0; i < nmsgs; i++) { DPRINTF("%s: msg[%d]: hold=%d slv=0x%x flags=0x%x len=%d\n", __func__, i, sc->hold, msgs[i].slave, msgs[i].flags, msgs[i].len); if (!sc->hold && (msgs[i].flags & IIC_M_NOSTART)) return (IIC_ENOTSUPP); if (msgs[i].flags & IIC_M_RD) { error = cdnc_i2c_xfer_rd(sc, &msgs[i]); if (error != IIC_NOERR) break; } else { error = cdnc_i2c_xfer_wr(sc, &msgs[i]); if (error != IIC_NOERR) break; } } I2C_SC_UNLOCK(sc); return (error); } static void cdnc_i2c_add_sysctls(device_t dev) { struct cdnc_i2c_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *ctx; struct sysctl_oid_list *child; ctx = device_get_sysctl_ctx(dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "i2c_clk_real_freq", CTLFLAG_RD, &sc->i2c_clk_real_freq, 0, "i2c clock real frequency"); SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "_interrupts", CTLFLAG_RD, &sc->interrupts, 0, "interrupt calls"); SYSCTL_ADD_UINT(ctx, child, OID_AUTO, "_timeouts", CTLFLAG_RD, &sc->timeout_ints, 0, "hardware timeout interrupts"); } static int cdnc_i2c_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Cadence I2C Controller"); return (BUS_PROBE_DEFAULT); } static int cdnc_i2c_detach(device_t); static int cdnc_i2c_attach(device_t dev) { struct cdnc_i2c_softc *sc; int rid, err; phandle_t node; pcell_t cell; uint64_t freq; sc = device_get_softc(dev); sc->dev = dev; sc->hwtype = ofw_bus_search_compatible(dev, compat_data)->ocd_data; I2C_SC_LOCK_INIT(sc); /* Get ref-clock and i2c-clock properties. */ node = ofw_bus_get_node(dev); if (OF_getprop(node, "ref-clock", &cell, sizeof(cell)) > 0) sc->ref_clock_freq = fdt32_to_cpu(cell); else if (clk_get_by_ofw_index(dev, node, 0, &sc->ref_clk) == 0) { if ((err = clk_enable(sc->ref_clk)) != 0) device_printf(dev, "Cannot enable clock. err=%d\n", err); else if ((err = clk_get_freq(sc->ref_clk, &freq)) != 0) device_printf(dev, "Cannot get clock frequency. err=%d\n", err); else sc->ref_clock_freq = freq; } else { device_printf(dev, "must have ref-clock property\n"); return (ENXIO); } if (OF_getprop(node, "clock-frequency", &cell, sizeof(cell)) > 0) sc->i2c_clock_freq = fdt32_to_cpu(cell); else sc->i2c_clock_freq = CDNC_I2C_DEFAULT_I2C_CLOCK; /* Get memory resource. */ rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem_res == NULL) { device_printf(dev, "could not allocate memory resources.\n"); cdnc_i2c_detach(dev); return (ENOMEM); } /* Allocate IRQ. */ rid = 0; sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->irq_res == NULL) { device_printf(dev, "could not allocate IRQ resource.\n"); cdnc_i2c_detach(dev); return (ENOMEM); } /* Activate the interrupt. */ err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, cdnc_i2c_intr, sc, &sc->intrhandle); if (err) { device_printf(dev, "could not setup IRQ.\n"); cdnc_i2c_detach(dev); return (err); } /* Configure the device. */ err = cdnc_i2c_init_hw(sc); if (err) { cdnc_i2c_detach(dev); return (err); } - sc->iicbus = device_add_child(dev, "iicbus", -1); + sc->iicbus = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY); cdnc_i2c_add_sysctls(dev); /* Probe and attach iicbus when interrupts work. */ bus_delayed_attach_children(dev); return (0); } static int cdnc_i2c_detach(device_t dev) { struct cdnc_i2c_softc *sc = device_get_softc(dev); bus_generic_detach(dev); if (sc->ref_clk != NULL) { clk_release(sc->ref_clk); sc->ref_clk = NULL; } /* Disable hardware. */ if (sc->mem_res != NULL) { sc->cfg_reg_shadow = 0; WR2(sc, CDNC_I2C_CR, CDNC_I2C_CR_CLR_FIFO); /* Clear and disable all interrupts. */ WR2(sc, CDNC_I2C_ISR, CDNC_I2C_ISR_ALL); WR2(sc, CDNC_I2C_IDR, CDNC_I2C_ISR_ALL); } /* Teardown and release interrupt. */ if (sc->irq_res != NULL) { if (sc->intrhandle) bus_teardown_intr(dev, sc->irq_res, sc->intrhandle); bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->irq_res), sc->irq_res); sc->irq_res = NULL; } /* Release memory resource. */ if (sc->mem_res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->mem_res), sc->mem_res); sc->mem_res = NULL; } I2C_SC_LOCK_DESTROY(sc); return (0); } static phandle_t cdnc_i2c_get_node(device_t bus, device_t dev) { return (ofw_bus_get_node(bus)); } static device_method_t cdnc_i2c_methods[] = { /* Device interface */ DEVMETHOD(device_probe, cdnc_i2c_probe), DEVMETHOD(device_attach, cdnc_i2c_attach), DEVMETHOD(device_detach, cdnc_i2c_detach), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, cdnc_i2c_get_node), /* iicbus methods */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_reset, cdnc_i2c_reset), DEVMETHOD(iicbus_transfer, cdnc_i2c_transfer), DEVMETHOD_END }; static driver_t cdnc_i2c_driver = { "cdnc_i2c", cdnc_i2c_methods, sizeof(struct cdnc_i2c_softc), }; DRIVER_MODULE(cdnc_i2c, simplebus, cdnc_i2c_driver, NULL, NULL); DRIVER_MODULE(ofw_iicbus, cdnc_i2c, ofw_iicbus_driver, NULL, NULL); MODULE_DEPEND(cdnc_i2c, iicbus, 1, 1, 1); MODULE_DEPEND(cdnc_i2c, ofw_iicbus, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); diff --git a/sys/dev/iicbus/controller/twsi/twsi.c b/sys/dev/iicbus/controller/twsi/twsi.c index b5aefbae7d7e..46704e1eab65 100644 --- a/sys/dev/iicbus/controller/twsi/twsi.c +++ b/sys/dev/iicbus/controller/twsi/twsi.c @@ -1,875 +1,876 @@ /*- * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. * All rights reserved. * * Developed by Semihalf. * * 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, 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. * 3. Neither the name of MARVELL nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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. */ /* * Driver for the TWSI (aka I2C, aka IIC) bus controller found on Marvell * and Allwinner SoCs. Supports master operation only. * * Calls to DELAY() are needed per Application Note AN-179 "TWSI Software * Guidelines for Discovery(TM), Horizon (TM) and Feroceon(TM) Devices". */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #define TWSI_CONTROL_ACK (1 << 2) #define TWSI_CONTROL_IFLG (1 << 3) #define TWSI_CONTROL_STOP (1 << 4) #define TWSI_CONTROL_START (1 << 5) #define TWSI_CONTROL_TWSIEN (1 << 6) #define TWSI_CONTROL_INTEN (1 << 7) #define TWSI_STATUS_BUS_ERROR 0x00 #define TWSI_STATUS_START 0x08 #define TWSI_STATUS_RPTD_START 0x10 #define TWSI_STATUS_ADDR_W_ACK 0x18 #define TWSI_STATUS_ADDR_W_NACK 0x20 #define TWSI_STATUS_DATA_WR_ACK 0x28 #define TWSI_STATUS_DATA_WR_NACK 0x30 #define TWSI_STATUS_ARBITRATION_LOST 0x38 #define TWSI_STATUS_ADDR_R_ACK 0x40 #define TWSI_STATUS_ADDR_R_NACK 0x48 #define TWSI_STATUS_DATA_RD_ACK 0x50 #define TWSI_STATUS_DATA_RD_NOACK 0x58 #define TWSI_STATUS_IDLE 0xf8 #define TWSI_DEBUG #undef TWSI_DEBUG #define debugf(sc, fmt, args...) if ((sc)->debug) \ device_printf((sc)->dev, "%s: " fmt, __func__, ##args) static struct resource_spec res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE}, { -1, 0 } }; static __inline uint32_t TWSI_READ(struct twsi_softc *sc, bus_size_t off) { uint32_t val; val = bus_read_4(sc->res[0], off); if (sc->debug > 1) debugf(sc, "read %x from %lx\n", val, off); return (val); } static __inline void TWSI_WRITE(struct twsi_softc *sc, bus_size_t off, uint32_t val) { if (sc->debug > 1) debugf(sc, "Writing %x to %lx\n", val, off); bus_write_4(sc->res[0], off, val); } static __inline void twsi_control_clear(struct twsi_softc *sc, uint32_t mask) { uint32_t val; val = TWSI_READ(sc, sc->reg_control); debugf(sc, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val &= ~mask; debugf(sc, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } static __inline void twsi_control_set(struct twsi_softc *sc, uint32_t mask) { uint32_t val; val = TWSI_READ(sc, sc->reg_control); debugf(sc, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val |= mask; debugf(sc, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } static __inline void twsi_clear_iflg(struct twsi_softc *sc) { DELAY(1000); /* There are two ways of clearing IFLAG. */ if (sc->iflag_w1c) twsi_control_set(sc, TWSI_CONTROL_IFLG); else twsi_control_clear(sc, TWSI_CONTROL_IFLG); DELAY(1000); } /* * timeout given in us * returns * 0 on successful mask change * non-zero on timeout */ static int twsi_poll_ctrl(struct twsi_softc *sc, int timeout, uint32_t mask) { timeout /= 10; debugf(sc, "Waiting for ctrl reg to match mask %x\n", mask); while (!(TWSI_READ(sc, sc->reg_control) & mask)) { DELAY(10); if (--timeout < 0) return (timeout); } debugf(sc, "done\n"); return (0); } /* * 'timeout' is given in us. Note also that timeout handling is not exact -- * twsi_locked_start() total wait can be more than 2 x timeout * (twsi_poll_ctrl() is called twice). 'mask' can be either TWSI_STATUS_START * or TWSI_STATUS_RPTD_START */ static int twsi_locked_start(device_t dev, struct twsi_softc *sc, int32_t mask, u_char slave, int timeout) { int read_access, iflg_set = 0; uint32_t status; mtx_assert(&sc->mutex, MA_OWNED); if (mask == TWSI_STATUS_RPTD_START) /* read IFLG to know if it should be cleared later; from NBSD */ iflg_set = TWSI_READ(sc, sc->reg_control) & TWSI_CONTROL_IFLG; debugf(sc, "send start\n"); twsi_control_set(sc, TWSI_CONTROL_START); if (mask == TWSI_STATUS_RPTD_START && iflg_set) { debugf(sc, "IFLG set, clearing (mask=%x)\n", mask); twsi_clear_iflg(sc); } /* * Without this delay we timeout checking IFLG if the timeout is 0. * NBSD driver always waits here too. */ DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(sc, "timeout sending %sSTART condition\n", mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ETIMEOUT); } status = TWSI_READ(sc, sc->reg_status); debugf(sc, "status=%x\n", status); if (status != mask) { debugf(sc, "wrong status (%02x) after sending %sSTART condition\n", status, mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ESTATUS); } TWSI_WRITE(sc, sc->reg_data, slave); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(sc, "timeout sending slave address (timeout=%d)\n", timeout); return (IIC_ETIMEOUT); } read_access = (slave & 0x1) ? 1 : 0; status = TWSI_READ(sc, sc->reg_status); if (status != (read_access ? TWSI_STATUS_ADDR_R_ACK : TWSI_STATUS_ADDR_W_ACK)) { debugf(sc, "no ACK (status: %02x) after sending slave address\n", status); return (IIC_ENOACK); } return (IIC_NOERR); } #define TWSI_BAUD_RATE_RAW(C,M,N) ((C)/((10*(M+1))<<(N))) #define ABSSUB(a,b) (((a) > (b)) ? (a) - (b) : (b) - (a)) static int twsi_calc_baud_rate(struct twsi_softc *sc, const u_int target, int *param) { uint64_t clk; uint32_t cur, diff, diff0; int m, n, m0, n0; /* Calculate baud rate. */ diff0 = 0xffffffff; if (clk_get_freq(sc->clk_core, &clk) < 0) return (-1); debugf(sc, "Bus clock is at %ju\n", clk); for (n = 0; n < 8; n++) { for (m = 0; m < 16; m++) { cur = TWSI_BAUD_RATE_RAW(clk,m,n); diff = ABSSUB(target, cur); if (diff < diff0) { m0 = m; n0 = n; diff0 = diff; } } } *param = TWSI_BAUD_RATE_PARAM(m0, n0); return (0); } /* * Only slave mode supported, disregard [old]addr */ static int twsi_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct twsi_softc *sc; uint32_t param; u_int busfreq; sc = device_get_softc(dev); busfreq = IICBUS_GET_FREQUENCY(sc->iicbus, speed); if (twsi_calc_baud_rate(sc, busfreq, ¶m) == -1) { switch (speed) { case IIC_SLOW: case IIC_FAST: param = sc->baud_rate[speed].param; debugf(sc, "Using IIC_FAST mode with speed param=%x\n", param); break; case IIC_FASTEST: case IIC_UNKNOWN: default: param = sc->baud_rate[IIC_FAST].param; debugf(sc, "Using IIC_FASTEST/UNKNOWN mode with speed param=%x\n", param); break; } } debugf(sc, "Using clock param=%x\n", param); mtx_lock(&sc->mutex); TWSI_WRITE(sc, sc->reg_soft_reset, 0x1); TWSI_WRITE(sc, sc->reg_baud_rate, param); TWSI_WRITE(sc, sc->reg_control, TWSI_CONTROL_TWSIEN); DELAY(1000); mtx_unlock(&sc->mutex); return (0); } static int twsi_stop(device_t dev) { struct twsi_softc *sc; sc = device_get_softc(dev); debugf(sc, "%s\n", __func__); mtx_lock(&sc->mutex); twsi_control_clear(sc, TWSI_CONTROL_ACK); twsi_control_set(sc, TWSI_CONTROL_STOP); twsi_clear_iflg(sc); DELAY(1000); mtx_unlock(&sc->mutex); return (IIC_NOERR); } /* * timeout is given in us */ static int twsi_repeated_start(device_t dev, u_char slave, int timeout) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); debugf(sc, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_RPTD_START, slave, timeout); mtx_unlock(&sc->mutex); if (rv) { twsi_stop(dev); return (rv); } else return (IIC_NOERR); } /* * timeout is given in us */ static int twsi_start(device_t dev, u_char slave, int timeout) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); debugf(sc, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_START, slave, timeout); mtx_unlock(&sc->mutex); if (rv) { twsi_stop(dev); return (rv); } else return (IIC_NOERR); } static int twsi_read(device_t dev, char *buf, int len, int *read, int last, int delay) { struct twsi_softc *sc; uint32_t status; int last_byte, rv; sc = device_get_softc(dev); mtx_lock(&sc->mutex); *read = 0; while (*read < len) { /* * Check if we are reading last byte of the last buffer, * do not send ACK then, per I2C specs */ last_byte = ((*read == len - 1) && last) ? 1 : 0; if (last_byte) twsi_control_clear(sc, TWSI_CONTROL_ACK); else twsi_control_set(sc, TWSI_CONTROL_ACK); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, delay, TWSI_CONTROL_IFLG)) { debugf(sc, "timeout reading data (delay=%d)\n", delay); rv = IIC_ETIMEOUT; goto out; } status = TWSI_READ(sc, sc->reg_status); if (status != (last_byte ? TWSI_STATUS_DATA_RD_NOACK : TWSI_STATUS_DATA_RD_ACK)) { debugf(sc, "wrong status (%02x) while reading\n", status); rv = IIC_ESTATUS; goto out; } *buf++ = TWSI_READ(sc, sc->reg_data); (*read)++; } rv = IIC_NOERR; out: mtx_unlock(&sc->mutex); return (rv); } static int twsi_write(device_t dev, const char *buf, int len, int *sent, int timeout) { struct twsi_softc *sc; uint32_t status; int rv; sc = device_get_softc(dev); mtx_lock(&sc->mutex); *sent = 0; while (*sent < len) { TWSI_WRITE(sc, sc->reg_data, *buf++); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(sc, "timeout writing data (timeout=%d)\n", timeout); rv = IIC_ETIMEOUT; goto out; } status = TWSI_READ(sc, sc->reg_status); if (status != TWSI_STATUS_DATA_WR_ACK) { debugf(sc, "wrong status (%02x) while writing\n", status); rv = IIC_ESTATUS; goto out; } (*sent)++; } rv = IIC_NOERR; out: mtx_unlock(&sc->mutex); return (rv); } static void twsi_error(struct twsi_softc *sc, int err) { /* * Must send stop condition to abort the current transfer. */ debugf(sc, "Sending STOP condition for error %d\n", err); sc->transfer = 0; sc->error = err; sc->control_val = 0; TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); } static int twsi_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct twsi_softc *sc; uint32_t status; int error; sc = device_get_softc(dev); if (!sc->have_intr) return (iicbus_transfer_gen(dev, msgs, nmsgs)); mtx_lock(&sc->mutex); KASSERT(sc->transfer == 0, ("starting a transfer while another is active")); debugf(sc, "transmitting %d messages\n", nmsgs); status = TWSI_READ(sc, sc->reg_status); debugf(sc, "status=0x%x\n", status); if (status != TWSI_STATUS_IDLE) { debugf(sc, "Bad status at start of transfer\n"); twsi_error(sc, IIC_ESTATUS); goto end; } sc->nmsgs = nmsgs; sc->msgs = msgs; sc->msg_idx = 0; sc->transfer = 1; sc->error = 0; #ifdef TWSI_DEBUG for (int i = 0; i < nmsgs; i++) debugf(sc, "msg %d is %d bytes long\n", i, msgs[i].len); #endif /* Send start and re-enable interrupts */ sc->control_val = TWSI_CONTROL_TWSIEN | TWSI_CONTROL_INTEN; TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_START); msleep_sbt(sc, &sc->mutex, 0, "twsi", 3000 * SBT_1MS, SBT_1MS, 0); debugf(sc, "pause finish\n"); if (sc->error == 0 && sc->transfer != 0) { device_printf(sc->dev, "transfer timeout\n"); sc->error = IIC_ETIMEOUT; sc->transfer = 0; } if (sc->error != 0) debugf(sc, "Error: %d\n", sc->error); end: /* Disable module and interrupts */ debugf(sc, "status=0x%x\n", TWSI_READ(sc, sc->reg_status)); TWSI_WRITE(sc, sc->reg_control, 0); debugf(sc, "status=0x%x\n", TWSI_READ(sc, sc->reg_status)); error = sc->error; mtx_unlock(&sc->mutex); return (error); } static void twsi_intr(void *arg) { struct twsi_softc *sc; uint32_t status; bool message_done; bool send_start; sc = arg; send_start = false; mtx_lock(&sc->mutex); debugf(sc, "Got interrupt, current msg=%u\n", sc->msg_idx); status = TWSI_READ(sc, sc->reg_status); debugf(sc, "reg control = 0x%x, status = 0x%x\n", TWSI_READ(sc, sc->reg_control), status); if (sc->transfer == 0) { device_printf(sc->dev, "interrupt without active transfer, " "status = 0x%x\n", status); TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); goto end; } restart: message_done = false; switch (status) { case TWSI_STATUS_START: case TWSI_STATUS_RPTD_START: /* Transmit the address */ debugf(sc, "Send address 0x%x\n", sc->msgs[sc->msg_idx].slave); if (sc->msgs[sc->msg_idx].flags & IIC_M_RD) TWSI_WRITE(sc, sc->reg_data, sc->msgs[sc->msg_idx].slave | LSB); else TWSI_WRITE(sc, sc->reg_data, sc->msgs[sc->msg_idx].slave & ~LSB); break; case TWSI_STATUS_ADDR_W_ACK: debugf(sc, "Address ACK-ed (write)\n"); if (sc->msgs[sc->msg_idx].len > 0) { /* Directly send the first byte */ sc->sent_bytes = 1; debugf(sc, "Sending byte 0 (of %d) = %x\n", sc->msgs[sc->msg_idx].len, sc->msgs[sc->msg_idx].buf[0]); TWSI_WRITE(sc, sc->reg_data, sc->msgs[sc->msg_idx].buf[0]); } else { debugf(sc, "Zero-length write, sending STOP\n"); TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); } break; case TWSI_STATUS_ADDR_R_ACK: debugf(sc, "Address ACK-ed (read)\n"); sc->recv_bytes = 0; if (sc->msgs[sc->msg_idx].len == 0) { debugf(sc, "Zero-length read, sending STOP\n"); TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); } else if (sc->msgs[sc->msg_idx].len == 1) { sc->control_val &= ~TWSI_CONTROL_ACK; } else { sc->control_val |= TWSI_CONTROL_ACK; } break; case TWSI_STATUS_ADDR_W_NACK: case TWSI_STATUS_ADDR_R_NACK: debugf(sc, "Address NACK-ed\n"); twsi_error(sc, IIC_ENOACK); break; case TWSI_STATUS_DATA_WR_NACK: debugf(sc, "Data byte NACK-ed\n"); twsi_error(sc, IIC_ENOACK); break; case TWSI_STATUS_DATA_WR_ACK: KASSERT(sc->sent_bytes <= sc->msgs[sc->msg_idx].len, ("sent_bytes beyond message length")); debugf(sc, "ACK received after transmitting data\n"); if (sc->sent_bytes == sc->msgs[sc->msg_idx].len) { debugf(sc, "Done TX data\n"); /* Send stop, no interrupts on stop */ if (!(sc->msgs[sc->msg_idx].flags & IIC_M_NOSTOP)) { TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); } else { debugf(sc, "NOSTOP flag\n"); } message_done = true; break; } debugf(sc, "Sending byte %d (of %d) = 0x%x\n", sc->sent_bytes, sc->msgs[sc->msg_idx].len, sc->msgs[sc->msg_idx].buf[sc->sent_bytes]); TWSI_WRITE(sc, sc->reg_data, sc->msgs[sc->msg_idx].buf[sc->sent_bytes]); sc->sent_bytes++; break; case TWSI_STATUS_DATA_RD_ACK: debugf(sc, "Received and ACK-ed data\n"); KASSERT(sc->recv_bytes < sc->msgs[sc->msg_idx].len, ("receiving beyond the end of buffer")); sc->msgs[sc->msg_idx].buf[sc->recv_bytes] = TWSI_READ(sc, sc->reg_data); debugf(sc, "Received byte %d (of %d) = 0x%x\n", sc->recv_bytes, sc->msgs[sc->msg_idx].len, sc->msgs[sc->msg_idx].buf[sc->recv_bytes]); sc->recv_bytes++; /* If we only have one byte left, disable ACK */ if (sc->msgs[sc->msg_idx].len - sc->recv_bytes == 1) { sc->control_val &= ~TWSI_CONTROL_ACK; } else if (sc->msgs[sc->msg_idx].len == sc->recv_bytes) { /* * We should not have ACK-ed the last byte. * The protocol state machine is in invalid state. */ debugf(sc, "RX all but asked for more?\n"); twsi_error(sc, IIC_ESTATUS); } break; case TWSI_STATUS_DATA_RD_NOACK: debugf(sc, "Received and NACK-ed data\n"); KASSERT(sc->recv_bytes == sc->msgs[sc->msg_idx].len - 1, ("sent NACK before receiving all requested data")); sc->msgs[sc->msg_idx].buf[sc->recv_bytes] = TWSI_READ(sc, sc->reg_data); debugf(sc, "Received byte %d (of %d) = 0x%x\n", sc->recv_bytes, sc->msgs[sc->msg_idx].len, sc->msgs[sc->msg_idx].buf[sc->recv_bytes]); sc->recv_bytes++; if (sc->msgs[sc->msg_idx].len == sc->recv_bytes) { debugf(sc, "Done RX data\n"); if (!(sc->msgs[sc->msg_idx].flags & IIC_M_NOSTOP)) { debugf(sc, "Send STOP\n"); TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_STOP); } message_done = true; } else { /* * We should not have NACK-ed yet. * The protocol state machine is in invalid state. */ debugf(sc, "NACK-ed before receving all bytes?\n"); twsi_error(sc, IIC_ESTATUS); } break; case TWSI_STATUS_BUS_ERROR: debugf(sc, "Bus error\n"); twsi_error(sc, IIC_EBUSERR); break; case TWSI_STATUS_ARBITRATION_LOST: debugf(sc, "Arbitration lost\n"); twsi_error(sc, IIC_EBUSBSY); break; default: debugf(sc, "unexpected status 0x%x\n", status); twsi_error(sc, IIC_ESTATUS); break; } if (message_done) { sc->msg_idx++; if (sc->msg_idx == sc->nmsgs) { debugf(sc, "All messages transmitted\n"); sc->transfer = 0; sc->error = 0; } else if ((sc->msgs[sc->msg_idx].flags & IIC_M_NOSTART) == 0) { debugf(sc, "Send (repeated) start\n"); send_start = true; } else { /* Just keep transmitting data. */ KASSERT((sc->msgs[sc->msg_idx - 1].flags & IIC_M_NOSTOP) != 0, ("NOSTART message after STOP")); KASSERT((sc->msgs[sc->msg_idx].flags & IIC_M_RD) == (sc->msgs[sc->msg_idx - 1].flags & IIC_M_RD), ("change of transfer direction without a START")); debugf(sc, "NOSTART message after NOSTOP\n"); sc->sent_bytes = 0; sc->recv_bytes = 0; if ((sc->msgs[sc->msg_idx].flags & IIC_M_RD) == 0) { status = TWSI_STATUS_ADDR_W_ACK; goto restart; } else { debugf(sc, "Read+NOSTART unsupported\n"); twsi_error(sc, IIC_ESTATUS); } } } end: /* * Newer Allwinner chips clear IFLG after writing 1 to it. */ debugf(sc, "Refresh reg_control\n"); TWSI_WRITE(sc, sc->reg_control, sc->control_val | (sc->iflag_w1c ? TWSI_CONTROL_IFLG : 0) | (send_start ? TWSI_CONTROL_START : 0)); debugf(sc, "Done with interrupt, transfer = %d\n", sc->transfer); if (sc->transfer == 0) wakeup(sc); mtx_unlock(&sc->mutex); } static void twsi_intr_start(void *pdev) { struct twsi_softc *sc; sc = device_get_softc(pdev); if ((bus_setup_intr(pdev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, twsi_intr, sc, &sc->intrhand))) device_printf(pdev, "unable to register interrupt handler\n"); sc->have_intr = true; } int twsi_attach(device_t dev) { struct twsi_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node; struct sysctl_oid_list *tree; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mutex, device_get_nameunit(dev), "twsi", MTX_DEF); if (bus_alloc_resources(dev, res_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); twsi_detach(dev); return (ENXIO); } #ifdef TWSI_DEBUG sc->debug = 1; #endif ctx = device_get_sysctl_ctx(dev); tree_node = device_get_sysctl_tree(dev); tree = SYSCTL_CHILDREN(tree_node); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "debug", CTLFLAG_RWTUN, &sc->debug, 0, "Set debug level (zero to disable)"); /* Attach the iicbus. */ - if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL) { + if ((sc->iicbus = device_add_child(dev, "iicbus", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "could not allocate iicbus instance\n"); twsi_detach(dev); return (ENXIO); } bus_attach_children(dev); config_intrhook_oneshot(twsi_intr_start, dev); return (0); } int twsi_detach(device_t dev) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); if ((rv = bus_generic_detach(dev)) != 0) return (rv); if (sc->intrhand != NULL) bus_teardown_intr(sc->dev, sc->res[1], sc->intrhand); bus_release_resources(dev, res_spec, sc->res); mtx_destroy(&sc->mutex); return (0); } static device_method_t twsi_methods[] = { /* device interface */ DEVMETHOD(device_detach, twsi_detach), /* Bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_repeated_start, twsi_repeated_start), DEVMETHOD(iicbus_start, twsi_start), DEVMETHOD(iicbus_stop, twsi_stop), DEVMETHOD(iicbus_write, twsi_write), DEVMETHOD(iicbus_read, twsi_read), DEVMETHOD(iicbus_reset, twsi_reset), DEVMETHOD(iicbus_transfer, twsi_transfer), { 0, 0 } }; DEFINE_CLASS_0(twsi, twsi_driver, twsi_methods, sizeof(struct twsi_softc)); diff --git a/sys/dev/iicbus/iic.c b/sys/dev/iicbus/iic.c index ec37a6e19342..3b7d603005aa 100644 --- a/sys/dev/iicbus/iic.c +++ b/sys/dev/iicbus/iic.c @@ -1,611 +1,611 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998, 2001 Nicolas Souchu * Copyright (c) 2023 Juniper Networks, Inc. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" struct iic_softc { device_t sc_dev; struct cdev *sc_devnode; }; struct iic_cdevpriv { struct sx lock; struct iic_softc *sc; bool started; uint8_t addr; }; #ifdef COMPAT_FREEBSD32 struct iic_msg32 { uint16_t slave; uint16_t flags; uint16_t len; uint32_t buf; }; struct iiccmd32 { u_char slave; uint32_t count; uint32_t last; uint32_t buf; }; struct iic_rdwr_data32 { uint32_t msgs; uint32_t nmsgs; }; #define I2CWRITE32 _IOW('i', 4, struct iiccmd32) #define I2CREAD32 _IOW('i', 5, struct iiccmd32) #define I2CRDWR32 _IOW('i', 6, struct iic_rdwr_data32) #endif #define IIC_LOCK(cdp) sx_xlock(&(cdp)->lock) #define IIC_UNLOCK(cdp) sx_xunlock(&(cdp)->lock) static MALLOC_DEFINE(M_IIC, "iic", "I2C device data"); static int iic_probe(device_t); static int iic_attach(device_t); static int iic_detach(device_t); static void iic_identify(driver_t *driver, device_t parent); static void iicdtor(void *data); static int iicuio_move(struct iic_cdevpriv *priv, struct uio *uio, int last); static int iicuio(struct cdev *dev, struct uio *uio, int ioflag); static int iicrdwr(struct iic_cdevpriv *priv, struct iic_rdwr_data *d, int flags, bool compat32); static device_method_t iic_methods[] = { /* device interface */ DEVMETHOD(device_identify, iic_identify), DEVMETHOD(device_probe, iic_probe), DEVMETHOD(device_attach, iic_attach), DEVMETHOD(device_detach, iic_detach), /* iicbus interface */ DEVMETHOD(iicbus_intr, iicbus_generic_intr), { 0, 0 } }; static driver_t iic_driver = { "iic", iic_methods, sizeof(struct iic_softc), }; static d_open_t iicopen; static d_ioctl_t iicioctl; static struct cdevsw iic_cdevsw = { .d_version = D_VERSION, .d_open = iicopen, .d_read = iicuio, .d_write = iicuio, .d_ioctl = iicioctl, .d_name = "iic", }; static void iic_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "iic", -1) == NULL) + if (device_find_child(parent, "iic", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "iic", DEVICE_UNIT_ANY); } static int iic_probe(device_t dev) { if (iicbus_get_addr(dev) > 0) return (ENXIO); device_set_desc(dev, "I2C generic I/O"); return (0); } static int iic_attach(device_t dev) { struct iic_softc *sc; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_devnode = make_dev(&iic_cdevsw, device_get_unit(dev), UID_ROOT, GID_WHEEL, 0600, "iic%d", device_get_unit(dev)); if (sc->sc_devnode == NULL) { device_printf(dev, "failed to create character device\n"); return (ENXIO); } sc->sc_devnode->si_drv1 = sc; return (0); } static int iic_detach(device_t dev) { struct iic_softc *sc; sc = device_get_softc(dev); if (sc->sc_devnode) destroy_dev(sc->sc_devnode); return (0); } static int iicopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct iic_cdevpriv *priv; int error; priv = malloc(sizeof(*priv), M_IIC, M_WAITOK | M_ZERO); sx_init(&priv->lock, "iic"); priv->sc = dev->si_drv1; error = devfs_set_cdevpriv(priv, iicdtor); if (error != 0) free(priv, M_IIC); return (error); } static void iicdtor(void *data) { device_t iicdev, parent; struct iic_cdevpriv *priv; priv = data; KASSERT(priv != NULL, ("iic cdevpriv should not be NULL!")); iicdev = priv->sc->sc_dev; parent = device_get_parent(iicdev); if (priv->started) { iicbus_stop(parent); iicbus_reset(parent, IIC_UNKNOWN, 0, NULL); iicbus_release_bus(parent, iicdev); } sx_destroy(&priv->lock); free(priv, M_IIC); } static int iicuio_move(struct iic_cdevpriv *priv, struct uio *uio, int last) { device_t parent; int error, num_bytes, transferred_bytes, written_bytes; char buffer[128]; parent = device_get_parent(priv->sc->sc_dev); error = 0; /* * We can only transfer up to sizeof(buffer) bytes in 1 shot, so loop until * everything has been transferred. */ while ((error == 0) && (uio->uio_resid > 0)) { num_bytes = MIN(uio->uio_resid, sizeof(buffer)); transferred_bytes = 0; switch (uio->uio_rw) { case UIO_WRITE: error = uiomove(buffer, num_bytes, uio); while ((error == 0) && (transferred_bytes < num_bytes)) { written_bytes = 0; error = iicbus_write(parent, &buffer[transferred_bytes], num_bytes - transferred_bytes, &written_bytes, 0); transferred_bytes += written_bytes; } break; case UIO_READ: error = iicbus_read(parent, buffer, num_bytes, &transferred_bytes, ((uio->uio_resid <= sizeof(buffer)) ? last : 0), 0); if (error == 0) error = uiomove(buffer, transferred_bytes, uio); break; } } return (error); } static int iicuio(struct cdev *dev, struct uio *uio, int ioflag) { device_t parent; struct iic_cdevpriv *priv; int error; uint8_t addr; priv = NULL; error = devfs_get_cdevpriv((void**)&priv); if (error != 0) return (error); KASSERT(priv != NULL, ("iic cdevpriv should not be NULL!")); IIC_LOCK(priv); if (priv->started || (priv->addr == 0)) { IIC_UNLOCK(priv); return (ENXIO); } parent = device_get_parent(priv->sc->sc_dev); error = iicbus_request_bus(parent, priv->sc->sc_dev, (ioflag & O_NONBLOCK) ? IIC_DONTWAIT : (IIC_WAIT | IIC_INTR)); if (error != 0) { IIC_UNLOCK(priv); return (error); } switch (uio->uio_rw) { case UIO_READ: addr = priv->addr | LSB; break; case UIO_WRITE: addr = priv->addr & ~LSB; break; } error = iicbus_start(parent, addr, 0); if (error != 0) { iicbus_release_bus(parent, priv->sc->sc_dev); IIC_UNLOCK(priv); return (error); } error = iicuio_move(priv, uio, IIC_LAST_READ); iicbus_stop(parent); iicbus_release_bus(parent, priv->sc->sc_dev); IIC_UNLOCK(priv); return (error); } #ifdef COMPAT_FREEBSD32 static int iic_copyinmsgs32(struct iic_rdwr_data *d, struct iic_msg *buf) { struct iic_msg32 msg32; struct iic_msg32 *m32; int error, i; m32 = (struct iic_msg32 *)d->msgs; for (i = 0; i < d->nmsgs; i++) { error = copyin(&m32[i], &msg32, sizeof(msg32)); if (error != 0) return (error); CP(msg32, buf[i], slave); CP(msg32, buf[i], flags); CP(msg32, buf[i], len); PTRIN_CP(msg32, buf[i], buf); } return (0); } #endif static int iicrdwr(struct iic_cdevpriv *priv, struct iic_rdwr_data *d, int flags, bool compat32 __unused) { #ifdef COMPAT_FREEBSD32 struct iic_rdwr_data dswab; struct iic_rdwr_data32 *d32; #endif struct iic_msg *buf, *m; void **usrbufs; device_t iicdev, parent; int error; uint32_t i; iicdev = priv->sc->sc_dev; parent = device_get_parent(iicdev); error = 0; #ifdef COMPAT_FREEBSD32 if (compat32) { d32 = (struct iic_rdwr_data32 *)d; PTRIN_CP(*d32, dswab, msgs); CP(*d32, dswab, nmsgs); d = &dswab; } #endif if (d->nmsgs > IIC_RDRW_MAX_MSGS) return (EINVAL); buf = malloc(sizeof(*d->msgs) * d->nmsgs, M_IIC, M_WAITOK); #ifdef COMPAT_FREEBSD32 if (compat32) error = iic_copyinmsgs32(d, buf); else #endif error = copyin(d->msgs, buf, sizeof(*d->msgs) * d->nmsgs); if (error != 0) { free(buf, M_IIC); return (error); } /* Alloc kernel buffers for userland data, copyin write data */ usrbufs = malloc(sizeof(void *) * d->nmsgs, M_IIC, M_WAITOK | M_ZERO); for (i = 0; i < d->nmsgs; i++) { m = &(buf[i]); usrbufs[i] = m->buf; /* * At least init the buffer to NULL so we can safely free() it later. * If the copyin() to buf failed, don't try to malloc bogus m->len. */ m->buf = NULL; if (error != 0) continue; /* m->len is uint16_t, so allocation size is capped at 64K. */ m->buf = malloc(m->len, M_IIC, M_WAITOK); if (!(m->flags & IIC_M_RD)) error = copyin(usrbufs[i], m->buf, m->len); } if (error == 0) error = iicbus_request_bus(parent, iicdev, (flags & O_NONBLOCK) ? IIC_DONTWAIT : (IIC_WAIT | IIC_INTR)); if (error == 0) { error = iicbus_transfer(iicdev, buf, d->nmsgs); iicbus_release_bus(parent, iicdev); } /* Copyout all read segments, free up kernel buffers */ for (i = 0; i < d->nmsgs; i++) { m = &(buf[i]); if ((error == 0) && (m->flags & IIC_M_RD)) error = copyout(m->buf, usrbufs[i], m->len); free(m->buf, M_IIC); } free(usrbufs, M_IIC); free(buf, M_IIC); return (error); } static int iicioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { #ifdef COMPAT_FREEBSD32 struct iiccmd iicswab; #endif device_t parent, iicdev; struct iiccmd *s; #ifdef COMPAT_FREEBSD32 struct iiccmd32 *s32; #endif struct uio ubuf; struct iovec uvec; struct iic_cdevpriv *priv; int error; bool compat32; s = (struct iiccmd *)data; #ifdef COMPAT_FREEBSD32 s32 = (struct iiccmd32 *)data; #endif error = devfs_get_cdevpriv((void**)&priv); if (error != 0) return (error); KASSERT(priv != NULL, ("iic cdevpriv should not be NULL!")); iicdev = priv->sc->sc_dev; parent = device_get_parent(iicdev); IIC_LOCK(priv); #ifdef COMPAT_FREEBSD32 switch (cmd) { case I2CWRITE32: case I2CREAD32: CP(*s32, iicswab, slave); CP(*s32, iicswab, count); CP(*s32, iicswab, last); PTRIN_CP(*s32, iicswab, buf); s = &iicswab; break; default: break; } #endif switch (cmd) { case I2CSTART: if (priv->started) { error = EINVAL; break; } error = iicbus_request_bus(parent, iicdev, (flags & O_NONBLOCK) ? IIC_DONTWAIT : (IIC_WAIT | IIC_INTR)); if (error == 0) error = iicbus_start(parent, s->slave, 0); if (error == 0) { priv->addr = s->slave; priv->started = true; } else iicbus_release_bus(parent, iicdev); break; case I2CSTOP: if (priv->started) { error = iicbus_stop(parent); iicbus_release_bus(parent, iicdev); priv->started = false; } break; case I2CRSTCARD: /* * Bus should be owned before we reset it. * We allow the bus to be already owned as the result of an in-progress * sequence; however, bus reset will always be followed by release * (a new start is presumably needed for I/O anyway). */ if (!priv->started) error = iicbus_request_bus(parent, iicdev, (flags & O_NONBLOCK) ? IIC_DONTWAIT : (IIC_WAIT | IIC_INTR)); if (error == 0) { error = iicbus_reset(parent, IIC_UNKNOWN, 0, NULL); /* * Ignore IIC_ENOADDR as it only means we have a master-only * controller. */ if (error == IIC_ENOADDR) error = 0; iicbus_release_bus(parent, iicdev); priv->started = false; } break; case I2CWRITE: #ifdef COMPAT_FREEBSD32 case I2CWRITE32: #endif if (!priv->started) { error = EINVAL; break; } uvec.iov_base = s->buf; uvec.iov_len = s->count; ubuf.uio_iov = &uvec; ubuf.uio_iovcnt = 1; ubuf.uio_segflg = UIO_USERSPACE; ubuf.uio_td = td; ubuf.uio_resid = s->count; ubuf.uio_offset = 0; ubuf.uio_rw = UIO_WRITE; error = iicuio_move(priv, &ubuf, 0); break; case I2CREAD: #ifdef COMPAT_FREEBSD32 case I2CREAD32: #endif if (!priv->started) { error = EINVAL; break; } uvec.iov_base = s->buf; uvec.iov_len = s->count; ubuf.uio_iov = &uvec; ubuf.uio_iovcnt = 1; ubuf.uio_segflg = UIO_USERSPACE; ubuf.uio_td = td; ubuf.uio_resid = s->count; ubuf.uio_offset = 0; ubuf.uio_rw = UIO_READ; error = iicuio_move(priv, &ubuf, s->last); break; #ifdef COMPAT_FREEBSD32 case I2CRDWR32: #endif case I2CRDWR: /* * The rdwr list should be a self-contained set of * transactions. Fail if another transaction is in progress. */ if (priv->started) { error = EINVAL; break; } #ifdef COMPAT_FREEBSD32 compat32 = (cmd == I2CRDWR32); #else compat32 = false; #endif error = iicrdwr(priv, (struct iic_rdwr_data *)data, flags, compat32); break; case I2CRPTSTART: if (!priv->started) { error = EINVAL; break; } error = iicbus_repeated_start(parent, s->slave, 0); break; case I2CSADDR: priv->addr = *((uint8_t*)data); break; default: error = ENOTTY; } IIC_UNLOCK(priv); return (error); } DRIVER_MODULE(iic, iicbus, iic_driver, 0, 0); MODULE_DEPEND(iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(iic, 1); diff --git a/sys/dev/iicbus/iicsmb.c b/sys/dev/iicbus/iicsmb.c index a5885648632e..e03e789dc05d 100644 --- a/sys/dev/iicbus/iicsmb.c +++ b/sys/dev/iicbus/iicsmb.c @@ -1,477 +1,477 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998, 2001 Nicolas Souchu * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include /* * I2C to SMB bridge * * Example: * * smb bttv * \ / * smbus * / \ * iicsmb bti2c * | * iicbus * / | \ * iicbb pcf ... * | * lpbb */ #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "smbus_if.h" struct iicsmb_softc { #define SMB_WAITING_ADDR 0x0 #define SMB_WAITING_LOW 0x1 #define SMB_WAITING_HIGH 0x2 #define SMB_DONE 0x3 int state; u_char devaddr; /* slave device address */ char low; /* low byte received first */ char high; /* high byte */ struct mtx lock; device_t smbus; }; static int iicsmb_probe(device_t); static int iicsmb_attach(device_t); static int iicsmb_detach(device_t); static void iicsmb_identify(driver_t *driver, device_t parent); static int iicsmb_intr(device_t dev, int event, char *buf); static int iicsmb_callback(device_t dev, int index, void *data); static int iicsmb_quick(device_t dev, u_char slave, int how); static int iicsmb_sendb(device_t dev, u_char slave, char byte); static int iicsmb_recvb(device_t dev, u_char slave, char *byte); static int iicsmb_writeb(device_t dev, u_char slave, char cmd, char byte); static int iicsmb_writew(device_t dev, u_char slave, char cmd, short word); static int iicsmb_readb(device_t dev, u_char slave, char cmd, char *byte); static int iicsmb_readw(device_t dev, u_char slave, char cmd, short *word); static int iicsmb_pcall(device_t dev, u_char slave, char cmd, short sdata, short *rdata); static int iicsmb_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf); static int iicsmb_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf); static device_method_t iicsmb_methods[] = { /* device interface */ DEVMETHOD(device_identify, iicsmb_identify), DEVMETHOD(device_probe, iicsmb_probe), DEVMETHOD(device_attach, iicsmb_attach), DEVMETHOD(device_detach, iicsmb_detach), /* iicbus interface */ DEVMETHOD(iicbus_intr, iicsmb_intr), /* smbus interface */ DEVMETHOD(smbus_callback, iicsmb_callback), DEVMETHOD(smbus_quick, iicsmb_quick), DEVMETHOD(smbus_sendb, iicsmb_sendb), DEVMETHOD(smbus_recvb, iicsmb_recvb), DEVMETHOD(smbus_writeb, iicsmb_writeb), DEVMETHOD(smbus_writew, iicsmb_writew), DEVMETHOD(smbus_readb, iicsmb_readb), DEVMETHOD(smbus_readw, iicsmb_readw), DEVMETHOD(smbus_pcall, iicsmb_pcall), DEVMETHOD(smbus_bwrite, iicsmb_bwrite), DEVMETHOD(smbus_bread, iicsmb_bread), DEVMETHOD_END }; static driver_t iicsmb_driver = { "iicsmb", iicsmb_methods, sizeof(struct iicsmb_softc), }; static void iicsmb_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "iicsmb", -1) == NULL) + if (device_find_child(parent, "iicsmb", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "iicsmb", DEVICE_UNIT_ANY); } static int iicsmb_probe(device_t dev) { device_set_desc(dev, "SMBus over I2C bridge"); return (BUS_PROBE_NOWILDCARD); } static int iicsmb_attach(device_t dev) { struct iicsmb_softc *sc = (struct iicsmb_softc *)device_get_softc(dev); mtx_init(&sc->lock, "iicsmb", NULL, MTX_DEF); sc->smbus = device_add_child(dev, "smbus", DEVICE_UNIT_ANY); /* probe and attach the smbus */ bus_attach_children(dev); return (0); } static int iicsmb_detach(device_t dev) { struct iicsmb_softc *sc = (struct iicsmb_softc *)device_get_softc(dev); bus_generic_detach(dev); mtx_destroy(&sc->lock); return (0); } /* * iicsmb_intr() * * iicbus interrupt handler */ static int iicsmb_intr(device_t dev, int event, char *buf) { struct iicsmb_softc *sc = (struct iicsmb_softc *)device_get_softc(dev); mtx_lock(&sc->lock); switch (event) { case INTR_GENERAL: case INTR_START: sc->state = SMB_WAITING_ADDR; break; case INTR_STOP: /* call smbus intr handler */ smbus_intr(sc->smbus, sc->devaddr, sc->low, sc->high, SMB_ENOERR); break; case INTR_RECEIVE: switch (sc->state) { case SMB_DONE: /* XXX too much data, discard */ printf("%s: too much data from 0x%x\n", __func__, sc->devaddr & 0xff); goto end; case SMB_WAITING_ADDR: sc->devaddr = (u_char)*buf; sc->state = SMB_WAITING_LOW; break; case SMB_WAITING_LOW: sc->low = *buf; sc->state = SMB_WAITING_HIGH; break; case SMB_WAITING_HIGH: sc->high = *buf; sc->state = SMB_DONE; break; } end: break; case INTR_TRANSMIT: case INTR_NOACK: break; case INTR_ERROR: switch (*buf) { case IIC_EBUSERR: smbus_intr(sc->smbus, sc->devaddr, 0, 0, SMB_EBUSERR); break; default: printf("%s unknown error 0x%x!\n", __func__, (int)*buf); break; } break; default: panic("%s: unknown event (%d)!", __func__, event); } mtx_unlock(&sc->lock); return (0); } static int iicsmb_callback(device_t dev, int index, void *data) { device_t parent = device_get_parent(dev); int error = 0; int how; switch (index) { case SMB_REQUEST_BUS: /* request underlying iicbus */ how = *(int *)data; error = iicbus_request_bus(parent, dev, how); break; case SMB_RELEASE_BUS: /* release underlying iicbus */ error = iicbus_release_bus(parent, dev); break; default: error = EINVAL; } return (error); } static int iic2smb_error(int error) { switch (error) { case IIC_NOERR: return (SMB_ENOERR); case IIC_EBUSERR: return (SMB_EBUSERR); case IIC_ENOACK: return (SMB_ENOACK); case IIC_ETIMEOUT: return (SMB_ETIMEOUT); case IIC_EBUSBSY: return (SMB_EBUSY); case IIC_ESTATUS: return (SMB_EBUSERR); case IIC_EUNDERFLOW: return (SMB_EBUSERR); case IIC_EOVERFLOW: return (SMB_EBUSERR); case IIC_ENOTSUPP: return (SMB_ENOTSUPP); case IIC_ENOADDR: return (SMB_EBUSERR); case IIC_ERESOURCE: return (SMB_EBUSERR); default: return (SMB_EBUSERR); } } #define TRANSFER_MSGS(dev, msgs) iicbus_transfer(dev, msgs, nitems(msgs)) static int iicsmb_quick(device_t dev, u_char slave, int how) { struct iic_msg msgs[] = { { slave, how == SMB_QWRITE ? IIC_M_WR : IIC_M_RD, 0, NULL }, }; int error; switch (how) { case SMB_QWRITE: case SMB_QREAD: break; default: return (SMB_EINVAL); } error = TRANSFER_MSGS(dev, msgs); return (iic2smb_error(error)); } static int iicsmb_sendb(device_t dev, u_char slave, char byte) { struct iic_msg msgs[] = { { slave, IIC_M_WR, 1, &byte }, }; int error; error = TRANSFER_MSGS(dev, msgs); return (iic2smb_error(error)); } static int iicsmb_recvb(device_t dev, u_char slave, char *byte) { struct iic_msg msgs[] = { { slave, IIC_M_RD, 1, byte }, }; int error; error = TRANSFER_MSGS(dev, msgs); return (iic2smb_error(error)); } static int iicsmb_writeb(device_t dev, u_char slave, char cmd, char byte) { uint8_t bytes[] = { cmd, byte }; struct iic_msg msgs[] = { { slave, IIC_M_WR, nitems(bytes), bytes }, }; int error; error = TRANSFER_MSGS(dev, msgs); return (iic2smb_error(error)); } static int iicsmb_writew(device_t dev, u_char slave, char cmd, short word) { uint8_t bytes[] = { cmd, word & 0xff, word >> 8 }; struct iic_msg msgs[] = { { slave, IIC_M_WR, nitems(bytes), bytes }, }; int error; error = TRANSFER_MSGS(dev, msgs); return (iic2smb_error(error)); } static int iicsmb_readb(device_t dev, u_char slave, char cmd, char *byte) { struct iic_msg msgs[] = { { slave, IIC_M_WR | IIC_M_NOSTOP, 1, &cmd }, { slave, IIC_M_RD, 1, byte }, }; int error; error = TRANSFER_MSGS(dev, msgs); return (iic2smb_error(error)); } static int iicsmb_readw(device_t dev, u_char slave, char cmd, short *word) { uint8_t buf[2]; struct iic_msg msgs[] = { { slave, IIC_M_WR | IIC_M_NOSTOP, 1, &cmd }, { slave, IIC_M_RD, nitems(buf), buf }, }; int error; error = TRANSFER_MSGS(dev, msgs); if (error == 0) *word = ((uint16_t)buf[1] << 8) | buf[0]; return (iic2smb_error(error)); } static int iicsmb_pcall(device_t dev, u_char slave, char cmd, short sdata, short *rdata) { uint8_t in[3] = { cmd, sdata & 0xff, sdata >> 8 }; uint8_t out[2]; struct iic_msg msgs[] = { { slave, IIC_M_WR | IIC_M_NOSTOP, nitems(in), in }, { slave, IIC_M_RD, nitems(out), out }, }; int error; error = TRANSFER_MSGS(dev, msgs); if (error == 0) *rdata = ((uint16_t)out[1] << 8) | out[0]; return (iic2smb_error(error)); } static int iicsmb_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf) { uint8_t bytes[2] = { cmd, count }; struct iic_msg msgs[] = { { slave, IIC_M_WR | IIC_M_NOSTOP, nitems(bytes), bytes }, { slave, IIC_M_WR | IIC_M_NOSTART, count, buf }, }; int error; if (count > SMB_MAXBLOCKSIZE || count == 0) return (SMB_EINVAL); error = TRANSFER_MSGS(dev, msgs); return (iic2smb_error(error)); } static int iicsmb_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf) { struct iic_msg msgs[] = { { slave, IIC_M_WR | IIC_M_NOSTOP, 1, &cmd }, { slave, IIC_M_RD | IIC_M_NOSTOP, 1, count }, }; struct iic_msg block_msg[] = { { slave, IIC_M_RD | IIC_M_NOSTART, 0, buf }, }; device_t parent = device_get_parent(dev); int error; /* Have to do this because the command is split in two transfers. */ error = iicbus_request_bus(parent, dev, IIC_WAIT | IIC_RECURSIVE); if (error == 0) error = TRANSFER_MSGS(dev, msgs); if (error == 0) { /* * If the slave offers an empty or a too long reply, * read one byte to generate the stop or abort. */ if (*count > SMB_MAXBLOCKSIZE || *count == 0) block_msg[0].len = 1; else block_msg[0].len = *count; error = TRANSFER_MSGS(dev, block_msg); if (*count > SMB_MAXBLOCKSIZE || *count == 0) error = SMB_EINVAL; } (void)iicbus_release_bus(parent, dev); return (iic2smb_error(error)); } DRIVER_MODULE(iicsmb, iicbus, iicsmb_driver, 0, 0); DRIVER_MODULE(smbus, iicsmb, smbus_driver, 0, 0); MODULE_DEPEND(iicsmb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_DEPEND(iicsmb, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); MODULE_VERSION(iicsmb, 1); diff --git a/sys/dev/ipmi/ipmi_isa.c b/sys/dev/ipmi/ipmi_isa.c index 7ae55baf2f8f..0c74307db00d 100644 --- a/sys/dev/ipmi/ipmi_isa.c +++ b/sys/dev/ipmi/ipmi_isa.c @@ -1,292 +1,292 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 IronPort Systems Inc. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LOCAL_MODULE #include #include #else #include #include #endif static void ipmi_isa_identify(driver_t *driver, device_t parent) { struct ipmi_get_info info; uint32_t devid; if (ipmi_smbios_identify(&info) && info.iface_type != SSIF_MODE && - device_find_child(parent, "ipmi", -1) == NULL) { + device_find_child(parent, "ipmi", DEVICE_UNIT_ANY) == NULL) { /* * XXX: Hack alert. On some broken systems, the IPMI * interface is described via SMBIOS, but the actual * IO resource is in a PCI device BAR, so we have to let * the PCI device attach ipmi instead. In that case don't * create an isa ipmi device. For now we hardcode the list * of bus, device, function tuples. */ devid = pci_cfgregread(0, 0, 4, 2, PCIR_DEVVENDOR, 4); if (devid != 0xffffffff && ipmi_pci_match(devid & 0xffff, devid >> 16) != NULL) return; BUS_ADD_CHILD(parent, 0, "ipmi", DEVICE_UNIT_ANY); } } static int ipmi_isa_probe(device_t dev) { /* * Give other drivers precedence. Unfortunately, this doesn't * work if we have an SMBIOS table that duplicates a PCI device * that's later on the bus than the PCI-ISA bridge. */ if (ipmi_attached) return (ENXIO); /* Skip any PNP devices. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); device_set_desc(dev, "IPMI System Interface"); return (BUS_PROBE_DEFAULT); } static int ipmi_hint_identify(device_t dev, struct ipmi_get_info *info) { const char *mode, *name; int i, unit, val; /* We require at least a "mode" hint. */ name = device_get_name(dev); unit = device_get_unit(dev); if (resource_string_value(name, unit, "mode", &mode) != 0) return (0); /* Set the mode and default I/O resources for each mode. */ bzero(info, sizeof(struct ipmi_get_info)); if (strcasecmp(mode, "KCS") == 0) { info->iface_type = KCS_MODE; info->address = 0xca2; info->io_mode = 1; info->offset = 1; } else if (strcasecmp(mode, "SMIC") == 0) { info->iface_type = SMIC_MODE; info->address = 0xca9; info->io_mode = 1; info->offset = 1; } else if (strcasecmp(mode, "BT") == 0) { info->iface_type = BT_MODE; info->address = 0xe4; info->io_mode = 1; info->offset = 1; } else { device_printf(dev, "Invalid mode %s\n", mode); return (0); } /* * Kill any resources that isahint.c might have setup for us * since it will conflict with how we do resources. */ for (i = 0; i < 2; i++) { bus_delete_resource(dev, SYS_RES_MEMORY, i); bus_delete_resource(dev, SYS_RES_IOPORT, i); } /* Allow the I/O address to be overridden via hints. */ if (resource_int_value(name, unit, "port", &val) == 0 && val != 0) { info->address = val; info->io_mode = 1; } else if (resource_int_value(name, unit, "maddr", &val) == 0 && val != 0) { info->address = val; info->io_mode = 0; } /* Allow the spacing to be overridden. */ if (resource_int_value(name, unit, "spacing", &val) == 0) { switch (val) { case 8: info->offset = 1; break; case 16: info->offset = 2; break; case 32: info->offset = 4; break; default: device_printf(dev, "Invalid register spacing\n"); return (0); } } return (1); } static int ipmi_isa_attach(device_t dev) { struct ipmi_softc *sc = device_get_softc(dev); struct ipmi_get_info info; const char *mode; int count, error, i, type; /* * Pull info out of the SMBIOS table. If that doesn't work, use * hints to enumerate a device. */ if (!ipmi_smbios_identify(&info) && !ipmi_hint_identify(dev, &info)) return (ENXIO); switch (info.iface_type) { case KCS_MODE: count = IPMI_IF_KCS_NRES; mode = "KCS"; break; case SMIC_MODE: count = IPMI_IF_SMIC_NRES; mode = "SMIC"; break; case BT_MODE: count = IPMI_IF_BT_NRES; mode = "BT"; break; default: return (ENXIO); } error = 0; sc->ipmi_dev = dev; device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n", mode, info.io_mode ? "io" : "mem", (uintmax_t)info.address, info.offset, device_get_name(device_get_parent(dev))); if (info.io_mode) type = SYS_RES_IOPORT; else type = SYS_RES_MEMORY; sc->ipmi_io_type = type; sc->ipmi_io_spacing = info.offset; if (info.offset == 1) { sc->ipmi_io_rid = 0; sc->ipmi_io_res[0] = bus_alloc_resource(dev, type, &sc->ipmi_io_rid, info.address, info.address + count - 1, count, RF_ACTIVE); if (sc->ipmi_io_res[0] == NULL) { device_printf(dev, "couldn't configure I/O resource\n"); return (ENXIO); } } else { for (i = 0; i < count; i++) { sc->ipmi_io_rid = i; sc->ipmi_io_res[i] = bus_alloc_resource(dev, type, &sc->ipmi_io_rid, info.address + i * info.offset, info.address + i * info.offset, 1, RF_ACTIVE); if (sc->ipmi_io_res[i] == NULL) { device_printf(dev, "couldn't configure I/O resource\n"); error = ENXIO; sc->ipmi_io_rid = 0; goto bad; } } sc->ipmi_io_rid = 0; } if (info.irq != 0) { sc->ipmi_irq_rid = 0; sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->ipmi_irq_rid, info.irq, info.irq, 1, RF_SHAREABLE | RF_ACTIVE); } error = ENXIO; switch (info.iface_type) { case KCS_MODE: error = ipmi_kcs_attach(sc); break; case SMIC_MODE: error = ipmi_smic_attach(sc); break; case BT_MODE: error = ipmi_bt_attach(sc); break; } if (error) goto bad; error = ipmi_attach(dev); if (error) goto bad; return (0); bad: ipmi_release_resources(dev); return (error); } static device_method_t ipmi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ipmi_isa_identify), DEVMETHOD(device_probe, ipmi_isa_probe), DEVMETHOD(device_attach, ipmi_isa_attach), DEVMETHOD(device_detach, ipmi_detach), { 0, 0 } }; static driver_t ipmi_isa_driver = { "ipmi", ipmi_methods, sizeof(struct ipmi_softc), }; DRIVER_MODULE(ipmi_isa, isa, ipmi_isa_driver, 0, 0); #ifdef ARCH_MAY_USE_EFI MODULE_DEPEND(ipmi_isa, efirt, 1, 1, 1); #endif diff --git a/sys/dev/ipmi/ipmi_smbus.c b/sys/dev/ipmi/ipmi_smbus.c index 1772d9313892..9516b3dfa487 100644 --- a/sys/dev/ipmi/ipmi_smbus.c +++ b/sys/dev/ipmi/ipmi_smbus.c @@ -1,134 +1,134 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 IronPort Systems Inc. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smbus_if.h" #ifdef LOCAL_MODULE #include #else #include #endif static void ipmi_smbus_identify(driver_t *driver, device_t parent); static int ipmi_smbus_probe(device_t dev); static int ipmi_smbus_attach(device_t dev); static void ipmi_smbus_identify(driver_t *driver, device_t parent) { struct ipmi_get_info info; if (ipmi_smbios_identify(&info) && info.iface_type == SSIF_MODE && - device_find_child(parent, "ipmi", -1) == NULL) + device_find_child(parent, "ipmi", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "ipmi", DEVICE_UNIT_ANY); } static int ipmi_smbus_probe(device_t dev) { device_set_desc(dev, "IPMI System Interface"); return (BUS_PROBE_DEFAULT); } static int ipmi_smbus_attach(device_t dev) { struct ipmi_softc *sc = device_get_softc(dev); struct ipmi_get_info info; int error; /* This should never fail. */ if (!ipmi_smbios_identify(&info)) return (ENXIO); if (info.iface_type != SSIF_MODE) { device_printf(dev, "No SSIF IPMI interface found\n"); return (ENXIO); } sc->ipmi_dev = dev; if (info.irq != 0) { sc->ipmi_irq_rid = 0; sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->ipmi_irq_rid, info.irq, info.irq, 1, RF_SHAREABLE | RF_ACTIVE); } device_printf(dev, "SSIF mode found at address 0x%llx on %s\n", (long long)info.address, device_get_name(device_get_parent(dev))); error = ipmi_ssif_attach(sc, device_get_parent(dev), info.address); if (error) goto bad; error = ipmi_attach(dev); if (error) goto bad; return (0); bad: ipmi_release_resources(dev); return (error); } static device_method_t ipmi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ipmi_smbus_identify), DEVMETHOD(device_probe, ipmi_smbus_probe), DEVMETHOD(device_attach, ipmi_smbus_attach), DEVMETHOD(device_detach, ipmi_detach), { 0, 0 } }; static driver_t ipmi_smbus_driver = { "ipmi", ipmi_methods, sizeof(struct ipmi_softc) }; DRIVER_MODULE(ipmi_smbus, smbus, ipmi_smbus_driver, 0, 0); MODULE_DEPEND(ipmi_smbus, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); #ifdef ARCH_MAY_USE_EFI MODULE_DEPEND(ipmi_smbus, efirt, 1, 1, 1); #endif diff --git a/sys/dev/isl/isl.c b/sys/dev/isl/isl.c index 009c02ad2b35..6a0d406aeeda 100644 --- a/sys/dev/isl/isl.c +++ b/sys/dev/isl/isl.c @@ -1,340 +1,340 @@ /*- * Copyright (c) 2015 Michael Gmelin * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include /* * Driver for intersil I2C ISL29018 Digital Ambient Light Sensor and Proximity * Sensor with Interrupt Function, only tested connected over SMBus (ig4iic). * * Datasheet: * http://www.intersil.com/en/products/optoelectronics/ambient-light-and-proximity-sensors/light-to-digital-sensors/ISL29018.html * http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29018.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "bus_if.h" #include "device_if.h" #define ISL_METHOD_ALS 0x10 #define ISL_METHOD_IR 0x11 #define ISL_METHOD_PROX 0x12 #define ISL_METHOD_RESOLUTION 0x13 #define ISL_METHOD_RANGE 0x14 struct isl_softc { device_t dev; struct sx isl_sx; }; /* Returns < 0 on problem. */ static int isl_read_sensor(device_t dev, uint8_t cmd_mask); static int isl_read_byte(device_t dev, uint8_t reg, uint8_t *val) { uint16_t addr = iicbus_get_addr(dev); struct iic_msg msgs[] = { { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, { addr, IIC_M_RD, 1, val }, }; return (iicbus_transfer(dev, msgs, nitems(msgs))); } static int isl_write_byte(device_t dev, uint8_t reg, uint8_t val) { uint16_t addr = iicbus_get_addr(dev); uint8_t bytes[] = { reg, val }; struct iic_msg msgs[] = { { addr, IIC_M_WR, nitems(bytes), bytes }, }; return (iicbus_transfer(dev, msgs, nitems(msgs))); } /* * Initialize the device */ static int init_device(device_t dev, int probe) { int error; /* * init procedure: send 0x00 to test ref and cmd reg 1 */ error = isl_write_byte(dev, REG_TEST, 0); if (error) goto done; error = isl_write_byte(dev, REG_CMD1, 0); if (error) goto done; pause("islinit", hz/100); done: if (error && !probe) device_printf(dev, "Unable to initialize\n"); return (error); } static int isl_probe(device_t); static int isl_attach(device_t); static int isl_detach(device_t); static int isl_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t isl_methods[] = { /* device interface */ DEVMETHOD(device_probe, isl_probe), DEVMETHOD(device_attach, isl_attach), DEVMETHOD(device_detach, isl_detach), DEVMETHOD_END }; static driver_t isl_driver = { "isl", isl_methods, sizeof(struct isl_softc), }; #if 0 static void isl_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "asl", -1)) { + if (device_find_child(parent, "asl", DEVICE_UNIT_ANY)) { if (bootverbose) printf("asl: device(s) already created\n"); return; } /* Check if we can communicate to our slave. */ if (init_device(dev, 0x88, 1) == 0) BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "isl", DEVICE_UNIT_ANY); } #endif static int isl_probe(device_t dev) { uint32_t addr = iicbus_get_addr(dev); if (addr != 0x88) return (ENXIO); if (init_device(dev, 1) != 0) return (ENXIO); device_set_desc(dev, "ISL Digital Ambient Light Sensor"); return (BUS_PROBE_VENDOR); } static int isl_attach(device_t dev) { struct isl_softc *sc; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; int use_als; int use_ir; int use_prox; sc = device_get_softc(dev); sc->dev = dev; if (init_device(dev, 0) != 0) return (ENXIO); sx_init(&sc->isl_sx, "ISL read lock"); sysctl_ctx = device_get_sysctl_ctx(dev); sysctl_tree = device_get_sysctl_tree(dev); use_als = isl_read_sensor(dev, CMD1_MASK_ALS_ONCE) >= 0; use_ir = isl_read_sensor(dev, CMD1_MASK_IR_ONCE) >= 0; use_prox = isl_read_sensor(dev, CMD1_MASK_PROX_ONCE) >= 0; if (use_als) { SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "als", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, ISL_METHOD_ALS, isl_sysctl, "I", "Current ALS sensor read-out"); } if (use_ir) { SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "ir", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, ISL_METHOD_IR, isl_sysctl, "I", "Current IR sensor read-out"); } if (use_prox) { SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "prox", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, ISL_METHOD_PROX, isl_sysctl, "I", "Current proximity sensor read-out"); } SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "resolution", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, ISL_METHOD_RESOLUTION, isl_sysctl, "I", "Current proximity sensor resolution"); SYSCTL_ADD_PROC(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "range", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, ISL_METHOD_RANGE, isl_sysctl, "I", "Current proximity sensor range"); return (0); } static int isl_detach(device_t dev) { struct isl_softc *sc; sc = device_get_softc(dev); sx_destroy(&sc->isl_sx); return (0); } static int isl_sysctl(SYSCTL_HANDLER_ARGS) { static int resolutions[] = { 16, 12, 8, 4}; static int ranges[] = { 1000, 4000, 16000, 64000}; struct isl_softc *sc; uint8_t rbyte; int arg; int resolution; int range; sc = (struct isl_softc *)oidp->oid_arg1; arg = -1; sx_xlock(&sc->isl_sx); if (isl_read_byte(sc->dev, REG_CMD2, &rbyte) != 0) { sx_xunlock(&sc->isl_sx); return (-1); } resolution = resolutions[(rbyte & CMD2_MASK_RESOLUTION) >> CMD2_SHIFT_RESOLUTION]; range = ranges[(rbyte & CMD2_MASK_RANGE) >> CMD2_SHIFT_RANGE]; switch (oidp->oid_arg2) { case ISL_METHOD_ALS: arg = (isl_read_sensor(sc->dev, CMD1_MASK_ALS_ONCE) * range) >> resolution; break; case ISL_METHOD_IR: arg = isl_read_sensor(sc->dev, CMD1_MASK_IR_ONCE); break; case ISL_METHOD_PROX: arg = isl_read_sensor(sc->dev, CMD1_MASK_PROX_ONCE); break; case ISL_METHOD_RESOLUTION: arg = (1 << resolution); break; case ISL_METHOD_RANGE: arg = range; break; } sx_xunlock(&sc->isl_sx); SYSCTL_OUT(req, &arg, sizeof(arg)); return (0); } static int isl_read_sensor(device_t dev, uint8_t cmd_mask) { uint8_t rbyte; uint8_t cmd; int ret; if (isl_read_byte(dev, REG_CMD1, &rbyte) != 0) { device_printf(dev, "Couldn't read first byte before issuing command %d\n", cmd_mask); return (-1); } cmd = (rbyte & 0x1f) | cmd_mask; if (isl_write_byte(dev, REG_CMD1, cmd) != 0) { device_printf(dev, "Couldn't write command %d\n", cmd_mask); return (-1); } pause("islconv", hz/10); if (isl_read_byte(dev, REG_DATA1, &rbyte) != 0) { device_printf(dev, "Couldn't read first byte after command %d\n", cmd_mask); return (-1); } ret = rbyte; if (isl_read_byte(dev, REG_DATA2, &rbyte) != 0) { device_printf(dev, "Couldn't read second byte after command %d\n", cmd_mask); return (-1); } ret += rbyte << 8; return (ret); } DRIVER_MODULE(isl, iicbus, isl_driver, NULL, NULL); MODULE_DEPEND(isl, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(isl, 1); diff --git a/sys/dev/ismt/ismt.c b/sys/dev/ismt/ismt.c index 4aea93d1f435..5e6b7c8ebf18 100644 --- a/sys/dev/ismt/ismt.c +++ b/sys/dev/ismt/ismt.c @@ -1,771 +1,772 @@ /*- * Copyright (C) 2014 Intel Corporation * 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, 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. * 3. Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smbus_if.h" #define ISMT_DESC_ENTRIES 32 /* Hardware Descriptor Constants - Control Field */ #define ISMT_DESC_CWRL 0x01 /* Command/Write Length */ #define ISMT_DESC_BLK 0X04 /* Perform Block Transaction */ #define ISMT_DESC_FAIR 0x08 /* Set fairness flag upon successful arbit. */ #define ISMT_DESC_PEC 0x10 /* Packet Error Code */ #define ISMT_DESC_I2C 0x20 /* I2C Enable */ #define ISMT_DESC_INT 0x40 /* Interrupt */ #define ISMT_DESC_SOE 0x80 /* Stop On Error */ /* Hardware Descriptor Constants - Status Field */ #define ISMT_DESC_SCS 0x01 /* Success */ #define ISMT_DESC_DLTO 0x04 /* Data Low Time Out */ #define ISMT_DESC_NAK 0x08 /* NAK Received */ #define ISMT_DESC_CRC 0x10 /* CRC Error */ #define ISMT_DESC_CLTO 0x20 /* Clock Low Time Out */ #define ISMT_DESC_COL 0x40 /* Collisions */ #define ISMT_DESC_LPR 0x80 /* Large Packet Received */ /* Macros */ #define ISMT_DESC_ADDR_RW(addr, is_read) ((addr) | (is_read)) /* iSMT General Register address offsets (SMBBAR + ) */ #define ISMT_GR_GCTRL 0x000 /* General Control */ #define ISMT_GR_SMTICL 0x008 /* SMT Interrupt Cause Location */ #define ISMT_GR_ERRINTMSK 0x010 /* Error Interrupt Mask */ #define ISMT_GR_ERRAERMSK 0x014 /* Error AER Mask */ #define ISMT_GR_ERRSTS 0x018 /* Error Status */ #define ISMT_GR_ERRINFO 0x01c /* Error Information */ /* iSMT Master Registers */ #define ISMT_MSTR_MDBA 0x100 /* Master Descriptor Base Address */ #define ISMT_MSTR_MCTRL 0x108 /* Master Control */ #define ISMT_MSTR_MSTS 0x10c /* Master Status */ #define ISMT_MSTR_MDS 0x110 /* Master Descriptor Size */ #define ISMT_MSTR_RPOLICY 0x114 /* Retry Policy */ /* iSMT Miscellaneous Registers */ #define ISMT_SPGT 0x300 /* SMBus PHY Global Timing */ /* General Control Register (GCTRL) bit definitions */ #define ISMT_GCTRL_TRST 0x04 /* Target Reset */ #define ISMT_GCTRL_KILL 0x08 /* Kill */ #define ISMT_GCTRL_SRST 0x40 /* Soft Reset */ /* Master Control Register (MCTRL) bit definitions */ #define ISMT_MCTRL_SS 0x01 /* Start/Stop */ #define ISMT_MCTRL_MEIE 0x10 /* Master Error Interrupt Enable */ #define ISMT_MCTRL_FMHP 0x00ff0000 /* Firmware Master Head Ptr (FMHP) */ /* Master Status Register (MSTS) bit definitions */ #define ISMT_MSTS_HMTP 0xff0000 /* HW Master Tail Pointer (HMTP) */ #define ISMT_MSTS_MIS 0x20 /* Master Interrupt Status (MIS) */ #define ISMT_MSTS_MEIS 0x10 /* Master Error Int Status (MEIS) */ #define ISMT_MSTS_IP 0x01 /* In Progress */ /* Master Descriptor Size (MDS) bit definitions */ #define ISMT_MDS_MASK 0xff /* Master Descriptor Size mask (MDS) */ /* SMBus PHY Global Timing Register (SPGT) bit definitions */ #define ISMT_SPGT_SPD_MASK 0xc0000000 /* SMBus Speed mask */ #define ISMT_SPGT_SPD_80K 0x00 /* 80 kHz */ #define ISMT_SPGT_SPD_100K (0x1 << 30) /* 100 kHz */ #define ISMT_SPGT_SPD_400K (0x2 << 30) /* 400 kHz */ #define ISMT_SPGT_SPD_1M (0x3 << 30) /* 1 MHz */ /* MSI Control Register (MSICTL) bit definitions */ #define ISMT_MSICTL_MSIE 0x01 /* MSI Enable */ #define ISMT_MAX_BLOCK_SIZE 32 /* per SMBus spec */ //#define ISMT_DEBUG device_printf #ifndef ISMT_DEBUG #define ISMT_DEBUG(...) #endif /* iSMT Hardware Descriptor */ struct ismt_desc { uint8_t tgtaddr_rw; /* target address & r/w bit */ uint8_t wr_len_cmd; /* write length in bytes or a command */ uint8_t rd_len; /* read length */ uint8_t control; /* control bits */ uint8_t status; /* status bits */ uint8_t retry; /* collision retry and retry count */ uint8_t rxbytes; /* received bytes */ uint8_t txbytes; /* transmitted bytes */ uint32_t dptr_low; /* lower 32 bit of the data pointer */ uint32_t dptr_high; /* upper 32 bit of the data pointer */ } __packed; #define DESC_SIZE (ISMT_DESC_ENTRIES * sizeof(struct ismt_desc)) #define DMA_BUFFER_SIZE 64 struct ismt_softc { device_t pcidev; device_t smbdev; struct thread *bus_reserved; int intr_rid; struct resource *intr_res; void *intr_handle; bus_space_tag_t mmio_tag; bus_space_handle_t mmio_handle; int mmio_rid; struct resource *mmio_res; uint8_t head; struct ismt_desc *desc; bus_dma_tag_t desc_dma_tag; bus_dmamap_t desc_dma_map; uint64_t desc_bus_addr; uint8_t *dma_buffer; bus_dma_tag_t dma_buffer_dma_tag; bus_dmamap_t dma_buffer_dma_map; uint64_t dma_buffer_bus_addr; uint8_t using_msi; }; static void ismt_intr(void *arg) { struct ismt_softc *sc = arg; uint32_t val; val = bus_read_4(sc->mmio_res, ISMT_MSTR_MSTS); ISMT_DEBUG(sc->pcidev, "%s MSTS=0x%x\n", __func__, val); val |= (ISMT_MSTS_MIS | ISMT_MSTS_MEIS); bus_write_4(sc->mmio_res, ISMT_MSTR_MSTS, val); wakeup(sc); } static int ismt_callback(device_t dev, int index, void *data) { struct ismt_softc *sc; int acquired, err; sc = device_get_softc(dev); switch (index) { case SMB_REQUEST_BUS: acquired = atomic_cmpset_ptr( (uintptr_t *)&sc->bus_reserved, (uintptr_t)NULL, (uintptr_t)curthread); ISMT_DEBUG(dev, "SMB_REQUEST_BUS acquired=%d\n", acquired); if (acquired) err = 0; else err = EWOULDBLOCK; break; case SMB_RELEASE_BUS: KASSERT(sc->bus_reserved == curthread, ("SMB_RELEASE_BUS called by wrong thread\n")); ISMT_DEBUG(dev, "SMB_RELEASE_BUS\n"); atomic_store_rel_ptr((uintptr_t *)&sc->bus_reserved, (uintptr_t)NULL); err = 0; break; default: err = SMB_EABORT; break; } return (err); } static struct ismt_desc * ismt_alloc_desc(struct ismt_softc *sc) { struct ismt_desc *desc; KASSERT(sc->bus_reserved == curthread, ("curthread %p did not request bus (%p has reserved)\n", curthread, sc->bus_reserved)); desc = &sc->desc[sc->head++]; if (sc->head == ISMT_DESC_ENTRIES) sc->head = 0; memset(desc, 0, sizeof(*desc)); return (desc); } static int ismt_submit(struct ismt_softc *sc, struct ismt_desc *desc, uint8_t slave, uint8_t is_read) { uint32_t err, fmhp, val; desc->control |= ISMT_DESC_FAIR; if (sc->using_msi) desc->control |= ISMT_DESC_INT; desc->tgtaddr_rw = ISMT_DESC_ADDR_RW(slave, is_read); desc->dptr_low = (sc->dma_buffer_bus_addr & 0xFFFFFFFFLL); desc->dptr_high = (sc->dma_buffer_bus_addr >> 32); wmb(); fmhp = sc->head << 16; val = bus_read_4(sc->mmio_res, ISMT_MSTR_MCTRL); val &= ~ISMT_MCTRL_FMHP; val |= fmhp; bus_write_4(sc->mmio_res, ISMT_MSTR_MCTRL, val); /* set the start bit */ val = bus_read_4(sc->mmio_res, ISMT_MSTR_MCTRL); val |= ISMT_MCTRL_SS; bus_write_4(sc->mmio_res, ISMT_MSTR_MCTRL, val); err = tsleep(sc, PWAIT, "ismt_wait", 5 * hz); if (err != 0) { ISMT_DEBUG(sc->pcidev, "%s timeout\n", __func__); return (SMB_ETIMEOUT); } ISMT_DEBUG(sc->pcidev, "%s status=0x%x\n", __func__, desc->status); if (desc->status & ISMT_DESC_SCS) return (SMB_ENOERR); if (desc->status & ISMT_DESC_NAK) return (SMB_ENOACK); if (desc->status & ISMT_DESC_CRC) return (SMB_EBUSERR); if (desc->status & ISMT_DESC_COL) return (SMB_ECOLLI); if (desc->status & ISMT_DESC_LPR) return (SMB_EINVAL); if (desc->status & (ISMT_DESC_DLTO | ISMT_DESC_CLTO)) return (SMB_ETIMEOUT); return (SMB_EBUSERR); } static int ismt_quick(device_t dev, u_char slave, int how) { struct ismt_desc *desc; struct ismt_softc *sc; int is_read; ISMT_DEBUG(dev, "%s\n", __func__); if (how != SMB_QREAD && how != SMB_QWRITE) { return (SMB_ENOTSUPP); } sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); is_read = (how == SMB_QREAD ? 1 : 0); return (ismt_submit(sc, desc, slave, is_read)); } static int ismt_sendb(device_t dev, u_char slave, char byte) { struct ismt_desc *desc; struct ismt_softc *sc; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->control = ISMT_DESC_CWRL; desc->wr_len_cmd = byte; return (ismt_submit(sc, desc, slave, 0)); } static int ismt_recvb(device_t dev, u_char slave, char *byte) { struct ismt_desc *desc; struct ismt_softc *sc; int err; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->rd_len = 1; err = ismt_submit(sc, desc, slave, 1); if (err != SMB_ENOERR) return (err); *byte = sc->dma_buffer[0]; return (err); } static int ismt_writeb(device_t dev, u_char slave, char cmd, char byte) { struct ismt_desc *desc; struct ismt_softc *sc; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->wr_len_cmd = 2; sc->dma_buffer[0] = cmd; sc->dma_buffer[1] = byte; return (ismt_submit(sc, desc, slave, 0)); } static int ismt_writew(device_t dev, u_char slave, char cmd, short word) { struct ismt_desc *desc; struct ismt_softc *sc; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->wr_len_cmd = 3; sc->dma_buffer[0] = cmd; sc->dma_buffer[1] = word & 0xFF; sc->dma_buffer[2] = word >> 8; return (ismt_submit(sc, desc, slave, 0)); } static int ismt_readb(device_t dev, u_char slave, char cmd, char *byte) { struct ismt_desc *desc; struct ismt_softc *sc; int err; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->control = ISMT_DESC_CWRL; desc->wr_len_cmd = cmd; desc->rd_len = 1; err = ismt_submit(sc, desc, slave, 1); if (err != SMB_ENOERR) return (err); *byte = sc->dma_buffer[0]; return (err); } static int ismt_readw(device_t dev, u_char slave, char cmd, short *word) { struct ismt_desc *desc; struct ismt_softc *sc; int err; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->control = ISMT_DESC_CWRL; desc->wr_len_cmd = cmd; desc->rd_len = 2; err = ismt_submit(sc, desc, slave, 1); if (err != SMB_ENOERR) return (err); *word = sc->dma_buffer[0] | (sc->dma_buffer[1] << 8); return (err); } static int ismt_pcall(device_t dev, u_char slave, char cmd, short sdata, short *rdata) { struct ismt_desc *desc; struct ismt_softc *sc; int err; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->wr_len_cmd = 3; desc->rd_len = 2; sc->dma_buffer[0] = cmd; sc->dma_buffer[1] = sdata & 0xff; sc->dma_buffer[2] = sdata >> 8; err = ismt_submit(sc, desc, slave, 0); if (err != SMB_ENOERR) return (err); *rdata = sc->dma_buffer[0] | (sc->dma_buffer[1] << 8); return (err); } static int ismt_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf) { struct ismt_desc *desc; struct ismt_softc *sc; ISMT_DEBUG(dev, "%s\n", __func__); if (count == 0 || count > ISMT_MAX_BLOCK_SIZE) return (SMB_EINVAL); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->control = ISMT_DESC_I2C; desc->wr_len_cmd = count + 1; sc->dma_buffer[0] = cmd; memcpy(&sc->dma_buffer[1], buf, count); return (ismt_submit(sc, desc, slave, 0)); } static int ismt_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf) { struct ismt_desc *desc; struct ismt_softc *sc; int err; ISMT_DEBUG(dev, "%s\n", __func__); if (*count == 0 || *count > ISMT_MAX_BLOCK_SIZE) return (SMB_EINVAL); sc = device_get_softc(dev); desc = ismt_alloc_desc(sc); desc->control = ISMT_DESC_I2C | ISMT_DESC_CWRL; desc->wr_len_cmd = cmd; desc->rd_len = *count; err = ismt_submit(sc, desc, slave, 0); if (err != SMB_ENOERR) return (err); memcpy(buf, sc->dma_buffer, desc->rxbytes); *count = desc->rxbytes; return (err); } static int ismt_detach(device_t dev) { struct ismt_softc *sc; int error; ISMT_DEBUG(dev, "%s\n", __func__); sc = device_get_softc(dev); error = bus_generic_detach(dev); if (error) return (error); if (sc->intr_handle != NULL) { bus_teardown_intr(dev, sc->intr_res, sc->intr_handle); sc->intr_handle = NULL; } if (sc->intr_res != NULL) { bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res); sc->intr_res = NULL; } if (sc->using_msi == 1) pci_release_msi(dev); if (sc->mmio_res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, sc->mmio_rid, sc->mmio_res); sc->mmio_res = NULL; } bus_dmamap_unload(sc->desc_dma_tag, sc->desc_dma_map); bus_dmamap_unload(sc->dma_buffer_dma_tag, sc->dma_buffer_dma_map); bus_dmamem_free(sc->desc_dma_tag, sc->desc, sc->desc_dma_map); bus_dmamem_free(sc->dma_buffer_dma_tag, sc->dma_buffer, sc->dma_buffer_dma_map); bus_dma_tag_destroy(sc->desc_dma_tag); bus_dma_tag_destroy(sc->dma_buffer_dma_tag); pci_disable_busmaster(dev); return 0; } static void ismt_single_map(void *arg, bus_dma_segment_t *seg, int nseg, int error) { uint64_t *bus_addr = (uint64_t *)arg; KASSERT(error == 0, ("%s: error=%d\n", __func__, error)); KASSERT(nseg == 1, ("%s: nseg=%d\n", __func__, nseg)); *bus_addr = seg[0].ds_addr; } static int ismt_attach(device_t dev) { struct ismt_softc *sc = device_get_softc(dev); int err, num_vectors, val; sc->pcidev = dev; pci_enable_busmaster(dev); - if ((sc->smbdev = device_add_child(dev, "smbus", -1)) == NULL) { + if ((sc->smbdev = device_add_child(dev, "smbus", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "no smbus child found\n"); err = ENXIO; goto fail; } sc->mmio_rid = PCIR_BAR(0); sc->mmio_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mmio_rid, RF_ACTIVE); if (sc->mmio_res == NULL) { device_printf(dev, "cannot allocate mmio region\n"); err = ENOMEM; goto fail; } sc->mmio_tag = rman_get_bustag(sc->mmio_res); sc->mmio_handle = rman_get_bushandle(sc->mmio_res); /* Attach "smbus" child */ bus_attach_children(dev); bus_dma_tag_create(bus_get_dma_tag(dev), 4, PAGE_SIZE, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, DESC_SIZE, 1, DESC_SIZE, 0, NULL, NULL, &sc->desc_dma_tag); bus_dma_tag_create(bus_get_dma_tag(dev), 4, PAGE_SIZE, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, DMA_BUFFER_SIZE, 1, DMA_BUFFER_SIZE, 0, NULL, NULL, &sc->dma_buffer_dma_tag); bus_dmamap_create(sc->desc_dma_tag, 0, &sc->desc_dma_map); bus_dmamap_create(sc->dma_buffer_dma_tag, 0, &sc->dma_buffer_dma_map); bus_dmamem_alloc(sc->desc_dma_tag, (void **)&sc->desc, BUS_DMA_WAITOK, &sc->desc_dma_map); bus_dmamem_alloc(sc->dma_buffer_dma_tag, (void **)&sc->dma_buffer, BUS_DMA_WAITOK, &sc->dma_buffer_dma_map); bus_dmamap_load(sc->desc_dma_tag, sc->desc_dma_map, sc->desc, DESC_SIZE, ismt_single_map, &sc->desc_bus_addr, 0); bus_dmamap_load(sc->dma_buffer_dma_tag, sc->dma_buffer_dma_map, sc->dma_buffer, DMA_BUFFER_SIZE, ismt_single_map, &sc->dma_buffer_bus_addr, 0); bus_write_4(sc->mmio_res, ISMT_MSTR_MDBA, (sc->desc_bus_addr & 0xFFFFFFFFLL)); bus_write_4(sc->mmio_res, ISMT_MSTR_MDBA + 4, (sc->desc_bus_addr >> 32)); /* initialize the Master Control Register (MCTRL) */ bus_write_4(sc->mmio_res, ISMT_MSTR_MCTRL, ISMT_MCTRL_MEIE); /* initialize the Master Status Register (MSTS) */ bus_write_4(sc->mmio_res, ISMT_MSTR_MSTS, 0); /* initialize the Master Descriptor Size (MDS) */ val = bus_read_4(sc->mmio_res, ISMT_MSTR_MDS); val &= ~ISMT_MDS_MASK; val |= (ISMT_DESC_ENTRIES - 1); bus_write_4(sc->mmio_res, ISMT_MSTR_MDS, val); sc->using_msi = 1; if (pci_msi_count(dev) == 0) { sc->using_msi = 0; goto intx; } num_vectors = 1; if (pci_alloc_msi(dev, &num_vectors) != 0) { sc->using_msi = 0; goto intx; } sc->intr_rid = 1; sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, RF_ACTIVE); if (sc->intr_res == NULL) { sc->using_msi = 0; pci_release_msi(dev); } intx: if (sc->using_msi == 0) { sc->intr_rid = 0; sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, RF_SHAREABLE | RF_ACTIVE); if (sc->intr_res == NULL) { device_printf(dev, "cannot allocate irq\n"); err = ENXIO; goto fail; } } ISMT_DEBUG(dev, "using_msi = %d\n", sc->using_msi); err = bus_setup_intr(dev, sc->intr_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, ismt_intr, sc, &sc->intr_handle); if (err != 0) { device_printf(dev, "cannot setup interrupt\n"); err = ENXIO; goto fail; } return (0); fail: ismt_detach(dev); return (err); } #define ID_INTEL_S1200_SMT0 0x0c598086 #define ID_INTEL_S1200_SMT1 0x0c5a8086 #define ID_INTEL_C2000_SMT 0x1f158086 #define ID_INTEL_C3000_SMT 0x19ac8086 static int ismt_probe(device_t dev) { const char *desc; switch (pci_get_devid(dev)) { case ID_INTEL_S1200_SMT0: desc = "Atom Processor S1200 SMBus 2.0 Controller 0"; break; case ID_INTEL_S1200_SMT1: desc = "Atom Processor S1200 SMBus 2.0 Controller 1"; break; case ID_INTEL_C2000_SMT: desc = "Atom Processor C2000 SMBus 2.0"; break; case ID_INTEL_C3000_SMT: desc = "Atom Processor C3000 SMBus 2.0"; break; default: return (ENXIO); } device_set_desc(dev, desc); return (BUS_PROBE_DEFAULT); } /* Device methods */ static device_method_t ismt_pci_methods[] = { DEVMETHOD(device_probe, ismt_probe), DEVMETHOD(device_attach, ismt_attach), DEVMETHOD(device_detach, ismt_detach), DEVMETHOD(smbus_callback, ismt_callback), DEVMETHOD(smbus_quick, ismt_quick), DEVMETHOD(smbus_sendb, ismt_sendb), DEVMETHOD(smbus_recvb, ismt_recvb), DEVMETHOD(smbus_writeb, ismt_writeb), DEVMETHOD(smbus_writew, ismt_writew), DEVMETHOD(smbus_readb, ismt_readb), DEVMETHOD(smbus_readw, ismt_readw), DEVMETHOD(smbus_pcall, ismt_pcall), DEVMETHOD(smbus_bwrite, ismt_bwrite), DEVMETHOD(smbus_bread, ismt_bread), DEVMETHOD_END }; static driver_t ismt_pci_driver = { "ismt", ismt_pci_methods, sizeof(struct ismt_softc) }; DRIVER_MODULE(ismt, pci, ismt_pci_driver, 0, 0); DRIVER_MODULE(smbus, ismt, smbus_driver, 0, 0); MODULE_DEPEND(ismt, pci, 1, 1, 1); MODULE_DEPEND(ismt, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); MODULE_VERSION(ismt, 1); diff --git a/sys/dev/kvm_clock/kvm_clock.c b/sys/dev/kvm_clock/kvm_clock.c index dd756b99b5e0..43da9b69edc8 100644 --- a/sys/dev/kvm_clock/kvm_clock.c +++ b/sys/dev/kvm_clock/kvm_clock.c @@ -1,297 +1,297 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Bryan Venteicher * Copyright (c) 2021 Mathieu Chouquet-Stringer * Copyright (c) 2021 Juniper Networks, Inc. * Copyright (c) 2021 Klara, Inc. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * Linux KVM paravirtual clock support * * References: * - [1] https://www.kernel.org/doc/html/latest/virt/kvm/cpuid.html * - [2] https://www.kernel.org/doc/html/latest/virt/kvm/msr.html */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clock_if.h" #define KVM_CLOCK_DEVNAME "kvmclock" /* * Note: Chosen to be (1) above HPET's value (always 950), (2) above the TSC's * default value of 800, and (3) below the TSC's value when it supports the * "Invariant TSC" feature and is believed to be synchronized across all CPUs. */ #define KVM_CLOCK_TC_QUALITY 975 struct kvm_clock_softc { struct pvclock pvc; struct pvclock_wall_clock wc; struct pvclock_vcpu_time_info *timeinfos; u_int msr_tc; u_int msr_wc; #ifndef EARLY_AP_STARTUP int firstcpu; #endif }; static struct pvclock_wall_clock *kvm_clock_get_wallclock(void *arg); static void kvm_clock_system_time_enable(struct kvm_clock_softc *sc, const cpuset_t *cpus); static void kvm_clock_system_time_enable_pcpu(void *arg); static void kvm_clock_setup_sysctl(device_t); static struct pvclock_wall_clock * kvm_clock_get_wallclock(void *arg) { struct kvm_clock_softc *sc = arg; wrmsr(sc->msr_wc, vtophys(&sc->wc)); return (&sc->wc); } static void kvm_clock_system_time_enable(struct kvm_clock_softc *sc, const cpuset_t *cpus) { smp_rendezvous_cpus(*cpus, NULL, kvm_clock_system_time_enable_pcpu, NULL, sc); } static void kvm_clock_system_time_enable_pcpu(void *arg) { struct kvm_clock_softc *sc = arg; /* * See [2]; the lsb of this MSR is the system time enable bit. */ wrmsr(sc->msr_tc, vtophys(&(sc->timeinfos)[curcpu]) | 1); } #ifndef EARLY_AP_STARTUP static void kvm_clock_init_smp(void *arg __unused) { devclass_t kvm_clock_devclass; cpuset_t cpus; struct kvm_clock_softc *sc; kvm_clock_devclass = devclass_find(KVM_CLOCK_DEVNAME); sc = devclass_get_softc(kvm_clock_devclass, 0); if (sc == NULL || mp_ncpus == 1) return; /* * Register with the hypervisor on all CPUs except the one that * registered in kvm_clock_attach(). */ cpus = all_cpus; KASSERT(CPU_ISSET(sc->firstcpu, &cpus), ("%s: invalid first CPU %d", __func__, sc->firstcpu)); CPU_CLR(sc->firstcpu, &cpus); kvm_clock_system_time_enable(sc, &cpus); } SYSINIT(kvm_clock, SI_SUB_SMP, SI_ORDER_ANY, kvm_clock_init_smp, NULL); #endif static void kvm_clock_identify(driver_t *driver, device_t parent) { u_int regs[4]; kvm_cpuid_get_features(regs); if ((regs[0] & (KVM_FEATURE_CLOCKSOURCE2 | KVM_FEATURE_CLOCKSOURCE)) == 0) return; - if (device_find_child(parent, KVM_CLOCK_DEVNAME, -1)) + if (device_find_child(parent, KVM_CLOCK_DEVNAME, DEVICE_UNIT_ANY)) return; BUS_ADD_CHILD(parent, 0, KVM_CLOCK_DEVNAME, 0); } static int kvm_clock_probe(device_t dev) { device_set_desc(dev, "KVM paravirtual clock"); return (BUS_PROBE_DEFAULT); } static int kvm_clock_attach(device_t dev) { u_int regs[4]; struct kvm_clock_softc *sc = device_get_softc(dev); bool stable_flag_supported; /* Process KVM "features" CPUID leaf content: */ kvm_cpuid_get_features(regs); if ((regs[0] & KVM_FEATURE_CLOCKSOURCE2) != 0) { sc->msr_tc = KVM_MSR_SYSTEM_TIME_NEW; sc->msr_wc = KVM_MSR_WALL_CLOCK_NEW; } else { KASSERT((regs[0] & KVM_FEATURE_CLOCKSOURCE) != 0, ("Clocksource feature flags disappeared since " "kvm_clock_identify: regs[0] %#0x.", regs[0])); sc->msr_tc = KVM_MSR_SYSTEM_TIME; sc->msr_wc = KVM_MSR_WALL_CLOCK; } stable_flag_supported = (regs[0] & KVM_FEATURE_CLOCKSOURCE_STABLE_BIT) != 0; /* Set up 'struct pvclock_vcpu_time_info' page(s): */ sc->timeinfos = kmem_malloc(mp_ncpus * sizeof(struct pvclock_vcpu_time_info), M_WAITOK | M_ZERO); #ifdef EARLY_AP_STARTUP kvm_clock_system_time_enable(sc, &all_cpus); #else sc->firstcpu = curcpu; kvm_clock_system_time_enable_pcpu(sc); #endif /* * Init pvclock; register KVM clock wall clock, register KVM clock * timecounter, and set up the requisite infrastructure for vDSO access * to this timecounter. * Regarding 'tc_flags': Since the KVM MSR documentation does not * specifically discuss suspend/resume scenarios, conservatively * leave 'TC_FLAGS_SUSPEND_SAFE' cleared and assume that the system * time must be re-inited in such cases. */ sc->pvc.get_wallclock = kvm_clock_get_wallclock; sc->pvc.get_wallclock_arg = sc; sc->pvc.timeinfos = sc->timeinfos; sc->pvc.stable_flag_supported = stable_flag_supported; pvclock_init(&sc->pvc, dev, KVM_CLOCK_DEVNAME, KVM_CLOCK_TC_QUALITY, 0); kvm_clock_setup_sysctl(dev); return (0); } static int kvm_clock_detach(device_t dev) { struct kvm_clock_softc *sc = device_get_softc(dev); return (pvclock_destroy(&sc->pvc)); } static int kvm_clock_suspend(device_t dev) { return (0); } static int kvm_clock_resume(device_t dev) { /* * See note in 'kvm_clock_attach()' regarding 'TC_FLAGS_SUSPEND_SAFE'; * conservatively assume that the system time must be re-inited in * suspend/resume scenarios. */ kvm_clock_system_time_enable(device_get_softc(dev), &all_cpus); pvclock_resume(); inittodr(time_second); return (0); } static int kvm_clock_gettime(device_t dev, struct timespec *ts) { struct kvm_clock_softc *sc = device_get_softc(dev); pvclock_gettime(&sc->pvc, ts); return (0); } static int kvm_clock_settime(device_t dev, struct timespec *ts) { /* * Even though it is not possible to set the KVM clock's wall clock, to * avoid the possibility of periodic benign error messages from * 'settime_task_func()', report success rather than, e.g., 'ENODEV'. */ return (0); } static int kvm_clock_tsc_freq_sysctl(SYSCTL_HANDLER_ARGS) { struct kvm_clock_softc *sc = oidp->oid_arg1; uint64_t freq = pvclock_tsc_freq(sc->timeinfos); return (sysctl_handle_64(oidp, &freq, 0, req)); } static void kvm_clock_setup_sysctl(device_t dev) { struct kvm_clock_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); struct sysctl_oid *tree = device_get_sysctl_tree(dev); struct sysctl_oid_list *child = SYSCTL_CHILDREN(tree); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "tsc_freq", CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, kvm_clock_tsc_freq_sysctl, "QU", "Time Stamp Counter frequency"); } static device_method_t kvm_clock_methods[] = { DEVMETHOD(device_identify, kvm_clock_identify), DEVMETHOD(device_probe, kvm_clock_probe), DEVMETHOD(device_attach, kvm_clock_attach), DEVMETHOD(device_detach, kvm_clock_detach), DEVMETHOD(device_suspend, kvm_clock_suspend), DEVMETHOD(device_resume, kvm_clock_resume), /* clock interface */ DEVMETHOD(clock_gettime, kvm_clock_gettime), DEVMETHOD(clock_settime, kvm_clock_settime), DEVMETHOD_END }; static driver_t kvm_clock_driver = { KVM_CLOCK_DEVNAME, kvm_clock_methods, sizeof(struct kvm_clock_softc), }; DRIVER_MODULE(kvm_clock, nexus, kvm_clock_driver, 0, 0); diff --git a/sys/dev/mdio/mdio.c b/sys/dev/mdio/mdio.c index 706a1048c5eb..0ef7e7453799 100644 --- a/sys/dev/mdio/mdio.c +++ b/sys/dev/mdio/mdio.c @@ -1,126 +1,126 @@ /*- * Copyright (c) 2011-2012 Stefan Bethke. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include "mdio_if.h" static void mdio_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, mdio_driver.name, -1) == NULL) + if (device_find_child(parent, mdio_driver.name, DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, mdio_driver.name, DEVICE_UNIT_ANY); } static int mdio_probe(device_t dev) { device_set_desc(dev, "MDIO"); return (BUS_PROBE_SPECIFIC); } static int mdio_attach(device_t dev) { bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); return (0); } static int mdio_readreg(device_t dev, int phy, int reg) { return (MDIO_READREG(device_get_parent(dev), phy, reg)); } static int mdio_writereg(device_t dev, int phy, int reg, int val) { return (MDIO_WRITEREG(device_get_parent(dev), phy, reg, val)); } static int mdio_readextreg(device_t dev, int phy, int devad, int reg) { return (MDIO_READEXTREG(device_get_parent(dev), phy, devad, reg)); } static int mdio_writeextreg(device_t dev, int phy, int devad, int reg, int val) { return (MDIO_WRITEEXTREG(device_get_parent(dev), phy, devad, reg, val)); } static void mdio_hinted_child(device_t dev, const char *name, int unit) { device_add_child(dev, name, unit); } static device_method_t mdio_methods[] = { /* device interface */ DEVMETHOD(device_identify, mdio_identify), DEVMETHOD(device_probe, mdio_probe), DEVMETHOD(device_attach, mdio_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), DEVMETHOD(bus_hinted_child, mdio_hinted_child), /* MDIO access */ DEVMETHOD(mdio_readreg, mdio_readreg), DEVMETHOD(mdio_writereg, mdio_writereg), DEVMETHOD(mdio_readextreg, mdio_readextreg), DEVMETHOD(mdio_writeextreg, mdio_writeextreg), DEVMETHOD_END }; driver_t mdio_driver = { "mdio", mdio_methods, 0 }; MODULE_VERSION(mdio, 1); diff --git a/sys/dev/mfi/mfi.c b/sys/dev/mfi/mfi.c index 9e41464a7a2a..13e5dfc84fd1 100644 --- a/sys/dev/mfi/mfi.c +++ b/sys/dev/mfi/mfi.c @@ -1,3789 +1,3791 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 IronPort Systems * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /*- * Copyright (c) 2007 LSI Corp. * Copyright (c) 2007 Rajesh Prabhakaran. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_mfi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int mfi_alloc_commands(struct mfi_softc *); static int mfi_comms_init(struct mfi_softc *); static int mfi_get_controller_info(struct mfi_softc *); static int mfi_get_log_state(struct mfi_softc *, struct mfi_evt_log_state **); static int mfi_parse_entries(struct mfi_softc *, int, int); static void mfi_data_cb(void *, bus_dma_segment_t *, int, int); static void mfi_startup(void *arg); static void mfi_intr(void *arg); static void mfi_ldprobe(struct mfi_softc *sc); static void mfi_syspdprobe(struct mfi_softc *sc); static void mfi_handle_evt(void *context, int pending); static int mfi_aen_register(struct mfi_softc *sc, int seq, int locale); static void mfi_aen_complete(struct mfi_command *); static int mfi_add_ld(struct mfi_softc *sc, int); static void mfi_add_ld_complete(struct mfi_command *); static int mfi_add_sys_pd(struct mfi_softc *sc, int); static void mfi_add_sys_pd_complete(struct mfi_command *); static struct mfi_command * mfi_bio_command(struct mfi_softc *); static void mfi_bio_complete(struct mfi_command *); static struct mfi_command *mfi_build_ldio(struct mfi_softc *,struct bio*); static struct mfi_command *mfi_build_syspdio(struct mfi_softc *,struct bio*); static int mfi_send_frame(struct mfi_softc *, struct mfi_command *); static int mfi_std_send_frame(struct mfi_softc *, struct mfi_command *); static int mfi_abort(struct mfi_softc *, struct mfi_command **); static int mfi_linux_ioctl_int(struct cdev *, u_long, caddr_t, int, struct thread *); static void mfi_timeout(void *); static int mfi_user_command(struct mfi_softc *, struct mfi_ioc_passthru *); static void mfi_enable_intr_xscale(struct mfi_softc *sc); static void mfi_enable_intr_ppc(struct mfi_softc *sc); static int32_t mfi_read_fw_status_xscale(struct mfi_softc *sc); static int32_t mfi_read_fw_status_ppc(struct mfi_softc *sc); static int mfi_check_clear_intr_xscale(struct mfi_softc *sc); static int mfi_check_clear_intr_ppc(struct mfi_softc *sc); static void mfi_issue_cmd_xscale(struct mfi_softc *sc, bus_addr_t bus_add, uint32_t frame_cnt); static void mfi_issue_cmd_ppc(struct mfi_softc *sc, bus_addr_t bus_add, uint32_t frame_cnt); static int mfi_config_lock(struct mfi_softc *sc, uint32_t opcode); static void mfi_config_unlock(struct mfi_softc *sc, int locked); static int mfi_check_command_pre(struct mfi_softc *sc, struct mfi_command *cm); static void mfi_check_command_post(struct mfi_softc *sc, struct mfi_command *cm); static int mfi_check_for_sscd(struct mfi_softc *sc, struct mfi_command *cm); SYSCTL_NODE(_hw, OID_AUTO, mfi, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "MFI driver parameters"); static int mfi_event_locale = MFI_EVT_LOCALE_ALL; SYSCTL_INT(_hw_mfi, OID_AUTO, event_locale, CTLFLAG_RWTUN, &mfi_event_locale, 0, "event message locale"); static int mfi_event_class = MFI_EVT_CLASS_INFO; SYSCTL_INT(_hw_mfi, OID_AUTO, event_class, CTLFLAG_RWTUN, &mfi_event_class, 0, "event message class"); static int mfi_max_cmds = 128; SYSCTL_INT(_hw_mfi, OID_AUTO, max_cmds, CTLFLAG_RDTUN, &mfi_max_cmds, 0, "Max commands limit (-1 = controller limit)"); static int mfi_detect_jbod_change = 1; SYSCTL_INT(_hw_mfi, OID_AUTO, detect_jbod_change, CTLFLAG_RWTUN, &mfi_detect_jbod_change, 0, "Detect a change to a JBOD"); int mfi_polled_cmd_timeout = MFI_POLL_TIMEOUT_SECS; SYSCTL_INT(_hw_mfi, OID_AUTO, polled_cmd_timeout, CTLFLAG_RWTUN, &mfi_polled_cmd_timeout, 0, "Polled command timeout - used for firmware flash etc (in seconds)"); static int mfi_cmd_timeout = MFI_CMD_TIMEOUT; SYSCTL_INT(_hw_mfi, OID_AUTO, cmd_timeout, CTLFLAG_RWTUN, &mfi_cmd_timeout, 0, "Command timeout (in seconds)"); /* Management interface */ static d_open_t mfi_open; static d_close_t mfi_close; static d_ioctl_t mfi_ioctl; static d_poll_t mfi_poll; static struct cdevsw mfi_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = mfi_open, .d_close = mfi_close, .d_ioctl = mfi_ioctl, .d_poll = mfi_poll, .d_name = "mfi", }; MALLOC_DEFINE(M_MFIBUF, "mfibuf", "Buffers for the MFI driver"); #define MFI_INQ_LENGTH SHORT_INQUIRY_LENGTH struct mfi_skinny_dma_info mfi_skinny; static void mfi_enable_intr_xscale(struct mfi_softc *sc) { MFI_WRITE4(sc, MFI_OMSK, 0x01); } static void mfi_enable_intr_ppc(struct mfi_softc *sc) { if (sc->mfi_flags & MFI_FLAGS_1078) { MFI_WRITE4(sc, MFI_ODCR0, 0xFFFFFFFF); MFI_WRITE4(sc, MFI_OMSK, ~MFI_1078_EIM); } else if (sc->mfi_flags & MFI_FLAGS_GEN2) { MFI_WRITE4(sc, MFI_ODCR0, 0xFFFFFFFF); MFI_WRITE4(sc, MFI_OMSK, ~MFI_GEN2_EIM); } else if (sc->mfi_flags & MFI_FLAGS_SKINNY) { MFI_WRITE4(sc, MFI_OMSK, ~0x00000001); } } static int32_t mfi_read_fw_status_xscale(struct mfi_softc *sc) { return MFI_READ4(sc, MFI_OMSG0); } static int32_t mfi_read_fw_status_ppc(struct mfi_softc *sc) { return MFI_READ4(sc, MFI_OSP0); } static int mfi_check_clear_intr_xscale(struct mfi_softc *sc) { int32_t status; status = MFI_READ4(sc, MFI_OSTS); if ((status & MFI_OSTS_INTR_VALID) == 0) return 1; MFI_WRITE4(sc, MFI_OSTS, status); return 0; } static int mfi_check_clear_intr_ppc(struct mfi_softc *sc) { int32_t status; status = MFI_READ4(sc, MFI_OSTS); if (sc->mfi_flags & MFI_FLAGS_1078) { if (!(status & MFI_1078_RM)) { return 1; } } else if (sc->mfi_flags & MFI_FLAGS_GEN2) { if (!(status & MFI_GEN2_RM)) { return 1; } } else if (sc->mfi_flags & MFI_FLAGS_SKINNY) { if (!(status & MFI_SKINNY_RM)) { return 1; } } if (sc->mfi_flags & MFI_FLAGS_SKINNY) MFI_WRITE4(sc, MFI_OSTS, status); else MFI_WRITE4(sc, MFI_ODCR0, status); return 0; } static void mfi_issue_cmd_xscale(struct mfi_softc *sc, bus_addr_t bus_add, uint32_t frame_cnt) { MFI_WRITE4(sc, MFI_IQP,(bus_add >>3)|frame_cnt); } static void mfi_issue_cmd_ppc(struct mfi_softc *sc, bus_addr_t bus_add, uint32_t frame_cnt) { if (sc->mfi_flags & MFI_FLAGS_SKINNY) { MFI_WRITE4(sc, MFI_IQPL, (bus_add | frame_cnt <<1)|1 ); MFI_WRITE4(sc, MFI_IQPH, 0x00000000); } else { MFI_WRITE4(sc, MFI_IQP, (bus_add | frame_cnt <<1)|1 ); } } int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t fw_state, cur_state; int max_wait, i; uint32_t cur_abs_reg_val = 0; uint32_t prev_abs_reg_val = 0; cur_abs_reg_val = sc->mfi_read_fw_status(sc); fw_state = cur_abs_reg_val & MFI_FWSTATE_MASK; while (fw_state != MFI_FWSTATE_READY) { if (bootverbose) device_printf(sc->mfi_dev, "Waiting for firmware to " "become ready\n"); cur_state = fw_state; switch (fw_state) { case MFI_FWSTATE_FAULT: device_printf(sc->mfi_dev, "Firmware fault\n"); return (ENXIO); case MFI_FWSTATE_WAIT_HANDSHAKE: if (sc->mfi_flags & MFI_FLAGS_SKINNY || sc->mfi_flags & MFI_FLAGS_TBOLT) MFI_WRITE4(sc, MFI_SKINNY_IDB, MFI_FWINIT_CLEAR_HANDSHAKE); else MFI_WRITE4(sc, MFI_IDB, MFI_FWINIT_CLEAR_HANDSHAKE); max_wait = MFI_RESET_WAIT_TIME; break; case MFI_FWSTATE_OPERATIONAL: if (sc->mfi_flags & MFI_FLAGS_SKINNY || sc->mfi_flags & MFI_FLAGS_TBOLT) MFI_WRITE4(sc, MFI_SKINNY_IDB, 7); else MFI_WRITE4(sc, MFI_IDB, MFI_FWINIT_READY); max_wait = MFI_RESET_WAIT_TIME; break; case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: max_wait = MFI_RESET_WAIT_TIME; break; case MFI_FWSTATE_FW_INIT_2: max_wait = MFI_RESET_WAIT_TIME; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_FLUSH_CACHE: max_wait = MFI_RESET_WAIT_TIME; break; case MFI_FWSTATE_DEVICE_SCAN: max_wait = MFI_RESET_WAIT_TIME; /* wait for 180 seconds */ prev_abs_reg_val = cur_abs_reg_val; break; case MFI_FWSTATE_BOOT_MESSAGE_PENDING: if (sc->mfi_flags & MFI_FLAGS_SKINNY || sc->mfi_flags & MFI_FLAGS_TBOLT) MFI_WRITE4(sc, MFI_SKINNY_IDB, MFI_FWINIT_HOTPLUG); else MFI_WRITE4(sc, MFI_IDB, MFI_FWINIT_HOTPLUG); max_wait = MFI_RESET_WAIT_TIME; break; default: device_printf(sc->mfi_dev, "Unknown firmware state %#x\n", fw_state); return (ENXIO); } for (i = 0; i < (max_wait * 10); i++) { cur_abs_reg_val = sc->mfi_read_fw_status(sc); fw_state = cur_abs_reg_val & MFI_FWSTATE_MASK; if (fw_state == cur_state) DELAY(100000); else break; } if (fw_state == MFI_FWSTATE_DEVICE_SCAN) { /* Check the device scanning progress */ if (prev_abs_reg_val != cur_abs_reg_val) { continue; } } if (fw_state == cur_state) { device_printf(sc->mfi_dev, "Firmware stuck in state " "%#x\n", fw_state); return (ENXIO); } } return (0); } static void mfi_addr_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { bus_addr_t *addr; addr = arg; *addr = segs[0].ds_addr; } int mfi_attach(struct mfi_softc *sc) { uint32_t status; int error, commsz, framessz, sensesz; int frames, unit, max_fw_sge, max_fw_cmds; uint32_t tb_mem_size = 0; struct cdev *dev_t; if (sc == NULL) return EINVAL; device_printf(sc->mfi_dev, "Megaraid SAS driver Ver %s \n", MEGASAS_VERSION); mtx_init(&sc->mfi_io_lock, "MFI I/O lock", NULL, MTX_DEF); sx_init(&sc->mfi_config_lock, "MFI config"); TAILQ_INIT(&sc->mfi_ld_tqh); TAILQ_INIT(&sc->mfi_syspd_tqh); TAILQ_INIT(&sc->mfi_ld_pend_tqh); TAILQ_INIT(&sc->mfi_syspd_pend_tqh); TAILQ_INIT(&sc->mfi_evt_queue); TASK_INIT(&sc->mfi_evt_task, 0, mfi_handle_evt, sc); TASK_INIT(&sc->mfi_map_sync_task, 0, mfi_handle_map_sync, sc); TAILQ_INIT(&sc->mfi_aen_pids); TAILQ_INIT(&sc->mfi_cam_ccbq); mfi_initq_free(sc); mfi_initq_ready(sc); mfi_initq_busy(sc); mfi_initq_bio(sc); sc->adpreset = 0; sc->last_seq_num = 0; sc->disableOnlineCtrlReset = 1; sc->issuepend_done = 1; sc->hw_crit_error = 0; if (sc->mfi_flags & MFI_FLAGS_1064R) { sc->mfi_enable_intr = mfi_enable_intr_xscale; sc->mfi_read_fw_status = mfi_read_fw_status_xscale; sc->mfi_check_clear_intr = mfi_check_clear_intr_xscale; sc->mfi_issue_cmd = mfi_issue_cmd_xscale; } else if (sc->mfi_flags & MFI_FLAGS_TBOLT) { sc->mfi_enable_intr = mfi_tbolt_enable_intr_ppc; sc->mfi_disable_intr = mfi_tbolt_disable_intr_ppc; sc->mfi_read_fw_status = mfi_tbolt_read_fw_status_ppc; sc->mfi_check_clear_intr = mfi_tbolt_check_clear_intr_ppc; sc->mfi_issue_cmd = mfi_tbolt_issue_cmd_ppc; sc->mfi_adp_reset = mfi_tbolt_adp_reset; sc->mfi_tbolt = 1; TAILQ_INIT(&sc->mfi_cmd_tbolt_tqh); } else { sc->mfi_enable_intr = mfi_enable_intr_ppc; sc->mfi_read_fw_status = mfi_read_fw_status_ppc; sc->mfi_check_clear_intr = mfi_check_clear_intr_ppc; sc->mfi_issue_cmd = mfi_issue_cmd_ppc; } /* Before we get too far, see if the firmware is working */ if ((error = mfi_transition_firmware(sc)) != 0) { device_printf(sc->mfi_dev, "Firmware not in READY state, " "error %d\n", error); return (ENXIO); } /* Start: LSIP200113393 */ if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MEGASAS_MAX_NAME*sizeof(bus_addr_t), /* maxsize */ 1, /* msegments */ MEGASAS_MAX_NAME*sizeof(bus_addr_t), /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->verbuf_h_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate verbuf_h_dmat DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->verbuf_h_dmat, (void **)&sc->verbuf, BUS_DMA_NOWAIT, &sc->verbuf_h_dmamap)) { device_printf(sc->mfi_dev, "Cannot allocate verbuf_h_dmamap memory\n"); return (ENOMEM); } bzero(sc->verbuf, MEGASAS_MAX_NAME*sizeof(bus_addr_t)); bus_dmamap_load(sc->verbuf_h_dmat, sc->verbuf_h_dmamap, sc->verbuf, MEGASAS_MAX_NAME*sizeof(bus_addr_t), mfi_addr_cb, &sc->verbuf_h_busaddr, 0); /* End: LSIP200113393 */ /* * Get information needed for sizing the contiguous memory for the * frame pool. Size down the sgl parameter since we know that * we will never need more than what's required for MFI_MAXPHYS. * It would be nice if these constants were available at runtime * instead of compile time. */ status = sc->mfi_read_fw_status(sc); max_fw_cmds = status & MFI_FWSTATE_MAXCMD_MASK; if (mfi_max_cmds > 0 && mfi_max_cmds < max_fw_cmds) { device_printf(sc->mfi_dev, "FW MaxCmds = %d, limiting to %d\n", max_fw_cmds, mfi_max_cmds); sc->mfi_max_fw_cmds = mfi_max_cmds; } else { sc->mfi_max_fw_cmds = max_fw_cmds; } max_fw_sge = (status & MFI_FWSTATE_MAXSGL_MASK) >> 16; sc->mfi_max_sge = min(max_fw_sge, ((MFI_MAXPHYS / PAGE_SIZE) + 1)); /* ThunderBolt Support get the contiguous memory */ if (sc->mfi_flags & MFI_FLAGS_TBOLT) { mfi_tbolt_init_globals(sc); device_printf(sc->mfi_dev, "MaxCmd = %d, Drv MaxCmd = %d, " "MaxSgl = %d, state = %#x\n", max_fw_cmds, sc->mfi_max_fw_cmds, sc->mfi_max_sge, status); tb_mem_size = mfi_tbolt_get_memory_requirement(sc); if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ tb_mem_size, /* maxsize */ 1, /* msegments */ tb_mem_size, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mfi_tb_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate comms DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->mfi_tb_dmat, (void **)&sc->request_message_pool, BUS_DMA_NOWAIT, &sc->mfi_tb_dmamap)) { device_printf(sc->mfi_dev, "Cannot allocate comms memory\n"); return (ENOMEM); } bzero(sc->request_message_pool, tb_mem_size); bus_dmamap_load(sc->mfi_tb_dmat, sc->mfi_tb_dmamap, sc->request_message_pool, tb_mem_size, mfi_addr_cb, &sc->mfi_tb_busaddr, 0); /* For ThunderBolt memory init */ if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 0x100, 0, /* alignmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MFI_FRAME_SIZE, /* maxsize */ 1, /* msegments */ MFI_FRAME_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mfi_tb_init_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate init DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->mfi_tb_init_dmat, (void **)&sc->mfi_tb_init, BUS_DMA_NOWAIT, &sc->mfi_tb_init_dmamap)) { device_printf(sc->mfi_dev, "Cannot allocate init memory\n"); return (ENOMEM); } bzero(sc->mfi_tb_init, MFI_FRAME_SIZE); bus_dmamap_load(sc->mfi_tb_init_dmat, sc->mfi_tb_init_dmamap, sc->mfi_tb_init, MFI_FRAME_SIZE, mfi_addr_cb, &sc->mfi_tb_init_busaddr, 0); if (mfi_tbolt_init_desc_pool(sc, sc->request_message_pool, tb_mem_size)) { device_printf(sc->mfi_dev, "Thunderbolt pool preparation error\n"); return 0; } /* Allocate DMA memory mapping for MPI2 IOC Init descriptor, we are taking it different from what we have allocated for Request and reply descriptors to avoid confusion later */ tb_mem_size = sizeof(struct MPI2_IOC_INIT_REQUEST); if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ tb_mem_size, /* maxsize */ 1, /* msegments */ tb_mem_size, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mfi_tb_ioc_init_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate comms DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->mfi_tb_ioc_init_dmat, (void **)&sc->mfi_tb_ioc_init_desc, BUS_DMA_NOWAIT, &sc->mfi_tb_ioc_init_dmamap)) { device_printf(sc->mfi_dev, "Cannot allocate comms memory\n"); return (ENOMEM); } bzero(sc->mfi_tb_ioc_init_desc, tb_mem_size); bus_dmamap_load(sc->mfi_tb_ioc_init_dmat, sc->mfi_tb_ioc_init_dmamap, sc->mfi_tb_ioc_init_desc, tb_mem_size, mfi_addr_cb, &sc->mfi_tb_ioc_init_busaddr, 0); } /* * Create the dma tag for data buffers. Used both for block I/O * and for various internal data queries. */ if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT,/* maxsize */ sc->mfi_max_sge, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT,/* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->mfi_io_lock, /* lockfuncarg */ &sc->mfi_buffer_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate buffer DMA tag\n"); return (ENOMEM); } /* * Allocate DMA memory for the comms queues. Keep it under 4GB for * efficiency. The mfi_hwcomms struct includes space for 1 reply queue * entry, so the calculated size here will be will be 1 more than * mfi_max_fw_cmds. This is apparently a requirement of the hardware. */ commsz = (sizeof(uint32_t) * sc->mfi_max_fw_cmds) + sizeof(struct mfi_hwcomms); if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ commsz, /* maxsize */ 1, /* msegments */ commsz, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mfi_comms_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate comms DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->mfi_comms_dmat, (void **)&sc->mfi_comms, BUS_DMA_NOWAIT, &sc->mfi_comms_dmamap)) { device_printf(sc->mfi_dev, "Cannot allocate comms memory\n"); return (ENOMEM); } bzero(sc->mfi_comms, commsz); bus_dmamap_load(sc->mfi_comms_dmat, sc->mfi_comms_dmamap, sc->mfi_comms, commsz, mfi_addr_cb, &sc->mfi_comms_busaddr, 0); /* * Allocate DMA memory for the command frames. Keep them in the * lower 4GB for efficiency. Calculate the size of the commands at * the same time; each command is one 64 byte frame plus a set of * additional frames for holding sg lists or other data. * The assumption here is that the SG list will start at the second * frame and not use the unused bytes in the first frame. While this * isn't technically correct, it simplifies the calculation and allows * for command frames that might be larger than an mfi_io_frame. */ if (sizeof(bus_addr_t) == 8) { sc->mfi_sge_size = sizeof(struct mfi_sg64); sc->mfi_flags |= MFI_FLAGS_SG64; } else { sc->mfi_sge_size = sizeof(struct mfi_sg32); } if (sc->mfi_flags & MFI_FLAGS_SKINNY) sc->mfi_sge_size = sizeof(struct mfi_sg_skinny); frames = (sc->mfi_sge_size * sc->mfi_max_sge - 1) / MFI_FRAME_SIZE + 2; sc->mfi_cmd_size = frames * MFI_FRAME_SIZE; framessz = sc->mfi_cmd_size * sc->mfi_max_fw_cmds; if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 64, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ framessz, /* maxsize */ 1, /* nsegments */ framessz, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mfi_frames_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate frame DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->mfi_frames_dmat, (void **)&sc->mfi_frames, BUS_DMA_NOWAIT, &sc->mfi_frames_dmamap)) { device_printf(sc->mfi_dev, "Cannot allocate frames memory\n"); return (ENOMEM); } bzero(sc->mfi_frames, framessz); bus_dmamap_load(sc->mfi_frames_dmat, sc->mfi_frames_dmamap, sc->mfi_frames, framessz, mfi_addr_cb, &sc->mfi_frames_busaddr,0); /* * Allocate DMA memory for the frame sense data. Keep them in the * lower 4GB for efficiency */ sensesz = sc->mfi_max_fw_cmds * MFI_SENSE_LEN; if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 4, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sensesz, /* maxsize */ 1, /* nsegments */ sensesz, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mfi_sense_dmat)) { device_printf(sc->mfi_dev, "Cannot allocate sense DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->mfi_sense_dmat, (void **)&sc->mfi_sense, BUS_DMA_NOWAIT, &sc->mfi_sense_dmamap)) { device_printf(sc->mfi_dev, "Cannot allocate sense memory\n"); return (ENOMEM); } bus_dmamap_load(sc->mfi_sense_dmat, sc->mfi_sense_dmamap, sc->mfi_sense, sensesz, mfi_addr_cb, &sc->mfi_sense_busaddr, 0); if ((error = mfi_alloc_commands(sc)) != 0) return (error); /* Before moving the FW to operational state, check whether * hostmemory is required by the FW or not */ /* ThunderBolt MFI_IOC2 INIT */ if (sc->mfi_flags & MFI_FLAGS_TBOLT) { sc->mfi_disable_intr(sc); mtx_lock(&sc->mfi_io_lock); if ((error = mfi_tbolt_init_MFI_queue(sc)) != 0) { device_printf(sc->mfi_dev, "TB Init has failed with error %d\n",error); mtx_unlock(&sc->mfi_io_lock); return error; } mtx_unlock(&sc->mfi_io_lock); if ((error = mfi_tbolt_alloc_cmd(sc)) != 0) return error; if (bus_setup_intr(sc->mfi_dev, sc->mfi_irq, INTR_MPSAFE|INTR_TYPE_BIO, NULL, mfi_intr_tbolt, sc, &sc->mfi_intr)) { device_printf(sc->mfi_dev, "Cannot set up interrupt\n"); return (EINVAL); } sc->mfi_intr_ptr = mfi_intr_tbolt; sc->mfi_enable_intr(sc); } else { if ((error = mfi_comms_init(sc)) != 0) return (error); if (bus_setup_intr(sc->mfi_dev, sc->mfi_irq, INTR_MPSAFE|INTR_TYPE_BIO, NULL, mfi_intr, sc, &sc->mfi_intr)) { device_printf(sc->mfi_dev, "Cannot set up interrupt\n"); return (EINVAL); } sc->mfi_intr_ptr = mfi_intr; sc->mfi_enable_intr(sc); } if ((error = mfi_get_controller_info(sc)) != 0) return (error); sc->disableOnlineCtrlReset = 0; /* Register a config hook to probe the bus for arrays */ sc->mfi_ich.ich_func = mfi_startup; sc->mfi_ich.ich_arg = sc; if (config_intrhook_establish(&sc->mfi_ich) != 0) { device_printf(sc->mfi_dev, "Cannot establish configuration " "hook\n"); return (EINVAL); } mtx_lock(&sc->mfi_io_lock); if ((error = mfi_aen_setup(sc, 0), 0) != 0) { mtx_unlock(&sc->mfi_io_lock); return (error); } mtx_unlock(&sc->mfi_io_lock); /* * Register a shutdown handler. */ if ((sc->mfi_eh = EVENTHANDLER_REGISTER(shutdown_final, mfi_shutdown, sc, SHUTDOWN_PRI_DEFAULT)) == NULL) { device_printf(sc->mfi_dev, "Warning: shutdown event " "registration failed\n"); } /* * Create the control device for doing management */ unit = device_get_unit(sc->mfi_dev); sc->mfi_cdev = make_dev(&mfi_cdevsw, unit, UID_ROOT, GID_OPERATOR, 0640, "mfi%d", unit); if (unit == 0) make_dev_alias_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK, &dev_t, sc->mfi_cdev, "%s", "megaraid_sas_ioctl_node"); if (sc->mfi_cdev != NULL) sc->mfi_cdev->si_drv1 = sc; SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->mfi_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->mfi_dev)), OID_AUTO, "delete_busy_volumes", CTLFLAG_RW, &sc->mfi_delete_busy_volumes, 0, "Allow removal of busy volumes"); SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->mfi_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->mfi_dev)), OID_AUTO, "keep_deleted_volumes", CTLFLAG_RW, &sc->mfi_keep_deleted_volumes, 0, "Don't detach the mfid device for a busy volume that is deleted"); device_add_child(sc->mfi_dev, "mfip", DEVICE_UNIT_ANY); bus_attach_children(sc->mfi_dev); /* Start the timeout watchdog */ callout_init(&sc->mfi_watchdog_callout, 1); callout_reset(&sc->mfi_watchdog_callout, mfi_cmd_timeout * hz, mfi_timeout, sc); if (sc->mfi_flags & MFI_FLAGS_TBOLT) { mtx_lock(&sc->mfi_io_lock); mfi_tbolt_sync_map_info(sc); mtx_unlock(&sc->mfi_io_lock); } return (0); } static int mfi_alloc_commands(struct mfi_softc *sc) { struct mfi_command *cm; int i, j; /* * XXX Should we allocate all the commands up front, or allocate on * demand later like 'aac' does? */ sc->mfi_commands = malloc(sizeof(sc->mfi_commands[0]) * sc->mfi_max_fw_cmds, M_MFIBUF, M_WAITOK | M_ZERO); for (i = 0; i < sc->mfi_max_fw_cmds; i++) { cm = &sc->mfi_commands[i]; cm->cm_frame = (union mfi_frame *)((uintptr_t)sc->mfi_frames + sc->mfi_cmd_size * i); cm->cm_frame_busaddr = sc->mfi_frames_busaddr + sc->mfi_cmd_size * i; cm->cm_frame->header.context = i; cm->cm_sense = &sc->mfi_sense[i]; cm->cm_sense_busaddr= sc->mfi_sense_busaddr + MFI_SENSE_LEN * i; cm->cm_sc = sc; cm->cm_index = i; if (bus_dmamap_create(sc->mfi_buffer_dmat, 0, &cm->cm_dmamap) == 0) { mtx_lock(&sc->mfi_io_lock); mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); } else { device_printf(sc->mfi_dev, "Failed to allocate %d " "command blocks, only allocated %d\n", sc->mfi_max_fw_cmds, i - 1); for (j = 0; j < i; j++) { cm = &sc->mfi_commands[i]; bus_dmamap_destroy(sc->mfi_buffer_dmat, cm->cm_dmamap); } free(sc->mfi_commands, M_MFIBUF); sc->mfi_commands = NULL; return (ENOMEM); } } return (0); } void mfi_release_command(struct mfi_command *cm) { struct mfi_frame_header *hdr; uint32_t *hdr_data; mtx_assert(&cm->cm_sc->mfi_io_lock, MA_OWNED); /* * Zero out the important fields of the frame, but make sure the * context field is preserved. For efficiency, handle the fields * as 32 bit words. Clear out the first S/G entry too for safety. */ hdr = &cm->cm_frame->header; if (cm->cm_data != NULL && hdr->sg_count) { cm->cm_sg->sg32[0].len = 0; cm->cm_sg->sg32[0].addr = 0; } /* * Command may be on other queues e.g. busy queue depending on the * flow of a previous call to mfi_mapcmd, so ensure its dequeued * properly */ if ((cm->cm_flags & MFI_ON_MFIQ_BUSY) != 0) mfi_remove_busy(cm); if ((cm->cm_flags & MFI_ON_MFIQ_READY) != 0) mfi_remove_ready(cm); /* We're not expecting it to be on any other queue but check */ if ((cm->cm_flags & MFI_ON_MFIQ_MASK) != 0) { panic("Command %p is still on another queue, flags = %#x", cm, cm->cm_flags); } /* tbolt cleanup */ if ((cm->cm_flags & MFI_CMD_TBOLT) != 0) { mfi_tbolt_return_cmd(cm->cm_sc, cm->cm_sc->mfi_cmd_pool_tbolt[cm->cm_extra_frames - 1], cm); } hdr_data = (uint32_t *)cm->cm_frame; hdr_data[0] = 0; /* cmd, sense_len, cmd_status, scsi_status */ hdr_data[1] = 0; /* target_id, lun_id, cdb_len, sg_count */ hdr_data[4] = 0; /* flags, timeout */ hdr_data[5] = 0; /* data_len */ cm->cm_extra_frames = 0; cm->cm_flags = 0; cm->cm_complete = NULL; cm->cm_private = NULL; cm->cm_data = NULL; cm->cm_sg = 0; cm->cm_total_frame_size = 0; cm->retry_for_fw_reset = 0; mfi_enqueue_free(cm); } int mfi_dcmd_command(struct mfi_softc *sc, struct mfi_command **cmp, uint32_t opcode, void **bufp, size_t bufsize) { struct mfi_command *cm; struct mfi_dcmd_frame *dcmd; void *buf = NULL; uint32_t context = 0; mtx_assert(&sc->mfi_io_lock, MA_OWNED); cm = mfi_dequeue_free(sc); if (cm == NULL) return (EBUSY); /* Zero out the MFI frame */ context = cm->cm_frame->header.context; bzero(cm->cm_frame, sizeof(union mfi_frame)); cm->cm_frame->header.context = context; if ((bufsize > 0) && (bufp != NULL)) { if (*bufp == NULL) { buf = malloc(bufsize, M_MFIBUF, M_NOWAIT|M_ZERO); if (buf == NULL) { mfi_release_command(cm); return (ENOMEM); } *bufp = buf; } else { buf = *bufp; } } dcmd = &cm->cm_frame->dcmd; bzero(dcmd->mbox, MFI_MBOX_SIZE); dcmd->header.cmd = MFI_CMD_DCMD; dcmd->header.timeout = 0; dcmd->header.flags = 0; dcmd->header.data_len = bufsize; dcmd->header.scsi_status = 0; dcmd->opcode = opcode; cm->cm_sg = &dcmd->sgl; cm->cm_total_frame_size = MFI_DCMD_FRAME_SIZE; cm->cm_flags = 0; cm->cm_data = buf; cm->cm_private = buf; cm->cm_len = bufsize; *cmp = cm; if ((bufp != NULL) && (*bufp == NULL) && (buf != NULL)) *bufp = buf; return (0); } static int mfi_comms_init(struct mfi_softc *sc) { struct mfi_command *cm; struct mfi_init_frame *init; struct mfi_init_qinfo *qinfo; int error; uint32_t context = 0; mtx_lock(&sc->mfi_io_lock); if ((cm = mfi_dequeue_free(sc)) == NULL) { mtx_unlock(&sc->mfi_io_lock); return (EBUSY); } /* Zero out the MFI frame */ context = cm->cm_frame->header.context; bzero(cm->cm_frame, sizeof(union mfi_frame)); cm->cm_frame->header.context = context; /* * Abuse the SG list area of the frame to hold the init_qinfo * object; */ init = &cm->cm_frame->init; qinfo = (struct mfi_init_qinfo *)((uintptr_t)init + MFI_FRAME_SIZE); bzero(qinfo, sizeof(struct mfi_init_qinfo)); qinfo->rq_entries = sc->mfi_max_fw_cmds + 1; qinfo->rq_addr_lo = sc->mfi_comms_busaddr + offsetof(struct mfi_hwcomms, hw_reply_q); qinfo->pi_addr_lo = sc->mfi_comms_busaddr + offsetof(struct mfi_hwcomms, hw_pi); qinfo->ci_addr_lo = sc->mfi_comms_busaddr + offsetof(struct mfi_hwcomms, hw_ci); init->header.cmd = MFI_CMD_INIT; init->header.data_len = sizeof(struct mfi_init_qinfo); init->qinfo_new_addr_lo = cm->cm_frame_busaddr + MFI_FRAME_SIZE; cm->cm_data = NULL; cm->cm_flags = MFI_CMD_POLLED; if ((error = mfi_mapcmd(sc, cm)) != 0) device_printf(sc->mfi_dev, "failed to send init command\n"); mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); return (error); } static int mfi_get_controller_info(struct mfi_softc *sc) { struct mfi_command *cm = NULL; struct mfi_ctrl_info *ci = NULL; uint32_t max_sectors_1, max_sectors_2; int error; mtx_lock(&sc->mfi_io_lock); error = mfi_dcmd_command(sc, &cm, MFI_DCMD_CTRL_GETINFO, (void **)&ci, sizeof(*ci)); if (error) goto out; cm->cm_flags = MFI_CMD_DATAIN | MFI_CMD_POLLED; if ((error = mfi_mapcmd(sc, cm)) != 0) { device_printf(sc->mfi_dev, "Failed to get controller info\n"); sc->mfi_max_io = (sc->mfi_max_sge - 1) * PAGE_SIZE / MFI_SECTOR_LEN; error = 0; goto out; } bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); max_sectors_1 = (1 << ci->stripe_sz_ops.max) * ci->max_strips_per_io; max_sectors_2 = ci->max_request_size; sc->mfi_max_io = min(max_sectors_1, max_sectors_2); sc->disableOnlineCtrlReset = ci->properties.OnOffProperties.disableOnlineCtrlReset; out: if (ci) free(ci, M_MFIBUF); if (cm) mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); return (error); } static int mfi_get_log_state(struct mfi_softc *sc, struct mfi_evt_log_state **log_state) { struct mfi_command *cm = NULL; int error; mtx_assert(&sc->mfi_io_lock, MA_OWNED); error = mfi_dcmd_command(sc, &cm, MFI_DCMD_CTRL_EVENT_GETINFO, (void **)log_state, sizeof(**log_state)); if (error) goto out; cm->cm_flags = MFI_CMD_DATAIN | MFI_CMD_POLLED; if ((error = mfi_mapcmd(sc, cm)) != 0) { device_printf(sc->mfi_dev, "Failed to get log state\n"); goto out; } bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); out: if (cm) mfi_release_command(cm); return (error); } int mfi_aen_setup(struct mfi_softc *sc, uint32_t seq_start) { struct mfi_evt_log_state *log_state = NULL; union mfi_evt class_locale; int error = 0; uint32_t seq; mtx_assert(&sc->mfi_io_lock, MA_OWNED); class_locale.members.reserved = 0; class_locale.members.locale = mfi_event_locale; class_locale.members.evt_class = mfi_event_class; if (seq_start == 0) { if ((error = mfi_get_log_state(sc, &log_state)) != 0) goto out; sc->mfi_boot_seq_num = log_state->boot_seq_num; /* * Walk through any events that fired since the last * shutdown. */ if ((error = mfi_parse_entries(sc, log_state->shutdown_seq_num, log_state->newest_seq_num)) != 0) goto out; seq = log_state->newest_seq_num; } else seq = seq_start; error = mfi_aen_register(sc, seq, class_locale.word); out: free(log_state, M_MFIBUF); return (error); } int mfi_wait_command(struct mfi_softc *sc, struct mfi_command *cm) { mtx_assert(&sc->mfi_io_lock, MA_OWNED); cm->cm_complete = NULL; /* * MegaCli can issue a DCMD of 0. In this case do nothing * and return 0 to it as status */ if (cm->cm_frame->dcmd.opcode == 0) { cm->cm_frame->header.cmd_status = MFI_STAT_OK; cm->cm_error = 0; return (cm->cm_error); } mfi_enqueue_ready(cm); mfi_startio(sc); if ((cm->cm_flags & MFI_CMD_COMPLETED) == 0) msleep(cm, &sc->mfi_io_lock, PRIBIO, "mfiwait", 0); return (cm->cm_error); } void mfi_free(struct mfi_softc *sc) { struct mfi_command *cm; int i; callout_drain(&sc->mfi_watchdog_callout); if (sc->mfi_cdev != NULL) destroy_dev(sc->mfi_cdev); if (sc->mfi_commands != NULL) { for (i = 0; i < sc->mfi_max_fw_cmds; i++) { cm = &sc->mfi_commands[i]; bus_dmamap_destroy(sc->mfi_buffer_dmat, cm->cm_dmamap); } free(sc->mfi_commands, M_MFIBUF); sc->mfi_commands = NULL; } if (sc->mfi_intr) bus_teardown_intr(sc->mfi_dev, sc->mfi_irq, sc->mfi_intr); if (sc->mfi_irq != NULL) bus_release_resource(sc->mfi_dev, SYS_RES_IRQ, sc->mfi_irq_rid, sc->mfi_irq); if (sc->mfi_sense_busaddr != 0) bus_dmamap_unload(sc->mfi_sense_dmat, sc->mfi_sense_dmamap); if (sc->mfi_sense != NULL) bus_dmamem_free(sc->mfi_sense_dmat, sc->mfi_sense, sc->mfi_sense_dmamap); if (sc->mfi_sense_dmat != NULL) bus_dma_tag_destroy(sc->mfi_sense_dmat); if (sc->mfi_frames_busaddr != 0) bus_dmamap_unload(sc->mfi_frames_dmat, sc->mfi_frames_dmamap); if (sc->mfi_frames != NULL) bus_dmamem_free(sc->mfi_frames_dmat, sc->mfi_frames, sc->mfi_frames_dmamap); if (sc->mfi_frames_dmat != NULL) bus_dma_tag_destroy(sc->mfi_frames_dmat); if (sc->mfi_comms_busaddr != 0) bus_dmamap_unload(sc->mfi_comms_dmat, sc->mfi_comms_dmamap); if (sc->mfi_comms != NULL) bus_dmamem_free(sc->mfi_comms_dmat, sc->mfi_comms, sc->mfi_comms_dmamap); if (sc->mfi_comms_dmat != NULL) bus_dma_tag_destroy(sc->mfi_comms_dmat); /* ThunderBolt contiguous memory free here */ if (sc->mfi_flags & MFI_FLAGS_TBOLT) { if (sc->mfi_tb_busaddr != 0) bus_dmamap_unload(sc->mfi_tb_dmat, sc->mfi_tb_dmamap); if (sc->request_message_pool != NULL) bus_dmamem_free(sc->mfi_tb_dmat, sc->request_message_pool, sc->mfi_tb_dmamap); if (sc->mfi_tb_dmat != NULL) bus_dma_tag_destroy(sc->mfi_tb_dmat); /* Version buffer memory free */ /* Start LSIP200113393 */ if (sc->verbuf_h_busaddr != 0) bus_dmamap_unload(sc->verbuf_h_dmat, sc->verbuf_h_dmamap); if (sc->verbuf != NULL) bus_dmamem_free(sc->verbuf_h_dmat, sc->verbuf, sc->verbuf_h_dmamap); if (sc->verbuf_h_dmat != NULL) bus_dma_tag_destroy(sc->verbuf_h_dmat); /* End LSIP200113393 */ /* ThunderBolt INIT packet memory Free */ if (sc->mfi_tb_init_busaddr != 0) bus_dmamap_unload(sc->mfi_tb_init_dmat, sc->mfi_tb_init_dmamap); if (sc->mfi_tb_init != NULL) bus_dmamem_free(sc->mfi_tb_init_dmat, sc->mfi_tb_init, sc->mfi_tb_init_dmamap); if (sc->mfi_tb_init_dmat != NULL) bus_dma_tag_destroy(sc->mfi_tb_init_dmat); /* ThunderBolt IOC Init Desc memory free here */ if (sc->mfi_tb_ioc_init_busaddr != 0) bus_dmamap_unload(sc->mfi_tb_ioc_init_dmat, sc->mfi_tb_ioc_init_dmamap); if (sc->mfi_tb_ioc_init_desc != NULL) bus_dmamem_free(sc->mfi_tb_ioc_init_dmat, sc->mfi_tb_ioc_init_desc, sc->mfi_tb_ioc_init_dmamap); if (sc->mfi_tb_ioc_init_dmat != NULL) bus_dma_tag_destroy(sc->mfi_tb_ioc_init_dmat); if (sc->mfi_cmd_pool_tbolt != NULL) { for (int i = 0; i < sc->mfi_max_fw_cmds; i++) { if (sc->mfi_cmd_pool_tbolt[i] != NULL) { free(sc->mfi_cmd_pool_tbolt[i], M_MFIBUF); sc->mfi_cmd_pool_tbolt[i] = NULL; } } free(sc->mfi_cmd_pool_tbolt, M_MFIBUF); sc->mfi_cmd_pool_tbolt = NULL; } if (sc->request_desc_pool != NULL) { free(sc->request_desc_pool, M_MFIBUF); sc->request_desc_pool = NULL; } } if (sc->mfi_buffer_dmat != NULL) bus_dma_tag_destroy(sc->mfi_buffer_dmat); if (sc->mfi_parent_dmat != NULL) bus_dma_tag_destroy(sc->mfi_parent_dmat); if (mtx_initialized(&sc->mfi_io_lock)) { mtx_destroy(&sc->mfi_io_lock); sx_destroy(&sc->mfi_config_lock); } return; } static void mfi_startup(void *arg) { struct mfi_softc *sc; sc = (struct mfi_softc *)arg; sc->mfi_enable_intr(sc); sx_xlock(&sc->mfi_config_lock); mtx_lock(&sc->mfi_io_lock); mfi_ldprobe(sc); if (sc->mfi_flags & MFI_FLAGS_SKINNY) mfi_syspdprobe(sc); mtx_unlock(&sc->mfi_io_lock); sx_xunlock(&sc->mfi_config_lock); config_intrhook_disestablish(&sc->mfi_ich); } static void mfi_intr(void *arg) { struct mfi_softc *sc; struct mfi_command *cm; uint32_t pi, ci, context; sc = (struct mfi_softc *)arg; if (sc->mfi_check_clear_intr(sc)) return; restart: pi = sc->mfi_comms->hw_pi; ci = sc->mfi_comms->hw_ci; mtx_lock(&sc->mfi_io_lock); while (ci != pi) { context = sc->mfi_comms->hw_reply_q[ci]; if (context < sc->mfi_max_fw_cmds) { cm = &sc->mfi_commands[context]; mfi_remove_busy(cm); cm->cm_error = 0; mfi_complete(sc, cm); } if (++ci == (sc->mfi_max_fw_cmds + 1)) ci = 0; } sc->mfi_comms->hw_ci = ci; /* Give defered I/O a chance to run */ sc->mfi_flags &= ~MFI_FLAGS_QFRZN; mfi_startio(sc); mtx_unlock(&sc->mfi_io_lock); /* * Dummy read to flush the bus; this ensures that the indexes are up * to date. Restart processing if more commands have come it. */ (void)sc->mfi_read_fw_status(sc); if (pi != sc->mfi_comms->hw_pi) goto restart; return; } int mfi_shutdown(struct mfi_softc *sc) { struct mfi_dcmd_frame *dcmd; struct mfi_command *cm; int error; if (sc->mfi_aen_cm != NULL) { sc->cm_aen_abort = 1; mfi_abort(sc, &sc->mfi_aen_cm); } if (sc->mfi_map_sync_cm != NULL) { sc->cm_map_abort = 1; mfi_abort(sc, &sc->mfi_map_sync_cm); } mtx_lock(&sc->mfi_io_lock); error = mfi_dcmd_command(sc, &cm, MFI_DCMD_CTRL_SHUTDOWN, NULL, 0); if (error) { mtx_unlock(&sc->mfi_io_lock); return (error); } dcmd = &cm->cm_frame->dcmd; dcmd->header.flags = MFI_FRAME_DIR_NONE; cm->cm_flags = MFI_CMD_POLLED; cm->cm_data = NULL; if ((error = mfi_mapcmd(sc, cm)) != 0) device_printf(sc->mfi_dev, "Failed to shutdown controller\n"); mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); return (error); } static void mfi_syspdprobe(struct mfi_softc *sc) { struct mfi_frame_header *hdr; struct mfi_command *cm = NULL; struct mfi_pd_list *pdlist = NULL; struct mfi_system_pd *syspd, *tmp; struct mfi_system_pending *syspd_pend; int error, i, found; sx_assert(&sc->mfi_config_lock, SA_XLOCKED); mtx_assert(&sc->mfi_io_lock, MA_OWNED); /* Add SYSTEM PD's */ error = mfi_dcmd_command(sc, &cm, MFI_DCMD_PD_LIST_QUERY, (void **)&pdlist, sizeof(*pdlist)); if (error) { device_printf(sc->mfi_dev, "Error while forming SYSTEM PD list\n"); goto out; } cm->cm_flags = MFI_CMD_DATAIN | MFI_CMD_POLLED; cm->cm_frame->dcmd.mbox[0] = MR_PD_QUERY_TYPE_EXPOSED_TO_HOST; cm->cm_frame->dcmd.mbox[1] = 0; if (mfi_mapcmd(sc, cm) != 0) { device_printf(sc->mfi_dev, "Failed to get syspd device listing\n"); goto out; } bus_dmamap_sync(sc->mfi_buffer_dmat,cm->cm_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); hdr = &cm->cm_frame->header; if (hdr->cmd_status != MFI_STAT_OK) { device_printf(sc->mfi_dev, "MFI_DCMD_PD_LIST_QUERY failed %x\n", hdr->cmd_status); goto out; } /* Get each PD and add it to the system */ for (i = 0; i < pdlist->count; i++) { if (pdlist->addr[i].device_id == pdlist->addr[i].encl_device_id) continue; found = 0; TAILQ_FOREACH(syspd, &sc->mfi_syspd_tqh, pd_link) { if (syspd->pd_id == pdlist->addr[i].device_id) found = 1; } TAILQ_FOREACH(syspd_pend, &sc->mfi_syspd_pend_tqh, pd_link) { if (syspd_pend->pd_id == pdlist->addr[i].device_id) found = 1; } if (found == 0) mfi_add_sys_pd(sc, pdlist->addr[i].device_id); } /* Delete SYSPD's whose state has been changed */ TAILQ_FOREACH_SAFE(syspd, &sc->mfi_syspd_tqh, pd_link, tmp) { found = 0; for (i = 0; i < pdlist->count; i++) { if (syspd->pd_id == pdlist->addr[i].device_id) { found = 1; break; } } if (found == 0) { printf("DELETE\n"); mtx_unlock(&sc->mfi_io_lock); bus_topo_lock(); device_delete_child(sc->mfi_dev, syspd->pd_dev); bus_topo_unlock(); mtx_lock(&sc->mfi_io_lock); } } out: if (pdlist) free(pdlist, M_MFIBUF); if (cm) mfi_release_command(cm); return; } static void mfi_ldprobe(struct mfi_softc *sc) { struct mfi_frame_header *hdr; struct mfi_command *cm = NULL; struct mfi_ld_list *list = NULL; struct mfi_disk *ld; struct mfi_disk_pending *ld_pend; int error, i; sx_assert(&sc->mfi_config_lock, SA_XLOCKED); mtx_assert(&sc->mfi_io_lock, MA_OWNED); error = mfi_dcmd_command(sc, &cm, MFI_DCMD_LD_GET_LIST, (void **)&list, sizeof(*list)); if (error) goto out; cm->cm_flags = MFI_CMD_DATAIN; if (mfi_wait_command(sc, cm) != 0) { device_printf(sc->mfi_dev, "Failed to get device listing\n"); goto out; } hdr = &cm->cm_frame->header; if (hdr->cmd_status != MFI_STAT_OK) { device_printf(sc->mfi_dev, "MFI_DCMD_LD_GET_LIST failed %x\n", hdr->cmd_status); goto out; } for (i = 0; i < list->ld_count; i++) { TAILQ_FOREACH(ld, &sc->mfi_ld_tqh, ld_link) { if (ld->ld_id == list->ld_list[i].ld.v.target_id) goto skip_add; } TAILQ_FOREACH(ld_pend, &sc->mfi_ld_pend_tqh, ld_link) { if (ld_pend->ld_id == list->ld_list[i].ld.v.target_id) goto skip_add; } mfi_add_ld(sc, list->ld_list[i].ld.v.target_id); skip_add:; } out: if (list) free(list, M_MFIBUF); if (cm) mfi_release_command(cm); return; } /* * The timestamp is the number of seconds since 00:00 Jan 1, 2000. If * the bits in 24-31 are all set, then it is the number of seconds since * boot. */ static const char * format_timestamp(uint32_t timestamp) { static char buffer[32]; if ((timestamp & 0xff000000) == 0xff000000) snprintf(buffer, sizeof(buffer), "boot + %us", timestamp & 0x00ffffff); else snprintf(buffer, sizeof(buffer), "%us", timestamp); return (buffer); } static const char * format_class(int8_t class) { static char buffer[6]; switch (class) { case MFI_EVT_CLASS_DEBUG: return ("debug"); case MFI_EVT_CLASS_PROGRESS: return ("progress"); case MFI_EVT_CLASS_INFO: return ("info"); case MFI_EVT_CLASS_WARNING: return ("WARN"); case MFI_EVT_CLASS_CRITICAL: return ("CRIT"); case MFI_EVT_CLASS_FATAL: return ("FATAL"); case MFI_EVT_CLASS_DEAD: return ("DEAD"); default: snprintf(buffer, sizeof(buffer), "%d", class); return (buffer); } } static void mfi_decode_evt(struct mfi_softc *sc, struct mfi_evt_detail *detail) { struct mfi_system_pd *syspd = NULL; device_printf(sc->mfi_dev, "%d (%s/0x%04x/%s) - %s\n", detail->seq, format_timestamp(detail->time), detail->evt_class.members.locale, format_class(detail->evt_class.members.evt_class), detail->description); /* Don't act on old AEN's or while shutting down */ if (detail->seq < sc->mfi_boot_seq_num || sc->mfi_detaching) return; switch (detail->arg_type) { case MR_EVT_ARGS_NONE: if (detail->code == MR_EVT_CTRL_HOST_BUS_SCAN_REQUESTED) { device_printf(sc->mfi_dev, "HostBus scan raised\n"); if (mfi_detect_jbod_change) { /* * Probe for new SYSPD's and Delete * invalid SYSPD's */ sx_xlock(&sc->mfi_config_lock); mtx_lock(&sc->mfi_io_lock); mfi_syspdprobe(sc); mtx_unlock(&sc->mfi_io_lock); sx_xunlock(&sc->mfi_config_lock); } } break; case MR_EVT_ARGS_LD_STATE: /* During load time driver reads all the events starting * from the one that has been logged after shutdown. Avoid * these old events. */ if (detail->args.ld_state.new_state == MFI_LD_STATE_OFFLINE ) { /* Remove the LD */ struct mfi_disk *ld; TAILQ_FOREACH(ld, &sc->mfi_ld_tqh, ld_link) { if (ld->ld_id == detail->args.ld_state.ld.target_id) break; } /* Fix: for kernel panics when SSCD is removed KASSERT(ld != NULL, ("volume dissappeared")); */ if (ld != NULL) { bus_topo_lock(); device_delete_child(sc->mfi_dev, ld->ld_dev); bus_topo_unlock(); } } break; case MR_EVT_ARGS_PD: if (detail->code == MR_EVT_PD_REMOVED) { if (mfi_detect_jbod_change) { /* * If the removed device is a SYSPD then * delete it */ TAILQ_FOREACH(syspd, &sc->mfi_syspd_tqh, pd_link) { if (syspd->pd_id == detail->args.pd.device_id) { bus_topo_lock(); device_delete_child( sc->mfi_dev, syspd->pd_dev); bus_topo_unlock(); break; } } } } if (detail->code == MR_EVT_PD_INSERTED) { if (mfi_detect_jbod_change) { /* Probe for new SYSPD's */ sx_xlock(&sc->mfi_config_lock); mtx_lock(&sc->mfi_io_lock); mfi_syspdprobe(sc); mtx_unlock(&sc->mfi_io_lock); sx_xunlock(&sc->mfi_config_lock); } } if (sc->mfi_cam_rescan_cb != NULL && (detail->code == MR_EVT_PD_INSERTED || detail->code == MR_EVT_PD_REMOVED)) { sc->mfi_cam_rescan_cb(sc, detail->args.pd.device_id); } break; } } static void mfi_queue_evt(struct mfi_softc *sc, struct mfi_evt_detail *detail) { struct mfi_evt_queue_elm *elm; mtx_assert(&sc->mfi_io_lock, MA_OWNED); elm = malloc(sizeof(*elm), M_MFIBUF, M_NOWAIT|M_ZERO); if (elm == NULL) return; memcpy(&elm->detail, detail, sizeof(*detail)); TAILQ_INSERT_TAIL(&sc->mfi_evt_queue, elm, link); taskqueue_enqueue(taskqueue_swi, &sc->mfi_evt_task); } static void mfi_handle_evt(void *context, int pending) { TAILQ_HEAD(,mfi_evt_queue_elm) queue; struct mfi_softc *sc; struct mfi_evt_queue_elm *elm; sc = context; TAILQ_INIT(&queue); mtx_lock(&sc->mfi_io_lock); TAILQ_CONCAT(&queue, &sc->mfi_evt_queue, link); mtx_unlock(&sc->mfi_io_lock); while ((elm = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, elm, link); mfi_decode_evt(sc, &elm->detail); free(elm, M_MFIBUF); } } static int mfi_aen_register(struct mfi_softc *sc, int seq, int locale) { struct mfi_command *cm; struct mfi_dcmd_frame *dcmd; union mfi_evt current_aen, prior_aen; struct mfi_evt_detail *ed = NULL; int error = 0; mtx_assert(&sc->mfi_io_lock, MA_OWNED); current_aen.word = locale; if (sc->mfi_aen_cm != NULL) { prior_aen.word = ((uint32_t *)&sc->mfi_aen_cm->cm_frame->dcmd.mbox)[1]; if (prior_aen.members.evt_class <= current_aen.members.evt_class && !((prior_aen.members.locale & current_aen.members.locale) ^current_aen.members.locale)) { return (0); } else { prior_aen.members.locale |= current_aen.members.locale; if (prior_aen.members.evt_class < current_aen.members.evt_class) current_aen.members.evt_class = prior_aen.members.evt_class; mfi_abort(sc, &sc->mfi_aen_cm); } } error = mfi_dcmd_command(sc, &cm, MFI_DCMD_CTRL_EVENT_WAIT, (void **)&ed, sizeof(*ed)); if (error) goto out; dcmd = &cm->cm_frame->dcmd; ((uint32_t *)&dcmd->mbox)[0] = seq; ((uint32_t *)&dcmd->mbox)[1] = locale; cm->cm_flags = MFI_CMD_DATAIN; cm->cm_complete = mfi_aen_complete; sc->last_seq_num = seq; sc->mfi_aen_cm = cm; mfi_enqueue_ready(cm); mfi_startio(sc); out: return (error); } static void mfi_aen_complete(struct mfi_command *cm) { struct mfi_frame_header *hdr; struct mfi_softc *sc; struct mfi_evt_detail *detail; struct mfi_aen *mfi_aen_entry, *tmp; int seq = 0, aborted = 0; sc = cm->cm_sc; mtx_assert(&sc->mfi_io_lock, MA_OWNED); if (sc->mfi_aen_cm == NULL) return; hdr = &cm->cm_frame->header; if (sc->cm_aen_abort || hdr->cmd_status == MFI_STAT_INVALID_STATUS) { sc->cm_aen_abort = 0; aborted = 1; } else { sc->mfi_aen_triggered = 1; if (sc->mfi_poll_waiting) { sc->mfi_poll_waiting = 0; selwakeup(&sc->mfi_select); } detail = cm->cm_data; mfi_queue_evt(sc, detail); seq = detail->seq + 1; TAILQ_FOREACH_SAFE(mfi_aen_entry, &sc->mfi_aen_pids, aen_link, tmp) { TAILQ_REMOVE(&sc->mfi_aen_pids, mfi_aen_entry, aen_link); PROC_LOCK(mfi_aen_entry->p); kern_psignal(mfi_aen_entry->p, SIGIO); PROC_UNLOCK(mfi_aen_entry->p); free(mfi_aen_entry, M_MFIBUF); } } free(cm->cm_data, M_MFIBUF); wakeup(&sc->mfi_aen_cm); sc->mfi_aen_cm = NULL; mfi_release_command(cm); /* set it up again so the driver can catch more events */ if (!aborted) mfi_aen_setup(sc, seq); } #define MAX_EVENTS 15 static int mfi_parse_entries(struct mfi_softc *sc, int start_seq, int stop_seq) { struct mfi_command *cm; struct mfi_dcmd_frame *dcmd; struct mfi_evt_list *el; union mfi_evt class_locale; int error, i, seq, size; mtx_assert(&sc->mfi_io_lock, MA_OWNED); class_locale.members.reserved = 0; class_locale.members.locale = mfi_event_locale; class_locale.members.evt_class = mfi_event_class; size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) * (MAX_EVENTS - 1); el = malloc(size, M_MFIBUF, M_NOWAIT | M_ZERO); if (el == NULL) return (ENOMEM); for (seq = start_seq;;) { if ((cm = mfi_dequeue_free(sc)) == NULL) { free(el, M_MFIBUF); return (EBUSY); } dcmd = &cm->cm_frame->dcmd; bzero(dcmd->mbox, MFI_MBOX_SIZE); dcmd->header.cmd = MFI_CMD_DCMD; dcmd->header.timeout = 0; dcmd->header.data_len = size; dcmd->opcode = MFI_DCMD_CTRL_EVENT_GET; ((uint32_t *)&dcmd->mbox)[0] = seq; ((uint32_t *)&dcmd->mbox)[1] = class_locale.word; cm->cm_sg = &dcmd->sgl; cm->cm_total_frame_size = MFI_DCMD_FRAME_SIZE; cm->cm_flags = MFI_CMD_DATAIN | MFI_CMD_POLLED; cm->cm_data = el; cm->cm_len = size; if ((error = mfi_mapcmd(sc, cm)) != 0) { device_printf(sc->mfi_dev, "Failed to get controller entries\n"); mfi_release_command(cm); break; } bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); if (dcmd->header.cmd_status == MFI_STAT_NOT_FOUND) { mfi_release_command(cm); break; } if (dcmd->header.cmd_status != MFI_STAT_OK) { device_printf(sc->mfi_dev, "Error %d fetching controller entries\n", dcmd->header.cmd_status); mfi_release_command(cm); error = EIO; break; } mfi_release_command(cm); for (i = 0; i < el->count; i++) { /* * If this event is newer than 'stop_seq' then * break out of the loop. Note that the log * is a circular buffer so we have to handle * the case that our stop point is earlier in * the buffer than our start point. */ if (el->event[i].seq >= stop_seq) { if (start_seq <= stop_seq) break; else if (el->event[i].seq < start_seq) break; } mfi_queue_evt(sc, &el->event[i]); } seq = el->event[el->count - 1].seq + 1; } free(el, M_MFIBUF); return (error); } static int mfi_add_ld(struct mfi_softc *sc, int id) { struct mfi_command *cm; struct mfi_dcmd_frame *dcmd = NULL; struct mfi_ld_info *ld_info = NULL; struct mfi_disk_pending *ld_pend; int error; mtx_assert(&sc->mfi_io_lock, MA_OWNED); ld_pend = malloc(sizeof(*ld_pend), M_MFIBUF, M_NOWAIT | M_ZERO); if (ld_pend != NULL) { ld_pend->ld_id = id; TAILQ_INSERT_TAIL(&sc->mfi_ld_pend_tqh, ld_pend, ld_link); } error = mfi_dcmd_command(sc, &cm, MFI_DCMD_LD_GET_INFO, (void **)&ld_info, sizeof(*ld_info)); if (error) { device_printf(sc->mfi_dev, "Failed to allocate for MFI_DCMD_LD_GET_INFO %d\n", error); if (ld_info) free(ld_info, M_MFIBUF); return (error); } cm->cm_flags = MFI_CMD_DATAIN; dcmd = &cm->cm_frame->dcmd; dcmd->mbox[0] = id; if (mfi_wait_command(sc, cm) != 0) { device_printf(sc->mfi_dev, "Failed to get logical drive: %d\n", id); free(ld_info, M_MFIBUF); return (0); } if (ld_info->ld_config.params.isSSCD != 1) mfi_add_ld_complete(cm); else { mfi_release_command(cm); if (ld_info) /* SSCD drives ld_info free here */ free(ld_info, M_MFIBUF); } return (0); } static void mfi_add_ld_complete(struct mfi_command *cm) { struct mfi_frame_header *hdr; struct mfi_ld_info *ld_info; struct mfi_softc *sc; device_t child; sc = cm->cm_sc; hdr = &cm->cm_frame->header; ld_info = cm->cm_private; if (sc->cm_map_abort || hdr->cmd_status != MFI_STAT_OK) { free(ld_info, M_MFIBUF); wakeup(&sc->mfi_map_sync_cm); mfi_release_command(cm); return; } wakeup(&sc->mfi_map_sync_cm); mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); bus_topo_lock(); - if ((child = device_add_child(sc->mfi_dev, "mfid", -1)) == NULL) { + if ((child = device_add_child(sc->mfi_dev, "mfid", + DEVICE_UNIT_ANY)) == NULL) { device_printf(sc->mfi_dev, "Failed to add logical disk\n"); free(ld_info, M_MFIBUF); bus_topo_unlock(); mtx_lock(&sc->mfi_io_lock); return; } device_set_ivars(child, ld_info); device_set_desc(child, "MFI Logical Disk"); bus_attach_children(sc->mfi_dev); bus_topo_unlock(); mtx_lock(&sc->mfi_io_lock); } static int mfi_add_sys_pd(struct mfi_softc *sc, int id) { struct mfi_command *cm; struct mfi_dcmd_frame *dcmd = NULL; struct mfi_pd_info *pd_info = NULL; struct mfi_system_pending *syspd_pend; int error; mtx_assert(&sc->mfi_io_lock, MA_OWNED); syspd_pend = malloc(sizeof(*syspd_pend), M_MFIBUF, M_NOWAIT | M_ZERO); if (syspd_pend != NULL) { syspd_pend->pd_id = id; TAILQ_INSERT_TAIL(&sc->mfi_syspd_pend_tqh, syspd_pend, pd_link); } error = mfi_dcmd_command(sc, &cm, MFI_DCMD_PD_GET_INFO, (void **)&pd_info, sizeof(*pd_info)); if (error) { device_printf(sc->mfi_dev, "Failed to allocated for MFI_DCMD_PD_GET_INFO %d\n", error); if (pd_info) free(pd_info, M_MFIBUF); return (error); } cm->cm_flags = MFI_CMD_DATAIN | MFI_CMD_POLLED; dcmd = &cm->cm_frame->dcmd; dcmd->mbox[0]=id; dcmd->header.scsi_status = 0; dcmd->header.pad0 = 0; if ((error = mfi_mapcmd(sc, cm)) != 0) { device_printf(sc->mfi_dev, "Failed to get physical drive info %d\n", id); free(pd_info, M_MFIBUF); mfi_release_command(cm); return (error); } bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); mfi_add_sys_pd_complete(cm); return (0); } static void mfi_add_sys_pd_complete(struct mfi_command *cm) { struct mfi_frame_header *hdr; struct mfi_pd_info *pd_info; struct mfi_softc *sc; device_t child; sc = cm->cm_sc; hdr = &cm->cm_frame->header; pd_info = cm->cm_private; if (hdr->cmd_status != MFI_STAT_OK) { free(pd_info, M_MFIBUF); mfi_release_command(cm); return; } if (pd_info->fw_state != MFI_PD_STATE_SYSTEM) { device_printf(sc->mfi_dev, "PD=%x is not SYSTEM PD\n", pd_info->ref.v.device_id); free(pd_info, M_MFIBUF); mfi_release_command(cm); return; } mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); bus_topo_lock(); - if ((child = device_add_child(sc->mfi_dev, "mfisyspd", -1)) == NULL) { + if ((child = device_add_child(sc->mfi_dev, "mfisyspd", + DEVICE_UNIT_ANY)) == NULL) { device_printf(sc->mfi_dev, "Failed to add system pd\n"); free(pd_info, M_MFIBUF); bus_topo_unlock(); mtx_lock(&sc->mfi_io_lock); return; } device_set_ivars(child, pd_info); device_set_desc(child, "MFI System PD"); bus_attach_children(sc->mfi_dev); bus_topo_unlock(); mtx_lock(&sc->mfi_io_lock); } static struct mfi_command * mfi_bio_command(struct mfi_softc *sc) { struct bio *bio; struct mfi_command *cm = NULL; /*reserving two commands to avoid starvation for IOCTL*/ if (sc->mfi_qstat[MFIQ_FREE].q_length < 2) { return (NULL); } if ((bio = mfi_dequeue_bio(sc)) == NULL) { return (NULL); } if ((uintptr_t)bio->bio_driver2 == MFI_LD_IO) { cm = mfi_build_ldio(sc, bio); } else if ((uintptr_t) bio->bio_driver2 == MFI_SYS_PD_IO) { cm = mfi_build_syspdio(sc, bio); } if (!cm) mfi_enqueue_bio(sc, bio); return cm; } /* * mostly copied from cam/scsi/scsi_all.c:scsi_read_write */ int mfi_build_cdb(int readop, uint8_t byte2, u_int64_t lba, u_int32_t block_count, uint8_t *cdb) { int cdb_len; if (((lba & 0x1fffff) == lba) && ((block_count & 0xff) == block_count) && (byte2 == 0)) { /* We can fit in a 6 byte cdb */ struct scsi_rw_6 *scsi_cmd; scsi_cmd = (struct scsi_rw_6 *)cdb; scsi_cmd->opcode = readop ? READ_6 : WRITE_6; scsi_ulto3b(lba, scsi_cmd->addr); scsi_cmd->length = block_count & 0xff; scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); } else if (((block_count & 0xffff) == block_count) && ((lba & 0xffffffff) == lba)) { /* Need a 10 byte CDB */ struct scsi_rw_10 *scsi_cmd; scsi_cmd = (struct scsi_rw_10 *)cdb; scsi_cmd->opcode = readop ? READ_10 : WRITE_10; scsi_cmd->byte2 = byte2; scsi_ulto4b(lba, scsi_cmd->addr); scsi_cmd->reserved = 0; scsi_ulto2b(block_count, scsi_cmd->length); scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); } else if (((block_count & 0xffffffff) == block_count) && ((lba & 0xffffffff) == lba)) { /* Block count is too big for 10 byte CDB use a 12 byte CDB */ struct scsi_rw_12 *scsi_cmd; scsi_cmd = (struct scsi_rw_12 *)cdb; scsi_cmd->opcode = readop ? READ_12 : WRITE_12; scsi_cmd->byte2 = byte2; scsi_ulto4b(lba, scsi_cmd->addr); scsi_cmd->reserved = 0; scsi_ulto4b(block_count, scsi_cmd->length); scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); } else { /* * 16 byte CDB. We'll only get here if the LBA is larger * than 2^32 */ struct scsi_rw_16 *scsi_cmd; scsi_cmd = (struct scsi_rw_16 *)cdb; scsi_cmd->opcode = readop ? READ_16 : WRITE_16; scsi_cmd->byte2 = byte2; scsi_u64to8b(lba, scsi_cmd->addr); scsi_cmd->reserved = 0; scsi_ulto4b(block_count, scsi_cmd->length); scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); } return cdb_len; } extern char *unmapped_buf; static struct mfi_command * mfi_build_syspdio(struct mfi_softc *sc, struct bio *bio) { struct mfi_command *cm; struct mfi_pass_frame *pass; uint32_t context = 0; int flags = 0, blkcount = 0, readop; uint8_t cdb_len; mtx_assert(&sc->mfi_io_lock, MA_OWNED); if ((cm = mfi_dequeue_free(sc)) == NULL) return (NULL); /* Zero out the MFI frame */ context = cm->cm_frame->header.context; bzero(cm->cm_frame, sizeof(union mfi_frame)); cm->cm_frame->header.context = context; pass = &cm->cm_frame->pass; bzero(pass->cdb, 16); pass->header.cmd = MFI_CMD_PD_SCSI_IO; switch (bio->bio_cmd) { case BIO_READ: flags = MFI_CMD_DATAIN | MFI_CMD_BIO; readop = 1; break; case BIO_WRITE: flags = MFI_CMD_DATAOUT | MFI_CMD_BIO; readop = 0; break; default: /* TODO: what about BIO_DELETE??? */ biofinish(bio, NULL, EOPNOTSUPP); mfi_enqueue_free(cm); return (NULL); } /* Cheat with the sector length to avoid a non-constant division */ blkcount = howmany(bio->bio_bcount, MFI_SECTOR_LEN); /* Fill the LBA and Transfer length in CDB */ cdb_len = mfi_build_cdb(readop, 0, bio->bio_pblkno, blkcount, pass->cdb); pass->header.target_id = (uintptr_t)bio->bio_driver1; pass->header.lun_id = 0; pass->header.timeout = 0; pass->header.flags = 0; pass->header.scsi_status = 0; pass->header.sense_len = MFI_SENSE_LEN; pass->header.data_len = bio->bio_bcount; pass->header.cdb_len = cdb_len; pass->sense_addr_lo = (uint32_t)cm->cm_sense_busaddr; pass->sense_addr_hi = (uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32); cm->cm_complete = mfi_bio_complete; cm->cm_private = bio; cm->cm_data = unmapped_buf; cm->cm_len = bio->bio_bcount; cm->cm_sg = &pass->sgl; cm->cm_total_frame_size = MFI_PASS_FRAME_SIZE; cm->cm_flags = flags; return (cm); } static struct mfi_command * mfi_build_ldio(struct mfi_softc *sc, struct bio *bio) { struct mfi_io_frame *io; struct mfi_command *cm; int flags; uint32_t blkcount; uint32_t context = 0; mtx_assert(&sc->mfi_io_lock, MA_OWNED); if ((cm = mfi_dequeue_free(sc)) == NULL) return (NULL); /* Zero out the MFI frame */ context = cm->cm_frame->header.context; bzero(cm->cm_frame, sizeof(union mfi_frame)); cm->cm_frame->header.context = context; io = &cm->cm_frame->io; switch (bio->bio_cmd) { case BIO_READ: io->header.cmd = MFI_CMD_LD_READ; flags = MFI_CMD_DATAIN | MFI_CMD_BIO; break; case BIO_WRITE: io->header.cmd = MFI_CMD_LD_WRITE; flags = MFI_CMD_DATAOUT | MFI_CMD_BIO; break; default: /* TODO: what about BIO_DELETE??? */ biofinish(bio, NULL, EOPNOTSUPP); mfi_enqueue_free(cm); return (NULL); } /* Cheat with the sector length to avoid a non-constant division */ blkcount = howmany(bio->bio_bcount, MFI_SECTOR_LEN); io->header.target_id = (uintptr_t)bio->bio_driver1; io->header.timeout = 0; io->header.flags = 0; io->header.scsi_status = 0; io->header.sense_len = MFI_SENSE_LEN; io->header.data_len = blkcount; io->sense_addr_lo = (uint32_t)cm->cm_sense_busaddr; io->sense_addr_hi = (uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32); io->lba_hi = (bio->bio_pblkno & 0xffffffff00000000) >> 32; io->lba_lo = bio->bio_pblkno & 0xffffffff; cm->cm_complete = mfi_bio_complete; cm->cm_private = bio; cm->cm_data = unmapped_buf; cm->cm_len = bio->bio_bcount; cm->cm_sg = &io->sgl; cm->cm_total_frame_size = MFI_IO_FRAME_SIZE; cm->cm_flags = flags; return (cm); } static void mfi_bio_complete(struct mfi_command *cm) { struct bio *bio; struct mfi_frame_header *hdr; struct mfi_softc *sc; bio = cm->cm_private; hdr = &cm->cm_frame->header; sc = cm->cm_sc; if ((hdr->cmd_status != MFI_STAT_OK) || (hdr->scsi_status != 0)) { bio->bio_flags |= BIO_ERROR; bio->bio_error = EIO; device_printf(sc->mfi_dev, "I/O error, cmd=%p, status=%#x, " "scsi_status=%#x\n", cm, hdr->cmd_status, hdr->scsi_status); mfi_print_sense(cm->cm_sc, cm->cm_sense); } else if (cm->cm_error != 0) { bio->bio_flags |= BIO_ERROR; bio->bio_error = cm->cm_error; device_printf(sc->mfi_dev, "I/O error, cmd=%p, error=%#x\n", cm, cm->cm_error); } mfi_release_command(cm); mfi_disk_complete(bio); } void mfi_startio(struct mfi_softc *sc) { struct mfi_command *cm; struct ccb_hdr *ccbh; for (;;) { /* Don't bother if we're short on resources */ if (sc->mfi_flags & MFI_FLAGS_QFRZN) break; /* Try a command that has already been prepared */ cm = mfi_dequeue_ready(sc); if (cm == NULL) { if ((ccbh = TAILQ_FIRST(&sc->mfi_cam_ccbq)) != NULL) cm = sc->mfi_cam_start(ccbh); } /* Nope, so look for work on the bioq */ if (cm == NULL) cm = mfi_bio_command(sc); /* No work available, so exit */ if (cm == NULL) break; /* Send the command to the controller */ if (mfi_mapcmd(sc, cm) != 0) { device_printf(sc->mfi_dev, "Failed to startio\n"); mfi_requeue_ready(cm); break; } } } int mfi_mapcmd(struct mfi_softc *sc, struct mfi_command *cm) { int error, polled; mtx_assert(&sc->mfi_io_lock, MA_OWNED); if ((cm->cm_data != NULL) && (cm->cm_frame->header.cmd != MFI_CMD_STP )) { polled = (cm->cm_flags & MFI_CMD_POLLED) ? BUS_DMA_NOWAIT : 0; if (cm->cm_flags & MFI_CMD_CCB) error = bus_dmamap_load_ccb(sc->mfi_buffer_dmat, cm->cm_dmamap, cm->cm_data, mfi_data_cb, cm, polled); else if (cm->cm_flags & MFI_CMD_BIO) error = bus_dmamap_load_bio(sc->mfi_buffer_dmat, cm->cm_dmamap, cm->cm_private, mfi_data_cb, cm, polled); else error = bus_dmamap_load(sc->mfi_buffer_dmat, cm->cm_dmamap, cm->cm_data, cm->cm_len, mfi_data_cb, cm, polled); if (error == EINPROGRESS) { sc->mfi_flags |= MFI_FLAGS_QFRZN; return (0); } } else { error = mfi_send_frame(sc, cm); } return (error); } static void mfi_data_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct mfi_frame_header *hdr; struct mfi_command *cm; union mfi_sgl *sgl; struct mfi_softc *sc; int i, j, first, dir; int sge_size, locked; cm = (struct mfi_command *)arg; sc = cm->cm_sc; hdr = &cm->cm_frame->header; sgl = cm->cm_sg; /* * We need to check if we have the lock as this is async * callback so even though our caller mfi_mapcmd asserts * it has the lock, there is no guarantee that hasn't been * dropped if bus_dmamap_load returned prior to our * completion. */ if ((locked = mtx_owned(&sc->mfi_io_lock)) == 0) mtx_lock(&sc->mfi_io_lock); if (error) { printf("error %d in callback\n", error); cm->cm_error = error; mfi_complete(sc, cm); goto out; } /* Use IEEE sgl only for IO's on a SKINNY controller * For other commands on a SKINNY controller use either * sg32 or sg64 based on the sizeof(bus_addr_t). * Also calculate the total frame size based on the type * of SGL used. */ if (((cm->cm_frame->header.cmd == MFI_CMD_PD_SCSI_IO) || (cm->cm_frame->header.cmd == MFI_CMD_LD_READ) || (cm->cm_frame->header.cmd == MFI_CMD_LD_WRITE)) && (sc->mfi_flags & MFI_FLAGS_SKINNY)) { for (i = 0; i < nsegs; i++) { sgl->sg_skinny[i].addr = segs[i].ds_addr; sgl->sg_skinny[i].len = segs[i].ds_len; sgl->sg_skinny[i].flag = 0; } hdr->flags |= MFI_FRAME_IEEE_SGL | MFI_FRAME_SGL64; sge_size = sizeof(struct mfi_sg_skinny); hdr->sg_count = nsegs; } else { j = 0; if (cm->cm_frame->header.cmd == MFI_CMD_STP) { first = cm->cm_stp_len; if ((sc->mfi_flags & MFI_FLAGS_SG64) == 0) { sgl->sg32[j].addr = segs[0].ds_addr; sgl->sg32[j++].len = first; } else { sgl->sg64[j].addr = segs[0].ds_addr; sgl->sg64[j++].len = first; } } else first = 0; if ((sc->mfi_flags & MFI_FLAGS_SG64) == 0) { for (i = 0; i < nsegs; i++) { sgl->sg32[j].addr = segs[i].ds_addr + first; sgl->sg32[j++].len = segs[i].ds_len - first; first = 0; } } else { for (i = 0; i < nsegs; i++) { sgl->sg64[j].addr = segs[i].ds_addr + first; sgl->sg64[j++].len = segs[i].ds_len - first; first = 0; } hdr->flags |= MFI_FRAME_SGL64; } hdr->sg_count = j; sge_size = sc->mfi_sge_size; } dir = 0; if (cm->cm_flags & MFI_CMD_DATAIN) { dir |= BUS_DMASYNC_PREREAD; hdr->flags |= MFI_FRAME_DIR_READ; } if (cm->cm_flags & MFI_CMD_DATAOUT) { dir |= BUS_DMASYNC_PREWRITE; hdr->flags |= MFI_FRAME_DIR_WRITE; } bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, dir); cm->cm_flags |= MFI_CMD_MAPPED; /* * Instead of calculating the total number of frames in the * compound frame, it's already assumed that there will be at * least 1 frame, so don't compensate for the modulo of the * following division. */ cm->cm_total_frame_size += (sge_size * nsegs); cm->cm_extra_frames = (cm->cm_total_frame_size - 1) / MFI_FRAME_SIZE; if ((error = mfi_send_frame(sc, cm)) != 0) { printf("error %d in callback from mfi_send_frame\n", error); cm->cm_error = error; mfi_complete(sc, cm); goto out; } out: /* leave the lock in the state we found it */ if (locked == 0) mtx_unlock(&sc->mfi_io_lock); return; } static int mfi_send_frame(struct mfi_softc *sc, struct mfi_command *cm) { int error; mtx_assert(&sc->mfi_io_lock, MA_OWNED); if (sc->MFA_enabled) error = mfi_tbolt_send_frame(sc, cm); else error = mfi_std_send_frame(sc, cm); if (error != 0 && (cm->cm_flags & MFI_ON_MFIQ_BUSY) != 0) mfi_remove_busy(cm); return (error); } static int mfi_std_send_frame(struct mfi_softc *sc, struct mfi_command *cm) { struct mfi_frame_header *hdr; int tm = mfi_polled_cmd_timeout * 1000; hdr = &cm->cm_frame->header; if ((cm->cm_flags & MFI_CMD_POLLED) == 0) { cm->cm_timestamp = time_uptime; mfi_enqueue_busy(cm); } else { hdr->cmd_status = MFI_STAT_INVALID_STATUS; hdr->flags |= MFI_FRAME_DONT_POST_IN_REPLY_QUEUE; } /* * The bus address of the command is aligned on a 64 byte boundary, * leaving the least 6 bits as zero. For whatever reason, the * hardware wants the address shifted right by three, leaving just * 3 zero bits. These three bits are then used as a prefetching * hint for the hardware to predict how many frames need to be * fetched across the bus. If a command has more than 8 frames * then the 3 bits are set to 0x7 and the firmware uses other * information in the command to determine the total amount to fetch. * However, FreeBSD doesn't support I/O larger than 128K, so 8 frames * is enough for both 32bit and 64bit systems. */ if (cm->cm_extra_frames > 7) cm->cm_extra_frames = 7; sc->mfi_issue_cmd(sc, cm->cm_frame_busaddr, cm->cm_extra_frames); if ((cm->cm_flags & MFI_CMD_POLLED) == 0) return (0); /* This is a polled command, so busy-wait for it to complete. */ while (hdr->cmd_status == MFI_STAT_INVALID_STATUS) { DELAY(1000); tm -= 1; if (tm <= 0) break; } if (hdr->cmd_status == MFI_STAT_INVALID_STATUS) { device_printf(sc->mfi_dev, "Frame %p timed out " "command 0x%X\n", hdr, cm->cm_frame->dcmd.opcode); return (ETIMEDOUT); } return (0); } void mfi_complete(struct mfi_softc *sc, struct mfi_command *cm) { int dir; mtx_assert(&sc->mfi_io_lock, MA_OWNED); if ((cm->cm_flags & MFI_CMD_MAPPED) != 0) { dir = 0; if ((cm->cm_flags & MFI_CMD_DATAIN) || (cm->cm_frame->header.cmd == MFI_CMD_STP)) dir |= BUS_DMASYNC_POSTREAD; if (cm->cm_flags & MFI_CMD_DATAOUT) dir |= BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, dir); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); cm->cm_flags &= ~MFI_CMD_MAPPED; } cm->cm_flags |= MFI_CMD_COMPLETED; if (cm->cm_complete != NULL) cm->cm_complete(cm); else wakeup(cm); } static int mfi_abort(struct mfi_softc *sc, struct mfi_command **cm_abort) { struct mfi_command *cm; struct mfi_abort_frame *abort; int i = 0, error; uint32_t context = 0; mtx_lock(&sc->mfi_io_lock); if ((cm = mfi_dequeue_free(sc)) == NULL) { mtx_unlock(&sc->mfi_io_lock); return (EBUSY); } /* Zero out the MFI frame */ context = cm->cm_frame->header.context; bzero(cm->cm_frame, sizeof(union mfi_frame)); cm->cm_frame->header.context = context; abort = &cm->cm_frame->abort; abort->header.cmd = MFI_CMD_ABORT; abort->header.flags = 0; abort->header.scsi_status = 0; abort->abort_context = (*cm_abort)->cm_frame->header.context; abort->abort_mfi_addr_lo = (uint32_t)(*cm_abort)->cm_frame_busaddr; abort->abort_mfi_addr_hi = (uint32_t)((uint64_t)(*cm_abort)->cm_frame_busaddr >> 32); cm->cm_data = NULL; cm->cm_flags = MFI_CMD_POLLED; if ((error = mfi_mapcmd(sc, cm)) != 0) device_printf(sc->mfi_dev, "failed to abort command\n"); mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); while (i < 5 && *cm_abort != NULL) { tsleep(cm_abort, 0, "mfiabort", 5 * hz); i++; } if (*cm_abort != NULL) { /* Force a complete if command didn't abort */ mtx_lock(&sc->mfi_io_lock); (*cm_abort)->cm_complete(*cm_abort); mtx_unlock(&sc->mfi_io_lock); } return (error); } int mfi_dump_blocks(struct mfi_softc *sc, int id, uint64_t lba, void *virt, int len) { struct mfi_command *cm; struct mfi_io_frame *io; int error; uint32_t context = 0; if ((cm = mfi_dequeue_free(sc)) == NULL) return (EBUSY); /* Zero out the MFI frame */ context = cm->cm_frame->header.context; bzero(cm->cm_frame, sizeof(union mfi_frame)); cm->cm_frame->header.context = context; io = &cm->cm_frame->io; io->header.cmd = MFI_CMD_LD_WRITE; io->header.target_id = id; io->header.timeout = 0; io->header.flags = 0; io->header.scsi_status = 0; io->header.sense_len = MFI_SENSE_LEN; io->header.data_len = howmany(len, MFI_SECTOR_LEN); io->sense_addr_lo = (uint32_t)cm->cm_sense_busaddr; io->sense_addr_hi = (uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32); io->lba_hi = (lba & 0xffffffff00000000) >> 32; io->lba_lo = lba & 0xffffffff; cm->cm_data = virt; cm->cm_len = len; cm->cm_sg = &io->sgl; cm->cm_total_frame_size = MFI_IO_FRAME_SIZE; cm->cm_flags = MFI_CMD_POLLED | MFI_CMD_DATAOUT; if ((error = mfi_mapcmd(sc, cm)) != 0) device_printf(sc->mfi_dev, "failed dump blocks\n"); bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); mfi_release_command(cm); return (error); } int mfi_dump_syspd_blocks(struct mfi_softc *sc, int id, uint64_t lba, void *virt, int len) { struct mfi_command *cm; struct mfi_pass_frame *pass; int error, readop, cdb_len; uint32_t blkcount; if ((cm = mfi_dequeue_free(sc)) == NULL) return (EBUSY); pass = &cm->cm_frame->pass; bzero(pass->cdb, 16); pass->header.cmd = MFI_CMD_PD_SCSI_IO; readop = 0; blkcount = howmany(len, MFI_SECTOR_LEN); cdb_len = mfi_build_cdb(readop, 0, lba, blkcount, pass->cdb); pass->header.target_id = id; pass->header.timeout = 0; pass->header.flags = 0; pass->header.scsi_status = 0; pass->header.sense_len = MFI_SENSE_LEN; pass->header.data_len = len; pass->header.cdb_len = cdb_len; pass->sense_addr_lo = (uint32_t)cm->cm_sense_busaddr; pass->sense_addr_hi = (uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32); cm->cm_data = virt; cm->cm_len = len; cm->cm_sg = &pass->sgl; cm->cm_total_frame_size = MFI_PASS_FRAME_SIZE; cm->cm_flags = MFI_CMD_POLLED | MFI_CMD_DATAOUT | MFI_CMD_SCSI; if ((error = mfi_mapcmd(sc, cm)) != 0) device_printf(sc->mfi_dev, "failed dump blocks\n"); bus_dmamap_sync(sc->mfi_buffer_dmat, cm->cm_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mfi_buffer_dmat, cm->cm_dmamap); mfi_release_command(cm); return (error); } static int mfi_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mfi_softc *sc; int error; sc = dev->si_drv1; mtx_lock(&sc->mfi_io_lock); if (sc->mfi_detaching) error = ENXIO; else { sc->mfi_flags |= MFI_FLAGS_OPEN; error = 0; } mtx_unlock(&sc->mfi_io_lock); return (error); } static int mfi_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mfi_softc *sc; struct mfi_aen *mfi_aen_entry, *tmp; sc = dev->si_drv1; mtx_lock(&sc->mfi_io_lock); sc->mfi_flags &= ~MFI_FLAGS_OPEN; TAILQ_FOREACH_SAFE(mfi_aen_entry, &sc->mfi_aen_pids, aen_link, tmp) { if (mfi_aen_entry->p == curproc) { TAILQ_REMOVE(&sc->mfi_aen_pids, mfi_aen_entry, aen_link); free(mfi_aen_entry, M_MFIBUF); } } mtx_unlock(&sc->mfi_io_lock); return (0); } static int mfi_config_lock(struct mfi_softc *sc, uint32_t opcode) { switch (opcode) { case MFI_DCMD_LD_DELETE: case MFI_DCMD_CFG_ADD: case MFI_DCMD_CFG_CLEAR: case MFI_DCMD_CFG_FOREIGN_IMPORT: sx_xlock(&sc->mfi_config_lock); return (1); default: return (0); } } static void mfi_config_unlock(struct mfi_softc *sc, int locked) { if (locked) sx_xunlock(&sc->mfi_config_lock); } /* * Perform pre-issue checks on commands from userland and possibly veto * them. */ static int mfi_check_command_pre(struct mfi_softc *sc, struct mfi_command *cm) { struct mfi_disk *ld, *ld2; int error; struct mfi_system_pd *syspd = NULL; uint16_t syspd_id; uint16_t *mbox; mtx_assert(&sc->mfi_io_lock, MA_OWNED); error = 0; switch (cm->cm_frame->dcmd.opcode) { case MFI_DCMD_LD_DELETE: TAILQ_FOREACH(ld, &sc->mfi_ld_tqh, ld_link) { if (ld->ld_id == cm->cm_frame->dcmd.mbox[0]) break; } if (ld == NULL) error = ENOENT; else error = mfi_disk_disable(ld); break; case MFI_DCMD_CFG_CLEAR: TAILQ_FOREACH(ld, &sc->mfi_ld_tqh, ld_link) { error = mfi_disk_disable(ld); if (error) break; } if (error) { TAILQ_FOREACH(ld2, &sc->mfi_ld_tqh, ld_link) { if (ld2 == ld) break; mfi_disk_enable(ld2); } } break; case MFI_DCMD_PD_STATE_SET: mbox = (uint16_t *) cm->cm_frame->dcmd.mbox; syspd_id = mbox[0]; if (mbox[2] == MFI_PD_STATE_UNCONFIGURED_GOOD) { TAILQ_FOREACH(syspd, &sc->mfi_syspd_tqh, pd_link) { if (syspd->pd_id == syspd_id) break; } } else break; if (syspd) error = mfi_syspd_disable(syspd); break; default: break; } return (error); } /* Perform post-issue checks on commands from userland. */ static void mfi_check_command_post(struct mfi_softc *sc, struct mfi_command *cm) { struct mfi_disk *ld, *ldn; struct mfi_system_pd *syspd = NULL; uint16_t syspd_id; uint16_t *mbox; switch (cm->cm_frame->dcmd.opcode) { case MFI_DCMD_LD_DELETE: TAILQ_FOREACH(ld, &sc->mfi_ld_tqh, ld_link) { if (ld->ld_id == cm->cm_frame->dcmd.mbox[0]) break; } KASSERT(ld != NULL, ("volume dissappeared")); if (cm->cm_frame->header.cmd_status == MFI_STAT_OK) { mtx_unlock(&sc->mfi_io_lock); bus_topo_lock(); device_delete_child(sc->mfi_dev, ld->ld_dev); bus_topo_unlock(); mtx_lock(&sc->mfi_io_lock); } else mfi_disk_enable(ld); break; case MFI_DCMD_CFG_CLEAR: if (cm->cm_frame->header.cmd_status == MFI_STAT_OK) { mtx_unlock(&sc->mfi_io_lock); bus_topo_lock(); TAILQ_FOREACH_SAFE(ld, &sc->mfi_ld_tqh, ld_link, ldn) { device_delete_child(sc->mfi_dev, ld->ld_dev); } bus_topo_unlock(); mtx_lock(&sc->mfi_io_lock); } else { TAILQ_FOREACH(ld, &sc->mfi_ld_tqh, ld_link) mfi_disk_enable(ld); } break; case MFI_DCMD_CFG_ADD: mfi_ldprobe(sc); break; case MFI_DCMD_CFG_FOREIGN_IMPORT: mfi_ldprobe(sc); break; case MFI_DCMD_PD_STATE_SET: mbox = (uint16_t *) cm->cm_frame->dcmd.mbox; syspd_id = mbox[0]; if (mbox[2] == MFI_PD_STATE_UNCONFIGURED_GOOD) { TAILQ_FOREACH(syspd, &sc->mfi_syspd_tqh,pd_link) { if (syspd->pd_id == syspd_id) break; } } else break; /* If the transition fails then enable the syspd again */ if (syspd && cm->cm_frame->header.cmd_status != MFI_STAT_OK) mfi_syspd_enable(syspd); break; } } static int mfi_check_for_sscd(struct mfi_softc *sc, struct mfi_command *cm) { struct mfi_config_data *conf_data; struct mfi_command *ld_cm = NULL; struct mfi_ld_info *ld_info = NULL; struct mfi_ld_config *ld; char *p; int error = 0; conf_data = (struct mfi_config_data *)cm->cm_data; if (cm->cm_frame->dcmd.opcode == MFI_DCMD_CFG_ADD) { p = (char *)conf_data->array; p += conf_data->array_size * conf_data->array_count; ld = (struct mfi_ld_config *)p; if (ld->params.isSSCD == 1) error = 1; } else if (cm->cm_frame->dcmd.opcode == MFI_DCMD_LD_DELETE) { error = mfi_dcmd_command (sc, &ld_cm, MFI_DCMD_LD_GET_INFO, (void **)&ld_info, sizeof(*ld_info)); if (error) { device_printf(sc->mfi_dev, "Failed to allocate" "MFI_DCMD_LD_GET_INFO %d", error); if (ld_info) free(ld_info, M_MFIBUF); return 0; } ld_cm->cm_flags = MFI_CMD_DATAIN; ld_cm->cm_frame->dcmd.mbox[0]= cm->cm_frame->dcmd.mbox[0]; ld_cm->cm_frame->header.target_id = cm->cm_frame->dcmd.mbox[0]; if (mfi_wait_command(sc, ld_cm) != 0) { device_printf(sc->mfi_dev, "failed to get log drv\n"); mfi_release_command(ld_cm); free(ld_info, M_MFIBUF); return 0; } if (ld_cm->cm_frame->header.cmd_status != MFI_STAT_OK) { free(ld_info, M_MFIBUF); mfi_release_command(ld_cm); return 0; } else ld_info = (struct mfi_ld_info *)ld_cm->cm_private; if (ld_info->ld_config.params.isSSCD == 1) error = 1; mfi_release_command(ld_cm); free(ld_info, M_MFIBUF); } return error; } static int mfi_stp_cmd(struct mfi_softc *sc, struct mfi_command *cm,caddr_t arg) { uint8_t i; struct mfi_ioc_packet *ioc; ioc = (struct mfi_ioc_packet *)arg; int sge_size, error; struct megasas_sge *kern_sge; memset(sc->kbuff_arr, 0, sizeof(sc->kbuff_arr)); kern_sge =(struct megasas_sge *) ((uintptr_t)cm->cm_frame + ioc->mfi_sgl_off); cm->cm_frame->header.sg_count = ioc->mfi_sge_count; if (sizeof(bus_addr_t) == 8) { cm->cm_frame->header.flags |= MFI_FRAME_SGL64; cm->cm_extra_frames = 2; sge_size = sizeof(struct mfi_sg64); } else { cm->cm_extra_frames = (cm->cm_total_frame_size - 1) / MFI_FRAME_SIZE; sge_size = sizeof(struct mfi_sg32); } cm->cm_total_frame_size += (sge_size * ioc->mfi_sge_count); for (i = 0; i < ioc->mfi_sge_count; i++) { if (bus_dma_tag_create( sc->mfi_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ioc->mfi_sgl[i].iov_len,/* maxsize */ 2, /* nsegments */ ioc->mfi_sgl[i].iov_len,/* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mfi_kbuff_arr_dmat[i])) { device_printf(sc->mfi_dev, "Cannot allocate mfi_kbuff_arr_dmat tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->mfi_kbuff_arr_dmat[i], (void **)&sc->kbuff_arr[i], BUS_DMA_NOWAIT, &sc->mfi_kbuff_arr_dmamap[i])) { device_printf(sc->mfi_dev, "Cannot allocate mfi_kbuff_arr_dmamap memory\n"); return (ENOMEM); } bus_dmamap_load(sc->mfi_kbuff_arr_dmat[i], sc->mfi_kbuff_arr_dmamap[i], sc->kbuff_arr[i], ioc->mfi_sgl[i].iov_len, mfi_addr_cb, &sc->mfi_kbuff_arr_busaddr[i], 0); if (!sc->kbuff_arr[i]) { device_printf(sc->mfi_dev, "Could not allocate memory for kbuff_arr info\n"); return -1; } kern_sge[i].phys_addr = sc->mfi_kbuff_arr_busaddr[i]; kern_sge[i].length = ioc->mfi_sgl[i].iov_len; if (sizeof(bus_addr_t) == 8) { cm->cm_frame->stp.sgl.sg64[i].addr = kern_sge[i].phys_addr; cm->cm_frame->stp.sgl.sg64[i].len = ioc->mfi_sgl[i].iov_len; } else { cm->cm_frame->stp.sgl.sg32[i].addr = kern_sge[i].phys_addr; cm->cm_frame->stp.sgl.sg32[i].len = ioc->mfi_sgl[i].iov_len; } error = copyin(ioc->mfi_sgl[i].iov_base, sc->kbuff_arr[i], ioc->mfi_sgl[i].iov_len); if (error != 0) { device_printf(sc->mfi_dev, "Copy in failed\n"); return error; } } cm->cm_flags |=MFI_CMD_MAPPED; return 0; } static int mfi_user_command(struct mfi_softc *sc, struct mfi_ioc_passthru *ioc) { struct mfi_command *cm; struct mfi_dcmd_frame *dcmd; void *ioc_buf = NULL; uint32_t context; int error = 0, locked; if (ioc->buf_size > 0) { if (ioc->buf_size > 1024 * 1024) return (ENOMEM); ioc_buf = malloc(ioc->buf_size, M_MFIBUF, M_WAITOK); error = copyin(ioc->buf, ioc_buf, ioc->buf_size); if (error) { device_printf(sc->mfi_dev, "failed to copyin\n"); free(ioc_buf, M_MFIBUF); return (error); } } locked = mfi_config_lock(sc, ioc->ioc_frame.opcode); mtx_lock(&sc->mfi_io_lock); while ((cm = mfi_dequeue_free(sc)) == NULL) msleep(mfi_user_command, &sc->mfi_io_lock, 0, "mfiioc", hz); /* Save context for later */ context = cm->cm_frame->header.context; dcmd = &cm->cm_frame->dcmd; bcopy(&ioc->ioc_frame, dcmd, sizeof(struct mfi_dcmd_frame)); cm->cm_sg = &dcmd->sgl; cm->cm_total_frame_size = MFI_DCMD_FRAME_SIZE; cm->cm_data = ioc_buf; cm->cm_len = ioc->buf_size; /* restore context */ cm->cm_frame->header.context = context; /* Cheat since we don't know if we're writing or reading */ cm->cm_flags = MFI_CMD_DATAIN | MFI_CMD_DATAOUT; error = mfi_check_command_pre(sc, cm); if (error) goto out; error = mfi_wait_command(sc, cm); if (error) { device_printf(sc->mfi_dev, "ioctl failed %d\n", error); goto out; } bcopy(dcmd, &ioc->ioc_frame, sizeof(struct mfi_dcmd_frame)); mfi_check_command_post(sc, cm); out: mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); mfi_config_unlock(sc, locked); if (ioc->buf_size > 0) error = copyout(ioc_buf, ioc->buf, ioc->buf_size); if (ioc_buf) free(ioc_buf, M_MFIBUF); return (error); } #define PTRIN(p) ((void *)(uintptr_t)(p)) static int mfi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td) { struct mfi_softc *sc; union mfi_statrequest *ms; struct mfi_ioc_packet *ioc; #ifdef COMPAT_FREEBSD32 struct mfi_ioc_packet32 *ioc32; #endif struct mfi_ioc_aen *aen; struct mfi_command *cm = NULL; uint32_t context = 0; union mfi_sense_ptr sense_ptr; uint8_t *data = NULL, *temp, *addr, skip_pre_post = 0; size_t len; int i, res; struct mfi_ioc_passthru *iop = (struct mfi_ioc_passthru *)arg; #ifdef COMPAT_FREEBSD32 struct mfi_ioc_passthru32 *iop32 = (struct mfi_ioc_passthru32 *)arg; struct mfi_ioc_passthru iop_swab; #endif int error, locked; sc = dev->si_drv1; error = 0; if (sc->adpreset) return EBUSY; if (sc->hw_crit_error) return EBUSY; if (sc->issuepend_done == 0) return EBUSY; switch (cmd) { case MFIIO_STATS: ms = (union mfi_statrequest *)arg; switch (ms->ms_item) { case MFIQ_FREE: case MFIQ_BIO: case MFIQ_READY: case MFIQ_BUSY: bcopy(&sc->mfi_qstat[ms->ms_item], &ms->ms_qstat, sizeof(struct mfi_qstat)); break; default: error = ENOIOCTL; break; } break; case MFIIO_QUERY_DISK: { struct mfi_query_disk *qd; struct mfi_disk *ld; qd = (struct mfi_query_disk *)arg; mtx_lock(&sc->mfi_io_lock); TAILQ_FOREACH(ld, &sc->mfi_ld_tqh, ld_link) { if (ld->ld_id == qd->array_id) break; } if (ld == NULL) { qd->present = 0; mtx_unlock(&sc->mfi_io_lock); return (0); } qd->present = 1; if (ld->ld_flags & MFI_DISK_FLAGS_OPEN) qd->open = 1; bzero(qd->devname, SPECNAMELEN + 1); snprintf(qd->devname, SPECNAMELEN, "mfid%d", ld->ld_unit); mtx_unlock(&sc->mfi_io_lock); break; } case MFI_CMD: #ifdef COMPAT_FREEBSD32 case MFI_CMD32: #endif { devclass_t devclass; ioc = (struct mfi_ioc_packet *)arg; int adapter; adapter = ioc->mfi_adapter_no; if (device_get_unit(sc->mfi_dev) == 0 && adapter != 0) { devclass = devclass_find("mfi"); sc = devclass_get_softc(devclass, adapter); } mtx_lock(&sc->mfi_io_lock); if ((cm = mfi_dequeue_free(sc)) == NULL) { mtx_unlock(&sc->mfi_io_lock); return (EBUSY); } mtx_unlock(&sc->mfi_io_lock); locked = 0; /* * save off original context since copying from user * will clobber some data */ context = cm->cm_frame->header.context; cm->cm_frame->header.context = cm->cm_index; bcopy(ioc->mfi_frame.raw, cm->cm_frame, 2 * MEGAMFI_FRAME_SIZE); cm->cm_total_frame_size = (sizeof(union mfi_sgl) * ioc->mfi_sge_count) + ioc->mfi_sgl_off; cm->cm_frame->header.scsi_status = 0; cm->cm_frame->header.pad0 = 0; if (ioc->mfi_sge_count) { cm->cm_sg = (union mfi_sgl *)&cm->cm_frame->bytes[ioc->mfi_sgl_off]; } cm->cm_flags = 0; if (cm->cm_frame->header.flags & MFI_FRAME_DATAIN) cm->cm_flags |= MFI_CMD_DATAIN; if (cm->cm_frame->header.flags & MFI_FRAME_DATAOUT) cm->cm_flags |= MFI_CMD_DATAOUT; /* Legacy app shim */ if (cm->cm_flags == 0) cm->cm_flags |= MFI_CMD_DATAIN | MFI_CMD_DATAOUT; cm->cm_len = cm->cm_frame->header.data_len; if (cm->cm_frame->header.cmd == MFI_CMD_STP) { #ifdef COMPAT_FREEBSD32 if (cmd == MFI_CMD) { #endif /* Native */ cm->cm_stp_len = ioc->mfi_sgl[0].iov_len; #ifdef COMPAT_FREEBSD32 } else { /* 32bit on 64bit */ ioc32 = (struct mfi_ioc_packet32 *)ioc; cm->cm_stp_len = ioc32->mfi_sgl[0].iov_len; } #endif cm->cm_len += cm->cm_stp_len; } if (cm->cm_len && (cm->cm_flags & (MFI_CMD_DATAIN | MFI_CMD_DATAOUT))) { cm->cm_data = data = malloc(cm->cm_len, M_MFIBUF, M_WAITOK | M_ZERO); } else { cm->cm_data = 0; } /* restore header context */ cm->cm_frame->header.context = context; if (cm->cm_frame->header.cmd == MFI_CMD_STP) { res = mfi_stp_cmd(sc, cm, arg); if (res != 0) goto out; } else { temp = data; if ((cm->cm_flags & MFI_CMD_DATAOUT) || (cm->cm_frame->header.cmd == MFI_CMD_STP)) { for (i = 0; i < ioc->mfi_sge_count; i++) { #ifdef COMPAT_FREEBSD32 if (cmd == MFI_CMD) { #endif /* Native */ addr = ioc->mfi_sgl[i].iov_base; len = ioc->mfi_sgl[i].iov_len; #ifdef COMPAT_FREEBSD32 } else { /* 32bit on 64bit */ ioc32 = (struct mfi_ioc_packet32 *)ioc; addr = PTRIN(ioc32->mfi_sgl[i].iov_base); len = ioc32->mfi_sgl[i].iov_len; } #endif error = copyin(addr, temp, len); if (error != 0) { device_printf(sc->mfi_dev, "Copy in failed\n"); goto out; } temp = &temp[len]; } } } if (cm->cm_frame->header.cmd == MFI_CMD_DCMD) locked = mfi_config_lock(sc, cm->cm_frame->dcmd.opcode); if (cm->cm_frame->header.cmd == MFI_CMD_PD_SCSI_IO) { cm->cm_frame->pass.sense_addr_lo = (uint32_t)cm->cm_sense_busaddr; cm->cm_frame->pass.sense_addr_hi = (uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32); } mtx_lock(&sc->mfi_io_lock); skip_pre_post = mfi_check_for_sscd (sc, cm); if (!skip_pre_post) { error = mfi_check_command_pre(sc, cm); if (error) { mtx_unlock(&sc->mfi_io_lock); goto out; } } if ((error = mfi_wait_command(sc, cm)) != 0) { device_printf(sc->mfi_dev, "Controller polled failed\n"); mtx_unlock(&sc->mfi_io_lock); goto out; } if (!skip_pre_post) { mfi_check_command_post(sc, cm); } mtx_unlock(&sc->mfi_io_lock); if (cm->cm_frame->header.cmd != MFI_CMD_STP) { temp = data; if ((cm->cm_flags & MFI_CMD_DATAIN) || (cm->cm_frame->header.cmd == MFI_CMD_STP)) { for (i = 0; i < ioc->mfi_sge_count; i++) { #ifdef COMPAT_FREEBSD32 if (cmd == MFI_CMD) { #endif /* Native */ addr = ioc->mfi_sgl[i].iov_base; len = ioc->mfi_sgl[i].iov_len; #ifdef COMPAT_FREEBSD32 } else { /* 32bit on 64bit */ ioc32 = (struct mfi_ioc_packet32 *)ioc; addr = PTRIN(ioc32->mfi_sgl[i].iov_base); len = ioc32->mfi_sgl[i].iov_len; } #endif error = copyout(temp, addr, len); if (error != 0) { device_printf(sc->mfi_dev, "Copy out failed\n"); goto out; } temp = &temp[len]; } } } if (ioc->mfi_sense_len) { /* get user-space sense ptr then copy out sense */ bcopy(&ioc->mfi_frame.raw[ioc->mfi_sense_off], &sense_ptr.sense_ptr_data[0], sizeof(sense_ptr.sense_ptr_data)); #ifdef COMPAT_FREEBSD32 if (cmd != MFI_CMD) { /* * not 64bit native so zero out any address * over 32bit */ sense_ptr.addr.high = 0; } #endif error = copyout(cm->cm_sense, sense_ptr.user_space, ioc->mfi_sense_len); if (error != 0) { device_printf(sc->mfi_dev, "Copy out failed\n"); goto out; } } ioc->mfi_frame.hdr.cmd_status = cm->cm_frame->header.cmd_status; out: mfi_config_unlock(sc, locked); if (data) free(data, M_MFIBUF); if (cm->cm_frame->header.cmd == MFI_CMD_STP) { for (i = 0; i < 2; i++) { if (sc->kbuff_arr[i]) { if (sc->mfi_kbuff_arr_busaddr[i] != 0) bus_dmamap_unload( sc->mfi_kbuff_arr_dmat[i], sc->mfi_kbuff_arr_dmamap[i] ); if (sc->kbuff_arr[i] != NULL) bus_dmamem_free( sc->mfi_kbuff_arr_dmat[i], sc->kbuff_arr[i], sc->mfi_kbuff_arr_dmamap[i] ); if (sc->mfi_kbuff_arr_dmat[i] != NULL) bus_dma_tag_destroy( sc->mfi_kbuff_arr_dmat[i]); } } } if (cm) { mtx_lock(&sc->mfi_io_lock); mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); } break; } case MFI_SET_AEN: aen = (struct mfi_ioc_aen *)arg; mtx_lock(&sc->mfi_io_lock); error = mfi_aen_register(sc, aen->aen_seq_num, aen->aen_class_locale); mtx_unlock(&sc->mfi_io_lock); break; case MFI_LINUX_CMD_2: /* Firmware Linux ioctl shim */ { devclass_t devclass; struct mfi_linux_ioc_packet l_ioc; int adapter; devclass = devclass_find("mfi"); if (devclass == NULL) return (ENOENT); error = copyin(arg, &l_ioc, sizeof(l_ioc)); if (error) return (error); adapter = l_ioc.lioc_adapter_no; sc = devclass_get_softc(devclass, adapter); if (sc == NULL) return (ENOENT); return (mfi_linux_ioctl_int(sc->mfi_cdev, cmd, arg, flag, td)); break; } case MFI_LINUX_SET_AEN_2: /* AEN Linux ioctl shim */ { devclass_t devclass; struct mfi_linux_ioc_aen l_aen; int adapter; devclass = devclass_find("mfi"); if (devclass == NULL) return (ENOENT); error = copyin(arg, &l_aen, sizeof(l_aen)); if (error) return (error); adapter = l_aen.laen_adapter_no; sc = devclass_get_softc(devclass, adapter); if (sc == NULL) return (ENOENT); return (mfi_linux_ioctl_int(sc->mfi_cdev, cmd, arg, flag, td)); break; } #ifdef COMPAT_FREEBSD32 case MFIIO_PASSTHRU32: if (!SV_CURPROC_FLAG(SV_ILP32)) { error = ENOTTY; break; } iop_swab.ioc_frame = iop32->ioc_frame; iop_swab.buf_size = iop32->buf_size; iop_swab.buf = PTRIN(iop32->buf); iop = &iop_swab; /* FALLTHROUGH */ #endif case MFIIO_PASSTHRU: error = mfi_user_command(sc, iop); #ifdef COMPAT_FREEBSD32 if (cmd == MFIIO_PASSTHRU32) iop32->ioc_frame = iop_swab.ioc_frame; #endif break; default: device_printf(sc->mfi_dev, "IOCTL 0x%lx not handled\n", cmd); error = ENOTTY; break; } return (error); } static int mfi_linux_ioctl_int(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td) { struct mfi_softc *sc; struct mfi_linux_ioc_packet l_ioc; struct mfi_linux_ioc_aen l_aen; struct mfi_command *cm = NULL; struct mfi_aen *mfi_aen_entry; union mfi_sense_ptr sense_ptr; uint32_t context = 0; uint8_t *data = NULL, *temp; int i; int error, locked; sc = dev->si_drv1; error = 0; switch (cmd) { case MFI_LINUX_CMD_2: /* Firmware Linux ioctl shim */ error = copyin(arg, &l_ioc, sizeof(l_ioc)); if (error != 0) return (error); if (l_ioc.lioc_sge_count > MAX_LINUX_IOCTL_SGE) { return (EINVAL); } mtx_lock(&sc->mfi_io_lock); if ((cm = mfi_dequeue_free(sc)) == NULL) { mtx_unlock(&sc->mfi_io_lock); return (EBUSY); } mtx_unlock(&sc->mfi_io_lock); locked = 0; /* * save off original context since copying from user * will clobber some data */ context = cm->cm_frame->header.context; bcopy(l_ioc.lioc_frame.raw, cm->cm_frame, 2 * MFI_DCMD_FRAME_SIZE); /* this isn't quite right */ cm->cm_total_frame_size = (sizeof(union mfi_sgl) * l_ioc.lioc_sge_count) + l_ioc.lioc_sgl_off; cm->cm_frame->header.scsi_status = 0; cm->cm_frame->header.pad0 = 0; if (l_ioc.lioc_sge_count) cm->cm_sg = (union mfi_sgl *)&cm->cm_frame->bytes[l_ioc.lioc_sgl_off]; cm->cm_flags = 0; if (cm->cm_frame->header.flags & MFI_FRAME_DATAIN) cm->cm_flags |= MFI_CMD_DATAIN; if (cm->cm_frame->header.flags & MFI_FRAME_DATAOUT) cm->cm_flags |= MFI_CMD_DATAOUT; cm->cm_len = cm->cm_frame->header.data_len; if (cm->cm_len && (cm->cm_flags & (MFI_CMD_DATAIN | MFI_CMD_DATAOUT))) { cm->cm_data = data = malloc(cm->cm_len, M_MFIBUF, M_WAITOK | M_ZERO); } else { cm->cm_data = 0; } /* restore header context */ cm->cm_frame->header.context = context; temp = data; if (cm->cm_flags & MFI_CMD_DATAOUT) { for (i = 0; i < l_ioc.lioc_sge_count; i++) { error = copyin(PTRIN(l_ioc.lioc_sgl[i].iov_base), temp, l_ioc.lioc_sgl[i].iov_len); if (error != 0) { device_printf(sc->mfi_dev, "Copy in failed\n"); goto out; } temp = &temp[l_ioc.lioc_sgl[i].iov_len]; } } if (cm->cm_frame->header.cmd == MFI_CMD_DCMD) locked = mfi_config_lock(sc, cm->cm_frame->dcmd.opcode); if (cm->cm_frame->header.cmd == MFI_CMD_PD_SCSI_IO) { cm->cm_frame->pass.sense_addr_lo = (uint32_t)cm->cm_sense_busaddr; cm->cm_frame->pass.sense_addr_hi = (uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32); } mtx_lock(&sc->mfi_io_lock); error = mfi_check_command_pre(sc, cm); if (error) { mtx_unlock(&sc->mfi_io_lock); goto out; } if ((error = mfi_wait_command(sc, cm)) != 0) { device_printf(sc->mfi_dev, "Controller polled failed\n"); mtx_unlock(&sc->mfi_io_lock); goto out; } mfi_check_command_post(sc, cm); mtx_unlock(&sc->mfi_io_lock); temp = data; if (cm->cm_flags & MFI_CMD_DATAIN) { for (i = 0; i < l_ioc.lioc_sge_count; i++) { error = copyout(temp, PTRIN(l_ioc.lioc_sgl[i].iov_base), l_ioc.lioc_sgl[i].iov_len); if (error != 0) { device_printf(sc->mfi_dev, "Copy out failed\n"); goto out; } temp = &temp[l_ioc.lioc_sgl[i].iov_len]; } } if (l_ioc.lioc_sense_len) { /* get user-space sense ptr then copy out sense */ bcopy(&((struct mfi_linux_ioc_packet*)arg) ->lioc_frame.raw[l_ioc.lioc_sense_off], &sense_ptr.sense_ptr_data[0], sizeof(sense_ptr.sense_ptr_data)); #ifdef __amd64__ /* * only 32bit Linux support so zero out any * address over 32bit */ sense_ptr.addr.high = 0; #endif error = copyout(cm->cm_sense, sense_ptr.user_space, l_ioc.lioc_sense_len); if (error != 0) { device_printf(sc->mfi_dev, "Copy out failed\n"); goto out; } } error = copyout(&cm->cm_frame->header.cmd_status, &((struct mfi_linux_ioc_packet*)arg) ->lioc_frame.hdr.cmd_status, 1); if (error != 0) { device_printf(sc->mfi_dev, "Copy out failed\n"); goto out; } out: mfi_config_unlock(sc, locked); if (data) free(data, M_MFIBUF); if (cm) { mtx_lock(&sc->mfi_io_lock); mfi_release_command(cm); mtx_unlock(&sc->mfi_io_lock); } return (error); case MFI_LINUX_SET_AEN_2: /* AEN Linux ioctl shim */ error = copyin(arg, &l_aen, sizeof(l_aen)); if (error != 0) return (error); printf("AEN IMPLEMENTED for pid %d\n", curproc->p_pid); mfi_aen_entry = malloc(sizeof(struct mfi_aen), M_MFIBUF, M_WAITOK); mtx_lock(&sc->mfi_io_lock); mfi_aen_entry->p = curproc; TAILQ_INSERT_TAIL(&sc->mfi_aen_pids, mfi_aen_entry, aen_link); error = mfi_aen_register(sc, l_aen.laen_seq_num, l_aen.laen_class_locale); if (error != 0) { TAILQ_REMOVE(&sc->mfi_aen_pids, mfi_aen_entry, aen_link); free(mfi_aen_entry, M_MFIBUF); } mtx_unlock(&sc->mfi_io_lock); return (error); default: device_printf(sc->mfi_dev, "IOCTL 0x%lx not handled\n", cmd); error = ENOENT; break; } return (error); } static int mfi_poll(struct cdev *dev, int poll_events, struct thread *td) { struct mfi_softc *sc; int revents = 0; sc = dev->si_drv1; if (poll_events & (POLLIN | POLLRDNORM)) { if (sc->mfi_aen_triggered != 0) { revents |= poll_events & (POLLIN | POLLRDNORM); sc->mfi_aen_triggered = 0; } if (sc->mfi_aen_triggered == 0 && sc->mfi_aen_cm == NULL) { revents |= POLLERR; } } if (revents == 0) { if (poll_events & (POLLIN | POLLRDNORM)) { sc->mfi_poll_waiting = 1; selrecord(td, &sc->mfi_select); } } return revents; } static void mfi_dump_all(void) { struct mfi_softc *sc; struct mfi_command *cm; devclass_t dc; time_t deadline; int timedout __unused; int i; dc = devclass_find("mfi"); if (dc == NULL) { printf("No mfi dev class\n"); return; } for (i = 0; ; i++) { sc = devclass_get_softc(dc, i); if (sc == NULL) break; device_printf(sc->mfi_dev, "Dumping\n\n"); timedout = 0; deadline = time_uptime - mfi_cmd_timeout; mtx_lock(&sc->mfi_io_lock); TAILQ_FOREACH(cm, &sc->mfi_busy, cm_link) { if (cm->cm_timestamp <= deadline) { device_printf(sc->mfi_dev, "COMMAND %p TIMEOUT AFTER %d SECONDS\n", cm, (int)(time_uptime - cm->cm_timestamp)); MFI_PRINT_CMD(cm); timedout++; } } #if 0 if (timedout) MFI_DUMP_CMDS(sc); #endif mtx_unlock(&sc->mfi_io_lock); } return; } static void mfi_timeout(void *data) { struct mfi_softc *sc = (struct mfi_softc *)data; struct mfi_command *cm, *tmp; time_t deadline; int timedout __unused = 0; deadline = time_uptime - mfi_cmd_timeout; if (sc->adpreset == 0) { if (!mfi_tbolt_reset(sc)) { callout_reset(&sc->mfi_watchdog_callout, mfi_cmd_timeout * hz, mfi_timeout, sc); return; } } mtx_lock(&sc->mfi_io_lock); TAILQ_FOREACH_SAFE(cm, &sc->mfi_busy, cm_link, tmp) { if (sc->mfi_aen_cm == cm || sc->mfi_map_sync_cm == cm) continue; if (cm->cm_timestamp <= deadline) { if (sc->adpreset != 0 && sc->issuepend_done == 0) { cm->cm_timestamp = time_uptime; } else { device_printf(sc->mfi_dev, "COMMAND %p TIMEOUT AFTER %d SECONDS\n", cm, (int)(time_uptime - cm->cm_timestamp) ); MFI_PRINT_CMD(cm); MFI_VALIDATE_CMD(sc, cm); /* * While commands can get stuck forever we do * not fail them as there is no way to tell if * the controller has actually processed them * or not. * * In addition its very likely that force * failing a command here would cause a panic * e.g. in UFS. */ timedout++; } } } #if 0 if (timedout) MFI_DUMP_CMDS(sc); #endif mtx_unlock(&sc->mfi_io_lock); callout_reset(&sc->mfi_watchdog_callout, mfi_cmd_timeout * hz, mfi_timeout, sc); if (0) mfi_dump_all(); return; } diff --git a/sys/dev/mfi/mfi_cam.c b/sys/dev/mfi/mfi_cam.c index cce303c123e5..af95ff957d8c 100644 --- a/sys/dev/mfi/mfi_cam.c +++ b/sys/dev/mfi/mfi_cam.c @@ -1,473 +1,473 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2007 Scott Long * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_mfi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum mfip_state { MFIP_STATE_NONE, MFIP_STATE_DETACH, MFIP_STATE_RESCAN }; struct mfip_softc { device_t dev; struct mfi_softc *mfi_sc; struct cam_devq *devq; struct cam_sim *sim; struct cam_path *path; enum mfip_state state; }; static int mfip_probe(device_t); static int mfip_attach(device_t); static int mfip_detach(device_t); static void mfip_cam_action(struct cam_sim *, union ccb *); static void mfip_cam_poll(struct cam_sim *); static void mfip_cam_rescan(struct mfi_softc *, uint32_t tid); static struct mfi_command * mfip_start(void *); static void mfip_done(struct mfi_command *cm); static int mfi_allow_disks = 0; SYSCTL_INT(_hw_mfi, OID_AUTO, allow_cam_disk_passthrough, CTLFLAG_RDTUN, &mfi_allow_disks, 0, "event message locale"); static device_method_t mfip_methods[] = { DEVMETHOD(device_probe, mfip_probe), DEVMETHOD(device_attach, mfip_attach), DEVMETHOD(device_detach, mfip_detach), DEVMETHOD_END }; static driver_t mfip_driver = { "mfip", mfip_methods, sizeof(struct mfip_softc) }; DRIVER_MODULE(mfip, mfi, mfip_driver, 0, 0); MODULE_DEPEND(mfip, cam, 1, 1, 1); MODULE_DEPEND(mfip, mfi, 1, 1, 1); #define ccb_mfip_ptr sim_priv.entries[0].ptr static int mfip_probe(device_t dev) { device_set_desc(dev, "SCSI Passthrough Bus"); return (0); } static int mfip_attach(device_t dev) { struct mfip_softc *sc; struct mfi_softc *mfisc; sc = device_get_softc(dev); if (sc == NULL) return (EINVAL); mfisc = device_get_softc(device_get_parent(dev)); sc->dev = dev; sc->state = MFIP_STATE_NONE; sc->mfi_sc = mfisc; mfisc->mfi_cam_start = mfip_start; if ((sc->devq = cam_simq_alloc(MFI_SCSI_MAX_CMDS)) == NULL) return (ENOMEM); sc->sim = cam_sim_alloc(mfip_cam_action, mfip_cam_poll, "mfi", sc, device_get_unit(dev), &mfisc->mfi_io_lock, 1, MFI_SCSI_MAX_CMDS, sc->devq); if (sc->sim == NULL) { cam_simq_free(sc->devq); sc->devq = NULL; device_printf(dev, "CAM SIM attach failed\n"); return (EINVAL); } mfisc->mfi_cam_rescan_cb = mfip_cam_rescan; mtx_lock(&mfisc->mfi_io_lock); if (xpt_bus_register(sc->sim, dev, 0) != 0) { device_printf(dev, "XPT bus registration failed\n"); cam_sim_free(sc->sim, FALSE); sc->sim = NULL; cam_simq_free(sc->devq); sc->devq = NULL; mtx_unlock(&mfisc->mfi_io_lock); return (EINVAL); } mtx_unlock(&mfisc->mfi_io_lock); return (0); } static int mfip_detach(device_t dev) { struct mfip_softc *sc; sc = device_get_softc(dev); if (sc == NULL) return (EINVAL); mtx_lock(&sc->mfi_sc->mfi_io_lock); if (sc->state == MFIP_STATE_RESCAN) { mtx_unlock(&sc->mfi_sc->mfi_io_lock); return (EBUSY); } sc->state = MFIP_STATE_DETACH; mtx_unlock(&sc->mfi_sc->mfi_io_lock); sc->mfi_sc->mfi_cam_rescan_cb = NULL; if (sc->sim != NULL) { mtx_lock(&sc->mfi_sc->mfi_io_lock); xpt_bus_deregister(cam_sim_path(sc->sim)); cam_sim_free(sc->sim, FALSE); sc->sim = NULL; mtx_unlock(&sc->mfi_sc->mfi_io_lock); } if (sc->devq != NULL) { cam_simq_free(sc->devq); sc->devq = NULL; } return (0); } static void mfip_cam_action(struct cam_sim *sim, union ccb *ccb) { struct mfip_softc *sc = cam_sim_softc(sim); struct mfi_softc *mfisc = sc->mfi_sc; mtx_assert(&mfisc->mfi_io_lock, MA_OWNED); switch (ccb->ccb_h.func_code) { case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_TAG_ABLE; cpi->target_sprt = 0; cpi->hba_misc = PIM_NOBUSRESET | PIM_SEQSCAN | PIM_UNMAPPED; cpi->hba_eng_cnt = 0; cpi->max_target = MFI_SCSI_MAX_TARGETS; cpi->max_lun = MFI_SCSI_MAX_LUNS; cpi->initiator_id = MFI_SCSI_INITIATOR_ID; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "LSI", HBA_IDLEN); strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 150000; cpi->transport = XPORT_SAS; cpi->transport_version = 0; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; break; } case XPT_RESET_BUS: ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_RESET_DEV: ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings_scsi *scsi = &ccb->cts.proto_specific.scsi; struct ccb_trans_settings_sas *sas = &ccb->cts.xport_specific.sas; ccb->cts.protocol = PROTO_SCSI; ccb->cts.protocol_version = SCSI_REV_2; ccb->cts.transport = XPORT_SAS; ccb->cts.transport_version = 0; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; sas->valid &= ~CTS_SAS_VALID_SPEED; sas->bitrate = 150000; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_SET_TRAN_SETTINGS: ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; break; case XPT_SCSI_IO: { struct ccb_hdr *ccbh = &ccb->ccb_h; struct ccb_scsiio *csio = &ccb->csio; ccbh->status = CAM_REQ_INPROG; if (csio->cdb_len > MFI_SCSI_MAX_CDB_LEN) { ccbh->status = CAM_REQ_INVALID; break; } ccbh->ccb_mfip_ptr = sc; TAILQ_INSERT_TAIL(&mfisc->mfi_cam_ccbq, ccbh, sim_links.tqe); mfi_startio(mfisc); return; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; } static void mfip_cam_rescan(struct mfi_softc *sc, uint32_t tid) { union ccb *ccb; struct mfip_softc *camsc; struct cam_sim *sim; device_t mfip_dev; bus_topo_lock(); - mfip_dev = device_find_child(sc->mfi_dev, "mfip", -1); + mfip_dev = device_find_child(sc->mfi_dev, "mfip", DEVICE_UNIT_ANY); bus_topo_unlock(); if (mfip_dev == NULL) { device_printf(sc->mfi_dev, "Couldn't find mfip child device!\n"); return; } mtx_lock(&sc->mfi_io_lock); camsc = device_get_softc(mfip_dev); if (camsc->state == MFIP_STATE_DETACH) { mtx_unlock(&sc->mfi_io_lock); return; } camsc->state = MFIP_STATE_RESCAN; ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { mtx_unlock(&sc->mfi_io_lock); device_printf(sc->mfi_dev, "Cannot allocate ccb for bus rescan.\n"); return; } sim = camsc->sim; if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(sim), tid, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_free_ccb(ccb); mtx_unlock(&sc->mfi_io_lock); device_printf(sc->mfi_dev, "Cannot create path for bus rescan.\n"); return; } xpt_rescan(ccb); camsc->state = MFIP_STATE_NONE; mtx_unlock(&sc->mfi_io_lock); } static struct mfi_command * mfip_start(void *data) { union ccb *ccb = data; struct ccb_hdr *ccbh = &ccb->ccb_h; struct ccb_scsiio *csio = &ccb->csio; struct mfip_softc *sc; struct mfi_pass_frame *pt; struct mfi_command *cm; uint32_t context = 0; sc = ccbh->ccb_mfip_ptr; if ((cm = mfi_dequeue_free(sc->mfi_sc)) == NULL) return (NULL); /* Zero out the MFI frame */ context = cm->cm_frame->header.context; bzero(cm->cm_frame, sizeof(union mfi_frame)); cm->cm_frame->header.context = context; pt = &cm->cm_frame->pass; pt->header.cmd = MFI_CMD_PD_SCSI_IO; pt->header.cmd_status = 0; pt->header.scsi_status = 0; pt->header.target_id = ccbh->target_id; pt->header.lun_id = ccbh->target_lun; pt->header.flags = 0; pt->header.timeout = 0; pt->header.data_len = csio->dxfer_len; pt->header.sense_len = MFI_SENSE_LEN; pt->header.cdb_len = csio->cdb_len; pt->sense_addr_lo = (uint32_t)cm->cm_sense_busaddr; pt->sense_addr_hi = (uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32); if (ccbh->flags & CAM_CDB_POINTER) bcopy(csio->cdb_io.cdb_ptr, &pt->cdb[0], csio->cdb_len); else bcopy(csio->cdb_io.cdb_bytes, &pt->cdb[0], csio->cdb_len); cm->cm_complete = mfip_done; cm->cm_private = ccb; cm->cm_sg = &pt->sgl; cm->cm_total_frame_size = MFI_PASS_FRAME_SIZE; cm->cm_data = ccb; cm->cm_len = csio->dxfer_len; switch (ccbh->flags & CAM_DIR_MASK) { case CAM_DIR_IN: cm->cm_flags = MFI_CMD_DATAIN | MFI_CMD_CCB; break; case CAM_DIR_OUT: cm->cm_flags = MFI_CMD_DATAOUT | MFI_CMD_CCB; break; case CAM_DIR_NONE: default: cm->cm_data = NULL; cm->cm_len = 0; cm->cm_flags = 0; break; } TAILQ_REMOVE(&sc->mfi_sc->mfi_cam_ccbq, ccbh, sim_links.tqe); return (cm); } static void mfip_done(struct mfi_command *cm) { union ccb *ccb = cm->cm_private; struct ccb_hdr *ccbh = &ccb->ccb_h; struct ccb_scsiio *csio = &ccb->csio; struct mfi_pass_frame *pt; pt = &cm->cm_frame->pass; switch (pt->header.cmd_status) { case MFI_STAT_OK: { uint8_t command, device; ccbh->status = CAM_REQ_CMP; csio->scsi_status = pt->header.scsi_status; if (ccbh->flags & CAM_CDB_POINTER) command = csio->cdb_io.cdb_ptr[0]; else command = csio->cdb_io.cdb_bytes[0]; if (command == INQUIRY) { device = csio->data_ptr[0] & 0x1f; if ((!mfi_allow_disks && device == T_DIRECT) || (device == T_PROCESSOR)) csio->data_ptr[0] = (csio->data_ptr[0] & 0xe0) | T_NODEVICE; } break; } case MFI_STAT_SCSI_DONE_WITH_ERROR: { int sense_len; ccbh->status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; csio->scsi_status = pt->header.scsi_status; if (pt->header.sense_len < csio->sense_len) csio->sense_resid = csio->sense_len - pt->header.sense_len; else csio->sense_resid = 0; sense_len = min(pt->header.sense_len, sizeof(struct scsi_sense_data)); bzero(&csio->sense_data, sizeof(struct scsi_sense_data)); bcopy(&cm->cm_sense->data[0], &csio->sense_data, sense_len); break; } case MFI_STAT_DEVICE_NOT_FOUND: ccbh->status = CAM_SEL_TIMEOUT; break; case MFI_STAT_SCSI_IO_FAILED: ccbh->status = CAM_REQ_CMP_ERR; csio->scsi_status = pt->header.scsi_status; break; default: ccbh->status = CAM_REQ_CMP_ERR; csio->scsi_status = pt->header.scsi_status; break; } mfi_release_command(cm); xpt_done(ccb); } static void mfip_cam_poll(struct cam_sim *sim) { struct mfip_softc *sc = cam_sim_softc(sim); struct mfi_softc *mfisc = sc->mfi_sc; mfisc->mfi_intr_ptr(mfisc); } diff --git a/sys/dev/mmcnull/mmcnull.c b/sys/dev/mmcnull/mmcnull.c index 028d3aabd7f1..ec4bc1339778 100644 --- a/sys/dev/mmcnull/mmcnull.c +++ b/sys/dev/mmcnull/mmcnull.c @@ -1,458 +1,458 @@ /*- * Copyright (c) 2013 Ilya Bakulin. 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, 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int is_sdio_mode = 1; struct mmcnull_softc { device_t dev; struct mtx sc_mtx; struct cam_devq *devq; struct cam_sim *sim; struct cam_path *path; struct callout tick; union ccb *cur_ccb; }; static void mmcnull_identify(driver_t *, device_t); static int mmcnull_probe(device_t); static int mmcnull_attach(device_t); static int mmcnull_detach(device_t); static void mmcnull_action_sd(struct cam_sim *, union ccb *); static void mmcnull_action_sdio(struct cam_sim *, union ccb *); static void mmcnull_intr_sd(void *xsc); static void mmcnull_intr_sdio(void *xsc); static void mmcnull_poll(struct cam_sim *); static void mmcnull_identify(driver_t *driver, device_t parent) { device_t child; if (resource_disabled("mmcnull", 0)) return; if (device_get_unit(parent) != 0) return; /* Avoid duplicates. */ - if (device_find_child(parent, "mmcnull", -1)) + if (device_find_child(parent, "mmcnull", DEVICE_UNIT_ANY)) return; child = BUS_ADD_CHILD(parent, 20, "mmcnull", 0); if (child == NULL) { device_printf(parent, "add MMCNULL child failed\n"); return; } } static int mmcnull_probe(device_t dev) { device_set_desc(dev, "Emulated MMC controller"); return (BUS_PROBE_DEFAULT); } static int mmcnull_attach(device_t dev) { struct mmcnull_softc *sc; sim_action_func action_func; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->sc_mtx, "mmcnullmtx", NULL, MTX_DEF); if ((sc->devq = cam_simq_alloc(1)) == NULL) return (ENOMEM); if (is_sdio_mode) action_func = mmcnull_action_sdio; else action_func = mmcnull_action_sd; sc->sim = cam_sim_alloc(action_func, mmcnull_poll, "mmcnull", sc, device_get_unit(dev), &sc->sc_mtx, 1, 1, sc->devq); if (sc->sim == NULL) { cam_simq_free(sc->devq); device_printf(dev, "cannot allocate CAM SIM\n"); return (EINVAL); } mtx_lock(&sc->sc_mtx); if (xpt_bus_register(sc->sim, dev, 0) != 0) { device_printf(dev, "cannot register SCSI pass-through bus\n"); cam_sim_free(sc->sim, FALSE); cam_simq_free(sc->devq); mtx_unlock(&sc->sc_mtx); return (EINVAL); } mtx_unlock(&sc->sc_mtx); callout_init_mtx(&sc->tick, &sc->sc_mtx, 0); /* Callout to emulate interrupts */ device_printf(dev, "attached OK\n"); return (0); } static int mmcnull_detach(device_t dev) { struct mmcnull_softc *sc; sc = device_get_softc(dev); if (sc == NULL) return (EINVAL); if (sc->sim != NULL) { mtx_lock(&sc->sc_mtx); xpt_bus_deregister(cam_sim_path(sc->sim)); cam_sim_free(sc->sim, FALSE); mtx_unlock(&sc->sc_mtx); } if (sc->devq != NULL) cam_simq_free(sc->devq); callout_drain(&sc->tick); mtx_destroy(&sc->sc_mtx); device_printf(dev, "detached OK\n"); return (0); } /* * The interrupt handler * This implementation calls it via callout(9) * with the mutex already taken */ static void mmcnull_intr_sd(void *xsc) { struct mmcnull_softc *sc; union ccb *ccb; struct ccb_mmcio *mmcio; sc = (struct mmcnull_softc *) xsc; mtx_assert(&sc->sc_mtx, MA_OWNED); ccb = sc->cur_ccb; mmcio = &ccb->mmcio; device_printf(sc->dev, "mmcnull_intr: MMC command = %d\n", mmcio->cmd.opcode); switch (mmcio->cmd.opcode) { case MMC_GO_IDLE_STATE: device_printf(sc->dev, "Reset device\n"); break; case SD_SEND_IF_COND: mmcio->cmd.resp[0] = 0x1AA; // To match mmc_xpt expectations :-) break; case MMC_APP_CMD: mmcio->cmd.resp[0] = R1_APP_CMD; break; case SD_SEND_RELATIVE_ADDR: case MMC_SELECT_CARD: mmcio->cmd.resp[0] = 0x1 << 16; break; case ACMD_SD_SEND_OP_COND: mmcio->cmd.resp[0] = 0xc0ff8000; mmcio->cmd.resp[0] |= MMC_OCR_CARD_BUSY; break; case MMC_ALL_SEND_CID: /* Note: this is a real CID from Wandboard int mmc */ mmcio->cmd.resp[0] = 0x1b534d30; mmcio->cmd.resp[1] = 0x30303030; mmcio->cmd.resp[2] = 0x10842806; mmcio->cmd.resp[3] = 0x5700e900; break; case MMC_SEND_CSD: /* Note: this is a real CSD from Wandboard int mmc */ mmcio->cmd.resp[0] = 0x400e0032; mmcio->cmd.resp[1] = 0x5b590000; mmcio->cmd.resp[2] = 0x751f7f80; mmcio->cmd.resp[3] = 0x0a404000; break; case MMC_READ_SINGLE_BLOCK: case MMC_READ_MULTIPLE_BLOCK: strcpy(mmcio->cmd.data->data, "WTF?!"); break; default: device_printf(sc->dev, "mmcnull_intr_sd: unknown command\n"); mmcio->cmd.error = 1; } ccb->ccb_h.status = CAM_REQ_CMP; sc->cur_ccb = NULL; xpt_done(ccb); } static void mmcnull_intr_sdio_newintr(void *xsc) { struct mmcnull_softc *sc; struct cam_path *dpath; sc = (struct mmcnull_softc *) xsc; mtx_assert(&sc->sc_mtx, MA_OWNED); device_printf(sc->dev, "mmcnull_intr_sdio_newintr()\n"); /* Our path */ if (xpt_create_path(&dpath, NULL, cam_sim_path(sc->sim), 0, 0) != CAM_REQ_CMP) { device_printf(sc->dev, "mmcnull_intr_sdio_newintr(): cannot create path\n"); return; } xpt_async(AC_UNIT_ATTENTION, dpath, NULL); xpt_free_path(dpath); } static void mmcnull_intr_sdio(void *xsc) { struct mmcnull_softc *sc; union ccb *ccb; struct ccb_mmcio *mmcio; sc = (struct mmcnull_softc *) xsc; mtx_assert(&sc->sc_mtx, MA_OWNED); ccb = sc->cur_ccb; mmcio = &ccb->mmcio; device_printf(sc->dev, "mmcnull_intr: MMC command = %d\n", mmcio->cmd.opcode); switch (mmcio->cmd.opcode) { case MMC_GO_IDLE_STATE: device_printf(sc->dev, "Reset device\n"); break; case SD_SEND_IF_COND: mmcio->cmd.resp[0] = 0x1AA; // To match mmc_xpt expectations :-) break; case MMC_APP_CMD: mmcio->cmd.resp[0] = R1_APP_CMD; break; case IO_SEND_OP_COND: mmcio->cmd.resp[0] = 0x12345678; mmcio->cmd.resp[0] |= ~ R4_IO_MEM_PRESENT; break; case SD_SEND_RELATIVE_ADDR: case MMC_SELECT_CARD: mmcio->cmd.resp[0] = 0x1 << 16; break; case ACMD_SD_SEND_OP_COND: /* TODO: steal valid OCR from somewhere :-) */ mmcio->cmd.resp[0] = 0x123; mmcio->cmd.resp[0] |= MMC_OCR_CARD_BUSY; break; case MMC_ALL_SEND_CID: mmcio->cmd.resp[0] = 0x1234; mmcio->cmd.resp[1] = 0x5678; mmcio->cmd.resp[2] = 0x9ABC; mmcio->cmd.resp[3] = 0xDEF0; break; case MMC_READ_SINGLE_BLOCK: case MMC_READ_MULTIPLE_BLOCK: strcpy(mmcio->cmd.data->data, "WTF?!"); break; case SD_IO_RW_DIRECT: device_printf(sc->dev, "Scheduling interrupt generation...\n"); callout_reset(&sc->tick, hz / 10, mmcnull_intr_sdio_newintr, sc); break; default: device_printf(sc->dev, "mmcnull_intr_sdio: unknown command\n"); } ccb->ccb_h.status = CAM_REQ_CMP; sc->cur_ccb = NULL; xpt_done(ccb); } /* * This is a MMC IO handler * It extracts MMC command from CCB and sends it * to the h/w */ static void mmcnull_handle_mmcio(struct cam_sim *sim, union ccb *ccb) { struct mmcnull_softc *sc; struct ccb_mmcio *mmcio; sc = cam_sim_softc(sim); mmcio = &ccb->mmcio; ccb->ccb_h.status = CAM_REQ_INPROG; sc->cur_ccb = ccb; /* Real h/w will wait for the interrupt */ if (is_sdio_mode) callout_reset(&sc->tick, hz / 10, mmcnull_intr_sdio, sc); else callout_reset(&sc->tick, hz / 10, mmcnull_intr_sd, sc); } static void mmcnull_action_sd(struct cam_sim *sim, union ccb *ccb) { struct mmcnull_softc *sc; sc = cam_sim_softc(sim); if (sc == NULL) { ccb->ccb_h.status = CAM_SEL_TIMEOUT; xpt_done(ccb); return; } mtx_assert(&sc->sc_mtx, MA_OWNED); device_printf(sc->dev, "action: func_code %0x\n", ccb->ccb_h.func_code); switch (ccb->ccb_h.func_code) { case XPT_PATH_INQ: { struct ccb_pathinq *cpi; cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE | PI_TAG_ABLE | PI_WIDE_16; cpi->target_sprt = 0; cpi->hba_misc = PIM_NOBUSRESET | PIM_SEQSCAN; cpi->hba_eng_cnt = 0; cpi->max_target = 0; cpi->max_lun = 0; cpi->initiator_id = 1; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "FreeBSD Foundation", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 100; /* XXX WTF? */ cpi->protocol = PROTO_MMCSD; cpi->protocol_version = SCSI_REV_0; cpi->transport = XPORT_MMCSD; cpi->transport_version = 0; cpi->ccb_h.status = CAM_REQ_CMP; break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; struct ccb_trans_settings_mmc *mcts; mcts = &ccb->cts.proto_specific.mmc; device_printf(sc->dev, "Got XPT_GET_TRAN_SETTINGS\n"); cts->protocol = PROTO_MMCSD; cts->protocol_version = 0; cts->transport = XPORT_MMCSD; cts->transport_version = 0; cts->xport_specific.valid = 0; mcts->host_f_max = 12000000; mcts->host_f_min = 200000; mcts->host_ocr = 1; /* Fix this */ ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_SET_TRAN_SETTINGS: device_printf(sc->dev, "Got XPT_SET_TRAN_SETTINGS, should update IOS...\n"); ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_RESET_BUS: device_printf(sc->dev, "Got XPT_RESET_BUS, ACK it...\n"); ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_MMC_IO: /* * Here is the HW-dependent part of * sending the command to the underlying h/w * At some point in the future an interrupt comes. * Then the request will be marked as completed. */ device_printf(sc->dev, "Got XPT_MMC_IO\n"); mmcnull_handle_mmcio(sim, ccb); return; break; case XPT_RESET_DEV: /* This is sent by `camcontrol reset`*/ device_printf(sc->dev, "Got XPT_RESET_DEV\n"); ccb->ccb_h.status = CAM_REQ_CMP; break; default: device_printf(sc->dev, "Func code %d is unknown\n", ccb->ccb_h.func_code); ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; } static void mmcnull_action_sdio(struct cam_sim *sim, union ccb *ccb) { mmcnull_action_sd(sim, ccb); } static void mmcnull_poll(struct cam_sim *sim) { return; } static device_method_t mmcnull_methods[] = { /* Device interface */ DEVMETHOD(device_identify, mmcnull_identify), DEVMETHOD(device_probe, mmcnull_probe), DEVMETHOD(device_attach, mmcnull_attach), DEVMETHOD(device_detach, mmcnull_detach), DEVMETHOD_END }; static driver_t mmcnull_driver = { "mmcnull", mmcnull_methods, sizeof(struct mmcnull_softc) }; DRIVER_MODULE(mmcnull, isa, mmcnull_driver, 0, 0); diff --git a/sys/dev/nvdimm/nvdimm_e820.c b/sys/dev/nvdimm/nvdimm_e820.c index 6f9bb4c70f7a..f916801750b6 100644 --- a/sys/dev/nvdimm/nvdimm_e820.c +++ b/sys/dev/nvdimm/nvdimm_e820.c @@ -1,385 +1,385 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Dell EMC Isilon * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct nvdimm_e820_bus { SLIST_HEAD(, SPA_mapping) spas; }; #define NVDIMM_E820 "nvdimm_e820" static MALLOC_DEFINE(M_NVDIMM_E820, NVDIMM_E820, "NVDIMM e820 bus memory"); static const struct bios_smap *smapbase; static struct { vm_paddr_t start; vm_paddr_t size; } pram_segments[VM_PHYSSEG_MAX]; static unsigned pram_nreg; static void nvdimm_e820_dump_prams(device_t dev, const char *func, int hintunit) { char buffer[256]; struct sbuf sb; bool printed = false; unsigned i; sbuf_new(&sb, buffer, sizeof(buffer), SBUF_FIXEDLEN); sbuf_set_drain(&sb, sbuf_printf_drain, NULL); sbuf_printf(&sb, "%s: %s: ", device_get_nameunit(dev), func); if (hintunit < 0) sbuf_cat(&sb, "Found BIOS PRAM regions: "); else sbuf_printf(&sb, "Remaining unallocated PRAM regions after " "hint %d: ", hintunit); for (i = 0; i < pram_nreg; i++) { if (pram_segments[i].size == 0) continue; if (printed) sbuf_putc(&sb, ','); else printed = true; sbuf_printf(&sb, "0x%jx-0x%jx", (uintmax_t)pram_segments[i].start, (uintmax_t)pram_segments[i].start + pram_segments[i].size - 1); } if (!printed) sbuf_cat(&sb, ""); sbuf_putc(&sb, '\n'); sbuf_finish(&sb); sbuf_delete(&sb); } static int nvdimm_e820_create_spas(device_t dev) { static const vm_size_t HINT_ALL = (vm_size_t)-1; ACPI_NFIT_SYSTEM_ADDRESS nfit_sa; struct SPA_mapping *spa_mapping; enum SPA_mapping_type spa_type; struct nvdimm_e820_bus *sc; const char *hinttype; long hintaddrl, hintsizel; vm_paddr_t hintaddr; vm_size_t hintsize; unsigned i, j; int error; sc = device_get_softc(dev); error = 0; nfit_sa = (ACPI_NFIT_SYSTEM_ADDRESS) { 0 }; if (bootverbose) nvdimm_e820_dump_prams(dev, __func__, -1); for (i = 0; resource_long_value("nvdimm_spa", i, "maddr", &hintaddrl) == 0; i++) { if (resource_long_value("nvdimm_spa", i, "msize", &hintsizel) != 0) { device_printf(dev, "hint.nvdimm_spa.%u missing msize\n", i); continue; } hintaddr = (vm_paddr_t)hintaddrl; hintsize = (vm_size_t)hintsizel; if ((hintaddr & PAGE_MASK) != 0 || ((hintsize & PAGE_MASK) != 0 && hintsize != HINT_ALL)) { device_printf(dev, "hint.nvdimm_spa.%u addr or size " "not page aligned\n", i); continue; } if (resource_string_value("nvdimm_spa", i, "type", &hinttype) != 0) { device_printf(dev, "hint.nvdimm_spa.%u missing type\n", i); continue; } spa_type = nvdimm_spa_type_from_name(hinttype); if (spa_type == SPA_TYPE_UNKNOWN) { device_printf(dev, "hint.nvdimm_spa%u.type does not " "match any known SPA types\n", i); continue; } for (j = 0; j < pram_nreg; j++) { if (pram_segments[j].start <= hintaddr && (hintsize == HINT_ALL || (pram_segments[j].start + pram_segments[j].size) >= (hintaddr + hintsize))) break; } if (j == pram_nreg) { device_printf(dev, "hint.nvdimm_spa%u hint does not " "match any region\n", i); continue; } /* Carve off "SPA" from available regions. */ if (pram_segments[j].start == hintaddr) { /* Easy case first: beginning of segment. */ if (hintsize == HINT_ALL) hintsize = pram_segments[j].size; pram_segments[j].start += hintsize; pram_segments[j].size -= hintsize; /* We might leave an empty segment; who cares. */ } else if (hintsize == HINT_ALL || (pram_segments[j].start + pram_segments[j].size) == (hintaddr + hintsize)) { /* 2nd easy case: end of segment. */ if (hintsize == HINT_ALL) hintsize = pram_segments[j].size - (hintaddr - pram_segments[j].start); pram_segments[j].size -= hintsize; } else { /* Hard case: mid segment. */ if (pram_nreg == nitems(pram_segments)) { /* Improbable, but handle gracefully. */ device_printf(dev, "Ran out of %zu segments\n", nitems(pram_segments)); error = ENOBUFS; break; } if (j != pram_nreg - 1) { memmove(&pram_segments[j + 2], &pram_segments[j + 1], (pram_nreg - 1 - j) * sizeof(pram_segments[0])); } pram_nreg++; pram_segments[j + 1].start = hintaddr + hintsize; pram_segments[j + 1].size = (pram_segments[j].start + pram_segments[j].size) - (hintaddr + hintsize); pram_segments[j].size = hintaddr - pram_segments[j].start; } if (bootverbose) nvdimm_e820_dump_prams(dev, __func__, (int)i); spa_mapping = malloc(sizeof(*spa_mapping), M_NVDIMM_E820, M_WAITOK | M_ZERO); /* Mock up a super primitive table for nvdimm_spa_init(). */ nfit_sa.RangeIndex = i; nfit_sa.Flags = 0; nfit_sa.Address = hintaddr; nfit_sa.Length = hintsize; nfit_sa.MemoryMapping = EFI_MD_ATTR_WB | EFI_MD_ATTR_WT | EFI_MD_ATTR_UC; error = nvdimm_spa_init(spa_mapping, &nfit_sa, spa_type); if (error != 0) { nvdimm_spa_fini(spa_mapping); free(spa_mapping, M_NVDIMM_E820); break; } SLIST_INSERT_HEAD(&sc->spas, spa_mapping, link); } return (error); } static int nvdimm_e820_remove_spas(device_t dev) { struct nvdimm_e820_bus *sc; struct SPA_mapping *spa, *next; sc = device_get_softc(dev); SLIST_FOREACH_SAFE(spa, &sc->spas, link, next) { nvdimm_spa_fini(spa); SLIST_REMOVE_HEAD(&sc->spas, link); free(spa, M_NVDIMM_E820); } return (0); } static void nvdimm_e820_identify(driver_t *driver, device_t parent) { device_t child; if (resource_disabled(driver->name, 0)) return; /* Just create a single instance of the fake bus. */ - if (device_find_child(parent, driver->name, -1) != NULL) + if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL) return; smapbase = (const void *)preload_search_info(preload_kmdp, MODINFO_METADATA | MODINFOMD_SMAP); /* Only supports BIOS SMAP for now. */ if (smapbase == NULL) return; child = BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY); if (child == NULL) device_printf(parent, "add %s child failed\n", driver->name); } static int nvdimm_e820_probe(device_t dev) { /* * nexus panics if a child doesn't have ivars. BUS_ADD_CHILD uses * nexus_add_child, which creates fuckin ivars. but sometimes if you * unload and reload nvdimm_e820, the device node stays but the ivars * are deleted??? avoid trivial panic but this is a kludge. */ if (device_get_ivars(dev) == NULL) return (ENXIO); device_quiet(dev); device_set_desc(dev, "Legacy e820 NVDIMM root device"); return (BUS_PROBE_NOWILDCARD); } static int nvdimm_e820_attach(device_t dev) { const struct bios_smap *smapend, *smap; uint32_t smapsize; unsigned nregions; int error; smapsize = *((const uint32_t *)smapbase - 1); smapend = (const void *)((const char *)smapbase + smapsize); for (nregions = 0, smap = smapbase; smap < smapend; smap++) { if (smap->type != SMAP_TYPE_PRAM || smap->length == 0) continue; pram_segments[nregions].start = smap->base; pram_segments[nregions].size = smap->length; device_printf(dev, "Found PRAM 0x%jx +0x%jx\n", (uintmax_t)smap->base, (uintmax_t)smap->length); nregions++; } if (nregions == 0) { device_printf(dev, "No e820 PRAM regions detected\n"); return (ENXIO); } pram_nreg = nregions; error = nvdimm_e820_create_spas(dev); return (error); } static int nvdimm_e820_detach(device_t dev) { int error; error = nvdimm_e820_remove_spas(dev); return (error); } static device_method_t nvdimm_e820_methods[] = { DEVMETHOD(device_identify, nvdimm_e820_identify), DEVMETHOD(device_probe, nvdimm_e820_probe), DEVMETHOD(device_attach, nvdimm_e820_attach), DEVMETHOD(device_detach, nvdimm_e820_detach), DEVMETHOD_END }; static driver_t nvdimm_e820_driver = { NVDIMM_E820, nvdimm_e820_methods, sizeof(struct nvdimm_e820_bus), }; static int nvdimm_e820_chainevh(struct module *m, int e, void *arg __unused) { devclass_t dc; device_t dev, parent; int i, error, maxunit; switch (e) { case MOD_UNLOAD: dc = devclass_find(nvdimm_e820_driver.name); maxunit = devclass_get_maxunit(dc); for (i = 0; i < maxunit; i++) { dev = devclass_get_device(dc, i); if (dev == NULL) continue; parent = device_get_parent(dev); if (parent == NULL) { /* Not sure how this would happen. */ continue; } error = device_delete_child(parent, dev); if (error != 0) return (error); } break; default: /* Prevent compiler warning about unhandled cases. */ break; } return (0); } DRIVER_MODULE(nvdimm_e820, nexus, nvdimm_e820_driver, nvdimm_e820_chainevh, NULL); diff --git a/sys/dev/nvmf/host/nvmf_ctldev.c b/sys/dev/nvmf/host/nvmf_ctldev.c index bc79bd99f639..275d5e9c932a 100644 --- a/sys/dev/nvmf/host/nvmf_ctldev.c +++ b/sys/dev/nvmf/host/nvmf_ctldev.c @@ -1,160 +1,160 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 Chelsio Communications, Inc. * Written by: John Baldwin */ #include #include #include #include #include #include #include #include #include static struct cdev *nvmf_cdev; static int nvmf_handoff_host(struct nvmf_ioc_nv *nv) { nvlist_t *nvl; device_t dev; int error; error = nvmf_copyin_handoff(nv, &nvl); if (error != 0) return (error); bus_topo_lock(); - dev = device_add_child(root_bus, "nvme", -1); + dev = device_add_child(root_bus, "nvme", DEVICE_UNIT_ANY); if (dev == NULL) { bus_topo_unlock(); error = ENXIO; goto out; } device_set_ivars(dev, nvl); error = device_probe_and_attach(dev); device_set_ivars(dev, NULL); if (error != 0) device_delete_child(root_bus, dev); bus_topo_unlock(); out: nvlist_destroy(nvl); return (error); } static bool nvmf_matches(device_t dev, char *name) { struct nvmf_softc *sc = device_get_softc(dev); if (strcmp(device_get_nameunit(dev), name) == 0) return (true); if (strcmp(sc->cdata->subnqn, name) == 0) return (true); return (false); } static int nvmf_disconnect_by_name(char *name) { devclass_t dc; device_t dev; int error, unit; bool found; found = false; error = 0; bus_topo_lock(); dc = devclass_find("nvme"); if (dc == NULL) goto out; for (unit = 0; unit < devclass_get_maxunit(dc); unit++) { dev = devclass_get_device(dc, unit); if (dev == NULL) continue; if (device_get_driver(dev) != &nvme_nvmf_driver) continue; if (device_get_parent(dev) != root_bus) continue; if (name != NULL && !nvmf_matches(dev, name)) continue; error = device_delete_child(root_bus, dev); if (error != 0) break; found = true; } out: bus_topo_unlock(); if (error == 0 && !found) error = ENOENT; return (error); } static int nvmf_disconnect_host(const char **namep) { char *name; int error; name = malloc(PATH_MAX, M_NVMF, M_WAITOK); error = copyinstr(*namep, name, PATH_MAX, NULL); if (error == 0) error = nvmf_disconnect_by_name(name); free(name, M_NVMF); return (error); } static int nvmf_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td) { switch (cmd) { case NVMF_HANDOFF_HOST: return (nvmf_handoff_host((struct nvmf_ioc_nv *)arg)); case NVMF_DISCONNECT_HOST: return (nvmf_disconnect_host((const char **)arg)); case NVMF_DISCONNECT_ALL: return (nvmf_disconnect_by_name(NULL)); default: return (ENOTTY); } } static struct cdevsw nvmf_ctl_cdevsw = { .d_version = D_VERSION, .d_ioctl = nvmf_ctl_ioctl }; int nvmf_ctl_load(void) { struct make_dev_args mda; int error; make_dev_args_init(&mda); mda.mda_devsw = &nvmf_ctl_cdevsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_WHEEL; mda.mda_mode = 0600; error = make_dev_s(&mda, &nvmf_cdev, "nvmf"); if (error != 0) nvmf_cdev = NULL; return (error); } void nvmf_ctl_unload(void) { if (nvmf_cdev != NULL) { destroy_dev(nvmf_cdev); nvmf_cdev = NULL; } } diff --git a/sys/dev/p2sb/lewisburg_gpiocm.c b/sys/dev/p2sb/lewisburg_gpiocm.c index f5c1792c69e1..9dbbd84f2379 100644 --- a/sys/dev/p2sb/lewisburg_gpiocm.c +++ b/sys/dev/p2sb/lewisburg_gpiocm.c @@ -1,340 +1,340 @@ /*- * Copyright (c) 2018 Stormshield * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "lewisburg_gpiocm.h" #include "p2sb.h" #define PADBAR 0x00c #define PADCFG0_GPIORXDIS (1<<9) #define PADCFG0_GPIOTXDIS (1<<8) #define PADCFG0_GPIORXSTATE (1<<1) #define PADCFG0_GPIOTXSTATE (1<<0) #define MAX_PAD_PER_GROUP 24 #define LBGGPIOCM_READ(sc, reg) p2sb_port_read_4(sc->p2sb, sc->port, reg) #define LBGGPIOCM_WRITE(sc, reg, val) \ p2sb_port_write_4(sc->p2sb, sc->port, reg, val) #define LBGGPIOCM_LOCK(sc) p2sb_lock(sc->p2sb) #define LBGGPIOCM_UNLOCK(sc) p2sb_unlock(sc->p2sb) struct lbggroup { int groupid; int npins; int pins_off; device_t dev; char grpname; }; struct lbgcommunity { uint8_t npins; const char *name; uint32_t pad_off; struct lbggroup groups[3]; int ngroups; const char *grpnames; }; #define LBG_COMMUNITY(n, np, g) \ { \ .name = n, \ .npins = np, \ .grpnames = g, \ } static struct lbgcommunity lbg_communities[] = { LBG_COMMUNITY("LewisBurg GPIO Community 0", 72, "ABF"), LBG_COMMUNITY("LewisBurg GPIO Community 1", 61, "CDE"), LBG_COMMUNITY("LewisBurg GPIO Community 2", 0, ""), LBG_COMMUNITY("LewisBurg GPIO Community 3", 12, "I"), LBG_COMMUNITY("LewisBurg GPIO Community 4", 36, "JK"), LBG_COMMUNITY("LewisBurg GPIO Community 5", 66, "GHL"), }; struct lbggpiocm_softc { int port; device_t p2sb; struct lbgcommunity *community; }; static struct lbggroup *lbggpiocm_get_group(struct lbggpiocm_softc *sc, device_t child); static __inline struct lbggroup * lbggpiocm_get_group(struct lbggpiocm_softc *sc, device_t child) { int i; for (i = 0; i < sc->community->ngroups; ++i) if (sc->community->groups[i].dev == child) return (&sc->community->groups[i]); return (NULL); } static __inline uint32_t lbggpiocm_getpad(struct lbggpiocm_softc *sc, uint32_t pin) { if (pin >= sc->community->npins) return (0); return (sc->community->pad_off + 2 * 4 * pin); } int lbggpiocm_get_group_npins(device_t dev, device_t child) { struct lbggpiocm_softc *sc = device_get_softc(dev); struct lbggroup *group; group = lbggpiocm_get_group(sc, child); if (group != NULL) return (group->npins); return (-1); } char lbggpiocm_get_group_name(device_t dev, device_t child) { struct lbggpiocm_softc *sc = device_get_softc(dev); struct lbggroup *group; group = lbggpiocm_get_group(sc, child); if (group != NULL) return (group->grpname); return ('\0'); } static int lbggpiocm_pin2cpin(struct lbggpiocm_softc *sc, device_t child, uint32_t pin) { struct lbggroup *group; group = lbggpiocm_get_group(sc, child); if (group != NULL) return (pin + group->pins_off); return (-1); } int lbggpiocm_pin_setflags(device_t dev, device_t child, uint32_t pin, uint32_t flags) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, padval; int rpin; if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) return (EINVAL); if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == 0) return (EINVAL); rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); padval = LBGGPIOCM_READ(sc, padreg); if (flags & GPIO_PIN_INPUT) { padval &= ~PADCFG0_GPIORXDIS; padval |= PADCFG0_GPIOTXDIS; } else if (flags & GPIO_PIN_OUTPUT) { padval &= ~PADCFG0_GPIOTXDIS; padval |= PADCFG0_GPIORXDIS; } LBGGPIOCM_WRITE(sc, padreg, padval); LBGGPIOCM_UNLOCK(sc); return (0); } int lbggpiocm_pin_get(device_t dev, device_t child, uint32_t pin, uint32_t *value) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, val; int rpin; if (value == NULL) return (EINVAL); rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); val = LBGGPIOCM_READ(sc, padreg); LBGGPIOCM_UNLOCK(sc); if (!(val & PADCFG0_GPIOTXDIS)) *value = !!(val & PADCFG0_GPIOTXSTATE); else *value = !!(val & PADCFG0_GPIORXSTATE); return (0); } int lbggpiocm_pin_set(device_t dev, device_t child, uint32_t pin, uint32_t value) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, padcfg; int rpin; rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); padcfg = LBGGPIOCM_READ(sc, padreg); if (value) padcfg |= PADCFG0_GPIOTXSTATE; else padcfg &= ~PADCFG0_GPIOTXSTATE; LBGGPIOCM_WRITE(sc, padreg, padcfg); LBGGPIOCM_UNLOCK(sc); return (0); } int lbggpiocm_pin_toggle(device_t dev, device_t child, uint32_t pin) { struct lbggpiocm_softc *sc = device_get_softc(dev); uint32_t padreg, padcfg; int rpin; rpin = lbggpiocm_pin2cpin(sc, child, pin); if (rpin < 0) return (EINVAL); padreg = lbggpiocm_getpad(sc, rpin); LBGGPIOCM_LOCK(sc); padcfg = LBGGPIOCM_READ(sc, padreg); padcfg ^= PADCFG0_GPIOTXSTATE; LBGGPIOCM_WRITE(sc, padreg, padcfg); LBGGPIOCM_UNLOCK(sc); return (0); } static int lbggpiocm_probe(device_t dev) { struct lbggpiocm_softc *sc = device_get_softc(dev); int unit; sc->p2sb = device_get_parent(dev); unit = device_get_unit(dev); KASSERT(unit < nitems(lbg_communities), ("Wrong number of devices or communities")); sc->port = p2sb_get_port(sc->p2sb, unit); sc->community = &lbg_communities[unit]; if (sc->port < 0) return (ENXIO); device_set_desc(dev, sc->community->name); return (BUS_PROBE_DEFAULT); } static int lbggpiocm_attach(device_t dev) { uint32_t npins; struct lbggpiocm_softc *sc; struct lbggroup *group; int i; sc = device_get_softc(dev); if (sc->community->npins == 0) return (ENXIO); LBGGPIOCM_LOCK(sc); sc->community->pad_off = LBGGPIOCM_READ(sc, PADBAR); LBGGPIOCM_UNLOCK(sc); npins = sc->community->npins; for (i = 0; i < nitems(sc->community->groups) && npins > 0; ++i) { group = &sc->community->groups[i]; group->groupid = i; group->grpname = sc->community->grpnames[i]; group->pins_off = i * MAX_PAD_PER_GROUP; group->npins = npins < MAX_PAD_PER_GROUP ? npins : MAX_PAD_PER_GROUP; npins -= group->npins; - group->dev = device_add_child(dev, "gpio", -1); + group->dev = device_add_child(dev, "gpio", DEVICE_UNIT_ANY); } sc->community->ngroups = i; bus_attach_children(dev); return (0); } static device_method_t lbggpiocm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, lbggpiocm_probe), DEVMETHOD(device_attach, lbggpiocm_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD_END }; static driver_t lbggpiocm_driver = { "lbggpiocm", lbggpiocm_methods, sizeof(struct lbggpiocm_softc) }; DRIVER_MODULE(lbggpiocm, p2sb, lbggpiocm_driver, NULL, NULL); diff --git a/sys/dev/pcf/pcf_isa.c b/sys/dev/pcf/pcf_isa.c index f86caed87e6a..c797dc31e6d9 100644 --- a/sys/dev/pcf/pcf_isa.c +++ b/sys/dev/pcf/pcf_isa.c @@ -1,206 +1,206 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004 Joerg Wunsch * * derived from sys/i386/isa/pcf.c which is: * * Copyright (c) 1998 Nicolas Souchu, Marc Bouget * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include /* * Hardware driver for a Philips PCF8584 I2C bus controller sitting * on a generic ISA bus. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #define PCF_NAME "pcf" static void pcf_isa_identify(driver_t *, device_t); static int pcf_isa_probe(device_t); static int pcf_isa_attach(device_t); static int pcf_isa_detach(device_t); static device_method_t pcf_isa_methods[] = { /* device interface */ DEVMETHOD(device_identify, pcf_isa_identify), DEVMETHOD(device_probe, pcf_isa_probe), DEVMETHOD(device_attach, pcf_isa_attach), DEVMETHOD(device_detach, pcf_isa_detach), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_repeated_start, pcf_repeated_start), DEVMETHOD(iicbus_start, pcf_start), DEVMETHOD(iicbus_stop, pcf_stop), DEVMETHOD(iicbus_write, pcf_write), DEVMETHOD(iicbus_read, pcf_read), DEVMETHOD(iicbus_reset, pcf_rst_card), { 0, 0 } }; static driver_t pcf_isa_driver = { PCF_NAME, pcf_isa_methods, sizeof(struct pcf_softc), }; static void pcf_isa_identify(driver_t *driver, device_t parent) { BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, PCF_NAME, 0); return; } static int pcf_isa_probe(device_t dev) { rman_res_t start, count; u_int rid = 0, port, error; /* skip PnP probes */ if (isa_get_logicalid(dev)) return (ENXIO); /* The port address must be explicitly specified */ bus_get_resource(dev, SYS_RES_IOPORT, rid, &start, &count); if ((error = resource_int_value(PCF_NAME, 0, "port", &port)) != 0) return (error); /* Probe is only successful for the specified base io */ if (port != (u_int)start) return (ENXIO); device_set_desc(dev, "PCF8584 I2C bus controller"); return (0); } static int pcf_isa_attach(device_t dev) { struct pcf_softc *sc; int rv = ENXIO; sc = DEVTOSOFTC(dev); mtx_init(&sc->pcf_lock, device_get_nameunit(dev), "pcf", MTX_DEF); /* IO port is mandatory */ sc->res_ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->rid_ioport, RF_ACTIVE); if (sc->res_ioport == 0) { device_printf(dev, "cannot reserve I/O port range\n"); goto error; } sc->pcf_flags = device_get_flags(dev); if (!(sc->pcf_flags & IIC_POLLED)) { sc->res_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->rid_irq, RF_ACTIVE); if (sc->res_irq == 0) { device_printf(dev, "can't reserve irq, polled mode.\n"); sc->pcf_flags |= IIC_POLLED; } } /* reset the chip */ pcf_rst_card(dev, IIC_FASTEST, PCF_DEFAULT_ADDR, NULL); if (sc->res_irq) { rv = bus_setup_intr(dev, sc->res_irq, INTR_TYPE_NET /* | INTR_ENTROPY */, NULL, pcf_intr, sc, &sc->intr_cookie); if (rv) { device_printf(dev, "could not setup IRQ\n"); goto error; } } - if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL) + if ((sc->iicbus = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY)) == NULL) device_printf(dev, "could not allocate iicbus instance\n"); /* probe and attach the iicbus */ bus_attach_children(dev); return (0); error: if (sc->res_irq != 0) { bus_release_resource(dev, SYS_RES_IRQ, sc->rid_irq, sc->res_irq); } if (sc->res_ioport != 0) { bus_release_resource(dev, SYS_RES_IOPORT, sc->rid_ioport, sc->res_ioport); } mtx_destroy(&sc->pcf_lock); return (rv); } static int pcf_isa_detach(device_t dev) { struct pcf_softc *sc; int rv; sc = DEVTOSOFTC(dev); if ((rv = bus_generic_detach(dev)) != 0) return (rv); if (sc->res_irq != 0) { bus_teardown_intr(dev, sc->res_irq, sc->intr_cookie); bus_release_resource(dev, SYS_RES_IRQ, sc->rid_irq, sc->res_irq); } bus_release_resource(dev, SYS_RES_IOPORT, sc->rid_ioport, sc->res_ioport); mtx_destroy(&sc->pcf_lock); return (0); } DRIVER_MODULE(pcf_isa, isa, pcf_isa_driver, 0, 0); diff --git a/sys/dev/pci/pci_host_generic_den0115.c b/sys/dev/pci/pci_host_generic_den0115.c index 3f6daa12344e..d8e3f9feaf18 100644 --- a/sys/dev/pci/pci_host_generic_den0115.c +++ b/sys/dev/pci/pci_host_generic_den0115.c @@ -1,260 +1,260 @@ /*- * Copyright (c) 2022 Andrew Turner * Copyright (c) 2023 Arm Ltd * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" static device_probe_t pci_host_acpi_smccc_probe; static device_attach_t pci_host_acpi_smccc_attach; static pcib_read_config_t pci_host_acpi_smccc_read_config; static pcib_write_config_t pci_host_acpi_smccc_write_config; static bool pci_host_acpi_smccc_pci_version(uint32_t *); static int pci_host_acpi_smccc_probe(device_t dev) { ACPI_DEVICE_INFO *devinfo; struct resource *res; ACPI_HANDLE h; int rid, root; if (acpi_disabled("pcib") || (h = acpi_get_handle(dev)) == NULL || ACPI_FAILURE(AcpiGetObjectInfo(h, &devinfo))) return (ENXIO); root = (devinfo->Flags & ACPI_PCI_ROOT_BRIDGE) != 0; AcpiOsFree(devinfo); if (!root) return (ENXIO); /* * Check if we have memory resources. We may have a non-memory * mapped device, e.g. using the Arm PCI Configuration Space * Access Firmware Interface (DEN0115). */ rid = 0; res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 0); if (res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, rid, res); return (ENXIO); } /* Check for the PCI_VERSION call */ if (!pci_host_acpi_smccc_pci_version(NULL)) { return (ENXIO); } device_set_desc(dev, "ARM PCI Firmware config space host controller"); return (BUS_PROBE_SPECIFIC); } #define SMCCC_PCI_VERSION \ SMCCC_FUNC_ID(SMCCC_FAST_CALL, SMCCC_32BIT_CALL, \ SMCCC_STD_SECURE_SERVICE_CALLS, 0x130) #define SMCCC_PCI_FEATURES \ SMCCC_FUNC_ID(SMCCC_FAST_CALL, SMCCC_32BIT_CALL, \ SMCCC_STD_SECURE_SERVICE_CALLS, 0x131) #define SMCCC_PCI_READ \ SMCCC_FUNC_ID(SMCCC_FAST_CALL, SMCCC_32BIT_CALL, \ SMCCC_STD_SECURE_SERVICE_CALLS, 0x132) #define SMCCC_PCI_WRITE \ SMCCC_FUNC_ID(SMCCC_FAST_CALL, SMCCC_32BIT_CALL, \ SMCCC_STD_SECURE_SERVICE_CALLS, 0x133) #define SMCCC_PCI_GET_SEG_INFO \ SMCCC_FUNC_ID(SMCCC_FAST_CALL, SMCCC_32BIT_CALL, \ SMCCC_STD_SECURE_SERVICE_CALLS, 0x134) CTASSERT(SMCCC_PCI_VERSION == 0x84000130); CTASSERT(SMCCC_PCI_FEATURES == 0x84000131); CTASSERT(SMCCC_PCI_READ == 0x84000132); CTASSERT(SMCCC_PCI_WRITE == 0x84000133); CTASSERT(SMCCC_PCI_GET_SEG_INFO == 0x84000134); #define SMCCC_PCI_MAJOR(x) (((x) >> 16) & 0x7fff) #define SMCCC_PCI_MINOR(x) ((x) & 0xffff) #define SMCCC_PCI_SEG_END(x) (((x) >> 8) & 0xff) #define SMCCC_PCI_SEG_START(x) ((x) & 0xff) static bool pci_host_acpi_smccc_has_feature(uint32_t pci_func_id) { struct arm_smccc_res result; if (arm_smccc_invoke(SMCCC_PCI_FEATURES, pci_func_id, &result) < 0) { return (false); } return (true); } static bool pci_host_acpi_smccc_pci_version(uint32_t *versionp) { struct arm_smccc_res result; if (arm_smccc_invoke(SMCCC_PCI_VERSION, &result) < 0) { return (false); } if (versionp != NULL) { *versionp = result.a0; } return (true); } static int pci_host_acpi_smccc_attach(device_t dev) { struct generic_pcie_acpi_softc *sc; struct arm_smccc_res result; uint32_t version; int end, start; int error; sc = device_get_softc(dev); sc->base.quirks |= PCIE_CUSTOM_CONFIG_SPACE_QUIRK; MPASS(psci_callfn != NULL); /* Read the version */ if (!pci_host_acpi_smccc_pci_version(&version)) { device_printf(dev, "Failed to read the SMCCC PCI version\n"); return (ENXIO); } if (bootverbose) { device_printf(dev, "Firmware v%d.%d\n", SMCCC_PCI_MAJOR(version), SMCCC_PCI_MINOR(version)); } if (!pci_host_acpi_smccc_has_feature(SMCCC_PCI_READ) || !pci_host_acpi_smccc_has_feature(SMCCC_PCI_WRITE)) { device_printf(dev, "Missing read/write functions\n"); return (ENXIO); } error = pci_host_generic_acpi_init(dev); if (error != 0) return (error); if (pci_host_acpi_smccc_has_feature(SMCCC_PCI_GET_SEG_INFO) && arm_smccc_invoke(SMCCC_PCI_GET_SEG_INFO, sc->base.ecam, &result) == SMCCC_RET_SUCCESS) { start = SMCCC_PCI_SEG_START(result.a1); end = SMCCC_PCI_SEG_END(result.a1); sc->base.bus_start = MAX(sc->base.bus_start, start); sc->base.bus_end = MIN(sc->base.bus_end, end); } - device_add_child(dev, "pci", -1); + device_add_child(dev, "pci", DEVICE_UNIT_ANY); bus_attach_children(dev); return (0); } static uint32_t pci_host_acpi_smccc_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int bytes) { struct generic_pcie_acpi_softc *sc; struct arm_smccc_res result; uint32_t addr; sc = device_get_softc(dev); if ((bus < sc->base.bus_start) || (bus > sc->base.bus_end)) return (~0U); if ((slot > PCI_SLOTMAX) || (func > PCI_FUNCMAX) || (reg > PCIE_REGMAX)) return (~0U); addr = (sc->base.ecam << 16) | (bus << 8) | (slot << 3) | (func << 0); if (arm_smccc_invoke(SMCCC_PCI_READ, addr, reg, bytes, &result) < 0) { return (~0U); } return (result.a1); } static void pci_host_acpi_smccc_write_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, uint32_t val, int bytes) { struct generic_pcie_acpi_softc *sc; struct arm_smccc_res result; uint32_t addr; sc = device_get_softc(dev); if ((bus < sc->base.bus_start) || (bus > sc->base.bus_end)) return; if ((slot > PCI_SLOTMAX) || (func > PCI_FUNCMAX) || (reg > PCIE_REGMAX)) return; addr = (sc->base.ecam << 16) | (bus << 8) | (slot << 3) | (func << 0); arm_smccc_invoke(SMCCC_PCI_WRITE, addr, reg, bytes, val, &result); } static device_method_t generic_pcie_acpi_smccc_methods[] = { DEVMETHOD(device_probe, pci_host_acpi_smccc_probe), DEVMETHOD(device_attach, pci_host_acpi_smccc_attach), /* pcib interface */ DEVMETHOD(pcib_read_config, pci_host_acpi_smccc_read_config), DEVMETHOD(pcib_write_config, pci_host_acpi_smccc_write_config), DEVMETHOD_END }; DEFINE_CLASS_1(pcib, generic_pcie_acpi_smccc_driver, generic_pcie_acpi_smccc_methods, sizeof(struct generic_pcie_acpi_softc), generic_pcie_acpi_driver); DRIVER_MODULE(pcib_smccc, acpi, generic_pcie_acpi_smccc_driver, 0, 0); diff --git a/sys/dev/ppbus/if_plip.c b/sys/dev/ppbus/if_plip.c index 598c0c49b82e..16139139c0e7 100644 --- a/sys/dev/ppbus/if_plip.c +++ b/sys/dev/ppbus/if_plip.c @@ -1,839 +1,839 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997 Poul-Henning Kamp * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * * From Id: lpt.c,v 1.55.2.1 1996/11/12 09:08:38 phk Exp */ #include /* * Parallel port TCP/IP interfaces added. I looked at the driver from * MACH but this is a complete rewrite, and btw. incompatible, and it * should perform better too. I have never run the MACH driver though. * * This driver sends two bytes (0x08, 0x00) in front of each packet, * to allow us to distinguish another format later. * * Now added a Linux/Crynwr compatibility mode which is enabled using * IF_LINK0 - Tim Wilkinson. * * TODO: * Make HDLC/PPP mode, use IF_LLC1 to enable. * * Connect the two computers using a Laplink parallel cable to use this * feature: * * +----------------------------------------+ * |A-name A-End B-End Descr. Port/Bit | * +----------------------------------------+ * |DATA0 2 15 Data 0/0x01 | * |-ERROR 15 2 1/0x08 | * +----------------------------------------+ * |DATA1 3 13 Data 0/0x02 | * |+SLCT 13 3 1/0x10 | * +----------------------------------------+ * |DATA2 4 12 Data 0/0x04 | * |+PE 12 4 1/0x20 | * +----------------------------------------+ * |DATA3 5 10 Strobe 0/0x08 | * |-ACK 10 5 1/0x40 | * +----------------------------------------+ * |DATA4 6 11 Data 0/0x10 | * |BUSY 11 6 1/~0x80 | * +----------------------------------------+ * |GND 18-25 18-25 GND - | * +----------------------------------------+ * * Expect transfer-rates up to 75 kbyte/sec. * * If GCC could correctly grok * register int port asm("edx") * the code would be cleaner * * Poul-Henning Kamp */ /* * Update for ppbus, PLIP support only - Nicolas Souchu */ #include "opt_plip.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ppbus_if.h" #include #ifndef LPMTU /* MTU for the lp# interfaces */ #define LPMTU 1500 #endif #ifndef LPMAXSPIN1 /* DELAY factor for the lp# interfaces */ #define LPMAXSPIN1 8000 /* Spinning for remote intr to happen */ #endif #ifndef LPMAXSPIN2 /* DELAY factor for the lp# interfaces */ #define LPMAXSPIN2 500 /* Spinning for remote handshake to happen */ #endif #ifndef LPMAXERRS /* Max errors before !RUNNING */ #define LPMAXERRS 100 #endif #define CLPIPHDRLEN 14 /* We send dummy ethernet addresses (two) + packet type in front of packet */ #define CLPIP_SHAKE 0x80 /* This bit toggles between nibble reception */ #define MLPIPHDRLEN CLPIPHDRLEN #define LPIPHDRLEN 2 /* We send 0x08, 0x00 in front of packet */ #define LPIP_SHAKE 0x40 /* This bit toggles between nibble reception */ #if !defined(MLPIPHDRLEN) || LPIPHDRLEN > MLPIPHDRLEN #define MLPIPHDRLEN LPIPHDRLEN #endif #define LPIPTBLSIZE 256 /* Size of octet translation table */ #define lprintf if (lptflag) printf #ifdef PLIP_DEBUG static int volatile lptflag = 1; #else static int volatile lptflag = 0; #endif struct lp_data { struct ifnet *sc_ifp; device_t sc_dev; u_char *sc_ifbuf; int sc_iferrs; struct resource *res_irq; void *sc_intr_cookie; }; static struct mtx lp_tables_lock; MTX_SYSINIT(lp_tables, &lp_tables_lock, "plip tables", MTX_DEF); /* Tables for the lp# interface */ static u_char *txmith; #define txmitl (txmith + (1 * LPIPTBLSIZE)) #define trecvh (txmith + (2 * LPIPTBLSIZE)) #define trecvl (txmith + (3 * LPIPTBLSIZE)) static u_char *ctxmith; #define ctxmitl (ctxmith + (1 * LPIPTBLSIZE)) #define ctrecvh (ctxmith + (2 * LPIPTBLSIZE)) #define ctrecvl (ctxmith + (3 * LPIPTBLSIZE)) /* Functions for the lp# interface */ static int lpinittables(void); static int lpioctl(if_t, u_long, caddr_t); static int lpoutput(if_t, struct mbuf *, const struct sockaddr *, struct route *); static void lpstop(struct lp_data *); static void lp_intr(void *); static int lp_module_handler(module_t, int, void *); #define DEVTOSOFTC(dev) \ ((struct lp_data *)device_get_softc(dev)) static int lp_module_handler(module_t mod, int what, void *arg) { switch (what) { case MOD_UNLOAD: mtx_lock(&lp_tables_lock); if (txmith != NULL) { free(txmith, M_DEVBUF); txmith = NULL; } if (ctxmith != NULL) { free(ctxmith, M_DEVBUF); ctxmith = NULL; } mtx_unlock(&lp_tables_lock); break; case MOD_LOAD: case MOD_QUIESCE: break; default: return (EOPNOTSUPP); } return (0); } static void lp_identify(driver_t *driver, device_t parent) { device_t dev; - dev = device_find_child(parent, "plip", -1); + dev = device_find_child(parent, "plip", DEVICE_UNIT_ANY); if (!dev) BUS_ADD_CHILD(parent, 0, "plip", DEVICE_UNIT_ANY); } static int lp_probe(device_t dev) { device_set_desc(dev, "PLIP network interface"); return (0); } static int lp_attach(device_t dev) { struct lp_data *lp = DEVTOSOFTC(dev); if_t ifp; int error, rid = 0; lp->sc_dev = dev; /* * Reserve the interrupt resource. If we don't have one, the * attach fails. */ lp->res_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE); if (lp->res_irq == NULL) { device_printf(dev, "cannot reserve interrupt, failed.\n"); return (ENXIO); } ifp = lp->sc_ifp = if_alloc(IFT_PARA); if_setsoftc(ifp, lp); if_initname(ifp, device_get_name(dev), device_get_unit(dev)); if_setmtu(ifp, LPMTU); if_setflags(ifp, IFF_SIMPLEX | IFF_POINTOPOINT | IFF_MULTICAST); if_setioctlfn(ifp, lpioctl); if_setoutputfn(ifp, lpoutput); if_setsendqlen(ifp, ifqmaxlen); if_attach(ifp); bpfattach(ifp, DLT_NULL, sizeof(u_int32_t)); /* * Attach our interrupt handler. It is only called while we * own the ppbus. */ error = bus_setup_intr(dev, lp->res_irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, lp_intr, lp, &lp->sc_intr_cookie); if (error) { bpfdetach(ifp); if_detach(ifp); bus_release_resource(dev, SYS_RES_IRQ, 0, lp->res_irq); device_printf(dev, "Unable to register interrupt handler\n"); return (error); } return (0); } static int lp_detach(device_t dev) { struct lp_data *sc = device_get_softc(dev); device_t ppbus = device_get_parent(dev); ppb_lock(ppbus); lpstop(sc); ppb_unlock(ppbus); bpfdetach(sc->sc_ifp); if_detach(sc->sc_ifp); bus_teardown_intr(dev, sc->res_irq, sc->sc_intr_cookie); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->res_irq); return (0); } /* * Build the translation tables for the LPIP (BSD unix) protocol. * We don't want to calculate these nasties in our tight loop, so we * precalculate them when we initialize. */ static int lpinittables(void) { int i; mtx_lock(&lp_tables_lock); if (txmith == NULL) txmith = malloc(4 * LPIPTBLSIZE, M_DEVBUF, M_NOWAIT); if (txmith == NULL) { mtx_unlock(&lp_tables_lock); return (1); } if (ctxmith == NULL) ctxmith = malloc(4 * LPIPTBLSIZE, M_DEVBUF, M_NOWAIT); if (ctxmith == NULL) { mtx_unlock(&lp_tables_lock); return (1); } for (i = 0; i < LPIPTBLSIZE; i++) { ctxmith[i] = (i & 0xF0) >> 4; ctxmitl[i] = 0x10 | (i & 0x0F); ctrecvh[i] = (i & 0x78) << 1; ctrecvl[i] = (i & 0x78) >> 3; } for (i = 0; i < LPIPTBLSIZE; i++) { txmith[i] = ((i & 0x80) >> 3) | ((i & 0x70) >> 4) | 0x08; txmitl[i] = ((i & 0x08) << 1) | (i & 0x07); trecvh[i] = ((~i) & 0x80) | ((i & 0x38) << 1); trecvl[i] = (((~i) & 0x80) >> 4) | ((i & 0x38) >> 3); } mtx_unlock(&lp_tables_lock); return (0); } static void lpstop(struct lp_data *sc) { device_t ppbus = device_get_parent(sc->sc_dev); ppb_assert_locked(ppbus); ppb_wctr(ppbus, 0x00); if_setdrvflagbits(sc->sc_ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)); free(sc->sc_ifbuf, M_DEVBUF); sc->sc_ifbuf = NULL; /* IFF_UP is not set, try to release the bus anyway */ ppb_release_bus(ppbus, sc->sc_dev); } static int lpinit_locked(if_t ifp) { struct lp_data *sc = if_getsoftc(ifp); device_t dev = sc->sc_dev; device_t ppbus = device_get_parent(dev); int error; ppb_assert_locked(ppbus); error = ppb_request_bus(ppbus, dev, PPB_DONTWAIT); if (error) return (error); /* Now IFF_UP means that we own the bus */ ppb_set_mode(ppbus, PPB_COMPATIBLE); if (lpinittables()) { ppb_release_bus(ppbus, dev); return (ENOBUFS); } sc->sc_ifbuf = malloc(if_getmtu(sc->sc_ifp) + MLPIPHDRLEN, M_DEVBUF, M_NOWAIT); if (sc->sc_ifbuf == NULL) { ppb_release_bus(ppbus, dev); return (ENOBUFS); } ppb_wctr(ppbus, IRQENABLE); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0); if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); return (0); } /* * Process an ioctl request. */ static int lpioctl(if_t ifp, u_long cmd, caddr_t data) { struct lp_data *sc = if_getsoftc(ifp); device_t dev = sc->sc_dev; device_t ppbus = device_get_parent(dev); struct ifaddr *ifa = (struct ifaddr *)data; struct ifreq *ifr = (struct ifreq *)data; u_char *ptr; int error; switch (cmd) { case SIOCAIFADDR: case SIOCSIFADDR: if (ifa->ifa_addr->sa_family != AF_INET) return (EAFNOSUPPORT); if_setflagbits(ifp, IFF_UP, 0); /* FALLTHROUGH */ case SIOCSIFFLAGS: error = 0; ppb_lock(ppbus); if ((!(if_getflags(ifp) & IFF_UP)) && (if_getdrvflags(ifp) & IFF_DRV_RUNNING)) lpstop(sc); else if (((if_getflags(ifp) & IFF_UP)) && (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING))) error = lpinit_locked(ifp); ppb_unlock(ppbus); return (error); case SIOCSIFMTU: ppb_lock(ppbus); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { ptr = malloc(ifr->ifr_mtu + MLPIPHDRLEN, M_DEVBUF, M_NOWAIT); if (ptr == NULL) { ppb_unlock(ppbus); return (ENOBUFS); } if (sc->sc_ifbuf) free(sc->sc_ifbuf, M_DEVBUF); sc->sc_ifbuf = ptr; } if_setmtu(ifp, ifr->ifr_mtu); ppb_unlock(ppbus); break; case SIOCGIFMTU: ifr->ifr_mtu = if_getmtu(sc->sc_ifp); break; case SIOCADDMULTI: case SIOCDELMULTI: if (ifr == NULL) { return (EAFNOSUPPORT); /* XXX */ } switch (ifr->ifr_addr.sa_family) { case AF_INET: break; default: return (EAFNOSUPPORT); } break; case SIOCGIFMEDIA: /* * No ifmedia support at this stage; maybe use it * in future for eg. protocol selection. */ return (EINVAL); default: lprintf("LP:ioctl(0x%lx)\n", cmd); return (EINVAL); } return (0); } static __inline int clpoutbyte(u_char byte, int spin, device_t ppbus) { ppb_wdtr(ppbus, ctxmitl[byte]); while (ppb_rstr(ppbus) & CLPIP_SHAKE) if (--spin == 0) { return (1); } ppb_wdtr(ppbus, ctxmith[byte]); while (!(ppb_rstr(ppbus) & CLPIP_SHAKE)) if (--spin == 0) { return (1); } return (0); } static __inline int clpinbyte(int spin, device_t ppbus) { u_char c, cl; while ((ppb_rstr(ppbus) & CLPIP_SHAKE)) if (!--spin) { return (-1); } cl = ppb_rstr(ppbus); ppb_wdtr(ppbus, 0x10); while (!(ppb_rstr(ppbus) & CLPIP_SHAKE)) if (!--spin) { return (-1); } c = ppb_rstr(ppbus); ppb_wdtr(ppbus, 0x00); return (ctrecvl[cl] | ctrecvh[c]); } static void lptap(if_t ifp, struct mbuf *m) { u_int32_t af = AF_INET; bpf_mtap2_if(ifp, &af, sizeof(af), m); } static void lp_intr(void *arg) { struct lp_data *sc = arg; device_t ppbus = device_get_parent(sc->sc_dev); int len, j; u_char *bp; u_char c, cl; struct mbuf *top; ppb_assert_locked(ppbus); if (if_getflags(sc->sc_ifp) & IFF_LINK0) { /* Ack. the request */ ppb_wdtr(ppbus, 0x01); /* Get the packet length */ j = clpinbyte(LPMAXSPIN2, ppbus); if (j == -1) goto err; len = j; j = clpinbyte(LPMAXSPIN2, ppbus); if (j == -1) goto err; len = len + (j << 8); if (len > if_getmtu(sc->sc_ifp) + MLPIPHDRLEN) goto err; bp = sc->sc_ifbuf; while (len--) { j = clpinbyte(LPMAXSPIN2, ppbus); if (j == -1) { goto err; } *bp++ = j; } /* Get and ignore checksum */ j = clpinbyte(LPMAXSPIN2, ppbus); if (j == -1) { goto err; } len = bp - sc->sc_ifbuf; if (len <= CLPIPHDRLEN) goto err; sc->sc_iferrs = 0; len -= CLPIPHDRLEN; if_inc_counter(sc->sc_ifp, IFCOUNTER_IPACKETS, 1); if_inc_counter(sc->sc_ifp, IFCOUNTER_IBYTES, len); top = m_devget(sc->sc_ifbuf + CLPIPHDRLEN, len, 0, sc->sc_ifp, 0); if (top) { ppb_unlock(ppbus); lptap(sc->sc_ifp, top); M_SETFIB(top, if_getfib(sc->sc_ifp)); /* mbuf is free'd on failure. */ netisr_queue(NETISR_IP, top); ppb_lock(ppbus); } return; } while ((ppb_rstr(ppbus) & LPIP_SHAKE)) { len = if_getmtu(sc->sc_ifp) + LPIPHDRLEN; bp = sc->sc_ifbuf; while (len--) { cl = ppb_rstr(ppbus); ppb_wdtr(ppbus, 8); j = LPMAXSPIN2; while ((ppb_rstr(ppbus) & LPIP_SHAKE)) if (!--j) goto err; c = ppb_rstr(ppbus); ppb_wdtr(ppbus, 0); *bp++= trecvh[cl] | trecvl[c]; j = LPMAXSPIN2; while (!((cl = ppb_rstr(ppbus)) & LPIP_SHAKE)) { if (cl != c && (((cl = ppb_rstr(ppbus)) ^ 0xb8) & 0xf8) == (c & 0xf8)) goto end; if (!--j) goto err; } } end: len = bp - sc->sc_ifbuf; if (len <= LPIPHDRLEN) goto err; sc->sc_iferrs = 0; len -= LPIPHDRLEN; if_inc_counter(sc->sc_ifp, IFCOUNTER_IPACKETS, 1); if_inc_counter(sc->sc_ifp, IFCOUNTER_IBYTES, len); top = m_devget(sc->sc_ifbuf + LPIPHDRLEN, len, 0, sc->sc_ifp, 0); if (top) { ppb_unlock(ppbus); lptap(sc->sc_ifp, top); M_SETFIB(top, if_getfib(sc->sc_ifp)); /* mbuf is free'd on failure. */ netisr_queue(NETISR_IP, top); ppb_lock(ppbus); } } return; err: ppb_wdtr(ppbus, 0); lprintf("R"); if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1); sc->sc_iferrs++; /* * We are not able to send receive anything for now, * so stop wasting our time */ if (sc->sc_iferrs > LPMAXERRS) { if_printf(sc->sc_ifp, "Too many errors, Going off-line.\n"); ppb_wctr(ppbus, 0x00); if_setdrvflagbits(sc->sc_ifp, 0, IFF_DRV_RUNNING); sc->sc_iferrs = 0; } } static __inline int lpoutbyte(u_char byte, int spin, device_t ppbus) { ppb_wdtr(ppbus, txmith[byte]); while (!(ppb_rstr(ppbus) & LPIP_SHAKE)) if (--spin == 0) return (1); ppb_wdtr(ppbus, txmitl[byte]); while (ppb_rstr(ppbus) & LPIP_SHAKE) if (--spin == 0) return (1); return (0); } static int lpoutput(if_t ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro) { struct lp_data *sc = if_getsoftc(ifp); device_t dev = sc->sc_dev; device_t ppbus = device_get_parent(dev); int err; struct mbuf *mm; u_char *cp = "\0\0"; u_char chksum = 0; int count = 0; int i, len, spin; /* We need a sensible value if we abort */ cp++; ppb_lock(ppbus); if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0); err = 1; /* assume we're aborting because of an error */ /* Suspend (on laptops) or receive-errors might have taken us offline */ ppb_wctr(ppbus, IRQENABLE); if (if_getflags(ifp) & IFF_LINK0) { if (!(ppb_rstr(ppbus) & CLPIP_SHAKE)) { lprintf("&"); lp_intr(sc); } /* Alert other end to pending packet */ spin = LPMAXSPIN1; ppb_wdtr(ppbus, 0x08); while ((ppb_rstr(ppbus) & 0x08) == 0) if (--spin == 0) { goto nend; } /* Calculate length of packet, then send that */ count += 14; /* Ethernet header len */ mm = m; for (mm = m; mm; mm = mm->m_next) { count += mm->m_len; } if (clpoutbyte(count & 0xFF, LPMAXSPIN1, ppbus)) goto nend; if (clpoutbyte((count >> 8) & 0xFF, LPMAXSPIN1, ppbus)) goto nend; /* Send dummy ethernet header */ for (i = 0; i < 12; i++) { if (clpoutbyte(i, LPMAXSPIN1, ppbus)) goto nend; chksum += i; } if (clpoutbyte(0x08, LPMAXSPIN1, ppbus)) goto nend; if (clpoutbyte(0x00, LPMAXSPIN1, ppbus)) goto nend; chksum += 0x08 + 0x00; /* Add into checksum */ mm = m; do { cp = mtod(mm, u_char *); len = mm->m_len; while (len--) { chksum += *cp; if (clpoutbyte(*cp++, LPMAXSPIN2, ppbus)) goto nend; } } while ((mm = mm->m_next)); /* Send checksum */ if (clpoutbyte(chksum, LPMAXSPIN2, ppbus)) goto nend; /* Go quiescent */ ppb_wdtr(ppbus, 0); err = 0; /* No errors */ nend: if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); if (err) { /* if we didn't timeout... */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); lprintf("X"); } else { if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len); lptap(ifp, m); } m_freem(m); if (!(ppb_rstr(ppbus) & CLPIP_SHAKE)) { lprintf("^"); lp_intr(sc); } ppb_unlock(ppbus); return (0); } if (ppb_rstr(ppbus) & LPIP_SHAKE) { lprintf("&"); lp_intr(sc); } if (lpoutbyte(0x08, LPMAXSPIN1, ppbus)) goto end; if (lpoutbyte(0x00, LPMAXSPIN2, ppbus)) goto end; mm = m; do { cp = mtod(mm, u_char *); len = mm->m_len; while (len--) if (lpoutbyte(*cp++, LPMAXSPIN2, ppbus)) goto end; } while ((mm = mm->m_next)); err = 0; /* no errors were encountered */ end: --cp; ppb_wdtr(ppbus, txmitl[*cp] ^ 0x17); if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); if (err) { /* if we didn't timeout... */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); lprintf("X"); } else { if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len); lptap(ifp, m); } m_freem(m); if (ppb_rstr(ppbus) & LPIP_SHAKE) { lprintf("^"); lp_intr(sc); } ppb_unlock(ppbus); return (0); } static device_method_t lp_methods[] = { /* device interface */ DEVMETHOD(device_identify, lp_identify), DEVMETHOD(device_probe, lp_probe), DEVMETHOD(device_attach, lp_attach), DEVMETHOD(device_detach, lp_detach), { 0, 0 } }; static driver_t lp_driver = { "plip", lp_methods, sizeof(struct lp_data), }; DRIVER_MODULE(plip, ppbus, lp_driver, lp_module_handler, NULL); MODULE_DEPEND(plip, ppbus, 1, 1, 1); diff --git a/sys/dev/ppbus/lpbb.c b/sys/dev/ppbus/lpbb.c index 3380cdfdaed4..3d2253ed9378 100644 --- a/sys/dev/ppbus/lpbb.c +++ b/sys/dev/ppbus/lpbb.c @@ -1,266 +1,266 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998, 2001 Nicolas Souchu, Marc Bouget * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * * */ #include /* * I2C Bit-Banging over parallel port * * See the Official Philips interface description in lpbb(4) */ #include #include #include #include #include #include #include #include #include #include "ppbus_if.h" #include #include #include #include "iicbb_if.h" static int lpbb_detect(device_t dev); static void lpbb_identify(driver_t *driver, device_t parent) { device_t dev; - dev = device_find_child(parent, "lpbb", -1); + dev = device_find_child(parent, "lpbb", DEVICE_UNIT_ANY); if (!dev) BUS_ADD_CHILD(parent, 0, "lpbb", DEVICE_UNIT_ANY); } static int lpbb_probe(device_t dev) { /* Perhaps call this during identify instead? */ if (!lpbb_detect(dev)) return (ENXIO); device_set_desc(dev, "Parallel I2C bit-banging interface"); return (0); } static int lpbb_attach(device_t dev) { device_t bitbang; /* add generic bit-banging code */ bitbang = device_add_child(dev, "iicbb", DEVICE_UNIT_ANY); device_probe_and_attach(bitbang); return (0); } static int lpbb_callback(device_t dev, int index, caddr_t data) { device_t ppbus = device_get_parent(dev); int error = 0; int how; switch (index) { case IIC_REQUEST_BUS: /* request the ppbus */ how = *(int *)data; ppb_lock(ppbus); error = ppb_request_bus(ppbus, dev, how); ppb_unlock(ppbus); break; case IIC_RELEASE_BUS: /* release the ppbus */ ppb_lock(ppbus); error = ppb_release_bus(ppbus, dev); ppb_unlock(ppbus); break; default: error = EINVAL; } return (error); } #define SDA_out 0x80 #define SCL_out 0x08 #define SDA_in 0x80 #define SCL_in 0x08 #define ALIM 0x20 #define I2CKEY 0x50 /* Reset bus by setting SDA first and then SCL. */ static void lpbb_reset_bus(device_t dev) { device_t ppbus = device_get_parent(dev); ppb_assert_locked(ppbus); ppb_wdtr(ppbus, (u_char)~SDA_out); ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus) | SCL_out)); } static int lpbb_getscl(device_t dev) { device_t ppbus = device_get_parent(dev); int rval; ppb_lock(ppbus); rval = ((ppb_rstr(ppbus) & SCL_in) == SCL_in); ppb_unlock(ppbus); return (rval); } static int lpbb_getsda(device_t dev) { device_t ppbus = device_get_parent(dev); int rval; ppb_lock(ppbus); rval = ((ppb_rstr(ppbus) & SDA_in) == SDA_in); ppb_unlock(ppbus); return (rval); } static void lpbb_setsda(device_t dev, int val) { device_t ppbus = device_get_parent(dev); ppb_lock(ppbus); if (val == 0) ppb_wdtr(ppbus, (u_char)SDA_out); else ppb_wdtr(ppbus, (u_char)~SDA_out); ppb_unlock(ppbus); } static void lpbb_setscl(device_t dev, int val) { device_t ppbus = device_get_parent(dev); ppb_lock(ppbus); if (val == 0) ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus) & ~SCL_out)); else ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus) | SCL_out)); ppb_unlock(ppbus); } static int lpbb_detect(device_t dev) { device_t ppbus = device_get_parent(dev); ppb_lock(ppbus); if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) { ppb_unlock(ppbus); device_printf(dev, "can't allocate ppbus\n"); return (0); } lpbb_reset_bus(dev); if ((ppb_rstr(ppbus) & I2CKEY) || ((ppb_rstr(ppbus) & ALIM) != ALIM)) { ppb_release_bus(ppbus, dev); ppb_unlock(ppbus); return (0); } ppb_release_bus(ppbus, dev); ppb_unlock(ppbus); return (1); } static int lpbb_reset(device_t dev, u_char speed, u_char addr, u_char * oldaddr) { device_t ppbus = device_get_parent(dev); ppb_lock(ppbus); if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) { ppb_unlock(ppbus); device_printf(dev, "can't allocate ppbus\n"); return (0); } lpbb_reset_bus(dev); ppb_release_bus(ppbus, dev); ppb_unlock(ppbus); return (IIC_ENOADDR); } static device_method_t lpbb_methods[] = { /* device interface */ DEVMETHOD(device_identify, lpbb_identify), DEVMETHOD(device_probe, lpbb_probe), DEVMETHOD(device_attach, lpbb_attach), /* iicbb interface */ DEVMETHOD(iicbb_callback, lpbb_callback), DEVMETHOD(iicbb_setsda, lpbb_setsda), DEVMETHOD(iicbb_setscl, lpbb_setscl), DEVMETHOD(iicbb_getsda, lpbb_getsda), DEVMETHOD(iicbb_getscl, lpbb_getscl), DEVMETHOD(iicbb_reset, lpbb_reset), DEVMETHOD_END }; static driver_t lpbb_driver = { "lpbb", lpbb_methods, 1, }; DRIVER_MODULE(lpbb, ppbus, lpbb_driver, 0, 0); DRIVER_MODULE(iicbb, lpbb, iicbb_driver, 0, 0); MODULE_DEPEND(lpbb, ppbus, 1, 1, 1); MODULE_DEPEND(lpbb, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER); MODULE_VERSION(lpbb, 1); diff --git a/sys/dev/ppbus/lpt.c b/sys/dev/ppbus/lpt.c index 97e8c59f9282..401e94d25727 100644 --- a/sys/dev/ppbus/lpt.c +++ b/sys/dev/ppbus/lpt.c @@ -1,991 +1,991 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1990 William F. Jolitz, TeleMuse * 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, 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This software is a component of "386BSD" developed by * William F. Jolitz, TeleMuse. * 4. Neither the name of the developer nor the name "386BSD" * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS A COMPONENT OF 386BSD DEVELOPED BY WILLIAM F. JOLITZ * AND IS INTENDED FOR RESEARCH AND EDUCATIONAL PURPOSES ONLY. THIS * SOFTWARE SHOULD NOT BE CONSIDERED TO BE A COMMERCIAL PRODUCT. * THE DEVELOPER URGES THAT USERS WHO REQUIRE A COMMERCIAL PRODUCT * NOT MAKE USE OF THIS WORK. * * FOR USERS WHO WISH TO UNDERSTAND THE 386BSD SYSTEM DEVELOPED * BY WILLIAM F. JOLITZ, WE RECOMMEND THE USER STUDY WRITTEN * REFERENCES SUCH AS THE "PORTING UNIX TO THE 386" SERIES * (BEGINNING JANUARY 1991 "DR. DOBBS JOURNAL", USA AND BEGINNING * JUNE 1991 "UNIX MAGAZIN", GERMANY) BY WILLIAM F. JOLITZ AND * LYNNE GREER JOLITZ, AS WELL AS OTHER BOOKS ON UNIX AND THE * ON-LINE 386BSD USER MANUAL BEFORE USE. A BOOK DISCUSSING THE INTERNALS * OF 386BSD ENTITLED "386BSD FROM THE INSIDE OUT" WILL BE AVAILABLE LATE 1992. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER ``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 DEVELOPER 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. * * from: unknown origin, 386BSD 0.1 * From Id: lpt.c,v 1.55.2.1 1996/11/12 09:08:38 phk Exp * From Id: nlpt.c,v 1.14 1999/02/08 13:55:43 des Exp */ #include /* * Device Driver for AT parallel printer port * Written by William Jolitz 12/18/90 */ /* * Updated for ppbus by Nicolas Souchu * [Mon Jul 28 1997] */ #include "opt_lpt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ppbus_if.h" #include #ifndef LPT_DEBUG #define lprintf(args) #else #define lprintf(args) \ do { \ if (lptflag) \ printf args; \ } while (0) static int volatile lptflag = 1; #endif #define LPINITRDY 4 /* wait up to 4 seconds for a ready */ #define LPTOUTINITIAL 10 /* initial timeout to wait for ready 1/10 s */ #define LPTOUTMAX 1 /* maximal timeout 1 s */ #define LPPRI (PWAIT) #define BUFSIZE 1024 #define BUFSTATSIZE 32 struct lpt_data { device_t sc_dev; struct cdev *sc_cdev; struct cdev *sc_cdev_bypass; short sc_state; /* default case: negative prime, negative ack, handshake strobe, prime once */ u_char sc_control; char sc_flags; #define LP_POS_INIT 0x04 /* if we are a positive init signal */ #define LP_POS_ACK 0x08 /* if we are a positive going ack */ #define LP_NO_PRIME 0x10 /* don't prime the printer at all */ #define LP_PRIMEOPEN 0x20 /* prime on every open */ #define LP_AUTOLF 0x40 /* tell printer to do an automatic lf */ #define LP_BYPASS 0x80 /* bypass printer ready checks */ void *sc_inbuf; void *sc_statbuf; short sc_xfercnt ; char sc_primed; char *sc_cp ; u_short sc_irq ; /* IRQ status of port */ #define LP_HAS_IRQ 0x01 /* we have an irq available */ #define LP_USE_IRQ 0x02 /* we are using our irq */ #define LP_ENABLE_IRQ 0x04 /* enable IRQ on open */ #define LP_ENABLE_EXT 0x10 /* we shall use advanced mode when possible */ u_char sc_backoff ; /* time to call lptout() again */ struct callout sc_timer; struct resource *sc_intr_resource; /* interrupt resource */ void *sc_intr_cookie; /* interrupt cookie */ }; #define LPT_NAME "lpt" /* our official name */ static callout_func_t lptout; static int lpt_port_test(device_t dev, u_char data, u_char mask); static int lpt_detect(device_t dev); #define DEVTOSOFTC(dev) \ ((struct lpt_data *)device_get_softc(dev)) static void lptintr(void *arg); /* bits for state */ #define OPEN (1<<0) /* device is open */ #define ASLP (1<<1) /* awaiting draining of printer */ #define EERROR (1<<2) /* error was received from printer */ #define OBUSY (1<<3) /* printer is busy doing output */ #define LPTOUT (1<<4) /* timeout while not selected */ #define TOUT (1<<5) /* timeout while not selected */ #define LPTINIT (1<<6) /* waiting to initialize for open */ #define INTERRUPTED (1<<7) /* write call was interrupted */ #define HAVEBUS (1<<8) /* the driver owns the bus */ /* status masks to interrogate printer status */ #define RDY_MASK (LPS_SEL|LPS_OUT|LPS_NBSY|LPS_NERR) /* ready ? */ #define LP_READY (LPS_SEL|LPS_NBSY|LPS_NERR) /* Printer Ready condition - from lpa.c */ /* Only used in polling code */ #define LPS_INVERT (LPS_NBSY | LPS_NACK | LPS_SEL | LPS_NERR) #define LPS_MASK (LPS_NBSY | LPS_NACK | LPS_OUT | LPS_SEL | LPS_NERR) #define NOT_READY(ppbus) ((ppb_rstr(ppbus)^LPS_INVERT)&LPS_MASK) #define MAX_SLEEP (hz*5) /* Timeout while waiting for device ready */ #define MAX_SPIN 20 /* Max delay for device ready in usecs */ static d_open_t lptopen; static d_close_t lptclose; static d_write_t lptwrite; static d_read_t lptread; static d_ioctl_t lptioctl; static struct cdevsw lpt_cdevsw = { .d_version = D_VERSION, .d_open = lptopen, .d_close = lptclose, .d_read = lptread, .d_write = lptwrite, .d_ioctl = lptioctl, .d_name = LPT_NAME, }; static int lpt_request_ppbus(device_t dev, int how) { device_t ppbus = device_get_parent(dev); struct lpt_data *sc = DEVTOSOFTC(dev); int error; /* * We might already have the bus for a write(2) after an interrupted * write(2) call. */ ppb_assert_locked(ppbus); if (sc->sc_state & HAVEBUS) return (0); error = ppb_request_bus(ppbus, dev, how); if (error == 0) sc->sc_state |= HAVEBUS; return (error); } static int lpt_release_ppbus(device_t dev) { device_t ppbus = device_get_parent(dev); struct lpt_data *sc = DEVTOSOFTC(dev); int error = 0; ppb_assert_locked(ppbus); if (sc->sc_state & HAVEBUS) { error = ppb_release_bus(ppbus, dev); if (error == 0) sc->sc_state &= ~HAVEBUS; } return (error); } /* * Internal routine to lptprobe to do port tests of one byte value */ static int lpt_port_test(device_t ppbus, u_char data, u_char mask) { int temp, timeout; data = data & mask; ppb_wdtr(ppbus, data); timeout = 10000; do { DELAY(10); temp = ppb_rdtr(ppbus) & mask; } while (temp != data && --timeout); lprintf(("out=%x\tin=%x\ttout=%d\n", data, temp, timeout)); return (temp == data); } /* * Probe simplified by replacing multiple loops with a hardcoded * test pattern - 1999/02/08 des@freebsd.org * * New lpt port probe Geoff Rehmet - Rhodes University - 14/2/94 * Based partially on Rod Grimes' printer probe * * Logic: * 1) If no port address was given, use the bios detected ports * and autodetect what ports the printers are on. * 2) Otherwise, probe the data port at the address given, * using the method in Rod Grimes' port probe. * (Much code ripped off directly from Rod's probe.) * * Comments from Rod's probe: * Logic: * 1) You should be able to write to and read back the same value * to the data port. Do an alternating zeros, alternating ones, * walking zero, and walking one test to check for stuck bits. * * 2) You should be able to write to and read back the same value * to the control port lower 5 bits, the upper 3 bits are reserved * per the IBM PC technical reference manuals and different boards * do different things with them. Do an alternating zeros, alternating * ones, walking zero, and walking one test to check for stuck bits. * * Some printers drag the strobe line down when the are powered off * so this bit has been masked out of the control port test. * * XXX Some printers may not like a fast pulse on init or strobe, I * don't know at this point, if that becomes a problem these bits * should be turned off in the mask byte for the control port test. * * We are finally left with a mask of 0x14, due to some printers * being adamant about holding other bits high ........ * * Before probing the control port, we write a 0 to the data port - * If not, some printers chuck out garbage when the strobe line * gets toggled. * * 3) Set the data and control ports to a value of 0 * * This probe routine has been tested on Epson Lx-800, HP LJ3P, * Epson FX-1170 and C.Itoh 8510RM * printers. * Quick exit on fail added. */ static int lpt_detect(device_t dev) { device_t ppbus = device_get_parent(dev); static u_char testbyte[18] = { 0x55, /* alternating zeros */ 0xaa, /* alternating ones */ 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f, /* walking zero */ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 /* walking one */ }; int i, error, status; status = 1; /* assume success */ ppb_lock(ppbus); if ((error = lpt_request_ppbus(dev, PPB_DONTWAIT))) { ppb_unlock(ppbus); device_printf(dev, "cannot alloc ppbus (%d)!\n", error); return (0); } for (i = 0; i < 18 && status; i++) if (!lpt_port_test(ppbus, testbyte[i], 0xff)) { status = 0; break; } /* write 0's to control and data ports */ ppb_wdtr(ppbus, 0); ppb_wctr(ppbus, 0); lpt_release_ppbus(dev); ppb_unlock(ppbus); return (status); } static void lpt_identify(driver_t *driver, device_t parent) { device_t dev; - dev = device_find_child(parent, LPT_NAME, -1); + dev = device_find_child(parent, LPT_NAME, DEVICE_UNIT_ANY); if (!dev) BUS_ADD_CHILD(parent, 0, LPT_NAME, DEVICE_UNIT_ANY); } /* * lpt_probe() */ static int lpt_probe(device_t dev) { if (!lpt_detect(dev)) return (ENXIO); device_set_desc(dev, "Printer"); return (0); } static int lpt_attach(device_t dev) { device_t ppbus = device_get_parent(dev); struct lpt_data *sc = DEVTOSOFTC(dev); int rid = 0, unit = device_get_unit(dev); int error; sc->sc_primed = 0; /* not primed yet */ ppb_init_callout(ppbus, &sc->sc_timer, 0); ppb_lock(ppbus); if ((error = lpt_request_ppbus(dev, PPB_DONTWAIT))) { ppb_unlock(ppbus); device_printf(dev, "cannot alloc ppbus (%d)!\n", error); return (0); } ppb_wctr(ppbus, LPC_NINIT); lpt_release_ppbus(dev); ppb_unlock(ppbus); /* declare our interrupt handler */ sc->sc_intr_resource = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE); if (sc->sc_intr_resource) { error = bus_setup_intr(dev, sc->sc_intr_resource, INTR_TYPE_TTY | INTR_MPSAFE, NULL, lptintr, sc, &sc->sc_intr_cookie); if (error) { bus_release_resource(dev, SYS_RES_IRQ, rid, sc->sc_intr_resource); device_printf(dev, "Unable to register interrupt handler\n"); return (error); } sc->sc_irq = LP_HAS_IRQ | LP_USE_IRQ | LP_ENABLE_IRQ; device_printf(dev, "Interrupt-driven port\n"); } else { sc->sc_irq = 0; device_printf(dev, "Polled port\n"); } lprintf(("irq %x\n", sc->sc_irq)); sc->sc_inbuf = malloc(BUFSIZE, M_DEVBUF, M_WAITOK); sc->sc_statbuf = malloc(BUFSTATSIZE, M_DEVBUF, M_WAITOK); sc->sc_dev = dev; sc->sc_cdev = make_dev(&lpt_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, LPT_NAME "%d", unit); sc->sc_cdev->si_drv1 = sc; sc->sc_cdev->si_drv2 = 0; sc->sc_cdev_bypass = make_dev(&lpt_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, LPT_NAME "%d.ctl", unit); sc->sc_cdev_bypass->si_drv1 = sc; sc->sc_cdev_bypass->si_drv2 = (void *)LP_BYPASS; return (0); } static int lpt_detach(device_t dev) { struct lpt_data *sc = DEVTOSOFTC(dev); device_t ppbus = device_get_parent(dev); destroy_dev(sc->sc_cdev); destroy_dev(sc->sc_cdev_bypass); ppb_lock(ppbus); lpt_release_ppbus(dev); ppb_unlock(ppbus); callout_drain(&sc->sc_timer); if (sc->sc_intr_resource != NULL) { bus_teardown_intr(dev, sc->sc_intr_resource, sc->sc_intr_cookie); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intr_resource); } free(sc->sc_inbuf, M_DEVBUF); free(sc->sc_statbuf, M_DEVBUF); return (0); } static void lptout(void *arg) { struct lpt_data *sc = arg; device_t dev = sc->sc_dev; device_t ppbus __unused; ppbus = device_get_parent(dev); ppb_assert_locked(ppbus); lprintf(("T %x ", ppb_rstr(ppbus))); if (sc->sc_state & OPEN) { sc->sc_backoff++; if (sc->sc_backoff > hz/LPTOUTMAX) sc->sc_backoff = hz/LPTOUTMAX; callout_reset(&sc->sc_timer, sc->sc_backoff, lptout, sc); } else sc->sc_state &= ~TOUT; if (sc->sc_state & EERROR) sc->sc_state &= ~EERROR; /* * Avoid possible hangs due to missed interrupts */ if (sc->sc_xfercnt) { lptintr(sc); } else { sc->sc_state &= ~OBUSY; wakeup(dev); } } /* * lptopen -- reset the printer, then wait until it's selected and not busy. * If LP_BYPASS flag is selected, then we do not try to select the * printer -- this is just used for passing ioctls. */ static int lptopen(struct cdev *dev, int flags, int fmt, struct thread *td) { int trys, err; struct lpt_data *sc = dev->si_drv1; device_t lptdev; device_t ppbus; if (!sc) return (ENXIO); lptdev = sc->sc_dev; ppbus = device_get_parent(lptdev); ppb_lock(ppbus); if (sc->sc_state) { lprintf(("%s: still open %x\n", device_get_nameunit(lptdev), sc->sc_state)); ppb_unlock(ppbus); return(EBUSY); } else sc->sc_state |= LPTINIT; sc->sc_flags = (uintptr_t)dev->si_drv2; /* Check for open with BYPASS flag set. */ if (sc->sc_flags & LP_BYPASS) { sc->sc_state = OPEN; ppb_unlock(ppbus); return(0); } /* request the ppbus only if we don't have it already */ if ((err = lpt_request_ppbus(lptdev, PPB_WAIT|PPB_INTR)) != 0) { /* give it a chance to try later */ sc->sc_state = 0; ppb_unlock(ppbus); return (err); } lprintf(("%s flags 0x%x\n", device_get_nameunit(lptdev), sc->sc_flags)); /* set IRQ status according to ENABLE_IRQ flag */ if (sc->sc_irq & LP_ENABLE_IRQ) sc->sc_irq |= LP_USE_IRQ; else sc->sc_irq &= ~LP_USE_IRQ; /* init printer */ if ((sc->sc_flags & LP_NO_PRIME) == 0) { if ((sc->sc_flags & LP_PRIMEOPEN) || sc->sc_primed == 0) { ppb_wctr(ppbus, 0); sc->sc_primed++; DELAY(500); } } ppb_wctr(ppbus, LPC_SEL|LPC_NINIT); /* wait till ready (printer running diagnostics) */ trys = 0; do { /* ran out of waiting for the printer */ if (trys++ >= LPINITRDY*4) { lprintf(("status %x\n", ppb_rstr(ppbus))); lpt_release_ppbus(lptdev); sc->sc_state = 0; ppb_unlock(ppbus); return (EBUSY); } /* wait 1/4 second, give up if we get a signal */ if (ppb_sleep(ppbus, lptdev, LPPRI | PCATCH, "lptinit", hz / 4) != EWOULDBLOCK) { lpt_release_ppbus(lptdev); sc->sc_state = 0; ppb_unlock(ppbus); return (EBUSY); } /* is printer online and ready for output */ } while ((ppb_rstr(ppbus) & RDY_MASK) != LP_READY); sc->sc_control = LPC_SEL|LPC_NINIT; if (sc->sc_flags & LP_AUTOLF) sc->sc_control |= LPC_AUTOL; /* enable interrupt if interrupt-driven */ if (sc->sc_irq & LP_USE_IRQ) sc->sc_control |= LPC_ENA; ppb_wctr(ppbus, sc->sc_control); sc->sc_state &= ~LPTINIT; sc->sc_state |= OPEN; sc->sc_xfercnt = 0; /* only use timeout if using interrupt */ lprintf(("irq %x\n", sc->sc_irq)); if (sc->sc_irq & LP_USE_IRQ) { sc->sc_state |= TOUT; sc->sc_backoff = hz / LPTOUTINITIAL; callout_reset(&sc->sc_timer, sc->sc_backoff, lptout, sc); } /* release the ppbus */ lpt_release_ppbus(lptdev); ppb_unlock(ppbus); lprintf(("opened.\n")); return(0); } /* * lptclose -- close the device, free the local line buffer. * * Check for interrupted write call added. */ static int lptclose(struct cdev *dev, int flags, int fmt, struct thread *td) { struct lpt_data *sc = dev->si_drv1; device_t lptdev = sc->sc_dev; device_t ppbus = device_get_parent(lptdev); int err; ppb_lock(ppbus); if (sc->sc_flags & LP_BYPASS) goto end_close; if ((err = lpt_request_ppbus(lptdev, PPB_WAIT|PPB_INTR)) != 0) { ppb_unlock(ppbus); return (err); } /* if the last write was interrupted, don't complete it */ if ((!(sc->sc_state & INTERRUPTED)) && (sc->sc_irq & LP_USE_IRQ)) while ((ppb_rstr(ppbus) & RDY_MASK) != LP_READY || sc->sc_xfercnt) /* wait 1 second, give up if we get a signal */ if (ppb_sleep(ppbus, lptdev, LPPRI | PCATCH, "lpclose", hz) != EWOULDBLOCK) break; sc->sc_state &= ~OPEN; callout_stop(&sc->sc_timer); ppb_wctr(ppbus, LPC_NINIT); /* * unregistration of interrupt forced by release */ lpt_release_ppbus(lptdev); end_close: sc->sc_state = 0; sc->sc_xfercnt = 0; ppb_unlock(ppbus); lprintf(("closed.\n")); return(0); } /* * lpt_pushbytes() * Workhorse for actually spinning and writing bytes to printer * Derived from lpa.c * Originally by ? * * This code is only used when we are polling the port */ static int lpt_pushbytes(struct lpt_data *sc) { device_t dev = sc->sc_dev; device_t ppbus = device_get_parent(dev); int spin, err, tic; char ch; ppb_assert_locked(ppbus); lprintf(("p")); /* loop for every character .. */ while (sc->sc_xfercnt > 0) { /* printer data */ ch = *(sc->sc_cp); sc->sc_cp++; sc->sc_xfercnt--; /* * Wait for printer ready. * Loop 20 usecs testing BUSY bit, then sleep * for exponentially increasing timeout. (vak) */ for (spin = 0; NOT_READY(ppbus) && spin < MAX_SPIN; ++spin) DELAY(1); /* XXX delay is NOT this accurate! */ if (spin >= MAX_SPIN) { tic = 0; while (NOT_READY(ppbus)) { /* * Now sleep, every cycle a * little longer .. */ tic = tic + tic + 1; /* * But no more than 10 seconds. (vak) */ if (tic > MAX_SLEEP) tic = MAX_SLEEP; err = ppb_sleep(ppbus, dev, LPPRI, LPT_NAME "poll", tic); if (err != EWOULDBLOCK) { return (err); } } } /* output data */ ppb_wdtr(ppbus, ch); /* strobe */ ppb_wctr(ppbus, sc->sc_control|LPC_STB); ppb_wctr(ppbus, sc->sc_control); } return(0); } /* * lptread --retrieve printer status in IEEE1284 NIBBLE mode */ static int lptread(struct cdev *dev, struct uio *uio, int ioflag) { struct lpt_data *sc = dev->si_drv1; device_t lptdev = sc->sc_dev; device_t ppbus = device_get_parent(lptdev); int error = 0, len; if (sc->sc_flags & LP_BYPASS) { /* we can't do reads in bypass mode */ return (EPERM); } ppb_lock(ppbus); if ((error = ppb_1284_negociate(ppbus, PPB_NIBBLE, 0))) { ppb_unlock(ppbus); return (error); } /* read data in an other buffer, read/write may be simultaneous */ len = 0; while (uio->uio_resid) { if ((error = ppb_1284_read(ppbus, PPB_NIBBLE, sc->sc_statbuf, min(BUFSTATSIZE, uio->uio_resid), &len))) { goto error; } if (!len) goto error; /* no more data */ ppb_unlock(ppbus); error = uiomove(sc->sc_statbuf, len, uio); ppb_lock(ppbus); if (error) goto error; } error: ppb_1284_terminate(ppbus); ppb_unlock(ppbus); return (error); } /* * lptwrite --copy a line from user space to a local buffer, then call * putc to get the chars moved to the output queue. * * Flagging of interrupted write added. */ static int lptwrite(struct cdev *dev, struct uio *uio, int ioflag) { register unsigned n; int err; struct lpt_data *sc = dev->si_drv1; device_t lptdev = sc->sc_dev; device_t ppbus = device_get_parent(lptdev); if (sc->sc_flags & LP_BYPASS) { /* we can't do writes in bypass mode */ return (EPERM); } /* request the ppbus only if we don't have it already */ ppb_lock(ppbus); if ((err = lpt_request_ppbus(lptdev, PPB_WAIT|PPB_INTR)) != 0) { ppb_unlock(ppbus); return (err); } sc->sc_state &= ~INTERRUPTED; while ((n = min(BUFSIZE, uio->uio_resid)) != 0) { sc->sc_cp = sc->sc_inbuf; ppb_unlock(ppbus); err = uiomove(sc->sc_cp, n, uio); ppb_lock(ppbus); if (err) break; sc->sc_xfercnt = n; if (sc->sc_irq & LP_ENABLE_EXT) { /* try any extended mode */ err = ppb_write(ppbus, sc->sc_cp, sc->sc_xfercnt, 0); switch (err) { case 0: /* if not all data was sent, we could rely * on polling for the last bytes */ sc->sc_xfercnt = 0; break; case EINTR: sc->sc_state |= INTERRUPTED; ppb_unlock(ppbus); return (err); case EINVAL: /* advanced mode not avail */ log(LOG_NOTICE, "%s: advanced mode not avail, polling\n", device_get_nameunit(sc->sc_dev)); break; default: ppb_unlock(ppbus); return (err); } } else while ((sc->sc_xfercnt > 0)&&(sc->sc_irq & LP_USE_IRQ)) { lprintf(("i")); /* if the printer is ready for a char, */ /* give it one */ if ((sc->sc_state & OBUSY) == 0){ lprintf(("\nC %d. ", sc->sc_xfercnt)); lptintr(sc); } lprintf(("W ")); if (sc->sc_state & OBUSY) if ((err = ppb_sleep(ppbus, lptdev, LPPRI|PCATCH, LPT_NAME "write", 0))) { sc->sc_state |= INTERRUPTED; ppb_unlock(ppbus); return(err); } } /* check to see if we must do a polled write */ if (!(sc->sc_irq & LP_USE_IRQ) && (sc->sc_xfercnt)) { lprintf(("p")); err = lpt_pushbytes(sc); if (err) { ppb_unlock(ppbus); return (err); } } } /* we have not been interrupted, release the ppbus */ lpt_release_ppbus(lptdev); ppb_unlock(ppbus); return (err); } /* * lptintr -- handle printer interrupts which occur when the printer is * ready to accept another char. * * do checking for interrupted write call. */ static void lptintr(void *arg) { struct lpt_data *sc = arg; device_t lptdev = sc->sc_dev; device_t ppbus = device_get_parent(lptdev); int sts = 0; int i; /* * Is printer online and ready for output? * * Avoid falling back to lptout() too quickly. First spin-loop * to see if the printer will become ready ``really soon now''. */ for (i = 0; i < 100 && ((sts=ppb_rstr(ppbus)) & RDY_MASK) != LP_READY; i++) ; if ((sts & RDY_MASK) == LP_READY) { sc->sc_state = (sc->sc_state | OBUSY) & ~EERROR; sc->sc_backoff = hz / LPTOUTINITIAL; if (sc->sc_xfercnt) { /* send char */ /*lprintf(("%x ", *sc->sc_cp)); */ ppb_wdtr(ppbus, *sc->sc_cp++) ; ppb_wctr(ppbus, sc->sc_control|LPC_STB); /* DELAY(X) */ ppb_wctr(ppbus, sc->sc_control); /* any more data for printer */ if (--(sc->sc_xfercnt) > 0) return; } /* * No more data waiting for printer. * Wakeup is not done if write call was not interrupted. */ sc->sc_state &= ~OBUSY; if (!(sc->sc_state & INTERRUPTED)) wakeup(lptdev); lprintf(("w ")); return; } else { /* check for error */ if (((sts & (LPS_NERR | LPS_OUT) ) != LPS_NERR) && (sc->sc_state & OPEN)) sc->sc_state |= EERROR; /* lptout() will jump in and try to restart. */ } lprintf(("sts %x ", sts)); } static int lptioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { int error = 0; struct lpt_data *sc = dev->si_drv1; device_t ppbus; u_char old_sc_irq; /* old printer IRQ status */ switch (cmd) { case LPT_IRQ : ppbus = device_get_parent(sc->sc_dev); ppb_lock(ppbus); if (sc->sc_irq & LP_HAS_IRQ) { /* * NOTE: * If the IRQ status is changed, * this will only be visible on the * next open. * * If interrupt status changes, * this gets syslog'd. */ old_sc_irq = sc->sc_irq; switch (*(int*)data) { case 0: sc->sc_irq &= (~LP_ENABLE_IRQ); break; case 1: sc->sc_irq &= (~LP_ENABLE_EXT); sc->sc_irq |= LP_ENABLE_IRQ; break; case 2: /* classic irq based transfer and advanced * modes are in conflict */ sc->sc_irq &= (~LP_ENABLE_IRQ); sc->sc_irq |= LP_ENABLE_EXT; break; case 3: sc->sc_irq &= (~LP_ENABLE_EXT); break; default: break; } if (old_sc_irq != sc->sc_irq ) log(LOG_NOTICE, "%s: switched to %s %s mode\n", device_get_nameunit(sc->sc_dev), (sc->sc_irq & LP_ENABLE_IRQ)? "interrupt-driven":"polled", (sc->sc_irq & LP_ENABLE_EXT)? "extended":"standard"); } else /* polled port */ error = EOPNOTSUPP; ppb_unlock(ppbus); break; default: error = ENODEV; } return(error); } static device_method_t lpt_methods[] = { /* device interface */ DEVMETHOD(device_identify, lpt_identify), DEVMETHOD(device_probe, lpt_probe), DEVMETHOD(device_attach, lpt_attach), DEVMETHOD(device_detach, lpt_detach), { 0, 0 } }; static driver_t lpt_driver = { LPT_NAME, lpt_methods, sizeof(struct lpt_data), }; DRIVER_MODULE(lpt, ppbus, lpt_driver, 0, 0); MODULE_DEPEND(lpt, ppbus, 1, 1, 1); diff --git a/sys/dev/ppbus/pcfclock.c b/sys/dev/ppbus/pcfclock.c index 7cee6692b367..47a1a010a311 100644 --- a/sys/dev/ppbus/pcfclock.c +++ b/sys/dev/ppbus/pcfclock.c @@ -1,333 +1,333 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2000 Sascha Schumann. 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, 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 SASCHA SCHUMANN ``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 OR CONTRIBUTORS 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. * * */ #include #include "opt_pcfclock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ppbus_if.h" #define PCFCLOCK_NAME "pcfclock" struct pcfclock_data { device_t dev; struct cdev *cdev; }; static d_open_t pcfclock_open; static d_close_t pcfclock_close; static d_read_t pcfclock_read; static struct cdevsw pcfclock_cdevsw = { .d_version = D_VERSION, .d_open = pcfclock_open, .d_close = pcfclock_close, .d_read = pcfclock_read, .d_name = PCFCLOCK_NAME, }; #ifndef PCFCLOCK_MAX_RETRIES #define PCFCLOCK_MAX_RETRIES 10 #endif #define AFC_HI 0 #define AFC_LO AUTOFEED /* AUTO FEED is used as clock */ #define AUTOFEED_CLOCK(val) \ ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr) /* SLCT is used as clock */ #define CLOCK_OK \ ((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0)) /* PE is used as data */ #define BIT_SET (ppb_rstr(ppbus)&PERROR) /* the first byte sent as reply must be 00001001b */ #define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9) #define NR(buf, off) (buf[off+1]*10+buf[off]) /* check for correct input values */ #define PCFCLOCK_CORRECT_FORMAT(buf) (\ NR(buf, 14) <= 99 && \ NR(buf, 12) <= 12 && \ NR(buf, 10) <= 31 && \ NR(buf, 6) <= 23 && \ NR(buf, 4) <= 59 && \ NR(buf, 2) <= 59) #define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4) #define PCFCLOCK_CMD_TIME 0 /* send current time */ #define PCFCLOCK_CMD_COPY 7 /* copy received signal to PC */ static void pcfclock_identify(driver_t *driver, device_t parent) { device_t dev; - dev = device_find_child(parent, PCFCLOCK_NAME, -1); + dev = device_find_child(parent, PCFCLOCK_NAME, DEVICE_UNIT_ANY); if (!dev) BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, DEVICE_UNIT_ANY); } static int pcfclock_probe(device_t dev) { device_set_desc(dev, "PCF-1.0"); return (0); } static int pcfclock_attach(device_t dev) { struct pcfclock_data *sc = device_get_softc(dev); int unit; unit = device_get_unit(dev); sc->dev = dev; sc->cdev = make_dev(&pcfclock_cdevsw, unit, UID_ROOT, GID_WHEEL, 0400, PCFCLOCK_NAME "%d", unit); if (sc->cdev == NULL) { device_printf(dev, "Failed to create character device\n"); return (ENXIO); } sc->cdev->si_drv1 = sc; return (0); } static int pcfclock_open(struct cdev *dev, int flag, int fms, struct thread *td) { struct pcfclock_data *sc = dev->si_drv1; device_t pcfclockdev; device_t ppbus; int res; if (!sc) return (ENXIO); pcfclockdev = sc->dev; ppbus = device_get_parent(pcfclockdev); ppb_lock(ppbus); res = ppb_request_bus(ppbus, pcfclockdev, (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT); ppb_unlock(ppbus); return (res); } static int pcfclock_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct pcfclock_data *sc = dev->si_drv1; device_t pcfclockdev = sc->dev; device_t ppbus = device_get_parent(pcfclockdev); ppb_lock(ppbus); ppb_release_bus(ppbus, pcfclockdev); ppb_unlock(ppbus); return (0); } static void pcfclock_write_cmd(struct cdev *dev, unsigned char command) { struct pcfclock_data *sc = dev->si_drv1; device_t pcfclockdev = sc->dev; device_t ppbus = device_get_parent(pcfclockdev); unsigned char ctr = 14; char i; for (i = 0; i <= 7; i++) { ppb_wdtr(ppbus, i); AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO); DELAY(3000); } ppb_wdtr(ppbus, command); AUTOFEED_CLOCK(AFC_LO); DELAY(3000); AUTOFEED_CLOCK(AFC_HI); } static void pcfclock_display_data(struct cdev *dev, char buf[18]) { struct pcfclock_data *sc = dev->si_drv1; #ifdef PCFCLOCK_VERBOSE int year; year = NR(buf, 14); if (year < 70) year += 100; device_printf(sc->dev, "%02d.%02d.%4d %02d:%02d:%02d, " "battery status: %s\n", NR(buf, 10), NR(buf, 12), 1900 + year, NR(buf, 6), NR(buf, 4), NR(buf, 2), PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok"); #else if (PCFCLOCK_BATTERY_STATUS_LOW(buf)) device_printf(sc->dev, "BATTERY STATUS LOW ON\n"); #endif } static int pcfclock_read_data(struct cdev *dev, char *buf, ssize_t bits) { struct pcfclock_data *sc = dev->si_drv1; device_t pcfclockdev = sc->dev; device_t ppbus = device_get_parent(pcfclockdev); int i; char waitfor; int offset; /* one byte per four bits */ bzero(buf, ((bits + 3) >> 2) + 1); waitfor = 100; for (i = 0; i <= bits; i++) { /* wait for clock, maximum (waitfor*100) usec */ while (!CLOCK_OK && --waitfor > 0) DELAY(100); /* timed out? */ if (!waitfor) return (EIO); waitfor = 100; /* reload */ /* give it some time */ DELAY(500); /* calculate offset into buffer */ offset = i >> 2; buf[offset] <<= 1; if (BIT_SET) buf[offset] |= 1; } return (0); } static int pcfclock_read_dev(struct cdev *dev, char *buf, int maxretries) { struct pcfclock_data *sc = dev->si_drv1; device_t pcfclockdev = sc->dev; device_t ppbus = device_get_parent(pcfclockdev); int error = 0; ppb_set_mode(ppbus, PPB_COMPATIBLE); while (--maxretries > 0) { pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME); if (pcfclock_read_data(dev, buf, 68)) continue; if (!PCFCLOCK_CORRECT_SYNC(buf)) continue; if (!PCFCLOCK_CORRECT_FORMAT(buf)) continue; break; } if (!maxretries) error = EIO; return (error); } static int pcfclock_read(struct cdev *dev, struct uio *uio, int ioflag) { struct pcfclock_data *sc = dev->si_drv1; device_t ppbus; char buf[18]; int error = 0; if (uio->uio_resid < 18) return (ERANGE); ppbus = device_get_parent(sc->dev); ppb_lock(ppbus); error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES); ppb_unlock(ppbus); if (error) { device_printf(sc->dev, "no PCF found\n"); } else { pcfclock_display_data(dev, buf); uiomove(buf, 18, uio); } return (error); } static device_method_t pcfclock_methods[] = { /* device interface */ DEVMETHOD(device_identify, pcfclock_identify), DEVMETHOD(device_probe, pcfclock_probe), DEVMETHOD(device_attach, pcfclock_attach), { 0, 0 } }; static driver_t pcfclock_driver = { PCFCLOCK_NAME, pcfclock_methods, sizeof(struct pcfclock_data), }; DRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, 0, 0); diff --git a/sys/dev/ppbus/ppi.c b/sys/dev/ppbus/ppi.c index 65921b53e0c6..3fd5f43a4a3a 100644 --- a/sys/dev/ppbus/ppi.c +++ b/sys/dev/ppbus/ppi.c @@ -1,618 +1,618 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, 1998, 1999 Nicolas Souchu, Michael Smith * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * * */ #include #include "opt_ppb_1284.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PERIPH_1284 #include #include #endif #include #include "ppbus_if.h" #include #define BUFSIZE 512 struct ppi_data { device_t ppi_device; struct cdev *ppi_cdev; struct sx ppi_lock; int ppi_flags; #define HAVE_PPBUS (1<<0) int ppi_mode; /* IEEE1284 mode */ char ppi_buffer[BUFSIZE]; #ifdef PERIPH_1284 struct resource *intr_resource; /* interrupt resource */ void *intr_cookie; /* interrupt registration cookie */ #endif /* PERIPH_1284 */ }; #define DEVTOSOFTC(dev) \ ((struct ppi_data *)device_get_softc(dev)) #ifdef PERIPH_1284 static void ppiintr(void *arg); #endif static d_open_t ppiopen; static d_close_t ppiclose; static d_ioctl_t ppiioctl; static d_write_t ppiwrite; static d_read_t ppiread; static struct cdevsw ppi_cdevsw = { .d_version = D_VERSION, .d_open = ppiopen, .d_close = ppiclose, .d_read = ppiread, .d_write = ppiwrite, .d_ioctl = ppiioctl, .d_name = "ppi", }; #ifdef PERIPH_1284 static void ppi_enable_intr(device_t ppidev) { char r; device_t ppbus = device_get_parent(ppidev); r = ppb_rctr(ppbus); ppb_wctr(ppbus, r | IRQENABLE); return; } static void ppi_disable_intr(device_t ppidev) { char r; device_t ppbus = device_get_parent(ppidev); r = ppb_rctr(ppbus); ppb_wctr(ppbus, r & ~IRQENABLE); return; } #endif /* PERIPH_1284 */ static void ppi_identify(driver_t *driver, device_t parent) { device_t dev; - dev = device_find_child(parent, "ppi", -1); + dev = device_find_child(parent, "ppi", DEVICE_UNIT_ANY); if (!dev) BUS_ADD_CHILD(parent, 0, "ppi", DEVICE_UNIT_ANY); } /* * ppi_probe() */ static int ppi_probe(device_t dev) { /* probe is always ok */ device_set_desc(dev, "Parallel I/O"); return (0); } /* * ppi_attach() */ static int ppi_attach(device_t dev) { struct ppi_data *ppi = DEVTOSOFTC(dev); #ifdef PERIPH_1284 int error, rid = 0; /* declare our interrupt handler */ ppi->intr_resource = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (ppi->intr_resource) { /* register our interrupt handler */ error = bus_setup_intr(dev, ppi->intr_resource, INTR_TYPE_TTY | INTR_MPSAFE, NULL, ppiintr, dev, &ppi->intr_cookie); if (error) { bus_release_resource(dev, SYS_RES_IRQ, rid, ppi->intr_resource); device_printf(dev, "Unable to register interrupt handler\n"); return (error); } } #endif /* PERIPH_1284 */ sx_init(&ppi->ppi_lock, "ppi"); ppi->ppi_cdev = make_dev(&ppi_cdevsw, device_get_unit(dev), UID_ROOT, GID_WHEEL, 0600, "ppi%d", device_get_unit(dev)); if (ppi->ppi_cdev == NULL) { device_printf(dev, "Failed to create character device\n"); return (ENXIO); } ppi->ppi_cdev->si_drv1 = ppi; ppi->ppi_device = dev; return (0); } static int ppi_detach(device_t dev) { struct ppi_data *ppi = DEVTOSOFTC(dev); destroy_dev(ppi->ppi_cdev); #ifdef PERIPH_1284 if (ppi->intr_resource != NULL) { bus_teardown_intr(dev, ppi->intr_resource, ppi->intr_cookie); bus_release_resource(dev, SYS_RES_IRQ, 0, ppi->intr_resource); } #endif sx_destroy(&ppi->ppi_lock); return (0); } #ifdef PERIPH_1284 /* * Cable * ----- * * Use an IEEE1284 compliant (DB25/DB25) cable with the following tricks: * * nStrobe <-> nAck 1 <-> 10 * nAutofd <-> Busy 11 <-> 14 * nSelectin <-> Select 17 <-> 13 * nInit <-> nFault 15 <-> 16 * */ static void ppiintr(void *arg) { device_t ppidev = (device_t)arg; device_t ppbus = device_get_parent(ppidev); struct ppi_data *ppi = DEVTOSOFTC(ppidev); ppb_assert_locked(ppbus); ppi_disable_intr(ppidev); switch (ppb_1284_get_state(ppbus)) { /* accept IEEE1284 negotiation then wakeup a waiting process to * continue negotiation at process level */ case PPB_FORWARD_IDLE: /* Event 1 */ if ((ppb_rstr(ppbus) & (SELECT | nBUSY)) == (SELECT | nBUSY)) { /* IEEE1284 negotiation */ #ifdef DEBUG_1284 printf("N"); #endif /* Event 2 - prepare for reading the ext. value */ ppb_wctr(ppbus, (PCD | STROBE | nINIT) & ~SELECTIN); ppb_1284_set_state(ppbus, PPB_NEGOCIATION); } else { #ifdef DEBUG_1284 printf("0x%x", ppb_rstr(ppbus)); #endif ppb_peripheral_terminate(ppbus, PPB_DONTWAIT); break; } /* wake up any process waiting for negotiation from * remote master host */ /* XXX should set a variable to warn the process about * the interrupt */ wakeup(ppi); break; default: #ifdef DEBUG_1284 printf("?%d", ppb_1284_get_state(ppbus)); #endif ppb_1284_set_state(ppbus, PPB_FORWARD_IDLE); ppb_set_mode(ppbus, PPB_COMPATIBLE); break; } ppi_enable_intr(ppidev); return; } #endif /* PERIPH_1284 */ static int ppiopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct ppi_data *ppi = dev->si_drv1; device_t ppidev = ppi->ppi_device; device_t ppbus = device_get_parent(ppidev); int res; sx_xlock(&ppi->ppi_lock); if (!(ppi->ppi_flags & HAVE_PPBUS)) { ppb_lock(ppbus); res = ppb_request_bus(ppbus, ppidev, (flags & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT | PPB_INTR); ppb_unlock(ppbus); if (res) { sx_xunlock(&ppi->ppi_lock); return (res); } ppi->ppi_flags |= HAVE_PPBUS; } sx_xunlock(&ppi->ppi_lock); return (0); } static int ppiclose(struct cdev *dev, int flags, int fmt, struct thread *td) { struct ppi_data *ppi = dev->si_drv1; device_t ppidev = ppi->ppi_device; device_t ppbus = device_get_parent(ppidev); sx_xlock(&ppi->ppi_lock); ppb_lock(ppbus); #ifdef PERIPH_1284 switch (ppb_1284_get_state(ppbus)) { case PPB_PERIPHERAL_IDLE: ppb_peripheral_terminate(ppbus, 0); break; case PPB_REVERSE_IDLE: case PPB_EPP_IDLE: case PPB_ECP_FORWARD_IDLE: default: ppb_1284_terminate(ppbus); break; } #endif /* PERIPH_1284 */ /* unregistration of interrupt forced by release */ ppb_release_bus(ppbus, ppidev); ppb_unlock(ppbus); ppi->ppi_flags &= ~HAVE_PPBUS; sx_xunlock(&ppi->ppi_lock); return (0); } /* * ppiread() * * IEEE1284 compliant read. * * First, try negotiation to BYTE then NIBBLE mode * If no data is available, wait for it otherwise transfer as much as possible */ static int ppiread(struct cdev *dev, struct uio *uio, int ioflag) { #ifdef PERIPH_1284 struct ppi_data *ppi = dev->si_drv1; device_t ppidev = ppi->ppi_device; device_t ppbus = device_get_parent(ppidev); int len, error = 0; char *buffer; buffer = malloc(BUFSIZE, M_DEVBUF, M_WAITOK); ppb_lock(ppbus); switch (ppb_1284_get_state(ppbus)) { case PPB_PERIPHERAL_IDLE: ppb_peripheral_terminate(ppbus, 0); /* FALLTHROUGH */ case PPB_FORWARD_IDLE: /* if can't negotiate NIBBLE mode then try BYTE mode, * the peripheral may be a computer */ if ((ppb_1284_negociate(ppbus, ppi->ppi_mode = PPB_NIBBLE, 0))) { /* XXX Wait 2 seconds to let the remote host some * time to terminate its interrupt */ ppb_sleep(ppbus, ppi, PPBPRI, "ppiread", 2 * hz); if ((error = ppb_1284_negociate(ppbus, ppi->ppi_mode = PPB_BYTE, 0))) { ppb_unlock(ppbus); free(buffer, M_DEVBUF); return (error); } } break; case PPB_REVERSE_IDLE: case PPB_EPP_IDLE: case PPB_ECP_FORWARD_IDLE: default: break; } #ifdef DEBUG_1284 printf("N"); #endif /* read data */ len = 0; while (uio->uio_resid) { error = ppb_1284_read(ppbus, ppi->ppi_mode, buffer, min(BUFSIZE, uio->uio_resid), &len); ppb_unlock(ppbus); if (error) goto error; if (!len) goto error; /* no more data */ #ifdef DEBUG_1284 printf("d"); #endif if ((error = uiomove(buffer, len, uio))) goto error; ppb_lock(ppbus); } ppb_unlock(ppbus); error: free(buffer, M_DEVBUF); #else /* PERIPH_1284 */ int error = ENODEV; #endif return (error); } /* * ppiwrite() * * IEEE1284 compliant write * * Actually, this is the peripheral side of a remote IEEE1284 read * * The first part of the negotiation (IEEE1284 device detection) is * done at interrupt level, then the remaining is done by the writing * process * * Once negotiation done, transfer data */ static int ppiwrite(struct cdev *dev, struct uio *uio, int ioflag) { #ifdef PERIPH_1284 struct ppi_data *ppi = dev->si_drv1; device_t ppidev = ppi->ppi_device; device_t ppbus = device_get_parent(ppidev); int len, error = 0, sent; char *buffer; #if 0 int ret; #define ADDRESS MS_PARAM(0, 0, MS_TYP_PTR) #define LENGTH MS_PARAM(0, 1, MS_TYP_INT) struct ppb_microseq msq[] = { { MS_OP_PUT, { MS_UNKNOWN, MS_UNKNOWN, MS_UNKNOWN } }, MS_RET(0) }; buffer = malloc(BUFSIZE, M_DEVBUF, M_WAITOK); ppb_lock(ppbus); /* negotiate ECP mode */ if (ppb_1284_negociate(ppbus, PPB_ECP, 0)) { printf("ppiwrite: ECP negotiation failed\n"); } while (!error && (len = min(uio->uio_resid, BUFSIZE))) { ppb_unlock(ppbus); uiomove(buffer, len, uio); ppb_MS_init_msq(msq, 2, ADDRESS, buffer, LENGTH, len); ppb_lock(ppbus); error = ppb_MS_microseq(ppbus, msq, &ret); } #else buffer = malloc(BUFSIZE, M_DEVBUF, M_WAITOK); ppb_lock(ppbus); #endif /* we have to be peripheral to be able to send data, so * wait for the appropriate state */ if (ppb_1284_get_state(ppbus) < PPB_PERIPHERAL_NEGOCIATION) ppb_1284_terminate(ppbus); while (ppb_1284_get_state(ppbus) != PPB_PERIPHERAL_IDLE) { /* XXX should check a variable before sleeping */ #ifdef DEBUG_1284 printf("s"); #endif ppi_enable_intr(ppidev); /* sleep until IEEE1284 negotiation starts */ error = ppb_sleep(ppbus, ppi, PCATCH | PPBPRI, "ppiwrite", 0); switch (error) { case 0: /* negotiate peripheral side with BYTE mode */ ppb_peripheral_negociate(ppbus, PPB_BYTE, 0); break; case EWOULDBLOCK: break; default: goto error; } } #ifdef DEBUG_1284 printf("N"); #endif /* negotiation done, write bytes to master host */ while ((len = min(uio->uio_resid, BUFSIZE)) != 0) { ppb_unlock(ppbus); uiomove(buffer, len, uio); ppb_lock(ppbus); if ((error = byte_peripheral_write(ppbus, buffer, len, &sent))) goto error; #ifdef DEBUG_1284 printf("d"); #endif } error: ppb_unlock(ppbus); free(buffer, M_DEVBUF); #else /* PERIPH_1284 */ int error = ENODEV; #endif return (error); } static int ppiioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { struct ppi_data *ppi = dev->si_drv1; device_t ppidev = ppi->ppi_device; device_t ppbus = device_get_parent(ppidev); int error = 0; u_int8_t *val = (u_int8_t *)data; ppb_lock(ppbus); switch (cmd) { case PPIGDATA: /* get data register */ *val = ppb_rdtr(ppbus); break; case PPIGSTATUS: /* get status bits */ *val = ppb_rstr(ppbus); break; case PPIGCTRL: /* get control bits */ *val = ppb_rctr(ppbus); break; case PPIGEPPD: /* get EPP data bits */ *val = ppb_repp_D(ppbus); break; case PPIGECR: /* get ECP bits */ *val = ppb_recr(ppbus); break; case PPIGFIFO: /* read FIFO */ *val = ppb_rfifo(ppbus); break; case PPISDATA: /* set data register */ ppb_wdtr(ppbus, *val); break; case PPISSTATUS: /* set status bits */ ppb_wstr(ppbus, *val); break; case PPISCTRL: /* set control bits */ ppb_wctr(ppbus, *val); break; case PPISEPPD: /* set EPP data bits */ ppb_wepp_D(ppbus, *val); break; case PPISECR: /* set ECP bits */ ppb_wecr(ppbus, *val); break; case PPISFIFO: /* write FIFO */ ppb_wfifo(ppbus, *val); break; case PPIGEPPA: /* get EPP address bits */ *val = ppb_repp_A(ppbus); break; case PPISEPPA: /* set EPP address bits */ ppb_wepp_A(ppbus, *val); break; default: error = ENOTTY; break; } ppb_unlock(ppbus); return (error); } static device_method_t ppi_methods[] = { /* device interface */ DEVMETHOD(device_identify, ppi_identify), DEVMETHOD(device_probe, ppi_probe), DEVMETHOD(device_attach, ppi_attach), DEVMETHOD(device_detach, ppi_detach), { 0, 0 } }; static driver_t ppi_driver = { "ppi", ppi_methods, sizeof(struct ppi_data), }; DRIVER_MODULE(ppi, ppbus, ppi_driver, 0, 0); MODULE_DEPEND(ppi, ppbus, 1, 1, 1); diff --git a/sys/dev/ppbus/pps.c b/sys/dev/ppbus/pps.c index 5a2791aa0335..80581e3beae7 100644 --- a/sys/dev/ppbus/pps.c +++ b/sys/dev/ppbus/pps.c @@ -1,345 +1,345 @@ /*- * SPDX-License-Identifier: Beerware * * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- * * * This driver implements a draft-mogul-pps-api-02.txt PPS source. * * The input pin is pin#10 * The echo output pin is pin#14 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ppbus_if.h" #include #define PPS_NAME "pps" /* our official name */ #define PRVERBOSE(fmt, arg...) if (bootverbose) printf(fmt, ##arg); struct pps_data { struct ppb_device pps_dev; struct pps_state pps[9]; struct cdev *devs[9]; device_t ppsdev; device_t ppbus; int busy; struct callout timeout; int lastdata; struct sx lock; struct resource *intr_resource; /* interrupt resource */ void *intr_cookie; /* interrupt registration cookie */ }; static void ppsintr(void *arg); static void ppshcpoll(void *arg); #define DEVTOSOFTC(dev) \ ((struct pps_data *)device_get_softc(dev)) static d_open_t ppsopen; static d_close_t ppsclose; static d_ioctl_t ppsioctl; static struct cdevsw pps_cdevsw = { .d_version = D_VERSION, .d_open = ppsopen, .d_close = ppsclose, .d_ioctl = ppsioctl, .d_name = PPS_NAME, }; static void ppsidentify(driver_t *driver, device_t parent) { device_t dev; - dev = device_find_child(parent, PPS_NAME, -1); + dev = device_find_child(parent, PPS_NAME, DEVICE_UNIT_ANY); if (!dev) BUS_ADD_CHILD(parent, 0, PPS_NAME, DEVICE_UNIT_ANY); } static int ppstry(device_t ppbus, int send, int expect) { int i; ppb_wdtr(ppbus, send); i = ppb_rdtr(ppbus); PRVERBOSE("S: %02x E: %02x G: %02x\n", send, expect, i); return (i != expect); } static int ppsprobe(device_t ppsdev) { device_set_desc(ppsdev, "Pulse per second Timing Interface"); return (0); } static int ppsattach(device_t dev) { struct pps_data *sc = DEVTOSOFTC(dev); device_t ppbus = device_get_parent(dev); struct cdev *d; int error, i, unit, rid = 0; /* declare our interrupt handler */ sc->intr_resource = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE); /* interrupts seem mandatory */ if (sc->intr_resource == NULL) { device_printf(dev, "Unable to allocate interrupt resource\n"); return (ENXIO); } error = bus_setup_intr(dev, sc->intr_resource, INTR_TYPE_TTY | INTR_MPSAFE, NULL, ppsintr, sc, &sc->intr_cookie); if (error) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->intr_resource); device_printf(dev, "Unable to register interrupt handler\n"); return (error); } sx_init(&sc->lock, "pps"); ppb_init_callout(ppbus, &sc->timeout, 0); sc->ppsdev = dev; sc->ppbus = ppbus; unit = device_get_unit(ppbus); d = make_dev(&pps_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, PPS_NAME "%d", unit); sc->devs[0] = d; sc->pps[0].ppscap = PPS_CAPTUREASSERT | PPS_ECHOASSERT; sc->pps[0].driver_abi = PPS_ABI_VERSION; sc->pps[0].driver_mtx = ppb_get_lock(ppbus); d->si_drv1 = sc; d->si_drv2 = (void*)0; pps_init_abi(&sc->pps[0]); ppb_lock(ppbus); if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) { ppb_unlock(ppbus); return (0); } do { i = ppb_set_mode(sc->ppbus, PPB_EPP); PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); if (i == -1) break; i = 0; ppb_wctr(ppbus, i); if (ppstry(ppbus, 0x00, 0x00)) break; if (ppstry(ppbus, 0x55, 0x55)) break; if (ppstry(ppbus, 0xaa, 0xaa)) break; if (ppstry(ppbus, 0xff, 0xff)) break; i = IRQENABLE | PCD | STROBE | nINIT | SELECTIN; ppb_wctr(ppbus, i); PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); if (ppstry(ppbus, 0x00, 0x00)) break; if (ppstry(ppbus, 0x55, 0x00)) break; if (ppstry(ppbus, 0xaa, 0x00)) break; if (ppstry(ppbus, 0xff, 0x00)) break; i = IRQENABLE | PCD | nINIT | SELECTIN; ppb_wctr(ppbus, i); PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); ppstry(ppbus, 0x00, 0xff); ppstry(ppbus, 0x55, 0xff); ppstry(ppbus, 0xaa, 0xff); ppstry(ppbus, 0xff, 0xff); ppb_unlock(ppbus); for (i = 1; i < 9; i++) { d = make_dev(&pps_cdevsw, unit + 0x10000 * i, UID_ROOT, GID_WHEEL, 0600, PPS_NAME "%db%d", unit, i - 1); sc->devs[i] = d; sc->pps[i].ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; sc->pps[i].driver_abi = PPS_ABI_VERSION; sc->pps[i].driver_mtx = ppb_get_lock(ppbus); d->si_drv1 = sc; d->si_drv2 = (void *)(intptr_t)i; pps_init_abi(&sc->pps[i]); } ppb_lock(ppbus); } while (0); i = ppb_set_mode(sc->ppbus, PPB_COMPATIBLE); ppb_release_bus(ppbus, dev); ppb_unlock(ppbus); return (0); } static int ppsopen(struct cdev *dev, int flags, int fmt, struct thread *td) { struct pps_data *sc = dev->si_drv1; device_t ppbus = sc->ppbus; int subdev = (intptr_t)dev->si_drv2; int i; /* * The sx lock is here solely to serialize open()'s to close * the race of concurrent open()'s when pps(4) doesn't own the * ppbus. */ sx_xlock(&sc->lock); ppb_lock(ppbus); if (!sc->busy) { device_t ppsdev = sc->ppsdev; if (ppb_request_bus(ppbus, ppsdev, PPB_WAIT|PPB_INTR)) { ppb_unlock(ppbus); sx_xunlock(&sc->lock); return (EINTR); } i = ppb_set_mode(sc->ppbus, PPB_PS2); PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); i = IRQENABLE | PCD | nINIT | SELECTIN; ppb_wctr(ppbus, i); } if (subdev > 0 && !(sc->busy & ~1)) { /* XXX: Timeout of 1? hz/100 instead perhaps? */ callout_reset(&sc->timeout, 1, ppshcpoll, sc); sc->lastdata = ppb_rdtr(sc->ppbus); } sc->busy |= (1 << subdev); ppb_unlock(ppbus); sx_xunlock(&sc->lock); return(0); } static int ppsclose(struct cdev *dev, int flags, int fmt, struct thread *td) { struct pps_data *sc = dev->si_drv1; int subdev = (intptr_t)dev->si_drv2; sx_xlock(&sc->lock); sc->pps[subdev].ppsparam.mode = 0; /* PHK ??? */ ppb_lock(sc->ppbus); sc->busy &= ~(1 << subdev); if (subdev > 0 && !(sc->busy & ~1)) callout_stop(&sc->timeout); if (!sc->busy) { device_t ppsdev = sc->ppsdev; device_t ppbus = sc->ppbus; ppb_wdtr(ppbus, 0); ppb_wctr(ppbus, 0); ppb_set_mode(ppbus, PPB_COMPATIBLE); ppb_release_bus(ppbus, ppsdev); } ppb_unlock(sc->ppbus); sx_xunlock(&sc->lock); return(0); } static void ppshcpoll(void *arg) { struct pps_data *sc = arg; int i, j, k, l; KASSERT(sc->busy & ~1, ("pps polling w/o opened devices")); i = ppb_rdtr(sc->ppbus); if (i == sc->lastdata) return; l = sc->lastdata ^ i; k = 1; for (j = 1; j < 9; j ++) { if (l & k) { pps_capture(&sc->pps[j]); pps_event(&sc->pps[j], i & k ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); } k += k; } sc->lastdata = i; callout_reset(&sc->timeout, 1, ppshcpoll, sc); } static void ppsintr(void *arg) { struct pps_data *sc = (struct pps_data *)arg; ppb_assert_locked(sc->ppbus); pps_capture(&sc->pps[0]); if (!(ppb_rstr(sc->ppbus) & nACK)) return; if (sc->pps[0].ppsparam.mode & PPS_ECHOASSERT) ppb_wctr(sc->ppbus, IRQENABLE | AUTOFEED); pps_event(&sc->pps[0], PPS_CAPTUREASSERT); if (sc->pps[0].ppsparam.mode & PPS_ECHOASSERT) ppb_wctr(sc->ppbus, IRQENABLE); } static int ppsioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { struct pps_data *sc = dev->si_drv1; int subdev = (intptr_t)dev->si_drv2; int err; ppb_lock(sc->ppbus); err = pps_ioctl(cmd, data, &sc->pps[subdev]); ppb_unlock(sc->ppbus); return (err); } static device_method_t pps_methods[] = { /* device interface */ DEVMETHOD(device_identify, ppsidentify), DEVMETHOD(device_probe, ppsprobe), DEVMETHOD(device_attach, ppsattach), { 0, 0 } }; static driver_t pps_driver = { PPS_NAME, pps_methods, sizeof(struct pps_data), }; DRIVER_MODULE(pps, ppbus, pps_driver, 0, 0); MODULE_DEPEND(pps, ppbus, 1, 1, 1); diff --git a/sys/dev/psci/smccc_errata.c b/sys/dev/psci/smccc_errata.c index db6b0a588a86..92f39c72c7f0 100644 --- a/sys/dev/psci/smccc_errata.c +++ b/sys/dev/psci/smccc_errata.c @@ -1,138 +1,138 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024 Arm Ltd * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * A driver for the Arm Errata Management Firmware Interface (Errata ABI). * This queries into the SMCCC firmware for the status of errata using the * interface documented in den0100 [1]. * * [1] https://developer.arm.com/documentation/den0100/latest */ #include #include #include #include #include #include #include #include #include #include #include #define ERRATA_HIGHER_EL_MITIGATION 3 #define ERRATA_NOT_AFFECTED 2 #define ERRATA_AFFECTED 1 #define EM_VERSION SMCCC_FUNC_ID(SMCCC_FAST_CALL, \ SMCCC_32BIT_CALL, SMCCC_STD_SECURE_SERVICE_CALLS, 0xf0u) #define EM_VERSION_MIN 0x10000L #define EM_FEATURES SMCCC_FUNC_ID(SMCCC_FAST_CALL, \ SMCCC_32BIT_CALL, SMCCC_STD_SECURE_SERVICE_CALLS, 0xf1u) #define EM_CPU_ERRATUM_FEATURES SMCCC_FUNC_ID(SMCCC_FAST_CALL, \ SMCCC_32BIT_CALL, SMCCC_STD_SECURE_SERVICE_CALLS, 0xf2u) static device_identify_t errata_identify; static device_probe_t errata_probe; static device_attach_t errata_attach; static cpu_feat_errata errata_cpu_feat_errata_check(const struct cpu_feat *, u_int); static void errata_identify(driver_t *driver, device_t parent) { int32_t version; if (smccc_get_version() < SMCCC_MAKE_VERSION(1, 1)) return; /* Check we have Errata 1.0 or later */ version = psci_call(EM_VERSION, 0, 0, 0); if (version < EM_VERSION_MIN) return; - if (BUS_ADD_CHILD(parent, 0, "errata", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "errata", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add errata child failed\n"); } static int errata_probe(device_t dev) { device_set_desc(dev, "Arm SMCCC Errata Management"); return (BUS_PROBE_NOWILDCARD); } static int errata_attach(device_t dev) { /* Check for EM_CPU_ERRATUM_FEATURES. It's mandatory, so should exist */ if (arm_smccc_invoke(EM_FEATURES, EM_CPU_ERRATUM_FEATURES, NULL) < 0) { device_printf(dev, "EM_CPU_ERRATUM_FEATURES is not implemented\n"); return (ENXIO); } cpu_feat_register_errata_check(errata_cpu_feat_errata_check); return (0); } static cpu_feat_errata errata_cpu_feat_errata_check(const struct cpu_feat *feat __unused, u_int errata_id) { struct arm_smccc_res res; switch(arm_smccc_invoke(EM_CPU_ERRATUM_FEATURES, errata_id, 0, &res)) { default: return (ERRATA_UNKNOWN); case ERRATA_NOT_AFFECTED: return (ERRATA_NONE); case ERRATA_AFFECTED: return (ERRATA_AFFECTED); case ERRATA_HIGHER_EL_MITIGATION: return (ERRATA_FW_MITIGAION); } } static device_method_t errata_methods[] = { DEVMETHOD(device_identify, errata_identify), DEVMETHOD(device_probe, errata_probe), DEVMETHOD(device_attach, errata_attach), DEVMETHOD_END }; static driver_t errata_driver = { "errata", errata_methods, 0 }; DRIVER_MODULE(errata, smccc, errata_driver, 0, 0); diff --git a/sys/dev/psci/smccc_trng.c b/sys/dev/psci/smccc_trng.c index 67939f3422fa..5f54d8adc24a 100644 --- a/sys/dev/psci/smccc_trng.c +++ b/sys/dev/psci/smccc_trng.c @@ -1,143 +1,143 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024 Arm Ltd * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * A driver for the Arm True Random Number Generator Firmware Interface. * This queries into the SMCCC firmware for random numbers using the * interface documented in den0098 [1]. * * [1] https://developer.arm.com/documentation/den0098/latest */ #include #include #include #include #include #include #include #include #include #include #define TRNG_VERSION SMCCC_FUNC_ID(SMCCC_FAST_CALL, \ SMCCC_32BIT_CALL, SMCCC_STD_SECURE_SERVICE_CALLS, 0x50) #define TRNG_VERSION_MIN 0x10000L #define TRNG_RND64 SMCCC_FUNC_ID(SMCCC_FAST_CALL, \ SMCCC_64BIT_CALL, SMCCC_STD_SECURE_SERVICE_CALLS, 0x53) static device_identify_t trng_identify; static device_probe_t trng_probe; static device_attach_t trng_attach; static unsigned trng_read(void *, unsigned); static struct random_source random_trng = { .rs_ident = "Arm SMCCC TRNG", .rs_source = RANDOM_PURE_ARM_TRNG, .rs_read = trng_read, }; static void trng_identify(driver_t *driver, device_t parent) { int32_t version; /* TRNG depends on SMCCC 1.1 (per the spec) */ if (smccc_get_version() < SMCCC_MAKE_VERSION(1, 1)) return; /* Check we have TRNG 1.0 or later */ version = psci_call(TRNG_VERSION, 0, 0, 0); if (version < TRNG_VERSION_MIN) return; - if (BUS_ADD_CHILD(parent, 0, "trng", -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, "trng", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add TRNG child failed\n"); } static int trng_probe(device_t dev) { device_set_desc(dev, "Arm SMCCC TRNG"); return (BUS_PROBE_NOWILDCARD); } static int trng_attach(device_t dev) { struct arm_smccc_res res; int32_t ret; ret = arm_smccc_invoke(TRNG_RND64, 192, &res); if (ret < 0) { device_printf(dev, "Failed to read fron TRNG\n"); } else { random_source_register(&random_trng); } return (0); } static unsigned trng_read(void *buf, unsigned usz) { struct arm_smccc_res res; register_t len; int32_t ret; len = usz; if (len > sizeof(uint64_t)) len = sizeof(uint64_t); if (len == 0) return (0); ret = arm_smccc_invoke(TRNG_RND64, len * 8, &res); if (ret < 0) return (0); memcpy(buf, &res.a0, len); return (len); } static device_method_t trng_methods[] = { DEVMETHOD(device_identify, trng_identify), DEVMETHOD(device_probe, trng_probe), DEVMETHOD(device_attach, trng_attach), DEVMETHOD_END }; static driver_t trng_driver = { "trng", trng_methods, 0 }; DRIVER_MODULE(trng, smccc, trng_driver, 0, 0); diff --git a/sys/dev/pwm/ofw_pwmbus.c b/sys/dev/pwm/ofw_pwmbus.c index 28a6a542c0a7..913792374fd9 100644 --- a/sys/dev/pwm/ofw_pwmbus.c +++ b/sys/dev/pwm/ofw_pwmbus.c @@ -1,219 +1,221 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Ian Lepore * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "pwmbus_if.h" struct ofw_pwmbus_ivars { struct pwmbus_ivars base; struct ofw_bus_devinfo devinfo; }; struct ofw_pwmbus_softc { struct pwmbus_softc base; }; /* * bus_if methods... */ static device_t ofw_pwmbus_add_child(device_t dev, u_int order, const char *name, int unit) { device_t child; struct ofw_pwmbus_ivars *ivars; if ((ivars = malloc(sizeof(struct ofw_pwmbus_ivars), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { return (NULL); } if ((child = device_add_child_ordered(dev, order, name, unit)) == NULL) { free(ivars, M_DEVBUF); return (NULL); } ivars->devinfo.obd_node = -1; device_set_ivars(child, ivars); return (child); } static void ofw_pwmbus_child_deleted(device_t dev, device_t child) { struct ofw_pwmbus_ivars *ivars; ivars = device_get_ivars(child); if (ivars != NULL) { ofw_bus_gen_destroy_devinfo(&ivars->devinfo); free(ivars, M_DEVBUF); } } static const struct ofw_bus_devinfo * ofw_pwmbus_get_devinfo(device_t bus, device_t dev) { struct ofw_pwmbus_ivars *ivars; ivars = device_get_ivars(dev); return (&ivars->devinfo); } /* * device_if methods... */ static int ofw_pwmbus_probe(device_t dev) { if (ofw_bus_get_node(dev) == -1) { return (ENXIO); } device_set_desc(dev, "OFW PWM bus"); return (BUS_PROBE_DEFAULT); } static int ofw_pwmbus_attach(device_t dev) { struct ofw_pwmbus_softc *sc; struct ofw_pwmbus_ivars *ivars; phandle_t node; device_t child, parent; pcell_t chan; bool any_children; sc = device_get_softc(dev); sc->base.dev = dev; parent = device_get_parent(dev); if (PWMBUS_CHANNEL_COUNT(parent, &sc->base.nchannels) != 0 || sc->base.nchannels == 0) { device_printf(dev, "No channels on parent %s\n", device_get_nameunit(parent)); return (ENXIO); } /* * Attach the children found in the fdt node of the hardware controller. * Hardware controllers must implement the ofw_bus_get_node method so * that our call to ofw_bus_get_node() gets back the controller's node. */ any_children = false; node = ofw_bus_get_node(dev); for (node = OF_child(node); node != 0; node = OF_peer(node)) { /* * The child has to have a reg property; its value is the * channel number so range-check it. */ if (OF_getencprop(node, "reg", &chan, sizeof(chan)) == -1) continue; if (chan >= sc->base.nchannels) continue; - if ((child = ofw_pwmbus_add_child(dev, 0, NULL, -1)) == NULL) + if ((child = ofw_pwmbus_add_child(dev, 0, NULL, + DEVICE_UNIT_ANY)) == NULL) continue; ivars = device_get_ivars(child); ivars->base.pi_channel = chan; /* Set up the standard ofw devinfo. */ if (ofw_bus_gen_setup_devinfo(&ivars->devinfo, node) != 0) { device_delete_child(dev, child); continue; } any_children = true; } /* * If we didn't find any children in the fdt data, add a pwmc(4) child * for each channel, like the base pwmbus does. The idea is that if * there is any fdt data, then we do exactly what it says and nothing * more, otherwise we just provide generic userland access to all the * pwm channels that exist like the base pwmbus's attach code does. */ if (!any_children) { for (chan = 0; chan < sc->base.nchannels; ++chan) { - child = ofw_pwmbus_add_child(dev, 0, "pwmc", -1); + child = ofw_pwmbus_add_child(dev, 0, "pwmc", + DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "failed to add pwmc child " " device for channel %u\n", chan); continue; } ivars = device_get_ivars(child); ivars->base.pi_channel = chan; } } bus_enumerate_hinted_children(dev); bus_identify_children(dev); bus_attach_children(dev); return (0); } static device_method_t ofw_pwmbus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ofw_pwmbus_probe), DEVMETHOD(device_attach, ofw_pwmbus_attach), /* Bus interface */ DEVMETHOD(bus_child_pnpinfo, ofw_bus_gen_child_pnpinfo), DEVMETHOD(bus_add_child, ofw_pwmbus_add_child), DEVMETHOD(bus_child_deleted, ofw_pwmbus_child_deleted), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_devinfo, ofw_pwmbus_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), DEVMETHOD_END }; DEFINE_CLASS_1(pwmbus, ofw_pwmbus_driver, ofw_pwmbus_methods, sizeof(struct pwmbus_softc), pwmbus_driver); EARLY_DRIVER_MODULE(ofw_pwmbus, pwm, ofw_pwmbus_driver, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(ofw_pwmbus, 1); MODULE_DEPEND(ofw_pwmbus, pwmbus, 1, 1, 1); diff --git a/sys/dev/qat/qat/qat_ocf.c b/sys/dev/qat/qat/qat_ocf.c index afdc5f396c80..74f113e46884 100644 --- a/sys/dev/qat/qat/qat_ocf.c +++ b/sys/dev/qat/qat/qat_ocf.c @@ -1,1299 +1,1299 @@ /* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright(c) 2007-2025 Intel Corporation */ /* System headers */ #include #include #include #include #include #include #include #include #include /* Cryptodev headers */ #include #include "cryptodev_if.h" /* QAT specific headers */ #include "cpa.h" #include "cpa_cy_im.h" #include "cpa_cy_sym_dp.h" #include "adf_accel_devices.h" #include "adf_common_drv.h" #include "lac_sym_hash_defs.h" #include "lac_sym_qat_hash_defs_lookup.h" /* To get only IRQ instances */ #include "icp_accel_devices.h" #include "icp_adf_accel_mgr.h" #include "lac_sal_types.h" /* To disable AEAD HW MAC verification */ #include "icp_sal_user.h" /* QAT OCF specific headers */ #include "qat_ocf_mem_pool.h" #include "qat_ocf_utils.h" #define QAT_OCF_MAX_INSTANCES (256) #define QAT_OCF_SESSION_WAIT_TIMEOUT_MS (1000) MALLOC_DEFINE(M_QAT_OCF, "qat_ocf", "qat_ocf(4) memory allocations"); /* QAT OCF internal structures */ struct qat_ocf_softc { device_t sc_dev; struct sysctl_oid *rc; uint32_t enabled; int32_t cryptodev_id; struct qat_ocf_instance cyInstHandles[QAT_OCF_MAX_INSTANCES]; int32_t numCyInstances; }; /* Function definitions */ static void qat_ocf_freesession(device_t dev, crypto_session_t cses); static int qat_ocf_probesession(device_t dev, const struct crypto_session_params *csp); static int qat_ocf_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp); static int qat_ocf_attach(device_t dev); static int qat_ocf_detach(device_t dev); static void symDpCallback(CpaCySymDpOpData *pOpData, CpaStatus result, CpaBoolean verifyResult) { struct qat_ocf_cookie *qat_cookie; struct cryptop *crp; struct qat_ocf_dsession *qat_dsession = NULL; struct qat_ocf_session *qat_session = NULL; struct qat_ocf_instance *qat_instance = NULL; CpaStatus status; int rc = 0; qat_cookie = (struct qat_ocf_cookie *)pOpData->pCallbackTag; if (!qat_cookie) return; crp = qat_cookie->crp_op; qat_dsession = crypto_get_driver_session(crp->crp_session); qat_instance = qat_dsession->qatInstance; status = qat_ocf_cookie_dma_post_sync(crp, pOpData); if (CPA_STATUS_SUCCESS != status) { rc = EIO; goto exit; } status = qat_ocf_cookie_dma_unload(crp, pOpData); if (CPA_STATUS_SUCCESS != status) { rc = EIO; goto exit; } /* Verify result */ if (CPA_STATUS_SUCCESS != result) { rc = EBADMSG; goto exit; } /* Verify digest by FW (GCM and CCM only) */ if (CPA_TRUE != verifyResult) { rc = EBADMSG; goto exit; } if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) qat_session = &qat_dsession->encSession; else qat_session = &qat_dsession->decSession; /* Copy back digest result if it's stored in separated buffer */ if (pOpData->digestResult && qat_session->authLen > 0) { if ((crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) != 0) { char icv[QAT_OCF_MAX_DIGEST] = { 0 }; crypto_copydata(crp, crp->crp_digest_start, qat_session->authLen, icv); if (timingsafe_bcmp(icv, qat_cookie->qat_ocf_digest, qat_session->authLen) != 0) { rc = EBADMSG; goto exit; } } else { crypto_copyback(crp, crp->crp_digest_start, qat_session->authLen, qat_cookie->qat_ocf_digest); } } exit: qat_ocf_cookie_free(qat_instance, qat_cookie); crp->crp_etype = rc; crypto_done(crp); return; } static inline CpaPhysicalAddr qatVirtToPhys(void *virtAddr) { return (CpaPhysicalAddr)vtophys(virtAddr); } static int qat_ocf_probesession(device_t dev, const struct crypto_session_params *csp) { if ((csp->csp_flags & ~(CSP_F_SEPARATE_OUTPUT | CSP_F_SEPARATE_AAD)) != 0) { return EINVAL; } switch (csp->csp_mode) { case CSP_MODE_CIPHER: switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: case CRYPTO_AES_ICM: if (csp->csp_ivlen != AES_BLOCK_LEN) return EINVAL; break; case CRYPTO_AES_XTS: if (csp->csp_ivlen != AES_XTS_IV_LEN) return EINVAL; break; default: return EINVAL; } break; case CSP_MODE_DIGEST: switch (csp->csp_auth_alg) { case CRYPTO_SHA1: case CRYPTO_SHA1_HMAC: case CRYPTO_SHA2_256: case CRYPTO_SHA2_256_HMAC: case CRYPTO_SHA2_384: case CRYPTO_SHA2_384_HMAC: case CRYPTO_SHA2_512: case CRYPTO_SHA2_512_HMAC: break; case CRYPTO_AES_NIST_GMAC: if (csp->csp_ivlen != AES_GCM_IV_LEN) return EINVAL; break; default: return EINVAL; } break; case CSP_MODE_AEAD: switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: if (csp->csp_ivlen != AES_GCM_IV_LEN) return EINVAL; break; default: return EINVAL; } break; case CSP_MODE_ETA: switch (csp->csp_auth_alg) { case CRYPTO_SHA1_HMAC: case CRYPTO_SHA2_256_HMAC: case CRYPTO_SHA2_384_HMAC: case CRYPTO_SHA2_512_HMAC: switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: case CRYPTO_AES_ICM: if (csp->csp_ivlen != AES_BLOCK_LEN) return EINVAL; break; case CRYPTO_AES_XTS: if (csp->csp_ivlen != AES_XTS_IV_LEN) return EINVAL; break; default: return EINVAL; } break; default: return EINVAL; } break; default: return EINVAL; } return CRYPTODEV_PROBE_HARDWARE; } static CpaStatus qat_ocf_session_init(device_t dev, struct cryptop *crp, struct qat_ocf_instance *qat_instance, struct qat_ocf_session *qat_ssession) { CpaStatus status = CPA_STATUS_SUCCESS; /* Crytpodev structures */ crypto_session_t cses; const struct crypto_session_params *csp; /* DP API Session configuration */ CpaCySymSessionSetupData sessionSetupData = { 0 }; CpaCySymSessionCtx sessionCtx = NULL; Cpa32U sessionCtxSize = 0; cses = crp->crp_session; if (NULL == cses) { device_printf(dev, "no crypto session in cryptodev request\n"); return CPA_STATUS_FAIL; } csp = crypto_get_params(cses); if (NULL == csp) { device_printf(dev, "no session in cryptodev session\n"); return CPA_STATUS_FAIL; } /* Common fields */ sessionSetupData.sessionPriority = CPA_CY_PRIORITY_HIGH; /* Cipher key */ if (crp->crp_cipher_key) sessionSetupData.cipherSetupData.pCipherKey = crp->crp_cipher_key; else sessionSetupData.cipherSetupData.pCipherKey = csp->csp_cipher_key; sessionSetupData.cipherSetupData.cipherKeyLenInBytes = csp->csp_cipher_klen; /* Auth key */ if (crp->crp_auth_key) sessionSetupData.hashSetupData.authModeSetupData.authKey = crp->crp_auth_key; else sessionSetupData.hashSetupData.authModeSetupData.authKey = csp->csp_auth_key; sessionSetupData.hashSetupData.authModeSetupData.authKeyLenInBytes = csp->csp_auth_klen; qat_ssession->aadLen = crp->crp_aad_length; if (CPA_TRUE == is_sep_aad_supported(csp)) sessionSetupData.hashSetupData.authModeSetupData.aadLenInBytes = crp->crp_aad_length; else sessionSetupData.hashSetupData.authModeSetupData.aadLenInBytes = 0; /* Just setup algorithm - regardless of mode */ if (csp->csp_cipher_alg) { sessionSetupData.symOperation = CPA_CY_SYM_OP_CIPHER; switch (csp->csp_cipher_alg) { case CRYPTO_AES_CBC: sessionSetupData.cipherSetupData.cipherAlgorithm = CPA_CY_SYM_CIPHER_AES_CBC; break; case CRYPTO_AES_ICM: sessionSetupData.cipherSetupData.cipherAlgorithm = CPA_CY_SYM_CIPHER_AES_CTR; break; case CRYPTO_AES_XTS: sessionSetupData.cipherSetupData.cipherAlgorithm = CPA_CY_SYM_CIPHER_AES_XTS; break; case CRYPTO_AES_NIST_GCM_16: sessionSetupData.cipherSetupData.cipherAlgorithm = CPA_CY_SYM_CIPHER_AES_GCM; sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_AES_GCM; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; break; default: device_printf(dev, "cipher_alg: %d not supported\n", csp->csp_cipher_alg); status = CPA_STATUS_UNSUPPORTED; goto fail; } } if (csp->csp_auth_alg) { switch (csp->csp_auth_alg) { case CRYPTO_SHA1_HMAC: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA1; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; break; case CRYPTO_SHA1: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA1; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_PLAIN; break; case CRYPTO_SHA2_256_HMAC: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA256; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; break; case CRYPTO_SHA2_256: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA256; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_PLAIN; break; case CRYPTO_SHA2_224_HMAC: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA224; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; break; case CRYPTO_SHA2_224: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA224; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_PLAIN; break; case CRYPTO_SHA2_384_HMAC: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA384; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; break; case CRYPTO_SHA2_384: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA384; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_PLAIN; break; case CRYPTO_SHA2_512_HMAC: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA512; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; break; case CRYPTO_SHA2_512: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA512; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_PLAIN; break; case CRYPTO_AES_NIST_GMAC: sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_AES_GMAC; break; default: status = CPA_STATUS_UNSUPPORTED; goto fail; } } /* csp->csp_auth_alg */ /* Setting digest-length if no cipher-only mode is set */ if (csp->csp_mode != CSP_MODE_CIPHER) { lac_sym_qat_hash_defs_t *pHashDefsInfo = NULL; if (csp->csp_auth_mlen) { sessionSetupData.hashSetupData.digestResultLenInBytes = csp->csp_auth_mlen; qat_ssession->authLen = csp->csp_auth_mlen; } else { LacSymQat_HashDefsLookupGet( qat_instance->cyInstHandle, sessionSetupData.hashSetupData.hashAlgorithm, &pHashDefsInfo); if (NULL == pHashDefsInfo) { device_printf( dev, "unable to find corresponding hash data\n"); status = CPA_STATUS_UNSUPPORTED; goto fail; } sessionSetupData.hashSetupData.digestResultLenInBytes = pHashDefsInfo->algInfo->digestLength; qat_ssession->authLen = pHashDefsInfo->algInfo->digestLength; } sessionSetupData.verifyDigest = CPA_FALSE; } switch (csp->csp_mode) { case CSP_MODE_AEAD: case CSP_MODE_ETA: sessionSetupData.symOperation = CPA_CY_SYM_OP_ALGORITHM_CHAINING; /* Place the digest result in a buffer unrelated to srcBuffer */ sessionSetupData.digestIsAppended = CPA_FALSE; /* Due to FW limitation to verify only appended MACs */ sessionSetupData.verifyDigest = CPA_FALSE; if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { sessionSetupData.cipherSetupData.cipherDirection = CPA_CY_SYM_CIPHER_DIRECTION_ENCRYPT; sessionSetupData.algChainOrder = CPA_CY_SYM_ALG_CHAIN_ORDER_CIPHER_THEN_HASH; } else { sessionSetupData.cipherSetupData.cipherDirection = CPA_CY_SYM_CIPHER_DIRECTION_DECRYPT; sessionSetupData.algChainOrder = CPA_CY_SYM_ALG_CHAIN_ORDER_HASH_THEN_CIPHER; } break; case CSP_MODE_CIPHER: if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { sessionSetupData.cipherSetupData.cipherDirection = CPA_CY_SYM_CIPHER_DIRECTION_ENCRYPT; } else { sessionSetupData.cipherSetupData.cipherDirection = CPA_CY_SYM_CIPHER_DIRECTION_DECRYPT; } sessionSetupData.symOperation = CPA_CY_SYM_OP_CIPHER; break; case CSP_MODE_DIGEST: sessionSetupData.symOperation = CPA_CY_SYM_OP_HASH; if (csp->csp_auth_alg == CRYPTO_AES_NIST_GMAC) { sessionSetupData.symOperation = CPA_CY_SYM_OP_ALGORITHM_CHAINING; /* GMAC is always encrypt */ sessionSetupData.cipherSetupData.cipherDirection = CPA_CY_SYM_CIPHER_DIRECTION_ENCRYPT; sessionSetupData.algChainOrder = CPA_CY_SYM_ALG_CHAIN_ORDER_CIPHER_THEN_HASH; sessionSetupData.cipherSetupData.cipherAlgorithm = CPA_CY_SYM_CIPHER_AES_GCM; sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_AES_GMAC; sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; /* Same key for cipher and auth */ sessionSetupData.cipherSetupData.pCipherKey = csp->csp_auth_key; sessionSetupData.cipherSetupData.cipherKeyLenInBytes = csp->csp_auth_klen; /* Generated GMAC stored in separated buffer */ sessionSetupData.digestIsAppended = CPA_FALSE; /* Digest verification not allowed in GMAC case */ sessionSetupData.verifyDigest = CPA_FALSE; /* No AAD allowed */ sessionSetupData.hashSetupData.authModeSetupData .aadLenInBytes = 0; } else { sessionSetupData.cipherSetupData.cipherDirection = CPA_CY_SYM_CIPHER_DIRECTION_ENCRYPT; sessionSetupData.symOperation = CPA_CY_SYM_OP_HASH; sessionSetupData.digestIsAppended = CPA_FALSE; } break; default: device_printf(dev, "%s: unhandled crypto algorithm %d, %d\n", __func__, csp->csp_cipher_alg, csp->csp_auth_alg); status = CPA_STATUS_FAIL; goto fail; } /* Extracting session size */ status = cpaCySymSessionCtxGetSize(qat_instance->cyInstHandle, &sessionSetupData, &sessionCtxSize); if (CPA_STATUS_SUCCESS != status) { device_printf(dev, "unable to get session size\n"); goto fail; } /* Allocating contiguous memory for session */ sessionCtx = contigmalloc(sessionCtxSize, M_QAT_OCF, M_NOWAIT, 0, ~1UL, 1 << (ilog2(sessionCtxSize - 1) + 1), 0); if (NULL == sessionCtx) { device_printf(dev, "unable to allocate memory for session\n"); status = CPA_STATUS_RESOURCE; goto fail; } status = cpaCySymDpInitSession(qat_instance->cyInstHandle, &sessionSetupData, sessionCtx); if (CPA_STATUS_SUCCESS != status) { device_printf(dev, "session initialization failed\n"); goto fail; } /* NOTE: lets keep double session (both directions) approach to overcome * lack of direction update in FBSD QAT. */ qat_ssession->sessionCtx = sessionCtx; qat_ssession->sessionCtxSize = sessionCtxSize; return CPA_STATUS_SUCCESS; fail: /* Release resources if any */ if (sessionCtx) free(sessionCtx, M_QAT_OCF); return status; } static int qat_ocf_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { /* Cryptodev QAT structures */ struct qat_ocf_softc *qat_softc; struct qat_ocf_dsession *qat_dsession; struct qat_ocf_instance *qat_instance; u_int cpu_id = PCPU_GET(cpuid); /* Create cryptodev session */ qat_softc = device_get_softc(dev); if (qat_softc->numCyInstances > 0) { qat_instance = &qat_softc ->cyInstHandles[cpu_id % qat_softc->numCyInstances]; qat_dsession = crypto_get_driver_session(cses); if (NULL == qat_dsession) { device_printf(dev, "Unable to create new session\n"); return (EINVAL); } /* Add only instance at this point remaining operations moved to * lazy session init */ qat_dsession->qatInstance = qat_instance; } else { return ENXIO; } return 0; } static CpaStatus qat_ocf_remove_session(device_t dev, CpaInstanceHandle cyInstHandle, struct qat_ocf_session *qat_session) { CpaStatus status = CPA_STATUS_SUCCESS; if (NULL == qat_session->sessionCtx) return CPA_STATUS_SUCCESS; /* User callback is executed right before decrementing pending * callback atomic counter. To avoid removing session rejection * we have to wait a very short while for counter update * after call back execution. */ status = qat_ocf_wait_for_session(qat_session->sessionCtx, QAT_OCF_SESSION_WAIT_TIMEOUT_MS); if (CPA_STATUS_SUCCESS != status) { device_printf(dev, "waiting for session un-busy failed\n"); return CPA_STATUS_FAIL; } status = cpaCySymDpRemoveSession(cyInstHandle, qat_session->sessionCtx); if (CPA_STATUS_SUCCESS != status) { device_printf(dev, "error while removing session\n"); return CPA_STATUS_FAIL; } explicit_bzero(qat_session->sessionCtx, qat_session->sessionCtxSize); free(qat_session->sessionCtx, M_QAT_OCF); qat_session->sessionCtx = NULL; qat_session->sessionCtxSize = 0; return CPA_STATUS_SUCCESS; } static void qat_ocf_freesession(device_t dev, crypto_session_t cses) { CpaStatus status = CPA_STATUS_SUCCESS; struct qat_ocf_dsession *qat_dsession = NULL; struct qat_ocf_instance *qat_instance = NULL; qat_dsession = crypto_get_driver_session(cses); qat_instance = qat_dsession->qatInstance; mtx_lock(&qat_instance->cyInstMtx); status = qat_ocf_remove_session(dev, qat_dsession->qatInstance->cyInstHandle, &qat_dsession->encSession); if (CPA_STATUS_SUCCESS != status) device_printf(dev, "unable to remove encrypt session\n"); status = qat_ocf_remove_session(dev, qat_dsession->qatInstance->cyInstHandle, &qat_dsession->decSession); if (CPA_STATUS_SUCCESS != status) device_printf(dev, "unable to remove decrypt session\n"); mtx_unlock(&qat_instance->cyInstMtx); } /* QAT GCM/CCM FW API are only algorithms which support separated AAD. */ static CpaStatus qat_ocf_load_aad_gcm(struct cryptop *crp, struct qat_ocf_cookie *qat_cookie) { CpaCySymDpOpData *pOpData; pOpData = &qat_cookie->pOpdata; if (NULL != crp->crp_aad) memcpy(qat_cookie->qat_ocf_gcm_aad, crp->crp_aad, crp->crp_aad_length); else crypto_copydata(crp, crp->crp_aad_start, crp->crp_aad_length, qat_cookie->qat_ocf_gcm_aad); pOpData->pAdditionalAuthData = qat_cookie->qat_ocf_gcm_aad; pOpData->additionalAuthData = qat_cookie->qat_ocf_gcm_aad_paddr; return CPA_STATUS_SUCCESS; } static CpaStatus qat_ocf_load_aad(struct cryptop *crp, struct qat_ocf_cookie *qat_cookie) { CpaStatus status = CPA_STATUS_SUCCESS; const struct crypto_session_params *csp; CpaCySymDpOpData *pOpData; struct qat_ocf_load_cb_arg args; pOpData = &qat_cookie->pOpdata; pOpData->pAdditionalAuthData = NULL; pOpData->additionalAuthData = 0UL; if (crp->crp_aad_length == 0) return CPA_STATUS_SUCCESS; if (crp->crp_aad_length > ICP_QAT_FW_CCM_GCM_AAD_SZ_MAX) return CPA_STATUS_FAIL; csp = crypto_get_params(crp->crp_session); /* Handle GCM/CCM case */ if (CPA_TRUE == is_sep_aad_supported(csp)) return qat_ocf_load_aad_gcm(crp, qat_cookie); if (NULL == crp->crp_aad) { /* AAD already embedded in source buffer */ pOpData->messageLenToCipherInBytes = crp->crp_payload_length; pOpData->cryptoStartSrcOffsetInBytes = crp->crp_payload_start; pOpData->messageLenToHashInBytes = crp->crp_aad_length + crp->crp_payload_length; pOpData->hashStartSrcOffsetInBytes = crp->crp_aad_start; return CPA_STATUS_SUCCESS; } /* Separated AAD not supported by QAT - lets place the content * of ADD buffer at the very beginning of source SGL */ args.crp_op = crp; args.qat_cookie = qat_cookie; args.pOpData = pOpData; args.error = 0; status = bus_dmamap_load(qat_cookie->gcm_aad_dma_mem.dma_tag, qat_cookie->gcm_aad_dma_mem.dma_map, crp->crp_aad, crp->crp_aad_length, qat_ocf_crypto_load_aadbuf_cb, &args, BUS_DMA_NOWAIT); qat_cookie->is_sep_aad_used = CPA_TRUE; /* Right after this step we have AAD placed in the first flat buffer * in source SGL */ pOpData->messageLenToCipherInBytes = crp->crp_payload_length; pOpData->cryptoStartSrcOffsetInBytes = crp->crp_aad_length + crp->crp_aad_start + crp->crp_payload_start; pOpData->messageLenToHashInBytes = crp->crp_aad_length + crp->crp_payload_length; pOpData->hashStartSrcOffsetInBytes = crp->crp_aad_start; return status; } static CpaStatus qat_ocf_load(struct cryptop *crp, struct qat_ocf_cookie *qat_cookie) { CpaStatus status = CPA_STATUS_SUCCESS; CpaCySymDpOpData *pOpData; struct qat_ocf_load_cb_arg args; /* cryptodev internals */ const struct crypto_session_params *csp; pOpData = &qat_cookie->pOpdata; csp = crypto_get_params(crp->crp_session); /* Load IV buffer if present */ if (csp->csp_ivlen > 0) { memset(qat_cookie->qat_ocf_iv_buf, 0, sizeof(qat_cookie->qat_ocf_iv_buf)); crypto_read_iv(crp, qat_cookie->qat_ocf_iv_buf); pOpData->iv = qat_cookie->qat_ocf_iv_buf_paddr; pOpData->pIv = qat_cookie->qat_ocf_iv_buf; pOpData->ivLenInBytes = csp->csp_ivlen; } /* GCM/CCM - load AAD to separated buffer * AES+SHA - load AAD to first flat in SGL */ status = qat_ocf_load_aad(crp, qat_cookie); if (CPA_STATUS_SUCCESS != status) goto fail; /* Load source buffer */ args.crp_op = crp; args.qat_cookie = qat_cookie; args.pOpData = pOpData; args.error = 0; status = bus_dmamap_load_crp_buffer(qat_cookie->src_dma_mem.dma_tag, qat_cookie->src_dma_mem.dma_map, &crp->crp_buf, qat_ocf_crypto_load_buf_cb, &args, BUS_DMA_NOWAIT); if (CPA_STATUS_SUCCESS != status) goto fail; pOpData->srcBuffer = qat_cookie->src_buffer_list_paddr; pOpData->srcBufferLen = CPA_DP_BUFLIST; /* Load destination buffer */ if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) { status = bus_dmamap_load_crp_buffer(qat_cookie->dst_dma_mem.dma_tag, qat_cookie->dst_dma_mem.dma_map, &crp->crp_obuf, qat_ocf_crypto_load_obuf_cb, &args, BUS_DMA_NOWAIT); if (CPA_STATUS_SUCCESS != status) goto fail; pOpData->dstBuffer = qat_cookie->dst_buffer_list_paddr; pOpData->dstBufferLen = CPA_DP_BUFLIST; } else { pOpData->dstBuffer = pOpData->srcBuffer; pOpData->dstBufferLen = pOpData->srcBufferLen; } if (CPA_TRUE == is_use_sep_digest(csp)) pOpData->digestResult = qat_cookie->qat_ocf_digest_paddr; else pOpData->digestResult = 0UL; /* GMAC - aka zero length buffer */ if (CPA_TRUE == is_gmac_exception(csp)) pOpData->messageLenToCipherInBytes = 0; fail: return status; } static int qat_ocf_check_input(device_t dev, struct cryptop *crp) { const struct crypto_session_params *csp; csp = crypto_get_params(crp->crp_session); if (crypto_buffer_len(&crp->crp_buf) > QAT_OCF_MAX_LEN) return E2BIG; if (CPA_TRUE == is_sep_aad_supported(csp) && (crp->crp_aad_length > ICP_QAT_FW_CCM_GCM_AAD_SZ_MAX)) return EBADMSG; return 0; } static int qat_ocf_process(device_t dev, struct cryptop *crp, int hint) { CpaStatus status = CPA_STATUS_SUCCESS; int rc = 0; struct qat_ocf_dsession *qat_dsession = NULL; struct qat_ocf_session *qat_session = NULL; struct qat_ocf_instance *qat_instance = NULL; CpaCySymDpOpData *pOpData = NULL; struct qat_ocf_cookie *qat_cookie = NULL; CpaBoolean memLoaded = CPA_FALSE; rc = qat_ocf_check_input(dev, crp); if (rc) goto fail; qat_dsession = crypto_get_driver_session(crp->crp_session); if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) qat_session = &qat_dsession->encSession; else qat_session = &qat_dsession->decSession; qat_instance = qat_dsession->qatInstance; status = qat_ocf_cookie_alloc(qat_instance, &qat_cookie); if (CPA_STATUS_SUCCESS != status) { rc = EAGAIN; goto fail; } qat_cookie->crp_op = crp; /* Common request fields */ pOpData = &qat_cookie->pOpdata; pOpData->instanceHandle = qat_instance->cyInstHandle; pOpData->sessionCtx = NULL; /* Cipher fields */ pOpData->cryptoStartSrcOffsetInBytes = crp->crp_payload_start; pOpData->messageLenToCipherInBytes = crp->crp_payload_length; /* Digest fields - any exceptions from this basic rules are covered * in qat_ocf_load */ pOpData->hashStartSrcOffsetInBytes = crp->crp_payload_start; pOpData->messageLenToHashInBytes = crp->crp_payload_length; status = qat_ocf_load(crp, qat_cookie); if (CPA_STATUS_SUCCESS != status) { device_printf(dev, "unable to load OCF buffers to QAT DMA " "transaction\n"); rc = EIO; goto fail; } memLoaded = CPA_TRUE; status = qat_ocf_cookie_dma_pre_sync(crp, pOpData); if (CPA_STATUS_SUCCESS != status) { device_printf(dev, "unable to sync DMA buffers\n"); rc = EIO; goto fail; } mtx_lock(&qat_instance->cyInstMtx); /* Session initialization at the first request. It's done * in such way to overcome missing QAT specific session data * such like AAD length and limited possibility to update * QAT session while handling traffic. */ if (NULL == qat_session->sessionCtx) { status = qat_ocf_session_init(dev, crp, qat_instance, qat_session); if (CPA_STATUS_SUCCESS != status) { mtx_unlock(&qat_instance->cyInstMtx); device_printf(dev, "unable to init session\n"); rc = EIO; goto fail; } } else { status = qat_ocf_handle_session_update(qat_dsession, crp); if (CPA_STATUS_RESOURCE == status) { mtx_unlock(&qat_instance->cyInstMtx); rc = EAGAIN; goto fail; } else if (CPA_STATUS_SUCCESS != status) { mtx_unlock(&qat_instance->cyInstMtx); rc = EIO; goto fail; } } pOpData->sessionCtx = qat_session->sessionCtx; status = cpaCySymDpEnqueueOp(pOpData, CPA_TRUE); mtx_unlock(&qat_instance->cyInstMtx); if (CPA_STATUS_SUCCESS != status) { if (CPA_STATUS_RETRY == status) { rc = EAGAIN; goto fail; } device_printf(dev, "unable to send request. Status: %d\n", status); rc = EIO; goto fail; } return 0; fail: if (qat_cookie) { if (memLoaded) qat_ocf_cookie_dma_unload(crp, pOpData); qat_ocf_cookie_free(qat_instance, qat_cookie); } crp->crp_etype = rc; crypto_done(crp); return 0; } static void qat_ocf_identify(driver_t *drv, device_t parent) { - if (device_find_child(parent, "qat_ocf", -1) == NULL && - BUS_ADD_CHILD(parent, 200, "qat_ocf", -1) == 0) + if (device_find_child(parent, "qat_ocf", DEVICE_UNIT_ANY) == NULL && + BUS_ADD_CHILD(parent, 200, "qat_ocf", DEVICE_UNIT_ANY) == 0) device_printf(parent, "qat_ocf: could not attach!"); } static int qat_ocf_probe(device_t dev) { device_set_desc(dev, "QAT engine"); return (BUS_PROBE_NOWILDCARD); } static CpaStatus qat_ocf_get_irq_instances(CpaInstanceHandle *cyInstHandles, Cpa16U cyInstHandlesSize, Cpa16U *foundInstances) { CpaStatus status = CPA_STATUS_SUCCESS; icp_accel_dev_t **pAdfInsts = NULL; icp_accel_dev_t *dev_addr = NULL; sal_t *baseAddr = NULL; sal_list_t *listTemp = NULL; CpaInstanceHandle cyInstHandle; CpaInstanceInfo2 info; Cpa16U numDevices; Cpa32U instCtr = 0; Cpa32U i; /* Get the number of devices */ status = icp_amgr_getNumInstances(&numDevices); if (CPA_STATUS_SUCCESS != status) return status; /* Allocate memory to store addr of accel_devs */ pAdfInsts = malloc(numDevices * sizeof(icp_accel_dev_t *), M_QAT_OCF, M_WAITOK); /* Get ADF to return all accel_devs that support either * symmetric or asymmetric crypto */ status = icp_amgr_getAllAccelDevByCapabilities( (ICP_ACCEL_CAPABILITIES_CRYPTO_SYMMETRIC), pAdfInsts, &numDevices); if (CPA_STATUS_SUCCESS != status) { free(pAdfInsts, M_QAT_OCF); return status; } for (i = 0; i < numDevices; i++) { dev_addr = (icp_accel_dev_t *)pAdfInsts[i]; baseAddr = dev_addr->pSalHandle; if (NULL == baseAddr) continue; listTemp = baseAddr->sym_services; if (NULL == listTemp) { listTemp = baseAddr->crypto_services; } while (NULL != listTemp) { cyInstHandle = SalList_getObject(listTemp); status = cpaCyInstanceGetInfo2(cyInstHandle, &info); if (CPA_STATUS_SUCCESS != status) continue; listTemp = SalList_next(listTemp); if (CPA_TRUE == info.isPolled) continue; if (instCtr >= cyInstHandlesSize) break; cyInstHandles[instCtr++] = cyInstHandle; } } free(pAdfInsts, M_QAT_OCF); *foundInstances = instCtr; return CPA_STATUS_SUCCESS; } static CpaStatus qat_ocf_start_instances(struct qat_ocf_softc *qat_softc, device_t dev) { CpaStatus status = CPA_STATUS_SUCCESS; Cpa16U numInstances = 0; CpaInstanceHandle cyInstHandles[QAT_OCF_MAX_INSTANCES] = { 0 }; CpaInstanceHandle cyInstHandle = NULL; Cpa32U startedInstances = 0; Cpa32U i; qat_softc->numCyInstances = 0; status = qat_ocf_get_irq_instances(cyInstHandles, QAT_OCF_MAX_INSTANCES, &numInstances); if (CPA_STATUS_SUCCESS != status) return status; for (i = 0; i < numInstances; i++) { struct qat_ocf_instance *qat_ocf_instance; cyInstHandle = cyInstHandles[i]; if (!cyInstHandle) continue; /* Starting instance */ status = cpaCyStartInstance(cyInstHandle); if (CPA_STATUS_SUCCESS != status) { device_printf(qat_softc->sc_dev, "unable to get start instance\n"); continue; } qat_ocf_instance = &qat_softc->cyInstHandles[startedInstances]; qat_ocf_instance->cyInstHandle = cyInstHandle; mtx_init(&qat_ocf_instance->cyInstMtx, "Instance MTX", NULL, MTX_DEF); status = cpaCySetAddressTranslation(cyInstHandle, qatVirtToPhys); if (CPA_STATUS_SUCCESS != status) { device_printf(qat_softc->sc_dev, "unable to add virt to phys callback\n"); goto fail; } status = cpaCySymDpRegCbFunc(cyInstHandle, symDpCallback); if (CPA_STATUS_SUCCESS != status) { device_printf(qat_softc->sc_dev, "unable to add user callback\n"); goto fail; } /* Initialize cookie pool */ status = qat_ocf_cookie_pool_init(qat_ocf_instance, dev); if (CPA_STATUS_SUCCESS != status) { device_printf(qat_softc->sc_dev, "unable to create cookie pool\n"); goto fail; } /* Disable forcing HW MAC validation for AEAD */ status = icp_sal_setForceAEADMACVerify(cyInstHandle, CPA_FALSE); if (CPA_STATUS_SUCCESS != status) { device_printf( qat_softc->sc_dev, "unable to disable AEAD HW MAC verification\n"); goto fail; } qat_ocf_instance->driver_id = qat_softc->cryptodev_id; startedInstances++; continue; fail: mtx_destroy(&qat_ocf_instance->cyInstMtx); /* Stop instance */ status = cpaCyStopInstance(cyInstHandle); if (CPA_STATUS_SUCCESS != status) device_printf(qat_softc->sc_dev, "unable to stop the instance\n"); } qat_softc->numCyInstances = startedInstances; return CPA_STATUS_SUCCESS; } static CpaStatus qat_ocf_stop_instances(struct qat_ocf_softc *qat_softc) { CpaStatus status = CPA_STATUS_SUCCESS; int i; for (i = 0; i < qat_softc->numCyInstances; i++) { struct qat_ocf_instance *qat_instance; qat_instance = &qat_softc->cyInstHandles[i]; status = cpaCyStopInstance(qat_instance->cyInstHandle); if (CPA_STATUS_SUCCESS != status) { pr_err("QAT: stopping instance id: %d failed\n", i); continue; } qat_ocf_cookie_pool_deinit(qat_instance); mtx_destroy(&qat_instance->cyInstMtx); } qat_softc->numCyInstances = 0; return status; } static int qat_ocf_deinit(struct qat_ocf_softc *qat_softc) { int status = 0; CpaStatus cpaStatus; if (qat_softc->cryptodev_id >= 0) { crypto_unregister_all(qat_softc->cryptodev_id); qat_softc->cryptodev_id = -1; } /* Stop QAT instances */ cpaStatus = qat_ocf_stop_instances(qat_softc); if (CPA_STATUS_SUCCESS != cpaStatus) { device_printf(qat_softc->sc_dev, "unable to stop instances\n"); status = EIO; } return status; } static int qat_ocf_init(struct qat_ocf_softc *qat_softc) { int32_t cryptodev_id; /* Starting instances for OCF */ if (qat_ocf_start_instances(qat_softc, qat_softc->sc_dev)) { device_printf(qat_softc->sc_dev, "unable to get QAT IRQ instances\n"); goto fail; } /* Register only if instances available */ if (qat_softc->numCyInstances) { cryptodev_id = crypto_get_driverid(qat_softc->sc_dev, sizeof(struct qat_ocf_dsession), CRYPTOCAP_F_HARDWARE); if (cryptodev_id < 0) { device_printf(qat_softc->sc_dev, "cannot initialize!\n"); goto fail; } qat_softc->cryptodev_id = cryptodev_id; } return 0; fail: qat_ocf_deinit(qat_softc); return ENXIO; } static int qat_ocf_sysctl_handle(SYSCTL_HANDLER_ARGS) { struct qat_ocf_softc *qat_softc = NULL; int ret = 0; device_t dev = arg1; u_int enabled; qat_softc = device_get_softc(dev); enabled = qat_softc->enabled; ret = sysctl_handle_int(oidp, &enabled, 0, req); if (ret || !req->newptr) return (ret); if (qat_softc->enabled != enabled) { if (enabled) { ret = qat_ocf_init(qat_softc); } else { ret = qat_ocf_deinit(qat_softc); } if (!ret) qat_softc->enabled = enabled; } return ret; } static int qat_ocf_attach(device_t dev) { int status; struct qat_ocf_softc *qat_softc; qat_softc = device_get_softc(dev); qat_softc->sc_dev = dev; qat_softc->cryptodev_id = -1; qat_softc->enabled = 1; qat_softc->rc = SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "enable", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, dev, 0, qat_ocf_sysctl_handle, "I", "QAT OCF support enablement"); if (!qat_softc->rc) return ENOMEM; if (qat_softc->enabled) { status = qat_ocf_init(qat_softc); if (status) { device_printf(dev, "qat_ocf init failed\n"); goto fail; } } return 0; fail: qat_ocf_deinit(qat_softc); return (ENXIO); } static int qat_ocf_detach(device_t dev) { struct qat_ocf_softc *qat_softc = device_get_softc(dev); return qat_ocf_deinit(qat_softc); } static device_method_t qat_ocf_methods[] = { DEVMETHOD(device_identify, qat_ocf_identify), DEVMETHOD(device_probe, qat_ocf_probe), DEVMETHOD(device_attach, qat_ocf_attach), DEVMETHOD(device_detach, qat_ocf_detach), /* Cryptodev interface */ DEVMETHOD(cryptodev_probesession, qat_ocf_probesession), DEVMETHOD(cryptodev_newsession, qat_ocf_newsession), DEVMETHOD(cryptodev_freesession, qat_ocf_freesession), DEVMETHOD(cryptodev_process, qat_ocf_process), DEVMETHOD_END }; static driver_t qat_ocf_driver = { .name = "qat_ocf", .methods = qat_ocf_methods, .size = sizeof(struct qat_ocf_softc), }; DRIVER_MODULE_ORDERED(qat, nexus, qat_ocf_driver, NULL, NULL, SI_ORDER_ANY); MODULE_VERSION(qat, 1); MODULE_DEPEND(qat, qat_c62x, 1, 1, 1); MODULE_DEPEND(qat, qat_200xx, 1, 1, 1); MODULE_DEPEND(qat, qat_c3xxx, 1, 1, 1); MODULE_DEPEND(qat, qat_c4xxx, 1, 1, 1); MODULE_DEPEND(qat, qat_dh895xcc, 1, 1, 1); MODULE_DEPEND(qat, qat_4xxx, 1, 1, 1); MODULE_DEPEND(qat, crypto, 1, 1, 1); MODULE_DEPEND(qat, qat_common, 1, 1, 1); MODULE_DEPEND(qat, qat_api, 1, 1, 1); MODULE_DEPEND(qat, linuxkpi, 1, 1, 1); diff --git a/sys/dev/qcom_qup/qcom_spi.c b/sys/dev/qcom_qup/qcom_spi.c index 88341b4d2083..87e70d531324 100644 --- a/sys/dev/qcom_qup/qcom_spi.c +++ b/sys/dev/qcom_qup/qcom_spi.c @@ -1,904 +1,904 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021, Adrian Chadd * * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" #include #include #include #include static struct ofw_compat_data compat_data[] = { { "qcom,spi-qup-v1.1.1", QCOM_SPI_HW_QPI_V1_1 }, { "qcom,spi-qup-v2.1.1", QCOM_SPI_HW_QPI_V2_1 }, { "qcom,spi-qup-v2.2.1", QCOM_SPI_HW_QPI_V2_2 }, { NULL, 0 } }; /* * Flip the CS GPIO line either active or inactive. * * Actually listen to the CS polarity. */ static void qcom_spi_set_chipsel(struct qcom_spi_softc *sc, int cs, bool active) { bool pinactive; bool invert = !! (cs & SPIBUS_CS_HIGH); cs = cs & ~SPIBUS_CS_HIGH; if (sc->cs_pins[cs] == NULL) { device_printf(sc->sc_dev, "%s: cs=%u, active=%u, invert=%u, no gpio?\n", __func__, cs, active, invert); return; } QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_CHIPSELECT, "%s: cs=%u active=%u\n", __func__, cs, active); /* * Default rule here is CS is active low. */ if (active) pinactive = false; else pinactive = true; /* * Invert the CS line if required. */ if (invert) pinactive = !! pinactive; gpio_pin_set_active(sc->cs_pins[cs], pinactive); gpio_pin_is_active(sc->cs_pins[cs], &pinactive); } static void qcom_spi_intr(void *arg) { struct qcom_spi_softc *sc = arg; int ret; QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, "%s: called\n", __func__); QCOM_SPI_LOCK(sc); ret = qcom_spi_hw_interrupt_handle(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to read intr status\n"); goto done; } /* * Handle spurious interrupts outside of an actual * transfer. */ if (sc->transfer.active == false) { device_printf(sc->sc_dev, "ERROR: spurious interrupt\n"); qcom_spi_hw_ack_opmode(sc); goto done; } /* Now, handle interrupts */ if (sc->intr.error) { sc->intr.error = false; device_printf(sc->sc_dev, "ERROR: intr\n"); } if (sc->intr.do_rx) { sc->intr.do_rx = false; QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, "%s: PIO_READ\n", __func__); if (sc->state.transfer_mode == QUP_IO_M_MODE_FIFO) ret = qcom_spi_hw_read_pio_fifo(sc); else ret = qcom_spi_hw_read_pio_block(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_read failed (%u)\n", ret); goto done; } } if (sc->intr.do_tx) { sc->intr.do_tx = false; QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, "%s: PIO_WRITE\n", __func__); /* * For FIFO operations we do not do a write here, we did * it at the beginning of the transfer. * * For BLOCK operations yes, we call the routine. */ if (sc->state.transfer_mode == QUP_IO_M_MODE_FIFO) ret = qcom_spi_hw_ack_write_pio_fifo(sc); else ret = qcom_spi_hw_write_pio_block(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_write failed (%u)\n", ret); goto done; } } /* * Do this last. We may actually have completed the * transfer in the PIO receive path above and it will * set the done flag here. */ if (sc->intr.done) { sc->intr.done = false; sc->transfer.done = true; QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, "%s: transfer done\n", __func__); wakeup(sc); } done: QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_INTR, "%s: done\n", __func__); QCOM_SPI_UNLOCK(sc); } static int qcom_spi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "Qualcomm SPI Interface"); return (BUS_PROBE_DEFAULT); } /* * Allocate GPIOs if provided in the SPI controller block. * * Some devices will use GPIO lines for chip select. * It's also quite annoying because some devices will want to use * the hardware provided CS gating for say, the first chipselect block, * and then use GPIOs for the later ones. * * So here we just assume for now that SPI index 0 uses the hardware * lines, and >0 use GPIO lines. Revisit this if better hardware * shows up. * * And finally, iterating over the cs-gpios list to allocate GPIOs * doesn't actually tell us what the polarity is. For that we need * to actually iterate over the list of child nodes and check what * their properties are (and look for "spi-cs-high".) */ static void qcom_spi_attach_gpios(struct qcom_spi_softc *sc) { phandle_t node; int idx, err; /* Allocate gpio pins for configured chip selects. */ node = ofw_bus_get_node(sc->sc_dev); for (idx = 0; idx < nitems(sc->cs_pins); idx++) { err = gpio_pin_get_by_ofw_propidx(sc->sc_dev, node, "cs-gpios", idx, &sc->cs_pins[idx]); if (err == 0) { err = gpio_pin_setflags(sc->cs_pins[idx], GPIO_PIN_OUTPUT); if (err != 0) { device_printf(sc->sc_dev, "error configuring gpio for" " cs %u (%d)\n", idx, err); } /* * We can't set this HIGH right now because * we don't know if it needs to be set to * high for inactive or low for inactive * based on the child SPI device flags. */ #if 0 gpio_pin_set_active(sc->cs_pins[idx], 1); gpio_pin_is_active(sc->cs_pins[idx], &tmp); #endif } else { device_printf(sc->sc_dev, "cannot configure gpio for chip select %u\n", idx); sc->cs_pins[idx] = NULL; } } } static void qcom_spi_sysctl_attach(struct qcom_spi_softc *sc) { struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, "control debugging printfs"); } static int qcom_spi_attach(device_t dev) { struct qcom_spi_softc *sc = device_get_softc(dev); int rid, ret, i, val; sc->sc_dev = dev; /* * Hardware version is stored in the ofw_compat_data table. */ sc->hw_version = ofw_bus_search_compatible(dev, compat_data)->ocd_data; mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_mem_res) { device_printf(dev, "ERROR: Could not map memory\n"); ret = ENXIO; goto error; } rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); if (!sc->sc_irq_res) { device_printf(dev, "ERROR: Could not map interrupt\n"); ret = ENXIO; goto error; } ret = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, qcom_spi_intr, sc, &sc->sc_irq_h); if (ret != 0) { device_printf(dev, "ERROR: could not configure interrupt " "(%d)\n", ret); goto error; } qcom_spi_attach_gpios(sc); ret = clk_get_by_ofw_name(dev, 0, "core", &sc->clk_core); if (ret != 0) { device_printf(dev, "ERROR: could not get %s clock (%d)\n", "core", ret); goto error; } ret = clk_get_by_ofw_name(dev, 0, "iface", &sc->clk_iface); if (ret != 0) { device_printf(dev, "ERROR: could not get %s clock (%d)\n", "iface", ret); goto error; } /* Bring up initial clocks if they're off */ ret = clk_enable(sc->clk_core); if (ret != 0) { device_printf(dev, "ERROR: couldn't enable core clock (%u)\n", ret); goto error; } ret = clk_enable(sc->clk_iface); if (ret != 0) { device_printf(dev, "ERROR: couldn't enable iface clock (%u)\n", ret); goto error; } /* * Read optional spi-max-frequency */ if (OF_getencprop(ofw_bus_get_node(dev), "spi-max-frequency", &val, sizeof(val)) > 0) sc->config.max_frequency = val; else sc->config.max_frequency = SPI_MAX_RATE; /* * Read optional cs-select */ if (OF_getencprop(ofw_bus_get_node(dev), "cs-select", &val, sizeof(val)) > 0) sc->config.cs_select = val; else sc->config.cs_select = 0; /* * Read optional num-cs */ if (OF_getencprop(ofw_bus_get_node(dev), "num-cs", &val, sizeof(val)) > 0) sc->config.num_cs = val; else sc->config.num_cs = SPI_NUM_CHIPSELECTS; ret = fdt_pinctrl_configure_by_name(dev, "default"); if (ret != 0) { device_printf(dev, "ERROR: could not configure default pinmux\n"); goto error; } ret = qcom_spi_hw_read_controller_transfer_sizes(sc); if (ret != 0) { device_printf(dev, "ERROR: Could not read transfer config\n"); goto error; } device_printf(dev, "BLOCK: input=%u bytes, output=%u bytes\n", sc->config.input_block_size, sc->config.output_block_size); device_printf(dev, "FIFO: input=%u bytes, output=%u bytes\n", sc->config.input_fifo_size, sc->config.output_fifo_size); /* QUP config */ QCOM_SPI_LOCK(sc); ret = qcom_spi_hw_qup_init_locked(sc); if (ret != 0) { device_printf(dev, "ERROR: QUP init failed (%d)\n", ret); QCOM_SPI_UNLOCK(sc); goto error; } /* Initial SPI config */ ret = qcom_spi_hw_spi_init_locked(sc); if (ret != 0) { device_printf(dev, "ERROR: SPI init failed (%d)\n", ret); QCOM_SPI_UNLOCK(sc); goto error; } QCOM_SPI_UNLOCK(sc); - sc->spibus = device_add_child(dev, "spibus", -1); + sc->spibus = device_add_child(dev, "spibus", DEVICE_UNIT_ANY); /* We're done, so shut down the interface clock for now */ device_printf(dev, "DONE: shutting down interface clock for now\n"); clk_disable(sc->clk_iface); /* Register for debug sysctl */ qcom_spi_sysctl_attach(sc); bus_attach_children(dev); return (0); error: if (sc->sc_irq_h) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_h); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); if (sc->clk_core) { clk_disable(sc->clk_core); clk_release(sc->clk_core); } if (sc->clk_iface) { clk_disable(sc->clk_iface); clk_release(sc->clk_iface); } for (i = 0; i < CS_MAX; i++) { if (sc->cs_pins[i] != NULL) gpio_pin_release(sc->cs_pins[i]); } mtx_destroy(&sc->sc_mtx); return (ret); } /* * Do a PIO transfer. * * Note that right now the TX/RX lens need to match, I'm not doing * dummy reads / dummy writes as required if they're not the same * size. The QUP hardware supports doing multi-phase transactions * where the FIFO isn't engaged for transmit or receive, but it's * not yet being done here. */ static int qcom_spi_transfer_pio_block(struct qcom_spi_softc *sc, int mode, char *tx_buf, int tx_len, char *rx_buf, int rx_len) { int ret = 0; QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, "%s: start\n", __func__); if (rx_len != tx_len) { device_printf(sc->sc_dev, "ERROR: tx/rx len doesn't match (%d/%d)\n", tx_len, rx_len); return (ENXIO); } QCOM_SPI_ASSERT_LOCKED(sc); /* * Make initial choices for transfer configuration. */ ret = qcom_spi_hw_setup_transfer_selection(sc, tx_len); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to setup transfer selection (%d)\n", ret); return (ret); } /* Now set suitable buffer/lengths */ sc->transfer.tx_buf = tx_buf; sc->transfer.tx_len = tx_len; sc->transfer.rx_buf = rx_buf; sc->transfer.rx_len = rx_len; sc->transfer.done = false; sc->transfer.active = false; /* * Loop until the full transfer set is done. * * qcom_spi_hw_setup_current_transfer() will take care of * setting a maximum transfer size for the hardware and choose * a suitable operating mode. */ while (sc->transfer.tx_offset < sc->transfer.tx_len) { /* * Set transfer to false early; this covers * it also finishing a sub-transfer and we're * about the put the block into RESET state before * starting a new transfer. */ sc->transfer.active = false; QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, "%s: tx=%d of %d bytes, rx=%d of %d bytes\n", __func__, sc->transfer.tx_offset, sc->transfer.tx_len, sc->transfer.rx_offset, sc->transfer.rx_len); /* * Set state to RESET before doing anything. * * Otherwise the second sub-transfer that we queue up * will generate interrupts immediately when we start * configuring it here and it'll start underflowing. */ ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RESET); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: can't transition to RESET (%u)\n", ret); goto done; } /* blank interrupt state; we'll do a RESET below */ bzero(&sc->intr, sizeof(sc->intr)); sc->transfer.done = false; /* * Configure what the transfer configuration for this * sub-transfer will be. */ ret = qcom_spi_hw_setup_current_transfer(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to setup sub transfer (%d)\n", ret); goto done; } /* * For now since we're configuring up PIO, we only setup * the PIO transfer size. */ ret = qcom_spi_hw_setup_pio_transfer_cnt(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_setup_pio_transfer_cnt failed" " (%u)\n", ret); goto done; } #if 0 /* * This is what we'd do to setup the block transfer sizes. */ ret = qcom_spi_hw_setup_block_transfer_cnt(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_setup_block_transfer_cnt failed" " (%u)\n", ret); goto done; } #endif ret = qcom_spi_hw_setup_io_modes(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_setup_io_modes failed" " (%u)\n", ret); goto done; } ret = qcom_spi_hw_setup_spi_io_clock_polarity(sc, !! (mode & SPIBUS_MODE_CPOL)); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_setup_spi_io_clock_polarity" " failed (%u)\n", ret); goto done; } ret = qcom_spi_hw_setup_spi_config(sc, sc->state.frequency, !! (mode & SPIBUS_MODE_CPHA)); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_setup_spi_config failed" " (%u)\n", ret); goto done; } ret = qcom_spi_hw_setup_qup_config(sc, !! (tx_len > 0), !! (rx_len > 0)); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_setup_qup_config failed" " (%u)\n", ret); goto done; } ret = qcom_spi_hw_setup_operational_mask(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_setup_operational_mask failed" " (%u)\n", ret); goto done; } /* * Setup is done; reset the controller and start the PIO * write. */ /* * Set state to RUN; we may start getting interrupts that * are valid and we need to handle. */ sc->transfer.active = true; ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RUN); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: can't transition to RUN (%u)\n", ret); goto done; } /* * Set state to PAUSE */ ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_PAUSE); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: can't transition to PAUSE (%u)\n", ret); goto done; } /* * If FIFO mode, write data now. Else, we'll get an * interrupt when it's time to populate more data * in BLOCK mode. */ if (sc->state.transfer_mode == QUP_IO_M_MODE_FIFO) ret = qcom_spi_hw_write_pio_fifo(sc); else ret = qcom_spi_hw_write_pio_block(sc); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: qcom_spi_hw_write failed (%u)\n", ret); goto done; } /* * Set state to RUN */ ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RUN); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: can't transition to RUN (%u)\n", ret); goto done; } /* * Wait for an interrupt notification (which will * continue to drive the state machine for this * sub-transfer) or timeout. */ ret = 0; while (ret == 0 && sc->transfer.done == false) { QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, "%s: waiting\n", __func__); ret = msleep(sc, &sc->sc_mtx, 0, "qcom_spi", 0); } } done: /* * Complete; put controller into reset. * * Don't worry about return value here; if we errored out above then * we want to communicate that value to the caller. */ (void) qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RESET); QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, "%s: completed\n", __func__); /* * Blank the transfer state so we don't use an old transfer * state in a subsequent interrupt. */ (void) qcom_spi_hw_complete_transfer(sc); sc->transfer.active = false; return (ret); } static int qcom_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct qcom_spi_softc *sc = device_get_softc(dev); uint32_t cs_val, mode_val, clock_val; uint32_t ret = 0; spibus_get_cs(child, &cs_val); spibus_get_clock(child, &clock_val); spibus_get_mode(child, &mode_val); QCOM_SPI_DPRINTF(sc, QCOM_SPI_DEBUG_TRANSFER, "%s: called; child cs=0x%08x, clock=%u, mode=0x%08x, " "cmd=%u/%u bytes; data=%u/%u bytes\n", __func__, cs_val, clock_val, mode_val, cmd->tx_cmd_sz, cmd->rx_cmd_sz, cmd->tx_data_sz, cmd->rx_data_sz); QCOM_SPI_LOCK(sc); /* * wait until the controller isn't busy */ while (sc->sc_busy == true) mtx_sleep(sc, &sc->sc_mtx, 0, "qcom_spi_wait", 0); /* * it's ours now! */ sc->sc_busy = true; sc->state.cs_high = !! (cs_val & SPIBUS_CS_HIGH); sc->state.frequency = clock_val; /* * We can't set the clock frequency and enable it * with the driver lock held, as the SPI lock is non-sleepable * and the clock framework is sleepable. * * No other transaction is going on right now, so we can * unlock here and do the clock related work. */ QCOM_SPI_UNLOCK(sc); /* * Set the clock frequency */ ret = clk_set_freq(sc->clk_iface, sc->state.frequency, 0); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to set frequency to %u\n", sc->state.frequency); goto done2; } clk_enable(sc->clk_iface); QCOM_SPI_LOCK(sc); /* * Set state to RESET */ ret = qcom_spi_hw_qup_set_state_locked(sc, QUP_STATE_RESET); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: can't transition to RESET (%u)\n", ret); goto done; } /* Assert hardware CS if set, else GPIO */ if (sc->cs_pins[cs_val & ~SPIBUS_CS_HIGH] == NULL) qcom_spi_hw_spi_cs_force(sc, cs_val & SPIBUS_CS_HIGH, true); else qcom_spi_set_chipsel(sc, cs_val & ~SPIBUS_CS_HIGH, true); /* * cmd buffer transfer */ ret = qcom_spi_transfer_pio_block(sc, mode_val, cmd->tx_cmd, cmd->tx_cmd_sz, cmd->rx_cmd, cmd->rx_cmd_sz); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to transfer cmd payload (%u)\n", ret); goto done; } /* * data buffer transfer */ if (cmd->tx_data_sz > 0) { ret = qcom_spi_transfer_pio_block(sc, mode_val, cmd->tx_data, cmd->tx_data_sz, cmd->rx_data, cmd->rx_data_sz); if (ret != 0) { device_printf(sc->sc_dev, "ERROR: failed to transfer data payload (%u)\n", ret); goto done; } } done: /* De-assert GPIO/CS */ if (sc->cs_pins[cs_val & ~SPIBUS_CS_HIGH] == NULL) qcom_spi_hw_spi_cs_force(sc, cs_val & ~SPIBUS_CS_HIGH, false); else qcom_spi_set_chipsel(sc, cs_val & ~SPIBUS_CS_HIGH, false); /* * Similarly to when we enabled the clock, we can't hold it here * across a clk API as that's a sleep lock and we're non-sleepable. * So instead we unlock/relock here, but we still hold the busy flag. */ QCOM_SPI_UNLOCK(sc); clk_disable(sc->clk_iface); QCOM_SPI_LOCK(sc); done2: /* * We're done; so mark the bus as not busy and wakeup * the next caller. */ sc->sc_busy = false; wakeup_one(sc); QCOM_SPI_UNLOCK(sc); return (ret); } static int qcom_spi_detach(device_t dev) { struct qcom_spi_softc *sc = device_get_softc(dev); int i; bus_generic_detach(sc->sc_dev); if (sc->sc_irq_h) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_h); if (sc->clk_iface) { clk_disable(sc->clk_iface); clk_release(sc->clk_iface); } if (sc->clk_core) { clk_disable(sc->clk_core); clk_release(sc->clk_core); } for (i = 0; i < CS_MAX; i++) { if (sc->cs_pins[i] != NULL) gpio_pin_release(sc->cs_pins[i]); } if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); mtx_destroy(&sc->sc_mtx); return (0); } static phandle_t qcom_spi_get_node(device_t bus, device_t dev) { return ofw_bus_get_node(bus); } static device_method_t qcom_spi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, qcom_spi_probe), DEVMETHOD(device_attach, qcom_spi_attach), DEVMETHOD(device_detach, qcom_spi_detach), /* TODO: suspend */ /* TODO: resume */ DEVMETHOD(spibus_transfer, qcom_spi_transfer), /* ofw_bus_if */ DEVMETHOD(ofw_bus_get_node, qcom_spi_get_node), DEVMETHOD_END }; static driver_t qcom_spi_driver = { "qcom_spi", qcom_spi_methods, sizeof(struct qcom_spi_softc), }; DRIVER_MODULE(qcom_spi, simplebus, qcom_spi_driver, 0, 0); DRIVER_MODULE(ofw_spibus, qcom_spi, ofw_spibus_driver, 0, 0); MODULE_DEPEND(qcom_spi, ofw_spibus, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); diff --git a/sys/dev/sdio/sdiob.c b/sys/dev/sdio/sdiob.c index 701ade4b3467..4ec2058fa2e4 100644 --- a/sys/dev/sdio/sdiob.c +++ b/sys/dev/sdio/sdiob.c @@ -1,1171 +1,1171 @@ /*- * Copyright (c) 2017 Ilya Bakulin. All rights reserved. * Copyright (c) 2018-2019 The FreeBSD Foundation * * Portions of this software were developed by Bjรถrn Zeeb * under sponsorship from the FreeBSD Foundation. * * 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, 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. * * * Portions of this software may have been developed with reference to * the SD Simplified Specification. The following disclaimer may apply: * * The following conditions apply to the release of the simplified * specification ("Simplified Specification") by the SD Card Association and * the SD Group. The Simplified Specification is a subset of the complete SD * Specification which is owned by the SD Card Association and the SD * Group. This Simplified Specification is provided on a non-confidential * basis subject to the disclaimers below. Any implementation of the * Simplified Specification may require a license from the SD Card * Association, SD Group, SD-3C LLC or other third parties. * * Disclaimers: * * The information contained in the Simplified Specification is presented only * as a standard specification for SD Cards and SD Host/Ancillary products and * is provided "AS-IS" without any representations or warranties of any * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD * Card Association for any damages, any infringements of patents or other * right of the SD Group, SD-3C LLC, the SD Card Association or any third * parties, which may result from its use. No license is granted by * implication, estoppel or otherwise under any patent or other rights of the * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing * herein shall be construed as an obligation by the SD Group, the SD-3C LLC * or the SD Card Association to disclose or distribute any technical * information, know-how or other confidential information to any third party. */ /* * Implements the (kernel specific) SDIO parts. * This will hide all cam(4) functionality from the SDIO driver implementations * which will just be newbus/device(9) and hence look like any other driver for, * e.g., PCI. * The sdiob(4) parts effetively "translate" between the two worlds "bridging" * messages from MMCCAM to newbus and back. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for cam_path */ #include #include #include #include #include "sdio_if.h" #ifdef DEBUG #define DPRINTF(...) printf(__VA_ARGS__) #define DPRINTFDEV(_dev, ...) device_printf((_dev), __VA_ARGS__) #else #define DPRINTF(...) #define DPRINTFDEV(_dev, ...) #endif struct sdiob_softc { uint32_t sdio_state; #define SDIO_STATE_DEAD 0x0001 #define SDIO_STATE_INITIALIZING 0x0002 #define SDIO_STATE_READY 0x0004 uint32_t nb_state; #define NB_STATE_DEAD 0x0001 #define NB_STATE_SIM_ADDED 0x0002 #define NB_STATE_READY 0x0004 /* CAM side. */ struct card_info cardinfo; struct cam_periph *periph; union ccb *ccb; struct task discover_task; /* Newbus side. */ device_t dev; /* Ourselves. */ device_t child[8]; }; /* -------------------------------------------------------------------------- */ /* * SDIO CMD52 and CM53 implementations along with wrapper functions for * read/write and a CAM periph helper function. * These are the backend implementations of the sdio_if.m framework talking * through CAM to sdhci. * Note: these functions are also called during early discovery stage when * we are not a device(9) yet. Hence they cannot always use device_printf() * to log errors and have to call CAM_DEBUG() during these early stages. */ static int sdioerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { return (cam_periph_error(ccb, cam_flags, sense_flags)); } /* CMD52: direct byte access. */ static int sdiob_rw_direct_sc(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, bool wr, uint8_t *val) { uint32_t arg, flags; int error; KASSERT((val != NULL), ("%s val passed as NULL\n", __func__)); if (sc->ccb == NULL) sc->ccb = xpt_alloc_ccb(); else memset(sc->ccb, 0, sizeof(*sc->ccb)); xpt_setup_ccb(&sc->ccb->ccb_h, sc->periph->path, CAM_PRIORITY_NONE); CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_TRACE, ("%s(fn=%d, addr=%#02x, wr=%d, *val=%#02x)\n", __func__, fn, addr, wr, *val)); flags = MMC_RSP_R5 | MMC_CMD_AC; arg = SD_IO_RW_FUNC(fn) | SD_IO_RW_ADR(addr); if (wr) arg |= SD_IO_RW_WR | SD_IO_RW_RAW | SD_IO_RW_DAT(*val); cam_fill_mmcio(&sc->ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ CAM_DIR_NONE, /*mmc_opcode*/ SD_IO_RW_DIRECT, /*mmc_arg*/ arg, /*mmc_flags*/ flags, /*mmc_data*/ 0, /*timeout*/ sc->cardinfo.f[fn].timeout); error = cam_periph_runccb(sc->ccb, sdioerror, CAM_FLAG_NONE, 0, NULL); if (error != 0) { if (sc->dev != NULL) device_printf(sc->dev, "%s: Failed to %s address %#10x error=%d\n", __func__, (wr) ? "write" : "read", addr, error); else CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_INFO, ("%s: Failed to %s address: %#10x error=%d\n", __func__, (wr) ? "write" : "read", addr, error)); return (error); } /* TODO: Add handling of MMC errors */ /* ccb->mmcio.cmd.error ? */ if (wr == false) *val = sc->ccb->mmcio.cmd.resp[0] & 0xff; return (0); } static int sdio_rw_direct(device_t dev, uint8_t fn, uint32_t addr, bool wr, uint8_t *val) { struct sdiob_softc *sc; int error; sc = device_get_softc(dev); cam_periph_lock(sc->periph); error = sdiob_rw_direct_sc(sc, fn, addr, wr, val); cam_periph_unlock(sc->periph); return (error); } static int sdiob_read_direct(device_t dev, uint8_t fn, uint32_t addr, uint8_t *val) { int error; uint8_t v; error = sdio_rw_direct(dev, fn, addr, false, &v); /* Be polite and do not touch the value on read error. */ if (error == 0 && val != NULL) *val = v; return (error); } static int sdiob_write_direct(device_t dev, uint8_t fn, uint32_t addr, uint8_t val) { return (sdio_rw_direct(dev, fn, addr, true, &val)); } /* * CMD53: IO_RW_EXTENDED, read and write multiple I/O registers. * Increment false gets FIFO mode (single register address). */ /* * A b_count of 0 means byte mode, b_count > 0 gets block mode. * A b_count of >= 512 would mean infinitive block transfer, which would become * b_count = 0, is not yet supported. * For b_count == 0, blksz is the len of bytes, otherwise it is the amount of * full sized blocks (you must not round the blocks up and leave the last one * partial!) * For byte mode, the maximum of blksz is the functions cur_blksize. * This function should ever only be called by sdio_rw_extended_sc()! */ static int sdiob_rw_extended_cam(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, bool wr, uint8_t *buffer, bool incaddr, uint32_t b_count, uint16_t blksz) { struct mmc_data mmcd; uint32_t arg, cam_flags, flags, len; int error; if (sc->ccb == NULL) sc->ccb = xpt_alloc_ccb(); else memset(sc->ccb, 0, sizeof(*sc->ccb)); xpt_setup_ccb(&sc->ccb->ccb_h, sc->periph->path, CAM_PRIORITY_NONE); CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_TRACE, ("%s(fn=%d addr=%#0x wr=%d b_count=%u blksz=%u buf=%p incr=%d)\n", __func__, fn, addr, wr, b_count, blksz, buffer, incaddr)); KASSERT((b_count <= 511), ("%s: infinitive block transfer not yet " "supported: b_count %u blksz %u, sc %p, fn %u, addr %#10x, %s, " "buffer %p, %s\n", __func__, b_count, blksz, sc, fn, addr, wr ? "wr" : "rd", buffer, incaddr ? "incaddr" : "fifo")); /* Blksz needs to be within bounds for both byte and block mode! */ KASSERT((blksz <= sc->cardinfo.f[fn].cur_blksize), ("%s: blksz " "%u > bur_blksize %u, sc %p, fn %u, addr %#10x, %s, " "buffer %p, %s, b_count %u\n", __func__, blksz, sc->cardinfo.f[fn].cur_blksize, sc, fn, addr, wr ? "wr" : "rd", buffer, incaddr ? "incaddr" : "fifo", b_count)); if (b_count == 0) { /* Byte mode */ len = blksz; if (blksz == 512) blksz = 0; arg = SD_IOE_RW_LEN(blksz); } else { /* Block mode. */ #ifdef __notyet__ if (b_count > 511) { /* Infinitive block transfer. */ b_count = 0; } #endif len = b_count * blksz; arg = SD_IOE_RW_BLK | SD_IOE_RW_LEN(b_count); } flags = MMC_RSP_R5 | MMC_CMD_ADTC; arg |= SD_IOE_RW_FUNC(fn) | SD_IOE_RW_ADR(addr); if (incaddr) arg |= SD_IOE_RW_INCR; memset(&mmcd, 0, sizeof(mmcd)); mmcd.data = buffer; mmcd.len = len; if (arg & SD_IOE_RW_BLK) { /* XXX both should be known from elsewhere, aren't they? */ mmcd.block_size = blksz; mmcd.block_count = b_count; } if (wr) { arg |= SD_IOE_RW_WR; cam_flags = CAM_DIR_OUT; mmcd.flags = MMC_DATA_WRITE; } else { cam_flags = CAM_DIR_IN; mmcd.flags = MMC_DATA_READ; } #ifdef __notyet__ if (b_count == 0) { /* XXX-BZ TODO FIXME. Cancel I/O: CCCR -> ASx */ /* Stop cmd. */ } #endif cam_fill_mmcio(&sc->ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ cam_flags, /*mmc_opcode*/ SD_IO_RW_EXTENDED, /*mmc_arg*/ arg, /*mmc_flags*/ flags, /*mmc_data*/ &mmcd, /*timeout*/ sc->cardinfo.f[fn].timeout); if (arg & SD_IOE_RW_BLK) { mmcd.flags |= MMC_DATA_BLOCK_SIZE; if (b_count != 1) sc->ccb->mmcio.cmd.data->flags |= MMC_DATA_MULTI; } /* Execute. */ error = cam_periph_runccb(sc->ccb, sdioerror, CAM_FLAG_NONE, 0, NULL); if (error != 0) { if (sc->dev != NULL) device_printf(sc->dev, "%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error); else CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_INFO, ("%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error)); return (error); } /* TODO: Add handling of MMC errors */ /* ccb->mmcio.cmd.error ? */ error = sc->ccb->mmcio.cmd.resp[0] & 0xff; if (error != 0) { if (sc->dev != NULL) device_printf(sc->dev, "%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u mmcio resp error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error); else CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_INFO, ("%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u mmcio resp error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error)); } return (error); } static int sdiob_rw_extended_sc(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, bool wr, uint32_t size, uint8_t *buffer, bool incaddr) { int error; uint32_t len; uint32_t b_count; /* * If block mode is supported and we have at least 4 bytes to write and * the size is at least one block, then start doing blk transfers. */ while (sc->cardinfo.support_multiblk && size > 4 && size >= sc->cardinfo.f[fn].cur_blksize) { b_count = size / sc->cardinfo.f[fn].cur_blksize; KASSERT(b_count >= 1, ("%s: block count too small %u size %u " "cur_blksize %u\n", __func__, b_count, size, sc->cardinfo.f[fn].cur_blksize)); #ifdef __notyet__ /* XXX support inifinite transfer with b_count = 0. */ #else if (b_count > 511) b_count = 511; #endif len = b_count * sc->cardinfo.f[fn].cur_blksize; error = sdiob_rw_extended_cam(sc, fn, addr, wr, buffer, incaddr, b_count, sc->cardinfo.f[fn].cur_blksize); if (error != 0) return (error); size -= len; buffer += len; if (incaddr) addr += len; } while (size > 0) { len = MIN(size, sc->cardinfo.f[fn].cur_blksize); error = sdiob_rw_extended_cam(sc, fn, addr, wr, buffer, incaddr, 0, len); if (error != 0) return (error); /* Prepare for next iteration. */ size -= len; buffer += len; if (incaddr) addr += len; } return (0); } static int sdiob_rw_extended(device_t dev, uint8_t fn, uint32_t addr, bool wr, uint32_t size, uint8_t *buffer, bool incaddr) { struct sdiob_softc *sc; int error; sc = device_get_softc(dev); cam_periph_lock(sc->periph); error = sdiob_rw_extended_sc(sc, fn, addr, wr, size, buffer, incaddr); cam_periph_unlock(sc->periph); return (error); } static int sdiob_read_extended(device_t dev, uint8_t fn, uint32_t addr, uint32_t size, uint8_t *buffer, bool incaddr) { return (sdiob_rw_extended(dev, fn, addr, false, size, buffer, incaddr)); } static int sdiob_write_extended(device_t dev, uint8_t fn, uint32_t addr, uint32_t size, uint8_t *buffer, bool incaddr) { return (sdiob_rw_extended(dev, fn, addr, true, size, buffer, incaddr)); } /* -------------------------------------------------------------------------- */ /* Bus interface, ivars handling. */ static int sdiob_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct sdiob_softc *sc; struct sdio_func *f; f = device_get_ivars(child); KASSERT(f != NULL, ("%s: dev %p child %p which %d, child ivars NULL\n", __func__, dev, child, which)); switch (which) { case SDIOB_IVAR_SUPPORT_MULTIBLK: sc = device_get_softc(dev); KASSERT(sc != NULL, ("%s: dev %p child %p which %d, sc NULL\n", __func__, dev, child, which)); *result = sc->cardinfo.support_multiblk; break; case SDIOB_IVAR_FUNCTION: *result = (uintptr_t)f; break; case SDIOB_IVAR_FUNCNUM: *result = f->fn; break; case SDIOB_IVAR_CLASS: *result = f->class; break; case SDIOB_IVAR_VENDOR: *result = f->vendor; break; case SDIOB_IVAR_DEVICE: *result = f->device; break; case SDIOB_IVAR_DRVDATA: *result = f->drvdata; break; default: return (ENOENT); } return (0); } static int sdiob_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct sdio_func *f; f = device_get_ivars(child); KASSERT(f != NULL, ("%s: dev %p child %p which %d, child ivars NULL\n", __func__, dev, child, which)); switch (which) { case SDIOB_IVAR_SUPPORT_MULTIBLK: case SDIOB_IVAR_FUNCTION: case SDIOB_IVAR_FUNCNUM: case SDIOB_IVAR_CLASS: case SDIOB_IVAR_VENDOR: case SDIOB_IVAR_DEVICE: return (EINVAL); /* Disallowed. */ case SDIOB_IVAR_DRVDATA: f->drvdata = value; break; default: return (ENOENT); } return (0); } /* -------------------------------------------------------------------------- */ /* * Newbus functions for ourselves to probe/attach/detach and become a proper * device(9). Attach will also probe for child devices (another driver * implementing SDIO). */ static int sdiob_probe(device_t dev) { device_set_desc(dev, "SDIO CAM-Newbus bridge"); return (BUS_PROBE_DEFAULT); } static int sdiob_attach(device_t dev) { struct sdiob_softc *sc; int error, i; sc = device_get_softc(dev); if (sc == NULL) return (ENXIO); /* * Now that we are a dev, create one child device per function, * initialize the backpointer, so we can pass them around and * call CAM operations on the parent, and also set the function * itself as ivars, so that we can query/update them. * Do this before any child gets a chance to attach. */ for (i = 0; i < sc->cardinfo.num_funcs; i++) { sc->child[i] = device_add_child(dev, NULL, DEVICE_UNIT_ANY); if (sc->child[i] == NULL) { device_printf(dev, "%s: failed to add child\n", __func__); return (ENXIO); } sc->cardinfo.f[i].dev = sc->child[i]; /* Set the function as ivar to the child device. */ device_set_ivars(sc->child[i], &sc->cardinfo.f[i]); } /* * No one will ever attach to F0; we do the above to have a "device" * to talk to in a general way in the code. * Also do the probe/attach in a 2nd loop, so that all devices are * present as we do have drivers consuming more than one device/func * and might play "tricks" in order to do that assuming devices and * ivars are available for all. */ for (i = 1; i < sc->cardinfo.num_funcs; i++) { error = device_probe_and_attach(sc->child[i]); if (error != 0 && bootverbose) device_printf(dev, "%s: device_probe_and_attach(%p %s) " "failed %d for function %d, no child yet\n", __func__, sc->child, device_get_nameunit(sc->child[i]), error, i); } sc->nb_state = NB_STATE_READY; cam_periph_lock(sc->periph); xpt_announce_periph(sc->periph, NULL); cam_periph_unlock(sc->periph); return (0); } static int sdiob_detach(device_t dev) { /* XXX TODO? */ return (EOPNOTSUPP); } /* -------------------------------------------------------------------------- */ /* * driver(9) and device(9) "control plane". * This is what we use when we are making ourselves a device(9) in order to * provide a newbus interface again, as well as the implementation of the * SDIO interface. */ static device_method_t sdiob_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, sdiob_probe), DEVMETHOD(device_attach, sdiob_attach), DEVMETHOD(device_detach, sdiob_detach), /* Bus interface. */ DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), DEVMETHOD(bus_read_ivar, sdiob_read_ivar), DEVMETHOD(bus_write_ivar, sdiob_write_ivar), /* SDIO interface. */ DEVMETHOD(sdio_read_direct, sdiob_read_direct), DEVMETHOD(sdio_write_direct, sdiob_write_direct), DEVMETHOD(sdio_read_extended, sdiob_read_extended), DEVMETHOD(sdio_write_extended, sdiob_write_extended), DEVMETHOD_END }; static driver_t sdiob_driver = { SDIOB_NAME_S, sdiob_methods, 0 }; /* -------------------------------------------------------------------------- */ /* * CIS related. * Read card and function information and populate the cardinfo structure. */ static int sdio_read_direct_sc(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, uint8_t *val) { int error; uint8_t v; error = sdiob_rw_direct_sc(sc, fn, addr, false, &v); if (error == 0 && val != NULL) *val = v; return (error); } static int sdio_func_read_cis(struct sdiob_softc *sc, uint8_t fn, uint32_t cis_addr) { char cis1_info_buf[256]; char *cis1_info[4]; int start, i, count, ret; uint32_t addr; uint8_t ch, tuple_id, tuple_len, tuple_count, v; /* If we encounter any read errors, abort and return. */ #define ERR_OUT(ret) \ if (ret != 0) \ goto err; ret = 0; /* Use to prevent infinite loop in case of parse errors. */ tuple_count = 0; memset(cis1_info_buf, 0, 256); do { addr = cis_addr; ret = sdio_read_direct_sc(sc, 0, addr++, &tuple_id); ERR_OUT(ret); if (tuple_id == SD_IO_CISTPL_END) break; if (tuple_id == 0) { cis_addr++; continue; } ret = sdio_read_direct_sc(sc, 0, addr++, &tuple_len); ERR_OUT(ret); if (tuple_len == 0) { CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: parse error: 0-length tuple %#02x\n", __func__, tuple_id)); return (EIO); } switch (tuple_id) { case SD_IO_CISTPL_VERS_1: addr += 2; for (count = 0, start = 0, i = 0; (count < 4) && ((i + 4) < 256); i++) { ret = sdio_read_direct_sc(sc, 0, addr + i, &ch); ERR_OUT(ret); DPRINTF("%s: count=%d, start=%d, i=%d, got " "(%#02x)\n", __func__, count, start, i, ch); if (ch == 0xff) break; cis1_info_buf[i] = ch; if (ch == 0) { cis1_info[count] = cis1_info_buf + start; start = i + 1; count++; } } DPRINTF("Card info: "); for (i=0; i < 4; i++) if (cis1_info[i]) DPRINTF(" %s", cis1_info[i]); DPRINTF("\n"); break; case SD_IO_CISTPL_MANFID: /* TPLMID_MANF */ ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); sc->cardinfo.f[fn].vendor = v; ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); sc->cardinfo.f[fn].vendor |= (v << 8); /* TPLMID_CARD */ ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); sc->cardinfo.f[fn].device = v; ret = sdio_read_direct_sc(sc, 0, addr, &v); ERR_OUT(ret); sc->cardinfo.f[fn].device |= (v << 8); break; case SD_IO_CISTPL_FUNCID: /* Not sure if we need to parse it? */ break; case SD_IO_CISTPL_FUNCE: if (tuple_len < 4) { printf("%s: FUNCE is too short: %d\n", __func__, tuple_len); break; } /* TPLFE_TYPE (Extended Data) */ ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); if (fn == 0) { if (v != 0x00) break; } else { if (v != 0x01) break; addr += 0x0b; } ret = sdio_read_direct_sc(sc, 0, addr, &v); ERR_OUT(ret); sc->cardinfo.f[fn].max_blksize = v; ret = sdio_read_direct_sc(sc, 0, addr+1, &v); ERR_OUT(ret); sc->cardinfo.f[fn].max_blksize |= (v << 8); break; default: CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: Skipping fn %d tuple %d ID %#02x " "len %#02x\n", __func__, fn, tuple_count, tuple_id, tuple_len)); } if (tuple_len == 0xff) { /* Also marks the end of a tuple chain (E1 16.2) */ /* The tuple is valid, hence this going at the end. */ break; } cis_addr += 2 + tuple_len; tuple_count++; } while (tuple_count < 20); err: #undef ERR_OUT return (ret); } static int sdio_get_common_cis_addr(struct sdiob_softc *sc, uint32_t *addr) { int error; uint32_t a; uint8_t val; error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CISPTR + 0, &val); if (error != 0) goto err; a = val; error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CISPTR + 1, &val); if (error != 0) goto err; a |= (val << 8); error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CISPTR + 2, &val); if (error != 0) goto err; a |= (val << 16); if (a < SD_IO_CIS_START || a > SD_IO_CIS_START + SD_IO_CIS_SIZE) { err: CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: bad CIS address: %#04x, error %d\n", __func__, a, error)); } else if (error == 0 && addr != NULL) *addr = a; return (error); } static int sdiob_get_card_info(struct sdiob_softc *sc) { struct mmc_params *mmcp; uint32_t cis_addr, fbr_addr; int fn, error; uint8_t fn_max, val; error = sdio_get_common_cis_addr(sc, &cis_addr); if (error != 0) return (-1); memset(&sc->cardinfo, 0, sizeof(sc->cardinfo)); /* F0 must always be present. */ fn = 0; error = sdio_func_read_cis(sc, fn, cis_addr); if (error != 0) return (error); sc->cardinfo.num_funcs++; /* Read CCCR Card Capability. */ error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CARDCAP, &val); if (error != 0) return (error); sc->cardinfo.support_multiblk = (val & CCCR_CC_SMB) ? true : false; DPRINTF("%s: F%d: Vendor %#04x product %#04x max block size %d bytes " "support_multiblk %s\n", __func__, fn, sc->cardinfo.f[fn].vendor, sc->cardinfo.f[fn].device, sc->cardinfo.f[fn].max_blksize, sc->cardinfo.support_multiblk ? "yes" : "no"); /* mmcp->sdio_func_count contains the number of functions w/o F0. */ mmcp = &sc->ccb->ccb_h.path->device->mmc_ident_data; fn_max = MIN(mmcp->sdio_func_count + 1, nitems(sc->cardinfo.f)); for (fn = 1; fn < fn_max; fn++) { fbr_addr = SD_IO_FBR_START * fn + SD_IO_FBR_CIS_OFFSET; error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; cis_addr = val; error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; cis_addr |= (val << 8); error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; cis_addr |= (val << 16); error = sdio_func_read_cis(sc, fn, cis_addr); if (error != 0) break; /* Read the Standard SDIO Function Interface Code. */ fbr_addr = SD_IO_FBR_START * fn; error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; sc->cardinfo.f[fn].class = (val & 0x0f); if (sc->cardinfo.f[fn].class == 0x0f) { error = sdio_read_direct_sc(sc, 0, fbr_addr, &val); if (error != 0) break; sc->cardinfo.f[fn].class = val; } sc->cardinfo.f[fn].fn = fn; sc->cardinfo.f[fn].cur_blksize = sc->cardinfo.f[fn].max_blksize; sc->cardinfo.f[fn].retries = 0; sc->cardinfo.f[fn].timeout = 5000; DPRINTF("%s: F%d: Class %d Vendor %#04x product %#04x " "max_blksize %d bytes\n", __func__, fn, sc->cardinfo.f[fn].class, sc->cardinfo.f[fn].vendor, sc->cardinfo.f[fn].device, sc->cardinfo.f[fn].max_blksize); if (sc->cardinfo.f[fn].vendor == 0) { DPRINTF("%s: F%d doesn't exist\n", __func__, fn); break; } sc->cardinfo.num_funcs++; } return (error); } /* -------------------------------------------------------------------------- */ /* * CAM periph registration, allocation, and detached from that a discovery * task, which goes off reads cardinfo, and then adds ourselves to our SIM's * device adding the devclass and registering the driver. This keeps the * newbus chain connected though we will talk CAM in the middle (until one * day CAM might be newbusyfied). */ static int sdio_newbus_sim_add(struct sdiob_softc *sc) { device_t pdev; devclass_t bus_devclass; int error; /* Add ourselves to our parent (SIM) device. */ /* Add ourselves to our parent. That way we can become a parent. */ pdev = xpt_path_sim_device(sc->periph->path); KASSERT(pdev != NULL, ("%s: pdev is NULL, sc %p periph %p sim %p\n", __func__, sc, sc->periph, sc->periph->sim)); if (sc->dev == NULL) - sc->dev = BUS_ADD_CHILD(pdev, 0, SDIOB_NAME_S, -1); + sc->dev = BUS_ADD_CHILD(pdev, 0, SDIOB_NAME_S, DEVICE_UNIT_ANY); if (sc->dev == NULL) return (ENXIO); device_set_softc(sc->dev, sc); /* * Don't set description here; devclass_add_driver() -> * device_probe_child() -> device_set_driver() will nuke it again. */ bus_devclass = device_get_devclass(pdev); if (bus_devclass == NULL) { printf("%s: Failed to get devclass from %s.\n", __func__, device_get_nameunit(pdev)); return (ENXIO); } bus_topo_lock(); error = devclass_add_driver(bus_devclass, &sdiob_driver, BUS_PASS_DEFAULT, NULL); bus_topo_unlock(); if (error != 0) { printf("%s: Failed to add driver to devclass: %d.\n", __func__, error); return (error); } /* Done. */ sc->nb_state = NB_STATE_SIM_ADDED; return (0); } static void sdiobdiscover(void *context, int pending) { struct cam_periph *periph; struct sdiob_softc *sc; int error; KASSERT(context != NULL, ("%s: context is NULL\n", __func__)); periph = (struct cam_periph *)context; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s\n", __func__)); /* Periph was held for us when this task was enqueued. */ if ((periph->flags & CAM_PERIPH_INVALID) != 0) { cam_periph_release(periph); return; } sc = periph->softc; sc->sdio_state = SDIO_STATE_INITIALIZING; if (sc->ccb == NULL) sc->ccb = xpt_alloc_ccb(); else memset(sc->ccb, 0, sizeof(*sc->ccb)); xpt_setup_ccb(&sc->ccb->ccb_h, periph->path, CAM_PRIORITY_NONE); /* * Read CCCR and FBR of each function, get manufacturer and device IDs, * max block size, and whatever else we deem necessary. */ cam_periph_lock(periph); error = sdiob_get_card_info(sc); if (error == 0) sc->sdio_state = SDIO_STATE_READY; else sc->sdio_state = SDIO_STATE_DEAD; cam_periph_unlock(periph); if (error) return; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s: num_func %d\n", __func__, sc->cardinfo.num_funcs)); /* * Now CAM portion of the driver has been initialized and * we know VID/PID of all the functions on the card. * Time to hook into the newbus. */ error = sdio_newbus_sim_add(sc); if (error != 0) sc->nb_state = NB_STATE_DEAD; return; } /* Called at the end of cam_periph_alloc() for us to finish allocation. */ static cam_status sdiobregister(struct cam_periph *periph, void *arg) { struct sdiob_softc *sc; int error; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s: arg %p\n", __func__, arg)); if (arg == NULL) { printf("%s: no getdev CCB, can't register device pariph %p\n", __func__, periph); return(CAM_REQ_CMP_ERR); } if (xpt_path_sim_device(periph->path) == NULL) { printf("%s: no device_t for sim %p\n", __func__, periph->sim); return(CAM_REQ_CMP_ERR); } sc = (struct sdiob_softc *) malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT|M_ZERO); if (sc == NULL) { printf("%s: unable to allocate sc\n", __func__); return (CAM_REQ_CMP_ERR); } sc->sdio_state = SDIO_STATE_DEAD; sc->nb_state = NB_STATE_DEAD; TASK_INIT(&sc->discover_task, 0, sdiobdiscover, periph); /* Refcount until we are setup. Can't block. */ error = cam_periph_hold(periph, PRIBIO); if (error != 0) { printf("%s: lost periph during registration!\n", __func__); free(sc, M_DEVBUF); return(CAM_REQ_CMP_ERR); } periph->softc = sc; sc->periph = periph; cam_periph_unlock(periph); error = taskqueue_enqueue(taskqueue_thread, &sc->discover_task); cam_periph_lock(periph); /* We will continue to hold a refcount for discover_task. */ /* cam_periph_unhold(periph); */ xpt_schedule(periph, CAM_PRIORITY_XPT); return (CAM_REQ_CMP); } static void sdioboninvalidate(struct cam_periph *periph) { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s:\n", __func__)); return; } static void sdiobcleanup(struct cam_periph *periph) { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s:\n", __func__)); return; } static void sdiobstart(struct cam_periph *periph, union ccb *ccb) { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s: ccb %p\n", __func__, ccb)); return; } static void sdiobasync(void *softc, uint32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; struct ccb_getdev *cgd; cam_status status; periph = (struct cam_periph *)softc; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("%s(code=%d)\n", __func__, code)); switch (code) { case AC_FOUND_DEVICE: if (arg == NULL) break; cgd = (struct ccb_getdev *)arg; if (cgd->protocol != PROTO_MMCSD) break; /* We do not support SD memory (Combo) Cards. */ if ((path->device->mmc_ident_data.card_features & CARD_FEATURE_MEMORY)) { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("Memory card, not interested\n")); break; } /* * Allocate a peripheral instance for this device which starts * the probe process. */ status = cam_periph_alloc(sdiobregister, sdioboninvalidate, sdiobcleanup, sdiobstart, SDIOB_NAME_S, CAM_PERIPH_BIO, path, sdiobasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) CAM_DEBUG(path, CAM_DEBUG_PERIPH, ("%s: Unable to attach to new device due to " "status %#02x\n", __func__, status)); break; default: CAM_DEBUG(path, CAM_DEBUG_PERIPH, ("%s: cannot handle async code %#02x\n", __func__, code)); cam_periph_async(periph, code, path, arg); break; } } static void sdiobinit(void) { cam_status status; /* * Register for new device notification. We will be notified for all * already existing ones. */ status = xpt_register_async(AC_FOUND_DEVICE, sdiobasync, NULL, NULL); if (status != CAM_REQ_CMP) printf("%s: Failed to attach async callback, statux %#02x", __func__, status); } /* This function will allow unloading the KLD. */ static int sdiobdeinit(void) { return (EOPNOTSUPP); } static struct periph_driver sdiobdriver = { .init = sdiobinit, .driver_name = SDIOB_NAME_S, .units = TAILQ_HEAD_INITIALIZER(sdiobdriver.units), .generation = 0, .flags = 0, .deinit = sdiobdeinit, }; PERIPHDRIVER_DECLARE(SDIOB_NAME, sdiobdriver); MODULE_VERSION(SDIOB_NAME, 1); diff --git a/sys/dev/smbus/smb.c b/sys/dev/smbus/smb.c index 0efa93ae0e89..514c42b88131 100644 --- a/sys/dev/smbus/smb.c +++ b/sys/dev/smbus/smb.c @@ -1,420 +1,420 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998, 2001 Nicolas Souchu * Copyright (c) 2023 Juniper Networks, Inc. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "smbus_if.h" #ifdef COMPAT_FREEBSD32 struct smbcmd32 { u_char cmd; u_char reserved; u_short op; union { char byte; char buf[2]; short word; } wdata; union { char byte; char buf[2]; short word; } rdata; int slave; uint32_t wbuf; int wcount; uint32_t rbuf; int rcount; }; #define SMB_QUICK_WRITE32 _IOW('i', 1, struct smbcmd32) #define SMB_QUICK_READ32 _IOW('i', 2, struct smbcmd32) #define SMB_SENDB32 _IOW('i', 3, struct smbcmd32) #define SMB_RECVB32 _IOWR('i', 4, struct smbcmd32) #define SMB_WRITEB32 _IOW('i', 5, struct smbcmd32) #define SMB_WRITEW32 _IOW('i', 6, struct smbcmd32) #define SMB_READB32 _IOWR('i', 7, struct smbcmd32) #define SMB_READW32 _IOWR('i', 8, struct smbcmd32) #define SMB_PCALL32 _IOWR('i', 9, struct smbcmd32) #define SMB_BWRITE32 _IOW('i', 10, struct smbcmd32) #define SMB_BREAD32 _IOWR('i', 11, struct smbcmd32) #define SMB_OLD_READB32 _IOW('i', 7, struct smbcmd32) #define SMB_OLD_READW32 _IOW('i', 8, struct smbcmd32) #define SMB_OLD_PCALL32 _IOW('i', 9, struct smbcmd32) #endif #define SMB_OLD_READB _IOW('i', 7, struct smbcmd) #define SMB_OLD_READW _IOW('i', 8, struct smbcmd) #define SMB_OLD_PCALL _IOW('i', 9, struct smbcmd) struct smb_softc { device_t sc_dev; struct cdev *sc_devnode; }; static void smb_identify(driver_t *driver, device_t parent); static int smb_probe(device_t); static int smb_attach(device_t); static int smb_detach(device_t); static device_method_t smb_methods[] = { /* device interface */ DEVMETHOD(device_identify, smb_identify), DEVMETHOD(device_probe, smb_probe), DEVMETHOD(device_attach, smb_attach), DEVMETHOD(device_detach, smb_detach), /* smbus interface */ DEVMETHOD(smbus_intr, smbus_generic_intr), { 0, 0 } }; static driver_t smb_driver = { "smb", smb_methods, sizeof(struct smb_softc), }; static d_ioctl_t smbioctl; static struct cdevsw smb_cdevsw = { .d_version = D_VERSION, .d_flags = D_TRACKCLOSE, .d_ioctl = smbioctl, .d_name = "smb", }; static void smb_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "smb", -1) == NULL) + if (device_find_child(parent, "smb", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "smb", DEVICE_UNIT_ANY); } static int smb_probe(device_t dev) { if (smbus_get_addr(dev) != -1) return (ENXIO); device_set_desc(dev, "SMBus generic I/O"); return (BUS_PROBE_NOWILDCARD); } static int smb_attach(device_t dev) { struct smb_softc *sc; struct make_dev_args mda; int error; sc = device_get_softc(dev); sc->sc_dev = dev; make_dev_args_init(&mda); mda.mda_devsw = &smb_cdevsw; mda.mda_unit = device_get_unit(dev); mda.mda_uid = UID_ROOT; mda.mda_gid = GID_WHEEL; mda.mda_mode = 0600; mda.mda_si_drv1 = sc; error = make_dev_s(&mda, &sc->sc_devnode, "smb%d", mda.mda_unit); return (error); } static int smb_detach(device_t dev) { struct smb_softc *sc; sc = device_get_softc(dev); destroy_dev(sc->sc_devnode); return (0); } #ifdef COMPAT_FREEBSD32 static void smbcopyincmd32(struct smbcmd32 *uaddr, struct smbcmd *kaddr) { CP(*uaddr, *kaddr, cmd); CP(*uaddr, *kaddr, op); CP(*uaddr, *kaddr, wdata.word); CP(*uaddr, *kaddr, slave); PTRIN_CP(*uaddr, *kaddr, wbuf); CP(*uaddr, *kaddr, wcount); PTRIN_CP(*uaddr, *kaddr, rbuf); CP(*uaddr, *kaddr, rcount); } #endif static int smbioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { char buf[SMB_MAXBLOCKSIZE]; device_t parent; #ifdef COMPAT_FREEBSD32 struct smbcmd sswab; struct smbcmd32 *s32 = (struct smbcmd32 *)data; #endif struct smbcmd *s = (struct smbcmd *)data; struct smb_softc *sc = dev->si_drv1; device_t smbdev = sc->sc_dev; int error; int unit; u_char bcount; /* * If a specific slave device is being used, override any passed-in * slave. */ unit = dev2unit(dev); if (unit & 0x0400) s->slave = unit & 0x03ff; parent = device_get_parent(smbdev); /* Make sure that LSB bit is cleared. */ if (s->slave & 0x1) return (EINVAL); /* Allocate the bus. */ if ((error = smbus_request_bus(parent, smbdev, (flags & O_NONBLOCK) ? SMB_DONTWAIT : (SMB_WAIT | SMB_INTR)))) return (error); #ifdef COMPAT_FREEBSD32 switch (cmd) { case SMB_QUICK_WRITE32: case SMB_QUICK_READ32: case SMB_SENDB32: case SMB_RECVB32: case SMB_WRITEB32: case SMB_WRITEW32: case SMB_OLD_READB32: case SMB_READB32: case SMB_OLD_READW32: case SMB_READW32: case SMB_OLD_PCALL32: case SMB_PCALL32: case SMB_BWRITE32: case SMB_BREAD32: smbcopyincmd32(s32, &sswab); s = &sswab; break; default: break; } #endif switch (cmd) { case SMB_QUICK_WRITE: #ifdef COMPAT_FREEBSD32 case SMB_QUICK_WRITE32: #endif error = smbus_error(smbus_quick(parent, s->slave, SMB_QWRITE)); break; case SMB_QUICK_READ: #ifdef COMPAT_FREEBSD32 case SMB_QUICK_READ32: #endif error = smbus_error(smbus_quick(parent, s->slave, SMB_QREAD)); break; case SMB_SENDB: #ifdef COMPAT_FREEBSD32 case SMB_SENDB32: #endif error = smbus_error(smbus_sendb(parent, s->slave, s->cmd)); break; case SMB_RECVB: #ifdef COMPAT_FREEBSD32 case SMB_RECVB32: #endif error = smbus_error(smbus_recvb(parent, s->slave, &s->cmd)); break; case SMB_WRITEB: #ifdef COMPAT_FREEBSD32 case SMB_WRITEB32: #endif error = smbus_error(smbus_writeb(parent, s->slave, s->cmd, s->wdata.byte)); break; case SMB_WRITEW: #ifdef COMPAT_FREEBSD32 case SMB_WRITEW32: #endif error = smbus_error(smbus_writew(parent, s->slave, s->cmd, s->wdata.word)); break; case SMB_OLD_READB: case SMB_READB: #ifdef COMPAT_FREEBSD32 case SMB_OLD_READB32: case SMB_READB32: #endif /* NB: for SMB_OLD_READB the read data goes to rbuf only. */ error = smbus_error(smbus_readb(parent, s->slave, s->cmd, &s->rdata.byte)); if (error) break; if (s->rbuf && s->rcount >= 1) { error = copyout(&s->rdata.byte, s->rbuf, 1); s->rcount = 1; } break; case SMB_OLD_READW: case SMB_READW: #ifdef COMPAT_FREEBSD32 case SMB_OLD_READW32: case SMB_READW32: #endif /* NB: for SMB_OLD_READW the read data goes to rbuf only. */ error = smbus_error(smbus_readw(parent, s->slave, s->cmd, &s->rdata.word)); if (error) break; if (s->rbuf && s->rcount >= 2) { buf[0] = (u_char)s->rdata.word; buf[1] = (u_char)(s->rdata.word >> 8); error = copyout(buf, s->rbuf, 2); s->rcount = 2; } break; case SMB_OLD_PCALL: case SMB_PCALL: #ifdef COMPAT_FREEBSD32 case SMB_OLD_PCALL32: case SMB_PCALL32: #endif /* NB: for SMB_OLD_PCALL the read data goes to rbuf only. */ error = smbus_error(smbus_pcall(parent, s->slave, s->cmd, s->wdata.word, &s->rdata.word)); if (error) break; if (s->rbuf && s->rcount >= 2) { buf[0] = (u_char)s->rdata.word; buf[1] = (u_char)(s->rdata.word >> 8); error = copyout(buf, s->rbuf, 2); s->rcount = 2; } break; case SMB_BWRITE: #ifdef COMPAT_FREEBSD32 case SMB_BWRITE32: #endif if (s->wcount < 0) { error = EINVAL; break; } if (s->wcount > SMB_MAXBLOCKSIZE) s->wcount = SMB_MAXBLOCKSIZE; if (s->wcount) error = copyin(s->wbuf, buf, s->wcount); if (error) break; error = smbus_error(smbus_bwrite(parent, s->slave, s->cmd, s->wcount, buf)); break; case SMB_BREAD: #ifdef COMPAT_FREEBSD32 case SMB_BREAD32: #endif if (s->rcount < 0) { error = EINVAL; break; } if (s->rcount > SMB_MAXBLOCKSIZE) s->rcount = SMB_MAXBLOCKSIZE; error = smbus_error(smbus_bread(parent, s->slave, s->cmd, &bcount, buf)); if (error) break; if (s->rcount > bcount) s->rcount = bcount; error = copyout(buf, s->rbuf, s->rcount); break; default: error = ENOTTY; } #ifdef COMPAT_FREEBSD32 switch (cmd) { case SMB_RECVB32: CP(*s, *s32, cmd); break; case SMB_OLD_READB32: case SMB_READB32: case SMB_OLD_READW32: case SMB_READW32: case SMB_OLD_PCALL32: case SMB_PCALL32: CP(*s, *s32, rdata.word); break; case SMB_BREAD32: if (s->rbuf == NULL) CP(*s, *s32, rdata.word); CP(*s, *s32, rcount); break; default: break; } #endif smbus_release_bus(parent, smbdev); return (error); } DRIVER_MODULE(smb, smbus, smb_driver, 0, 0); MODULE_DEPEND(smb, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); MODULE_VERSION(smb, 1); diff --git a/sys/dev/sound/dummy.c b/sys/dev/sound/dummy.c index 1e2a81f40103..4df5b112d3f4 100644 --- a/sys/dev/sound/dummy.c +++ b/sys/dev/sound/dummy.c @@ -1,385 +1,385 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024-2025 The FreeBSD Foundation * * This software was developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #define DUMMY_NPCHAN 1 #define DUMMY_NRCHAN 1 #define DUMMY_NCHAN (DUMMY_NPCHAN + DUMMY_NRCHAN) struct dummy_chan { struct dummy_softc *sc; struct pcm_channel *chan; struct snd_dbuf *buf; struct pcmchan_caps *caps; uint32_t ptr; int dir; int run; }; struct dummy_softc { struct snddev_info info; device_t dev; uint32_t cap_fmts[4]; struct pcmchan_caps caps; int chnum; struct dummy_chan chans[DUMMY_NCHAN]; struct callout callout; struct mtx *lock; bool stopped; }; static bool dummy_active(struct dummy_softc *sc) { struct dummy_chan *ch; int i; snd_mtxassert(sc->lock); for (i = 0; i < sc->chnum; i++) { ch = &sc->chans[i]; if (ch->run) return (true); } /* No channel is running at the moment. */ return (false); } static void dummy_chan_io(void *arg) { struct dummy_softc *sc = arg; struct dummy_chan *ch; int i = 0; if (sc->stopped) return; /* Do not reschedule if no channel is running. */ if (!dummy_active(sc)) return; for (i = 0; i < sc->chnum; i++) { ch = &sc->chans[i]; if (!ch->run) continue; if (ch->dir == PCMDIR_PLAY) ch->ptr += sndbuf_getblksz(ch->buf); else sndbuf_fillsilence(ch->buf); snd_mtxunlock(sc->lock); chn_intr(ch->chan); snd_mtxlock(sc->lock); } if (!sc->stopped) callout_schedule(&sc->callout, 1); } static int dummy_chan_free(kobj_t obj, void *data) { struct dummy_chan *ch =data; uint8_t *buf; buf = sndbuf_getbuf(ch->buf); if (buf != NULL) free(buf, M_DEVBUF); return (0); } static void * dummy_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct dummy_softc *sc; struct dummy_chan *ch; uint8_t *buf; size_t bufsz; sc = devinfo; snd_mtxlock(sc->lock); ch = &sc->chans[sc->chnum++]; ch->sc = sc; ch->dir = dir; ch->chan = c; ch->buf = b; ch->caps = &sc->caps; snd_mtxunlock(sc->lock); bufsz = pcm_getbuffersize(sc->dev, 2048, 2048, 65536); buf = malloc(bufsz, M_DEVBUF, M_WAITOK | M_ZERO); if (sndbuf_setup(ch->buf, buf, bufsz) != 0) { dummy_chan_free(obj, ch); return (NULL); } return (ch); } static int dummy_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct dummy_chan *ch = data; int i; for (i = 0; ch->caps->fmtlist[i]; i++) if (format == ch->caps->fmtlist[i]) return (0); return (EINVAL); } static uint32_t dummy_chan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct dummy_chan *ch = data; RANGE(speed, ch->caps->minspeed, ch->caps->maxspeed); return (speed); } static uint32_t dummy_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct dummy_chan *ch = data; return (sndbuf_getblksz(ch->buf)); } static int dummy_chan_trigger(kobj_t obj, void *data, int go) { struct dummy_chan *ch = data; struct dummy_softc *sc = ch->sc; snd_mtxlock(sc->lock); if (sc->stopped) { snd_mtxunlock(sc->lock); return (0); } switch (go) { case PCMTRIG_START: ch->ptr = 0; ch->run = 1; callout_reset(&sc->callout, 1, dummy_chan_io, sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; /* If all channels are stopped, stop the callout as well. */ if (!dummy_active(sc)) callout_stop(&sc->callout); default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t dummy_chan_getptr(kobj_t obj, void *data) { struct dummy_chan *ch = data; return (ch->run ? ch->ptr : 0); } static struct pcmchan_caps * dummy_chan_getcaps(kobj_t obj, void *data) { struct dummy_chan *ch = data; return (ch->caps); } static kobj_method_t dummy_chan_methods[] = { KOBJMETHOD(channel_init, dummy_chan_init), KOBJMETHOD(channel_free, dummy_chan_free), KOBJMETHOD(channel_setformat, dummy_chan_setformat), KOBJMETHOD(channel_setspeed, dummy_chan_setspeed), KOBJMETHOD(channel_setblocksize,dummy_chan_setblocksize), KOBJMETHOD(channel_trigger, dummy_chan_trigger), KOBJMETHOD(channel_getptr, dummy_chan_getptr), KOBJMETHOD(channel_getcaps, dummy_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(dummy_chan); static int dummy_mixer_init(struct snd_mixer *m) { struct dummy_softc *sc; sc = mix_getdevinfo(m); if (sc == NULL) return (-1); pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV); mix_setrecdevs(m, SOUND_MASK_RECLEV); return (0); } static int dummy_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { return (0); } static uint32_t dummy_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) { return (src == SOUND_MASK_RECLEV ? src : 0); } static kobj_method_t dummy_mixer_methods[] = { KOBJMETHOD(mixer_init, dummy_mixer_init), KOBJMETHOD(mixer_set, dummy_mixer_set), KOBJMETHOD(mixer_setrecsrc, dummy_mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(dummy_mixer); static void dummy_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, driver->name, -1) != NULL) + if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL) return; - if (BUS_ADD_CHILD(parent, 0, driver->name, -1) == NULL) + if (BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add child failed\n"); } static int dummy_probe(device_t dev) { device_set_desc(dev, "Dummy Audio Device"); return (0); } static int dummy_attach(device_t dev) { struct dummy_softc *sc; char status[SND_STATUSLEN]; int i = 0; sc = device_get_softc(dev); sc->dev = dev; sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_dummy softc"); callout_init_mtx(&sc->callout, sc->lock, 0); sc->cap_fmts[0] = SND_FORMAT(AFMT_S32_LE, 2, 0); sc->cap_fmts[1] = SND_FORMAT(AFMT_S24_LE, 2, 0); sc->cap_fmts[2] = SND_FORMAT(AFMT_S16_LE, 2, 0); sc->cap_fmts[3] = 0; sc->caps = (struct pcmchan_caps){ 8000, /* minspeed */ 96000, /* maxspeed */ sc->cap_fmts, /* fmtlist */ 0, /* caps */ }; pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); pcm_init(dev, sc); for (i = 0; i < DUMMY_NPCHAN; i++) pcm_addchan(dev, PCMDIR_PLAY, &dummy_chan_class, sc); for (i = 0; i < DUMMY_NRCHAN; i++) pcm_addchan(dev, PCMDIR_REC, &dummy_chan_class, sc); snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); if (pcm_register(dev, status)) return (ENXIO); mixer_init(dev, &dummy_mixer_class, sc); return (0); } static int dummy_detach(device_t dev) { struct dummy_softc *sc = device_get_softc(dev); int err; snd_mtxlock(sc->lock); sc->stopped = true; snd_mtxunlock(sc->lock); callout_drain(&sc->callout); err = pcm_unregister(dev); snd_mtxfree(sc->lock); return (err); } static device_method_t dummy_methods[] = { /* Device interface */ DEVMETHOD(device_identify, dummy_identify), DEVMETHOD(device_probe, dummy_probe), DEVMETHOD(device_attach, dummy_attach), DEVMETHOD(device_detach, dummy_detach), DEVMETHOD_END }; static driver_t dummy_driver = { "pcm", dummy_methods, sizeof(struct dummy_softc), }; DRIVER_MODULE(snd_dummy, nexus, dummy_driver, 0, 0); MODULE_DEPEND(snd_dummy, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_dummy, 1); diff --git a/sys/dev/sound/pci/hdsp.c b/sys/dev/sound/pci/hdsp.c index 4712d78ea88b..4ba23d22ebce 100644 --- a/sys/dev/sound/pci/hdsp.c +++ b/sys/dev/sound/pci/hdsp.c @@ -1,1022 +1,1022 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2016 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * RME HDSP driver for FreeBSD. * Supported cards: HDSP 9632, HDSP 9652. */ #include #include #include #include #include #include #include static bool hdsp_unified_pcm = false; static SYSCTL_NODE(_hw, OID_AUTO, hdsp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "PCI HDSP"); SYSCTL_BOOL(_hw_hdsp, OID_AUTO, unified_pcm, CTLFLAG_RWTUN, &hdsp_unified_pcm, 0, "Combine physical ports in one unified pcm device"); static struct hdsp_clock_source hdsp_clock_source_table_9632[] = { { "internal", HDSP_CLOCK_INTERNAL }, { "adat", HDSP_CLOCK_ADAT1 }, { "spdif", HDSP_CLOCK_SPDIF }, { "word", HDSP_CLOCK_WORD }, { NULL, HDSP_CLOCK_INTERNAL } }; static struct hdsp_clock_source hdsp_clock_source_table_9652[] = { { "internal", HDSP_CLOCK_INTERNAL }, { "adat1", HDSP_CLOCK_ADAT1 }, { "adat2", HDSP_CLOCK_ADAT2 }, { "adat3", HDSP_CLOCK_ADAT3 }, { "spdif", HDSP_CLOCK_SPDIF }, { "word", HDSP_CLOCK_WORD }, { "adat_sync", HDSP_CLOCK_ADAT_SYNC }, { NULL, HDSP_CLOCK_INTERNAL } }; static struct hdsp_channel chan_map_9632[] = { { HDSP_CHAN_9632_ADAT, "adat" }, { HDSP_CHAN_9632_SPDIF, "s/pdif" }, { HDSP_CHAN_9632_LINE, "line" }, { HDSP_CHAN_9632_EXT, "ext" }, { 0, NULL }, }; static struct hdsp_channel chan_map_9632_uni[] = { { HDSP_CHAN_9632_ALL, "all" }, { 0, NULL }, }; static struct hdsp_channel chan_map_9652[] = { { HDSP_CHAN_9652_ADAT1, "adat1" }, { HDSP_CHAN_9652_ADAT2, "adat2" }, { HDSP_CHAN_9652_ADAT3, "adat3" }, { HDSP_CHAN_9652_SPDIF, "s/pdif" }, { 0, NULL }, }; static struct hdsp_channel chan_map_9652_uni[] = { { HDSP_CHAN_9652_ALL, "all" }, { 0, NULL }, }; static void hdsp_intr(void *p) { struct sc_pcminfo *scp; struct sc_info *sc; device_t *devlist; int devcount; int status; int err; int i; sc = (struct sc_info *)p; snd_mtxlock(sc->lock); status = hdsp_read_1(sc, HDSP_STATUS_REG); if (status & HDSP_AUDIO_IRQ_PENDING) { if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) return; for (i = 0; i < devcount; i++) { scp = device_get_ivars(devlist[i]); if (scp->ih != NULL) scp->ih(scp); } hdsp_write_1(sc, HDSP_INTERRUPT_ACK, 0); free(devlist, M_TEMP); } snd_mtxunlock(sc->lock); } static void hdsp_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { #if 0 device_printf(sc->dev, "hdsp_dmapsetmap()\n"); #endif } static int hdsp_alloc_resources(struct sc_info *sc) { /* Allocate resource. */ sc->csid = PCIR_BAR(0); sc->cs = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &sc->csid, RF_ACTIVE); if (!sc->cs) { device_printf(sc->dev, "Unable to map SYS_RES_MEMORY.\n"); return (ENXIO); } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); /* Allocate interrupt resource. */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV, NULL, hdsp_intr, sc, &sc->ih)) { device_printf(sc->dev, "Unable to alloc interrupt resource.\n"); return (ENXIO); } /* Allocate DMA resources. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), /*alignment*/4, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/2 * HDSP_DMASEGSIZE, /*nsegments*/2, /*maxsegsz*/HDSP_DMASEGSIZE, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, /*dmatag*/&sc->dmat) != 0) { device_printf(sc->dev, "Unable to create dma tag.\n"); return (ENXIO); } sc->bufsize = HDSP_DMASEGSIZE; /* pbuf (play buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_WAITOK, &sc->pmap)) { device_printf(sc->dev, "Can't alloc pbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize, hdsp_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load pbuf.\n"); return (ENXIO); } /* rbuf (rec buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_WAITOK, &sc->rmap)) { device_printf(sc->dev, "Can't alloc rbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize, hdsp_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load rbuf.\n"); return (ENXIO); } bzero(sc->pbuf, sc->bufsize); bzero(sc->rbuf, sc->bufsize); return (0); } static void hdsp_map_dmabuf(struct sc_info *sc) { uint32_t paddr, raddr; paddr = vtophys(sc->pbuf); raddr = vtophys(sc->rbuf); hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_OUT, paddr); hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_IN, raddr); } static const char * hdsp_control_input_level(uint32_t control) { switch (control & HDSP_INPUT_LEVEL_MASK) { case HDSP_INPUT_LEVEL_LOWGAIN: return ("LowGain"); case HDSP_INPUT_LEVEL_PLUS4DBU: return ("+4dBu"); case HDSP_INPUT_LEVEL_MINUS10DBV: return ("-10dBV"); default: return (NULL); } } static int hdsp_sysctl_input_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Only available on HDSP 9632. */ if (sc->type != HDSP_9632) return (ENXIO); /* Extract current input level from control register. */ control = sc->ctrl_register & HDSP_INPUT_LEVEL_MASK; label = hdsp_control_input_level(control); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find input level matching the sysctl string. */ label = hdsp_control_input_level(HDSP_INPUT_LEVEL_LOWGAIN); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_INPUT_LEVEL_LOWGAIN; label = hdsp_control_input_level(HDSP_INPUT_LEVEL_PLUS4DBU); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_INPUT_LEVEL_PLUS4DBU; label = hdsp_control_input_level(HDSP_INPUT_LEVEL_MINUS10DBV); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_INPUT_LEVEL_MINUS10DBV; /* Set input level in control register. */ control &= HDSP_INPUT_LEVEL_MASK; if (control != (sc->ctrl_register & HDSP_INPUT_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_INPUT_LEVEL_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static const char * hdsp_control_output_level(uint32_t control) { switch (control & HDSP_OUTPUT_LEVEL_MASK) { case HDSP_OUTPUT_LEVEL_MINUS10DBV: return ("-10dBV"); case HDSP_OUTPUT_LEVEL_PLUS4DBU: return ("+4dBu"); case HDSP_OUTPUT_LEVEL_HIGHGAIN: return ("HighGain"); default: return (NULL); } } static int hdsp_sysctl_output_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Only available on HDSP 9632. */ if (sc->type != HDSP_9632) return (ENXIO); /* Extract current output level from control register. */ control = sc->ctrl_register & HDSP_OUTPUT_LEVEL_MASK; label = hdsp_control_output_level(control); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find output level matching the sysctl string. */ label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_MINUS10DBV); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_OUTPUT_LEVEL_MINUS10DBV; label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_PLUS4DBU); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_OUTPUT_LEVEL_PLUS4DBU; label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_HIGHGAIN); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_OUTPUT_LEVEL_HIGHGAIN; /* Set output level in control register. */ control &= HDSP_OUTPUT_LEVEL_MASK; if (control != (sc->ctrl_register & HDSP_OUTPUT_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_OUTPUT_LEVEL_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static const char * hdsp_control_phones_level(uint32_t control) { switch (control & HDSP_PHONES_LEVEL_MASK) { case HDSP_PHONES_LEVEL_MINUS12DB: return ("-12dB"); case HDSP_PHONES_LEVEL_MINUS6DB: return ("-6dB"); case HDSP_PHONES_LEVEL_0DB: return ("0dB"); default: return (NULL); } } static int hdsp_sysctl_phones_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Only available on HDSP 9632. */ if (sc->type != HDSP_9632) return (ENXIO); /* Extract current phones level from control register. */ control = sc->ctrl_register & HDSP_PHONES_LEVEL_MASK; label = hdsp_control_phones_level(control); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find phones level matching the sysctl string. */ label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_MINUS12DB); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_PHONES_LEVEL_MINUS12DB; label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_MINUS6DB); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_PHONES_LEVEL_MINUS6DB; label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_0DB); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_PHONES_LEVEL_0DB; /* Set phones level in control register. */ control &= HDSP_PHONES_LEVEL_MASK; if (control != (sc->ctrl_register & HDSP_PHONES_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_PHONES_LEVEL_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static int hdsp_sysctl_sample_rate(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int speed, multiplier; speed = sc->force_speed; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &speed, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Speed from 32000 to 192000, 0 falls back to pcm speed setting. */ sc->force_speed = 0; if (speed > 0) { multiplier = 1; if ((speed > (96000 + 128000) / 2) && sc->type == HDSP_9632) multiplier = 4; else if (speed > (48000 + 64000) / 2) multiplier = 2; if (speed < ((32000 + 44100) / 2) * multiplier) sc->force_speed = 32000 * multiplier; else if (speed < ((44100 + 48000) / 2) * multiplier) sc->force_speed = 44100 * multiplier; else sc->force_speed = 48000 * multiplier; } return (0); } static int hdsp_sysctl_period(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int period; period = sc->force_period; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &period, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Period is from 2^5 to 2^14, 0 falls back to pcm latency settings. */ sc->force_period = 0; if (period > 0) { sc->force_period = 32; while (sc->force_period < period && sc->force_period < 4096) sc->force_period <<= 1; } return (0); } static uint32_t hdsp_control_clock_preference(enum hdsp_clock_type type) { switch (type) { case HDSP_CLOCK_INTERNAL: return (HDSP_CONTROL_MASTER); case HDSP_CLOCK_ADAT1: return (HDSP_CONTROL_CLOCK(0)); case HDSP_CLOCK_ADAT2: return (HDSP_CONTROL_CLOCK(1)); case HDSP_CLOCK_ADAT3: return (HDSP_CONTROL_CLOCK(2)); case HDSP_CLOCK_SPDIF: return (HDSP_CONTROL_CLOCK(3)); case HDSP_CLOCK_WORD: return (HDSP_CONTROL_CLOCK(4)); case HDSP_CLOCK_ADAT_SYNC: return (HDSP_CONTROL_CLOCK(5)); default: return (HDSP_CONTROL_MASTER); } } static int hdsp_sysctl_clock_preference(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* Extract preferred clock source from control register. */ control = sc->ctrl_register & HDSP_CONTROL_CLOCK_MASK; for (clock = clock_table; clock->name != NULL; ++clock) { if (hdsp_control_clock_preference(clock->type) == control) break; } if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find clock source matching the sysctl string. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (strncasecmp(buf, clock->name, sizeof(buf)) == 0) break; } /* Set preferred clock source in control register. */ if (clock->name != NULL) { control = hdsp_control_clock_preference(clock->type); control &= HDSP_CONTROL_CLOCK_MASK; snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_CONTROL_CLOCK_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static uint32_t hdsp_status2_clock_source(enum hdsp_clock_type type) { switch (type) { case HDSP_CLOCK_INTERNAL: return (0); case HDSP_CLOCK_ADAT1: return (HDSP_STATUS2_CLOCK(0)); case HDSP_CLOCK_ADAT2: return (HDSP_STATUS2_CLOCK(1)); case HDSP_CLOCK_ADAT3: return (HDSP_STATUS2_CLOCK(2)); case HDSP_CLOCK_SPDIF: return (HDSP_STATUS2_CLOCK(3)); case HDSP_CLOCK_WORD: return (HDSP_STATUS2_CLOCK(4)); case HDSP_CLOCK_ADAT_SYNC: return (HDSP_STATUS2_CLOCK(5)); default: return (0); } } static int hdsp_sysctl_clock_source(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[16] = "invalid"; uint32_t status2; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* Read current (autosync) clock source from status2 register. */ snd_mtxlock(sc->lock); status2 = hdsp_read_4(sc, HDSP_STATUS2_REG); status2 &= HDSP_STATUS2_CLOCK_MASK; snd_mtxunlock(sc->lock); /* Translate status2 register value to clock source. */ for (clock = clock_table; clock->name != NULL; ++clock) { /* In clock master mode, override with internal clock source. */ if (sc->ctrl_register & HDSP_CONTROL_MASTER) { if (clock->type == HDSP_CLOCK_INTERNAL) break; } else if (hdsp_status2_clock_source(clock->type) == status2) break; } /* Process sysctl string request. */ if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdsp_sysctl_clock_list(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[256]; int n; sc = oidp->oid_arg1; n = 0; /* Select clock source table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* List available clock sources. */ buf[0] = 0; for (clock = clock_table; clock->name != NULL; ++clock) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); n += strlcpy(buf + n, clock->name, sizeof(buf) - n); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static bool hdsp_clock_source_locked(enum hdsp_clock_type type, uint32_t status, uint32_t status2) { switch (type) { case HDSP_CLOCK_INTERNAL: return (true); case HDSP_CLOCK_ADAT1: return ((status >> 3) & 0x01); case HDSP_CLOCK_ADAT2: return ((status >> 2) & 0x01); case HDSP_CLOCK_ADAT3: return ((status >> 1) & 0x01); case HDSP_CLOCK_SPDIF: return (!((status >> 25) & 0x01)); case HDSP_CLOCK_WORD: return ((status2 >> 3) & 0x01); case HDSP_CLOCK_ADAT_SYNC: return ((status >> 5) & 0x01); default: return (false); } } static bool hdsp_clock_source_synced(enum hdsp_clock_type type, uint32_t status, uint32_t status2) { switch (type) { case HDSP_CLOCK_INTERNAL: return (true); case HDSP_CLOCK_ADAT1: return ((status >> 18) & 0x01); case HDSP_CLOCK_ADAT2: return ((status >> 17) & 0x01); case HDSP_CLOCK_ADAT3: return ((status >> 16) & 0x01); case HDSP_CLOCK_SPDIF: return (((status >> 4) & 0x01) && !((status >> 25) & 0x01)); case HDSP_CLOCK_WORD: return ((status2 >> 4) & 0x01); case HDSP_CLOCK_ADAT_SYNC: return ((status >> 27) & 0x01); default: return (false); } } static int hdsp_sysctl_sync_status(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[256]; char *state; int n; uint32_t status, status2; sc = oidp->oid_arg1; n = 0; /* Select sync ports table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* Read current lock and sync bits from status registers. */ snd_mtxlock(sc->lock); status = hdsp_read_4(sc, HDSP_STATUS_REG); status2 = hdsp_read_4(sc, HDSP_STATUS2_REG); snd_mtxunlock(sc->lock); /* List clock sources with lock and sync state. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->type == HDSP_CLOCK_INTERNAL) continue; if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); state = "none"; if (hdsp_clock_source_locked(clock->type, status, status2)) { if (hdsp_clock_source_synced(clock->type, status, status2)) state = "sync"; else state = "lock"; } n += snprintf(buf + n, sizeof(buf) - n, "%s(%s)", clock->name, state); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdsp_probe(device_t dev) { uint32_t rev; if (pci_get_vendor(dev) == PCI_VENDOR_XILINX && pci_get_device(dev) == PCI_DEVICE_XILINX_HDSP) { rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_9632: device_set_desc(dev, "RME HDSP 9632"); return (0); case PCI_REVISION_9652: device_set_desc(dev, "RME HDSP 9652"); return (0); } } return (ENXIO); } static int hdsp_init(struct sc_info *sc) { unsigned mixer_controls; /* Set latency. */ sc->period = 256; /* * The pcm channel latency settings propagate unreliable blocksizes, * different for recording and playback, and skewed due to rounding * and total buffer size limits. * Force period to a consistent default until these issues are fixed. */ sc->force_period = 256; sc->ctrl_register = hdsp_encode_latency(2); /* Set rate. */ sc->speed = HDSP_SPEED_DEFAULT; sc->force_speed = 0; sc->ctrl_register &= ~HDSP_FREQ_MASK; sc->ctrl_register |= HDSP_FREQ_MASK_DEFAULT; /* Set internal clock source (master). */ sc->ctrl_register &= ~HDSP_CONTROL_CLOCK_MASK; sc->ctrl_register |= HDSP_CONTROL_MASTER; /* SPDIF from coax in, line out. */ sc->ctrl_register &= ~HDSP_CONTROL_SPDIF_COAX; sc->ctrl_register |= HDSP_CONTROL_SPDIF_COAX; sc->ctrl_register &= ~HDSP_CONTROL_LINE_OUT; sc->ctrl_register |= HDSP_CONTROL_LINE_OUT; /* Default gain levels. */ sc->ctrl_register &= ~HDSP_INPUT_LEVEL_MASK; sc->ctrl_register |= HDSP_INPUT_LEVEL_LOWGAIN; sc->ctrl_register &= ~HDSP_OUTPUT_LEVEL_MASK; sc->ctrl_register |= HDSP_OUTPUT_LEVEL_MINUS10DBV; sc->ctrl_register &= ~HDSP_PHONES_LEVEL_MASK; sc->ctrl_register |= HDSP_PHONES_LEVEL_MINUS12DB; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); if (sc->type == HDSP_9652) hdsp_write_4(sc, HDSP_CONTROL2_REG, HDSP_CONTROL2_9652_MIXER); else hdsp_write_4(sc, HDSP_CONTROL2_REG, 0); switch (sc->type) { case HDSP_9632: /* Mixer matrix is 2 source rows (input, playback) per output. */ mixer_controls = 2 * HDSP_MIX_SLOTS_9632 * HDSP_MIX_SLOTS_9632; break; case HDSP_9652: /* Mixer matrix is 2 source rows (input, playback) per output. */ mixer_controls = 2 * HDSP_MIX_SLOTS_9652 * HDSP_MIX_SLOTS_9652; break; default: return (ENXIO); } /* Initialize mixer matrix by silencing all controls. */ for (unsigned offset = 0; offset < mixer_controls * 2; offset += 4) { /* Only accepts 4 byte values, pairs of 16 bit volume controls. */ hdsp_write_4(sc, HDSP_MIXER_BASE + offset, (HDSP_MIN_GAIN << 16) | HDSP_MIN_GAIN); } /* Reset pointer, rewrite frequency (same register) for 9632. */ hdsp_write_4(sc, HDSP_RESET_POINTER, 0); if (sc->type == HDSP_9632) { /* Set DDS value. */ hdsp_write_4(sc, HDSP_FREQ_REG, hdsp_freq_reg_value(sc->speed)); } return (0); } static int hdsp_attach(device_t dev) { struct hdsp_channel *chan_map; struct sc_pcminfo *scp; struct sc_info *sc; uint32_t rev; int i, err; #if 0 device_printf(dev, "hdsp_attach()\n"); #endif sc = device_get_softc(dev); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_hdsp softc"); sc->dev = dev; pci_enable_busmaster(dev); rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_9632: sc->type = HDSP_9632; chan_map = hdsp_unified_pcm ? chan_map_9632_uni : chan_map_9632; break; case PCI_REVISION_9652: sc->type = HDSP_9652; chan_map = hdsp_unified_pcm ? chan_map_9652_uni : chan_map_9652; break; default: return (ENXIO); } /* Allocate resources. */ err = hdsp_alloc_resources(sc); if (err) { device_printf(dev, "Unable to allocate system resources.\n"); return (ENXIO); } if (hdsp_init(sc) != 0) return (ENXIO); for (i = 0; i < HDSP_MAX_CHANS && chan_map[i].descr != NULL; i++) { scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_WAITOK | M_ZERO); scp->hc = &chan_map[i]; scp->sc = sc; - scp->dev = device_add_child(dev, "pcm", -1); + scp->dev = device_add_child(dev, "pcm", DEVICE_UNIT_ANY); device_set_ivars(scp->dev, scp); } hdsp_map_dmabuf(sc); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sync_status", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_sync_status, "A", "List clock source signal lock and sync status"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_source", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_clock_source, "A", "Currently effective clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_preference", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_clock_preference, "A", "Set 'internal' (master) or preferred autosync clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_list", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_clock_list, "A", "List of supported clock sources"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "period", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_period, "A", "Force period of samples per interrupt (32, 64, ... 4096)"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sample_rate", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_sample_rate, "A", "Force sample rate (32000, 44100, 48000, ... 192000)"); if (sc->type == HDSP_9632) { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "phones_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_phones_level, "A", "Phones output level ('0dB', '-6dB', '-12dB')"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "output_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_output_level, "A", "Analog output level ('HighGain', '+4dBU', '-10dBV')"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "input_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_input_level, "A", "Analog input level ('LowGain', '+4dBU', '-10dBV')"); } bus_attach_children(dev); return (0); } static void hdsp_child_deleted(device_t dev, device_t child) { free(device_get_ivars(child), M_DEVBUF); } static void hdsp_dmafree(struct sc_info *sc) { bus_dmamap_unload(sc->dmat, sc->rmap); bus_dmamap_unload(sc->dmat, sc->pmap); bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); sc->rbuf = sc->pbuf = NULL; } static int hdsp_detach(device_t dev) { struct sc_info *sc; int err; sc = device_get_softc(dev); if (sc == NULL) { device_printf(dev,"Can't detach: softc is null.\n"); return (0); } err = bus_generic_detach(dev); if (err) return (err); hdsp_dmafree(sc); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); if (sc->cs) bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), sc->cs); if (sc->lock) snd_mtxfree(sc->lock); return (0); } static device_method_t hdsp_methods[] = { DEVMETHOD(device_probe, hdsp_probe), DEVMETHOD(device_attach, hdsp_attach), DEVMETHOD(device_detach, hdsp_detach), DEVMETHOD(bus_child_deleted, hdsp_child_deleted), { 0, 0 } }; static driver_t hdsp_driver = { "hdsp", hdsp_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdsp, pci, hdsp_driver, 0, 0); diff --git a/sys/dev/spibus/acpi_spibus.c b/sys/dev/spibus/acpi_spibus.c index 749113d81220..a3280ffa567f 100644 --- a/sys/dev/spibus/acpi_spibus.c +++ b/sys/dev/spibus/acpi_spibus.c @@ -1,581 +1,581 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 Vladimir Kondratyev * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Make a copy of ACPI_RESOURCE_SPI_SERIALBUS type and replace "pointer to ACPI * object name string" field with pointer to ACPI object itself. * This saves us extra strdup()/free() pair on acpi_spibus_get_acpi_res call. */ typedef ACPI_RESOURCE_SPI_SERIALBUS ACPI_SPIBUS_RESOURCE_SPI_SERIALBUS; #define ResourceSource_Handle ResourceSource.StringPtr /* Hooks for the ACPI CA debugging infrastructure. */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("SPI") #if defined (__amd64__) || defined (__i386__) static bool is_apple; #endif struct acpi_spibus_ivar { struct spibus_ivar super_ivar; ACPI_HANDLE handle; }; static inline bool acpi_resource_is_spi_serialbus(ACPI_RESOURCE *res) { return (res->Type == ACPI_RESOURCE_TYPE_SERIAL_BUS && res->Data.CommonSerialBus.Type == ACPI_RESOURCE_SERIAL_TYPE_SPI); } static ACPI_STATUS acpi_spibus_get_acpi_res_cb(ACPI_RESOURCE *res, void *context) { ACPI_SPIBUS_RESOURCE_SPI_SERIALBUS *sb = context; ACPI_STATUS status; ACPI_HANDLE handle; if (acpi_resource_is_spi_serialbus(res)) { status = AcpiGetHandle(ACPI_ROOT_OBJECT, res->Data.SpiSerialBus.ResourceSource.StringPtr, &handle); if (ACPI_FAILURE(status)) return (status); memcpy(sb, &res->Data.SpiSerialBus, sizeof(ACPI_SPIBUS_RESOURCE_SPI_SERIALBUS)); /* * replace "pointer to ACPI object name string" field * with pointer to ACPI object itself. */ sb->ResourceSource_Handle = handle; return (AE_CTRL_TERMINATE); } else if (res->Type == ACPI_RESOURCE_TYPE_END_TAG) return (AE_NOT_FOUND); return (AE_OK); } static void acpi_spibus_dump_res(device_t dev, ACPI_SPIBUS_RESOURCE_SPI_SERIALBUS *sb) { device_printf(dev, "found ACPI child\n"); printf(" DeviceSelection: 0x%04hx\n", sb->DeviceSelection); printf(" ConnectionSpeed: %uHz\n", sb->ConnectionSpeed); printf(" WireMode: %s\n", sb->WireMode == ACPI_SPI_4WIRE_MODE ? "FourWireMode" : "ThreeWireMode"); printf(" DevicePolarity: %s\n", sb->DevicePolarity == ACPI_SPI_ACTIVE_LOW ? "PolarityLow" : "PolarityHigh"); printf(" DataBitLength: %uBit\n", sb->DataBitLength); printf(" ClockPhase: %s\n", sb->ClockPhase == ACPI_SPI_FIRST_PHASE ? "ClockPhaseFirst" : "ClockPhaseSecond"); printf(" ClockPolarity: %s\n", sb->ClockPolarity == ACPI_SPI_START_LOW ? "ClockPolarityLow" : "ClockPolarityHigh"); printf(" SlaveMode: %s\n", sb->SlaveMode == ACPI_CONTROLLER_INITIATED ? "ControllerInitiated" : "DeviceInitiated"); printf(" ConnectionSharing: %s\n", sb->ConnectionSharing == 0 ? "Exclusive" : "Shared"); } static int acpi_spibus_get_acpi_res(device_t spibus, ACPI_HANDLE dev, struct spibus_ivar *res) { ACPI_SPIBUS_RESOURCE_SPI_SERIALBUS sb; /* * Read "SPI Serial Bus Connection Resource Descriptor" * described in p.19.6.126 of ACPI specification. */ bzero(&sb, sizeof(ACPI_SPIBUS_RESOURCE_SPI_SERIALBUS)); if (ACPI_FAILURE(AcpiWalkResources(dev, "_CRS", acpi_spibus_get_acpi_res_cb, &sb))) return (ENXIO); if (sb.ResourceSource_Handle != acpi_get_handle(device_get_parent(spibus))) return (ENXIO); if (bootverbose) acpi_spibus_dump_res(spibus, &sb); /* * The Windows Baytrail and Braswell SPI host controller * drivers uses 1 as the first (and only) value for ACPI * DeviceSelection. */ if (sb.DeviceSelection != 0 && (acpi_MatchHid(sb.ResourceSource_Handle, "80860F0E") || acpi_MatchHid(sb.ResourceSource_Handle, "8086228E"))) res->cs = sb.DeviceSelection - 1; else res->cs = sb.DeviceSelection; res->mode = (sb.ClockPhase != ACPI_SPI_FIRST_PHASE ? SPIBUS_MODE_CPHA : 0) | (sb.ClockPolarity != ACPI_SPI_START_LOW ? SPIBUS_MODE_CPOL : 0); res->clock = sb.ConnectionSpeed; return (0); } #if defined (__amd64__) || defined (__i386__) static int acpi_spibus_get_apple_res(device_t spibus, ACPI_HANDLE dev, struct spibus_ivar *ivar) { /* a0b5b7c6-1318-441c-b0c9-fe695eaf949b */ static const uint8_t apple_guid[ACPI_UUID_LENGTH] = { 0xC6, 0xB7, 0xB5, 0xA0, 0x18, 0x13, 0x1C, 0x44, 0xB0, 0xC9, 0xFE, 0x69, 0x5E, 0xAF, 0x94, 0x9B, }; ACPI_BUFFER buf; ACPI_OBJECT *pkg, *comp; ACPI_HANDLE parent; char *k; uint64_t val; /* Apple does not use _CRS but nested devices for SPI slaves */ if (ACPI_FAILURE(AcpiGetParent(dev, &parent))) return (ENXIO); if (parent != acpi_get_handle(device_get_parent(spibus))) return (ENXIO); if (ACPI_FAILURE(acpi_EvaluateDSMTyped(dev, apple_guid, 1, 1, NULL, &buf, ACPI_TYPE_PACKAGE))) return (ENXIO); pkg = ((ACPI_OBJECT *)buf.Pointer); if (pkg->Package.Count % 2 != 0) { device_printf(spibus, "_DSM length %d not even\n", pkg->Package.Count); AcpiOsFree(pkg); return (ENXIO); } if (bootverbose) device_printf(spibus, "found ACPI child\n"); for (comp = pkg->Package.Elements; comp < pkg->Package.Elements + pkg->Package.Count; comp += 2) { if (comp[0].Type != ACPI_TYPE_STRING || comp[1].Type != ACPI_TYPE_BUFFER) { device_printf(spibus, "expected string+buffer, " "got %d+%d\n", comp[0].Type, comp[1].Type); continue; } k = comp[0].String.Pointer; val = comp[1].Buffer.Length >= 8 ? *(uint64_t *)comp[1].Buffer.Pointer : 0; if (bootverbose) printf(" %s: %ju\n", k, (intmax_t)val); if (strcmp(k, "spiSclkPeriod") == 0) { if (val != 0) ivar->clock = 1000000000 / val; } else if (strcmp(k, "spiSPO") == 0) { if (val != 0) ivar->mode |= SPIBUS_MODE_CPOL; } else if (strcmp(k, "spiSPH") == 0) { if (val != 0) ivar->mode |= SPIBUS_MODE_CPHA; } else if (strcmp(k, "spiCSDelay") == 0) { ivar->cs_delay = val; } } AcpiOsFree(pkg); return (0); } #endif static int acpi_spibus_delete_acpi_child(ACPI_HANDLE handle) { device_t acpi_child, acpi0; /* Delete existing child of acpi bus */ acpi_child = acpi_get_device(handle); if (acpi_child != NULL) { acpi0 = devclass_get_device(devclass_find("acpi"), 0); if (device_get_parent(acpi_child) != acpi0) return (ENXIO); if (device_is_attached(acpi_child)) return (ENXIO); if (device_delete_child(acpi0, acpi_child) != 0) return (ENXIO); } return (0); } static device_t acpi_spibus_add_child(device_t dev, u_int order, const char *name, int unit) { return (spibus_add_child_common( dev, order, name, unit, sizeof(struct acpi_spibus_ivar))); } static ACPI_STATUS acpi_spibus_enumerate_child(ACPI_HANDLE handle, UINT32 level, void *context, void **result) { device_t spibus, child; struct spibus_ivar res; ACPI_STATUS status; UINT32 sta; bool found = false; spibus = context; /* * If no _STA method or if it failed, then assume that * the device is present. */ if (!ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) && !ACPI_DEVICE_PRESENT(sta)) return (AE_OK); if (!acpi_has_hid(handle)) return (AE_OK); bzero(&res, sizeof(res)); if (acpi_spibus_get_acpi_res(spibus, handle, &res) == 0) found = true; #if defined (__amd64__) || defined (__i386__) if (!found && is_apple && acpi_spibus_get_apple_res(spibus, handle, &res) == 0) found = true; #endif if (!found || res.clock == 0) return (AE_OK); /* Delete existing child of acpi bus */ if (acpi_spibus_delete_acpi_child(handle) != 0) return (AE_OK); - child = BUS_ADD_CHILD(spibus, 0, NULL, -1); + child = BUS_ADD_CHILD(spibus, 0, NULL, DEVICE_UNIT_ANY); if (child == NULL) { device_printf(spibus, "add child failed\n"); return (AE_OK); } spibus_set_cs(child, res.cs); spibus_set_mode(child, res.mode); spibus_set_clock(child, res.clock); spibus_set_cs_delay(child, res.cs_delay); acpi_set_handle(child, handle); acpi_parse_resources(child, handle, &acpi_res_parse_set, NULL); /* * Update ACPI-CA to use the IIC enumerated device_t for this handle. */ status = AcpiAttachData(handle, acpi_fake_objhandler, child); if (ACPI_FAILURE(status)) printf("WARNING: Unable to attach object data to %s - %s\n", acpi_name(handle), AcpiFormatException(status)); return (AE_OK); } static ACPI_STATUS acpi_spibus_enumerate_children(device_t dev) { return (AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, acpi_spibus_enumerate_child, NULL, dev, NULL)); } static void acpi_spibus_set_power_children(device_t dev, int state, bool all_children) { device_t *devlist; int i, numdevs; if (device_get_children(dev, &devlist, &numdevs) != 0) return; for (i = 0; i < numdevs; i++) if (all_children || device_is_attached(devlist[i]) != 0) acpi_set_powerstate(devlist[i], state); free(devlist, M_TEMP); } static int acpi_spibus_probe(device_t dev) { ACPI_HANDLE handle; device_t controller; if (acpi_disabled("spibus")) return (ENXIO); controller = device_get_parent(dev); if (controller == NULL) return (ENXIO); handle = acpi_get_handle(controller); if (handle == NULL) return (ENXIO); device_set_desc(dev, "SPI bus (ACPI-hinted)"); return (BUS_PROBE_DEFAULT + 1); } static int acpi_spibus_attach(device_t dev) { #if defined (__amd64__) || defined (__i386__) char *vendor = kern_getenv("smbios.bios.vendor"); if (vendor != NULL && (strcmp(vendor, "Apple Inc.") == 0 || strcmp(vendor, "Apple Computer, Inc.") == 0)) is_apple = true; #endif if (ACPI_FAILURE(acpi_spibus_enumerate_children(dev))) device_printf(dev, "children enumeration failed\n"); acpi_spibus_set_power_children(dev, ACPI_STATE_D0, true); return (spibus_attach(dev)); } static int acpi_spibus_detach(device_t dev) { acpi_spibus_set_power_children(dev, ACPI_STATE_D3, false); return (bus_generic_detach(dev)); } static int acpi_spibus_suspend(device_t dev) { acpi_spibus_set_power_children(dev, ACPI_STATE_D3, false); return (bus_generic_suspend(dev)); } static int acpi_spibus_resume(device_t dev) { acpi_spibus_set_power_children(dev, ACPI_STATE_D0, false); return (bus_generic_resume(dev)); } #ifndef INTRNG /* Mostly copy of acpi_alloc_resource() */ static struct resource * acpi_spibus_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { ACPI_RESOURCE ares; struct resource_list *rl; struct resource *res; if (device_get_parent(child) != dev) return (BUS_ALLOC_RESOURCE(device_get_parent(dev), child, type, rid, start, end, count, flags)); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl == NULL) return (NULL); res = resource_list_alloc(rl, dev, child, type, rid, start, end, count, flags); if (res != NULL && type == SYS_RES_IRQ && ACPI_SUCCESS(acpi_lookup_irq_resource(child, *rid, res, &ares))) acpi_config_intr(child, &ares); return (res); } #endif /* * If this device is an ACPI child but no one claimed it, attempt * to power it off. We'll power it back up when a driver is added. */ static void acpi_spibus_probe_nomatch(device_t bus, device_t child) { spibus_probe_nomatch(bus, child); acpi_set_powerstate(child, ACPI_STATE_D3); } /* * If a new driver has a chance to probe a child, first power it up. */ static void acpi_spibus_driver_added(device_t dev, driver_t *driver) { device_t child, *devlist; int i, numdevs; DEVICE_IDENTIFY(driver, dev); if (device_get_children(dev, &devlist, &numdevs) != 0) return; for (i = 0; i < numdevs; i++) { child = devlist[i]; if (device_get_state(child) == DS_NOTPRESENT) { acpi_set_powerstate(child, ACPI_STATE_D0); if (device_probe_and_attach(child) != 0) acpi_set_powerstate(child, ACPI_STATE_D3); } } free(devlist, M_TEMP); } static void acpi_spibus_child_deleted(device_t bus, device_t child) { struct acpi_spibus_ivar *devi = device_get_ivars(child); if (acpi_get_device(devi->handle) == child) AcpiDetachData(devi->handle, acpi_fake_objhandler); } static int acpi_spibus_read_ivar(device_t bus, device_t child, int which, uintptr_t *res) { struct acpi_spibus_ivar *devi = device_get_ivars(child); switch (which) { case ACPI_IVAR_HANDLE: *res = (uintptr_t)devi->handle; break; default: return (spibus_read_ivar(bus, child, which, res)); } return (0); } static int acpi_spibus_write_ivar(device_t bus, device_t child, int which, uintptr_t val) { struct acpi_spibus_ivar *devi = device_get_ivars(child); switch (which) { case ACPI_IVAR_HANDLE: if (devi->handle != NULL) return (EINVAL); devi->handle = (ACPI_HANDLE)val; break; default: return (spibus_write_ivar(bus, child, which, val)); } return (0); } /* Location hint for devctl(8). Concatenate IIC and ACPI hints. */ static int acpi_spibus_child_location(device_t bus, device_t child, struct sbuf *sb) { struct acpi_spibus_ivar *devi = device_get_ivars(child); int error; /* read SPI location hint string into the buffer. */ error = spibus_child_location(bus, child, sb); if (error != 0) return (error); /* Place ACPI string right after IIC one's terminating NUL. */ if (devi->handle != NULL) sbuf_printf(sb, " handle=%s", acpi_name(devi->handle)); return (0); } /* PnP information for devctl(8). */ static int acpi_spibus_child_pnpinfo(device_t bus, device_t child, struct sbuf *sb) { struct acpi_spibus_ivar *devi = device_get_ivars(child); return ( devi->handle == NULL ? ENOTSUP : acpi_pnpinfo(devi->handle, sb)); } static device_method_t acpi_spibus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_spibus_probe), DEVMETHOD(device_attach, acpi_spibus_attach), DEVMETHOD(device_detach, acpi_spibus_detach), DEVMETHOD(device_suspend, acpi_spibus_suspend), DEVMETHOD(device_resume, acpi_spibus_resume), /* Bus interface */ #ifndef INTRNG DEVMETHOD(bus_alloc_resource, acpi_spibus_alloc_resource), #endif DEVMETHOD(bus_add_child, acpi_spibus_add_child), DEVMETHOD(bus_child_deleted, spibus_child_deleted), DEVMETHOD(bus_probe_nomatch, acpi_spibus_probe_nomatch), DEVMETHOD(bus_driver_added, acpi_spibus_driver_added), DEVMETHOD(bus_child_deleted, acpi_spibus_child_deleted), DEVMETHOD(bus_read_ivar, acpi_spibus_read_ivar), DEVMETHOD(bus_write_ivar, acpi_spibus_write_ivar), DEVMETHOD(bus_child_location, acpi_spibus_child_location), DEVMETHOD(bus_child_pnpinfo, acpi_spibus_child_pnpinfo), DEVMETHOD(bus_get_device_path, acpi_get_acpi_device_path), DEVMETHOD_END, }; DEFINE_CLASS_1(spibus, acpi_spibus_driver, acpi_spibus_methods, sizeof(struct spibus_softc), spibus_driver); DRIVER_MODULE(acpi_spibus, spi, acpi_spibus_driver, NULL, NULL); MODULE_VERSION(acpi_spibus, 1); MODULE_DEPEND(acpi_spibus, acpi, 1, 1, 1); diff --git a/sys/dev/superio/superio.c b/sys/dev/superio/superio.c index be852a3db367..24d40eb7a208 100644 --- a/sys/dev/superio/superio.c +++ b/sys/dev/superio/superio.c @@ -1,1120 +1,1120 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Andriy Gapon * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "isa_if.h" typedef void (*sio_conf_enter_f)(struct resource*, uint16_t); typedef void (*sio_conf_exit_f)(struct resource*, uint16_t); struct sio_conf_methods { sio_conf_enter_f enter; sio_conf_exit_f exit; superio_vendor_t vendor; }; struct sio_device { uint8_t ldn; superio_dev_type_t type; }; struct superio_devinfo { STAILQ_ENTRY(superio_devinfo) link; struct resource_list resources; device_t dev; uint8_t ldn; superio_dev_type_t type; uint16_t iobase; uint16_t iobase2; uint8_t irq; uint8_t dma; }; struct siosc { struct mtx conf_lock; STAILQ_HEAD(, superio_devinfo) devlist; struct resource* io_res; struct cdev *chardev; int io_rid; uint16_t io_port; const struct sio_conf_methods *methods; const struct sio_device *known_devices; superio_vendor_t vendor; uint16_t devid; uint8_t revid; int extid; uint8_t current_ldn; uint8_t ldn_reg; uint8_t enable_reg; }; static d_ioctl_t superio_ioctl; static struct cdevsw superio_cdevsw = { .d_version = D_VERSION, .d_ioctl = superio_ioctl, .d_name = "superio", }; #define NUMPORTS 2 static uint8_t sio_read(struct resource* res, uint8_t reg) { bus_write_1(res, 0, reg); return (bus_read_1(res, 1)); } /* Read a word from two one-byte registers, big endian. */ static uint16_t sio_readw(struct resource* res, uint8_t reg) { uint16_t v; v = sio_read(res, reg); v <<= 8; v |= sio_read(res, reg + 1); return (v); } static void sio_write(struct resource* res, uint8_t reg, uint8_t val) { bus_write_1(res, 0, reg); bus_write_1(res, 1, val); } static void sio_ldn_select(struct siosc *sc, uint8_t ldn) { mtx_assert(&sc->conf_lock, MA_OWNED); if (ldn == sc->current_ldn) return; sio_write(sc->io_res, sc->ldn_reg, ldn); sc->current_ldn = ldn; } static uint8_t sio_ldn_read(struct siosc *sc, uint8_t ldn, uint8_t reg) { mtx_assert(&sc->conf_lock, MA_OWNED); if (reg >= sc->enable_reg) { sio_ldn_select(sc, ldn); KASSERT(sc->current_ldn == ldn, ("sio_ldn_select failed")); } return (sio_read(sc->io_res, reg)); } static uint16_t sio_ldn_readw(struct siosc *sc, uint8_t ldn, uint8_t reg) { mtx_assert(&sc->conf_lock, MA_OWNED); if (reg >= sc->enable_reg) { sio_ldn_select(sc, ldn); KASSERT(sc->current_ldn == ldn, ("sio_ldn_select failed")); } return (sio_readw(sc->io_res, reg)); } static void sio_ldn_write(struct siosc *sc, uint8_t ldn, uint8_t reg, uint8_t val) { mtx_assert(&sc->conf_lock, MA_OWNED); if (reg <= sc->ldn_reg) { printf("ignored attempt to write special register 0x%x\n", reg); return; } sio_ldn_select(sc, ldn); KASSERT(sc->current_ldn == ldn, ("sio_ldn_select failed")); sio_write(sc->io_res, reg, val); } static void sio_conf_enter(struct siosc *sc) { mtx_lock(&sc->conf_lock); sc->methods->enter(sc->io_res, sc->io_port); } static void sio_conf_exit(struct siosc *sc) { sc->methods->exit(sc->io_res, sc->io_port); sc->current_ldn = 0xff; mtx_unlock(&sc->conf_lock); } static void ite_conf_enter(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0x87); bus_write_1(res, 0, 0x01); bus_write_1(res, 0, 0x55); bus_write_1(res, 0, port == 0x2e ? 0x55 : 0xaa); } static void ite_conf_exit(struct resource* res, uint16_t port) { sio_write(res, 0x02, 0x02); } static const struct sio_conf_methods ite_conf_methods = { .enter = ite_conf_enter, .exit = ite_conf_exit, .vendor = SUPERIO_VENDOR_ITE }; static void nvt_conf_enter(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0x87); bus_write_1(res, 0, 0x87); } static void nvt_conf_exit(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0xaa); } static const struct sio_conf_methods nvt_conf_methods = { .enter = nvt_conf_enter, .exit = nvt_conf_exit, .vendor = SUPERIO_VENDOR_NUVOTON }; static void fintek_conf_enter(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0x87); bus_write_1(res, 0, 0x87); } static void fintek_conf_exit(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0xaa); } static const struct sio_conf_methods fintek_conf_methods = { .enter = fintek_conf_enter, .exit = fintek_conf_exit, .vendor = SUPERIO_VENDOR_FINTEK }; static const struct sio_conf_methods * const methods_table[] = { &ite_conf_methods, &nvt_conf_methods, &fintek_conf_methods, NULL }; static const uint16_t ports_table[] = { 0x2e, 0x4e, 0 }; const struct sio_device ite_devices[] = { { .ldn = 4, .type = SUPERIO_DEV_HWM }, { .ldn = 7, .type = SUPERIO_DEV_WDT }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device w83627_devices[] = { { .ldn = 8, .type = SUPERIO_DEV_WDT }, { .ldn = 9, .type = SUPERIO_DEV_GPIO }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device nvt_devices[] = { { .ldn = 8, .type = SUPERIO_DEV_WDT }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device nct5104_devices[] = { { .ldn = 7, .type = SUPERIO_DEV_GPIO }, { .ldn = 8, .type = SUPERIO_DEV_WDT }, { .ldn = 15, .type = SUPERIO_DEV_GPIO }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device nct5585_devices[] = { { .ldn = 9, .type = SUPERIO_DEV_GPIO }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device nct611x_devices[] = { { .ldn = 0x7, .type = SUPERIO_DEV_GPIO }, { .ldn = 0x8, .type = SUPERIO_DEV_WDT }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device nct67xx_devices[] = { { .ldn = 0x8, .type = SUPERIO_DEV_WDT }, { .ldn = 0x9, .type = SUPERIO_DEV_GPIO }, { .ldn = 0xb, .type = SUPERIO_DEV_HWM }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device fintek_devices[] = { { .ldn = 6, .type = SUPERIO_DEV_GPIO }, { .ldn = 7, .type = SUPERIO_DEV_WDT }, { .type = SUPERIO_DEV_NONE }, }; static const struct { superio_vendor_t vendor; uint16_t devid; uint16_t mask; int extid; /* Extra ID: used to handle conflicting devid. */ const char *descr; const struct sio_device *devices; } superio_table[] = { { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8613, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8712, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8716, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8718, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8720, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8721, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8726, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8728, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8771, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x1061, .mask = 0x00, .descr = "Nuvoton NCT5104D/NCT6102D/NCT6106D (rev. A)", .devices = nct5104_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x5200, .mask = 0xff, .descr = "Winbond 83627HF/F/HG/G", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x5900, .mask = 0xff, .descr = "Winbond 83627S", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x6000, .mask = 0xff, .descr = "Winbond 83697HF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x6800, .mask = 0xff, .descr = "Winbond 83697UG", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x7000, .mask = 0xff, .descr = "Winbond 83637HF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x8200, .mask = 0xff, .descr = "Winbond 83627THF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x8500, .mask = 0xff, .descr = "Winbond 83687THF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x8800, .mask = 0xff, .descr = "Winbond 83627EHF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xa000, .mask = 0xff, .descr = "Winbond 83627DHG", .devices = w83627_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xa200, .mask = 0xff, .descr = "Winbond 83627UHG", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xa500, .mask = 0xff, .descr = "Winbond 83667HG", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xb000, .mask = 0xff, .descr = "Winbond 83627DHG-P", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xb300, .mask = 0xff, .descr = "Winbond 83667HG-B", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xb400, .mask = 0xff, .descr = "Nuvoton NCT6775", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc300, .mask = 0xff, .descr = "Nuvoton NCT6776", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc400, .mask = 0xff, .descr = "Nuvoton NCT5104D/NCT6102D/NCT6106D (rev. B+)", .devices = nct5104_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc500, .mask = 0xff, .descr = "Nuvoton NCT6779D", .devices = nct67xx_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd42a, .extid = 1, .descr = "Nuvoton NCT6796D-E", .devices = nct67xx_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd42a, .extid = 2, .descr = "Nuvoton NCT5585D", .devices = nct5585_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc800, .mask = 0xff, .descr = "Nuvoton NCT6791", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc900, .mask = 0xff, .descr = "Nuvoton NCT6792", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd100, .mask = 0xff, .descr = "Nuvoton NCT6793", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd200, .mask = 0xff, .descr = "Nuvoton NCT6112D/NCT6114D/NCT6116D", .devices = nct611x_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd300, .mask = 0xff, .descr = "Nuvoton NCT6795", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_FINTEK, .devid = 0x1210, .mask = 0xff, .descr = "Fintek F81803", .devices = fintek_devices, }, { .vendor = SUPERIO_VENDOR_FINTEK, .devid = 0x0704, .descr = "Fintek F81865", .devices = fintek_devices, }, { 0, 0 } }; static const char * devtype_to_str(superio_dev_type_t type) { switch (type) { case SUPERIO_DEV_NONE: return ("none"); case SUPERIO_DEV_HWM: return ("HWM"); case SUPERIO_DEV_WDT: return ("WDT"); case SUPERIO_DEV_GPIO: return ("GPIO"); case SUPERIO_DEV_MAX: return ("invalid"); } return ("invalid"); } static int superio_detect(device_t dev, bool claim, struct siosc *sc) { struct resource *res; rman_res_t port; rman_res_t count; uint16_t devid; uint8_t revid; int error; int rid; int i, m; int prefer; error = bus_get_resource(dev, SYS_RES_IOPORT, 0, &port, &count); if (error != 0) return (error); if (port > UINT16_MAX || count < NUMPORTS) { device_printf(dev, "unexpected I/O range size\n"); return (ENXIO); } /* * Make a temporary resource reservation for hardware probing. * If we can't get the resources we need then * we need to abort. Possibly this indicates * the resources were used by another device * in which case the probe would have failed anyhow. */ rid = 0; res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (res == NULL) { if (claim) device_printf(dev, "failed to allocate I/O resource\n"); return (ENXIO); } prefer = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "prefer", &prefer); if (bootverbose && prefer > 0) device_printf(dev, "prefer extid %d\n", prefer); for (m = 0; methods_table[m] != NULL; m++) { methods_table[m]->enter(res, port); if (methods_table[m]->vendor == SUPERIO_VENDOR_ITE) { devid = sio_readw(res, 0x20); revid = sio_read(res, 0x22); } else if (methods_table[m]->vendor == SUPERIO_VENDOR_NUVOTON) { devid = sio_read(res, 0x20); revid = sio_read(res, 0x21); devid = (devid << 8) | revid; } else if (methods_table[m]->vendor == SUPERIO_VENDOR_FINTEK) { devid = sio_read(res, 0x20); revid = sio_read(res, 0x21); devid = (devid << 8) | revid; } else { continue; } methods_table[m]->exit(res, port); for (i = 0; superio_table[i].vendor != 0; i++) { uint16_t mask; mask = superio_table[i].mask; if (superio_table[i].vendor != methods_table[m]->vendor) continue; if ((superio_table[i].devid & ~mask) != (devid & ~mask)) continue; if (prefer > 0 && prefer != superio_table[i].extid) continue; break; } /* Found a matching SuperIO entry. */ if (superio_table[i].vendor != 0) break; } if (methods_table[m] == NULL) error = ENXIO; else error = 0; if (!claim || error != 0) { bus_release_resource(dev, SYS_RES_IOPORT, rid, res); return (error); } sc->methods = methods_table[m]; sc->vendor = sc->methods->vendor; sc->known_devices = superio_table[i].devices; sc->io_res = res; sc->io_rid = rid; sc->io_port = port; sc->devid = devid; sc->revid = revid; sc->extid = superio_table[i].extid; KASSERT(sc->vendor == SUPERIO_VENDOR_ITE || sc->vendor == SUPERIO_VENDOR_NUVOTON || sc->vendor == SUPERIO_VENDOR_FINTEK, ("Only ITE, Nuvoton and Fintek SuperIO-s are supported")); sc->ldn_reg = 0x07; sc->enable_reg = 0x30; /* FIXME enable_reg not used by nctgpio(4). */ sc->current_ldn = 0xff; /* no device should have this */ if (superio_table[i].descr != NULL) { device_set_desc(dev, superio_table[i].descr); } else if (sc->vendor == SUPERIO_VENDOR_ITE) { device_set_descf(dev, "ITE IT%4x SuperIO (revision 0x%02x)", sc->devid, sc->revid); } return (0); } static void superio_identify(driver_t *driver, device_t parent) { device_t child; int i; /* * Don't create child devices if any already exist. * Those could be created via isa hints or if this * driver is loaded, unloaded and then loaded again. */ - if (device_find_child(parent, "superio", -1)) { + if (device_find_child(parent, "superio", DEVICE_UNIT_ANY)) { if (bootverbose) printf("superio: device(s) already created\n"); return; } /* * Create a child for each candidate port. * It would be nice if we could somehow clean up those * that this driver fails to probe. */ for (i = 0; ports_table[i] != 0; i++) { child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "superio", -1); if (child == NULL) { device_printf(parent, "failed to add superio child\n"); continue; } bus_set_resource(child, SYS_RES_IOPORT, 0, ports_table[i], 2); if (superio_detect(child, false, NULL) != 0) device_delete_child(parent, child); } } static int superio_probe(device_t dev) { struct siosc *sc; int error; /* Make sure we do not claim some ISA PNP device. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); /* * XXX We can populate the softc now only because we return * BUS_PROBE_SPECIFIC */ sc = device_get_softc(dev); error = superio_detect(dev, true, sc); if (error != 0) return (error); return (BUS_PROBE_SPECIFIC); } static void superio_add_known_child(device_t dev, superio_dev_type_t type, uint8_t ldn) { struct siosc *sc = device_get_softc(dev); struct superio_devinfo *dinfo; device_t child; child = BUS_ADD_CHILD(dev, 0, NULL, DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "failed to add child for ldn %d, type %s\n", ldn, devtype_to_str(type)); return; } dinfo = device_get_ivars(child); dinfo->ldn = ldn; dinfo->type = type; sio_conf_enter(sc); dinfo->iobase = sio_ldn_readw(sc, ldn, 0x60); dinfo->iobase2 = sio_ldn_readw(sc, ldn, 0x62); dinfo->irq = sio_ldn_readw(sc, ldn, 0x70); dinfo->dma = sio_ldn_readw(sc, ldn, 0x74); sio_conf_exit(sc); STAILQ_INSERT_TAIL(&sc->devlist, dinfo, link); } static int superio_attach(device_t dev) { struct siosc *sc = device_get_softc(dev); int i; mtx_init(&sc->conf_lock, device_get_nameunit(dev), "superio", MTX_DEF); STAILQ_INIT(&sc->devlist); for (i = 0; sc->known_devices[i].type != SUPERIO_DEV_NONE; i++) { superio_add_known_child(dev, sc->known_devices[i].type, sc->known_devices[i].ldn); } bus_identify_children(dev); bus_attach_children(dev); sc->chardev = make_dev(&superio_cdevsw, device_get_unit(dev), UID_ROOT, GID_WHEEL, 0600, "superio%d", device_get_unit(dev)); if (sc->chardev == NULL) device_printf(dev, "failed to create character device\n"); else sc->chardev->si_drv1 = sc; return (0); } static int superio_detach(device_t dev) { struct siosc *sc = device_get_softc(dev); int error; error = bus_generic_detach(dev); if (error != 0) return (error); if (sc->chardev != NULL) destroy_dev(sc->chardev); bus_release_resource(dev, SYS_RES_IOPORT, sc->io_rid, sc->io_res); mtx_destroy(&sc->conf_lock); return (0); } static device_t superio_add_child(device_t dev, u_int order, const char *name, int unit) { struct superio_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (dinfo == NULL) { device_delete_child(dev, child); return (NULL); } dinfo->ldn = 0xff; dinfo->type = SUPERIO_DEV_NONE; dinfo->dev = child; resource_list_init(&dinfo->resources); device_set_ivars(child, dinfo); return (child); } static void superio_child_deleted(device_t dev, device_t child) { struct superio_devinfo *dinfo; dinfo = device_get_ivars(child); if (dinfo == NULL) return; resource_list_free(&dinfo->resources); free(dinfo, M_DEVBUF); } static int superio_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct superio_devinfo *dinfo; dinfo = device_get_ivars(child); switch (which) { case SUPERIO_IVAR_LDN: *result = dinfo->ldn; break; case SUPERIO_IVAR_TYPE: *result = dinfo->type; break; case SUPERIO_IVAR_IOBASE: *result = dinfo->iobase; break; case SUPERIO_IVAR_IOBASE2: *result = dinfo->iobase2; break; case SUPERIO_IVAR_IRQ: *result = dinfo->irq; break; case SUPERIO_IVAR_DMA: *result = dinfo->dma; break; default: return (ENOENT); } return (0); } static int superio_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { switch (which) { case SUPERIO_IVAR_LDN: case SUPERIO_IVAR_TYPE: case SUPERIO_IVAR_IOBASE: case SUPERIO_IVAR_IOBASE2: case SUPERIO_IVAR_IRQ: case SUPERIO_IVAR_DMA: return (EINVAL); default: return (ENOENT); } } static struct resource_list * superio_get_resource_list(device_t dev, device_t child) { struct superio_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } static int superio_printf(struct superio_devinfo *dinfo, const char *fmt, ...) { va_list ap; int retval; retval = printf("superio:%s@ldn%0x2x: ", devtype_to_str(dinfo->type), dinfo->ldn); va_start(ap, fmt); retval += vprintf(fmt, ap); va_end(ap); return (retval); } static void superio_child_detached(device_t dev, device_t child) { struct superio_devinfo *dinfo; struct resource_list *rl; dinfo = device_get_ivars(child); rl = &dinfo->resources; if (resource_list_release_active(rl, dev, child, SYS_RES_IRQ) != 0) superio_printf(dinfo, "Device leaked IRQ resources\n"); if (resource_list_release_active(rl, dev, child, SYS_RES_MEMORY) != 0) superio_printf(dinfo, "Device leaked memory resources\n"); if (resource_list_release_active(rl, dev, child, SYS_RES_IOPORT) != 0) superio_printf(dinfo, "Device leaked I/O resources\n"); } static int superio_child_location(device_t parent, device_t child, struct sbuf *sb) { uint8_t ldn; ldn = superio_get_ldn(child); sbuf_printf(sb, "ldn=0x%02x", ldn); return (0); } static int superio_child_pnp(device_t parent, device_t child, struct sbuf *sb) { superio_dev_type_t type; type = superio_get_type(child); sbuf_printf(sb, "type=%s", devtype_to_str(type)); return (0); } static int superio_print_child(device_t parent, device_t child) { superio_dev_type_t type; uint8_t ldn; int retval; ldn = superio_get_ldn(child); type = superio_get_type(child); retval = bus_print_child_header(parent, child); retval += printf(" at %s ldn 0x%02x", devtype_to_str(type), ldn); retval += bus_print_child_footer(parent, child); return (retval); } superio_vendor_t superio_vendor(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->vendor); } uint16_t superio_devid(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->devid); } uint8_t superio_revid(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->revid); } int superio_extid(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->extid); } uint8_t superio_ldn_read(device_t dev, uint8_t ldn, uint8_t reg) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); uint8_t v; sio_conf_enter(sc); v = sio_ldn_read(sc, ldn, reg); sio_conf_exit(sc); return (v); } uint8_t superio_read(device_t dev, uint8_t reg) { struct superio_devinfo *dinfo = device_get_ivars(dev); return (superio_ldn_read(dev, dinfo->ldn, reg)); } void superio_ldn_write(device_t dev, uint8_t ldn, uint8_t reg, uint8_t val) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); sio_conf_enter(sc); sio_ldn_write(sc, ldn, reg, val); sio_conf_exit(sc); } void superio_write(device_t dev, uint8_t reg, uint8_t val) { struct superio_devinfo *dinfo = device_get_ivars(dev); return (superio_ldn_write(dev, dinfo->ldn, reg, val)); } bool superio_dev_enabled(device_t dev, uint8_t mask) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); struct superio_devinfo *dinfo = device_get_ivars(dev); uint8_t v; /* GPIO device is always active in ITE chips. */ if (sc->vendor == SUPERIO_VENDOR_ITE && dinfo->ldn == 7) return (true); v = superio_read(dev, sc->enable_reg); /* FIXME enable_reg not used by nctgpio(4). */ return ((v & mask) != 0); } void superio_dev_enable(device_t dev, uint8_t mask) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); struct superio_devinfo *dinfo = device_get_ivars(dev); uint8_t v; /* GPIO device is always active in ITE chips. */ if (sc->vendor == SUPERIO_VENDOR_ITE && dinfo->ldn == 7) return; sio_conf_enter(sc); v = sio_ldn_read(sc, dinfo->ldn, sc->enable_reg); v |= mask; sio_ldn_write(sc, dinfo->ldn, sc->enable_reg, v); sio_conf_exit(sc); } void superio_dev_disable(device_t dev, uint8_t mask) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); struct superio_devinfo *dinfo = device_get_ivars(dev); uint8_t v; /* GPIO device is always active in ITE chips. */ if (sc->vendor == SUPERIO_VENDOR_ITE && dinfo->ldn == 7) return; sio_conf_enter(sc); v = sio_ldn_read(sc, dinfo->ldn, sc->enable_reg); v &= ~mask; sio_ldn_write(sc, dinfo->ldn, sc->enable_reg, v); sio_conf_exit(sc); } device_t superio_find_dev(device_t superio, superio_dev_type_t type, int ldn) { struct siosc *sc = device_get_softc(superio); struct superio_devinfo *dinfo; if (ldn < -1 || ldn > UINT8_MAX) return (NULL); /* ERANGE */ if (type == SUPERIO_DEV_NONE && ldn == -1) return (NULL); /* EINVAL */ STAILQ_FOREACH(dinfo, &sc->devlist, link) { if (ldn != -1 && dinfo->ldn != ldn) continue; if (type != SUPERIO_DEV_NONE && dinfo->type != type) continue; return (dinfo->dev); } return (NULL); } static int superio_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { struct siosc *sc; struct superiocmd *s; sc = dev->si_drv1; s = (struct superiocmd *)data; switch (cmd) { case SUPERIO_CR_READ: sio_conf_enter(sc); s->val = sio_ldn_read(sc, s->ldn, s->cr); sio_conf_exit(sc); return (0); case SUPERIO_CR_WRITE: sio_conf_enter(sc); sio_ldn_write(sc, s->ldn, s->cr, s->val); sio_conf_exit(sc); return (0); default: return (ENOTTY); } } static device_method_t superio_methods[] = { DEVMETHOD(device_identify, superio_identify), DEVMETHOD(device_probe, superio_probe), DEVMETHOD(device_attach, superio_attach), DEVMETHOD(device_detach, superio_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(bus_add_child, superio_add_child), DEVMETHOD(bus_child_deleted, superio_child_deleted), DEVMETHOD(bus_child_detached, superio_child_detached), DEVMETHOD(bus_child_location, superio_child_location), DEVMETHOD(bus_child_pnpinfo, superio_child_pnp), DEVMETHOD(bus_print_child, superio_print_child), DEVMETHOD(bus_read_ivar, superio_read_ivar), DEVMETHOD(bus_write_ivar, superio_write_ivar), DEVMETHOD(bus_get_resource_list, superio_get_resource_list), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t superio_driver = { "superio", superio_methods, sizeof(struct siosc) }; DRIVER_MODULE(superio, isa, superio_driver, 0, 0); MODULE_VERSION(superio, 1); diff --git a/sys/dev/usb/misc/i2ctinyusb.c b/sys/dev/usb/misc/i2ctinyusb.c index ca40fd5baf5c..c6e8f946d78e 100644 --- a/sys/dev/usb/misc/i2ctinyusb.c +++ b/sys/dev/usb/misc/i2ctinyusb.c @@ -1,301 +1,301 @@ /*- * Copyright (c) 2024 Denis Bodor * 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, 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. */ /* * i2c-tiny-usb, DIY USB to IIC bridge (using AVR or RP2040) from * Till Harbaum & Nicolai Electronics * See : * https://github.com/harbaum/I2C-Tiny-USB * and * https://github.com/Nicolai-Electronics/rp2040-i2c-interface */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" // commands via USB, must match command ids in the firmware #define CMD_ECHO 0 #define CMD_GET_FUNC 1 #define CMD_SET_DELAY 2 #define CMD_GET_STATUS 3 #define CMD_I2C_IO 4 #define CMD_SET_LED 8 #define CMD_I2C_IO_BEGIN (1 << 0) #define CMD_I2C_IO_END (1 << 1) #define STATUS_IDLE 0 #define STATUS_ADDRESS_ACK 1 #define STATUS_ADDRESS_NAK 2 struct i2ctinyusb_softc { struct usb_device *sc_udev; device_t sc_iic_dev; device_t iicbus_dev; struct mtx sc_mtx; }; #define USB_VENDOR_EZPROTOTYPES 0x1c40 #define USB_VENDOR_FTDI 0x0403 static const STRUCT_USB_HOST_ID i2ctinyusb_devs[] = { { USB_VPI(USB_VENDOR_EZPROTOTYPES, 0x0534, 0) }, { USB_VPI(USB_VENDOR_FTDI, 0xc631, 0) }, }; /* Prototypes. */ static int i2ctinyusb_probe(device_t dev); static int i2ctinyusb_attach(device_t dev); static int i2ctinyusb_detach(device_t dev); static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs); static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr); static int usb_read(struct i2ctinyusb_softc *sc, int cmd, int value, int index, void *data, int len) { int error; struct usb_device_request req; uint16_t actlen; req.bmRequestType = UT_READ_VENDOR_INTERFACE; req.bRequest = cmd; USETW(req.wValue, value); USETW(req.wIndex, (index >> 1)); USETW(req.wLength, len); error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0, &actlen, 2000); if (error) actlen = -1; return (actlen); } static int usb_write(struct i2ctinyusb_softc *sc, int cmd, int value, int index, void *data, int len) { int error; struct usb_device_request req; uint16_t actlen; req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; req.bRequest = cmd; USETW(req.wValue, value); USETW(req.wIndex, (index >> 1)); USETW(req.wLength, len); error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0, &actlen, 2000); if (error) { actlen = -1; } return (actlen); } static int i2ctinyusb_probe(device_t dev) { struct usb_attach_arg *uaa; uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (usbd_lookup_id_by_uaa(i2ctinyusb_devs, sizeof(i2ctinyusb_devs), uaa) == 0) { device_set_desc(dev, "I2C-Tiny-USB I2C interface"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int i2ctinyusb_attach(device_t dev) { struct i2ctinyusb_softc *sc; struct usb_attach_arg *uaa; int err; sc = device_get_softc(dev); uaa = device_get_ivars(dev); device_set_usb_desc(dev); sc->sc_udev = uaa->device; mtx_init(&sc->sc_mtx, "i2ctinyusb lock", NULL, MTX_DEF | MTX_RECURSE); - sc->iicbus_dev = device_add_child(dev, "iicbus", -1); + sc->iicbus_dev = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY); if (sc->iicbus_dev == NULL) { device_printf(dev, "iicbus creation failed\n"); err = ENXIO; goto detach; } bus_attach_children(dev); return (0); detach: i2ctinyusb_detach(dev); return (err); } static int i2ctinyusb_detach(device_t dev) { struct i2ctinyusb_softc *sc; int err; sc = device_get_softc(dev); err = bus_generic_detach(dev); if (err != 0) return (err); mtx_destroy(&sc->sc_mtx); return (0); } static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct i2ctinyusb_softc *sc; uint32_t i; int ret = 0; int cmd = CMD_I2C_IO; struct iic_msg *pmsg; unsigned char pstatus; sc = device_get_softc(dev); mtx_lock(&sc->sc_mtx); for (i = 0; i < nmsgs; i++) { pmsg = &msgs[i]; if (i == 0) cmd |= CMD_I2C_IO_BEGIN; if (i == nmsgs - 1) cmd |= CMD_I2C_IO_END; if ((msgs[i].flags & IIC_M_RD) != 0) { if ((ret = usb_read(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf, pmsg->len)) != pmsg->len) { printf("Read error: got %u\n", ret); ret = EIO; goto out; } } else { if ((ret = usb_write(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf, pmsg->len)) != pmsg->len) { printf("Write error: got %u\n", ret); ret = EIO; goto out; } } // check status if ((ret = usb_read(sc, CMD_GET_STATUS, 0, 0, &pstatus, 1)) != 1) { ret = EIO; goto out; } if (pstatus == STATUS_ADDRESS_NAK) { ret = EIO; goto out; } } ret = 0; out: mtx_unlock(&sc->sc_mtx); return (ret); } static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct i2ctinyusb_softc *sc; int ret; sc = device_get_softc(dev); mtx_lock(&sc->sc_mtx); ret = usb_write(sc, CMD_SET_DELAY, 10, 0, NULL, 0); mtx_unlock(&sc->sc_mtx); if (ret < 0) printf("i2ctinyusb_reset error!\n"); return (0); } static device_method_t i2ctinyusb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, i2ctinyusb_probe), DEVMETHOD(device_attach, i2ctinyusb_attach), DEVMETHOD(device_detach, i2ctinyusb_detach), /* I2C methods */ DEVMETHOD(iicbus_transfer, i2ctinyusb_transfer), DEVMETHOD(iicbus_reset, i2ctinyusb_reset), DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD_END }; static driver_t i2ctinyusb_driver = { .name = "iichb", .methods = i2ctinyusb_methods, .size = sizeof(struct i2ctinyusb_softc), }; DRIVER_MODULE(i2ctinyusb, uhub, i2ctinyusb_driver, NULL, NULL); MODULE_DEPEND(i2ctinyusb, usb, 1, 1, 1); MODULE_DEPEND(i2ctinyusb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(i2ctinyusb, 1); /* vi: set ts=8 sw=8: */ diff --git a/sys/dev/viapm/viapm.c b/sys/dev/viapm/viapm.c index 36c33422d5b3..1aaaf25dcc34 100644 --- a/sys/dev/viapm/viapm.c +++ b/sys/dev/viapm/viapm.c @@ -1,1011 +1,1011 @@ /*- * Copyright (c) 2001 Alcove - Nicolas Souchu * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include "opt_isa.h" #include #include #include #include #include #include #include #include #include #include #ifdef DEV_ISA #include #include #endif #include #include #include #include #include "iicbb_if.h" #include "smbus_if.h" #define VIAPM_DEBUG(x) if (viapm_debug) (x) #ifdef DEBUG static int viapm_debug = 1; #else static int viapm_debug = 0; #endif #define VIA_586B_PMU_ID 0x30401106 #define VIA_596A_PMU_ID 0x30501106 #define VIA_596B_PMU_ID 0x30511106 #define VIA_686A_PMU_ID 0x30571106 #define VIA_8233_PMU_ID 0x30741106 #define VIA_8233A_PMU_ID 0x31471106 #define VIA_8235_PMU_ID 0x31771106 #define VIA_8237_PMU_ID 0x32271106 #define VIA_CX700_PMU_ID 0x83241106 #define VIAPM_INB(port) \ ((u_char)bus_read_1(viapm->iores, port)) #define VIAPM_OUTB(port,val) \ (bus_write_1(viapm->iores, port, (u_char)(val))) #define VIAPM_TYP_UNKNOWN 0 #define VIAPM_TYP_586B_3040E 1 #define VIAPM_TYP_586B_3040F 2 #define VIAPM_TYP_596B 3 #define VIAPM_TYP_686A 4 #define VIAPM_TYP_8233 5 #define VIAPM_LOCK(sc) mtx_lock(&(sc)->lock) #define VIAPM_UNLOCK(sc) mtx_unlock(&(sc)->lock) #define VIAPM_LOCK_ASSERT(sc) mtx_assert(&(sc)->lock, MA_OWNED) struct viapm_softc { int type; u_int32_t base; int iorid; int irqrid; struct resource *iores; struct resource *irqres; void *irqih; device_t iicbb; device_t smbus; struct mtx lock; }; /* * VT82C586B definitions */ #define VIAPM_586B_REVID 0x08 #define VIAPM_586B_3040E_BASE 0x20 #define VIAPM_586B_3040E_ACTIV 0x4 /* 16 bits */ #define VIAPM_586B_3040F_BASE 0x48 #define VIAPM_586B_3040F_ACTIV 0x41 /* 8 bits */ #define VIAPM_586B_OEM_REV_E 0x00 #define VIAPM_586B_OEM_REV_F 0x01 #define VIAPM_586B_PROD_REV_A 0x10 #define VIAPM_586B_BA_MASK 0x0000ff00 #define GPIO_DIR 0x40 #define GPIO_VAL 0x42 #define EXTSMI_VAL 0x44 #define VIAPM_SCL 0x02 /* GPIO1_VAL */ #define VIAPM_SDA 0x04 /* GPIO2_VAL */ /* * VIAPRO common definitions */ #define VIAPM_PRO_BA_MASK 0x0000fff0 #define VIAPM_PRO_SMBCTRL 0xd2 #define VIAPM_PRO_REVID 0xd6 /* * VT82C686A definitions */ #define VIAPM_PRO_BASE 0x90 #define SMBHST 0x0 #define SMBHSL 0x1 #define SMBHCTRL 0x2 #define SMBHCMD 0x3 #define SMBHADDR 0x4 #define SMBHDATA0 0x5 #define SMBHDATA1 0x6 #define SMBHBLOCK 0x7 #define SMBSST 0x1 #define SMBSCTRL 0x8 #define SMBSSDWCMD 0x9 #define SMBSEVENT 0xa #define SMBSDATA 0xc #define SMBHST_RESERVED 0xef /* reserved bits */ #define SMBHST_FAILED 0x10 /* failed bus transaction */ #define SMBHST_COLLID 0x08 /* bus collision */ #define SMBHST_ERROR 0x04 /* device error */ #define SMBHST_INTR 0x02 /* command completed */ #define SMBHST_BUSY 0x01 /* host busy */ #define SMBHCTRL_START 0x40 /* start command */ #define SMBHCTRL_PROTO 0x1c /* command protocol mask */ #define SMBHCTRL_QUICK 0x00 #define SMBHCTRL_SENDRECV 0x04 #define SMBHCTRL_BYTE 0x08 #define SMBHCTRL_WORD 0x0c #define SMBHCTRL_BLOCK 0x14 #define SMBHCTRL_KILL 0x02 /* stop the current transaction */ #define SMBHCTRL_ENABLE 0x01 /* enable interrupts */ #define SMBSCTRL_ENABLE 0x01 /* enable slave */ /* * VIA8233 definitions */ #define VIAPM_8233_BASE 0xD0 static int viapm_586b_probe(device_t dev) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); u_int32_t l; u_int16_t s; u_int8_t c; switch (pci_get_devid(dev)) { case VIA_586B_PMU_ID: bzero(viapm, sizeof(struct viapm_softc)); l = pci_read_config(dev, VIAPM_586B_REVID, 1); switch (l) { case VIAPM_586B_OEM_REV_E: viapm->type = VIAPM_TYP_586B_3040E; viapm->iorid = VIAPM_586B_3040E_BASE; /* Activate IO block access */ s = pci_read_config(dev, VIAPM_586B_3040E_ACTIV, 2); pci_write_config(dev, VIAPM_586B_3040E_ACTIV, s | 0x1, 2); break; case VIAPM_586B_OEM_REV_F: case VIAPM_586B_PROD_REV_A: default: viapm->type = VIAPM_TYP_586B_3040F; viapm->iorid = VIAPM_586B_3040F_BASE; /* Activate IO block access */ c = pci_read_config(dev, VIAPM_586B_3040F_ACTIV, 1); pci_write_config(dev, VIAPM_586B_3040F_ACTIV, c | 0x80, 1); break; } viapm->base = pci_read_config(dev, viapm->iorid, 4) & VIAPM_586B_BA_MASK; /* * We have to set the I/O resources by hand because it is * described outside the viapmope of the traditional maps */ if (bus_set_resource(dev, SYS_RES_IOPORT, viapm->iorid, viapm->base, 256)) { device_printf(dev, "could not set bus resource\n"); return ENXIO; } device_set_desc(dev, "VIA VT82C586B Power Management Unit"); return (BUS_PROBE_DEFAULT); default: break; } return ENXIO; } static int viapm_pro_probe(device_t dev) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); #ifdef VIAPM_BASE_ADDR u_int32_t l; #endif u_int32_t base_cfgreg; char *desc; switch (pci_get_devid(dev)) { case VIA_596A_PMU_ID: desc = "VIA VT82C596A Power Management Unit"; viapm->type = VIAPM_TYP_596B; base_cfgreg = VIAPM_PRO_BASE; goto viapro; case VIA_596B_PMU_ID: desc = "VIA VT82C596B Power Management Unit"; viapm->type = VIAPM_TYP_596B; base_cfgreg = VIAPM_PRO_BASE; goto viapro; case VIA_686A_PMU_ID: desc = "VIA VT82C686A Power Management Unit"; viapm->type = VIAPM_TYP_686A; base_cfgreg = VIAPM_PRO_BASE; goto viapro; case VIA_8233_PMU_ID: case VIA_8233A_PMU_ID: desc = "VIA VT8233 Power Management Unit"; viapm->type = VIAPM_TYP_UNKNOWN; base_cfgreg = VIAPM_8233_BASE; goto viapro; case VIA_8235_PMU_ID: desc = "VIA VT8235 Power Management Unit"; viapm->type = VIAPM_TYP_UNKNOWN; base_cfgreg = VIAPM_8233_BASE; goto viapro; case VIA_8237_PMU_ID: desc = "VIA VT8237 Power Management Unit"; viapm->type = VIAPM_TYP_UNKNOWN; base_cfgreg = VIAPM_8233_BASE; goto viapro; case VIA_CX700_PMU_ID: desc = "VIA CX700 Power Management Unit"; viapm->type = VIAPM_TYP_UNKNOWN; base_cfgreg = VIAPM_8233_BASE; goto viapro; viapro: #ifdef VIAPM_BASE_ADDR /* force VIAPM I/O base address */ /* enable the SMBus controller function */ l = pci_read_config(dev, VIAPM_PRO_SMBCTRL, 1); pci_write_config(dev, VIAPM_PRO_SMBCTRL, l | 1, 1); /* write the base address */ pci_write_config(dev, base_cfgreg, VIAPM_BASE_ADDR & VIAPM_PRO_BA_MASK, 4); #endif viapm->base = pci_read_config(dev, base_cfgreg, 4) & VIAPM_PRO_BA_MASK; /* * We have to set the I/O resources by hand because it is * described outside the viapmope of the traditional maps */ viapm->iorid = base_cfgreg; if (bus_set_resource(dev, SYS_RES_IOPORT, viapm->iorid, viapm->base, 16)) { device_printf(dev, "could not set bus resource 0x%x\n", viapm->base); return ENXIO; } if (bootverbose) { device_printf(dev, "SMBus I/O base at 0x%x\n", viapm->base); } device_set_desc(dev, desc); return (BUS_PROBE_DEFAULT); default: break; } return ENXIO; } static int viapm_pro_attach(device_t dev) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); u_int32_t l; mtx_init(&viapm->lock, device_get_nameunit(dev), "viapm", MTX_DEF); if (!(viapm->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &viapm->iorid, RF_ACTIVE))) { device_printf(dev, "could not allocate bus space\n"); goto error; } #ifdef notyet /* force irq 9 */ l = pci_read_config(dev, VIAPM_PRO_SMBCTRL, 1); pci_write_config(dev, VIAPM_PRO_SMBCTRL, l | 0x80, 1); viapm->irqrid = 0; if (!(viapm->irqres = bus_alloc_resource(dev, SYS_RES_IRQ, &viapm->irqrid, 9, 9, 1, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "could not allocate irq\n"); goto error; } if (bus_setup_intr(dev, viapm->irqres, INTR_TYPE_MISC | INTR_MPSAFE, (driver_intr_t *) viasmb_intr, viapm, &viapm->irqih)) { device_printf(dev, "could not setup irq\n"); goto error; } #endif if (bootverbose) { l = pci_read_config(dev, VIAPM_PRO_REVID, 1); device_printf(dev, "SMBus revision code 0x%x\n", l); } viapm->smbus = device_add_child(dev, "smbus", DEVICE_UNIT_ANY); /* probe and attach the smbus */ bus_attach_children(dev); /* disable slave function */ VIAPM_OUTB(SMBSCTRL, VIAPM_INB(SMBSCTRL) & ~SMBSCTRL_ENABLE); /* enable the SMBus controller function */ l = pci_read_config(dev, VIAPM_PRO_SMBCTRL, 1); pci_write_config(dev, VIAPM_PRO_SMBCTRL, l | 1, 1); #ifdef notyet /* enable interrupts */ VIAPM_OUTB(SMBHCTRL, VIAPM_INB(SMBHCTRL) | SMBHCTRL_ENABLE); #endif #ifdef DEV_ISA /* If this device is a PCI-ISA bridge, then attach an ISA bus. */ if ((pci_get_class(dev) == PCIC_BRIDGE) && (pci_get_subclass(dev) == PCIS_BRIDGE_ISA)) isab_attach(dev); #endif return 0; error: if (viapm->iores) bus_release_resource(dev, SYS_RES_IOPORT, viapm->iorid, viapm->iores); #ifdef notyet if (viapm->irqres) bus_release_resource(dev, SYS_RES_IRQ, viapm->irqrid, viapm->irqres); #endif mtx_destroy(&viapm->lock); return ENXIO; } static int viapm_586b_attach(device_t dev) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); mtx_init(&viapm->lock, device_get_nameunit(dev), "viapm", MTX_DEF); if (!(viapm->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &viapm->iorid, RF_ACTIVE | RF_SHAREABLE))) { device_printf(dev, "could not allocate bus resource\n"); goto error; } VIAPM_OUTB(GPIO_DIR, VIAPM_INB(GPIO_DIR) | VIAPM_SCL | VIAPM_SDA); /* add generic bit-banging code */ - if (!(viapm->iicbb = device_add_child(dev, "iicbb", -1))) + if (!(viapm->iicbb = device_add_child(dev, "iicbb", DEVICE_UNIT_ANY))) goto error; bus_attach_children(dev); return 0; error: if (viapm->iores) bus_release_resource(dev, SYS_RES_IOPORT, viapm->iorid, viapm->iores); mtx_destroy(&viapm->lock); return ENXIO; } static int viapm_586b_detach(device_t dev) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); bus_generic_detach(dev); if (viapm->iores) bus_release_resource(dev, SYS_RES_IOPORT, viapm->iorid, viapm->iores); mtx_destroy(&viapm->lock); return 0; } static int viapm_pro_detach(device_t dev) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); bus_generic_detach(dev); bus_release_resource(dev, SYS_RES_IOPORT, viapm->iorid, viapm->iores); #ifdef notyet bus_release_resource(dev, SYS_RES_IRQ, viapm->irqrid, viapm->irqres); #endif mtx_destroy(&viapm->lock); return 0; } static int viabb_callback(device_t dev, int index, caddr_t data) { return 0; } static void viabb_setscl(device_t dev, int ctrl) { struct viapm_softc *viapm = device_get_softc(dev); u_char val; VIAPM_LOCK(viapm); val = VIAPM_INB(GPIO_VAL); if (ctrl) val |= VIAPM_SCL; else val &= ~VIAPM_SCL; VIAPM_OUTB(GPIO_VAL, val); VIAPM_UNLOCK(viapm); return; } static void viabb_setsda(device_t dev, int data) { struct viapm_softc *viapm = device_get_softc(dev); u_char val; VIAPM_LOCK(viapm); val = VIAPM_INB(GPIO_VAL); if (data) val |= VIAPM_SDA; else val &= ~VIAPM_SDA; VIAPM_OUTB(GPIO_VAL, val); VIAPM_UNLOCK(viapm); return; } static int viabb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { /* reset bus */ viabb_setsda(dev, 1); viabb_setscl(dev, 1); return (IIC_ENOADDR); } static int viabb_getscl(device_t dev) { struct viapm_softc *viapm = device_get_softc(dev); u_char val; VIAPM_LOCK(viapm); val = VIAPM_INB(EXTSMI_VAL); VIAPM_UNLOCK(viapm); return ((val & VIAPM_SCL) != 0); } static int viabb_getsda(device_t dev) { struct viapm_softc *viapm = device_get_softc(dev); u_char val; VIAPM_LOCK(viapm); val = VIAPM_INB(EXTSMI_VAL); VIAPM_UNLOCK(viapm); return ((val & VIAPM_SDA) != 0); } static int viapm_abort(struct viapm_softc *viapm) { VIAPM_OUTB(SMBHCTRL, SMBHCTRL_KILL); DELAY(10); return (0); } static int viapm_clear(struct viapm_softc *viapm) { VIAPM_OUTB(SMBHST, SMBHST_FAILED | SMBHST_COLLID | SMBHST_ERROR | SMBHST_INTR); DELAY(10); return (0); } static int viapm_busy(struct viapm_softc *viapm) { u_char sts; sts = VIAPM_INB(SMBHST); VIAPM_DEBUG(printf("viapm: idle? STS=0x%x\n", sts)); return (sts & SMBHST_BUSY); } /* * Poll the SMBus controller */ static int viapm_wait(struct viapm_softc *viapm) { int count = 10000; u_char sts = 0; int error; VIAPM_LOCK_ASSERT(viapm); /* wait for command to complete and SMBus controller is idle */ while(count--) { DELAY(10); sts = VIAPM_INB(SMBHST); /* check if the controller is processing a command */ if (!(sts & SMBHST_BUSY) && (sts & SMBHST_INTR)) break; } VIAPM_DEBUG(printf("viapm: SMBHST=0x%x\n", sts)); error = SMB_ENOERR; if (!count) error |= SMB_ETIMEOUT; if (sts & SMBHST_FAILED) error |= SMB_EABORT; if (sts & SMBHST_COLLID) error |= SMB_ENOACK; if (sts & SMBHST_ERROR) error |= SMB_EBUSERR; if (error != SMB_ENOERR) viapm_abort(viapm); viapm_clear(viapm); return (error); } static int viasmb_callback(device_t dev, int index, void *data) { int error = 0; switch (index) { case SMB_REQUEST_BUS: case SMB_RELEASE_BUS: /* ok, bus allocation accepted */ break; default: error = EINVAL; } return (error); } static int viasmb_quick(device_t dev, u_char slave, int how) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); int error; VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } switch (how) { case SMB_QWRITE: VIAPM_DEBUG(printf("viapm: QWRITE to 0x%x", slave)); VIAPM_OUTB(SMBHADDR, slave & ~LSB); break; case SMB_QREAD: VIAPM_DEBUG(printf("viapm: QREAD to 0x%x", slave)); VIAPM_OUTB(SMBHADDR, slave | LSB); break; default: panic("%s: unknown QUICK command (%x)!", __func__, how); } VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_QUICK); error = viapm_wait(viapm); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_sendb(device_t dev, u_char slave, char byte) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); int error; VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave & ~ LSB); VIAPM_OUTB(SMBHCMD, byte); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_SENDRECV); error = viapm_wait(viapm); VIAPM_DEBUG(printf("viapm: SENDB to 0x%x, byte=0x%x, error=0x%x\n", slave, byte, error)); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_recvb(device_t dev, u_char slave, char *byte) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); int error; VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave | LSB); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_SENDRECV); if ((error = viapm_wait(viapm)) == SMB_ENOERR) *byte = VIAPM_INB(SMBHDATA0); VIAPM_DEBUG(printf("viapm: RECVB from 0x%x, byte=0x%x, error=0x%x\n", slave, *byte, error)); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_writeb(device_t dev, u_char slave, char cmd, char byte) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); int error; VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave & ~ LSB); VIAPM_OUTB(SMBHCMD, cmd); VIAPM_OUTB(SMBHDATA0, byte); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_BYTE); error = viapm_wait(viapm); VIAPM_DEBUG(printf("viapm: WRITEB to 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, byte, error)); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_readb(device_t dev, u_char slave, char cmd, char *byte) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); int error; VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave | LSB); VIAPM_OUTB(SMBHCMD, cmd); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_BYTE); if ((error = viapm_wait(viapm)) == SMB_ENOERR) *byte = VIAPM_INB(SMBHDATA0); VIAPM_DEBUG(printf("viapm: READB from 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, *byte, error)); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_writew(device_t dev, u_char slave, char cmd, short word) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); int error; VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave & ~ LSB); VIAPM_OUTB(SMBHCMD, cmd); VIAPM_OUTB(SMBHDATA0, word & 0x00ff); VIAPM_OUTB(SMBHDATA1, (word & 0xff00) >> 8); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_WORD); error = viapm_wait(viapm); VIAPM_DEBUG(printf("viapm: WRITEW to 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, word, error)); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_readw(device_t dev, u_char slave, char cmd, short *word) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); int error; u_char high, low; VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave | LSB); VIAPM_OUTB(SMBHCMD, cmd); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_WORD); if ((error = viapm_wait(viapm)) == SMB_ENOERR) { low = VIAPM_INB(SMBHDATA0); high = VIAPM_INB(SMBHDATA1); *word = ((high & 0xff) << 8) | (low & 0xff); } VIAPM_DEBUG(printf("viapm: READW from 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, *word, error)); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); u_char i; int error; if (count < 1 || count > 32) return (SMB_EINVAL); VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave & ~LSB); VIAPM_OUTB(SMBHCMD, cmd); VIAPM_OUTB(SMBHDATA0, count); i = VIAPM_INB(SMBHCTRL); /* fill the 32-byte internal buffer */ for (i = 0; i < count; i++) { VIAPM_OUTB(SMBHBLOCK, buf[i]); DELAY(2); } VIAPM_OUTB(SMBHCMD, cmd); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_BLOCK); error = viapm_wait(viapm); VIAPM_DEBUG(printf("viapm: WRITEBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, count, cmd, error)); VIAPM_UNLOCK(viapm); return (error); } static int viasmb_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf) { struct viapm_softc *viapm = (struct viapm_softc *)device_get_softc(dev); u_char data, len, i; int error; if (*count < 1 || *count > 32) return (SMB_EINVAL); VIAPM_LOCK(viapm); viapm_clear(viapm); if (viapm_busy(viapm)) { VIAPM_UNLOCK(viapm); return (SMB_EBUSY); } VIAPM_OUTB(SMBHADDR, slave | LSB); VIAPM_OUTB(SMBHCMD, cmd); VIAPM_OUTB(SMBHCTRL, SMBHCTRL_START | SMBHCTRL_BLOCK); if ((error = viapm_wait(viapm)) != SMB_ENOERR) goto error; len = VIAPM_INB(SMBHDATA0); i = VIAPM_INB(SMBHCTRL); /* reset counter */ /* read the 32-byte internal buffer */ for (i = 0; i < len; i++) { data = VIAPM_INB(SMBHBLOCK); if (i < *count) buf[i] = data; DELAY(2); } *count = len; error: VIAPM_DEBUG(printf("viapm: READBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, *count, cmd, error)); VIAPM_UNLOCK(viapm); return (error); } static device_method_t viapm_methods[] = { /* device interface */ DEVMETHOD(device_probe, viapm_586b_probe), DEVMETHOD(device_attach, viapm_586b_attach), DEVMETHOD(device_detach, viapm_586b_detach), /* iicbb interface */ DEVMETHOD(iicbb_callback, viabb_callback), DEVMETHOD(iicbb_setscl, viabb_setscl), DEVMETHOD(iicbb_setsda, viabb_setsda), DEVMETHOD(iicbb_getscl, viabb_getscl), DEVMETHOD(iicbb_getsda, viabb_getsda), DEVMETHOD(iicbb_reset, viabb_reset), /* Bus interface */ DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t viapm_driver = { "viapm", viapm_methods, sizeof(struct viapm_softc), }; static device_method_t viapropm_methods[] = { /* device interface */ DEVMETHOD(device_probe, viapm_pro_probe), DEVMETHOD(device_attach, viapm_pro_attach), DEVMETHOD(device_detach, viapm_pro_detach), /* smbus interface */ DEVMETHOD(smbus_callback, viasmb_callback), DEVMETHOD(smbus_quick, viasmb_quick), DEVMETHOD(smbus_sendb, viasmb_sendb), DEVMETHOD(smbus_recvb, viasmb_recvb), DEVMETHOD(smbus_writeb, viasmb_writeb), DEVMETHOD(smbus_readb, viasmb_readb), DEVMETHOD(smbus_writew, viasmb_writew), DEVMETHOD(smbus_readw, viasmb_readw), DEVMETHOD(smbus_bwrite, viasmb_bwrite), DEVMETHOD(smbus_bread, viasmb_bread), /* Bus interface */ DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t viapropm_driver = { "viapropm", viapropm_methods, sizeof(struct viapm_softc), }; DRIVER_MODULE(viapm, pci, viapm_driver, 0, 0); DRIVER_MODULE(viapropm, pci, viapropm_driver, 0, 0); DRIVER_MODULE(iicbb, viapm, iicbb_driver, 0, 0); DRIVER_MODULE(smbus, viapropm, smbus_driver, 0, 0); MODULE_DEPEND(viapm, pci, 1, 1, 1); MODULE_DEPEND(viapropm, pci, 1, 1, 1); MODULE_DEPEND(viapm, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER); MODULE_DEPEND(viapropm, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER); MODULE_VERSION(viapm, 1); #ifdef DEV_ISA DRIVER_MODULE(isa, viapm, isa_driver, 0, 0); DRIVER_MODULE(isa, viapropm, isa_driver, 0, 0); MODULE_DEPEND(viapm, isa, 1, 1, 1); MODULE_DEPEND(viapropm, isa, 1, 1, 1); #endif diff --git a/sys/dev/viawd/viawd.c b/sys/dev/viawd/viawd.c index d088284762cb..9e815b8171d1 100644 --- a/sys/dev/viawd/viawd.c +++ b/sys/dev/viawd/viawd.c @@ -1,250 +1,250 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Fabien Thomas * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "viawd.h" #define viawd_read_4(sc, off) bus_read_4((sc)->wd_res, (off)) #define viawd_write_4(sc, off, val) \ bus_write_4((sc)->wd_res, (off), (val)) static struct viawd_device viawd_devices[] = { { DEVICEID_VT8251, "VIA VT8251 watchdog timer" }, { DEVICEID_CX700, "VIA CX700 watchdog timer" }, { DEVICEID_VX800, "VIA VX800 watchdog timer" }, { DEVICEID_VX855, "VIA VX855 watchdog timer" }, { DEVICEID_VX900, "VIA VX900 watchdog timer" }, { 0, NULL }, }; static void viawd_tmr_state(struct viawd_softc *sc, int enable) { uint32_t reg; reg = viawd_read_4(sc, VIAWD_MEM_CTRL); if (enable) reg |= VIAWD_MEM_CTRL_TRIGGER | VIAWD_MEM_CTRL_ENABLE; else reg &= ~VIAWD_MEM_CTRL_ENABLE; viawd_write_4(sc, VIAWD_MEM_CTRL, reg); } static void viawd_tmr_set(struct viawd_softc *sc, unsigned int timeout) { /* Keep value in range. */ if (timeout < VIAWD_MEM_COUNT_MIN) timeout = VIAWD_MEM_COUNT_MIN; else if (timeout > VIAWD_MEM_COUNT_MAX) timeout = VIAWD_MEM_COUNT_MAX; viawd_write_4(sc, VIAWD_MEM_COUNT, timeout); sc->timeout = timeout; } /* * Watchdog event handler - called by the framework to enable or disable * the watchdog or change the initial timeout value. */ static void viawd_event(void *arg, unsigned int cmd, int *error) { struct viawd_softc *sc = arg; unsigned int timeout; /* Convert from power-of-two-ns to second. */ cmd &= WD_INTERVAL; timeout = ((uint64_t)1 << cmd) / 1000000000; if (cmd) { if (timeout != sc->timeout) viawd_tmr_set(sc, timeout); viawd_tmr_state(sc, 1); *error = 0; } else viawd_tmr_state(sc, 0); } /* Look for a supported VIA south bridge. */ static struct viawd_device * viawd_find(device_t dev) { struct viawd_device *id; if (pci_get_vendor(dev) != VENDORID_VIA) return (NULL); for (id = viawd_devices; id->desc != NULL; id++) if (pci_get_device(dev) == id->device) return (id); return (NULL); } static void viawd_identify(driver_t *driver, device_t parent) { if (viawd_find(parent) == NULL) return; - if (device_find_child(parent, driver->name, -1) == NULL) + if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, driver->name, 0); } static int viawd_probe(device_t dev) { struct viawd_device *id; id = viawd_find(device_get_parent(dev)); KASSERT(id != NULL, ("parent should be a valid VIA SB")); device_set_desc(dev, id->desc); return (BUS_PROBE_GENERIC); } static int viawd_attach(device_t dev) { device_t sb_dev; struct viawd_softc *sc; uint32_t pmbase, reg; sc = device_get_softc(dev); sc->dev = dev; sb_dev = device_get_parent(dev); if (sb_dev == NULL) { device_printf(dev, "Can not find watchdog device.\n"); goto fail; } sc->sb_dev = sb_dev; /* Get watchdog memory base. */ pmbase = pci_read_config(sb_dev, VIAWD_CONFIG_BASE, 4); if (pmbase == 0) { device_printf(dev, "Watchdog disabled in BIOS or hardware\n"); goto fail; } /* Allocate I/O register space. */ sc->wd_rid = VIAWD_CONFIG_BASE; sc->wd_res = bus_alloc_resource_any(sb_dev, SYS_RES_MEMORY, &sc->wd_rid, RF_ACTIVE | RF_SHAREABLE); if (sc->wd_res == NULL) { device_printf(dev, "Unable to map watchdog memory\n"); goto fail; } if (rman_get_size(sc->wd_res) < VIAWD_MEM_LEN) { device_printf(dev, "Bad size for watchdog memory: %#x\n", (unsigned)rman_get_size(sc->wd_res)); goto fail; } /* Check if watchdog fired last boot. */ reg = viawd_read_4(sc, VIAWD_MEM_CTRL); if (reg & VIAWD_MEM_CTRL_FIRED) { device_printf(dev, "ERROR: watchdog rebooted the system\n"); /* Reset bit state. */ viawd_write_4(sc, VIAWD_MEM_CTRL, reg); } /* Register the watchdog event handler. */ sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, viawd_event, sc, 0); return (0); fail: if (sc->wd_res != NULL) bus_release_resource(sb_dev, SYS_RES_MEMORY, sc->wd_rid, sc->wd_res); return (ENXIO); } static int viawd_detach(device_t dev) { struct viawd_softc *sc; uint32_t reg; sc = device_get_softc(dev); /* Deregister event handler. */ if (sc->ev_tag != NULL) EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); sc->ev_tag = NULL; /* * Do not stop the watchdog on shutdown if active but bump the * timer to avoid spurious reset. */ reg = viawd_read_4(sc, VIAWD_MEM_CTRL); if (reg & VIAWD_MEM_CTRL_ENABLE) { viawd_tmr_set(sc, VIAWD_TIMEOUT_SHUTDOWN); viawd_tmr_state(sc, 1); device_printf(dev, "Keeping watchdog alive during shutdown for %d seconds\n", VIAWD_TIMEOUT_SHUTDOWN); } if (sc->wd_res != NULL) bus_release_resource(sc->sb_dev, SYS_RES_MEMORY, sc->wd_rid, sc->wd_res); return (0); } static device_method_t viawd_methods[] = { DEVMETHOD(device_identify, viawd_identify), DEVMETHOD(device_probe, viawd_probe), DEVMETHOD(device_attach, viawd_attach), DEVMETHOD(device_detach, viawd_detach), DEVMETHOD(device_shutdown, viawd_detach), {0,0} }; static driver_t viawd_driver = { "viawd", viawd_methods, sizeof(struct viawd_softc), }; DRIVER_MODULE(viawd, isab, viawd_driver, NULL, NULL); diff --git a/sys/dev/virtio/mmio/virtio_mmio.c b/sys/dev/virtio/mmio/virtio_mmio.c index 175b33b42ed8..5a81c8a24779 100644 --- a/sys/dev/virtio/mmio/virtio_mmio.c +++ b/sys/dev/virtio/mmio/virtio_mmio.c @@ -1,1022 +1,1022 @@ /*- * Copyright (c) 2014 Ruslan Bukin * Copyright (c) 2014 The FreeBSD Foundation * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * Portions of this software were developed by Andrew Turner * under sponsorship from the FreeBSD Foundation. * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * VirtIO MMIO interface. * This driver is heavily based on VirtIO PCI interface driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "virtio_mmio_if.h" #include "virtio_bus_if.h" #include "virtio_if.h" struct vtmmio_virtqueue { struct virtqueue *vtv_vq; int vtv_no_intr; }; static int vtmmio_detach(device_t); static int vtmmio_suspend(device_t); static int vtmmio_resume(device_t); static int vtmmio_shutdown(device_t); static void vtmmio_driver_added(device_t, driver_t *); static void vtmmio_child_detached(device_t, device_t); static int vtmmio_read_ivar(device_t, device_t, int, uintptr_t *); static int vtmmio_write_ivar(device_t, device_t, int, uintptr_t); static uint64_t vtmmio_negotiate_features(device_t, uint64_t); static int vtmmio_finalize_features(device_t); static bool vtmmio_with_feature(device_t, uint64_t); static void vtmmio_set_virtqueue(struct vtmmio_softc *sc, struct virtqueue *vq, uint32_t size); static int vtmmio_alloc_virtqueues(device_t, int, struct vq_alloc_info *); static int vtmmio_setup_intr(device_t, enum intr_type); static void vtmmio_stop(device_t); static void vtmmio_poll(device_t); static int vtmmio_reinit(device_t, uint64_t); static void vtmmio_reinit_complete(device_t); static void vtmmio_notify_virtqueue(device_t, uint16_t, bus_size_t); static int vtmmio_config_generation(device_t); static uint8_t vtmmio_get_status(device_t); static void vtmmio_set_status(device_t, uint8_t); static void vtmmio_read_dev_config(device_t, bus_size_t, void *, int); static uint64_t vtmmio_read_dev_config_8(struct vtmmio_softc *, bus_size_t); static void vtmmio_write_dev_config(device_t, bus_size_t, const void *, int); static void vtmmio_describe_features(struct vtmmio_softc *, const char *, uint64_t); static void vtmmio_probe_and_attach_child(struct vtmmio_softc *); static int vtmmio_reinit_virtqueue(struct vtmmio_softc *, int); static void vtmmio_free_interrupts(struct vtmmio_softc *); static void vtmmio_free_virtqueues(struct vtmmio_softc *); static void vtmmio_release_child_resources(struct vtmmio_softc *); static void vtmmio_reset(struct vtmmio_softc *); static void vtmmio_select_virtqueue(struct vtmmio_softc *, int); static void vtmmio_vq_intr(void *); /* * I/O port read/write wrappers. */ #define vtmmio_write_config_1(sc, o, v) \ do { \ if (sc->platform != NULL) \ VIRTIO_MMIO_PREWRITE(sc->platform, (o), (v)); \ bus_write_1((sc)->res[0], (o), (v)); \ if (sc->platform != NULL) \ VIRTIO_MMIO_NOTE(sc->platform, (o), (v)); \ } while (0) #define vtmmio_write_config_2(sc, o, v) \ do { \ if (sc->platform != NULL) \ VIRTIO_MMIO_PREWRITE(sc->platform, (o), (v)); \ bus_write_2((sc)->res[0], (o), (v)); \ if (sc->platform != NULL) \ VIRTIO_MMIO_NOTE(sc->platform, (o), (v)); \ } while (0) #define vtmmio_write_config_4(sc, o, v) \ do { \ if (sc->platform != NULL) \ VIRTIO_MMIO_PREWRITE(sc->platform, (o), (v)); \ bus_write_4((sc)->res[0], (o), (v)); \ if (sc->platform != NULL) \ VIRTIO_MMIO_NOTE(sc->platform, (o), (v)); \ } while (0) #define vtmmio_read_config_1(sc, o) \ bus_read_1((sc)->res[0], (o)) #define vtmmio_read_config_2(sc, o) \ bus_read_2((sc)->res[0], (o)) #define vtmmio_read_config_4(sc, o) \ bus_read_4((sc)->res[0], (o)) static device_method_t vtmmio_methods[] = { /* Device interface. */ DEVMETHOD(device_attach, vtmmio_attach), DEVMETHOD(device_detach, vtmmio_detach), DEVMETHOD(device_suspend, vtmmio_suspend), DEVMETHOD(device_resume, vtmmio_resume), DEVMETHOD(device_shutdown, vtmmio_shutdown), /* Bus interface. */ DEVMETHOD(bus_driver_added, vtmmio_driver_added), DEVMETHOD(bus_child_detached, vtmmio_child_detached), DEVMETHOD(bus_child_pnpinfo, virtio_child_pnpinfo), DEVMETHOD(bus_read_ivar, vtmmio_read_ivar), DEVMETHOD(bus_write_ivar, vtmmio_write_ivar), /* VirtIO bus interface. */ DEVMETHOD(virtio_bus_negotiate_features, vtmmio_negotiate_features), DEVMETHOD(virtio_bus_finalize_features, vtmmio_finalize_features), DEVMETHOD(virtio_bus_with_feature, vtmmio_with_feature), DEVMETHOD(virtio_bus_alloc_virtqueues, vtmmio_alloc_virtqueues), DEVMETHOD(virtio_bus_setup_intr, vtmmio_setup_intr), DEVMETHOD(virtio_bus_stop, vtmmio_stop), DEVMETHOD(virtio_bus_poll, vtmmio_poll), DEVMETHOD(virtio_bus_reinit, vtmmio_reinit), DEVMETHOD(virtio_bus_reinit_complete, vtmmio_reinit_complete), DEVMETHOD(virtio_bus_notify_vq, vtmmio_notify_virtqueue), DEVMETHOD(virtio_bus_config_generation, vtmmio_config_generation), DEVMETHOD(virtio_bus_read_device_config, vtmmio_read_dev_config), DEVMETHOD(virtio_bus_write_device_config, vtmmio_write_dev_config), DEVMETHOD_END }; DEFINE_CLASS_0(virtio_mmio, vtmmio_driver, vtmmio_methods, sizeof(struct vtmmio_softc)); MODULE_VERSION(virtio_mmio, 1); int vtmmio_probe(device_t dev) { struct vtmmio_softc *sc; int rid; uint32_t magic, version; sc = device_get_softc(dev); rid = 0; sc->res[0] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->res[0] == NULL) { device_printf(dev, "Cannot allocate memory window.\n"); return (ENXIO); } magic = vtmmio_read_config_4(sc, VIRTIO_MMIO_MAGIC_VALUE); if (magic != VIRTIO_MMIO_MAGIC_VIRT) { device_printf(dev, "Bad magic value %#x\n", magic); bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res[0]); return (ENXIO); } version = vtmmio_read_config_4(sc, VIRTIO_MMIO_VERSION); if (version < 1 || version > 2) { device_printf(dev, "Unsupported version: %#x\n", version); bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res[0]); return (ENXIO); } if (vtmmio_read_config_4(sc, VIRTIO_MMIO_DEVICE_ID) == 0) { bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res[0]); return (ENXIO); } bus_release_resource(dev, SYS_RES_MEMORY, rid, sc->res[0]); device_set_desc(dev, "VirtIO MMIO adapter"); return (BUS_PROBE_DEFAULT); } static int vtmmio_setup_intr(device_t dev, enum intr_type type) { struct vtmmio_softc *sc; int rid; int err; sc = device_get_softc(dev); if (sc->platform != NULL) { err = VIRTIO_MMIO_SETUP_INTR(sc->platform, sc->dev, vtmmio_vq_intr, sc); if (err == 0) { /* Okay we have backend-specific interrupts */ return (0); } } rid = 0; sc->res[1] = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (!sc->res[1]) { device_printf(dev, "Can't allocate interrupt\n"); return (ENXIO); } if (bus_setup_intr(dev, sc->res[1], type | INTR_MPSAFE, NULL, vtmmio_vq_intr, sc, &sc->ih)) { device_printf(dev, "Can't setup the interrupt\n"); return (ENXIO); } return (0); } int vtmmio_attach(device_t dev) { struct vtmmio_softc *sc; device_t child; int rid; sc = device_get_softc(dev); sc->dev = dev; rid = 0; sc->res[0] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->res[0] == NULL) { device_printf(dev, "Cannot allocate memory window.\n"); return (ENXIO); } sc->vtmmio_version = vtmmio_read_config_4(sc, VIRTIO_MMIO_VERSION); vtmmio_reset(sc); /* Tell the host we've noticed this device. */ vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_ACK); - if ((child = device_add_child(dev, NULL, -1)) == NULL) { + if ((child = device_add_child(dev, NULL, DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "Cannot create child device.\n"); vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_FAILED); vtmmio_detach(dev); return (ENOMEM); } sc->vtmmio_child_dev = child; vtmmio_probe_and_attach_child(sc); return (0); } static int vtmmio_detach(device_t dev) { struct vtmmio_softc *sc; int error; sc = device_get_softc(dev); error = bus_generic_detach(dev); if (error) return (error); vtmmio_reset(sc); if (sc->res[0] != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->res[0]); sc->res[0] = NULL; } return (0); } static int vtmmio_suspend(device_t dev) { return (bus_generic_suspend(dev)); } static int vtmmio_resume(device_t dev) { return (bus_generic_resume(dev)); } static int vtmmio_shutdown(device_t dev) { (void) bus_generic_shutdown(dev); /* Forcibly stop the host device. */ vtmmio_stop(dev); return (0); } static void vtmmio_driver_added(device_t dev, driver_t *driver) { struct vtmmio_softc *sc; sc = device_get_softc(dev); vtmmio_probe_and_attach_child(sc); } static void vtmmio_child_detached(device_t dev, device_t child) { struct vtmmio_softc *sc; sc = device_get_softc(dev); vtmmio_reset(sc); vtmmio_release_child_resources(sc); } static int vtmmio_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { struct vtmmio_softc *sc; sc = device_get_softc(dev); if (sc->vtmmio_child_dev != child) return (ENOENT); switch (index) { case VIRTIO_IVAR_DEVTYPE: case VIRTIO_IVAR_SUBDEVICE: *result = vtmmio_read_config_4(sc, VIRTIO_MMIO_DEVICE_ID); break; case VIRTIO_IVAR_VENDOR: *result = vtmmio_read_config_4(sc, VIRTIO_MMIO_VENDOR_ID); break; case VIRTIO_IVAR_SUBVENDOR: case VIRTIO_IVAR_DEVICE: /* * Dummy value for fields not present in this bus. Used by * bus-agnostic virtio_child_pnpinfo. */ *result = 0; break; case VIRTIO_IVAR_MODERN: /* * There are several modern (aka MMIO v2) spec compliance * issues with this driver, but keep the status quo. */ *result = sc->vtmmio_version > 1; break; default: return (ENOENT); } return (0); } static int vtmmio_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { struct vtmmio_softc *sc; sc = device_get_softc(dev); if (sc->vtmmio_child_dev != child) return (ENOENT); switch (index) { case VIRTIO_IVAR_FEATURE_DESC: sc->vtmmio_child_feat_desc = (void *) value; break; default: return (ENOENT); } return (0); } static uint64_t vtmmio_negotiate_features(device_t dev, uint64_t child_features) { struct vtmmio_softc *sc; uint64_t host_features, features; sc = device_get_softc(dev); if (sc->vtmmio_version > 1) { child_features |= VIRTIO_F_VERSION_1; } vtmmio_write_config_4(sc, VIRTIO_MMIO_HOST_FEATURES_SEL, 1); host_features = vtmmio_read_config_4(sc, VIRTIO_MMIO_HOST_FEATURES); host_features <<= 32; vtmmio_write_config_4(sc, VIRTIO_MMIO_HOST_FEATURES_SEL, 0); host_features |= vtmmio_read_config_4(sc, VIRTIO_MMIO_HOST_FEATURES); vtmmio_describe_features(sc, "host", host_features); /* * Limit negotiated features to what the driver, virtqueue, and * host all support. */ features = host_features & child_features; features = virtio_filter_transport_features(features); sc->vtmmio_features = features; vtmmio_describe_features(sc, "negotiated", features); vtmmio_write_config_4(sc, VIRTIO_MMIO_GUEST_FEATURES_SEL, 1); vtmmio_write_config_4(sc, VIRTIO_MMIO_GUEST_FEATURES, features >> 32); vtmmio_write_config_4(sc, VIRTIO_MMIO_GUEST_FEATURES_SEL, 0); vtmmio_write_config_4(sc, VIRTIO_MMIO_GUEST_FEATURES, features); return (features); } static int vtmmio_finalize_features(device_t dev) { struct vtmmio_softc *sc; uint8_t status; sc = device_get_softc(dev); if (sc->vtmmio_version > 1) { /* * Must re-read the status after setting it to verify the * negotiated features were accepted by the device. */ vtmmio_set_status(dev, VIRTIO_CONFIG_S_FEATURES_OK); status = vtmmio_get_status(dev); if ((status & VIRTIO_CONFIG_S_FEATURES_OK) == 0) { device_printf(dev, "desired features were not accepted\n"); return (ENOTSUP); } } return (0); } static bool vtmmio_with_feature(device_t dev, uint64_t feature) { struct vtmmio_softc *sc; sc = device_get_softc(dev); return ((sc->vtmmio_features & feature) != 0); } static void vtmmio_set_virtqueue(struct vtmmio_softc *sc, struct virtqueue *vq, uint32_t size) { vm_paddr_t paddr; vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_NUM, size); if (sc->vtmmio_version == 1) { vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_ALIGN, VIRTIO_MMIO_VRING_ALIGN); paddr = virtqueue_paddr(vq); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN, paddr >> PAGE_SHIFT); } else { paddr = virtqueue_desc_paddr(vq); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_DESC_LOW, paddr); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_DESC_HIGH, ((uint64_t)paddr) >> 32); paddr = virtqueue_avail_paddr(vq); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_AVAIL_LOW, paddr); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, ((uint64_t)paddr) >> 32); paddr = virtqueue_used_paddr(vq); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_USED_LOW, paddr); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_USED_HIGH, ((uint64_t)paddr) >> 32); vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_READY, 1); } } static int vtmmio_alloc_virtqueues(device_t dev, int nvqs, struct vq_alloc_info *vq_info) { struct vtmmio_virtqueue *vqx; struct vq_alloc_info *info; struct vtmmio_softc *sc; struct virtqueue *vq; uint32_t size; int idx, error; sc = device_get_softc(dev); if (sc->vtmmio_nvqs != 0) return (EALREADY); if (nvqs <= 0) return (EINVAL); sc->vtmmio_vqs = malloc(nvqs * sizeof(struct vtmmio_virtqueue), M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->vtmmio_vqs == NULL) return (ENOMEM); if (sc->vtmmio_version == 1) { vtmmio_write_config_4(sc, VIRTIO_MMIO_GUEST_PAGE_SIZE, (1 << PAGE_SHIFT)); } for (idx = 0; idx < nvqs; idx++) { vqx = &sc->vtmmio_vqs[idx]; info = &vq_info[idx]; vtmmio_select_virtqueue(sc, idx); size = vtmmio_read_config_4(sc, VIRTIO_MMIO_QUEUE_NUM_MAX); error = virtqueue_alloc(dev, idx, size, VIRTIO_MMIO_QUEUE_NOTIFY, VIRTIO_MMIO_VRING_ALIGN, ~(vm_paddr_t)0, info, &vq); if (error) { device_printf(dev, "cannot allocate virtqueue %d: %d\n", idx, error); break; } vtmmio_set_virtqueue(sc, vq, size); vqx->vtv_vq = *info->vqai_vq = vq; vqx->vtv_no_intr = info->vqai_intr == NULL; sc->vtmmio_nvqs++; } if (error) vtmmio_free_virtqueues(sc); return (error); } static void vtmmio_stop(device_t dev) { vtmmio_reset(device_get_softc(dev)); } static void vtmmio_poll(device_t dev) { struct vtmmio_softc *sc; sc = device_get_softc(dev); if (sc->platform != NULL) VIRTIO_MMIO_POLL(sc->platform); } static int vtmmio_reinit(device_t dev, uint64_t features) { struct vtmmio_softc *sc; int idx, error; sc = device_get_softc(dev); if (vtmmio_get_status(dev) != VIRTIO_CONFIG_STATUS_RESET) vtmmio_stop(dev); /* * Quickly drive the status through ACK and DRIVER. The device * does not become usable again until vtmmio_reinit_complete(). */ vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_ACK); vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER); /* * TODO: Check that features are not added as to what was * originally negotiated. */ vtmmio_negotiate_features(dev, features); error = vtmmio_finalize_features(dev); if (error) { device_printf(dev, "cannot finalize features during reinit\n"); return (error); } if (sc->vtmmio_version == 1) { vtmmio_write_config_4(sc, VIRTIO_MMIO_GUEST_PAGE_SIZE, (1 << PAGE_SHIFT)); } for (idx = 0; idx < sc->vtmmio_nvqs; idx++) { error = vtmmio_reinit_virtqueue(sc, idx); if (error) return (error); } return (0); } static void vtmmio_reinit_complete(device_t dev) { vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER_OK); } static void vtmmio_notify_virtqueue(device_t dev, uint16_t queue, bus_size_t offset) { struct vtmmio_softc *sc; sc = device_get_softc(dev); MPASS(offset == VIRTIO_MMIO_QUEUE_NOTIFY); vtmmio_write_config_4(sc, offset, queue); } static int vtmmio_config_generation(device_t dev) { struct vtmmio_softc *sc; uint32_t gen; sc = device_get_softc(dev); if (sc->vtmmio_version > 1) gen = vtmmio_read_config_4(sc, VIRTIO_MMIO_CONFIG_GENERATION); else gen = 0; return (gen); } static uint8_t vtmmio_get_status(device_t dev) { struct vtmmio_softc *sc; sc = device_get_softc(dev); return (vtmmio_read_config_4(sc, VIRTIO_MMIO_STATUS)); } static void vtmmio_set_status(device_t dev, uint8_t status) { struct vtmmio_softc *sc; sc = device_get_softc(dev); if (status != VIRTIO_CONFIG_STATUS_RESET) status |= vtmmio_get_status(dev); vtmmio_write_config_4(sc, VIRTIO_MMIO_STATUS, status); } static void vtmmio_read_dev_config(device_t dev, bus_size_t offset, void *dst, int length) { struct vtmmio_softc *sc; bus_size_t off; uint8_t *d; int size; sc = device_get_softc(dev); off = VIRTIO_MMIO_CONFIG + offset; /* * The non-legacy MMIO specification adds the following restriction: * * 4.2.2.2: For the device-specific configuration space, the driver * MUST use 8 bit wide accesses for 8 bit wide fields, 16 bit wide * and aligned accesses for 16 bit wide fields and 32 bit wide and * aligned accesses for 32 and 64 bit wide fields. * * The endianness also varies between non-legacy and legacy: * * 2.4: Note: The device configuration space uses the little-endian * format for multi-byte fields. * * 2.4.3: Note that for legacy interfaces, device configuration space * is generally the guestโ€™s native endian, rather than PCIโ€™s * little-endian. The correct endian-ness is documented for each * device. */ if (sc->vtmmio_version > 1) { switch (length) { case 1: *(uint8_t *)dst = vtmmio_read_config_1(sc, off); break; case 2: *(uint16_t *)dst = le16toh(vtmmio_read_config_2(sc, off)); break; case 4: *(uint32_t *)dst = le32toh(vtmmio_read_config_4(sc, off)); break; case 8: *(uint64_t *)dst = vtmmio_read_dev_config_8(sc, off); break; default: panic("%s: invalid length %d\n", __func__, length); } return; } for (d = dst; length > 0; d += size, off += size, length -= size) { #ifdef ALLOW_WORD_ALIGNED_ACCESS if (length >= 4) { size = 4; *(uint32_t *)d = vtmmio_read_config_4(sc, off); } else if (length >= 2) { size = 2; *(uint16_t *)d = vtmmio_read_config_2(sc, off); } else #endif { size = 1; *d = vtmmio_read_config_1(sc, off); } } } static uint64_t vtmmio_read_dev_config_8(struct vtmmio_softc *sc, bus_size_t off) { device_t dev; int gen; uint32_t val0, val1; dev = sc->dev; do { gen = vtmmio_config_generation(dev); val0 = le32toh(vtmmio_read_config_4(sc, off)); val1 = le32toh(vtmmio_read_config_4(sc, off + 4)); } while (gen != vtmmio_config_generation(dev)); return (((uint64_t) val1 << 32) | val0); } static void vtmmio_write_dev_config(device_t dev, bus_size_t offset, const void *src, int length) { struct vtmmio_softc *sc; bus_size_t off; const uint8_t *s; int size; sc = device_get_softc(dev); off = VIRTIO_MMIO_CONFIG + offset; /* * The non-legacy MMIO specification adds size and alignment * restrctions. It also changes the endianness from native-endian to * little-endian. See vtmmio_read_dev_config. */ if (sc->vtmmio_version > 1) { switch (length) { case 1: vtmmio_write_config_1(sc, off, *(const uint8_t *)src); break; case 2: vtmmio_write_config_2(sc, off, htole16(*(const uint16_t *)src)); break; case 4: vtmmio_write_config_4(sc, off, htole32(*(const uint32_t *)src)); break; case 8: vtmmio_write_config_4(sc, off, htole32(*(const uint64_t *)src)); vtmmio_write_config_4(sc, off + 4, htole32((*(const uint64_t *)src) >> 32)); break; default: panic("%s: invalid length %d\n", __func__, length); } return; } for (s = src; length > 0; s += size, off += size, length -= size) { #ifdef ALLOW_WORD_ALIGNED_ACCESS if (length >= 4) { size = 4; vtmmio_write_config_4(sc, off, *(uint32_t *)s); } else if (length >= 2) { size = 2; vtmmio_write_config_2(sc, off, *(uint16_t *)s); } else #endif { size = 1; vtmmio_write_config_1(sc, off, *s); } } } static void vtmmio_describe_features(struct vtmmio_softc *sc, const char *msg, uint64_t features) { device_t dev, child; dev = sc->dev; child = sc->vtmmio_child_dev; if (device_is_attached(child) || bootverbose == 0) return; virtio_describe(dev, msg, features, sc->vtmmio_child_feat_desc); } static void vtmmio_probe_and_attach_child(struct vtmmio_softc *sc) { device_t dev, child; dev = sc->dev; child = sc->vtmmio_child_dev; if (child == NULL) return; if (device_get_state(child) != DS_NOTPRESENT) { return; } if (device_probe(child) != 0) { return; } vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER); if (device_attach(child) != 0) { vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_FAILED); vtmmio_reset(sc); vtmmio_release_child_resources(sc); /* Reset status for future attempt. */ vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_ACK); } else { vtmmio_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER_OK); VIRTIO_ATTACH_COMPLETED(child); } } static int vtmmio_reinit_virtqueue(struct vtmmio_softc *sc, int idx) { struct vtmmio_virtqueue *vqx; struct virtqueue *vq; int error; uint16_t size; vqx = &sc->vtmmio_vqs[idx]; vq = vqx->vtv_vq; KASSERT(vq != NULL, ("%s: vq %d not allocated", __func__, idx)); vtmmio_select_virtqueue(sc, idx); size = vtmmio_read_config_4(sc, VIRTIO_MMIO_QUEUE_NUM_MAX); error = virtqueue_reinit(vq, size); if (error) return (error); vtmmio_set_virtqueue(sc, vq, size); return (0); } static void vtmmio_free_interrupts(struct vtmmio_softc *sc) { if (sc->ih != NULL) bus_teardown_intr(sc->dev, sc->res[1], sc->ih); if (sc->res[1] != NULL) bus_release_resource(sc->dev, SYS_RES_IRQ, 0, sc->res[1]); } static void vtmmio_free_virtqueues(struct vtmmio_softc *sc) { struct vtmmio_virtqueue *vqx; int idx; for (idx = 0; idx < sc->vtmmio_nvqs; idx++) { vqx = &sc->vtmmio_vqs[idx]; vtmmio_select_virtqueue(sc, idx); if (sc->vtmmio_version > 1) { vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_READY, 0); vtmmio_read_config_4(sc, VIRTIO_MMIO_QUEUE_READY); } else vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN, 0); virtqueue_free(vqx->vtv_vq); vqx->vtv_vq = NULL; } free(sc->vtmmio_vqs, M_DEVBUF); sc->vtmmio_vqs = NULL; sc->vtmmio_nvqs = 0; } static void vtmmio_release_child_resources(struct vtmmio_softc *sc) { vtmmio_free_interrupts(sc); vtmmio_free_virtqueues(sc); } static void vtmmio_reset(struct vtmmio_softc *sc) { /* * Setting the status to RESET sets the host device to * the original, uninitialized state. */ vtmmio_set_status(sc->dev, VIRTIO_CONFIG_STATUS_RESET); } static void vtmmio_select_virtqueue(struct vtmmio_softc *sc, int idx) { vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_SEL, idx); } static void vtmmio_vq_intr(void *arg) { struct vtmmio_virtqueue *vqx; struct vtmmio_softc *sc; struct virtqueue *vq; uint32_t status; int idx; sc = arg; status = vtmmio_read_config_4(sc, VIRTIO_MMIO_INTERRUPT_STATUS); vtmmio_write_config_4(sc, VIRTIO_MMIO_INTERRUPT_ACK, status); /* The config changed */ if (status & VIRTIO_MMIO_INT_CONFIG) if (sc->vtmmio_child_dev != NULL) VIRTIO_CONFIG_CHANGE(sc->vtmmio_child_dev); /* Notify all virtqueues. */ if (status & VIRTIO_MMIO_INT_VRING) { for (idx = 0; idx < sc->vtmmio_nvqs; idx++) { vqx = &sc->vtmmio_vqs[idx]; if (vqx->vtv_no_intr == 0) { vq = vqx->vtv_vq; virtqueue_intr(vq); } } } } diff --git a/sys/isa/isahint.c b/sys/isa/isahint.c index d9ec9f35f7f2..68aa1de00927 100644 --- a/sys/isa/isahint.c +++ b/sys/isa/isahint.c @@ -1,179 +1,180 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1999 Doug Rabson * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include void isa_hinted_child(device_t parent, const char *name, int unit) { device_t child; int sensitive, start, count; int order; /* device-specific flag overrides any wildcard */ sensitive = 0; if (resource_int_value(name, unit, "sensitive", &sensitive) != 0) - resource_int_value(name, -1, "sensitive", &sensitive); + resource_int_value(name, DEVICE_UNIT_ANY, "sensitive", + &sensitive); if (sensitive) order = ISA_ORDER_SENSITIVE; else order = ISA_ORDER_SPECULATIVE; child = BUS_ADD_CHILD(parent, order, name, unit); if (child == 0) return; start = 0; count = 0; resource_int_value(name, unit, "port", &start); resource_int_value(name, unit, "portsize", &count); if (start > 0 || count > 0) bus_set_resource(child, SYS_RES_IOPORT, 0, start, count); start = 0; count = 0; resource_int_value(name, unit, "maddr", &start); resource_int_value(name, unit, "msize", &count); if (start > 0 || count > 0) bus_set_resource(child, SYS_RES_MEMORY, 0, start, count); if (resource_int_value(name, unit, "irq", &start) == 0 && start > 0) bus_set_resource(child, SYS_RES_IRQ, 0, start, 1); if (resource_int_value(name, unit, "drq", &start) == 0 && start >= 0) bus_set_resource(child, SYS_RES_DRQ, 0, start, 1); if (resource_disabled(name, unit)) device_disable(child); isa_set_configattr(child, (isa_get_configattr(child)|ISACFGATTR_HINTS)); } static int isa_match_resource_hint(device_t dev, int type, long value) { struct isa_device* idev = DEVTOISA(dev); struct resource_list *rl = &idev->id_resources; struct resource_list_entry *rle; STAILQ_FOREACH(rle, rl, link) { if (rle->type != type) continue; if (rle->start <= value && rle->end >= value) return (1); } return (0); } void isa_hint_device_unit(device_t bus, device_t child, const char *name, int *unitp) { const char *s; long value; int line, matches, unit; line = 0; for (;;) { if (resource_find_dev(&line, name, &unit, "at", NULL) != 0) break; /* Must have an "at" for isa. */ resource_string_value(name, unit, "at", &s); if (!(strcmp(s, device_get_nameunit(bus)) == 0 || strcmp(s, device_get_name(bus)) == 0)) continue; /* * Check for matching resources. We must have at * least one match. Since I/O and memory resources * cannot be shared, if we get a match on either of * those, ignore any mismatches in IRQs or DRQs. * * XXX: We may want to revisit this to be more lenient * and wire as long as it gets one match. */ matches = 0; if (resource_long_value(name, unit, "port", &value) == 0) { /* * Floppy drive controllers are notorious for * having a wide variety of resources not all * of which include the first port that is * specified by the hint (typically 0x3f0) * (see the comment above * fdc_isa_alloc_resources() in fdc_isa.c). * However, they do all seem to include port + * 2 (e.g. 0x3f2) so for a floppy device, look * for 'value + 2' in the port resources * instead of the hint value. */ if (strcmp(name, "fdc") == 0) value += 2; if (isa_match_resource_hint(child, SYS_RES_IOPORT, value)) matches++; else continue; } if (resource_long_value(name, unit, "maddr", &value) == 0) { if (isa_match_resource_hint(child, SYS_RES_MEMORY, value)) matches++; else continue; } if (matches > 0) goto matched; if (resource_long_value(name, unit, "irq", &value) == 0) { if (isa_match_resource_hint(child, SYS_RES_IRQ, value)) matches++; else continue; } if (resource_long_value(name, unit, "drq", &value) == 0) { if (isa_match_resource_hint(child, SYS_RES_DRQ, value)) matches++; else continue; } matched: if (matches > 0) { /* We have a winner! */ *unitp = unit; break; } } } diff --git a/sys/kern/kern_cpu.c b/sys/kern/kern_cpu.c index 1b55ebf0738b..1fb3d8002c7f 100644 --- a/sys/kern/kern_cpu.c +++ b/sys/kern/kern_cpu.c @@ -1,1158 +1,1159 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004-2007 Nate Lawson (SDG) * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" /* * Common CPU frequency glue code. Drivers for specific hardware can * attach this interface to allow users to get/set the CPU frequency. */ /* * Number of levels we can handle. Levels are synthesized from settings * so for M settings and N drivers, there may be M*N levels. */ #define CF_MAX_LEVELS 256 struct cf_saved_freq { struct cf_level level; int priority; SLIST_ENTRY(cf_saved_freq) link; }; struct cpufreq_softc { struct sx lock; struct cf_level curr_level; int curr_priority; SLIST_HEAD(, cf_saved_freq) saved_freq; struct cf_level_lst all_levels; int all_count; int max_mhz; device_t dev; device_t cf_drv_dev; struct sysctl_ctx_list sysctl_ctx; struct task startup_task; struct cf_level *levels_buf; }; struct cf_setting_array { struct cf_setting sets[MAX_SETTINGS]; int count; TAILQ_ENTRY(cf_setting_array) link; }; TAILQ_HEAD(cf_setting_lst, cf_setting_array); #define CF_MTX_INIT(x) sx_init((x), "cpufreq lock") #define CF_MTX_LOCK(x) sx_xlock((x)) #define CF_MTX_UNLOCK(x) sx_xunlock((x)) #define CF_MTX_ASSERT(x) sx_assert((x), SX_XLOCKED) #define CF_DEBUG(msg...) do { \ if (cf_verbose) \ printf("cpufreq: " msg); \ } while (0) static int cpufreq_probe(device_t dev); static int cpufreq_attach(device_t dev); static void cpufreq_startup_task(void *ctx, int pending); static int cpufreq_detach(device_t dev); static int cf_set_method(device_t dev, const struct cf_level *level, int priority); static int cf_get_method(device_t dev, struct cf_level *level); static int cf_levels_method(device_t dev, struct cf_level *levels, int *count); static int cpufreq_insert_abs(struct cpufreq_softc *sc, struct cf_setting *sets, int count); static int cpufreq_expand_set(struct cpufreq_softc *sc, struct cf_setting_array *set_arr); static struct cf_level *cpufreq_dup_set(struct cpufreq_softc *sc, struct cf_level *dup, struct cf_setting *set); static int cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS); static int cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS); static int cpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t cpufreq_methods[] = { DEVMETHOD(device_probe, cpufreq_probe), DEVMETHOD(device_attach, cpufreq_attach), DEVMETHOD(device_detach, cpufreq_detach), DEVMETHOD(cpufreq_set, cf_set_method), DEVMETHOD(cpufreq_get, cf_get_method), DEVMETHOD(cpufreq_levels, cf_levels_method), {0, 0} }; static driver_t cpufreq_driver = { "cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc) }; DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, 0, 0); static int cf_lowest_freq; static int cf_verbose; static SYSCTL_NODE(_debug, OID_AUTO, cpufreq, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "cpufreq debugging"); SYSCTL_INT(_debug_cpufreq, OID_AUTO, lowest, CTLFLAG_RWTUN, &cf_lowest_freq, 1, "Don't provide levels below this frequency."); SYSCTL_INT(_debug_cpufreq, OID_AUTO, verbose, CTLFLAG_RWTUN, &cf_verbose, 1, "Print verbose debugging messages"); static int cpufreq_probe(device_t dev) { device_set_desc(dev, "CPU frequency control"); return (BUS_PROBE_DEFAULT); } /* * This is called as the result of a hardware specific frequency control driver * calling cpufreq_register. It provides a general interface for system wide * frequency controls and operates on a per cpu basis. */ static int cpufreq_attach(device_t dev) { struct cpufreq_softc *sc; struct pcpu *pc; device_t parent; uint64_t rate; CF_DEBUG("initializing %s\n", device_get_nameunit(dev)); sc = device_get_softc(dev); parent = device_get_parent(dev); sc->dev = dev; sysctl_ctx_init(&sc->sysctl_ctx); TAILQ_INIT(&sc->all_levels); CF_MTX_INIT(&sc->lock); sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; SLIST_INIT(&sc->saved_freq); /* Try to get nominal CPU freq to use it as maximum later if needed */ sc->max_mhz = cpu_get_nominal_mhz(dev); /* If that fails, try to measure the current rate */ if (sc->max_mhz <= 0) { CF_DEBUG("Unable to obtain nominal frequency.\n"); pc = cpu_get_pcpu(dev); if (cpu_est_clockrate(pc->pc_cpuid, &rate) == 0) sc->max_mhz = rate / 1000000; else sc->max_mhz = CPUFREQ_VAL_UNKNOWN; } CF_DEBUG("initializing one-time data for %s\n", device_get_nameunit(dev)); sc->levels_buf = malloc(CF_MAX_LEVELS * sizeof(*sc->levels_buf), M_DEVBUF, M_WAITOK); SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, cpufreq_curr_sysctl, "I", "Current CPU frequency"); SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, 0, cpufreq_levels_sysctl, "A", "CPU frequency levels"); /* * Queue a one-shot broadcast that levels have changed. * It will run once the system has completed booting. */ TASK_INIT(&sc->startup_task, 0, cpufreq_startup_task, dev); taskqueue_enqueue(taskqueue_thread, &sc->startup_task); return (0); } /* Handle any work to be done for all drivers that attached during boot. */ static void cpufreq_startup_task(void *ctx, int pending) { cpufreq_settings_changed((device_t)ctx); } static int cpufreq_detach(device_t dev) { struct cpufreq_softc *sc; struct cf_saved_freq *saved_freq; CF_DEBUG("shutdown %s\n", device_get_nameunit(dev)); sc = device_get_softc(dev); sysctl_ctx_free(&sc->sysctl_ctx); while ((saved_freq = SLIST_FIRST(&sc->saved_freq)) != NULL) { SLIST_REMOVE_HEAD(&sc->saved_freq, link); free(saved_freq, M_TEMP); } free(sc->levels_buf, M_DEVBUF); return (0); } static int cf_set_method(device_t dev, const struct cf_level *level, int priority) { struct cpufreq_softc *sc; const struct cf_setting *set; struct cf_saved_freq *saved_freq, *curr_freq; struct pcpu *pc; int error, i; u_char pri; sc = device_get_softc(dev); error = 0; set = NULL; saved_freq = NULL; /* We are going to change levels so notify the pre-change handler. */ EVENTHANDLER_INVOKE(cpufreq_pre_change, level, &error); if (error != 0) { EVENTHANDLER_INVOKE(cpufreq_post_change, level, error); return (error); } CF_MTX_LOCK(&sc->lock); #ifdef SMP #ifdef EARLY_AP_STARTUP MPASS(mp_ncpus == 1 || smp_started); #else /* * If still booting and secondary CPUs not started yet, don't allow * changing the frequency until they're online. This is because we * can't switch to them using sched_bind() and thus we'd only be * switching the main CPU. XXXTODO: Need to think more about how to * handle having different CPUs at different frequencies. */ if (mp_ncpus > 1 && !smp_started) { device_printf(dev, "rejecting change, SMP not started yet\n"); error = ENXIO; goto out; } #endif #endif /* SMP */ /* * If the requested level has a lower priority, don't allow * the new level right now. */ if (priority < sc->curr_priority) { CF_DEBUG("ignoring, curr prio %d less than %d\n", priority, sc->curr_priority); error = EPERM; goto out; } /* * If the caller didn't specify a level and one is saved, prepare to * restore the saved level. If none has been saved, return an error. */ if (level == NULL) { saved_freq = SLIST_FIRST(&sc->saved_freq); if (saved_freq == NULL) { CF_DEBUG("NULL level, no saved level\n"); error = ENXIO; goto out; } level = &saved_freq->level; priority = saved_freq->priority; CF_DEBUG("restoring saved level, freq %d prio %d\n", level->total_set.freq, priority); } /* Reject levels that are below our specified threshold. */ if (level->total_set.freq < cf_lowest_freq) { CF_DEBUG("rejecting freq %d, less than %d limit\n", level->total_set.freq, cf_lowest_freq); error = EINVAL; goto out; } /* If already at this level, just return. */ if (sc->curr_level.total_set.freq == level->total_set.freq) { CF_DEBUG("skipping freq %d, same as current level %d\n", level->total_set.freq, sc->curr_level.total_set.freq); goto skip; } /* First, set the absolute frequency via its driver. */ set = &level->abs_set; if (set->dev) { if (!device_is_attached(set->dev)) { error = ENXIO; goto out; } /* Bind to the target CPU before switching. */ pc = cpu_get_pcpu(set->dev); /* Skip settings if CPU is not started. */ if (pc == NULL) { error = 0; goto out; } thread_lock(curthread); pri = curthread->td_priority; sched_prio(curthread, PRI_MIN); sched_bind(curthread, pc->pc_cpuid); thread_unlock(curthread); CF_DEBUG("setting abs freq %d on %s (cpu %d)\n", set->freq, device_get_nameunit(set->dev), PCPU_GET(cpuid)); error = CPUFREQ_DRV_SET(set->dev, set); thread_lock(curthread); sched_unbind(curthread); sched_prio(curthread, pri); thread_unlock(curthread); if (error) { goto out; } } /* Next, set any/all relative frequencies via their drivers. */ for (i = 0; i < level->rel_count; i++) { set = &level->rel_set[i]; if (!device_is_attached(set->dev)) { error = ENXIO; goto out; } /* Bind to the target CPU before switching. */ pc = cpu_get_pcpu(set->dev); thread_lock(curthread); pri = curthread->td_priority; sched_prio(curthread, PRI_MIN); sched_bind(curthread, pc->pc_cpuid); thread_unlock(curthread); CF_DEBUG("setting rel freq %d on %s (cpu %d)\n", set->freq, device_get_nameunit(set->dev), PCPU_GET(cpuid)); error = CPUFREQ_DRV_SET(set->dev, set); thread_lock(curthread); sched_unbind(curthread); sched_prio(curthread, pri); thread_unlock(curthread); if (error) { /* XXX Back out any successful setting? */ goto out; } } skip: /* * Before recording the current level, check if we're going to a * higher priority. If so, save the previous level and priority. */ if (sc->curr_level.total_set.freq != CPUFREQ_VAL_UNKNOWN && priority > sc->curr_priority) { CF_DEBUG("saving level, freq %d prio %d\n", sc->curr_level.total_set.freq, sc->curr_priority); curr_freq = malloc(sizeof(*curr_freq), M_TEMP, M_NOWAIT); if (curr_freq == NULL) { error = ENOMEM; goto out; } curr_freq->level = sc->curr_level; curr_freq->priority = sc->curr_priority; SLIST_INSERT_HEAD(&sc->saved_freq, curr_freq, link); } sc->curr_level = *level; sc->curr_priority = priority; /* If we were restoring a saved state, reset it to "unused". */ if (saved_freq != NULL) { CF_DEBUG("resetting saved level\n"); sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; SLIST_REMOVE_HEAD(&sc->saved_freq, link); free(saved_freq, M_TEMP); } out: CF_MTX_UNLOCK(&sc->lock); /* * We changed levels (or attempted to) so notify the post-change * handler of new frequency or error. */ EVENTHANDLER_INVOKE(cpufreq_post_change, level, error); if (error && set) device_printf(set->dev, "set freq failed, err %d\n", error); return (error); } static int cpufreq_get_frequency(device_t dev) { struct cf_setting set; if (CPUFREQ_DRV_GET(dev, &set) != 0) return (-1); return (set.freq); } /* Returns the index into *levels with the match */ static int cpufreq_get_level(device_t dev, struct cf_level *levels, int count) { int i, freq; if ((freq = cpufreq_get_frequency(dev)) < 0) return (-1); for (i = 0; i < count; i++) if (freq == levels[i].total_set.freq) return (i); return (-1); } /* * Used by the cpufreq core, this function will populate *level with the current * frequency as either determined by a cached value sc->curr_level, or in the * case the lower level driver has set the CPUFREQ_FLAG_UNCACHED flag, it will * obtain the frequency from the driver itself. */ static int cf_get_method(device_t dev, struct cf_level *level) { struct cpufreq_softc *sc; struct cf_level *levels; struct cf_setting *curr_set; struct pcpu *pc; int bdiff, count, diff, error, i, type; uint64_t rate; sc = device_get_softc(dev); error = 0; levels = NULL; /* * If we already know the current frequency, and the driver didn't ask * for uncached usage, we're done. */ CF_MTX_LOCK(&sc->lock); curr_set = &sc->curr_level.total_set; error = CPUFREQ_DRV_TYPE(sc->cf_drv_dev, &type); if (error == 0 && (type & CPUFREQ_FLAG_UNCACHED)) { struct cf_setting set; /* * If the driver wants to always report back the real frequency, * first try the driver and if that fails, fall back to * estimating. */ if (CPUFREQ_DRV_GET(sc->cf_drv_dev, &set) == 0) { sc->curr_level.total_set = set; CF_DEBUG("get returning immediate freq %d\n", curr_set->freq); goto out; } } else if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) { CF_DEBUG("get returning known freq %d\n", curr_set->freq); error = 0; goto out; } CF_MTX_UNLOCK(&sc->lock); /* * We need to figure out the current level. Loop through every * driver, getting the current setting. Then, attempt to get a best * match of settings against each level. */ count = CF_MAX_LEVELS; levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT); if (levels == NULL) return (ENOMEM); error = CPUFREQ_LEVELS(sc->dev, levels, &count); if (error) { if (error == E2BIG) printf("cpufreq: need to increase CF_MAX_LEVELS\n"); free(levels, M_TEMP); return (error); } /* * Reacquire the lock and search for the given level. * * XXX Note: this is not quite right since we really need to go * through each level and compare both absolute and relative * settings for each driver in the system before making a match. * The estimation code below catches this case though. */ CF_MTX_LOCK(&sc->lock); i = cpufreq_get_level(sc->cf_drv_dev, levels, count); if (i >= 0) sc->curr_level = levels[i]; else CF_DEBUG("Couldn't find supported level for %s\n", device_get_nameunit(sc->cf_drv_dev)); if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) { CF_DEBUG("get matched freq %d from drivers\n", curr_set->freq); goto out; } /* * We couldn't find an exact match, so attempt to estimate and then * match against a level. */ pc = cpu_get_pcpu(dev); if (pc == NULL) { error = ENXIO; goto out; } cpu_est_clockrate(pc->pc_cpuid, &rate); rate /= 1000000; bdiff = 1 << 30; for (i = 0; i < count; i++) { diff = abs(levels[i].total_set.freq - rate); if (diff < bdiff) { bdiff = diff; sc->curr_level = levels[i]; } } CF_DEBUG("get estimated freq %d\n", curr_set->freq); out: if (error == 0) *level = sc->curr_level; CF_MTX_UNLOCK(&sc->lock); if (levels) free(levels, M_TEMP); return (error); } /* * Either directly obtain settings from the cpufreq driver, or build a list of * relative settings to be integrated later against an absolute max. */ static int cpufreq_add_levels(device_t cf_dev, struct cf_setting_lst *rel_sets) { struct cf_setting_array *set_arr; struct cf_setting *sets; device_t dev; struct cpufreq_softc *sc; int type, set_count, error; sc = device_get_softc(cf_dev); dev = sc->cf_drv_dev; /* Skip devices that aren't ready. */ if (!device_is_attached(cf_dev)) return (0); /* * Get settings, skipping drivers that offer no settings or * provide settings for informational purposes only. */ error = CPUFREQ_DRV_TYPE(dev, &type); if (error != 0 || (type & CPUFREQ_FLAG_INFO_ONLY)) { if (error == 0) { CF_DEBUG("skipping info-only driver %s\n", device_get_nameunit(cf_dev)); } return (error); } sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT); if (sets == NULL) return (ENOMEM); set_count = MAX_SETTINGS; error = CPUFREQ_DRV_SETTINGS(dev, sets, &set_count); if (error != 0 || set_count == 0) goto out; /* Add the settings to our absolute/relative lists. */ switch (type & CPUFREQ_TYPE_MASK) { case CPUFREQ_TYPE_ABSOLUTE: error = cpufreq_insert_abs(sc, sets, set_count); break; case CPUFREQ_TYPE_RELATIVE: CF_DEBUG("adding %d relative settings\n", set_count); set_arr = malloc(sizeof(*set_arr), M_TEMP, M_NOWAIT); if (set_arr == NULL) { error = ENOMEM; goto out; } bcopy(sets, set_arr->sets, set_count * sizeof(*sets)); set_arr->count = set_count; TAILQ_INSERT_TAIL(rel_sets, set_arr, link); break; default: error = EINVAL; } out: free(sets, M_TEMP); return (error); } static int cf_levels_method(device_t dev, struct cf_level *levels, int *count) { struct cf_setting_array *set_arr; struct cf_setting_lst rel_sets; struct cpufreq_softc *sc; struct cf_level *lev; struct pcpu *pc; int error, i; uint64_t rate; if (levels == NULL || count == NULL) return (EINVAL); TAILQ_INIT(&rel_sets); sc = device_get_softc(dev); CF_MTX_LOCK(&sc->lock); error = cpufreq_add_levels(sc->dev, &rel_sets); if (error) goto out; /* * If there are no absolute levels, create a fake one at 100%. We * then cache the clockrate for later use as our base frequency. */ if (TAILQ_EMPTY(&sc->all_levels)) { struct cf_setting set; CF_DEBUG("No absolute levels returned by driver\n"); if (sc->max_mhz == CPUFREQ_VAL_UNKNOWN) { sc->max_mhz = cpu_get_nominal_mhz(dev); /* * If the CPU can't report a rate for 100%, hope * the CPU is running at its nominal rate right now, * and use that instead. */ if (sc->max_mhz <= 0) { pc = cpu_get_pcpu(dev); cpu_est_clockrate(pc->pc_cpuid, &rate); sc->max_mhz = rate / 1000000; } } memset(&set, CPUFREQ_VAL_UNKNOWN, sizeof(set)); set.freq = sc->max_mhz; set.dev = NULL; error = cpufreq_insert_abs(sc, &set, 1); if (error) goto out; } /* Create a combined list of absolute + relative levels. */ TAILQ_FOREACH(set_arr, &rel_sets, link) cpufreq_expand_set(sc, set_arr); /* If the caller doesn't have enough space, return the actual count. */ if (sc->all_count > *count) { *count = sc->all_count; error = E2BIG; goto out; } /* Finally, output the list of levels. */ i = 0; TAILQ_FOREACH(lev, &sc->all_levels, link) { /* Skip levels that have a frequency that is too low. */ if (lev->total_set.freq < cf_lowest_freq) { sc->all_count--; continue; } levels[i] = *lev; i++; } *count = sc->all_count; error = 0; out: /* Clear all levels since we regenerate them each time. */ while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) { TAILQ_REMOVE(&sc->all_levels, lev, link); free(lev, M_TEMP); } sc->all_count = 0; CF_MTX_UNLOCK(&sc->lock); while ((set_arr = TAILQ_FIRST(&rel_sets)) != NULL) { TAILQ_REMOVE(&rel_sets, set_arr, link); free(set_arr, M_TEMP); } return (error); } /* * Create levels for an array of absolute settings and insert them in * sorted order in the specified list. */ static int cpufreq_insert_abs(struct cpufreq_softc *sc, struct cf_setting *sets, int count) { struct cf_level_lst *list; struct cf_level *level, *search; int i, inserted; CF_MTX_ASSERT(&sc->lock); list = &sc->all_levels; for (i = 0; i < count; i++) { level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO); if (level == NULL) return (ENOMEM); level->abs_set = sets[i]; level->total_set = sets[i]; level->total_set.dev = NULL; sc->all_count++; inserted = 0; if (TAILQ_EMPTY(list)) { CF_DEBUG("adding abs setting %d at head\n", sets[i].freq); TAILQ_INSERT_HEAD(list, level, link); continue; } TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) if (sets[i].freq <= search->total_set.freq) { CF_DEBUG("adding abs setting %d after %d\n", sets[i].freq, search->total_set.freq); TAILQ_INSERT_AFTER(list, search, level, link); inserted = 1; break; } if (inserted == 0) { TAILQ_FOREACH(search, list, link) if (sets[i].freq >= search->total_set.freq) { CF_DEBUG("adding abs setting %d before %d\n", sets[i].freq, search->total_set.freq); TAILQ_INSERT_BEFORE(search, level, link); break; } } } return (0); } /* * Expand a group of relative settings, creating derived levels from them. */ static int cpufreq_expand_set(struct cpufreq_softc *sc, struct cf_setting_array *set_arr) { struct cf_level *fill, *search; struct cf_setting *set; int i; CF_MTX_ASSERT(&sc->lock); /* * Walk the set of all existing levels in reverse. This is so we * create derived states from the lowest absolute settings first * and discard duplicates created from higher absolute settings. * For instance, a level of 50 Mhz derived from 100 Mhz + 50% is * preferable to 200 Mhz + 25% because absolute settings are more * efficient since they often change the voltage as well. */ TAILQ_FOREACH_REVERSE(search, &sc->all_levels, cf_level_lst, link) { /* Add each setting to the level, duplicating if necessary. */ for (i = 0; i < set_arr->count; i++) { set = &set_arr->sets[i]; /* * If this setting is less than 100%, split the level * into two and add this setting to the new level. */ fill = search; if (set->freq < 10000) { fill = cpufreq_dup_set(sc, search, set); /* * The new level was a duplicate of an existing * level or its absolute setting is too high * so we freed it. For example, we discard a * derived level of 1000 MHz/25% if a level * of 500 MHz/100% already exists. */ if (fill == NULL) break; } /* Add this setting to the existing or new level. */ KASSERT(fill->rel_count < MAX_SETTINGS, ("cpufreq: too many relative drivers (%d)", MAX_SETTINGS)); fill->rel_set[fill->rel_count] = *set; fill->rel_count++; CF_DEBUG( "expand set added rel setting %d%% to %d level\n", set->freq / 100, fill->total_set.freq); } } return (0); } static struct cf_level * cpufreq_dup_set(struct cpufreq_softc *sc, struct cf_level *dup, struct cf_setting *set) { struct cf_level_lst *list; struct cf_level *fill, *itr; struct cf_setting *fill_set, *itr_set; int i; CF_MTX_ASSERT(&sc->lock); /* * Create a new level, copy it from the old one, and update the * total frequency and power by the percentage specified in the * relative setting. */ fill = malloc(sizeof(*fill), M_TEMP, M_NOWAIT); if (fill == NULL) return (NULL); *fill = *dup; fill_set = &fill->total_set; fill_set->freq = ((uint64_t)fill_set->freq * set->freq) / 10000; if (fill_set->power != CPUFREQ_VAL_UNKNOWN) { fill_set->power = ((uint64_t)fill_set->power * set->freq) / 10000; } if (set->lat != CPUFREQ_VAL_UNKNOWN) { if (fill_set->lat != CPUFREQ_VAL_UNKNOWN) fill_set->lat += set->lat; else fill_set->lat = set->lat; } CF_DEBUG("dup set considering derived setting %d\n", fill_set->freq); /* * If we copied an old level that we already modified (say, at 100%), * we need to remove that setting before adding this one. Since we * process each setting array in order, we know any settings for this * driver will be found at the end. */ for (i = fill->rel_count; i != 0; i--) { if (fill->rel_set[i - 1].dev != set->dev) break; CF_DEBUG("removed last relative driver: %s\n", device_get_nameunit(set->dev)); fill->rel_count--; } /* * Insert the new level in sorted order. If it is a duplicate of an * existing level (1) or has an absolute setting higher than the * existing level (2), do not add it. We can do this since any such * level is guaranteed use less power. For example (1), a level with * one absolute setting of 800 Mhz uses less power than one composed * of an absolute setting of 1600 Mhz and a relative setting at 50%. * Also for example (2), a level of 800 Mhz/75% is preferable to * 1600 Mhz/25% even though the latter has a lower total frequency. */ list = &sc->all_levels; KASSERT(!TAILQ_EMPTY(list), ("all levels list empty in dup set")); TAILQ_FOREACH_REVERSE(itr, list, cf_level_lst, link) { itr_set = &itr->total_set; if (CPUFREQ_CMP(fill_set->freq, itr_set->freq)) { CF_DEBUG("dup set rejecting %d (dupe)\n", fill_set->freq); itr = NULL; break; } else if (fill_set->freq < itr_set->freq) { if (fill->abs_set.freq <= itr->abs_set.freq) { CF_DEBUG( "dup done, inserting new level %d after %d\n", fill_set->freq, itr_set->freq); TAILQ_INSERT_AFTER(list, itr, fill, link); sc->all_count++; } else { CF_DEBUG("dup set rejecting %d (abs too big)\n", fill_set->freq); itr = NULL; } break; } } /* We didn't find a good place for this new level so free it. */ if (itr == NULL) { CF_DEBUG("dup set freeing new level %d (not optimal)\n", fill_set->freq); free(fill, M_TEMP); fill = NULL; } return (fill); } static int cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS) { struct cpufreq_softc *sc; struct cf_level *levels; int best, count, diff, bdiff, devcount, error, freq, i, n; device_t *devs; devs = NULL; sc = oidp->oid_arg1; levels = sc->levels_buf; error = CPUFREQ_GET(sc->dev, &levels[0]); if (error) goto out; freq = levels[0].total_set.freq; error = sysctl_handle_int(oidp, &freq, 0, req); if (error != 0 || req->newptr == NULL) goto out; /* * While we only call cpufreq_get() on one device (assuming all * CPUs have equal levels), we call cpufreq_set() on all CPUs. * This is needed for some MP systems. */ error = devclass_get_devices(devclass_find("cpufreq"), &devs, &devcount); if (error) goto out; for (n = 0; n < devcount; n++) { count = CF_MAX_LEVELS; error = CPUFREQ_LEVELS(devs[n], levels, &count); if (error) { if (error == E2BIG) printf( "cpufreq: need to increase CF_MAX_LEVELS\n"); break; } best = 0; bdiff = 1 << 30; for (i = 0; i < count; i++) { diff = abs(levels[i].total_set.freq - freq); if (diff < bdiff) { bdiff = diff; best = i; } } error = CPUFREQ_SET(devs[n], &levels[best], CPUFREQ_PRIO_USER); } out: if (devs) free(devs, M_TEMP); return (error); } static int cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS) { struct cpufreq_softc *sc; struct cf_level *levels; struct cf_setting *set; struct sbuf sb; int count, error, i; sc = oidp->oid_arg1; sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); /* Get settings from the device and generate the output string. */ count = CF_MAX_LEVELS; levels = sc->levels_buf; if (levels == NULL) { sbuf_delete(&sb); return (ENOMEM); } error = CPUFREQ_LEVELS(sc->dev, levels, &count); if (error) { if (error == E2BIG) printf("cpufreq: need to increase CF_MAX_LEVELS\n"); goto out; } if (count) { for (i = 0; i < count; i++) { set = &levels[i].total_set; sbuf_printf(&sb, "%d/%d ", set->freq, set->power); } } else sbuf_cpy(&sb, "0"); sbuf_trim(&sb); sbuf_finish(&sb); error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); out: sbuf_delete(&sb); return (error); } static int cpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; struct cf_setting *sets; struct sbuf sb; int error, i, set_count; dev = oidp->oid_arg1; sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); /* Get settings from the device and generate the output string. */ set_count = MAX_SETTINGS; sets = malloc(set_count * sizeof(*sets), M_TEMP, M_NOWAIT); if (sets == NULL) { sbuf_delete(&sb); return (ENOMEM); } error = CPUFREQ_DRV_SETTINGS(dev, sets, &set_count); if (error) goto out; if (set_count) { for (i = 0; i < set_count; i++) sbuf_printf(&sb, "%d/%d ", sets[i].freq, sets[i].power); } else sbuf_cpy(&sb, "0"); sbuf_trim(&sb); sbuf_finish(&sb); error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); out: free(sets, M_TEMP); sbuf_delete(&sb); return (error); } static void cpufreq_add_freq_driver_sysctl(device_t cf_dev) { struct cpufreq_softc *sc; sc = device_get_softc(cf_dev); SYSCTL_ADD_CONST_STRING(&sc->sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(cf_dev)), OID_AUTO, "freq_driver", CTLFLAG_RD, device_get_nameunit(sc->cf_drv_dev), "cpufreq driver used by this cpu"); } int cpufreq_register(device_t dev) { struct cpufreq_softc *sc; device_t cf_dev, cpu_dev; int error; /* Add a sysctl to get each driver's settings separately. */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "freq_settings", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT, dev, 0, cpufreq_settings_sysctl, "A", "CPU frequency driver settings"); /* * Add only one cpufreq device to each CPU. Currently, all CPUs * must offer the same levels and be switched at the same time. */ cpu_dev = device_get_parent(dev); - if ((cf_dev = device_find_child(cpu_dev, "cpufreq", -1))) { + if ((cf_dev = device_find_child(cpu_dev, "cpufreq", DEVICE_UNIT_ANY))) { sc = device_get_softc(cf_dev); sc->max_mhz = CPUFREQ_VAL_UNKNOWN; MPASS(sc->cf_drv_dev != NULL); return (0); } /* Add the child device and possibly sysctls. */ cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", device_get_unit(cpu_dev)); if (cf_dev == NULL) return (ENOMEM); device_quiet(cf_dev); error = device_probe_and_attach(cf_dev); if (error) return (error); sc = device_get_softc(cf_dev); sc->cf_drv_dev = dev; cpufreq_add_freq_driver_sysctl(cf_dev); return (error); } int cpufreq_unregister(device_t dev) { device_t cf_dev; struct cpufreq_softc *sc __diagused; /* * If this is the last cpufreq child device, remove the control * device as well. We identify cpufreq children by calling a method * they support. */ - cf_dev = device_find_child(device_get_parent(dev), "cpufreq", -1); + cf_dev = device_find_child(device_get_parent(dev), "cpufreq", + DEVICE_UNIT_ANY); if (cf_dev == NULL) { device_printf(dev, "warning: cpufreq_unregister called with no cpufreq device active\n"); return (0); } sc = device_get_softc(cf_dev); MPASS(sc->cf_drv_dev == dev); device_delete_child(device_get_parent(cf_dev), cf_dev); return (0); } int cpufreq_settings_changed(device_t dev) { EVENTHANDLER_INVOKE(cpufreq_levels_changed, device_get_unit(device_get_parent(dev))); return (0); } diff --git a/sys/opencrypto/cryptosoft.c b/sys/opencrypto/cryptosoft.c index b4634bed07c2..aeea3fe665a1 100644 --- a/sys/opencrypto/cryptosoft.c +++ b/sys/opencrypto/cryptosoft.c @@ -1,1754 +1,1754 @@ /* $OpenBSD: cryptosoft.c,v 1.35 2002/04/26 08:43:50 deraadt Exp $ */ /*- * The author of this code is Angelos D. Keromytis (angelos@cis.upenn.edu) * Copyright (c) 2002-2006 Sam Leffler, Errno Consulting * * This code was written by Angelos D. Keromytis in Athens, Greece, in * February 2000. Network Security Technologies Inc. (NSTI) kindly * supported the development of this code. * * Copyright (c) 2000, 2001 Angelos D. Keromytis * Copyright (c) 2014-2021 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by John-Mark Gurney * under sponsorship of the FreeBSD Foundation and * Rubicon Communications, LLC (Netgate). * * Portions of this software were developed by Ararat River * Consulting, LLC under sponsorship of the FreeBSD Foundation. * * Permission to use, copy, and modify this software with or without fee * is hereby granted, provided that this entire notice is included in * all source code copies of any software which is or includes a copy or * modification of this software. * * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR * PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cryptodev_if.h" struct swcr_auth { void *sw_ictx; void *sw_octx; const struct auth_hash *sw_axf; uint16_t sw_mlen; bool sw_hmac; }; struct swcr_encdec { void *sw_ctx; const struct enc_xform *sw_exf; }; struct swcr_compdec { const struct comp_algo *sw_cxf; }; struct swcr_session { int (*swcr_process)(const struct swcr_session *, struct cryptop *); struct swcr_auth swcr_auth; struct swcr_encdec swcr_encdec; struct swcr_compdec swcr_compdec; }; static int32_t swcr_id; static void swcr_freesession(device_t dev, crypto_session_t cses); /* Used for CRYPTO_NULL_CBC. */ static int swcr_null(const struct swcr_session *ses, struct cryptop *crp) { return (0); } /* * Apply a symmetric encryption/decryption algorithm. */ static int swcr_encdec(const struct swcr_session *ses, struct cryptop *crp) { unsigned char blk[EALG_MAX_BLOCK_LEN]; const struct crypto_session_params *csp; const struct enc_xform *exf; const struct swcr_encdec *sw; void *ctx; size_t inlen, outlen, todo; int blksz, resid; struct crypto_buffer_cursor cc_in, cc_out; const unsigned char *inblk; unsigned char *outblk; int error; bool encrypting; error = 0; sw = &ses->swcr_encdec; exf = sw->sw_exf; csp = crypto_get_params(crp->crp_session); if (exf->native_blocksize == 0) { /* Check for non-padded data */ if ((crp->crp_payload_length % exf->blocksize) != 0) return (EINVAL); blksz = exf->blocksize; } else blksz = exf->native_blocksize; if (exf == &enc_xform_aes_icm && (crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0) return (EINVAL); ctx = __builtin_alloca(exf->ctxsize); if (crp->crp_cipher_key != NULL) { error = exf->setkey(ctx, crp->crp_cipher_key, csp->csp_cipher_klen); if (error) return (error); } else memcpy(ctx, sw->sw_ctx, exf->ctxsize); crypto_read_iv(crp, blk); exf->reinit(ctx, blk, csp->csp_ivlen); crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_payload_start); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) { crypto_cursor_init(&cc_out, &crp->crp_obuf); crypto_cursor_advance(&cc_out, crp->crp_payload_output_start); } else cc_out = cc_in; encrypting = CRYPTO_OP_IS_ENCRYPT(crp->crp_op); /* * Loop through encrypting blocks. 'inlen' is the remaining * length of the current segment in the input buffer. * 'outlen' is the remaining length of current segment in the * output buffer. */ inlen = outlen = 0; for (resid = crp->crp_payload_length; resid >= blksz; resid -= todo) { if (inlen == 0) inblk = crypto_cursor_segment(&cc_in, &inlen); if (outlen == 0) outblk = crypto_cursor_segment(&cc_out, &outlen); /* * If the current block is not contained within the * current input/output segment, use 'blk' as a local * buffer. */ if (inlen < blksz) { crypto_cursor_copydata(&cc_in, blksz, blk); inblk = blk; inlen = blksz; } if (outlen < blksz) { outblk = blk; outlen = blksz; } todo = rounddown2(MIN(resid, MIN(inlen, outlen)), blksz); if (encrypting) exf->encrypt_multi(ctx, inblk, outblk, todo); else exf->decrypt_multi(ctx, inblk, outblk, todo); if (inblk == blk) { inblk = crypto_cursor_segment(&cc_in, &inlen); } else { crypto_cursor_advance(&cc_in, todo); inlen -= todo; inblk += todo; } if (outblk == blk) { crypto_cursor_copyback(&cc_out, blksz, blk); outblk = crypto_cursor_segment(&cc_out, &outlen); } else { crypto_cursor_advance(&cc_out, todo); outlen -= todo; outblk += todo; } } /* Handle trailing partial block for stream ciphers. */ if (resid > 0) { KASSERT(exf->native_blocksize != 0, ("%s: partial block of %d bytes for cipher %s", __func__, resid, exf->name)); KASSERT(resid < blksz, ("%s: partial block too big", __func__)); inblk = crypto_cursor_segment(&cc_in, &inlen); outblk = crypto_cursor_segment(&cc_out, &outlen); if (inlen < resid) { crypto_cursor_copydata(&cc_in, resid, blk); inblk = blk; } if (outlen < resid) outblk = blk; if (encrypting) exf->encrypt_last(ctx, inblk, outblk, resid); else exf->decrypt_last(ctx, inblk, outblk, resid); if (outlen < resid) crypto_cursor_copyback(&cc_out, resid, blk); } explicit_bzero(ctx, exf->ctxsize); explicit_bzero(blk, sizeof(blk)); return (0); } /* * Compute or verify hash. */ static int swcr_authcompute(const struct swcr_session *ses, struct cryptop *crp) { struct { union authctx ctx; u_char aalg[HASH_MAX_LEN]; u_char uaalg[HASH_MAX_LEN]; } s; const struct crypto_session_params *csp; const struct swcr_auth *sw; const struct auth_hash *axf; int err; sw = &ses->swcr_auth; axf = sw->sw_axf; csp = crypto_get_params(crp->crp_session); if (crp->crp_auth_key != NULL) { if (sw->sw_hmac) { hmac_init_ipad(axf, crp->crp_auth_key, csp->csp_auth_klen, &s.ctx); } else { axf->Init(&s.ctx); axf->Setkey(&s.ctx, crp->crp_auth_key, csp->csp_auth_klen); } } else memcpy(&s.ctx, sw->sw_ictx, axf->ctxsize); if (crp->crp_aad != NULL) err = axf->Update(&s.ctx, crp->crp_aad, crp->crp_aad_length); else err = crypto_apply(crp, crp->crp_aad_start, crp->crp_aad_length, axf->Update, &s.ctx); if (err) goto out; if (CRYPTO_HAS_OUTPUT_BUFFER(crp) && CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) err = crypto_apply_buf(&crp->crp_obuf, crp->crp_payload_output_start, crp->crp_payload_length, axf->Update, &s.ctx); else err = crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, axf->Update, &s.ctx); if (err) goto out; if (csp->csp_flags & CSP_F_ESN) axf->Update(&s.ctx, crp->crp_esn, 4); axf->Final(s.aalg, &s.ctx); if (sw->sw_hmac) { if (crp->crp_auth_key != NULL) hmac_init_opad(axf, crp->crp_auth_key, csp->csp_auth_klen, &s.ctx); else memcpy(&s.ctx, sw->sw_octx, axf->ctxsize); axf->Update(&s.ctx, s.aalg, axf->hashsize); axf->Final(s.aalg, &s.ctx); } if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) { crypto_copydata(crp, crp->crp_digest_start, sw->sw_mlen, s.uaalg); if (timingsafe_bcmp(s.aalg, s.uaalg, sw->sw_mlen) != 0) err = EBADMSG; } else { /* Inject the authentication data */ crypto_copyback(crp, crp->crp_digest_start, sw->sw_mlen, s.aalg); } out: explicit_bzero(&s, sizeof(s)); return (err); } CTASSERT(INT_MAX <= (1ll<<39) - 256); /* GCM: plain text < 2^39-256 */ CTASSERT(INT_MAX <= (uint64_t)-1); /* GCM: associated data <= 2^64-1 */ static int swcr_gmac(const struct swcr_session *ses, struct cryptop *crp) { struct { union authctx ctx; uint32_t blkbuf[howmany(AES_BLOCK_LEN, sizeof(uint32_t))]; u_char tag[GMAC_DIGEST_LEN]; u_char tag2[GMAC_DIGEST_LEN]; } s; u_char *blk = (u_char *)s.blkbuf; struct crypto_buffer_cursor cc; const u_char *inblk; const struct swcr_auth *swa; const struct auth_hash *axf; uint32_t *blkp; size_t len; int blksz, error, ivlen, resid; swa = &ses->swcr_auth; axf = swa->sw_axf; blksz = GMAC_BLOCK_LEN; KASSERT(axf->blocksize == blksz, ("%s: axf block size mismatch", __func__)); if (crp->crp_auth_key != NULL) { axf->Init(&s.ctx); axf->Setkey(&s.ctx, crp->crp_auth_key, crypto_get_params(crp->crp_session)->csp_auth_klen); } else memcpy(&s.ctx, swa->sw_ictx, axf->ctxsize); /* Initialize the IV */ ivlen = AES_GCM_IV_LEN; crypto_read_iv(crp, blk); axf->Reinit(&s.ctx, blk, ivlen); crypto_cursor_init(&cc, &crp->crp_buf); crypto_cursor_advance(&cc, crp->crp_payload_start); for (resid = crp->crp_payload_length; resid >= blksz; resid -= len) { inblk = crypto_cursor_segment(&cc, &len); if (len >= blksz) { len = rounddown(MIN(len, resid), blksz); crypto_cursor_advance(&cc, len); } else { len = blksz; crypto_cursor_copydata(&cc, len, blk); inblk = blk; } axf->Update(&s.ctx, inblk, len); } if (resid > 0) { memset(blk, 0, blksz); crypto_cursor_copydata(&cc, resid, blk); axf->Update(&s.ctx, blk, blksz); } /* length block */ memset(blk, 0, blksz); blkp = (uint32_t *)blk + 1; *blkp = htobe32(crp->crp_payload_length * 8); axf->Update(&s.ctx, blk, blksz); /* Finalize MAC */ axf->Final(s.tag, &s.ctx); error = 0; if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) { crypto_copydata(crp, crp->crp_digest_start, swa->sw_mlen, s.tag2); if (timingsafe_bcmp(s.tag, s.tag2, swa->sw_mlen) != 0) error = EBADMSG; } else { /* Inject the authentication data */ crypto_copyback(crp, crp->crp_digest_start, swa->sw_mlen, s.tag); } explicit_bzero(&s, sizeof(s)); return (error); } static int swcr_gcm(const struct swcr_session *ses, struct cryptop *crp) { struct { uint32_t blkbuf[howmany(AES_BLOCK_LEN, sizeof(uint32_t))]; u_char tag[GMAC_DIGEST_LEN]; u_char tag2[GMAC_DIGEST_LEN]; } s; u_char *blk = (u_char *)s.blkbuf; struct crypto_buffer_cursor cc_in, cc_out; const u_char *inblk; u_char *outblk; size_t inlen, outlen, todo; const struct swcr_auth *swa; const struct swcr_encdec *swe; const struct enc_xform *exf; void *ctx; uint32_t *blkp; int blksz, error, ivlen, r, resid; swa = &ses->swcr_auth; swe = &ses->swcr_encdec; exf = swe->sw_exf; blksz = GMAC_BLOCK_LEN; KASSERT(blksz == exf->native_blocksize, ("%s: blocksize mismatch", __func__)); if ((crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0) return (EINVAL); ivlen = AES_GCM_IV_LEN; ctx = __builtin_alloca(exf->ctxsize); if (crp->crp_cipher_key != NULL) exf->setkey(ctx, crp->crp_cipher_key, crypto_get_params(crp->crp_session)->csp_cipher_klen); else memcpy(ctx, swe->sw_ctx, exf->ctxsize); exf->reinit(ctx, crp->crp_iv, ivlen); /* Supply MAC with AAD */ if (crp->crp_aad != NULL) { inlen = rounddown2(crp->crp_aad_length, blksz); if (inlen != 0) exf->update(ctx, crp->crp_aad, inlen); if (crp->crp_aad_length != inlen) { memset(blk, 0, blksz); memcpy(blk, (char *)crp->crp_aad + inlen, crp->crp_aad_length - inlen); exf->update(ctx, blk, blksz); } } else { crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_aad_start); for (resid = crp->crp_aad_length; resid >= blksz; resid -= inlen) { inblk = crypto_cursor_segment(&cc_in, &inlen); if (inlen >= blksz) { inlen = rounddown2(MIN(inlen, resid), blksz); crypto_cursor_advance(&cc_in, inlen); } else { inlen = blksz; crypto_cursor_copydata(&cc_in, inlen, blk); inblk = blk; } exf->update(ctx, inblk, inlen); } if (resid > 0) { memset(blk, 0, blksz); crypto_cursor_copydata(&cc_in, resid, blk); exf->update(ctx, blk, blksz); } } /* Do encryption with MAC */ crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_payload_start); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) { crypto_cursor_init(&cc_out, &crp->crp_obuf); crypto_cursor_advance(&cc_out, crp->crp_payload_output_start); } else cc_out = cc_in; inlen = outlen = 0; for (resid = crp->crp_payload_length; resid >= blksz; resid -= todo) { if (inlen == 0) inblk = crypto_cursor_segment(&cc_in, &inlen); if (outlen == 0) outblk = crypto_cursor_segment(&cc_out, &outlen); if (inlen < blksz) { crypto_cursor_copydata(&cc_in, blksz, blk); inblk = blk; inlen = blksz; } if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { if (outlen < blksz) { outblk = blk; outlen = blksz; } todo = rounddown2(MIN(resid, MIN(inlen, outlen)), blksz); exf->encrypt_multi(ctx, inblk, outblk, todo); exf->update(ctx, outblk, todo); if (outblk == blk) { crypto_cursor_copyback(&cc_out, blksz, blk); outblk = crypto_cursor_segment(&cc_out, &outlen); } else { crypto_cursor_advance(&cc_out, todo); outlen -= todo; outblk += todo; } } else { todo = rounddown2(MIN(resid, inlen), blksz); exf->update(ctx, inblk, todo); } if (inblk == blk) { inblk = crypto_cursor_segment(&cc_in, &inlen); } else { crypto_cursor_advance(&cc_in, todo); inlen -= todo; inblk += todo; } } if (resid > 0) { crypto_cursor_copydata(&cc_in, resid, blk); if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { exf->encrypt_last(ctx, blk, blk, resid); crypto_cursor_copyback(&cc_out, resid, blk); } exf->update(ctx, blk, resid); } /* length block */ memset(blk, 0, blksz); blkp = (uint32_t *)blk + 1; *blkp = htobe32(crp->crp_aad_length * 8); blkp = (uint32_t *)blk + 3; *blkp = htobe32(crp->crp_payload_length * 8); exf->update(ctx, blk, blksz); /* Finalize MAC */ exf->final(s.tag, ctx); /* Validate tag */ error = 0; if (!CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { crypto_copydata(crp, crp->crp_digest_start, swa->sw_mlen, s.tag2); r = timingsafe_bcmp(s.tag, s.tag2, swa->sw_mlen); if (r != 0) { error = EBADMSG; goto out; } /* tag matches, decrypt data */ crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_payload_start); inlen = 0; for (resid = crp->crp_payload_length; resid > blksz; resid -= todo) { if (inlen == 0) inblk = crypto_cursor_segment(&cc_in, &inlen); if (outlen == 0) outblk = crypto_cursor_segment(&cc_out, &outlen); if (inlen < blksz) { crypto_cursor_copydata(&cc_in, blksz, blk); inblk = blk; inlen = blksz; } if (outlen < blksz) { outblk = blk; outlen = blksz; } todo = rounddown2(MIN(resid, MIN(inlen, outlen)), blksz); exf->decrypt_multi(ctx, inblk, outblk, todo); if (inblk == blk) { inblk = crypto_cursor_segment(&cc_in, &inlen); } else { crypto_cursor_advance(&cc_in, todo); inlen -= todo; inblk += todo; } if (outblk == blk) { crypto_cursor_copyback(&cc_out, blksz, blk); outblk = crypto_cursor_segment(&cc_out, &outlen); } else { crypto_cursor_advance(&cc_out, todo); outlen -= todo; outblk += todo; } } if (resid > 0) { crypto_cursor_copydata(&cc_in, resid, blk); exf->decrypt_last(ctx, blk, blk, resid); crypto_cursor_copyback(&cc_out, resid, blk); } } else { /* Inject the authentication data */ crypto_copyback(crp, crp->crp_digest_start, swa->sw_mlen, s.tag); } out: explicit_bzero(ctx, exf->ctxsize); explicit_bzero(&s, sizeof(s)); return (error); } static void build_ccm_b0(const char *nonce, u_int nonce_length, u_int aad_length, u_int data_length, u_int tag_length, uint8_t *b0) { uint8_t *bp; uint8_t flags, L; KASSERT(nonce_length >= 7 && nonce_length <= 13, ("nonce_length must be between 7 and 13 bytes")); /* * Need to determine the L field value. This is the number of * bytes needed to specify the length of the message; the length * is whatever is left in the 16 bytes after specifying flags and * the nonce. */ L = 15 - nonce_length; flags = ((aad_length > 0) << 6) + (((tag_length - 2) / 2) << 3) + L - 1; /* * Now we need to set up the first block, which has flags, nonce, * and the message length. */ b0[0] = flags; memcpy(b0 + 1, nonce, nonce_length); bp = b0 + 1 + nonce_length; /* Need to copy L' [aka L-1] bytes of data_length */ for (uint8_t *dst = b0 + CCM_CBC_BLOCK_LEN - 1; dst >= bp; dst--) { *dst = data_length; data_length >>= 8; } } /* NB: OCF only supports AAD lengths < 2^32. */ static int build_ccm_aad_length(u_int aad_length, uint8_t *blk) { if (aad_length < ((1 << 16) - (1 << 8))) { be16enc(blk, aad_length); return (sizeof(uint16_t)); } else { blk[0] = 0xff; blk[1] = 0xfe; be32enc(blk + 2, aad_length); return (2 + sizeof(uint32_t)); } } static int swcr_ccm_cbc_mac(const struct swcr_session *ses, struct cryptop *crp) { struct { union authctx ctx; u_char blk[CCM_CBC_BLOCK_LEN]; u_char tag[AES_CBC_MAC_HASH_LEN]; u_char tag2[AES_CBC_MAC_HASH_LEN]; } s; const struct crypto_session_params *csp; const struct swcr_auth *swa; const struct auth_hash *axf; int error, ivlen, len; csp = crypto_get_params(crp->crp_session); swa = &ses->swcr_auth; axf = swa->sw_axf; if (crp->crp_auth_key != NULL) { axf->Init(&s.ctx); axf->Setkey(&s.ctx, crp->crp_auth_key, csp->csp_auth_klen); } else memcpy(&s.ctx, swa->sw_ictx, axf->ctxsize); /* Initialize the IV */ ivlen = csp->csp_ivlen; /* Supply MAC with IV */ axf->Reinit(&s.ctx, crp->crp_iv, ivlen); /* Supply MAC with b0. */ build_ccm_b0(crp->crp_iv, ivlen, crp->crp_payload_length, 0, swa->sw_mlen, s.blk); axf->Update(&s.ctx, s.blk, CCM_CBC_BLOCK_LEN); len = build_ccm_aad_length(crp->crp_payload_length, s.blk); axf->Update(&s.ctx, s.blk, len); crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, axf->Update, &s.ctx); /* Finalize MAC */ axf->Final(s.tag, &s.ctx); error = 0; if (crp->crp_op & CRYPTO_OP_VERIFY_DIGEST) { crypto_copydata(crp, crp->crp_digest_start, swa->sw_mlen, s.tag2); if (timingsafe_bcmp(s.tag, s.tag2, swa->sw_mlen) != 0) error = EBADMSG; } else { /* Inject the authentication data */ crypto_copyback(crp, crp->crp_digest_start, swa->sw_mlen, s.tag); } explicit_bzero(&s, sizeof(s)); return (error); } static int swcr_ccm(const struct swcr_session *ses, struct cryptop *crp) { const struct crypto_session_params *csp; struct { uint32_t blkbuf[howmany(AES_BLOCK_LEN, sizeof(uint32_t))]; u_char tag[AES_CBC_MAC_HASH_LEN]; u_char tag2[AES_CBC_MAC_HASH_LEN]; } s; u_char *blk = (u_char *)s.blkbuf; struct crypto_buffer_cursor cc_in, cc_out; const u_char *inblk; u_char *outblk; size_t inlen, outlen, todo; const struct swcr_auth *swa; const struct swcr_encdec *swe; const struct enc_xform *exf; void *ctx; size_t len; int blksz, error, ivlen, r, resid; csp = crypto_get_params(crp->crp_session); swa = &ses->swcr_auth; swe = &ses->swcr_encdec; exf = swe->sw_exf; blksz = AES_BLOCK_LEN; KASSERT(blksz == exf->native_blocksize, ("%s: blocksize mismatch", __func__)); if (crp->crp_payload_length > ccm_max_payload_length(csp)) return (EMSGSIZE); if ((crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0) return (EINVAL); ivlen = csp->csp_ivlen; ctx = __builtin_alloca(exf->ctxsize); if (crp->crp_cipher_key != NULL) exf->setkey(ctx, crp->crp_cipher_key, crypto_get_params(crp->crp_session)->csp_cipher_klen); else memcpy(ctx, swe->sw_ctx, exf->ctxsize); exf->reinit(ctx, crp->crp_iv, ivlen); /* Supply MAC with b0. */ _Static_assert(sizeof(s.blkbuf) >= CCM_CBC_BLOCK_LEN, "blkbuf too small for b0"); build_ccm_b0(crp->crp_iv, ivlen, crp->crp_aad_length, crp->crp_payload_length, swa->sw_mlen, blk); exf->update(ctx, blk, CCM_CBC_BLOCK_LEN); /* Supply MAC with AAD */ if (crp->crp_aad_length != 0) { len = build_ccm_aad_length(crp->crp_aad_length, blk); exf->update(ctx, blk, len); if (crp->crp_aad != NULL) exf->update(ctx, crp->crp_aad, crp->crp_aad_length); else crypto_apply(crp, crp->crp_aad_start, crp->crp_aad_length, exf->update, ctx); /* Pad the AAD (including length field) to a full block. */ len = (len + crp->crp_aad_length) % CCM_CBC_BLOCK_LEN; if (len != 0) { len = CCM_CBC_BLOCK_LEN - len; memset(blk, 0, CCM_CBC_BLOCK_LEN); exf->update(ctx, blk, len); } } /* Do encryption/decryption with MAC */ crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_payload_start); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) { crypto_cursor_init(&cc_out, &crp->crp_obuf); crypto_cursor_advance(&cc_out, crp->crp_payload_output_start); } else cc_out = cc_in; inlen = outlen = 0; for (resid = crp->crp_payload_length; resid >= blksz; resid -= todo) { if (inlen == 0) inblk = crypto_cursor_segment(&cc_in, &inlen); if (outlen == 0) outblk = crypto_cursor_segment(&cc_out, &outlen); if (inlen < blksz) { crypto_cursor_copydata(&cc_in, blksz, blk); inblk = blk; inlen = blksz; } if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { if (outlen < blksz) { outblk = blk; outlen = blksz; } todo = rounddown2(MIN(resid, MIN(inlen, outlen)), blksz); exf->update(ctx, inblk, todo); exf->encrypt_multi(ctx, inblk, outblk, todo); if (outblk == blk) { crypto_cursor_copyback(&cc_out, blksz, blk); outblk = crypto_cursor_segment(&cc_out, &outlen); } else { crypto_cursor_advance(&cc_out, todo); outlen -= todo; outblk += todo; } } else { /* * One of the problems with CCM+CBC is that * the authentication is done on the * unencrypted data. As a result, we have to * decrypt the data twice: once to generate * the tag and a second time after the tag is * verified. */ todo = blksz; exf->decrypt(ctx, inblk, blk); exf->update(ctx, blk, todo); } if (inblk == blk) { inblk = crypto_cursor_segment(&cc_in, &inlen); } else { crypto_cursor_advance(&cc_in, todo); inlen -= todo; inblk += todo; } } if (resid > 0) { crypto_cursor_copydata(&cc_in, resid, blk); if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { exf->update(ctx, blk, resid); exf->encrypt_last(ctx, blk, blk, resid); crypto_cursor_copyback(&cc_out, resid, blk); } else { exf->decrypt_last(ctx, blk, blk, resid); exf->update(ctx, blk, resid); } } /* Finalize MAC */ exf->final(s.tag, ctx); /* Validate tag */ error = 0; if (!CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { crypto_copydata(crp, crp->crp_digest_start, swa->sw_mlen, s.tag2); r = timingsafe_bcmp(s.tag, s.tag2, swa->sw_mlen); if (r != 0) { error = EBADMSG; goto out; } /* tag matches, decrypt data */ exf->reinit(ctx, crp->crp_iv, ivlen); crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_payload_start); inlen = 0; for (resid = crp->crp_payload_length; resid >= blksz; resid -= todo) { if (inlen == 0) inblk = crypto_cursor_segment(&cc_in, &inlen); if (outlen == 0) outblk = crypto_cursor_segment(&cc_out, &outlen); if (inlen < blksz) { crypto_cursor_copydata(&cc_in, blksz, blk); inblk = blk; inlen = blksz; } if (outlen < blksz) { outblk = blk; outlen = blksz; } todo = rounddown2(MIN(resid, MIN(inlen, outlen)), blksz); exf->decrypt_multi(ctx, inblk, outblk, todo); if (inblk == blk) { inblk = crypto_cursor_segment(&cc_in, &inlen); } else { crypto_cursor_advance(&cc_in, todo); inlen -= todo; inblk += todo; } if (outblk == blk) { crypto_cursor_copyback(&cc_out, blksz, blk); outblk = crypto_cursor_segment(&cc_out, &outlen); } else { crypto_cursor_advance(&cc_out, todo); outlen -= todo; outblk += todo; } } if (resid > 0) { crypto_cursor_copydata(&cc_in, resid, blk); exf->decrypt_last(ctx, blk, blk, resid); crypto_cursor_copyback(&cc_out, resid, blk); } } else { /* Inject the authentication data */ crypto_copyback(crp, crp->crp_digest_start, swa->sw_mlen, s.tag); } out: explicit_bzero(ctx, exf->ctxsize); explicit_bzero(&s, sizeof(s)); return (error); } static int swcr_chacha20_poly1305(const struct swcr_session *ses, struct cryptop *crp) { const struct crypto_session_params *csp; struct { uint64_t blkbuf[howmany(CHACHA20_NATIVE_BLOCK_LEN, sizeof(uint64_t))]; u_char tag[POLY1305_HASH_LEN]; u_char tag2[POLY1305_HASH_LEN]; } s; u_char *blk = (u_char *)s.blkbuf; struct crypto_buffer_cursor cc_in, cc_out; const u_char *inblk; u_char *outblk; size_t inlen, outlen, todo; uint64_t *blkp; const struct swcr_auth *swa; const struct swcr_encdec *swe; const struct enc_xform *exf; void *ctx; int blksz, error, r, resid; swa = &ses->swcr_auth; swe = &ses->swcr_encdec; exf = swe->sw_exf; blksz = exf->native_blocksize; KASSERT(blksz <= sizeof(s.blkbuf), ("%s: blocksize mismatch", __func__)); if ((crp->crp_flags & CRYPTO_F_IV_SEPARATE) == 0) return (EINVAL); csp = crypto_get_params(crp->crp_session); ctx = __builtin_alloca(exf->ctxsize); if (crp->crp_cipher_key != NULL) exf->setkey(ctx, crp->crp_cipher_key, csp->csp_cipher_klen); else memcpy(ctx, swe->sw_ctx, exf->ctxsize); exf->reinit(ctx, crp->crp_iv, csp->csp_ivlen); /* Supply MAC with AAD */ if (crp->crp_aad != NULL) exf->update(ctx, crp->crp_aad, crp->crp_aad_length); else crypto_apply(crp, crp->crp_aad_start, crp->crp_aad_length, exf->update, ctx); if (crp->crp_aad_length % POLY1305_BLOCK_LEN != 0) { /* padding1 */ memset(blk, 0, POLY1305_BLOCK_LEN); exf->update(ctx, blk, POLY1305_BLOCK_LEN - crp->crp_aad_length % POLY1305_BLOCK_LEN); } /* Do encryption with MAC */ crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_payload_start); if (CRYPTO_HAS_OUTPUT_BUFFER(crp)) { crypto_cursor_init(&cc_out, &crp->crp_obuf); crypto_cursor_advance(&cc_out, crp->crp_payload_output_start); } else cc_out = cc_in; inlen = outlen = 0; if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { for (resid = crp->crp_payload_length; resid >= blksz; resid -= todo) { if (inlen == 0) inblk = crypto_cursor_segment(&cc_in, &inlen); if (outlen == 0) outblk = crypto_cursor_segment(&cc_out, &outlen); if (inlen < blksz) { crypto_cursor_copydata(&cc_in, blksz, blk); inblk = blk; inlen = blksz; } if (outlen < blksz) { outblk = blk; outlen = blksz; } todo = rounddown2(MIN(resid, MIN(inlen, outlen)), blksz); exf->encrypt_multi(ctx, inblk, outblk, todo); exf->update(ctx, outblk, todo); if (inblk == blk) { inblk = crypto_cursor_segment(&cc_in, &inlen); } else { crypto_cursor_advance(&cc_in, todo); inlen -= todo; inblk += todo; } if (outblk == blk) { crypto_cursor_copyback(&cc_out, blksz, blk); outblk = crypto_cursor_segment(&cc_out, &outlen); } else { crypto_cursor_advance(&cc_out, todo); outlen -= todo; outblk += todo; } } if (resid > 0) { crypto_cursor_copydata(&cc_in, resid, blk); exf->encrypt_last(ctx, blk, blk, resid); crypto_cursor_copyback(&cc_out, resid, blk); exf->update(ctx, blk, resid); } } else crypto_apply(crp, crp->crp_payload_start, crp->crp_payload_length, exf->update, ctx); if (crp->crp_payload_length % POLY1305_BLOCK_LEN != 0) { /* padding2 */ memset(blk, 0, POLY1305_BLOCK_LEN); exf->update(ctx, blk, POLY1305_BLOCK_LEN - crp->crp_payload_length % POLY1305_BLOCK_LEN); } /* lengths */ blkp = (uint64_t *)blk; blkp[0] = htole64(crp->crp_aad_length); blkp[1] = htole64(crp->crp_payload_length); exf->update(ctx, blk, sizeof(uint64_t) * 2); /* Finalize MAC */ exf->final(s.tag, ctx); /* Validate tag */ error = 0; if (!CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { crypto_copydata(crp, crp->crp_digest_start, swa->sw_mlen, s.tag2); r = timingsafe_bcmp(s.tag, s.tag2, swa->sw_mlen); if (r != 0) { error = EBADMSG; goto out; } /* tag matches, decrypt data */ crypto_cursor_init(&cc_in, &crp->crp_buf); crypto_cursor_advance(&cc_in, crp->crp_payload_start); inlen = 0; for (resid = crp->crp_payload_length; resid > blksz; resid -= todo) { if (inlen == 0) inblk = crypto_cursor_segment(&cc_in, &inlen); if (outlen == 0) outblk = crypto_cursor_segment(&cc_out, &outlen); if (inlen < blksz) { crypto_cursor_copydata(&cc_in, blksz, blk); inblk = blk; inlen = blksz; } if (outlen < blksz) { outblk = blk; outlen = blksz; } todo = rounddown2(MIN(resid, MIN(inlen, outlen)), blksz); exf->decrypt_multi(ctx, inblk, outblk, todo); if (inblk == blk) { inblk = crypto_cursor_segment(&cc_in, &inlen); } else { crypto_cursor_advance(&cc_in, todo); inlen -= todo; inblk += todo; } if (outblk == blk) { crypto_cursor_copyback(&cc_out, blksz, blk); outblk = crypto_cursor_segment(&cc_out, &outlen); } else { crypto_cursor_advance(&cc_out, todo); outlen -= todo; outblk += todo; } } if (resid > 0) { crypto_cursor_copydata(&cc_in, resid, blk); exf->decrypt_last(ctx, blk, blk, resid); crypto_cursor_copyback(&cc_out, resid, blk); } } else { /* Inject the authentication data */ crypto_copyback(crp, crp->crp_digest_start, swa->sw_mlen, s.tag); } out: explicit_bzero(ctx, exf->ctxsize); explicit_bzero(&s, sizeof(s)); return (error); } /* * Apply a cipher and a digest to perform EtA. */ static int swcr_eta(const struct swcr_session *ses, struct cryptop *crp) { int error; if (CRYPTO_OP_IS_ENCRYPT(crp->crp_op)) { error = swcr_encdec(ses, crp); if (error == 0) error = swcr_authcompute(ses, crp); } else { error = swcr_authcompute(ses, crp); if (error == 0) error = swcr_encdec(ses, crp); } return (error); } /* * Apply a compression/decompression algorithm */ static int swcr_compdec(const struct swcr_session *ses, struct cryptop *crp) { const struct comp_algo *cxf; uint8_t *data, *out; int adj; uint32_t result; cxf = ses->swcr_compdec.sw_cxf; /* We must handle the whole buffer of data in one time * then if there is not all the data in the mbuf, we must * copy in a buffer. */ data = malloc(crp->crp_payload_length, M_CRYPTO_DATA, M_NOWAIT); if (data == NULL) return (EINVAL); crypto_copydata(crp, crp->crp_payload_start, crp->crp_payload_length, data); if (CRYPTO_OP_IS_COMPRESS(crp->crp_op)) result = cxf->compress(data, crp->crp_payload_length, &out); else result = cxf->decompress(data, crp->crp_payload_length, &out); free(data, M_CRYPTO_DATA); if (result == 0) return (EINVAL); crp->crp_olen = result; /* Check the compressed size when doing compression */ if (CRYPTO_OP_IS_COMPRESS(crp->crp_op)) { if (result >= crp->crp_payload_length) { /* Compression was useless, we lost time */ free(out, M_CRYPTO_DATA); return (0); } } /* Copy back the (de)compressed data. m_copyback is * extending the mbuf as necessary. */ crypto_copyback(crp, crp->crp_payload_start, result, out); if (result < crp->crp_payload_length) { switch (crp->crp_buf.cb_type) { case CRYPTO_BUF_MBUF: case CRYPTO_BUF_SINGLE_MBUF: adj = result - crp->crp_payload_length; m_adj(crp->crp_buf.cb_mbuf, adj); break; case CRYPTO_BUF_UIO: { struct uio *uio = crp->crp_buf.cb_uio; int ind; adj = crp->crp_payload_length - result; ind = uio->uio_iovcnt - 1; while (adj > 0 && ind >= 0) { if (adj < uio->uio_iov[ind].iov_len) { uio->uio_iov[ind].iov_len -= adj; break; } adj -= uio->uio_iov[ind].iov_len; uio->uio_iov[ind].iov_len = 0; ind--; uio->uio_iovcnt--; } } break; case CRYPTO_BUF_VMPAGE: adj = crp->crp_payload_length - result; crp->crp_buf.cb_vm_page_len -= adj; break; default: break; } } free(out, M_CRYPTO_DATA); return 0; } static int swcr_setup_cipher(struct swcr_session *ses, const struct crypto_session_params *csp) { struct swcr_encdec *swe; const struct enc_xform *txf; int error; swe = &ses->swcr_encdec; txf = crypto_cipher(csp); if (csp->csp_cipher_key != NULL) { if (txf->ctxsize != 0) { swe->sw_ctx = malloc(txf->ctxsize, M_CRYPTO_DATA, M_NOWAIT); if (swe->sw_ctx == NULL) return (ENOMEM); } error = txf->setkey(swe->sw_ctx, csp->csp_cipher_key, csp->csp_cipher_klen); if (error) return (error); } swe->sw_exf = txf; return (0); } static int swcr_setup_auth(struct swcr_session *ses, const struct crypto_session_params *csp) { struct swcr_auth *swa; const struct auth_hash *axf; swa = &ses->swcr_auth; axf = crypto_auth_hash(csp); swa->sw_axf = axf; if (csp->csp_auth_mlen < 0 || csp->csp_auth_mlen > axf->hashsize) return (EINVAL); if (csp->csp_auth_mlen == 0) swa->sw_mlen = axf->hashsize; else swa->sw_mlen = csp->csp_auth_mlen; if (csp->csp_auth_klen == 0 || csp->csp_auth_key != NULL) { swa->sw_ictx = malloc(axf->ctxsize, M_CRYPTO_DATA, M_NOWAIT); if (swa->sw_ictx == NULL) return (ENOBUFS); } switch (csp->csp_auth_alg) { case CRYPTO_SHA1_HMAC: case CRYPTO_SHA2_224_HMAC: case CRYPTO_SHA2_256_HMAC: case CRYPTO_SHA2_384_HMAC: case CRYPTO_SHA2_512_HMAC: case CRYPTO_RIPEMD160_HMAC: swa->sw_hmac = true; if (csp->csp_auth_key != NULL) { swa->sw_octx = malloc(axf->ctxsize, M_CRYPTO_DATA, M_NOWAIT); if (swa->sw_octx == NULL) return (ENOBUFS); hmac_init_ipad(axf, csp->csp_auth_key, csp->csp_auth_klen, swa->sw_ictx); hmac_init_opad(axf, csp->csp_auth_key, csp->csp_auth_klen, swa->sw_octx); } break; case CRYPTO_RIPEMD160: case CRYPTO_SHA1: case CRYPTO_SHA2_224: case CRYPTO_SHA2_256: case CRYPTO_SHA2_384: case CRYPTO_SHA2_512: case CRYPTO_NULL_HMAC: axf->Init(swa->sw_ictx); break; case CRYPTO_AES_NIST_GMAC: case CRYPTO_AES_CCM_CBC_MAC: case CRYPTO_POLY1305: if (csp->csp_auth_key != NULL) { axf->Init(swa->sw_ictx); axf->Setkey(swa->sw_ictx, csp->csp_auth_key, csp->csp_auth_klen); } break; case CRYPTO_BLAKE2B: case CRYPTO_BLAKE2S: /* * Blake2b and Blake2s support an optional key but do * not require one. */ if (csp->csp_auth_klen == 0) axf->Init(swa->sw_ictx); else if (csp->csp_auth_key != NULL) axf->Setkey(swa->sw_ictx, csp->csp_auth_key, csp->csp_auth_klen); break; } if (csp->csp_mode == CSP_MODE_DIGEST) { switch (csp->csp_auth_alg) { case CRYPTO_AES_NIST_GMAC: ses->swcr_process = swcr_gmac; break; case CRYPTO_AES_CCM_CBC_MAC: ses->swcr_process = swcr_ccm_cbc_mac; break; default: ses->swcr_process = swcr_authcompute; } } return (0); } static int swcr_setup_aead(struct swcr_session *ses, const struct crypto_session_params *csp) { struct swcr_auth *swa; int error; error = swcr_setup_cipher(ses, csp); if (error) return (error); swa = &ses->swcr_auth; if (csp->csp_auth_mlen == 0) swa->sw_mlen = ses->swcr_encdec.sw_exf->macsize; else swa->sw_mlen = csp->csp_auth_mlen; return (0); } static bool swcr_auth_supported(const struct crypto_session_params *csp) { const struct auth_hash *axf; axf = crypto_auth_hash(csp); if (axf == NULL) return (false); switch (csp->csp_auth_alg) { case CRYPTO_SHA1_HMAC: case CRYPTO_SHA2_224_HMAC: case CRYPTO_SHA2_256_HMAC: case CRYPTO_SHA2_384_HMAC: case CRYPTO_SHA2_512_HMAC: case CRYPTO_NULL_HMAC: case CRYPTO_RIPEMD160_HMAC: break; case CRYPTO_AES_NIST_GMAC: switch (csp->csp_auth_klen * 8) { case 128: case 192: case 256: break; default: return (false); } if (csp->csp_auth_key == NULL) return (false); if (csp->csp_ivlen != AES_GCM_IV_LEN) return (false); break; case CRYPTO_POLY1305: if (csp->csp_auth_klen != POLY1305_KEY_LEN) return (false); break; case CRYPTO_AES_CCM_CBC_MAC: switch (csp->csp_auth_klen * 8) { case 128: case 192: case 256: break; default: return (false); } if (csp->csp_auth_key == NULL) return (false); break; } return (true); } static bool swcr_cipher_supported(const struct crypto_session_params *csp) { const struct enc_xform *txf; txf = crypto_cipher(csp); if (txf == NULL) return (false); if (csp->csp_cipher_alg != CRYPTO_NULL_CBC && txf->ivsize != csp->csp_ivlen) return (false); return (true); } #define SUPPORTED_SES (CSP_F_SEPARATE_OUTPUT | CSP_F_SEPARATE_AAD | CSP_F_ESN) static int swcr_probesession(device_t dev, const struct crypto_session_params *csp) { if ((csp->csp_flags & ~(SUPPORTED_SES)) != 0) return (EINVAL); switch (csp->csp_mode) { case CSP_MODE_COMPRESS: switch (csp->csp_cipher_alg) { case CRYPTO_DEFLATE_COMP: break; default: return (EINVAL); } break; case CSP_MODE_CIPHER: switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: case CRYPTO_AES_CCM_16: case CRYPTO_CHACHA20_POLY1305: case CRYPTO_XCHACHA20_POLY1305: return (EINVAL); default: if (!swcr_cipher_supported(csp)) return (EINVAL); break; } break; case CSP_MODE_DIGEST: if (!swcr_auth_supported(csp)) return (EINVAL); break; case CSP_MODE_AEAD: switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: case CRYPTO_AES_CCM_16: switch (csp->csp_cipher_klen * 8) { case 128: case 192: case 256: break; default: return (EINVAL); } break; case CRYPTO_CHACHA20_POLY1305: case CRYPTO_XCHACHA20_POLY1305: break; default: return (EINVAL); } break; case CSP_MODE_ETA: /* AEAD algorithms cannot be used for EtA. */ switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: case CRYPTO_AES_CCM_16: case CRYPTO_CHACHA20_POLY1305: case CRYPTO_XCHACHA20_POLY1305: return (EINVAL); } switch (csp->csp_auth_alg) { case CRYPTO_AES_NIST_GMAC: case CRYPTO_AES_CCM_CBC_MAC: return (EINVAL); } if (!swcr_cipher_supported(csp) || !swcr_auth_supported(csp)) return (EINVAL); break; default: return (EINVAL); } return (CRYPTODEV_PROBE_SOFTWARE); } /* * Generate a new software session. */ static int swcr_newsession(device_t dev, crypto_session_t cses, const struct crypto_session_params *csp) { struct swcr_session *ses; const struct comp_algo *cxf; int error; ses = crypto_get_driver_session(cses); error = 0; switch (csp->csp_mode) { case CSP_MODE_COMPRESS: switch (csp->csp_cipher_alg) { case CRYPTO_DEFLATE_COMP: cxf = &comp_algo_deflate; break; #ifdef INVARIANTS default: panic("bad compression algo"); #endif } ses->swcr_compdec.sw_cxf = cxf; ses->swcr_process = swcr_compdec; break; case CSP_MODE_CIPHER: switch (csp->csp_cipher_alg) { case CRYPTO_NULL_CBC: ses->swcr_process = swcr_null; break; #ifdef INVARIANTS case CRYPTO_AES_NIST_GCM_16: case CRYPTO_AES_CCM_16: case CRYPTO_CHACHA20_POLY1305: case CRYPTO_XCHACHA20_POLY1305: panic("bad cipher algo"); #endif default: error = swcr_setup_cipher(ses, csp); if (error == 0) ses->swcr_process = swcr_encdec; } break; case CSP_MODE_DIGEST: error = swcr_setup_auth(ses, csp); break; case CSP_MODE_AEAD: switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: error = swcr_setup_aead(ses, csp); if (error == 0) ses->swcr_process = swcr_gcm; break; case CRYPTO_AES_CCM_16: error = swcr_setup_aead(ses, csp); if (error == 0) ses->swcr_process = swcr_ccm; break; case CRYPTO_CHACHA20_POLY1305: case CRYPTO_XCHACHA20_POLY1305: error = swcr_setup_aead(ses, csp); if (error == 0) ses->swcr_process = swcr_chacha20_poly1305; break; #ifdef INVARIANTS default: panic("bad aead algo"); #endif } break; case CSP_MODE_ETA: #ifdef INVARIANTS switch (csp->csp_cipher_alg) { case CRYPTO_AES_NIST_GCM_16: case CRYPTO_AES_CCM_16: case CRYPTO_CHACHA20_POLY1305: case CRYPTO_XCHACHA20_POLY1305: panic("bad eta cipher algo"); } switch (csp->csp_auth_alg) { case CRYPTO_AES_NIST_GMAC: case CRYPTO_AES_CCM_CBC_MAC: panic("bad eta auth algo"); } #endif error = swcr_setup_auth(ses, csp); if (error) break; if (csp->csp_cipher_alg == CRYPTO_NULL_CBC) { /* Effectively degrade to digest mode. */ ses->swcr_process = swcr_authcompute; break; } error = swcr_setup_cipher(ses, csp); if (error == 0) ses->swcr_process = swcr_eta; break; default: error = EINVAL; } if (error) swcr_freesession(dev, cses); return (error); } static void swcr_freesession(device_t dev, crypto_session_t cses) { struct swcr_session *ses; ses = crypto_get_driver_session(cses); zfree(ses->swcr_encdec.sw_ctx, M_CRYPTO_DATA); zfree(ses->swcr_auth.sw_ictx, M_CRYPTO_DATA); zfree(ses->swcr_auth.sw_octx, M_CRYPTO_DATA); } /* * Process a software request. */ static int swcr_process(device_t dev, struct cryptop *crp, int hint) { struct swcr_session *ses; ses = crypto_get_driver_session(crp->crp_session); crp->crp_etype = ses->swcr_process(ses, crp); crypto_done(crp); return (0); } static void swcr_identify(driver_t *drv, device_t parent) { /* NB: order 10 is so we get attached after h/w devices */ - if (device_find_child(parent, "cryptosoft", -1) == NULL && + if (device_find_child(parent, "cryptosoft", DEVICE_UNIT_ANY) == NULL && BUS_ADD_CHILD(parent, 10, "cryptosoft", 0) == 0) panic("cryptosoft: could not attach"); } static int swcr_probe(device_t dev) { device_set_desc(dev, "software crypto"); device_quiet(dev); return (BUS_PROBE_NOWILDCARD); } static int swcr_attach(device_t dev) { swcr_id = crypto_get_driverid(dev, sizeof(struct swcr_session), CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_SYNC); if (swcr_id < 0) { device_printf(dev, "cannot initialize!"); return (ENXIO); } return (0); } static int swcr_detach(device_t dev) { crypto_unregister_all(swcr_id); return 0; } static device_method_t swcr_methods[] = { DEVMETHOD(device_identify, swcr_identify), DEVMETHOD(device_probe, swcr_probe), DEVMETHOD(device_attach, swcr_attach), DEVMETHOD(device_detach, swcr_detach), DEVMETHOD(cryptodev_probesession, swcr_probesession), DEVMETHOD(cryptodev_newsession, swcr_newsession), DEVMETHOD(cryptodev_freesession,swcr_freesession), DEVMETHOD(cryptodev_process, swcr_process), {0, 0}, }; static driver_t swcr_driver = { "cryptosoft", swcr_methods, 0, /* NB: no softc */ }; /* * NB: We explicitly reference the crypto module so we * get the necessary ordering when built as a loadable * module. This is required because we bundle the crypto * module code together with the cryptosoft driver (otherwise * normal module dependencies would handle things). */ extern int crypto_modevent(struct module *, int, void *); /* XXX where to attach */ DRIVER_MODULE(cryptosoft, nexus, swcr_driver, crypto_modevent, NULL); MODULE_VERSION(cryptosoft, 1); MODULE_DEPEND(cryptosoft, crypto, 1, 1, 1); diff --git a/sys/powerpc/cpufreq/dfs.c b/sys/powerpc/cpufreq/dfs.c index 247ddcaa9c6b..cd4587a7b43e 100644 --- a/sys/powerpc/cpufreq/dfs.c +++ b/sys/powerpc/cpufreq/dfs.c @@ -1,226 +1,226 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Nathan Whitehorn * 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, 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. */ #include #include #include #include #include #include #include "cpufreq_if.h" struct dfs_softc { device_t dev; int dfs4; }; static void dfs_identify(driver_t *driver, device_t parent); static int dfs_probe(device_t dev); static int dfs_attach(device_t dev); static int dfs_settings(device_t dev, struct cf_setting *sets, int *count); static int dfs_set(device_t dev, const struct cf_setting *set); static int dfs_get(device_t dev, struct cf_setting *set); static int dfs_type(device_t dev, int *type); static device_method_t dfs_methods[] = { /* Device interface */ DEVMETHOD(device_identify, dfs_identify), DEVMETHOD(device_probe, dfs_probe), DEVMETHOD(device_attach, dfs_attach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, dfs_set), DEVMETHOD(cpufreq_drv_get, dfs_get), DEVMETHOD(cpufreq_drv_type, dfs_type), DEVMETHOD(cpufreq_drv_settings, dfs_settings), {0, 0} }; static driver_t dfs_driver = { "dfs", dfs_methods, sizeof(struct dfs_softc) }; DRIVER_MODULE(dfs, cpu, dfs_driver, 0, 0); /* * Bits of the HID1 register to enable DFS. See page 2-24 of "MPC7450 * RISC Microprocessor Family Reference Manual", rev. 5. */ #define HID1_DFS2 (1UL << 22) #define HID1_DFS4 (1UL << 23) static void dfs_identify(driver_t *driver, device_t parent) { uint16_t vers; vers = mfpvr() >> 16; /* Check for an MPC 7447A or 7448 CPU */ switch (vers) { case MPC7447A: case MPC7448: break; default: return; } /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "dfs", -1) != NULL) + if (device_find_child(parent, "dfs", DEVICE_UNIT_ANY) != NULL) return; /* * We attach a child for every CPU since settings need to * be performed on every CPU in the SMP case. */ - if (BUS_ADD_CHILD(parent, 10, "dfs", -1) == NULL) + if (BUS_ADD_CHILD(parent, 10, "dfs", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add dfs child failed\n"); } static int dfs_probe(device_t dev) { if (resource_disabled("dfs", 0)) return (ENXIO); device_set_desc(dev, "Dynamic Frequency Switching"); return (0); } static int dfs_attach(device_t dev) { struct dfs_softc *sc; uint16_t vers; sc = device_get_softc(dev); sc->dev = dev; sc->dfs4 = 0; vers = mfpvr() >> 16; /* The 7448 supports divide-by-four as well */ if (vers == MPC7448) sc->dfs4 = 1; cpufreq_register(dev); return (0); } static int dfs_settings(device_t dev, struct cf_setting *sets, int *count) { struct dfs_softc *sc; int states; sc = device_get_softc(dev); states = sc->dfs4 ? 3 : 2; if (sets == NULL || count == NULL) return (EINVAL); if (*count < states) return (E2BIG); /* Return a list of valid settings for this driver. */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * states); sets[0].freq = 10000; sets[0].dev = dev; sets[1].freq = 5000; sets[1].dev = dev; if (sc->dfs4) { sets[2].freq = 2500; sets[2].dev = dev; } *count = states; return (0); } static int dfs_set(device_t dev, const struct cf_setting *set) { register_t hid1; if (set == NULL) return (EINVAL); hid1 = mfspr(SPR_HID1); hid1 &= ~(HID1_DFS2 | HID1_DFS4); if (set->freq == 5000) hid1 |= HID1_DFS2; else if (set->freq == 2500) hid1 |= HID1_DFS4; /* * Now set the HID1 register with new values. Calling sequence * taken from page 2-26 of the MPC7450 family CPU manual. */ powerpc_sync(); mtspr(SPR_HID1, hid1); powerpc_sync(); isync(); return (0); } static int dfs_get(device_t dev, struct cf_setting *set) { struct dfs_softc *sc; register_t hid1; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); hid1 = mfspr(SPR_HID1); set->freq = 10000; if (hid1 & HID1_DFS2) set->freq = 5000; else if (sc->dfs4 && (hid1 & HID1_DFS4)) set->freq = 2500; set->dev = dev; return (0); } static int dfs_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_RELATIVE; return (0); } diff --git a/sys/powerpc/cpufreq/mpc85xx_jog.c b/sys/powerpc/cpufreq/mpc85xx_jog.c index 2d66fa05e942..e405c4e94e89 100644 --- a/sys/powerpc/cpufreq/mpc85xx_jog.c +++ b/sys/powerpc/cpufreq/mpc85xx_jog.c @@ -1,337 +1,337 @@ /*- * Copyright (c) 2017 Justin Hibbits * 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, 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" /* No worries about uint32_t math overflow in here, because the highest * multiplier supported is 4, and the highest speed part is still well below * 2GHz. */ #define GUTS_PORPLLSR (CCSRBAR_VA + 0xe0000) #define GUTS_PMJCR (CCSRBAR_VA + 0xe007c) #define PMJCR_RATIO_M 0x3f #define PMJCR_CORE_MULT(x,y) ((x) << (16 + ((y) * 8))) #define PMJCR_GET_CORE_MULT(x,y) (((x) >> (16 + ((y) * 8))) & 0x3f) #define GUTS_POWMGTCSR (CCSRBAR_VA + 0xe0080) #define POWMGTCSR_JOG 0x00200000 #define POWMGTCSR_INT_MASK 0x00000f00 #define MHZ 1000000 struct mpc85xx_jog_softc { device_t dev; int cpu; int low; int high; int min_freq; }; static struct ofw_compat_data *mpc85xx_jog_devcompat(void); static void mpc85xx_jog_identify(driver_t *driver, device_t parent); static int mpc85xx_jog_probe(device_t dev); static int mpc85xx_jog_attach(device_t dev); static int mpc85xx_jog_settings(device_t dev, struct cf_setting *sets, int *count); static int mpc85xx_jog_set(device_t dev, const struct cf_setting *set); static int mpc85xx_jog_get(device_t dev, struct cf_setting *set); static int mpc85xx_jog_type(device_t dev, int *type); static device_method_t mpc85xx_jog_methods[] = { /* Device interface */ DEVMETHOD(device_identify, mpc85xx_jog_identify), DEVMETHOD(device_probe, mpc85xx_jog_probe), DEVMETHOD(device_attach, mpc85xx_jog_attach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, mpc85xx_jog_set), DEVMETHOD(cpufreq_drv_get, mpc85xx_jog_get), DEVMETHOD(cpufreq_drv_type, mpc85xx_jog_type), DEVMETHOD(cpufreq_drv_settings, mpc85xx_jog_settings), {0, 0} }; static driver_t mpc85xx_jog_driver = { "jog", mpc85xx_jog_methods, sizeof(struct mpc85xx_jog_softc) }; DRIVER_MODULE(mpc85xx_jog, cpu, mpc85xx_jog_driver, 0, 0); struct mpc85xx_constraints { int threshold; /* Threshold frequency, in MHz, for setting CORE_SPD bit. */ int min_mult; /* Minimum PLL multiplier. */ }; static struct mpc85xx_constraints mpc8536_constraints = { 800, 3 }; static struct mpc85xx_constraints p1022_constraints = { 500, 2 }; static struct ofw_compat_data jog_compat[] = { {"fsl,mpc8536-guts", (uintptr_t)&mpc8536_constraints}, {"fsl,p1022-guts", (uintptr_t)&p1022_constraints}, {NULL, 0} }; static struct ofw_compat_data * mpc85xx_jog_devcompat(void) { phandle_t node; int i; node = OF_finddevice("/soc"); if (node == -1) return (NULL); for (i = 0; jog_compat[i].ocd_str != NULL; i++) if (ofw_bus_find_compatible(node, jog_compat[i].ocd_str) > 0) break; if (jog_compat[i].ocd_str == NULL) return (NULL); return (&jog_compat[i]); } static void mpc85xx_jog_identify(driver_t *driver, device_t parent) { struct ofw_compat_data *compat; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "mpc85xx_jog", -1) != NULL) + if (device_find_child(parent, "mpc85xx_jog", DEVICE_UNIT_ANY) != NULL) return; compat = mpc85xx_jog_devcompat(); if (compat == NULL) return; /* * We attach a child for every CPU since settings need to * be performed on every CPU in the SMP case. */ - if (BUS_ADD_CHILD(parent, 10, "jog", -1) == NULL) + if (BUS_ADD_CHILD(parent, 10, "jog", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add jog child failed\n"); } static int mpc85xx_jog_probe(device_t dev) { struct ofw_compat_data *compat; compat = mpc85xx_jog_devcompat(); if (compat == NULL || compat->ocd_str == NULL) return (ENXIO); device_set_desc(dev, "Freescale CPU Jogger"); return (0); } static int mpc85xx_jog_attach(device_t dev) { struct ofw_compat_data *compat; struct mpc85xx_jog_softc *sc; struct mpc85xx_constraints *constraints; phandle_t cpu; uint32_t reg; sc = device_get_softc(dev); sc->dev = dev; compat = mpc85xx_jog_devcompat(); constraints = (struct mpc85xx_constraints *)compat->ocd_data; cpu = ofw_bus_get_node(device_get_parent(dev)); if (cpu <= 0) { device_printf(dev,"No CPU device tree node!\n"); return (ENXIO); } OF_getencprop(cpu, "reg", &sc->cpu, sizeof(sc->cpu)); reg = ccsr_read4(GUTS_PORPLLSR); /* * Assume power-on PLL is the highest PLL config supported on the * board. */ sc->high = PMJCR_GET_CORE_MULT(reg, sc->cpu); sc->min_freq = constraints->threshold; sc->low = constraints->min_mult; cpufreq_register(dev); return (0); } static int mpc85xx_jog_settings(device_t dev, struct cf_setting *sets, int *count) { struct mpc85xx_jog_softc *sc; uint32_t sysclk; int i; sc = device_get_softc(dev); if (sets == NULL || count == NULL) return (EINVAL); if (*count < sc->high - 1) return (E2BIG); sysclk = mpc85xx_get_system_clock(); /* Return a list of valid settings for this driver. */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * sc->high); for (i = sc->high; i >= sc->low; --i) { sets[sc->high - i].freq = sysclk * i / MHZ; sets[sc->high - i].dev = dev; sets[sc->high - i].spec[0] = i; } *count = sc->high - sc->low + 1; return (0); } struct jog_rv_args { int cpu; int mult; int slow; volatile int inprogress; }; static void mpc85xx_jog_set_int(void *arg) { struct jog_rv_args *args = arg; uint32_t reg; if (PCPU_GET(cpuid) == args->cpu) { reg = ccsr_read4(GUTS_PMJCR); reg &= ~PMJCR_CORE_MULT(PMJCR_RATIO_M, args->cpu); reg |= PMJCR_CORE_MULT(args->mult, args->cpu); if (args->slow) reg &= ~(1 << (12 + args->cpu)); else reg |= (1 << (12 + args->cpu)); ccsr_write4(GUTS_PMJCR, reg); reg = ccsr_read4(GUTS_POWMGTCSR); reg |= POWMGTCSR_JOG | POWMGTCSR_INT_MASK; ccsr_write4(GUTS_POWMGTCSR, reg); /* Wait for completion */ do { DELAY(100); reg = ccsr_read4(GUTS_POWMGTCSR); } while (reg & POWMGTCSR_JOG); reg = ccsr_read4(GUTS_POWMGTCSR); ccsr_write4(GUTS_POWMGTCSR, reg & ~POWMGTCSR_INT_MASK); ccsr_read4(GUTS_POWMGTCSR); args->inprogress = 0; } else { while (args->inprogress) cpu_spinwait(); } } static int mpc85xx_jog_set(device_t dev, const struct cf_setting *set) { struct mpc85xx_jog_softc *sc; struct jog_rv_args args; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); args.slow = (set->freq <= sc->min_freq); args.mult = set->spec[0]; args.cpu = PCPU_GET(cpuid); args.inprogress = 1; smp_rendezvous(smp_no_rendezvous_barrier, mpc85xx_jog_set_int, smp_no_rendezvous_barrier, &args); return (0); } static int mpc85xx_jog_get(device_t dev, struct cf_setting *set) { struct mpc85xx_jog_softc *sc; uint32_t pmjcr; uint32_t freq; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); pmjcr = ccsr_read4(GUTS_PORPLLSR); freq = PMJCR_GET_CORE_MULT(pmjcr, sc->cpu); freq *= mpc85xx_get_system_clock(); freq /= MHZ; set->freq = freq; set->dev = dev; return (0); } static int mpc85xx_jog_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } diff --git a/sys/powerpc/cpufreq/pcr.c b/sys/powerpc/cpufreq/pcr.c index 235f2d155357..335a1d011a66 100644 --- a/sys/powerpc/cpufreq/pcr.c +++ b/sys/powerpc/cpufreq/pcr.c @@ -1,339 +1,339 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Nathan Whitehorn * 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, 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. */ #include #include #include #include #include #include #include #include "cpufreq_if.h" struct pcr_softc { device_t dev; uint32_t pcr_vals[3]; int nmodes; }; static void pcr_identify(driver_t *driver, device_t parent); static int pcr_probe(device_t dev); static int pcr_attach(device_t dev); static int pcr_settings(device_t dev, struct cf_setting *sets, int *count); static int pcr_set(device_t dev, const struct cf_setting *set); static int pcr_get(device_t dev, struct cf_setting *set); static int pcr_type(device_t dev, int *type); static device_method_t pcr_methods[] = { /* Device interface */ DEVMETHOD(device_identify, pcr_identify), DEVMETHOD(device_probe, pcr_probe), DEVMETHOD(device_attach, pcr_attach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, pcr_set), DEVMETHOD(cpufreq_drv_get, pcr_get), DEVMETHOD(cpufreq_drv_type, pcr_type), DEVMETHOD(cpufreq_drv_settings, pcr_settings), {0, 0} }; static driver_t pcr_driver = { "pcr", pcr_methods, sizeof(struct pcr_softc) }; DRIVER_MODULE(pcr, cpu, pcr_driver, 0, 0); /* * States */ #define PCR_TO_FREQ(a) ((a >> 17) & 3) #define PCR_FULL 0 #define PCR_HALF 1 #define PCR_QUARTER 2 /* Only on 970MP */ #define PSR_RECEIVED (1ULL << 61) #define PSR_COMPLETED (1ULL << 61) /* * SCOM addresses */ #define SCOM_PCR 0x0aa00100 /* Power Control Register */ #define SCOM_PCR_BIT 0x80000000 /* Data bit for PCR */ #define SCOM_PSR 0x40800100 /* Power Status Register */ /* * SCOM Glue */ #define SCOMC_READ 0x00008000 #define SCOMC_WRITE 0x00000000 static void write_scom(register_t address, uint64_t value) { register_t msr; #ifndef __powerpc64__ register_t hi, lo, scratch; #endif msr = mfmsr(); mtmsr(msr & ~PSL_EE); isync(); #ifdef __powerpc64__ mtspr(SPR_SCOMD, value); #else hi = (value >> 32) & 0xffffffff; lo = value & 0xffffffff; mtspr64(SPR_SCOMD, hi, lo, scratch); #endif isync(); mtspr(SPR_SCOMC, address | SCOMC_WRITE); isync(); mtmsr(msr); isync(); } static uint64_t read_scom(register_t address) { register_t msr; uint64_t ret; msr = mfmsr(); mtmsr(msr & ~PSL_EE); isync(); mtspr(SPR_SCOMC, address | SCOMC_READ); isync(); __asm __volatile ("mfspr %0,%1;" " mr %0+1, %0; srdi %0,%0,32" : "=r" (ret) : "K" (SPR_SCOMD)); (void)mfspr(SPR_SCOMC); /* Complete transcation */ mtmsr(msr); isync(); return (ret); } static void pcr_identify(driver_t *driver, device_t parent) { uint16_t vers; vers = mfpvr() >> 16; /* Check for an IBM 970-class CPU */ switch (vers) { case IBM970FX: case IBM970GX: case IBM970MP: break; default: return; } /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "pcr", -1) != NULL) + if (device_find_child(parent, "pcr", DEVICE_UNIT_ANY) != NULL) return; /* * We attach a child for every CPU since settings need to * be performed on every CPU in the SMP case. */ - if (BUS_ADD_CHILD(parent, 10, "pcr", -1) == NULL) + if (BUS_ADD_CHILD(parent, 10, "pcr", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add pcr child failed\n"); } static int pcr_probe(device_t dev) { if (resource_disabled("pcr", 0)) return (ENXIO); device_set_desc(dev, "PPC 970 Power Control Register"); return (0); } static int pcr_attach(device_t dev) { struct pcr_softc *sc; phandle_t cpu; uint32_t modes[3]; int i; sc = device_get_softc(dev); sc->dev = dev; cpu = ofw_bus_get_node(device_get_parent(dev)); if (cpu <= 0) { device_printf(dev,"No CPU device tree node!\n"); return (ENXIO); } if (OF_getproplen(cpu, "power-mode-data") <= 0) { /* Use the first CPU's node */ cpu = OF_child(OF_parent(cpu)); } /* * Collect the PCR values for each mode from the device tree. * These include bus timing information, and so cannot be * directly computed. */ sc->nmodes = OF_getproplen(cpu, "power-mode-data"); if (sc->nmodes <= 0 || sc->nmodes > sizeof(sc->pcr_vals)) { device_printf(dev,"No power mode data in device tree!\n"); return (ENXIO); } OF_getprop(cpu, "power-mode-data", modes, sc->nmodes); sc->nmodes /= sizeof(modes[0]); /* Sort the modes */ for (i = 0; i < sc->nmodes; i++) sc->pcr_vals[PCR_TO_FREQ(modes[i])] = modes[i]; cpufreq_register(dev); return (0); } static int pcr_settings(device_t dev, struct cf_setting *sets, int *count) { struct pcr_softc *sc; sc = device_get_softc(dev); if (sets == NULL || count == NULL) return (EINVAL); if (*count < sc->nmodes) return (E2BIG); /* Return a list of valid settings for this driver. */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * sc->nmodes); sets[0].freq = 10000; sets[0].dev = dev; sets[1].freq = 5000; sets[1].dev = dev; if (sc->nmodes > 2) { sets[2].freq = 2500; sets[2].dev = dev; } *count = sc->nmodes; return (0); } static int pcr_set(device_t dev, const struct cf_setting *set) { struct pcr_softc *sc; register_t pcr, msr; uint64_t psr; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); /* Construct the new PCR */ pcr = SCOM_PCR_BIT; if (set->freq == 10000) pcr |= sc->pcr_vals[0]; else if (set->freq == 5000) pcr |= sc->pcr_vals[1]; else if (set->freq == 2500) pcr |= sc->pcr_vals[2]; msr = mfmsr(); mtmsr(msr & ~PSL_EE); isync(); /* 970MP requires PCR and PCRH to be cleared first */ write_scom(SCOM_PCR,0); /* Clear PCRH */ write_scom(SCOM_PCR,SCOM_PCR_BIT); /* Clear PCR */ /* Set PCR */ write_scom(SCOM_PCR, pcr); /* Wait for completion */ do { DELAY(100); psr = read_scom(SCOM_PSR); } while ((psr & PSR_RECEIVED) && !(psr & PSR_COMPLETED)); mtmsr(msr); isync(); return (0); } static int pcr_get(device_t dev, struct cf_setting *set) { uint64_t psr; if (set == NULL) return (EINVAL); memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); psr = read_scom(SCOM_PSR); /* We want bits 6 and 7 */ psr = (psr >> 56) & 3; set->freq = 10000; if (psr == PCR_HALF) set->freq = 5000; else if (psr == PCR_QUARTER) set->freq = 2500; set->dev = dev; return (0); } static int pcr_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_RELATIVE; return (0); } diff --git a/sys/powerpc/cpufreq/pmcr.c b/sys/powerpc/cpufreq/pmcr.c index 8dd57079e3bd..dd489b607606 100644 --- a/sys/powerpc/cpufreq/pmcr.c +++ b/sys/powerpc/cpufreq/pmcr.c @@ -1,238 +1,238 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 Justin Hibbits * * 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, 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. */ #include #include #include #include #include #include #include #include "cpufreq_if.h" static int pstate_ids[256]; static int pstate_freqs[256]; static int npstates; static void parse_pstates(void) { phandle_t node; node = OF_finddevice("/ibm,opal/power-mgt"); /* If this fails, npstates will remain 0, and any attachment will bail. */ if (node == -1) return; npstates = OF_getencprop(node, "ibm,pstate-ids", pstate_ids, sizeof(pstate_ids)); if (npstates < 0) { npstates = 0; return; } if (OF_getencprop(node, "ibm,pstate-frequencies-mhz", pstate_freqs, sizeof(pstate_freqs)) != npstates) { npstates = 0; return; } npstates /= sizeof(cell_t); } /* Make this a sysinit so it runs before the cpufreq driver attaches. */ SYSINIT(parse_pstates, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, parse_pstates, NULL); #define PMCR_UPPERPS_MASK 0xff00000000000000UL #define PMCR_UPPERPS_SHIFT 56 #define PMCR_LOWERPS_MASK 0x00ff000000000000UL #define PMCR_LOWERPS_SHIFT 48 #define PMCR_VERSION_MASK 0x0000000f #define PMCR_VERSION_1 1 struct pmcr_softc { device_t dev; }; static void pmcr_identify(driver_t *driver, device_t parent); static int pmcr_probe(device_t dev); static int pmcr_attach(device_t dev); static int pmcr_settings(device_t dev, struct cf_setting *sets, int *count); static int pmcr_set(device_t dev, const struct cf_setting *set); static int pmcr_get(device_t dev, struct cf_setting *set); static int pmcr_type(device_t dev, int *type); static device_method_t pmcr_methods[] = { /* Device interface */ DEVMETHOD(device_identify, pmcr_identify), DEVMETHOD(device_probe, pmcr_probe), DEVMETHOD(device_attach, pmcr_attach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, pmcr_set), DEVMETHOD(cpufreq_drv_get, pmcr_get), DEVMETHOD(cpufreq_drv_type, pmcr_type), DEVMETHOD(cpufreq_drv_settings, pmcr_settings), {0, 0} }; static driver_t pmcr_driver = { "pmcr", pmcr_methods, sizeof(struct pmcr_softc) }; DRIVER_MODULE(pmcr, cpu, pmcr_driver, 0, 0); static void pmcr_identify(driver_t *driver, device_t parent) { /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "pmcr", -1) != NULL) + if (device_find_child(parent, "pmcr", DEVICE_UNIT_ANY) != NULL) return; /* * We attach a child for every CPU since settings need to * be performed on every CPU in the SMP case. */ - if (BUS_ADD_CHILD(parent, 10, "pmcr", -1) == NULL) + if (BUS_ADD_CHILD(parent, 10, "pmcr", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add pmcr child failed\n"); } static int pmcr_probe(device_t dev) { if (resource_disabled("pmcr", 0)) return (ENXIO); if (npstates == 0) return (ENXIO); device_set_desc(dev, "Power Management Control Register"); return (0); } static int pmcr_attach(device_t dev) { struct pmcr_softc *sc; sc = device_get_softc(dev); sc->dev = dev; cpufreq_register(dev); return (0); } static int pmcr_settings(device_t dev, struct cf_setting *sets, int *count) { int i; if (sets == NULL || count == NULL) return (EINVAL); if (*count < npstates) return (E2BIG); /* Return a list of valid settings for this driver. */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * npstates); for (i = 0; i < npstates; i++) { sets[i].freq = pstate_freqs[i]; sets[i].spec[0] = pstate_ids[i]; sets[i].spec[1] = i; sets[i].dev = dev; } *count = npstates; return (0); } static int pmcr_set(device_t dev, const struct cf_setting *set) { register_t pmcr; if (set == NULL) return (EINVAL); if (set->spec[1] < 0 || set->spec[1] >= npstates) return (EINVAL); pmcr = ((long)set->spec[0] << PMCR_LOWERPS_SHIFT) & PMCR_LOWERPS_MASK; pmcr |= ((long)set->spec[0] << PMCR_UPPERPS_SHIFT) & PMCR_UPPERPS_MASK; pmcr |= PMCR_VERSION_1; mtspr(SPR_PMCR, pmcr); powerpc_sync(); isync(); return (0); } static int pmcr_get(device_t dev, struct cf_setting *set) { register_t pmcr; int i, pstate; if (set == NULL) return (EINVAL); memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); pmcr = mfspr(SPR_PMCR); pstate = (pmcr & PMCR_LOWERPS_MASK) >> PMCR_LOWERPS_SHIFT; for (i = 0; i < npstates && pstate_ids[i] != pstate; i++) ; if (i == npstates) return (EINVAL); set->spec[0] = pstate; set->spec[1] = i; set->freq = pstate_freqs[i]; set->dev = dev; return (0); } static int pmcr_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } diff --git a/sys/powerpc/cpufreq/pmufreq.c b/sys/powerpc/cpufreq/pmufreq.c index 5c8f413539b4..2603ccf0b725 100644 --- a/sys/powerpc/cpufreq/pmufreq.c +++ b/sys/powerpc/cpufreq/pmufreq.c @@ -1,215 +1,215 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Justin Hibbits * Copyright (c) 2009 Nathan Whitehorn * 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, 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. */ #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include "powerpc/powermac/pmuvar.h" struct pmufreq_softc { device_t dev; uint32_t minfreq; uint32_t maxfreq; uint32_t curfreq; }; static void pmufreq_identify(driver_t *driver, device_t parent); static int pmufreq_probe(device_t dev); static int pmufreq_attach(device_t dev); static int pmufreq_settings(device_t dev, struct cf_setting *sets, int *count); static int pmufreq_set(device_t dev, const struct cf_setting *set); static int pmufreq_get(device_t dev, struct cf_setting *set); static int pmufreq_type(device_t dev, int *type); static device_method_t pmufreq_methods[] = { /* Device interface */ DEVMETHOD(device_identify, pmufreq_identify), DEVMETHOD(device_probe, pmufreq_probe), DEVMETHOD(device_attach, pmufreq_attach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, pmufreq_set), DEVMETHOD(cpufreq_drv_get, pmufreq_get), DEVMETHOD(cpufreq_drv_type, pmufreq_type), DEVMETHOD(cpufreq_drv_settings, pmufreq_settings), {0, 0} }; static driver_t pmufreq_driver = { "pmufreq", pmufreq_methods, sizeof(struct pmufreq_softc) }; DRIVER_MODULE(pmufreq, cpu, pmufreq_driver, 0, 0); static void pmufreq_identify(driver_t *driver, device_t parent) { phandle_t node; uint32_t min_freq; node = ofw_bus_get_node(parent); if (OF_getprop(node, "min-clock-frequency", &min_freq, sizeof(min_freq)) == -1) return; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "pmufreq", -1) != NULL) + if (device_find_child(parent, "pmufreq", DEVICE_UNIT_ANY) != NULL) return; /* * We attach a child for every CPU since settings need to * be performed on every CPU in the SMP case. */ - if (BUS_ADD_CHILD(parent, 10, "pmufreq", -1) == NULL) + if (BUS_ADD_CHILD(parent, 10, "pmufreq", DEVICE_UNIT_ANY) == NULL) device_printf(parent, "add pmufreq child failed\n"); } static int pmufreq_probe(device_t dev) { phandle_t node; uint32_t min_freq; if (resource_disabled("pmufreq", 0)) return (ENXIO); node = ofw_bus_get_node(device_get_parent(dev)); /* * A scalable MPC7455 has min-clock-frequency/max-clock-frequency as OFW * properties of the 'cpu' node. */ if (OF_getprop(node, "min-clock-frequency", &min_freq, sizeof(min_freq)) == -1) return (ENXIO); device_set_desc(dev, "PMU-based frequency scaling"); return (0); } static int pmufreq_attach(device_t dev) { struct pmufreq_softc *sc; phandle_t node; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(device_get_parent(dev)); OF_getprop(node, "min-clock-frequency", &sc->minfreq, sizeof(sc->minfreq)); OF_getprop(node, "max-clock-frequency", &sc->maxfreq, sizeof(sc->maxfreq)); OF_getprop(node, "rounded-clock-frequency", &sc->curfreq, sizeof(sc->curfreq)); sc->minfreq /= 1000000; sc->maxfreq /= 1000000; sc->curfreq /= 1000000; cpufreq_register(dev); return (0); } static int pmufreq_settings(device_t dev, struct cf_setting *sets, int *count) { struct pmufreq_softc *sc; sc = device_get_softc(dev); if (sets == NULL || count == NULL) return (EINVAL); if (*count < 2) return (E2BIG); /* Return a list of valid settings for this driver. */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * 2); sets[0].freq = sc->maxfreq; sets[0].dev = dev; sets[1].freq = sc->minfreq; sets[1].dev = dev; /* Set high latency for CPU frequency changes, it's a tedious process. */ sets[0].lat = INT_MAX; sets[1].lat = INT_MAX; *count = 2; return (0); } static int pmufreq_set(device_t dev, const struct cf_setting *set) { struct pmufreq_softc *sc; int error, speed_sel; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); if (set->freq == sc->maxfreq) speed_sel = 0; else speed_sel = 1; error = pmu_set_speed(speed_sel); if (error == 0) sc->curfreq = set->freq; return (error); } static int pmufreq_get(device_t dev, struct cf_setting *set) { struct pmufreq_softc *sc; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); set->freq = sc->curfreq; set->dev = dev; return (0); } static int pmufreq_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } diff --git a/sys/powerpc/powermac/atibl.c b/sys/powerpc/powermac/atibl.c index 1bc521f8bfad..74632fc77e59 100644 --- a/sys/powerpc/powermac/atibl.c +++ b/sys/powerpc/powermac/atibl.c @@ -1,318 +1,318 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 Justin Hibbits * 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, 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. */ #include #include #include #include #include #include #include #include #include #include #ifndef PCI_VENDOR_ID_ATI #define PCI_VENDOR_ID_ATI 0x1002 #endif /* From the xf86-video-ati driver's radeon_reg.h */ #define RADEON_LVDS_GEN_CNTL 0x02d0 #define RADEON_LVDS_ON (1 << 0) #define RADEON_LVDS_DISPLAY_DIS (1 << 1) #define RADEON_LVDS_PANEL_TYPE (1 << 2) #define RADEON_LVDS_PANEL_FORMAT (1 << 3) #define RADEON_LVDS_RST_FM (1 << 6) #define RADEON_LVDS_EN (1 << 7) #define RADEON_LVDS_BL_MOD_LEVEL_SHIFT 8 #define RADEON_LVDS_BL_MOD_LEVEL_MASK (0xff << 8) #define RADEON_LVDS_BL_MOD_EN (1 << 16) #define RADEON_LVDS_DIGON (1 << 18) #define RADEON_LVDS_BLON (1 << 19) #define RADEON_LVDS_PLL_CNTL 0x02d4 #define RADEON_LVDS_PLL_EN (1 << 16) #define RADEON_LVDS_PLL_RESET (1 << 17) #define RADEON_PIXCLKS_CNTL 0x002d #define RADEON_PIXCLK_LVDS_ALWAYS_ONb (1 << 14) #define RADEON_DISP_PWR_MAN 0x0d08 #define RADEON_AUTO_PWRUP_EN (1 << 26) #define RADEON_CLOCK_CNTL_DATA 0x000c #define RADEON_CLOCK_CNTL_INDEX 0x0008 #define RADEON_PLL_WR_EN (1 << 7) #define RADEON_CRTC_GEN_CNTL 0x0050 struct atibl_softc { struct resource *sc_memr; int sc_level; }; static void atibl_identify(driver_t *driver, device_t parent); static int atibl_probe(device_t dev); static int atibl_attach(device_t dev); static int atibl_setlevel(struct atibl_softc *sc, int newlevel); static int atibl_getlevel(struct atibl_softc *sc); static int atibl_resume(device_t dev); static int atibl_suspend(device_t dev); static int atibl_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t atibl_methods[] = { /* Device interface */ DEVMETHOD(device_identify, atibl_identify), DEVMETHOD(device_probe, atibl_probe), DEVMETHOD(device_attach, atibl_attach), DEVMETHOD(device_suspend, atibl_suspend), DEVMETHOD(device_resume, atibl_resume), {0, 0}, }; static driver_t atibl_driver = { "backlight", atibl_methods, sizeof(struct atibl_softc) }; DRIVER_MODULE(atibl, vgapci, atibl_driver, 0, 0); static void atibl_identify(driver_t *driver, device_t parent) { if (OF_finddevice("mac-io/backlight") == -1) return; - if (device_find_child(parent, "backlight", -1) == NULL) + if (device_find_child(parent, "backlight", DEVICE_UNIT_ANY) == NULL) device_add_child(parent, "backlight", DEVICE_UNIT_ANY); } static int atibl_probe(device_t dev) { char control[8]; phandle_t handle; handle = OF_finddevice("mac-io/backlight"); if (handle == -1) return (ENXIO); if (OF_getprop(handle, "backlight-control", &control, sizeof(control)) < 0) return (ENXIO); if (strcmp(control, "ati") != 0 && (strcmp(control, "mnca") != 0 || pci_get_vendor(device_get_parent(dev)) != 0x1002)) return (ENXIO); device_set_desc(dev, "PowerBook backlight for ATI graphics"); return (0); } static int atibl_attach(device_t dev) { struct atibl_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; int rid; sc = device_get_softc(dev); rid = 0x18; /* BAR[2], for the MMIO register */ sc->sc_memr = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE | RF_SHAREABLE); if (sc->sc_memr == NULL) { device_printf(dev, "Could not alloc mem resource!\n"); return (ENXIO); } ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "level", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, atibl_sysctl, "I", "Backlight level (0-100)"); return (0); } static uint32_t __inline atibl_pll_rreg(struct atibl_softc *sc, uint32_t reg) { uint32_t data, save, tmp; bus_write_1(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX, (reg & 0x3f)); (void)bus_read_4(sc->sc_memr, RADEON_CLOCK_CNTL_DATA); (void)bus_read_4(sc->sc_memr, RADEON_CRTC_GEN_CNTL); data = bus_read_4(sc->sc_memr, RADEON_CLOCK_CNTL_DATA); /* Only necessary on R300, but won't hurt others. */ save = bus_read_4(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX); tmp = save & (~0x3f | RADEON_PLL_WR_EN); bus_write_4(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX, tmp); tmp = bus_read_4(sc->sc_memr, RADEON_CLOCK_CNTL_DATA); bus_write_4(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX, save); return data; } static void __inline atibl_pll_wreg(struct atibl_softc *sc, uint32_t reg, uint32_t val) { uint32_t save, tmp; bus_write_1(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX, ((reg & 0x3f) | RADEON_PLL_WR_EN)); (void)bus_read_4(sc->sc_memr, RADEON_CLOCK_CNTL_DATA); (void)bus_read_4(sc->sc_memr, RADEON_CRTC_GEN_CNTL); bus_write_4(sc->sc_memr, RADEON_CLOCK_CNTL_DATA, val); DELAY(5000); /* Only necessary on R300, but won't hurt others. */ save = bus_read_4(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX); tmp = save & (~0x3f | RADEON_PLL_WR_EN); bus_write_4(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX, tmp); tmp = bus_read_4(sc->sc_memr, RADEON_CLOCK_CNTL_DATA); bus_write_4(sc->sc_memr, RADEON_CLOCK_CNTL_INDEX, save); } static int atibl_setlevel(struct atibl_softc *sc, int newlevel) { uint32_t lvds_gen_cntl; uint32_t lvds_pll_cntl; uint32_t pixclks_cntl; uint32_t disp_pwr_reg; if (newlevel > 100) newlevel = 100; if (newlevel < 0) newlevel = 0; lvds_gen_cntl = bus_read_4(sc->sc_memr, RADEON_LVDS_GEN_CNTL); if (newlevel > 0) { newlevel = (newlevel * 5) / 2 + 5; disp_pwr_reg = bus_read_4(sc->sc_memr, RADEON_DISP_PWR_MAN); disp_pwr_reg |= RADEON_AUTO_PWRUP_EN; bus_write_4(sc->sc_memr, RADEON_DISP_PWR_MAN, disp_pwr_reg); lvds_pll_cntl = bus_read_4(sc->sc_memr, RADEON_LVDS_PLL_CNTL); lvds_pll_cntl |= RADEON_LVDS_PLL_EN; bus_write_4(sc->sc_memr, RADEON_LVDS_PLL_CNTL, lvds_pll_cntl); lvds_pll_cntl &= ~RADEON_LVDS_PLL_RESET; bus_write_4(sc->sc_memr, RADEON_LVDS_PLL_CNTL, lvds_pll_cntl); DELAY(1000); lvds_gen_cntl &= ~(RADEON_LVDS_DISPLAY_DIS | RADEON_LVDS_BL_MOD_LEVEL_MASK); lvds_gen_cntl |= RADEON_LVDS_ON | RADEON_LVDS_EN | RADEON_LVDS_DIGON | RADEON_LVDS_BLON; lvds_gen_cntl |= (newlevel << RADEON_LVDS_BL_MOD_LEVEL_SHIFT) & RADEON_LVDS_BL_MOD_LEVEL_MASK; lvds_gen_cntl |= RADEON_LVDS_BL_MOD_EN; DELAY(200000); bus_write_4(sc->sc_memr, RADEON_LVDS_GEN_CNTL, lvds_gen_cntl); } else { pixclks_cntl = atibl_pll_rreg(sc, RADEON_PIXCLKS_CNTL); atibl_pll_wreg(sc, RADEON_PIXCLKS_CNTL, pixclks_cntl & ~RADEON_PIXCLK_LVDS_ALWAYS_ONb); lvds_gen_cntl |= RADEON_LVDS_DISPLAY_DIS; lvds_gen_cntl &= ~(RADEON_LVDS_BL_MOD_EN | RADEON_LVDS_BL_MOD_LEVEL_MASK); bus_write_4(sc->sc_memr, RADEON_LVDS_GEN_CNTL, lvds_gen_cntl); lvds_gen_cntl &= ~(RADEON_LVDS_ON | RADEON_LVDS_EN); DELAY(200000); bus_write_4(sc->sc_memr, RADEON_LVDS_GEN_CNTL, lvds_gen_cntl); atibl_pll_wreg(sc, RADEON_PIXCLKS_CNTL, pixclks_cntl); DELAY(200000); } return (0); } static int atibl_getlevel(struct atibl_softc *sc) { uint32_t lvds_gen_cntl; int level; lvds_gen_cntl = bus_read_4(sc->sc_memr, RADEON_LVDS_GEN_CNTL); level = ((lvds_gen_cntl & RADEON_LVDS_BL_MOD_LEVEL_MASK) >> RADEON_LVDS_BL_MOD_LEVEL_SHIFT); if (level != 0) level = ((level - 5) * 2) / 5; return (level); } static int atibl_suspend(device_t dev) { struct atibl_softc *sc; sc = device_get_softc(dev); sc->sc_level = atibl_getlevel(sc); atibl_setlevel(sc, 0); return (0); } static int atibl_resume(device_t dev) { struct atibl_softc *sc; sc = device_get_softc(dev); atibl_setlevel(sc, sc->sc_level); return (0); } static int atibl_sysctl(SYSCTL_HANDLER_ARGS) { struct atibl_softc *sc; int newlevel, error; sc = arg1; newlevel = atibl_getlevel(sc); error = sysctl_handle_int(oidp, &newlevel, 0, req); if (error || !req->newptr) return (error); return (atibl_setlevel(sc, newlevel)); } diff --git a/sys/powerpc/powermac/nvbl.c b/sys/powerpc/powermac/nvbl.c index 79e41c66b34d..d60ff5a7a099 100644 --- a/sys/powerpc/powermac/nvbl.c +++ b/sys/powerpc/powermac/nvbl.c @@ -1,199 +1,199 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 Justin Hibbits * 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, 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. */ #include #include #include #include #include #include #include #include #include #include #define PCI_VENDOR_ID_NVIDIA 0x10de #define NVIDIA_BRIGHT_MIN (0x0ec) #define NVIDIA_BRIGHT_MAX (0x538) #define NVIDIA_BRIGHT_SCALE ((NVIDIA_BRIGHT_MAX - NVIDIA_BRIGHT_MIN)/100) /* nVidia's MMIO registers are at PCI BAR[0] */ #define NVIDIA_MMIO_PMC (0x0) #define NVIDIA_PMC_OFF (NVIDIA_MMIO_PMC + 0x10f0) #define NVIDIA_PMC_BL_SHIFT (16) #define NVIDIA_PMC_BL_EN (1U << 31) struct nvbl_softc { device_t dev; struct resource *sc_memr; }; static void nvbl_identify(driver_t *driver, device_t parent); static int nvbl_probe(device_t dev); static int nvbl_attach(device_t dev); static int nvbl_setlevel(struct nvbl_softc *sc, int newlevel); static int nvbl_getlevel(struct nvbl_softc *sc); static int nvbl_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t nvbl_methods[] = { /* Device interface */ DEVMETHOD(device_identify, nvbl_identify), DEVMETHOD(device_probe, nvbl_probe), DEVMETHOD(device_attach, nvbl_attach), {0, 0}, }; static driver_t nvbl_driver = { "backlight", nvbl_methods, sizeof(struct nvbl_softc) }; DRIVER_MODULE(nvbl, vgapci, nvbl_driver, 0, 0); static void nvbl_identify(driver_t *driver, device_t parent) { if (OF_finddevice("mac-io/backlight") == -1) return; - if (device_find_child(parent, "backlight", -1) == NULL) + if (device_find_child(parent, "backlight", DEVICE_UNIT_ANY) == NULL) device_add_child(parent, "backlight", DEVICE_UNIT_ANY); } static int nvbl_probe(device_t dev) { char control[8]; phandle_t handle; handle = OF_finddevice("mac-io/backlight"); if (handle == -1) return (ENXIO); if (OF_getprop(handle, "backlight-control", &control, sizeof(control)) < 0) return (ENXIO); if ((strcmp(control, "mnca") != 0) || pci_get_vendor(device_get_parent(dev)) != PCI_VENDOR_ID_NVIDIA) return (ENXIO); device_set_desc(dev, "PowerBook backlight for nVidia graphics"); return (0); } static int nvbl_attach(device_t dev) { struct nvbl_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; int rid; sc = device_get_softc(dev); rid = 0x10; /* BAR[0], for the MMIO register */ sc->sc_memr = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE | RF_SHAREABLE); if (sc->sc_memr == NULL) { device_printf(dev, "Could not alloc mem resource!\n"); return (ENXIO); } /* Turn on big-endian mode */ if (!(bus_read_stream_4(sc->sc_memr, NVIDIA_MMIO_PMC + 4) & 0x01000001)) { bus_write_stream_4(sc->sc_memr, NVIDIA_MMIO_PMC + 4, 0x01000001); mb(); } ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "level", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, nvbl_sysctl, "I", "Backlight level (0-100)"); return (0); } static int nvbl_setlevel(struct nvbl_softc *sc, int newlevel) { uint32_t pmc_reg; if (newlevel > 100) newlevel = 100; if (newlevel < 0) newlevel = 0; if (newlevel > 0) newlevel = (newlevel * NVIDIA_BRIGHT_SCALE) + NVIDIA_BRIGHT_MIN; pmc_reg = bus_read_stream_4(sc->sc_memr, NVIDIA_PMC_OFF) & 0xffff; pmc_reg |= NVIDIA_PMC_BL_EN | (newlevel << NVIDIA_PMC_BL_SHIFT); bus_write_stream_4(sc->sc_memr, NVIDIA_PMC_OFF, pmc_reg); return (0); } static int nvbl_getlevel(struct nvbl_softc *sc) { uint16_t level; level = bus_read_stream_2(sc->sc_memr, NVIDIA_PMC_OFF) & 0x7fff; if (level < NVIDIA_BRIGHT_MIN) return 0; level = (level - NVIDIA_BRIGHT_MIN) / NVIDIA_BRIGHT_SCALE; return (level); } static int nvbl_sysctl(SYSCTL_HANDLER_ARGS) { struct nvbl_softc *sc; int newlevel, error; sc = arg1; newlevel = nvbl_getlevel(sc); error = sysctl_handle_int(oidp, &newlevel, 0, req); if (error || !req->newptr) return (error); return (nvbl_setlevel(sc, newlevel)); } diff --git a/sys/powerpc/powernv/opal_i2c.c b/sys/powerpc/powernv/opal_i2c.c index 4e3bc4a7384a..b5c5f740f4a3 100644 --- a/sys/powerpc/powernv/opal_i2c.c +++ b/sys/powerpc/powernv/opal_i2c.c @@ -1,244 +1,245 @@ /*- * Copyright (c) 2017-2018 QCM Technologies. * Copyright (c) 2017-2018 Semihalf. * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "opal.h" #ifdef FDT #include #include #endif struct opal_i2c_softc { device_t dev; device_t iicbus; uint32_t opal_id; struct mtx sc_mtx; }; /* OPAL I2C request */ struct opal_i2c_request { uint8_t type; #define OPAL_I2C_RAW_READ 0 #define OPAL_I2C_RAW_WRITE 1 #define OPAL_I2C_SM_READ 2 #define OPAL_I2C_SM_WRITE 3 uint8_t flags; uint8_t subaddr_sz; /* Max 4 */ uint8_t reserved; uint16_t addr; /* 7 or 10 bit address */ uint16_t reserved2; uint32_t subaddr; /* Sub-address if any */ uint32_t size; /* Data size */ uint64_t buffer_pa; /* Buffer real address */ }; static int opal_i2c_attach(device_t); static int opal_i2c_callback(device_t, int, caddr_t); static int opal_i2c_probe(device_t); static int opal_i2c_transfer(device_t, struct iic_msg *, uint32_t); static int i2c_opal_send_request(uint32_t, struct opal_i2c_request *); static phandle_t opal_i2c_get_node(device_t bus, device_t dev); static device_method_t opal_i2c_methods[] = { /* Device interface */ DEVMETHOD(device_probe, opal_i2c_probe), DEVMETHOD(device_attach, opal_i2c_attach), /* iicbus interface */ DEVMETHOD(iicbus_callback, opal_i2c_callback), DEVMETHOD(iicbus_transfer, opal_i2c_transfer), DEVMETHOD(ofw_bus_get_node, opal_i2c_get_node), DEVMETHOD_END }; #define I2C_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define I2C_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define I2C_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ "i2c", MTX_DEF) static driver_t opal_i2c_driver = { "iichb", opal_i2c_methods, sizeof(struct opal_i2c_softc), }; static int opal_i2c_probe(device_t dev) { if (!(ofw_bus_is_compatible(dev, "ibm,opal-i2c"))) return (ENXIO); device_set_desc(dev, "opal-i2c"); return (0); } static int opal_i2c_attach(device_t dev) { struct opal_i2c_softc *sc; int len; sc = device_get_softc(dev); sc->dev = dev; len = OF_getproplen(ofw_bus_get_node(dev), "ibm,opal-id"); if (len <= 0) return (EINVAL); OF_getencprop(ofw_bus_get_node(dev), "ibm,opal-id", &sc->opal_id, len); - if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL) { + if ((sc->iicbus = device_add_child(dev, "iicbus", + DEVICE_UNIT_ANY)) == NULL) { device_printf(dev, "could not allocate iicbus instance\n"); return (EINVAL); } I2C_LOCK_INIT(sc); bus_attach_children(dev); return (0); } static int opal_get_async_rc(struct opal_msg msg) { if (msg.msg_type != OPAL_MSG_ASYNC_COMP) return OPAL_PARAMETER; else return htobe64(msg.params[1]); } static int i2c_opal_send_request(uint32_t bus_id, struct opal_i2c_request *req) { struct opal_msg msg; uint64_t token; int rc; token = opal_alloc_async_token(); memset(&msg, 0, sizeof(msg)); rc = opal_call(OPAL_I2C_REQUEST, token, bus_id, vtophys(req)); if (rc != OPAL_ASYNC_COMPLETION) goto out; rc = opal_wait_completion(&msg, sizeof(msg), token); if (rc != OPAL_SUCCESS) goto out; rc = opal_get_async_rc(msg); out: opal_free_async_token(token); return (rc); } static int opal_i2c_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct opal_i2c_softc *sc; int i, err = 0; struct opal_i2c_request req; sc = device_get_softc(dev); memset(&req, 0, sizeof(req)); I2C_LOCK(sc); for (i = 0; i < nmsgs; i++) { req.type = (msgs[i].flags & IIC_M_RD) ? OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE; req.addr = htobe16(msgs[i].slave >> 1); req.size = htobe32(msgs[i].len); req.buffer_pa = htobe64(pmap_kextract((uint64_t)msgs[i].buf)); err = i2c_opal_send_request(sc->opal_id, &req); } I2C_UNLOCK(sc); return (err); } static int opal_i2c_callback(device_t dev, int index, caddr_t data) { int error = 0; switch (index) { case IIC_REQUEST_BUS: break; case IIC_RELEASE_BUS: break; default: error = EINVAL; } return (error); } static phandle_t opal_i2c_get_node(device_t bus, device_t dev) { /* Share controller node with iibus device. */ return (ofw_bus_get_node(bus)); } DRIVER_MODULE(opal_i2c, opal_i2cm, opal_i2c_driver, NULL, NULL); DRIVER_MODULE(iicbus, opal_i2c, iicbus_driver, NULL, NULL); MODULE_DEPEND(opal_i2c, iicbus, 1, 1, 1); diff --git a/sys/powerpc/ps3/ps3bus.c b/sys/powerpc/ps3/ps3bus.c index 8aa0a806f116..dc4026cb9a5e 100644 --- a/sys/powerpc/ps3/ps3bus.c +++ b/sys/powerpc/ps3/ps3bus.c @@ -1,780 +1,780 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (C) 2010 Nathan Whitehorn * Copyright (C) 2011 glevand (geoffrey.levand@mail.ru) * 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, 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 TOOLS GMBH 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ps3bus.h" #include "ps3-hvcall.h" #include "iommu_if.h" #include "clock_if.h" static void ps3bus_identify(driver_t *, device_t); static int ps3bus_probe(device_t); static int ps3bus_attach(device_t); static int ps3bus_print_child(device_t dev, device_t child); static int ps3bus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result); static struct rman *ps3bus_get_rman(device_t bus, int type, u_int flags); static struct resource *ps3bus_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); static int ps3bus_map_resource(device_t bus, device_t child, struct resource *r, struct resource_map_request *argsp, struct resource_map *map); static int ps3bus_unmap_resource(device_t bus, device_t child, struct resource *r, struct resource_map *map); static bus_dma_tag_t ps3bus_get_dma_tag(device_t dev, device_t child); static int ps3_iommu_map(device_t dev, bus_dma_segment_t *segs, int *nsegs, bus_addr_t min, bus_addr_t max, bus_size_t alignment, bus_addr_t boundary, void *cookie); static int ps3_iommu_unmap(device_t dev, bus_dma_segment_t *segs, int nsegs, void *cookie); static int ps3_gettime(device_t dev, struct timespec *ts); static int ps3_settime(device_t dev, struct timespec *ts); struct ps3bus_devinfo { int bus; int dev; uint64_t bustype; uint64_t devtype; int busidx; int devidx; struct resource_list resources; bus_dma_tag_t dma_tag; struct mtx iommu_mtx; bus_addr_t dma_base[4]; }; static MALLOC_DEFINE(M_PS3BUS, "ps3bus", "PS3 system bus device information"); enum ps3bus_irq_type { SB_IRQ = 2, OHCI_IRQ = 3, EHCI_IRQ = 4, }; enum ps3bus_reg_type { OHCI_REG = 3, EHCI_REG = 4, }; static device_method_t ps3bus_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ps3bus_identify), DEVMETHOD(device_probe, ps3bus_probe), DEVMETHOD(device_attach, ps3bus_attach), /* Bus interface */ DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_get_dma_tag, ps3bus_get_dma_tag), DEVMETHOD(bus_print_child, ps3bus_print_child), DEVMETHOD(bus_read_ivar, ps3bus_read_ivar), DEVMETHOD(bus_get_rman, ps3bus_get_rman), DEVMETHOD(bus_alloc_resource, ps3bus_alloc_resource), DEVMETHOD(bus_adjust_resource, bus_generic_rman_adjust_resource), DEVMETHOD(bus_activate_resource, bus_generic_rman_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_rman_deactivate_resource), DEVMETHOD(bus_map_resource, ps3bus_map_resource), DEVMETHOD(bus_unmap_resource, ps3bus_unmap_resource), DEVMETHOD(bus_release_resource, bus_generic_rman_release_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), /* IOMMU interface */ DEVMETHOD(iommu_map, ps3_iommu_map), DEVMETHOD(iommu_unmap, ps3_iommu_unmap), /* Clock interface */ DEVMETHOD(clock_gettime, ps3_gettime), DEVMETHOD(clock_settime, ps3_settime), DEVMETHOD_END }; struct ps3bus_softc { struct rman sc_mem_rman; struct rman sc_intr_rman; struct mem_region *regions; int rcount; }; static driver_t ps3bus_driver = { "ps3bus", ps3bus_methods, sizeof(struct ps3bus_softc) }; DRIVER_MODULE(ps3bus, nexus, ps3bus_driver, 0, 0); static void ps3bus_identify(driver_t *driver, device_t parent) { if (strcmp(installed_platform(), "ps3") != 0) return; - if (device_find_child(parent, "ps3bus", -1) == NULL) + if (device_find_child(parent, "ps3bus", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "ps3bus", 0); } static int ps3bus_probe(device_t dev) { /* Do not attach to any OF nodes that may be present */ device_set_desc(dev, "Playstation 3 System Bus"); return (BUS_PROBE_NOWILDCARD); } static void ps3bus_resources_init(struct rman *rm, int bus_index, int dev_index, struct ps3bus_devinfo *dinfo) { uint64_t irq_type, irq, outlet; uint64_t reg_type, paddr, len; uint64_t ppe, junk; int i, result; int thread; resource_list_init(&dinfo->resources); lv1_get_logical_ppe_id(&ppe); thread = 32 - fls(mfctrl()); /* Scan for interrupts */ for (i = 0; i < 10; i++) { result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("intr") | i, 0, &irq_type, &irq); if (result != 0) break; switch (irq_type) { case SB_IRQ: lv1_construct_event_receive_port(&outlet); lv1_connect_irq_plug_ext(ppe, thread, outlet, outlet, 0); lv1_connect_interrupt_event_receive_port(dinfo->bus, dinfo->dev, outlet, irq); break; case OHCI_IRQ: case EHCI_IRQ: lv1_construct_io_irq_outlet(irq, &outlet); lv1_connect_irq_plug_ext(ppe, thread, outlet, outlet, 0); break; default: printf("Unknown IRQ type %ld for device %d.%d\n", irq_type, dinfo->bus, dinfo->dev); break; } resource_list_add(&dinfo->resources, SYS_RES_IRQ, i, outlet, outlet, 1); } /* Scan for registers */ for (i = 0; i < 10; i++) { result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("reg") | i, lv1_repository_string("type"), ®_type, &junk); if (result != 0) break; result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("reg") | i, lv1_repository_string("data"), &paddr, &len); result = lv1_map_device_mmio_region(dinfo->bus, dinfo->dev, paddr, len, 12 /* log_2(4 KB) */, &paddr); if (result != 0) { printf("Mapping registers failed for device " "%d.%d (%ld.%ld): %d\n", dinfo->bus, dinfo->dev, dinfo->bustype, dinfo->devtype, result); continue; } rman_manage_region(rm, paddr, paddr + len - 1); resource_list_add(&dinfo->resources, SYS_RES_MEMORY, i, paddr, paddr + len, len); } } static void ps3bus_resources_init_by_type(struct rman *rm, int bus_index, int dev_index, uint64_t irq_type, uint64_t reg_type, struct ps3bus_devinfo *dinfo) { uint64_t _irq_type, irq, outlet; uint64_t _reg_type, paddr, len; uint64_t ppe, junk; int i, result; int thread; resource_list_init(&dinfo->resources); lv1_get_logical_ppe_id(&ppe); thread = 32 - fls(mfctrl()); /* Scan for interrupts */ for (i = 0; i < 10; i++) { result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("intr") | i, 0, &_irq_type, &irq); if (result != 0) break; if (_irq_type != irq_type) continue; lv1_construct_io_irq_outlet(irq, &outlet); lv1_connect_irq_plug_ext(ppe, thread, outlet, outlet, 0); resource_list_add(&dinfo->resources, SYS_RES_IRQ, i, outlet, outlet, 1); } /* Scan for registers */ for (i = 0; i < 10; i++) { result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("reg") | i, lv1_repository_string("type"), &_reg_type, &junk); if (result != 0) break; if (_reg_type != reg_type) continue; result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("reg") | i, lv1_repository_string("data"), &paddr, &len); result = lv1_map_device_mmio_region(dinfo->bus, dinfo->dev, paddr, len, 12 /* log_2(4 KB) */, &paddr); if (result != 0) { printf("Mapping registers failed for device " "%d.%d (%ld.%ld): %d\n", dinfo->bus, dinfo->dev, dinfo->bustype, dinfo->devtype, result); break; } rman_manage_region(rm, paddr, paddr + len - 1); resource_list_add(&dinfo->resources, SYS_RES_MEMORY, i, paddr, paddr + len, len); } } static int ps3bus_attach(device_t self) { struct ps3bus_softc *sc; struct ps3bus_devinfo *dinfo; int bus_index, dev_index, result; uint64_t bustype, bus, devs; uint64_t dev, devtype; uint64_t junk; device_t cdev; sc = device_get_softc(self); sc->sc_mem_rman.rm_type = RMAN_ARRAY; sc->sc_mem_rman.rm_descr = "PS3Bus Memory Mapped I/O"; sc->sc_intr_rman.rm_type = RMAN_ARRAY; sc->sc_intr_rman.rm_descr = "PS3Bus Interrupts"; rman_init(&sc->sc_mem_rman); rman_init(&sc->sc_intr_rman); rman_manage_region(&sc->sc_intr_rman, 0, ~0); /* Get memory regions for DMA */ mem_regions(&sc->regions, &sc->rcount, NULL, NULL); /* * Probe all the PS3's buses. */ for (bus_index = 0; bus_index < 5; bus_index++) { result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("type"), 0, 0, &bustype, &junk); if (result != 0) continue; result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("id"), 0, 0, &bus, &junk); if (result != 0) continue; result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("num_dev"), 0, 0, &devs, &junk); for (dev_index = 0; dev_index < devs; dev_index++) { result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("type"), 0, &devtype, &junk); if (result != 0) continue; result = lv1_get_repository_node_value(PS3_LPAR_ID_PME, (lv1_repository_string("bus") >> 32) | bus_index, lv1_repository_string("dev") | dev_index, lv1_repository_string("id"), 0, &dev, &junk); if (result != 0) continue; switch (devtype) { case PS3_DEVTYPE_USB: /* USB device has OHCI and EHCI USB host controllers */ lv1_open_device(bus, dev, 0); /* OHCI host controller */ dinfo = malloc(sizeof(*dinfo), M_PS3BUS, M_WAITOK | M_ZERO); dinfo->bus = bus; dinfo->dev = dev; dinfo->bustype = bustype; dinfo->devtype = devtype; dinfo->busidx = bus_index; dinfo->devidx = dev_index; ps3bus_resources_init_by_type(&sc->sc_mem_rman, bus_index, dev_index, OHCI_IRQ, OHCI_REG, dinfo); cdev = device_add_child(self, "ohci", DEVICE_UNIT_ANY); if (cdev == NULL) { device_printf(self, "device_add_child failed\n"); free(dinfo, M_PS3BUS); continue; } mtx_init(&dinfo->iommu_mtx, "iommu", NULL, MTX_DEF); device_set_ivars(cdev, dinfo); /* EHCI host controller */ dinfo = malloc(sizeof(*dinfo), M_PS3BUS, M_WAITOK | M_ZERO); dinfo->bus = bus; dinfo->dev = dev; dinfo->bustype = bustype; dinfo->devtype = devtype; dinfo->busidx = bus_index; dinfo->devidx = dev_index; ps3bus_resources_init_by_type(&sc->sc_mem_rman, bus_index, dev_index, EHCI_IRQ, EHCI_REG, dinfo); cdev = device_add_child(self, "ehci", DEVICE_UNIT_ANY); if (cdev == NULL) { device_printf(self, "device_add_child failed\n"); free(dinfo, M_PS3BUS); continue; } mtx_init(&dinfo->iommu_mtx, "iommu", NULL, MTX_DEF); device_set_ivars(cdev, dinfo); break; default: dinfo = malloc(sizeof(*dinfo), M_PS3BUS, M_WAITOK | M_ZERO); dinfo->bus = bus; dinfo->dev = dev; dinfo->bustype = bustype; dinfo->devtype = devtype; dinfo->busidx = bus_index; dinfo->devidx = dev_index; if (dinfo->bustype == PS3_BUSTYPE_SYSBUS || dinfo->bustype == PS3_BUSTYPE_STORAGE) lv1_open_device(bus, dev, 0); ps3bus_resources_init(&sc->sc_mem_rman, bus_index, dev_index, dinfo); cdev = device_add_child(self, NULL, DEVICE_UNIT_ANY); if (cdev == NULL) { device_printf(self, "device_add_child failed\n"); free(dinfo, M_PS3BUS); continue; } mtx_init(&dinfo->iommu_mtx, "iommu", NULL, MTX_DEF); device_set_ivars(cdev, dinfo); } } } clock_register(self, 1000); bus_attach_children(self); return (0); } static int ps3bus_print_child(device_t dev, device_t child) { struct ps3bus_devinfo *dinfo = device_get_ivars(child); int retval = 0; retval += bus_print_child_header(dev, child); retval += resource_list_print_type(&dinfo->resources, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(&dinfo->resources, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } static int ps3bus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct ps3bus_devinfo *dinfo = device_get_ivars(child); switch (which) { case PS3BUS_IVAR_BUS: *result = dinfo->bus; break; case PS3BUS_IVAR_DEVICE: *result = dinfo->dev; break; case PS3BUS_IVAR_BUSTYPE: *result = dinfo->bustype; break; case PS3BUS_IVAR_DEVTYPE: *result = dinfo->devtype; break; case PS3BUS_IVAR_BUSIDX: *result = dinfo->busidx; break; case PS3BUS_IVAR_DEVIDX: *result = dinfo->devidx; break; default: return (EINVAL); } return (0); } static struct rman * ps3bus_get_rman(device_t bus, int type, u_int flags) { struct ps3bus_softc *sc; sc = device_get_softc(bus); switch (type) { case SYS_RES_MEMORY: return (&sc->sc_mem_rman); case SYS_RES_IRQ: return (&sc->sc_intr_rman); default: return (NULL); } } static struct resource * ps3bus_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct ps3bus_devinfo *dinfo; rman_res_t adjstart, adjend, adjcount; struct resource_list_entry *rle; dinfo = device_get_ivars(child); switch (type) { case SYS_RES_MEMORY: rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY, *rid); if (rle == NULL) { device_printf(bus, "no rle for %s memory %d\n", device_get_nameunit(child), *rid); return (NULL); } if (start < rle->start) adjstart = rle->start; else if (start > rle->end) adjstart = rle->end; else adjstart = start; if (end < rle->start) adjend = rle->start; else if (end > rle->end) adjend = rle->end; else adjend = end; adjcount = adjend - adjstart; break; case SYS_RES_IRQ: rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, *rid); adjstart = rle->start; adjcount = ulmax(count, rle->count); adjend = ulmax(rle->end, rle->start + adjcount - 1); break; default: device_printf(bus, "unknown resource request from %s\n", device_get_nameunit(child)); return (NULL); } return (bus_generic_rman_alloc_resource(bus, child, type, rid, adjstart, adjend, adjcount, flags)); } static int ps3bus_map_resource(device_t bus, device_t child, struct resource *r, struct resource_map_request *argsp, struct resource_map *map) { struct resource_map_request args; rman_res_t length, start; int error; /* Resources must be active to be mapped. */ if (!(rman_get_flags(r) & RF_ACTIVE)) return (ENXIO); /* Mappings are only supported on memory resources. */ switch (rman_get_type(r)) { case SYS_RES_MEMORY: break; default: return (EINVAL); } resource_init_map_request(&args); error = resource_validate_map_request(r, argsp, &args, &start, &length); if (error) return (error); if (bootverbose) printf("ps3 mapdev: start %jx, len %jd\n", start, length); map->r_vaddr = pmap_mapdev_attr(start, length, args.memattr); if (map->r_vaddr == NULL) return (ENOMEM); map->r_bustag = &bs_be_tag; map->r_bushandle = (vm_offset_t)map->r_vaddr; map->r_size = length; return (0); } static int ps3bus_unmap_resource(device_t bus, device_t child, struct resource *r, struct resource_map *map) { switch (rman_get_type(r)) { case SYS_RES_MEMORY: pmap_unmapdev(map->r_vaddr, map->r_size); return (0); default: return (EINVAL); } } static bus_dma_tag_t ps3bus_get_dma_tag(device_t dev, device_t child) { struct ps3bus_devinfo *dinfo = device_get_ivars(child); struct ps3bus_softc *sc = device_get_softc(dev); int i, err, flags, pagesize; if (dinfo->bustype != PS3_BUSTYPE_SYSBUS && dinfo->bustype != PS3_BUSTYPE_STORAGE) return (bus_get_dma_tag(dev)); mtx_lock(&dinfo->iommu_mtx); if (dinfo->dma_tag != NULL) { mtx_unlock(&dinfo->iommu_mtx); return (dinfo->dma_tag); } flags = 0; /* 32-bit mode */ if (dinfo->bustype == PS3_BUSTYPE_SYSBUS && dinfo->devtype == PS3_DEVTYPE_USB) flags = 2; /* 8-bit mode */ pagesize = 24; /* log_2(16 MB) */ if (dinfo->bustype == PS3_BUSTYPE_STORAGE) pagesize = 12; /* 4 KB */ for (i = 0; i < sc->rcount; i++) { err = lv1_allocate_device_dma_region(dinfo->bus, dinfo->dev, sc->regions[i].mr_size, pagesize, flags, &dinfo->dma_base[i]); if (err != 0) { device_printf(child, "could not allocate DMA region %d: %d\n", i, err); goto fail; } err = lv1_map_device_dma_region(dinfo->bus, dinfo->dev, sc->regions[i].mr_start, dinfo->dma_base[i], sc->regions[i].mr_size, 0xf800000000000800UL /* Cell Handbook Figure 7.3.4.1 */); if (err != 0) { device_printf(child, "could not map DMA region %d: %d\n", i, err); goto fail; } } err = bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE, 0, BUS_SPACE_MAXSIZE, 0, NULL, NULL, &dinfo->dma_tag); /* * Note: storage devices have IOMMU mappings set up by the hypervisor, * but use physical, non-translated addresses. The above IOMMU * initialization is necessary for the hypervisor to be able to set up * the mappings, but actual DMA mappings should not use the IOMMU * routines. */ if (dinfo->bustype != PS3_BUSTYPE_STORAGE) bus_dma_tag_set_iommu(dinfo->dma_tag, dev, dinfo); fail: mtx_unlock(&dinfo->iommu_mtx); if (err) return (NULL); return (dinfo->dma_tag); } static int ps3_iommu_map(device_t dev, bus_dma_segment_t *segs, int *nsegs, bus_addr_t min, bus_addr_t max, bus_size_t alignment, bus_addr_t boundary, void *cookie) { struct ps3bus_devinfo *dinfo = cookie; struct ps3bus_softc *sc = device_get_softc(dev); int i, j; for (i = 0; i < *nsegs; i++) { for (j = 0; j < sc->rcount; j++) { if (segs[i].ds_addr >= sc->regions[j].mr_start && segs[i].ds_addr < sc->regions[j].mr_start + sc->regions[j].mr_size) break; } KASSERT(j < sc->rcount, ("Trying to map address %#lx not in physical memory", segs[i].ds_addr)); segs[i].ds_addr = dinfo->dma_base[j] + (segs[i].ds_addr - sc->regions[j].mr_start); } return (0); } static int ps3_iommu_unmap(device_t dev, bus_dma_segment_t *segs, int nsegs, void *cookie) { return (0); } #define Y2K 946684800 static int ps3_gettime(device_t dev, struct timespec *ts) { uint64_t rtc, tb; int result; result = lv1_get_rtc(&rtc, &tb); if (result) return (result); ts->tv_sec = rtc + Y2K; ts->tv_nsec = 0; return (0); } static int ps3_settime(device_t dev, struct timespec *ts) { return (-1); } diff --git a/sys/powerpc/ps3/ps3pic.c b/sys/powerpc/ps3/ps3pic.c index c947b3e3e502..5463f6a6e3b6 100644 --- a/sys/powerpc/ps3/ps3pic.c +++ b/sys/powerpc/ps3/ps3pic.c @@ -1,246 +1,246 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2010 Nathan Whitehorn * * 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, 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ps3-hvcall.h" #include "pic_if.h" static void ps3pic_identify(driver_t *driver, device_t parent); static int ps3pic_probe(device_t); static int ps3pic_attach(device_t); static void ps3pic_dispatch(device_t, struct trapframe *); static void ps3pic_enable(device_t, u_int, u_int, void **); static void ps3pic_eoi(device_t, u_int, void *); static void ps3pic_ipi(device_t, u_int); static void ps3pic_mask(device_t, u_int, void *); static void ps3pic_unmask(device_t, u_int, void *); struct ps3pic_softc { volatile uint64_t *bitmap_thread0; volatile uint64_t *mask_thread0; volatile uint64_t *bitmap_thread1; volatile uint64_t *mask_thread1; uint64_t sc_ipi_outlet[2]; uint64_t sc_ipi_virq; int sc_vector[64]; }; static device_method_t ps3pic_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ps3pic_identify), DEVMETHOD(device_probe, ps3pic_probe), DEVMETHOD(device_attach, ps3pic_attach), /* PIC interface */ DEVMETHOD(pic_dispatch, ps3pic_dispatch), DEVMETHOD(pic_enable, ps3pic_enable), DEVMETHOD(pic_eoi, ps3pic_eoi), DEVMETHOD(pic_ipi, ps3pic_ipi), DEVMETHOD(pic_mask, ps3pic_mask), DEVMETHOD(pic_unmask, ps3pic_unmask), { 0, 0 }, }; static driver_t ps3pic_driver = { "ps3pic", ps3pic_methods, sizeof(struct ps3pic_softc) }; DRIVER_MODULE(ps3pic, nexus, ps3pic_driver, 0, 0); static MALLOC_DEFINE(M_PS3PIC, "ps3pic", "PS3 PIC"); static void ps3pic_identify(driver_t *driver, device_t parent) { if (strcmp(installed_platform(), "ps3") != 0) return; - if (device_find_child(parent, "ps3pic", -1) == NULL) + if (device_find_child(parent, "ps3pic", DEVICE_UNIT_ANY) == NULL) BUS_ADD_CHILD(parent, 0, "ps3pic", 0); } static int ps3pic_probe(device_t dev) { device_set_desc(dev, "Playstation 3 interrupt controller"); return (BUS_PROBE_NOWILDCARD); } static int ps3pic_attach(device_t dev) { struct ps3pic_softc *sc; uint64_t ppe; int thread; sc = device_get_softc(dev); sc->bitmap_thread0 = contigmalloc(128 /* 512 bits * 2 */, M_PS3PIC, M_NOWAIT | M_ZERO, 0, BUS_SPACE_MAXADDR, 64 /* alignment */, PAGE_SIZE /* boundary */); sc->mask_thread0 = sc->bitmap_thread0 + 4; sc->bitmap_thread1 = sc->bitmap_thread0 + 8; sc->mask_thread1 = sc->bitmap_thread0 + 12; lv1_get_logical_ppe_id(&ppe); thread = 32 - fls(mfctrl()); lv1_configure_irq_state_bitmap(ppe, thread, vtophys(sc->bitmap_thread0)); sc->sc_ipi_virq = 63; #ifdef SMP lv1_configure_irq_state_bitmap(ppe, !thread, vtophys(sc->bitmap_thread1)); /* Map both IPIs to the same VIRQ to avoid changes in intr_machdep */ lv1_construct_event_receive_port(&sc->sc_ipi_outlet[0]); lv1_connect_irq_plug_ext(ppe, thread, sc->sc_ipi_virq, sc->sc_ipi_outlet[0], 0); lv1_construct_event_receive_port(&sc->sc_ipi_outlet[1]); lv1_connect_irq_plug_ext(ppe, !thread, sc->sc_ipi_virq, sc->sc_ipi_outlet[1], 0); #endif powerpc_register_pic(dev, 0, sc->sc_ipi_virq, 1, FALSE); return (0); } /* * PIC I/F methods. */ static void ps3pic_dispatch(device_t dev, struct trapframe *tf) { uint64_t bitmap, mask; int irq; struct ps3pic_softc *sc; sc = device_get_softc(dev); if (PCPU_GET(cpuid) == 0) { bitmap = atomic_readandclear_64(&sc->bitmap_thread0[0]); mask = sc->mask_thread0[0]; } else { bitmap = atomic_readandclear_64(&sc->bitmap_thread1[0]); mask = sc->mask_thread1[0]; } powerpc_sync(); while ((irq = ffsl(bitmap & mask) - 1) != -1) { bitmap &= ~(1UL << irq); powerpc_dispatch_intr(sc->sc_vector[63 - irq], tf); } } static void ps3pic_enable(device_t dev, u_int irq, u_int vector, void **priv) { struct ps3pic_softc *sc; sc = device_get_softc(dev); sc->sc_vector[irq] = vector; ps3pic_unmask(dev, irq, priv); } static void ps3pic_eoi(device_t dev, u_int irq, void *priv) { uint64_t ppe; int thread; lv1_get_logical_ppe_id(&ppe); thread = 32 - fls(mfctrl()); lv1_end_of_interrupt_ext(ppe, thread, irq); } static void ps3pic_ipi(device_t dev, u_int cpu) { struct ps3pic_softc *sc; sc = device_get_softc(dev); lv1_send_event_locally(sc->sc_ipi_outlet[cpu]); } static void ps3pic_mask(device_t dev, u_int irq, void *priv) { struct ps3pic_softc *sc; uint64_t ppe; sc = device_get_softc(dev); /* Do not mask IPIs! */ if (irq == sc->sc_ipi_virq) return; atomic_clear_64(&sc->mask_thread0[0], 1UL << (63 - irq)); atomic_clear_64(&sc->mask_thread1[0], 1UL << (63 - irq)); lv1_get_logical_ppe_id(&ppe); lv1_did_update_interrupt_mask(ppe, 0); lv1_did_update_interrupt_mask(ppe, 1); } static void ps3pic_unmask(device_t dev, u_int irq, void *priv) { struct ps3pic_softc *sc; uint64_t ppe; sc = device_get_softc(dev); atomic_set_64(&sc->mask_thread0[0], 1UL << (63 - irq)); atomic_set_64(&sc->mask_thread1[0], 1UL << (63 - irq)); lv1_get_logical_ppe_id(&ppe); lv1_did_update_interrupt_mask(ppe, 0); lv1_did_update_interrupt_mask(ppe, 1); } diff --git a/sys/riscv/riscv/intc.c b/sys/riscv/riscv/intc.c index 248175e8bea3..b700b9c97793 100644 --- a/sys/riscv/riscv/intc.c +++ b/sys/riscv/riscv/intc.c @@ -1,311 +1,311 @@ /*- * Copyright (c) 2015-2017 Ruslan Bukin * All rights reserved. * Copyright (c) 2021 Jessica Clarke * * Portions of this software were developed by SRI International and the * University of Cambridge Computer Laboratory under DARPA/AFRL contract * FA8750-10-C-0237 ("CTSRD"), as part of the DARPA CRASH research programme. * * Portions of this software were developed by the University of Cambridge * Computer Laboratory as part of the CTSRD Project, with support from the * UK Higher Education Innovation Fund (HEIF). * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pic_if.h" #define INTC_NIRQS 16 struct intc_irqsrc { struct intr_irqsrc isrc; u_int irq; }; struct intc_softc { device_t dev; struct intc_irqsrc isrcs[INTC_NIRQS]; }; static int intc_intr(void *arg); static phandle_t intc_ofw_find(device_t dev, uint32_t hartid) { phandle_t node; pcell_t reg; node = OF_finddevice("/cpus"); if (node == -1) { device_printf(dev, "Can't find cpus node\n"); return ((phandle_t)-1); } for (node = OF_child(node); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; if (!ofw_bus_node_is_compatible(node, "riscv")) continue; if (OF_searchencprop(node, "reg", ®, sizeof(reg)) == -1) continue; if (reg == hartid) break; } if (node == 0) { device_printf(dev, "Can't find boot cpu node\n"); return ((phandle_t)-1); } for (node = OF_child(node); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; if (ofw_bus_node_is_compatible(node, "riscv,cpu-intc")) break; } if (node == 0) { device_printf(dev, "Can't find boot cpu local interrupt controller\n"); return ((phandle_t)-1); } return (node); } static void intc_identify(driver_t *driver, device_t parent) { device_t dev; phandle_t node; - if (device_find_child(parent, "intc", -1) != NULL) + if (device_find_child(parent, "intc", DEVICE_UNIT_ANY) != NULL) return; node = intc_ofw_find(parent, PCPU_GET(hart)); if (node == -1) return; dev = simplebus_add_device(parent, node, 0, "intc", -1, NULL); if (dev == NULL) device_printf(parent, "Can't add intc child\n"); } static int intc_probe(device_t dev) { device_set_desc(dev, "RISC-V Local Interrupt Controller"); return (BUS_PROBE_NOWILDCARD); } static int intc_attach(device_t dev) { struct intc_irqsrc *isrcs; struct intc_softc *sc; struct intr_pic *pic; const char *name; phandle_t xref; u_int flags; int i, error; sc = device_get_softc(dev); sc->dev = dev; name = device_get_nameunit(dev); xref = OF_xref_from_node(ofw_bus_get_node(dev)); isrcs = sc->isrcs; for (i = 0; i < INTC_NIRQS; i++) { isrcs[i].irq = i; flags = i == IRQ_SOFTWARE_SUPERVISOR ? INTR_ISRCF_IPI : INTR_ISRCF_PPI; error = intr_isrc_register(&isrcs[i].isrc, sc->dev, flags, "%s,%u", name, i); if (error != 0) { device_printf(dev, "Can't register interrupt %d\n", i); return (error); } } pic = intr_pic_register(sc->dev, xref); if (pic == NULL) return (ENXIO); return (intr_pic_claim_root(sc->dev, xref, intc_intr, sc, INTR_ROOT_IRQ)); } static void intc_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct intc_irqsrc *)isrc)->irq; if (irq >= INTC_NIRQS) panic("%s: Unsupported IRQ %u", __func__, irq); csr_clear(sie, 1ul << irq); } static void intc_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct intc_irqsrc *)isrc)->irq; if (irq >= INTC_NIRQS) panic("%s: Unsupported IRQ %u", __func__, irq); csr_set(sie, 1ul << irq); } static int intc_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { struct intr_map_data_fdt *daf; struct intc_softc *sc; sc = device_get_softc(dev); if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); daf = (struct intr_map_data_fdt *)data; if (daf->ncells != 1 || daf->cells[0] >= INTC_NIRQS) return (EINVAL); *isrcp = &sc->isrcs[daf->cells[0]].isrc; return (0); } static int intc_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { if (isrc->isrc_flags & INTR_ISRCF_PPI) CPU_SET(PCPU_GET(cpuid), &isrc->isrc_cpu); return (0); } #ifdef SMP static void intc_init_secondary(device_t dev, uint32_t rootnum) { struct intc_softc *sc; struct intr_irqsrc *isrc; u_int cpu, irq; sc = device_get_softc(dev); cpu = PCPU_GET(cpuid); /* Unmask attached interrupts */ for (irq = 0; irq < INTC_NIRQS; irq++) { isrc = &sc->isrcs[irq].isrc; if (intr_isrc_init_on_cpu(isrc, cpu)) intc_enable_intr(dev, isrc); } } #endif static int intc_intr(void *arg) { struct trapframe *frame; struct intc_softc *sc; uint64_t active_irq; struct intc_irqsrc *src; sc = arg; frame = curthread->td_intr_frame; KASSERT((frame->tf_scause & SCAUSE_INTR) != 0, ("%s: not an interrupt frame", __func__)); active_irq = frame->tf_scause & SCAUSE_CODE; if (active_irq >= INTC_NIRQS) return (FILTER_HANDLED); src = &sc->isrcs[active_irq]; if (intr_isrc_dispatch(&src->isrc, frame) != 0) { intc_disable_intr(sc->dev, &src->isrc); device_printf(sc->dev, "Stray irq %lu disabled\n", active_irq); } return (FILTER_HANDLED); } static device_method_t intc_methods[] = { /* Device interface */ DEVMETHOD(device_identify, intc_identify), DEVMETHOD(device_probe, intc_probe), DEVMETHOD(device_attach, intc_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, intc_disable_intr), DEVMETHOD(pic_enable_intr, intc_enable_intr), DEVMETHOD(pic_map_intr, intc_map_intr), DEVMETHOD(pic_setup_intr, intc_setup_intr), #ifdef SMP DEVMETHOD(pic_init_secondary, intc_init_secondary), #endif DEVMETHOD_END }; DEFINE_CLASS_0(intc, intc_driver, intc_methods, sizeof(struct intc_softc)); EARLY_DRIVER_MODULE(intc, ofwbus, intc_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_FIRST); diff --git a/sys/riscv/riscv/sbi.c b/sys/riscv/riscv/sbi.c index b0a05bd88ef1..2d8e96a22bbe 100644 --- a/sys/riscv/riscv/sbi.c +++ b/sys/riscv/riscv/sbi.c @@ -1,437 +1,437 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Mitchell Horne * Copyright (c) 2021 Jessica Clarke * * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #include #include #include #include #include #include #include #include #include #include /* SBI Implementation-Specific Definitions */ #define OPENSBI_VERSION_MAJOR_OFFSET 16 #define OPENSBI_VERSION_MINOR_MASK 0xFFFF struct sbi_softc { device_t dev; }; struct sbi_devinfo { struct resource_list rl; }; static struct sbi_softc *sbi_softc = NULL; static u_long sbi_spec_version; static u_long sbi_impl_id; static u_long sbi_impl_version; static bool has_time_extension = false; static bool has_ipi_extension = false; static bool has_rfnc_extension = false; static bool has_srst_extension = false; static struct sbi_ret sbi_get_spec_version(void) { return (SBI_CALL0(SBI_EXT_ID_BASE, SBI_BASE_GET_SPEC_VERSION)); } static struct sbi_ret sbi_get_impl_id(void) { return (SBI_CALL0(SBI_EXT_ID_BASE, SBI_BASE_GET_IMPL_ID)); } static struct sbi_ret sbi_get_impl_version(void) { return (SBI_CALL0(SBI_EXT_ID_BASE, SBI_BASE_GET_IMPL_VERSION)); } static struct sbi_ret sbi_get_mvendorid(void) { return (SBI_CALL0(SBI_EXT_ID_BASE, SBI_BASE_GET_MVENDORID)); } static struct sbi_ret sbi_get_marchid(void) { return (SBI_CALL0(SBI_EXT_ID_BASE, SBI_BASE_GET_MARCHID)); } static struct sbi_ret sbi_get_mimpid(void) { return (SBI_CALL0(SBI_EXT_ID_BASE, SBI_BASE_GET_MIMPID)); } static void sbi_shutdown_final(void *dummy __unused, int howto) { if ((howto & RB_POWEROFF) != 0) sbi_system_reset(SBI_SRST_TYPE_SHUTDOWN, SBI_SRST_REASON_NONE); } void sbi_system_reset(u_long reset_type, u_long reset_reason) { /* Use the SRST extension, if available. */ if (has_srst_extension) { (void)SBI_CALL2(SBI_EXT_ID_SRST, SBI_SRST_SYSTEM_RESET, reset_type, reset_reason); } (void)SBI_CALL0(SBI_SHUTDOWN, 0); } void sbi_print_version(void) { u_int major; u_int minor; /* For legacy SBI implementations. */ if (sbi_spec_version == 0) { printf("SBI: Unknown (Legacy) Implementation\n"); printf("SBI Specification Version: 0.1\n"); return; } switch (sbi_impl_id) { case (SBI_IMPL_ID_BBL): printf("SBI: Berkely Boot Loader %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_OPENSBI): major = sbi_impl_version >> OPENSBI_VERSION_MAJOR_OFFSET; minor = sbi_impl_version & OPENSBI_VERSION_MINOR_MASK; printf("SBI: OpenSBI v%u.%u\n", major, minor); break; case (SBI_IMPL_ID_XVISOR): printf("SBI: eXtensible Versatile hypervISOR %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_KVM): printf("SBI: Kernel-based Virtual Machine %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_RUSTSBI): printf("SBI: RustSBI %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_DIOSIX): printf("SBI: Diosix %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_COFFER): printf("SBI: Coffer %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_XEN_PROJECT): printf("SBI: Xen Project %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_POLARFIRE_HSS): printf("SBI: PolarFire Hart Software Services %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_COREBOOT): printf("SBI: coreboot %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_OREBOOT): printf("SBI: oreboot %lu\n", sbi_impl_version); break; case (SBI_IMPL_ID_BHYVE): printf("SBI: bhyve %lu\n", sbi_impl_version); break; default: printf("SBI: Unrecognized Implementation: %lu\n", sbi_impl_id); break; } major = (sbi_spec_version & SBI_SPEC_VERS_MAJOR_MASK) >> SBI_SPEC_VERS_MAJOR_OFFSET; minor = (sbi_spec_version & SBI_SPEC_VERS_MINOR_MASK); printf("SBI Specification Version: %u.%u\n", major, minor); } void sbi_set_timer(uint64_t val) { struct sbi_ret ret __diagused; /* Use the TIME legacy replacement extension, if available. */ if (has_time_extension) { ret = SBI_CALL1(SBI_EXT_ID_TIME, SBI_TIME_SET_TIMER, val); MPASS(ret.error == SBI_SUCCESS); } else { (void)SBI_CALL1(SBI_SET_TIMER, 0, val); } } void sbi_send_ipi(const u_long *hart_mask) { struct sbi_ret ret __diagused; /* Use the IPI legacy replacement extension, if available. */ if (has_ipi_extension) { ret = SBI_CALL2(SBI_EXT_ID_IPI, SBI_IPI_SEND_IPI, *hart_mask, 0); MPASS(ret.error == SBI_SUCCESS); } else { (void)SBI_CALL1(SBI_SEND_IPI, 0, (uint64_t)hart_mask); } } void sbi_remote_fence_i(const u_long *hart_mask) { struct sbi_ret ret __diagused; /* Use the RFENCE legacy replacement extension, if available. */ if (has_rfnc_extension) { ret = SBI_CALL2(SBI_EXT_ID_RFNC, SBI_RFNC_REMOTE_FENCE_I, *hart_mask, 0); MPASS(ret.error == SBI_SUCCESS); } else { (void)SBI_CALL1(SBI_REMOTE_FENCE_I, 0, (uint64_t)hart_mask); } } void sbi_remote_sfence_vma(const u_long *hart_mask, u_long start, u_long size) { struct sbi_ret ret __diagused; /* Use the RFENCE legacy replacement extension, if available. */ if (has_rfnc_extension) { ret = SBI_CALL4(SBI_EXT_ID_RFNC, SBI_RFNC_REMOTE_SFENCE_VMA, *hart_mask, 0, start, size); MPASS(ret.error == SBI_SUCCESS); } else { (void)SBI_CALL3(SBI_REMOTE_SFENCE_VMA, 0, (uint64_t)hart_mask, start, size); } } void sbi_remote_sfence_vma_asid(const u_long *hart_mask, u_long start, u_long size, u_long asid) { struct sbi_ret ret __diagused; /* Use the RFENCE legacy replacement extension, if available. */ if (has_rfnc_extension) { ret = SBI_CALL5(SBI_EXT_ID_RFNC, SBI_RFNC_REMOTE_SFENCE_VMA_ASID, *hart_mask, 0, start, size, asid); MPASS(ret.error == SBI_SUCCESS); } else { (void)SBI_CALL4(SBI_REMOTE_SFENCE_VMA_ASID, 0, (uint64_t)hart_mask, start, size, asid); } } int sbi_hsm_hart_start(u_long hart, u_long start_addr, u_long priv) { struct sbi_ret ret; ret = SBI_CALL3(SBI_EXT_ID_HSM, SBI_HSM_HART_START, hart, start_addr, priv); return (ret.error != 0 ? (int)ret.error : 0); } void sbi_hsm_hart_stop(void) { (void)SBI_CALL0(SBI_EXT_ID_HSM, SBI_HSM_HART_STOP); } int sbi_hsm_hart_status(u_long hart) { struct sbi_ret ret; ret = SBI_CALL1(SBI_EXT_ID_HSM, SBI_HSM_HART_STATUS, hart); return (ret.error != 0 ? (int)ret.error : (int)ret.value); } void sbi_init(void) { struct sbi_ret sret; /* * Get the spec version. For legacy SBI implementations this will * return an error, otherwise it is guaranteed to succeed. */ sret = sbi_get_spec_version(); if (sret.error != 0) { /* We are running a legacy SBI implementation. */ sbi_spec_version = 0; return; } /* Set the SBI implementation info. */ sbi_spec_version = sret.value; sbi_impl_id = sbi_get_impl_id().value; sbi_impl_version = sbi_get_impl_version().value; /* Set the hardware implementation info. */ mvendorid = sbi_get_mvendorid().value; marchid = sbi_get_marchid().value; mimpid = sbi_get_mimpid().value; /* Probe for legacy replacement extensions. */ if (sbi_probe_extension(SBI_EXT_ID_TIME) != 0) has_time_extension = true; if (sbi_probe_extension(SBI_EXT_ID_IPI) != 0) has_ipi_extension = true; if (sbi_probe_extension(SBI_EXT_ID_RFNC) != 0) has_rfnc_extension = true; if (sbi_probe_extension(SBI_EXT_ID_SRST) != 0) has_srst_extension = true; /* * Probe for legacy extensions. We still rely on many of them to be * implemented, but this is not guaranteed by the spec. */ KASSERT(has_time_extension || sbi_probe_extension(SBI_SET_TIMER) != 0, ("SBI doesn't implement sbi_set_timer()")); KASSERT(sbi_probe_extension(SBI_CONSOLE_PUTCHAR) != 0, ("SBI doesn't implement sbi_console_putchar()")); KASSERT(sbi_probe_extension(SBI_CONSOLE_GETCHAR) != 0, ("SBI doesn't implement sbi_console_getchar()")); KASSERT(has_ipi_extension || sbi_probe_extension(SBI_SEND_IPI) != 0, ("SBI doesn't implement sbi_send_ipi()")); KASSERT(has_rfnc_extension || sbi_probe_extension(SBI_REMOTE_FENCE_I) != 0, ("SBI doesn't implement sbi_remote_fence_i()")); KASSERT(has_rfnc_extension || sbi_probe_extension(SBI_REMOTE_SFENCE_VMA) != 0, ("SBI doesn't implement sbi_remote_sfence_vma()")); KASSERT(has_rfnc_extension || sbi_probe_extension(SBI_REMOTE_SFENCE_VMA_ASID) != 0, ("SBI doesn't implement sbi_remote_sfence_vma_asid()")); KASSERT(has_srst_extension || sbi_probe_extension(SBI_SHUTDOWN) != 0, ("SBI doesn't implement a shutdown or reset extension")); } static void sbi_identify(driver_t *driver, device_t parent) { device_t dev; - if (device_find_child(parent, "sbi", -1) != NULL) + if (device_find_child(parent, "sbi", DEVICE_UNIT_ANY) != NULL) return; - dev = BUS_ADD_CHILD(parent, 0, "sbi", -1); + dev = BUS_ADD_CHILD(parent, 0, "sbi", DEVICE_UNIT_ANY); if (dev == NULL) device_printf(parent, "Can't add sbi child\n"); } static int sbi_probe(device_t dev) { device_set_desc(dev, "RISC-V Supervisor Binary Interface"); return (BUS_PROBE_NOWILDCARD); } static int sbi_attach(device_t dev) { struct sbi_softc *sc; #ifdef SMP device_t child; struct sbi_devinfo *di; #endif if (sbi_softc != NULL) return (ENXIO); sc = device_get_softc(dev); sc->dev = dev; sbi_softc = sc; EVENTHANDLER_REGISTER(shutdown_final, sbi_shutdown_final, NULL, SHUTDOWN_PRI_LAST); #ifdef SMP di = malloc(sizeof(*di), M_DEVBUF, M_WAITOK | M_ZERO); resource_list_init(&di->rl); - child = device_add_child(dev, "sbi_ipi", -1); + child = device_add_child(dev, "sbi_ipi", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "Could not add sbi_ipi child\n"); return (ENXIO); } device_set_ivars(child, di); #endif return (0); } static struct resource_list * sbi_get_resource_list(device_t bus, device_t child) { struct sbi_devinfo *di; di = device_get_ivars(child); KASSERT(di != NULL, ("%s: No devinfo", __func__)); return (&di->rl); } static device_method_t sbi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, sbi_identify), DEVMETHOD(device_probe, sbi_probe), DEVMETHOD(device_attach, sbi_attach), /* Bus interface */ DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_resource_list, sbi_get_resource_list), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD_END }; DEFINE_CLASS_0(sbi, sbi_driver, sbi_methods, sizeof(struct sbi_softc)); EARLY_DRIVER_MODULE(sbi, nexus, sbi_driver, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_FIRST); diff --git a/sys/x86/cpufreq/est.c b/sys/x86/cpufreq/est.c index 19448ff127ef..82f35934aa99 100644 --- a/sys/x86/cpufreq/est.c +++ b/sys/x86/cpufreq/est.c @@ -1,1364 +1,1366 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004 Colin Percival * Copyright (c) 2005 Nate Lawson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, 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. */ #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include #include #include #include #include #include #include "acpi_if.h" #include /* Status/control registers (from the IA-32 System Programming Guide). */ #define MSR_PERF_STATUS 0x198 #define MSR_PERF_CTL 0x199 /* Register and bit for enabling SpeedStep. */ #define MSR_MISC_ENABLE 0x1a0 #define MSR_SS_ENABLE (1<<16) /* Frequency and MSR control values. */ typedef struct { uint16_t freq; uint16_t volts; uint16_t id16; int power; } freq_info; /* Identifying characteristics of a processor and supported frequencies. */ typedef struct { const u_int vendor_id; uint32_t id32; freq_info *freqtab; size_t tablen; } cpu_info; struct est_softc { device_t dev; int acpi_settings; int msr_settings; freq_info *freq_list; size_t flist_len; }; /* Convert MHz and mV into IDs for passing to the MSR. */ #define ID16(MHz, mV, bus_clk) \ (((MHz / bus_clk) << 8) | ((mV ? mV - 700 : 0) >> 4)) #define ID32(MHz_hi, mV_hi, MHz_lo, mV_lo, bus_clk) \ ((ID16(MHz_lo, mV_lo, bus_clk) << 16) | (ID16(MHz_hi, mV_hi, bus_clk))) /* Format for storing IDs in our table. */ #define FREQ_INFO_PWR(MHz, mV, bus_clk, mW) \ { MHz, mV, ID16(MHz, mV, bus_clk), mW } #define FREQ_INFO(MHz, mV, bus_clk) \ FREQ_INFO_PWR(MHz, mV, bus_clk, CPUFREQ_VAL_UNKNOWN) #define INTEL(tab, zhi, vhi, zlo, vlo, bus_clk) \ { CPU_VENDOR_INTEL, ID32(zhi, vhi, zlo, vlo, bus_clk), tab, nitems(tab) } #define CENTAUR(tab, zhi, vhi, zlo, vlo, bus_clk) \ { CPU_VENDOR_CENTAUR, ID32(zhi, vhi, zlo, vlo, bus_clk), tab, nitems(tab) } static int msr_info_enabled = 0; TUNABLE_INT("hw.est.msr_info", &msr_info_enabled); static int strict = -1; TUNABLE_INT("hw.est.strict", &strict); /* Default bus clock value for Centrino processors. */ #define INTEL_BUS_CLK 100 /* XXX Update this if new CPUs have more settings. */ #define EST_MAX_SETTINGS 10 CTASSERT(EST_MAX_SETTINGS <= MAX_SETTINGS); /* Estimate in microseconds of latency for performing a transition. */ #define EST_TRANS_LAT 1000 /* * Frequency (MHz) and voltage (mV) settings. * * Dothan processors have multiple VID#s with different settings for * each VID#. Since we can't uniquely identify this info * without undisclosed methods from Intel, we can't support newer * processors with this table method. If ACPI Px states are supported, * we get info from them. * * Data from the "Intel Pentium M Processor Datasheet", * Order Number 252612-003, Table 5. */ static freq_info PM17_130[] = { /* 130nm 1.70GHz Pentium M */ FREQ_INFO(1700, 1484, INTEL_BUS_CLK), FREQ_INFO(1400, 1308, INTEL_BUS_CLK), FREQ_INFO(1200, 1228, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1004, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM16_130[] = { /* 130nm 1.60GHz Pentium M */ FREQ_INFO(1600, 1484, INTEL_BUS_CLK), FREQ_INFO(1400, 1420, INTEL_BUS_CLK), FREQ_INFO(1200, 1276, INTEL_BUS_CLK), FREQ_INFO(1000, 1164, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM15_130[] = { /* 130nm 1.50GHz Pentium M */ FREQ_INFO(1500, 1484, INTEL_BUS_CLK), FREQ_INFO(1400, 1452, INTEL_BUS_CLK), FREQ_INFO(1200, 1356, INTEL_BUS_CLK), FREQ_INFO(1000, 1228, INTEL_BUS_CLK), FREQ_INFO( 800, 1116, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM14_130[] = { /* 130nm 1.40GHz Pentium M */ FREQ_INFO(1400, 1484, INTEL_BUS_CLK), FREQ_INFO(1200, 1436, INTEL_BUS_CLK), FREQ_INFO(1000, 1308, INTEL_BUS_CLK), FREQ_INFO( 800, 1180, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM13_130[] = { /* 130nm 1.30GHz Pentium M */ FREQ_INFO(1300, 1388, INTEL_BUS_CLK), FREQ_INFO(1200, 1356, INTEL_BUS_CLK), FREQ_INFO(1000, 1292, INTEL_BUS_CLK), FREQ_INFO( 800, 1260, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM13_LV_130[] = { /* 130nm 1.30GHz Low Voltage Pentium M */ FREQ_INFO(1300, 1180, INTEL_BUS_CLK), FREQ_INFO(1200, 1164, INTEL_BUS_CLK), FREQ_INFO(1100, 1100, INTEL_BUS_CLK), FREQ_INFO(1000, 1020, INTEL_BUS_CLK), FREQ_INFO( 900, 1004, INTEL_BUS_CLK), FREQ_INFO( 800, 988, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM12_LV_130[] = { /* 130 nm 1.20GHz Low Voltage Pentium M */ FREQ_INFO(1200, 1180, INTEL_BUS_CLK), FREQ_INFO(1100, 1164, INTEL_BUS_CLK), FREQ_INFO(1000, 1100, INTEL_BUS_CLK), FREQ_INFO( 900, 1020, INTEL_BUS_CLK), FREQ_INFO( 800, 1004, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM11_LV_130[] = { /* 130 nm 1.10GHz Low Voltage Pentium M */ FREQ_INFO(1100, 1180, INTEL_BUS_CLK), FREQ_INFO(1000, 1164, INTEL_BUS_CLK), FREQ_INFO( 900, 1100, INTEL_BUS_CLK), FREQ_INFO( 800, 1020, INTEL_BUS_CLK), FREQ_INFO( 600, 956, INTEL_BUS_CLK), }; static freq_info PM11_ULV_130[] = { /* 130 nm 1.10GHz Ultra Low Voltage Pentium M */ FREQ_INFO(1100, 1004, INTEL_BUS_CLK), FREQ_INFO(1000, 988, INTEL_BUS_CLK), FREQ_INFO( 900, 972, INTEL_BUS_CLK), FREQ_INFO( 800, 956, INTEL_BUS_CLK), FREQ_INFO( 600, 844, INTEL_BUS_CLK), }; static freq_info PM10_ULV_130[] = { /* 130 nm 1.00GHz Ultra Low Voltage Pentium M */ FREQ_INFO(1000, 1004, INTEL_BUS_CLK), FREQ_INFO( 900, 988, INTEL_BUS_CLK), FREQ_INFO( 800, 972, INTEL_BUS_CLK), FREQ_INFO( 600, 844, INTEL_BUS_CLK), }; /* * Data from "Intel Pentium M Processor on 90nm Process with * 2-MB L2 Cache Datasheet", Order Number 302189-008, Table 5. */ static freq_info PM_765A_90[] = { /* 90 nm 2.10GHz Pentium M, VID #A */ FREQ_INFO(2100, 1340, INTEL_BUS_CLK), FREQ_INFO(1800, 1276, INTEL_BUS_CLK), FREQ_INFO(1600, 1228, INTEL_BUS_CLK), FREQ_INFO(1400, 1180, INTEL_BUS_CLK), FREQ_INFO(1200, 1132, INTEL_BUS_CLK), FREQ_INFO(1000, 1084, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_765B_90[] = { /* 90 nm 2.10GHz Pentium M, VID #B */ FREQ_INFO(2100, 1324, INTEL_BUS_CLK), FREQ_INFO(1800, 1260, INTEL_BUS_CLK), FREQ_INFO(1600, 1212, INTEL_BUS_CLK), FREQ_INFO(1400, 1180, INTEL_BUS_CLK), FREQ_INFO(1200, 1132, INTEL_BUS_CLK), FREQ_INFO(1000, 1084, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_765C_90[] = { /* 90 nm 2.10GHz Pentium M, VID #C */ FREQ_INFO(2100, 1308, INTEL_BUS_CLK), FREQ_INFO(1800, 1244, INTEL_BUS_CLK), FREQ_INFO(1600, 1212, INTEL_BUS_CLK), FREQ_INFO(1400, 1164, INTEL_BUS_CLK), FREQ_INFO(1200, 1116, INTEL_BUS_CLK), FREQ_INFO(1000, 1084, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_765E_90[] = { /* 90 nm 2.10GHz Pentium M, VID #E */ FREQ_INFO(2100, 1356, INTEL_BUS_CLK), FREQ_INFO(1800, 1292, INTEL_BUS_CLK), FREQ_INFO(1600, 1244, INTEL_BUS_CLK), FREQ_INFO(1400, 1196, INTEL_BUS_CLK), FREQ_INFO(1200, 1148, INTEL_BUS_CLK), FREQ_INFO(1000, 1100, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_755A_90[] = { /* 90 nm 2.00GHz Pentium M, VID #A */ FREQ_INFO(2000, 1340, INTEL_BUS_CLK), FREQ_INFO(1800, 1292, INTEL_BUS_CLK), FREQ_INFO(1600, 1244, INTEL_BUS_CLK), FREQ_INFO(1400, 1196, INTEL_BUS_CLK), FREQ_INFO(1200, 1148, INTEL_BUS_CLK), FREQ_INFO(1000, 1100, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_755B_90[] = { /* 90 nm 2.00GHz Pentium M, VID #B */ FREQ_INFO(2000, 1324, INTEL_BUS_CLK), FREQ_INFO(1800, 1276, INTEL_BUS_CLK), FREQ_INFO(1600, 1228, INTEL_BUS_CLK), FREQ_INFO(1400, 1180, INTEL_BUS_CLK), FREQ_INFO(1200, 1132, INTEL_BUS_CLK), FREQ_INFO(1000, 1084, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_755C_90[] = { /* 90 nm 2.00GHz Pentium M, VID #C */ FREQ_INFO(2000, 1308, INTEL_BUS_CLK), FREQ_INFO(1800, 1276, INTEL_BUS_CLK), FREQ_INFO(1600, 1228, INTEL_BUS_CLK), FREQ_INFO(1400, 1180, INTEL_BUS_CLK), FREQ_INFO(1200, 1132, INTEL_BUS_CLK), FREQ_INFO(1000, 1084, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_755D_90[] = { /* 90 nm 2.00GHz Pentium M, VID #D */ FREQ_INFO(2000, 1276, INTEL_BUS_CLK), FREQ_INFO(1800, 1244, INTEL_BUS_CLK), FREQ_INFO(1600, 1196, INTEL_BUS_CLK), FREQ_INFO(1400, 1164, INTEL_BUS_CLK), FREQ_INFO(1200, 1116, INTEL_BUS_CLK), FREQ_INFO(1000, 1084, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_745A_90[] = { /* 90 nm 1.80GHz Pentium M, VID #A */ FREQ_INFO(1800, 1340, INTEL_BUS_CLK), FREQ_INFO(1600, 1292, INTEL_BUS_CLK), FREQ_INFO(1400, 1228, INTEL_BUS_CLK), FREQ_INFO(1200, 1164, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_745B_90[] = { /* 90 nm 1.80GHz Pentium M, VID #B */ FREQ_INFO(1800, 1324, INTEL_BUS_CLK), FREQ_INFO(1600, 1276, INTEL_BUS_CLK), FREQ_INFO(1400, 1212, INTEL_BUS_CLK), FREQ_INFO(1200, 1164, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_745C_90[] = { /* 90 nm 1.80GHz Pentium M, VID #C */ FREQ_INFO(1800, 1308, INTEL_BUS_CLK), FREQ_INFO(1600, 1260, INTEL_BUS_CLK), FREQ_INFO(1400, 1212, INTEL_BUS_CLK), FREQ_INFO(1200, 1148, INTEL_BUS_CLK), FREQ_INFO(1000, 1100, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_745D_90[] = { /* 90 nm 1.80GHz Pentium M, VID #D */ FREQ_INFO(1800, 1276, INTEL_BUS_CLK), FREQ_INFO(1600, 1228, INTEL_BUS_CLK), FREQ_INFO(1400, 1180, INTEL_BUS_CLK), FREQ_INFO(1200, 1132, INTEL_BUS_CLK), FREQ_INFO(1000, 1084, INTEL_BUS_CLK), FREQ_INFO( 800, 1036, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_735A_90[] = { /* 90 nm 1.70GHz Pentium M, VID #A */ FREQ_INFO(1700, 1340, INTEL_BUS_CLK), FREQ_INFO(1400, 1244, INTEL_BUS_CLK), FREQ_INFO(1200, 1180, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_735B_90[] = { /* 90 nm 1.70GHz Pentium M, VID #B */ FREQ_INFO(1700, 1324, INTEL_BUS_CLK), FREQ_INFO(1400, 1244, INTEL_BUS_CLK), FREQ_INFO(1200, 1180, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_735C_90[] = { /* 90 nm 1.70GHz Pentium M, VID #C */ FREQ_INFO(1700, 1308, INTEL_BUS_CLK), FREQ_INFO(1400, 1228, INTEL_BUS_CLK), FREQ_INFO(1200, 1164, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_735D_90[] = { /* 90 nm 1.70GHz Pentium M, VID #D */ FREQ_INFO(1700, 1276, INTEL_BUS_CLK), FREQ_INFO(1400, 1212, INTEL_BUS_CLK), FREQ_INFO(1200, 1148, INTEL_BUS_CLK), FREQ_INFO(1000, 1100, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_725A_90[] = { /* 90 nm 1.60GHz Pentium M, VID #A */ FREQ_INFO(1600, 1340, INTEL_BUS_CLK), FREQ_INFO(1400, 1276, INTEL_BUS_CLK), FREQ_INFO(1200, 1212, INTEL_BUS_CLK), FREQ_INFO(1000, 1132, INTEL_BUS_CLK), FREQ_INFO( 800, 1068, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_725B_90[] = { /* 90 nm 1.60GHz Pentium M, VID #B */ FREQ_INFO(1600, 1324, INTEL_BUS_CLK), FREQ_INFO(1400, 1260, INTEL_BUS_CLK), FREQ_INFO(1200, 1196, INTEL_BUS_CLK), FREQ_INFO(1000, 1132, INTEL_BUS_CLK), FREQ_INFO( 800, 1068, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_725C_90[] = { /* 90 nm 1.60GHz Pentium M, VID #C */ FREQ_INFO(1600, 1308, INTEL_BUS_CLK), FREQ_INFO(1400, 1244, INTEL_BUS_CLK), FREQ_INFO(1200, 1180, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_725D_90[] = { /* 90 nm 1.60GHz Pentium M, VID #D */ FREQ_INFO(1600, 1276, INTEL_BUS_CLK), FREQ_INFO(1400, 1228, INTEL_BUS_CLK), FREQ_INFO(1200, 1164, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_715A_90[] = { /* 90 nm 1.50GHz Pentium M, VID #A */ FREQ_INFO(1500, 1340, INTEL_BUS_CLK), FREQ_INFO(1200, 1228, INTEL_BUS_CLK), FREQ_INFO(1000, 1148, INTEL_BUS_CLK), FREQ_INFO( 800, 1068, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_715B_90[] = { /* 90 nm 1.50GHz Pentium M, VID #B */ FREQ_INFO(1500, 1324, INTEL_BUS_CLK), FREQ_INFO(1200, 1212, INTEL_BUS_CLK), FREQ_INFO(1000, 1148, INTEL_BUS_CLK), FREQ_INFO( 800, 1068, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_715C_90[] = { /* 90 nm 1.50GHz Pentium M, VID #C */ FREQ_INFO(1500, 1308, INTEL_BUS_CLK), FREQ_INFO(1200, 1212, INTEL_BUS_CLK), FREQ_INFO(1000, 1132, INTEL_BUS_CLK), FREQ_INFO( 800, 1068, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_715D_90[] = { /* 90 nm 1.50GHz Pentium M, VID #D */ FREQ_INFO(1500, 1276, INTEL_BUS_CLK), FREQ_INFO(1200, 1180, INTEL_BUS_CLK), FREQ_INFO(1000, 1116, INTEL_BUS_CLK), FREQ_INFO( 800, 1052, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_778_90[] = { /* 90 nm 1.60GHz Low Voltage Pentium M */ FREQ_INFO(1600, 1116, INTEL_BUS_CLK), FREQ_INFO(1500, 1116, INTEL_BUS_CLK), FREQ_INFO(1400, 1100, INTEL_BUS_CLK), FREQ_INFO(1300, 1084, INTEL_BUS_CLK), FREQ_INFO(1200, 1068, INTEL_BUS_CLK), FREQ_INFO(1100, 1052, INTEL_BUS_CLK), FREQ_INFO(1000, 1052, INTEL_BUS_CLK), FREQ_INFO( 900, 1036, INTEL_BUS_CLK), FREQ_INFO( 800, 1020, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_758_90[] = { /* 90 nm 1.50GHz Low Voltage Pentium M */ FREQ_INFO(1500, 1116, INTEL_BUS_CLK), FREQ_INFO(1400, 1116, INTEL_BUS_CLK), FREQ_INFO(1300, 1100, INTEL_BUS_CLK), FREQ_INFO(1200, 1084, INTEL_BUS_CLK), FREQ_INFO(1100, 1068, INTEL_BUS_CLK), FREQ_INFO(1000, 1052, INTEL_BUS_CLK), FREQ_INFO( 900, 1036, INTEL_BUS_CLK), FREQ_INFO( 800, 1020, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_738_90[] = { /* 90 nm 1.40GHz Low Voltage Pentium M */ FREQ_INFO(1400, 1116, INTEL_BUS_CLK), FREQ_INFO(1300, 1116, INTEL_BUS_CLK), FREQ_INFO(1200, 1100, INTEL_BUS_CLK), FREQ_INFO(1100, 1068, INTEL_BUS_CLK), FREQ_INFO(1000, 1052, INTEL_BUS_CLK), FREQ_INFO( 900, 1036, INTEL_BUS_CLK), FREQ_INFO( 800, 1020, INTEL_BUS_CLK), FREQ_INFO( 600, 988, INTEL_BUS_CLK), }; static freq_info PM_773G_90[] = { /* 90 nm 1.30GHz Ultra Low Voltage Pentium M, VID #G */ FREQ_INFO(1300, 956, INTEL_BUS_CLK), FREQ_INFO(1200, 940, INTEL_BUS_CLK), FREQ_INFO(1100, 924, INTEL_BUS_CLK), FREQ_INFO(1000, 908, INTEL_BUS_CLK), FREQ_INFO( 900, 876, INTEL_BUS_CLK), FREQ_INFO( 800, 860, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_773H_90[] = { /* 90 nm 1.30GHz Ultra Low Voltage Pentium M, VID #H */ FREQ_INFO(1300, 940, INTEL_BUS_CLK), FREQ_INFO(1200, 924, INTEL_BUS_CLK), FREQ_INFO(1100, 908, INTEL_BUS_CLK), FREQ_INFO(1000, 892, INTEL_BUS_CLK), FREQ_INFO( 900, 876, INTEL_BUS_CLK), FREQ_INFO( 800, 860, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_773I_90[] = { /* 90 nm 1.30GHz Ultra Low Voltage Pentium M, VID #I */ FREQ_INFO(1300, 924, INTEL_BUS_CLK), FREQ_INFO(1200, 908, INTEL_BUS_CLK), FREQ_INFO(1100, 892, INTEL_BUS_CLK), FREQ_INFO(1000, 876, INTEL_BUS_CLK), FREQ_INFO( 900, 860, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_773J_90[] = { /* 90 nm 1.30GHz Ultra Low Voltage Pentium M, VID #J */ FREQ_INFO(1300, 908, INTEL_BUS_CLK), FREQ_INFO(1200, 908, INTEL_BUS_CLK), FREQ_INFO(1100, 892, INTEL_BUS_CLK), FREQ_INFO(1000, 876, INTEL_BUS_CLK), FREQ_INFO( 900, 860, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_773K_90[] = { /* 90 nm 1.30GHz Ultra Low Voltage Pentium M, VID #K */ FREQ_INFO(1300, 892, INTEL_BUS_CLK), FREQ_INFO(1200, 892, INTEL_BUS_CLK), FREQ_INFO(1100, 876, INTEL_BUS_CLK), FREQ_INFO(1000, 860, INTEL_BUS_CLK), FREQ_INFO( 900, 860, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_773L_90[] = { /* 90 nm 1.30GHz Ultra Low Voltage Pentium M, VID #L */ FREQ_INFO(1300, 876, INTEL_BUS_CLK), FREQ_INFO(1200, 876, INTEL_BUS_CLK), FREQ_INFO(1100, 860, INTEL_BUS_CLK), FREQ_INFO(1000, 860, INTEL_BUS_CLK), FREQ_INFO( 900, 844, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_753G_90[] = { /* 90 nm 1.20GHz Ultra Low Voltage Pentium M, VID #G */ FREQ_INFO(1200, 956, INTEL_BUS_CLK), FREQ_INFO(1100, 940, INTEL_BUS_CLK), FREQ_INFO(1000, 908, INTEL_BUS_CLK), FREQ_INFO( 900, 892, INTEL_BUS_CLK), FREQ_INFO( 800, 860, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_753H_90[] = { /* 90 nm 1.20GHz Ultra Low Voltage Pentium M, VID #H */ FREQ_INFO(1200, 940, INTEL_BUS_CLK), FREQ_INFO(1100, 924, INTEL_BUS_CLK), FREQ_INFO(1000, 908, INTEL_BUS_CLK), FREQ_INFO( 900, 876, INTEL_BUS_CLK), FREQ_INFO( 800, 860, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_753I_90[] = { /* 90 nm 1.20GHz Ultra Low Voltage Pentium M, VID #I */ FREQ_INFO(1200, 924, INTEL_BUS_CLK), FREQ_INFO(1100, 908, INTEL_BUS_CLK), FREQ_INFO(1000, 892, INTEL_BUS_CLK), FREQ_INFO( 900, 876, INTEL_BUS_CLK), FREQ_INFO( 800, 860, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_753J_90[] = { /* 90 nm 1.20GHz Ultra Low Voltage Pentium M, VID #J */ FREQ_INFO(1200, 908, INTEL_BUS_CLK), FREQ_INFO(1100, 892, INTEL_BUS_CLK), FREQ_INFO(1000, 876, INTEL_BUS_CLK), FREQ_INFO( 900, 860, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_753K_90[] = { /* 90 nm 1.20GHz Ultra Low Voltage Pentium M, VID #K */ FREQ_INFO(1200, 892, INTEL_BUS_CLK), FREQ_INFO(1100, 892, INTEL_BUS_CLK), FREQ_INFO(1000, 876, INTEL_BUS_CLK), FREQ_INFO( 900, 860, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_753L_90[] = { /* 90 nm 1.20GHz Ultra Low Voltage Pentium M, VID #L */ FREQ_INFO(1200, 876, INTEL_BUS_CLK), FREQ_INFO(1100, 876, INTEL_BUS_CLK), FREQ_INFO(1000, 860, INTEL_BUS_CLK), FREQ_INFO( 900, 844, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_733JG_90[] = { /* 90 nm 1.10GHz Ultra Low Voltage Pentium M, VID #G */ FREQ_INFO(1100, 956, INTEL_BUS_CLK), FREQ_INFO(1000, 940, INTEL_BUS_CLK), FREQ_INFO( 900, 908, INTEL_BUS_CLK), FREQ_INFO( 800, 876, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_733JH_90[] = { /* 90 nm 1.10GHz Ultra Low Voltage Pentium M, VID #H */ FREQ_INFO(1100, 940, INTEL_BUS_CLK), FREQ_INFO(1000, 924, INTEL_BUS_CLK), FREQ_INFO( 900, 892, INTEL_BUS_CLK), FREQ_INFO( 800, 876, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_733JI_90[] = { /* 90 nm 1.10GHz Ultra Low Voltage Pentium M, VID #I */ FREQ_INFO(1100, 924, INTEL_BUS_CLK), FREQ_INFO(1000, 908, INTEL_BUS_CLK), FREQ_INFO( 900, 892, INTEL_BUS_CLK), FREQ_INFO( 800, 860, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_733JJ_90[] = { /* 90 nm 1.10GHz Ultra Low Voltage Pentium M, VID #J */ FREQ_INFO(1100, 908, INTEL_BUS_CLK), FREQ_INFO(1000, 892, INTEL_BUS_CLK), FREQ_INFO( 900, 876, INTEL_BUS_CLK), FREQ_INFO( 800, 860, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_733JK_90[] = { /* 90 nm 1.10GHz Ultra Low Voltage Pentium M, VID #K */ FREQ_INFO(1100, 892, INTEL_BUS_CLK), FREQ_INFO(1000, 876, INTEL_BUS_CLK), FREQ_INFO( 900, 860, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_733JL_90[] = { /* 90 nm 1.10GHz Ultra Low Voltage Pentium M, VID #L */ FREQ_INFO(1100, 876, INTEL_BUS_CLK), FREQ_INFO(1000, 876, INTEL_BUS_CLK), FREQ_INFO( 900, 860, INTEL_BUS_CLK), FREQ_INFO( 800, 844, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_733_90[] = { /* 90 nm 1.10GHz Ultra Low Voltage Pentium M */ FREQ_INFO(1100, 940, INTEL_BUS_CLK), FREQ_INFO(1000, 924, INTEL_BUS_CLK), FREQ_INFO( 900, 892, INTEL_BUS_CLK), FREQ_INFO( 800, 876, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; static freq_info PM_723_90[] = { /* 90 nm 1.00GHz Ultra Low Voltage Pentium M */ FREQ_INFO(1000, 940, INTEL_BUS_CLK), FREQ_INFO( 900, 908, INTEL_BUS_CLK), FREQ_INFO( 800, 876, INTEL_BUS_CLK), FREQ_INFO( 600, 812, INTEL_BUS_CLK), }; /* * VIA C7-M 500 MHz FSB, 400 MHz FSB, and ULV variants. * Data from the "VIA C7-M Processor BIOS Writer's Guide (v2.17)" datasheet. */ static freq_info C7M_795[] = { /* 2.00GHz Centaur C7-M 533 Mhz FSB */ FREQ_INFO_PWR(2000, 1148, 133, 20000), FREQ_INFO_PWR(1867, 1132, 133, 18000), FREQ_INFO_PWR(1600, 1100, 133, 15000), FREQ_INFO_PWR(1467, 1052, 133, 13000), FREQ_INFO_PWR(1200, 1004, 133, 10000), FREQ_INFO_PWR( 800, 844, 133, 7000), FREQ_INFO_PWR( 667, 844, 133, 6000), FREQ_INFO_PWR( 533, 844, 133, 5000), }; static freq_info C7M_785[] = { /* 1.80GHz Centaur C7-M 533 Mhz FSB */ FREQ_INFO_PWR(1867, 1148, 133, 18000), FREQ_INFO_PWR(1600, 1100, 133, 15000), FREQ_INFO_PWR(1467, 1052, 133, 13000), FREQ_INFO_PWR(1200, 1004, 133, 10000), FREQ_INFO_PWR( 800, 844, 133, 7000), FREQ_INFO_PWR( 667, 844, 133, 6000), FREQ_INFO_PWR( 533, 844, 133, 5000), }; static freq_info C7M_765[] = { /* 1.60GHz Centaur C7-M 533 Mhz FSB */ FREQ_INFO_PWR(1600, 1084, 133, 15000), FREQ_INFO_PWR(1467, 1052, 133, 13000), FREQ_INFO_PWR(1200, 1004, 133, 10000), FREQ_INFO_PWR( 800, 844, 133, 7000), FREQ_INFO_PWR( 667, 844, 133, 6000), FREQ_INFO_PWR( 533, 844, 133, 5000), }; static freq_info C7M_794[] = { /* 2.00GHz Centaur C7-M 400 Mhz FSB */ FREQ_INFO_PWR(2000, 1148, 100, 20000), FREQ_INFO_PWR(1800, 1132, 100, 18000), FREQ_INFO_PWR(1600, 1100, 100, 15000), FREQ_INFO_PWR(1400, 1052, 100, 13000), FREQ_INFO_PWR(1000, 1004, 100, 10000), FREQ_INFO_PWR( 800, 844, 100, 7000), FREQ_INFO_PWR( 600, 844, 100, 6000), FREQ_INFO_PWR( 400, 844, 100, 5000), }; static freq_info C7M_784[] = { /* 1.80GHz Centaur C7-M 400 Mhz FSB */ FREQ_INFO_PWR(1800, 1148, 100, 18000), FREQ_INFO_PWR(1600, 1100, 100, 15000), FREQ_INFO_PWR(1400, 1052, 100, 13000), FREQ_INFO_PWR(1000, 1004, 100, 10000), FREQ_INFO_PWR( 800, 844, 100, 7000), FREQ_INFO_PWR( 600, 844, 100, 6000), FREQ_INFO_PWR( 400, 844, 100, 5000), }; static freq_info C7M_764[] = { /* 1.60GHz Centaur C7-M 400 Mhz FSB */ FREQ_INFO_PWR(1600, 1084, 100, 15000), FREQ_INFO_PWR(1400, 1052, 100, 13000), FREQ_INFO_PWR(1000, 1004, 100, 10000), FREQ_INFO_PWR( 800, 844, 100, 7000), FREQ_INFO_PWR( 600, 844, 100, 6000), FREQ_INFO_PWR( 400, 844, 100, 5000), }; static freq_info C7M_754[] = { /* 1.50GHz Centaur C7-M 400 Mhz FSB */ FREQ_INFO_PWR(1500, 1004, 100, 12000), FREQ_INFO_PWR(1400, 988, 100, 11000), FREQ_INFO_PWR(1000, 940, 100, 9000), FREQ_INFO_PWR( 800, 844, 100, 7000), FREQ_INFO_PWR( 600, 844, 100, 6000), FREQ_INFO_PWR( 400, 844, 100, 5000), }; static freq_info C7M_771[] = { /* 1.20GHz Centaur C7-M 400 Mhz FSB */ FREQ_INFO_PWR(1200, 860, 100, 7000), FREQ_INFO_PWR(1000, 860, 100, 6000), FREQ_INFO_PWR( 800, 844, 100, 5500), FREQ_INFO_PWR( 600, 844, 100, 5000), FREQ_INFO_PWR( 400, 844, 100, 4000), }; static freq_info C7M_775_ULV[] = { /* 1.50GHz Centaur C7-M ULV */ FREQ_INFO_PWR(1500, 956, 100, 7500), FREQ_INFO_PWR(1400, 940, 100, 6000), FREQ_INFO_PWR(1000, 860, 100, 5000), FREQ_INFO_PWR( 800, 828, 100, 2800), FREQ_INFO_PWR( 600, 796, 100, 2500), FREQ_INFO_PWR( 400, 796, 100, 2000), }; static freq_info C7M_772_ULV[] = { /* 1.20GHz Centaur C7-M ULV */ FREQ_INFO_PWR(1200, 844, 100, 5000), FREQ_INFO_PWR(1000, 844, 100, 4000), FREQ_INFO_PWR( 800, 828, 100, 2800), FREQ_INFO_PWR( 600, 796, 100, 2500), FREQ_INFO_PWR( 400, 796, 100, 2000), }; static freq_info C7M_779_ULV[] = { /* 1.00GHz Centaur C7-M ULV */ FREQ_INFO_PWR(1000, 796, 100, 3500), FREQ_INFO_PWR( 800, 796, 100, 2800), FREQ_INFO_PWR( 600, 796, 100, 2500), FREQ_INFO_PWR( 400, 796, 100, 2000), }; static freq_info C7M_770_ULV[] = { /* 1.00GHz Centaur C7-M ULV */ FREQ_INFO_PWR(1000, 844, 100, 5000), FREQ_INFO_PWR( 800, 796, 100, 2800), FREQ_INFO_PWR( 600, 796, 100, 2500), FREQ_INFO_PWR( 400, 796, 100, 2000), }; static cpu_info ESTprocs[] = { INTEL(PM17_130, 1700, 1484, 600, 956, INTEL_BUS_CLK), INTEL(PM16_130, 1600, 1484, 600, 956, INTEL_BUS_CLK), INTEL(PM15_130, 1500, 1484, 600, 956, INTEL_BUS_CLK), INTEL(PM14_130, 1400, 1484, 600, 956, INTEL_BUS_CLK), INTEL(PM13_130, 1300, 1388, 600, 956, INTEL_BUS_CLK), INTEL(PM13_LV_130, 1300, 1180, 600, 956, INTEL_BUS_CLK), INTEL(PM12_LV_130, 1200, 1180, 600, 956, INTEL_BUS_CLK), INTEL(PM11_LV_130, 1100, 1180, 600, 956, INTEL_BUS_CLK), INTEL(PM11_ULV_130, 1100, 1004, 600, 844, INTEL_BUS_CLK), INTEL(PM10_ULV_130, 1000, 1004, 600, 844, INTEL_BUS_CLK), INTEL(PM_765A_90, 2100, 1340, 600, 988, INTEL_BUS_CLK), INTEL(PM_765B_90, 2100, 1324, 600, 988, INTEL_BUS_CLK), INTEL(PM_765C_90, 2100, 1308, 600, 988, INTEL_BUS_CLK), INTEL(PM_765E_90, 2100, 1356, 600, 988, INTEL_BUS_CLK), INTEL(PM_755A_90, 2000, 1340, 600, 988, INTEL_BUS_CLK), INTEL(PM_755B_90, 2000, 1324, 600, 988, INTEL_BUS_CLK), INTEL(PM_755C_90, 2000, 1308, 600, 988, INTEL_BUS_CLK), INTEL(PM_755D_90, 2000, 1276, 600, 988, INTEL_BUS_CLK), INTEL(PM_745A_90, 1800, 1340, 600, 988, INTEL_BUS_CLK), INTEL(PM_745B_90, 1800, 1324, 600, 988, INTEL_BUS_CLK), INTEL(PM_745C_90, 1800, 1308, 600, 988, INTEL_BUS_CLK), INTEL(PM_745D_90, 1800, 1276, 600, 988, INTEL_BUS_CLK), INTEL(PM_735A_90, 1700, 1340, 600, 988, INTEL_BUS_CLK), INTEL(PM_735B_90, 1700, 1324, 600, 988, INTEL_BUS_CLK), INTEL(PM_735C_90, 1700, 1308, 600, 988, INTEL_BUS_CLK), INTEL(PM_735D_90, 1700, 1276, 600, 988, INTEL_BUS_CLK), INTEL(PM_725A_90, 1600, 1340, 600, 988, INTEL_BUS_CLK), INTEL(PM_725B_90, 1600, 1324, 600, 988, INTEL_BUS_CLK), INTEL(PM_725C_90, 1600, 1308, 600, 988, INTEL_BUS_CLK), INTEL(PM_725D_90, 1600, 1276, 600, 988, INTEL_BUS_CLK), INTEL(PM_715A_90, 1500, 1340, 600, 988, INTEL_BUS_CLK), INTEL(PM_715B_90, 1500, 1324, 600, 988, INTEL_BUS_CLK), INTEL(PM_715C_90, 1500, 1308, 600, 988, INTEL_BUS_CLK), INTEL(PM_715D_90, 1500, 1276, 600, 988, INTEL_BUS_CLK), INTEL(PM_778_90, 1600, 1116, 600, 988, INTEL_BUS_CLK), INTEL(PM_758_90, 1500, 1116, 600, 988, INTEL_BUS_CLK), INTEL(PM_738_90, 1400, 1116, 600, 988, INTEL_BUS_CLK), INTEL(PM_773G_90, 1300, 956, 600, 812, INTEL_BUS_CLK), INTEL(PM_773H_90, 1300, 940, 600, 812, INTEL_BUS_CLK), INTEL(PM_773I_90, 1300, 924, 600, 812, INTEL_BUS_CLK), INTEL(PM_773J_90, 1300, 908, 600, 812, INTEL_BUS_CLK), INTEL(PM_773K_90, 1300, 892, 600, 812, INTEL_BUS_CLK), INTEL(PM_773L_90, 1300, 876, 600, 812, INTEL_BUS_CLK), INTEL(PM_753G_90, 1200, 956, 600, 812, INTEL_BUS_CLK), INTEL(PM_753H_90, 1200, 940, 600, 812, INTEL_BUS_CLK), INTEL(PM_753I_90, 1200, 924, 600, 812, INTEL_BUS_CLK), INTEL(PM_753J_90, 1200, 908, 600, 812, INTEL_BUS_CLK), INTEL(PM_753K_90, 1200, 892, 600, 812, INTEL_BUS_CLK), INTEL(PM_753L_90, 1200, 876, 600, 812, INTEL_BUS_CLK), INTEL(PM_733JG_90, 1100, 956, 600, 812, INTEL_BUS_CLK), INTEL(PM_733JH_90, 1100, 940, 600, 812, INTEL_BUS_CLK), INTEL(PM_733JI_90, 1100, 924, 600, 812, INTEL_BUS_CLK), INTEL(PM_733JJ_90, 1100, 908, 600, 812, INTEL_BUS_CLK), INTEL(PM_733JK_90, 1100, 892, 600, 812, INTEL_BUS_CLK), INTEL(PM_733JL_90, 1100, 876, 600, 812, INTEL_BUS_CLK), INTEL(PM_733_90, 1100, 940, 600, 812, INTEL_BUS_CLK), INTEL(PM_723_90, 1000, 940, 600, 812, INTEL_BUS_CLK), CENTAUR(C7M_795, 2000, 1148, 533, 844, 133), CENTAUR(C7M_794, 2000, 1148, 400, 844, 100), CENTAUR(C7M_785, 1867, 1148, 533, 844, 133), CENTAUR(C7M_784, 1800, 1148, 400, 844, 100), CENTAUR(C7M_765, 1600, 1084, 533, 844, 133), CENTAUR(C7M_764, 1600, 1084, 400, 844, 100), CENTAUR(C7M_754, 1500, 1004, 400, 844, 100), CENTAUR(C7M_775_ULV, 1500, 956, 400, 796, 100), CENTAUR(C7M_771, 1200, 860, 400, 844, 100), CENTAUR(C7M_772_ULV, 1200, 844, 400, 796, 100), CENTAUR(C7M_779_ULV, 1000, 796, 400, 796, 100), CENTAUR(C7M_770_ULV, 1000, 844, 400, 796, 100), { 0, 0, NULL }, }; static void est_identify(driver_t *driver, device_t parent); static int est_features(driver_t *driver, u_int *features); static int est_probe(device_t parent); static int est_attach(device_t parent); static int est_detach(device_t parent); static int est_get_info(device_t dev); static int est_acpi_info(device_t dev, freq_info **freqs, size_t *freqslen); static int est_table_info(device_t dev, uint64_t msr, freq_info **freqs, size_t *freqslen); static int est_msr_info(device_t dev, uint64_t msr, freq_info **freqs, size_t *freqslen); static freq_info *est_get_current(freq_info *freq_list, size_t tablen); static int est_settings(device_t dev, struct cf_setting *sets, int *count); static int est_set(device_t dev, const struct cf_setting *set); static int est_get(device_t dev, struct cf_setting *set); static int est_type(device_t dev, int *type); static int est_set_id16(device_t dev, uint16_t id16, int need_check); static void est_get_id16(uint16_t *id16_p); static device_method_t est_methods[] = { /* Device interface */ DEVMETHOD(device_identify, est_identify), DEVMETHOD(device_probe, est_probe), DEVMETHOD(device_attach, est_attach), DEVMETHOD(device_detach, est_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, est_set), DEVMETHOD(cpufreq_drv_get, est_get), DEVMETHOD(cpufreq_drv_type, est_type), DEVMETHOD(cpufreq_drv_settings, est_settings), /* ACPI interface */ DEVMETHOD(acpi_get_features, est_features), {0, 0} }; static driver_t est_driver = { "est", est_methods, sizeof(struct est_softc), }; DRIVER_MODULE(est, cpu, est_driver, 0, 0); MODULE_DEPEND(est, hwpstate_intel, 1, 1, 1); static int est_features(driver_t *driver, u_int *features) { /* * Notify the ACPI CPU that we support direct access to MSRs. * XXX C1 "I/O then Halt" seems necessary for some broken BIOS. */ *features = ACPI_CAP_PERF_MSRS | ACPI_CAP_C1_IO_HALT; return (0); } static void est_identify(driver_t *driver, device_t parent) { device_t child; /* * Defer to hwpstate if it is present. This priority logic * should be replaced with normal newbus probing in the * future. */ intel_hwpstate_identify(NULL, parent); - if (device_find_child(parent, "hwpstate_intel", -1) != NULL) + if (device_find_child(parent, "hwpstate_intel", DEVICE_UNIT_ANY) != NULL) return; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "est", -1) != NULL) + if (device_find_child(parent, "est", DEVICE_UNIT_ANY) != NULL) return; /* Check that CPUID is supported and the vendor is Intel.*/ if (cpu_high == 0 || (cpu_vendor_id != CPU_VENDOR_INTEL && cpu_vendor_id != CPU_VENDOR_CENTAUR)) return; /* * Check if the CPU supports EST. */ if (!(cpu_feature2 & CPUID2_EST)) return; /* * We add a child for each CPU since settings must be performed * on each CPU in the SMP case. */ child = BUS_ADD_CHILD(parent, 10, "est", device_get_unit(parent)); if (child == NULL) device_printf(parent, "add est child failed\n"); } static int est_probe(device_t dev) { device_t perf_dev; uint64_t msr; int error, type; if (resource_disabled("est", 0)) return (ENXIO); /* * If the ACPI perf driver has attached and is not just offering * info, let it manage things. */ - perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1); + perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", + DEVICE_UNIT_ANY); if (perf_dev && device_is_attached(perf_dev)) { error = CPUFREQ_DRV_TYPE(perf_dev, &type); if (error == 0 && (type & CPUFREQ_FLAG_INFO_ONLY) == 0) return (ENXIO); } /* Attempt to enable SpeedStep if not currently enabled. */ msr = rdmsr(MSR_MISC_ENABLE); if ((msr & MSR_SS_ENABLE) == 0) { wrmsr(MSR_MISC_ENABLE, msr | MSR_SS_ENABLE); if (bootverbose) device_printf(dev, "enabling SpeedStep\n"); /* Check if the enable failed. */ msr = rdmsr(MSR_MISC_ENABLE); if ((msr & MSR_SS_ENABLE) == 0) { device_printf(dev, "failed to enable SpeedStep\n"); return (ENXIO); } } device_set_desc(dev, "Enhanced SpeedStep Frequency Control"); return (0); } static int est_attach(device_t dev) { struct est_softc *sc; sc = device_get_softc(dev); sc->dev = dev; /* On SMP system we can't guarantie independent freq setting. */ if (strict == -1 && mp_ncpus > 1) strict = 0; /* Check CPU for supported settings. */ if (est_get_info(dev)) return (ENXIO); cpufreq_register(dev); return (0); } static int est_detach(device_t dev) { struct est_softc *sc; int error; error = cpufreq_unregister(dev); if (error) return (error); sc = device_get_softc(dev); if (sc->acpi_settings || sc->msr_settings) free(sc->freq_list, M_DEVBUF); return (0); } /* * Probe for supported CPU settings. First, check our static table of * settings. If no match, try using the ones offered by acpi_perf * (i.e., _PSS). We use ACPI second because some systems (IBM R/T40 * series) export both legacy SMM IO-based access and direct MSR access * but the direct access specifies invalid values for _PSS. */ static int est_get_info(device_t dev) { struct est_softc *sc; uint64_t msr; int error; sc = device_get_softc(dev); msr = rdmsr(MSR_PERF_STATUS); error = est_table_info(dev, msr, &sc->freq_list, &sc->flist_len); if (error) error = est_acpi_info(dev, &sc->freq_list, &sc->flist_len); if (error) error = est_msr_info(dev, msr, &sc->freq_list, &sc->flist_len); if (error) { printf( "est: CPU supports Enhanced Speedstep, but is not recognized.\n" "est: cpu_vendor %s, msr %0jx\n", cpu_vendor, msr); return (ENXIO); } return (0); } static int est_acpi_info(device_t dev, freq_info **freqs, size_t *freqslen) { struct est_softc *sc; struct cf_setting *sets; freq_info *table; device_t perf_dev; int count, error, i, j; uint16_t saved_id16; - perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1); + perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", + DEVICE_UNIT_ANY); if (perf_dev == NULL || !device_is_attached(perf_dev)) return (ENXIO); /* Fetch settings from acpi_perf. */ sc = device_get_softc(dev); table = NULL; sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT); if (sets == NULL) return (ENOMEM); count = MAX_SETTINGS; error = CPUFREQ_DRV_SETTINGS(perf_dev, sets, &count); if (error) goto out; /* Parse settings into our local table format. */ table = malloc(count * sizeof(*table), M_DEVBUF, M_NOWAIT); if (table == NULL) { error = ENOMEM; goto out; } est_get_id16(&saved_id16); for (i = 0, j = 0; i < count; i++) { /* * Confirm id16 value is correct. */ if (sets[i].freq > 0) { error = est_set_id16(dev, sets[i].spec[0], strict); if (error != 0) { if (bootverbose) device_printf(dev, "Invalid freq %u, " "ignored.\n", sets[i].freq); continue; } table[j].freq = sets[i].freq; table[j].volts = sets[i].volts; table[j].id16 = sets[i].spec[0]; table[j].power = sets[i].power; ++j; } } /* restore saved setting */ est_set_id16(dev, saved_id16, 0); sc->acpi_settings = TRUE; *freqs = table; *freqslen = j; error = 0; out: if (sets) free(sets, M_TEMP); if (error && table) free(table, M_DEVBUF); return (error); } static int est_table_info(device_t dev, uint64_t msr, freq_info **freqs, size_t *freqslen) { cpu_info *p; uint32_t id; /* Find a table which matches (vendor, id32). */ id = msr >> 32; for (p = ESTprocs; p->id32 != 0; p++) { if (p->vendor_id == cpu_vendor_id && p->id32 == id) break; } if (p->id32 == 0) return (EOPNOTSUPP); /* Make sure the current setpoint is valid. */ if (est_get_current(p->freqtab, p->tablen) == NULL) { device_printf(dev, "current setting not found in table\n"); return (EOPNOTSUPP); } *freqs = p->freqtab; *freqslen = p->tablen; return (0); } static int bus_speed_ok(int bus) { switch (bus) { case 100: case 133: case 333: return (1); default: return (0); } } /* * Flesh out a simple rate table containing the high and low frequencies * based on the current clock speed and the upper 32 bits of the MSR. */ static int est_msr_info(device_t dev, uint64_t msr, freq_info **freqs, size_t *freqslen) { struct est_softc *sc; freq_info *fp; int bus, freq, volts; uint16_t id; if (!msr_info_enabled) return (EOPNOTSUPP); /* Figure out the bus clock. */ freq = atomic_load_acq_64(&tsc_freq) / 1000000; id = msr >> 32; bus = freq / (id >> 8); device_printf(dev, "Guessed bus clock (high) of %d MHz\n", bus); if (!bus_speed_ok(bus)) { /* We may be running on the low frequency. */ id = msr >> 48; bus = freq / (id >> 8); device_printf(dev, "Guessed bus clock (low) of %d MHz\n", bus); if (!bus_speed_ok(bus)) return (EOPNOTSUPP); /* Calculate high frequency. */ id = msr >> 32; freq = ((id >> 8) & 0xff) * bus; } /* Fill out a new freq table containing just the high and low freqs. */ sc = device_get_softc(dev); fp = malloc(sizeof(freq_info) * 2, M_DEVBUF, M_WAITOK | M_ZERO); /* First, the high frequency. */ volts = id & 0xff; if (volts != 0) { volts <<= 4; volts += 700; } fp[0].freq = freq; fp[0].volts = volts; fp[0].id16 = id; fp[0].power = CPUFREQ_VAL_UNKNOWN; device_printf(dev, "Guessed high setting of %d MHz @ %d Mv\n", freq, volts); /* Second, the low frequency. */ id = msr >> 48; freq = ((id >> 8) & 0xff) * bus; volts = id & 0xff; if (volts != 0) { volts <<= 4; volts += 700; } fp[1].freq = freq; fp[1].volts = volts; fp[1].id16 = id; fp[1].power = CPUFREQ_VAL_UNKNOWN; device_printf(dev, "Guessed low setting of %d MHz @ %d Mv\n", freq, volts); /* Table is already terminated due to M_ZERO. */ sc->msr_settings = TRUE; *freqs = fp; *freqslen = 2; return (0); } static void est_get_id16(uint16_t *id16_p) { *id16_p = rdmsr(MSR_PERF_STATUS) & 0xffff; } static int est_set_id16(device_t dev, uint16_t id16, int need_check) { uint64_t msr; uint16_t new_id16; int ret = 0; /* Read the current register, mask out the old, set the new id. */ msr = rdmsr(MSR_PERF_CTL); msr = (msr & ~0xffff) | id16; wrmsr(MSR_PERF_CTL, msr); if (need_check) { /* Wait a short while and read the new status. */ DELAY(EST_TRANS_LAT); est_get_id16(&new_id16); if (new_id16 != id16) { if (bootverbose) device_printf(dev, "Invalid id16 (set, cur) " "= (%u, %u)\n", id16, new_id16); ret = ENXIO; } } return (ret); } static freq_info * est_get_current(freq_info *freq_list, size_t tablen) { freq_info *f; int i; uint16_t id16; /* * Try a few times to get a valid value. Sometimes, if the CPU * is in the middle of an asynchronous transition (i.e., P4TCC), * we get a temporary invalid result. */ for (i = 0; i < 5; i++) { est_get_id16(&id16); for (f = freq_list; f < freq_list + tablen; f++) { if (f->id16 == id16) return (f); } DELAY(100); } return (NULL); } static int est_settings(device_t dev, struct cf_setting *sets, int *count) { struct est_softc *sc; freq_info *f; int i; sc = device_get_softc(dev); if (*count < EST_MAX_SETTINGS) return (E2BIG); i = 0; for (f = sc->freq_list; f < sc->freq_list + sc->flist_len; f++, i++) { sets[i].freq = f->freq; sets[i].volts = f->volts; sets[i].power = f->power; sets[i].lat = EST_TRANS_LAT; sets[i].dev = dev; } *count = i; return (0); } static int est_set(device_t dev, const struct cf_setting *set) { struct est_softc *sc; freq_info *f; /* Find the setting matching the requested one. */ sc = device_get_softc(dev); for (f = sc->freq_list; f < sc->freq_list + sc->flist_len; f++) { if (f->freq == set->freq) break; } if (f->freq == 0) return (EINVAL); /* Read the current register, mask out the old, set the new id. */ est_set_id16(dev, f->id16, 0); return (0); } static int est_get(device_t dev, struct cf_setting *set) { struct est_softc *sc; freq_info *f; sc = device_get_softc(dev); f = est_get_current(sc->freq_list, sc->flist_len); if (f == NULL) return (ENXIO); set->freq = f->freq; set->volts = f->volts; set->power = f->power; set->lat = EST_TRANS_LAT; set->dev = dev; return (0); } static int est_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } diff --git a/sys/x86/cpufreq/hwpstate_amd.c b/sys/x86/cpufreq/hwpstate_amd.c index a9305e571ece..fc948dc90a15 100644 --- a/sys/x86/cpufreq/hwpstate_amd.c +++ b/sys/x86/cpufreq/hwpstate_amd.c @@ -1,606 +1,607 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005 Nate Lawson * Copyright (c) 2004 Colin Percival * Copyright (c) 2004-2005 Bruno Durcot * Copyright (c) 2004 FUKUDA Nobuhiko * Copyright (c) 2009 Michael Reifenberger * Copyright (c) 2009 Norikatsu Shigemura * Copyright (c) 2008-2009 Gen Otsuji * * This code is depending on kern_cpu.c, est.c, powernow.c, p4tcc.c, smist.c * in various parts. The authors of these files are Nate Lawson, * Colin Percival, Bruno Durcot, and FUKUDA Nobuhiko. * This code contains patches by Michael Reifenberger and Norikatsu Shigemura. * Thank you. * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, 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. */ /* * For more info: * BIOS and Kernel Developer's Guide(BKDG) for AMD Family 10h Processors * 31116 Rev 3.20 February 04, 2009 * BIOS and Kernel Developer's Guide(BKDG) for AMD Family 11h Processors * 41256 Rev 3.00 - July 07, 2008 * Processor Programming Reference (PPR) for AMD Family 1Ah Model 02h, * Revision C1 Processors Volume 1 of 7 - Sep 29, 2024 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_if.h" #include "cpufreq_if.h" #define MSR_AMD_10H_11H_LIMIT 0xc0010061 #define MSR_AMD_10H_11H_CONTROL 0xc0010062 #define MSR_AMD_10H_11H_STATUS 0xc0010063 #define MSR_AMD_10H_11H_CONFIG 0xc0010064 #define AMD_10H_11H_MAX_STATES 16 /* for MSR_AMD_10H_11H_LIMIT C001_0061 */ #define AMD_10H_11H_GET_PSTATE_MAX_VAL(msr) (((msr) >> 4) & 0x7) #define AMD_10H_11H_GET_PSTATE_LIMIT(msr) (((msr)) & 0x7) /* for MSR_AMD_10H_11H_CONFIG 10h:C001_0064:68 / 11h:C001_0064:6B */ #define AMD_10H_11H_CUR_VID(msr) (((msr) >> 9) & 0x7F) #define AMD_10H_11H_CUR_DID(msr) (((msr) >> 6) & 0x07) #define AMD_10H_11H_CUR_FID(msr) ((msr) & 0x3F) #define AMD_17H_CUR_IDIV(msr) (((msr) >> 30) & 0x03) #define AMD_17H_CUR_IDD(msr) (((msr) >> 22) & 0xFF) #define AMD_17H_CUR_VID(msr) (((msr) >> 14) & 0xFF) #define AMD_17H_CUR_DID(msr) (((msr) >> 8) & 0x3F) #define AMD_17H_CUR_FID(msr) ((msr) & 0xFF) #define AMD_1AH_CUR_FID(msr) ((msr) & 0xFFF) #define HWPSTATE_DEBUG(dev, msg...) \ do { \ if (hwpstate_verbose) \ device_printf(dev, msg); \ } while (0) struct hwpstate_setting { int freq; /* CPU clock in Mhz or 100ths of a percent. */ int volts; /* Voltage in mV. */ int power; /* Power consumed in mW. */ int lat; /* Transition latency in us. */ int pstate_id; /* P-State id */ }; struct hwpstate_softc { device_t dev; struct hwpstate_setting hwpstate_settings[AMD_10H_11H_MAX_STATES]; int cfnum; }; static void hwpstate_identify(driver_t *driver, device_t parent); static int hwpstate_probe(device_t dev); static int hwpstate_attach(device_t dev); static int hwpstate_detach(device_t dev); static int hwpstate_set(device_t dev, const struct cf_setting *cf); static int hwpstate_get(device_t dev, struct cf_setting *cf); static int hwpstate_settings(device_t dev, struct cf_setting *sets, int *count); static int hwpstate_type(device_t dev, int *type); static int hwpstate_shutdown(device_t dev); static int hwpstate_features(driver_t *driver, u_int *features); static int hwpstate_get_info_from_acpi_perf(device_t dev, device_t perf_dev); static int hwpstate_get_info_from_msr(device_t dev); static int hwpstate_goto_pstate(device_t dev, int pstate_id); static int hwpstate_verbose; SYSCTL_INT(_debug, OID_AUTO, hwpstate_verbose, CTLFLAG_RWTUN, &hwpstate_verbose, 0, "Debug hwpstate"); static int hwpstate_verify; SYSCTL_INT(_debug, OID_AUTO, hwpstate_verify, CTLFLAG_RWTUN, &hwpstate_verify, 0, "Verify P-state after setting"); static bool hwpstate_pstate_limit; SYSCTL_BOOL(_debug, OID_AUTO, hwpstate_pstate_limit, CTLFLAG_RWTUN, &hwpstate_pstate_limit, 0, "If enabled (1), limit administrative control of P-states to the value in " "CurPstateLimit"); static device_method_t hwpstate_methods[] = { /* Device interface */ DEVMETHOD(device_identify, hwpstate_identify), DEVMETHOD(device_probe, hwpstate_probe), DEVMETHOD(device_attach, hwpstate_attach), DEVMETHOD(device_detach, hwpstate_detach), DEVMETHOD(device_shutdown, hwpstate_shutdown), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, hwpstate_set), DEVMETHOD(cpufreq_drv_get, hwpstate_get), DEVMETHOD(cpufreq_drv_settings, hwpstate_settings), DEVMETHOD(cpufreq_drv_type, hwpstate_type), /* ACPI interface */ DEVMETHOD(acpi_get_features, hwpstate_features), {0, 0} }; static driver_t hwpstate_driver = { "hwpstate", hwpstate_methods, sizeof(struct hwpstate_softc), }; DRIVER_MODULE(hwpstate, cpu, hwpstate_driver, 0, 0); static int hwpstate_amd_iscale(int val, int div) { switch (div) { case 3: /* divide by 1000 */ val /= 10; case 2: /* divide by 100 */ val /= 10; case 1: /* divide by 10 */ val /= 10; case 0: /* divide by 1 */ ; } return (val); } /* * Go to Px-state on all cpus, considering the limit register (if so * configured). */ static int hwpstate_goto_pstate(device_t dev, int id) { sbintime_t sbt; uint64_t msr; int cpu, i, j, limit; if (hwpstate_pstate_limit) { /* get the current pstate limit */ msr = rdmsr(MSR_AMD_10H_11H_LIMIT); limit = AMD_10H_11H_GET_PSTATE_LIMIT(msr); if (limit > id) { HWPSTATE_DEBUG(dev, "Restricting requested P%d to P%d " "due to HW limit\n", id, limit); id = limit; } } cpu = curcpu; HWPSTATE_DEBUG(dev, "setting P%d-state on cpu%d\n", id, cpu); /* Go To Px-state */ wrmsr(MSR_AMD_10H_11H_CONTROL, id); /* * We are going to the same Px-state on all cpus. * Probably should take _PSD into account. */ CPU_FOREACH(i) { if (i == cpu) continue; /* Bind to each cpu. */ thread_lock(curthread); sched_bind(curthread, i); thread_unlock(curthread); HWPSTATE_DEBUG(dev, "setting P%d-state on cpu%d\n", id, i); /* Go To Px-state */ wrmsr(MSR_AMD_10H_11H_CONTROL, id); } /* * Verify whether each core is in the requested P-state. */ if (hwpstate_verify) { CPU_FOREACH(i) { thread_lock(curthread); sched_bind(curthread, i); thread_unlock(curthread); /* wait loop (100*100 usec is enough ?) */ for (j = 0; j < 100; j++) { /* get the result. not assure msr=id */ msr = rdmsr(MSR_AMD_10H_11H_STATUS); if (msr == id) break; sbt = SBT_1MS / 10; tsleep_sbt(dev, PZERO, "pstate_goto", sbt, sbt >> tc_precexp, 0); } HWPSTATE_DEBUG(dev, "result: P%d-state on cpu%d\n", (int)msr, i); if (msr != id) { HWPSTATE_DEBUG(dev, "error: loop is not enough.\n"); return (ENXIO); } } } return (0); } static int hwpstate_set(device_t dev, const struct cf_setting *cf) { struct hwpstate_softc *sc; struct hwpstate_setting *set; int i; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); set = sc->hwpstate_settings; for (i = 0; i < sc->cfnum; i++) if (CPUFREQ_CMP(cf->freq, set[i].freq)) break; if (i == sc->cfnum) return (EINVAL); return (hwpstate_goto_pstate(dev, set[i].pstate_id)); } static int hwpstate_get(device_t dev, struct cf_setting *cf) { struct hwpstate_softc *sc; struct hwpstate_setting set; uint64_t msr; sc = device_get_softc(dev); if (cf == NULL) return (EINVAL); msr = rdmsr(MSR_AMD_10H_11H_STATUS); if (msr >= sc->cfnum) return (EINVAL); set = sc->hwpstate_settings[msr]; cf->freq = set.freq; cf->volts = set.volts; cf->power = set.power; cf->lat = set.lat; cf->dev = dev; return (0); } static int hwpstate_settings(device_t dev, struct cf_setting *sets, int *count) { struct hwpstate_softc *sc; struct hwpstate_setting set; int i; if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); if (*count < sc->cfnum) return (E2BIG); for (i = 0; i < sc->cfnum; i++, sets++) { set = sc->hwpstate_settings[i]; sets->freq = set.freq; sets->volts = set.volts; sets->power = set.power; sets->lat = set.lat; sets->dev = dev; } *count = sc->cfnum; return (0); } static int hwpstate_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static void hwpstate_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "hwpstate", -1) != NULL) + if (device_find_child(parent, "hwpstate", DEVICE_UNIT_ANY) != NULL) return; if ((cpu_vendor_id != CPU_VENDOR_AMD || CPUID_TO_FAMILY(cpu_id) < 0x10) && cpu_vendor_id != CPU_VENDOR_HYGON) return; /* * Check if hardware pstate enable bit is set. */ if ((amd_pminfo & AMDPM_HW_PSTATE) == 0) { HWPSTATE_DEBUG(parent, "hwpstate enable bit is not set.\n"); return; } if (resource_disabled("hwpstate", 0)) return; if (BUS_ADD_CHILD(parent, 10, "hwpstate", device_get_unit(parent)) == NULL) device_printf(parent, "hwpstate: add child failed\n"); } static int hwpstate_probe(device_t dev) { struct hwpstate_softc *sc; device_t perf_dev; uint64_t msr; int error, type; /* * Only hwpstate0. * It goes well with acpi_throttle. */ if (device_get_unit(dev) != 0) return (ENXIO); sc = device_get_softc(dev); sc->dev = dev; /* * Check if acpi_perf has INFO only flag. */ - perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1); + perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", + DEVICE_UNIT_ANY); error = TRUE; if (perf_dev && device_is_attached(perf_dev)) { error = CPUFREQ_DRV_TYPE(perf_dev, &type); if (error == 0) { if ((type & CPUFREQ_FLAG_INFO_ONLY) == 0) { /* * If acpi_perf doesn't have INFO_ONLY flag, * it will take care of pstate transitions. */ HWPSTATE_DEBUG(dev, "acpi_perf will take care of pstate transitions.\n"); return (ENXIO); } else { /* * If acpi_perf has INFO_ONLY flag, (_PCT has FFixedHW) * we can get _PSS info from acpi_perf * without going into ACPI. */ HWPSTATE_DEBUG(dev, "going to fetch info from acpi_perf\n"); error = hwpstate_get_info_from_acpi_perf(dev, perf_dev); } } } if (error == 0) { /* * Now we get _PSS info from acpi_perf without error. * Let's check it. */ msr = rdmsr(MSR_AMD_10H_11H_LIMIT); if (sc->cfnum != 1 + AMD_10H_11H_GET_PSTATE_MAX_VAL(msr)) { HWPSTATE_DEBUG(dev, "MSR (%jd) and ACPI _PSS (%d)" " count mismatch\n", (intmax_t)msr, sc->cfnum); error = TRUE; } } /* * If we cannot get info from acpi_perf, * Let's get info from MSRs. */ if (error) error = hwpstate_get_info_from_msr(dev); if (error) return (error); device_set_desc(dev, "Cool`n'Quiet 2.0"); return (0); } static int hwpstate_attach(device_t dev) { return (cpufreq_register(dev)); } static int hwpstate_get_info_from_msr(device_t dev) { struct hwpstate_softc *sc; struct hwpstate_setting *hwpstate_set; uint64_t msr; int family, i, fid, did; family = CPUID_TO_FAMILY(cpu_id); sc = device_get_softc(dev); /* Get pstate count */ msr = rdmsr(MSR_AMD_10H_11H_LIMIT); sc->cfnum = 1 + AMD_10H_11H_GET_PSTATE_MAX_VAL(msr); hwpstate_set = sc->hwpstate_settings; for (i = 0; i < sc->cfnum; i++) { msr = rdmsr(MSR_AMD_10H_11H_CONFIG + i); if ((msr & ((uint64_t)1 << 63)) == 0) { HWPSTATE_DEBUG(dev, "msr is not valid.\n"); return (ENXIO); } did = AMD_10H_11H_CUR_DID(msr); fid = AMD_10H_11H_CUR_FID(msr); hwpstate_set[i].volts = CPUFREQ_VAL_UNKNOWN; hwpstate_set[i].power = CPUFREQ_VAL_UNKNOWN; hwpstate_set[i].lat = CPUFREQ_VAL_UNKNOWN; /* Convert fid/did to frequency. */ switch (family) { case 0x11: hwpstate_set[i].freq = (100 * (fid + 0x08)) >> did; break; case 0x10: case 0x12: case 0x15: case 0x16: hwpstate_set[i].freq = (100 * (fid + 0x10)) >> did; break; case 0x17: case 0x18: case 0x19: case 0x1A: /* calculate freq */ if (family == 0x1A) { fid = AMD_1AH_CUR_FID(msr); /* 1Ah CPU don't use a divisor */ hwpstate_set[i].freq = fid; if (fid > 0x0f) hwpstate_set[i].freq *= 5; else { HWPSTATE_DEBUG(dev, "unexpected fid: %d\n", fid); return (ENXIO); } } else { did = AMD_17H_CUR_DID(msr); if (did == 0) { HWPSTATE_DEBUG(dev, "unexpected did: 0\n"); did = 1; } fid = AMD_17H_CUR_FID(msr); hwpstate_set[i].freq = (200 * fid) / did; } /* Vid step is 6.25mV, so scale by 100. */ hwpstate_set[i].volts = (155000 - (625 * AMD_17H_CUR_VID(msr))) / 100; /* * Calculate current first. * This equation is mentioned in * "BKDG for AMD Family 15h Models 70h-7fh Processors", * section 2.5.2.1.6. */ hwpstate_set[i].power = AMD_17H_CUR_IDD(msr) * 1000; hwpstate_set[i].power = hwpstate_amd_iscale( hwpstate_set[i].power, AMD_17H_CUR_IDIV(msr)); hwpstate_set[i].power *= hwpstate_set[i].volts; /* Milli amps * milli volts to milli watts. */ hwpstate_set[i].power /= 1000; break; default: HWPSTATE_DEBUG(dev, "get_info_from_msr: %s family" " 0x%02x CPUs are not supported yet\n", cpu_vendor_id == CPU_VENDOR_HYGON ? "Hygon" : "AMD", family); return (ENXIO); } hwpstate_set[i].pstate_id = i; } return (0); } static int hwpstate_get_info_from_acpi_perf(device_t dev, device_t perf_dev) { struct hwpstate_softc *sc; struct cf_setting *perf_set; struct hwpstate_setting *hwpstate_set; int count, error, i; perf_set = malloc(MAX_SETTINGS * sizeof(*perf_set), M_TEMP, M_NOWAIT); if (perf_set == NULL) { HWPSTATE_DEBUG(dev, "nomem\n"); return (ENOMEM); } /* * Fetch settings from acpi_perf. * Now it is attached, and has info only flag. */ count = MAX_SETTINGS; error = CPUFREQ_DRV_SETTINGS(perf_dev, perf_set, &count); if (error) { HWPSTATE_DEBUG(dev, "error: CPUFREQ_DRV_SETTINGS.\n"); goto out; } sc = device_get_softc(dev); sc->cfnum = count; hwpstate_set = sc->hwpstate_settings; for (i = 0; i < count; i++) { if (i == perf_set[i].spec[0]) { hwpstate_set[i].pstate_id = i; hwpstate_set[i].freq = perf_set[i].freq; hwpstate_set[i].volts = perf_set[i].volts; hwpstate_set[i].power = perf_set[i].power; hwpstate_set[i].lat = perf_set[i].lat; } else { HWPSTATE_DEBUG(dev, "ACPI _PSS object mismatch.\n"); error = ENXIO; goto out; } } out: if (perf_set) free(perf_set, M_TEMP); return (error); } static int hwpstate_detach(device_t dev) { hwpstate_goto_pstate(dev, 0); return (cpufreq_unregister(dev)); } static int hwpstate_shutdown(device_t dev) { /* hwpstate_goto_pstate(dev, 0); */ return (0); } static int hwpstate_features(driver_t *driver, u_int *features) { /* Notify the ACPI CPU that we support direct access to MSRs */ *features = ACPI_CAP_PERF_MSRS; return (0); } diff --git a/sys/x86/cpufreq/hwpstate_intel.c b/sys/x86/cpufreq/hwpstate_intel.c index b4378154c408..259aeac399c8 100644 --- a/sys/x86/cpufreq/hwpstate_intel.c +++ b/sys/x86/cpufreq/hwpstate_intel.c @@ -1,633 +1,633 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 Intel Corporation * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_if.h" #include "cpufreq_if.h" extern uint64_t tsc_freq; static int intel_hwpstate_probe(device_t dev); static int intel_hwpstate_attach(device_t dev); static int intel_hwpstate_detach(device_t dev); static int intel_hwpstate_suspend(device_t dev); static int intel_hwpstate_resume(device_t dev); static int intel_hwpstate_get(device_t dev, struct cf_setting *cf); static int intel_hwpstate_type(device_t dev, int *type); static device_method_t intel_hwpstate_methods[] = { /* Device interface */ DEVMETHOD(device_identify, intel_hwpstate_identify), DEVMETHOD(device_probe, intel_hwpstate_probe), DEVMETHOD(device_attach, intel_hwpstate_attach), DEVMETHOD(device_detach, intel_hwpstate_detach), DEVMETHOD(device_suspend, intel_hwpstate_suspend), DEVMETHOD(device_resume, intel_hwpstate_resume), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_get, intel_hwpstate_get), DEVMETHOD(cpufreq_drv_type, intel_hwpstate_type), DEVMETHOD_END }; struct hwp_softc { device_t dev; bool hwp_notifications; bool hwp_activity_window; bool hwp_pref_ctrl; bool hwp_pkg_ctrl; bool hwp_pkg_ctrl_en; bool hwp_perf_bias; bool hwp_perf_bias_cached; uint64_t req; /* Cached copy of HWP_REQUEST */ uint64_t hwp_energy_perf_bias; /* Cache PERF_BIAS */ uint8_t high; uint8_t guaranteed; uint8_t efficient; uint8_t low; }; static driver_t hwpstate_intel_driver = { "hwpstate_intel", intel_hwpstate_methods, sizeof(struct hwp_softc), }; DRIVER_MODULE(hwpstate_intel, cpu, hwpstate_intel_driver, NULL, NULL); MODULE_VERSION(hwpstate_intel, 1); static bool hwpstate_pkg_ctrl_enable = true; SYSCTL_BOOL(_machdep, OID_AUTO, hwpstate_pkg_ctrl, CTLFLAG_RDTUN, &hwpstate_pkg_ctrl_enable, 0, "Set 1 (default) to enable package-level control, 0 to disable"); static int intel_hwp_dump_sysctl_handler(SYSCTL_HANDLER_ARGS) { device_t dev; struct pcpu *pc; struct sbuf *sb; struct hwp_softc *sc; uint64_t data, data2; int ret; sc = (struct hwp_softc *)arg1; dev = sc->dev; pc = cpu_get_pcpu(dev); if (pc == NULL) return (ENXIO); sb = sbuf_new(NULL, NULL, 1024, SBUF_FIXEDLEN | SBUF_INCLUDENUL); sbuf_putc(sb, '\n'); thread_lock(curthread); sched_bind(curthread, pc->pc_cpuid); thread_unlock(curthread); rdmsr_safe(MSR_IA32_PM_ENABLE, &data); sbuf_printf(sb, "CPU%d: HWP %sabled\n", pc->pc_cpuid, ((data & 1) ? "En" : "Dis")); if (data == 0) { ret = 0; goto out; } rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &data); sbuf_printf(sb, "\tHighest Performance: %03ju\n", data & 0xff); sbuf_printf(sb, "\tGuaranteed Performance: %03ju\n", (data >> 8) & 0xff); sbuf_printf(sb, "\tEfficient Performance: %03ju\n", (data >> 16) & 0xff); sbuf_printf(sb, "\tLowest Performance: %03ju\n", (data >> 24) & 0xff); rdmsr_safe(MSR_IA32_HWP_REQUEST, &data); data2 = 0; if (sc->hwp_pkg_ctrl && (data & IA32_HWP_REQUEST_PACKAGE_CONTROL)) rdmsr_safe(MSR_IA32_HWP_REQUEST_PKG, &data2); sbuf_putc(sb, '\n'); #define pkg_print(x, name, offset) do { \ if (!sc->hwp_pkg_ctrl || (data & x) != 0) \ sbuf_printf(sb, "\t%s: %03u\n", name, \ (unsigned)(data >> offset) & 0xff); \ else \ sbuf_printf(sb, "\t%s: %03u\n", name, \ (unsigned)(data2 >> offset) & 0xff); \ } while (0) pkg_print(IA32_HWP_REQUEST_EPP_VALID, "Requested Efficiency Performance Preference", 24); pkg_print(IA32_HWP_REQUEST_DESIRED_VALID, "Requested Desired Performance", 16); pkg_print(IA32_HWP_REQUEST_MAXIMUM_VALID, "Requested Maximum Performance", 8); pkg_print(IA32_HWP_REQUEST_MINIMUM_VALID, "Requested Minimum Performance", 0); #undef pkg_print sbuf_putc(sb, '\n'); out: thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); ret = sbuf_finish(sb); if (ret == 0) ret = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb)); sbuf_delete(sb); return (ret); } static inline int percent_to_raw(int x) { MPASS(x <= 100 && x >= 0); return (0xff * x / 100); } /* * Given x * 10 in [0, 1000], round to the integer nearest x. * * This allows round-tripping nice human readable numbers through this * interface. Otherwise, user-provided percentages such as 25, 50, 75 get * rounded down to 24, 49, and 74, which is a bit ugly. */ static inline int round10(int xtimes10) { return ((xtimes10 + 5) / 10); } static inline int raw_to_percent(int x) { MPASS(x <= 0xff && x >= 0); return (round10(x * 1000 / 0xff)); } /* Range of MSR_IA32_ENERGY_PERF_BIAS is more limited: 0-0xf. */ static inline int percent_to_raw_perf_bias(int x) { /* * Round up so that raw values present as nice round human numbers and * also round-trip to the same raw value. */ MPASS(x <= 100 && x >= 0); return (((0xf * x) + 50) / 100); } static inline int raw_to_percent_perf_bias(int x) { /* Rounding to nice human numbers despite a step interval of 6.67%. */ MPASS(x <= 0xf && x >= 0); return (((x * 20) / 0xf) * 5); } static int sysctl_epp_select(SYSCTL_HANDLER_ARGS) { struct hwp_softc *sc; device_t dev; struct pcpu *pc; uint64_t epb; uint32_t val; int ret; dev = oidp->oid_arg1; sc = device_get_softc(dev); if (!sc->hwp_pref_ctrl && !sc->hwp_perf_bias) return (ENODEV); pc = cpu_get_pcpu(dev); if (pc == NULL) return (ENXIO); thread_lock(curthread); sched_bind(curthread, pc->pc_cpuid); thread_unlock(curthread); if (sc->hwp_pref_ctrl) { val = (sc->req & IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE) >> 24; val = raw_to_percent(val); } else { /* * If cpuid indicates EPP is not supported, the HWP controller * uses MSR_IA32_ENERGY_PERF_BIAS instead (Intel SDM ยง14.4.4). * This register is per-core (but not HT). */ if (!sc->hwp_perf_bias_cached) { ret = rdmsr_safe(MSR_IA32_ENERGY_PERF_BIAS, &epb); if (ret) goto out; sc->hwp_energy_perf_bias = epb; sc->hwp_perf_bias_cached = true; } val = sc->hwp_energy_perf_bias & IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK; val = raw_to_percent_perf_bias(val); } MPASS(val >= 0 && val <= 100); ret = sysctl_handle_int(oidp, &val, 0, req); if (ret || req->newptr == NULL) goto out; if (val > 100) { ret = EINVAL; goto out; } if (sc->hwp_pref_ctrl) { val = percent_to_raw(val); sc->req = ((sc->req & ~IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE) | (val << 24u)); if (sc->hwp_pkg_ctrl_en) ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req); else ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req); } else { val = percent_to_raw_perf_bias(val); MPASS((val & ~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) == 0); sc->hwp_energy_perf_bias = ((sc->hwp_energy_perf_bias & ~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) | val); ret = wrmsr_safe(MSR_IA32_ENERGY_PERF_BIAS, sc->hwp_energy_perf_bias); } out: thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); return (ret); } void intel_hwpstate_identify(driver_t *driver, device_t parent) { - if (device_find_child(parent, "hwpstate_intel", -1) != NULL) + if (device_find_child(parent, "hwpstate_intel", DEVICE_UNIT_ANY) != NULL) return; if (cpu_vendor_id != CPU_VENDOR_INTEL) return; if (resource_disabled("hwpstate_intel", 0)) return; /* * Intel SDM 14.4.1 (HWP Programming Interfaces): * Availability of HWP baseline resource and capability, * CPUID.06H:EAX[bit 7]: If this bit is set, HWP provides several new * architectural MSRs: IA32_PM_ENABLE, IA32_HWP_CAPABILITIES, * IA32_HWP_REQUEST, IA32_HWP_STATUS. */ if ((cpu_power_eax & CPUTPM1_HWP) == 0) return; if (BUS_ADD_CHILD(parent, 10, "hwpstate_intel", device_get_unit(parent)) == NULL) device_printf(parent, "hwpstate_intel: add child failed\n"); } static int intel_hwpstate_probe(device_t dev) { device_set_desc(dev, "Intel Speed Shift"); return (BUS_PROBE_NOWILDCARD); } static int set_autonomous_hwp(struct hwp_softc *sc) { struct pcpu *pc; device_t dev; uint64_t caps; int ret; dev = sc->dev; pc = cpu_get_pcpu(dev); if (pc == NULL) return (ENXIO); thread_lock(curthread); sched_bind(curthread, pc->pc_cpuid); thread_unlock(curthread); /* XXX: Many MSRs aren't readable until feature is enabled */ ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1); if (ret) { /* * This is actually a package-level MSR, and only the first * write is not ignored. So it is harmless to enable it across * all devices, and this allows us not to care especially in * which order cores (and packages) are probed. This error * condition should not happen given we gate on the HWP CPUID * feature flag, if the Intel SDM is correct. */ device_printf(dev, "Failed to enable HWP for cpu%d (%d)\n", pc->pc_cpuid, ret); goto out; } ret = rdmsr_safe(MSR_IA32_HWP_REQUEST, &sc->req); if (ret) { device_printf(dev, "Failed to read HWP request MSR for cpu%d (%d)\n", pc->pc_cpuid, ret); goto out; } ret = rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &caps); if (ret) { device_printf(dev, "Failed to read HWP capabilities MSR for cpu%d (%d)\n", pc->pc_cpuid, ret); goto out; } /* * High and low are static; "guaranteed" is dynamic; and efficient is * also dynamic. */ sc->high = IA32_HWP_CAPABILITIES_HIGHEST_PERFORMANCE(caps); sc->guaranteed = IA32_HWP_CAPABILITIES_GUARANTEED_PERFORMANCE(caps); sc->efficient = IA32_HWP_CAPABILITIES_EFFICIENT_PERFORMANCE(caps); sc->low = IA32_HWP_CAPABILITIES_LOWEST_PERFORMANCE(caps); /* hardware autonomous selection determines the performance target */ sc->req &= ~IA32_HWP_DESIRED_PERFORMANCE; /* enable HW dynamic selection of window size */ sc->req &= ~IA32_HWP_ACTIVITY_WINDOW; /* IA32_HWP_REQUEST.Minimum_Performance = IA32_HWP_CAPABILITIES.Lowest_Performance */ sc->req &= ~IA32_HWP_MINIMUM_PERFORMANCE; sc->req |= sc->low; /* IA32_HWP_REQUEST.Maximum_Performance = IA32_HWP_CAPABILITIES.Highest_Performance. */ sc->req &= ~IA32_HWP_REQUEST_MAXIMUM_PERFORMANCE; sc->req |= sc->high << 8; /* If supported, request package-level control for this CPU. */ if (sc->hwp_pkg_ctrl_en) ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req | IA32_HWP_REQUEST_PACKAGE_CONTROL); else ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req); if (ret) { device_printf(dev, "Failed to setup%s autonomous HWP for cpu%d\n", sc->hwp_pkg_ctrl_en ? " PKG" : "", pc->pc_cpuid); goto out; } /* If supported, write the PKG-wide control MSR. */ if (sc->hwp_pkg_ctrl_en) { /* * "The structure of the IA32_HWP_REQUEST_PKG MSR * (package-level) is identical to the IA32_HWP_REQUEST MSR * with the exception of the Package Control field, which does * not exist." (Intel SDM ยง14.4.4) */ ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req); if (ret) { device_printf(dev, "Failed to set autonomous HWP for package\n"); } } out: thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); return (ret); } static int intel_hwpstate_attach(device_t dev) { struct hwp_softc *sc; int ret; sc = device_get_softc(dev); sc->dev = dev; /* eax */ if (cpu_power_eax & CPUTPM1_HWP_NOTIFICATION) sc->hwp_notifications = true; if (cpu_power_eax & CPUTPM1_HWP_ACTIVITY_WINDOW) sc->hwp_activity_window = true; if (cpu_power_eax & CPUTPM1_HWP_PERF_PREF) sc->hwp_pref_ctrl = true; if (cpu_power_eax & CPUTPM1_HWP_PKG) sc->hwp_pkg_ctrl = true; /* Allow administrators to disable pkg-level control. */ sc->hwp_pkg_ctrl_en = (sc->hwp_pkg_ctrl && hwpstate_pkg_ctrl_enable); /* ecx */ if (cpu_power_ecx & CPUID_PERF_BIAS) sc->hwp_perf_bias = true; ret = set_autonomous_hwp(sc); if (ret) return (ret); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_STATIC_CHILDREN(_debug), OID_AUTO, device_get_nameunit(dev), CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP | CTLFLAG_MPSAFE, sc, 0, intel_hwp_dump_sysctl_handler, "A", ""); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "epp", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, dev, 0, sysctl_epp_select, "I", "Efficiency/Performance Preference " "(range from 0, most performant, through 100, most efficient)"); return (cpufreq_register(dev)); } static int intel_hwpstate_detach(device_t dev) { return (cpufreq_unregister(dev)); } static int intel_hwpstate_get(device_t dev, struct cf_setting *set) { struct pcpu *pc; uint64_t rate; int ret; if (set == NULL) return (EINVAL); pc = cpu_get_pcpu(dev); if (pc == NULL) return (ENXIO); memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); set->dev = dev; ret = cpu_est_clockrate(pc->pc_cpuid, &rate); if (ret == 0) set->freq = rate / 1000000; set->volts = CPUFREQ_VAL_UNKNOWN; set->power = CPUFREQ_VAL_UNKNOWN; set->lat = CPUFREQ_VAL_UNKNOWN; return (0); } static int intel_hwpstate_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE | CPUFREQ_FLAG_INFO_ONLY | CPUFREQ_FLAG_UNCACHED; return (0); } static int intel_hwpstate_suspend(device_t dev) { return (0); } /* * Redo a subset of set_autonomous_hwp on resume; untested. Without this, * testers observed that on resume MSR_IA32_HWP_REQUEST was bogus. */ static int intel_hwpstate_resume(device_t dev) { struct hwp_softc *sc; struct pcpu *pc; int ret; sc = device_get_softc(dev); pc = cpu_get_pcpu(dev); if (pc == NULL) return (ENXIO); thread_lock(curthread); sched_bind(curthread, pc->pc_cpuid); thread_unlock(curthread); ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1); if (ret) { device_printf(dev, "Failed to enable HWP for cpu%d after suspend (%d)\n", pc->pc_cpuid, ret); goto out; } if (sc->hwp_pkg_ctrl_en) ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req | IA32_HWP_REQUEST_PACKAGE_CONTROL); else ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req); if (ret) { device_printf(dev, "Failed to set%s autonomous HWP for cpu%d after suspend\n", sc->hwp_pkg_ctrl_en ? " PKG" : "", pc->pc_cpuid); goto out; } if (sc->hwp_pkg_ctrl_en) { ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req); if (ret) { device_printf(dev, "Failed to set autonomous HWP for package after " "suspend\n"); goto out; } } if (!sc->hwp_pref_ctrl && sc->hwp_perf_bias_cached) { ret = wrmsr_safe(MSR_IA32_ENERGY_PERF_BIAS, sc->hwp_energy_perf_bias); if (ret) { device_printf(dev, "Failed to set energy perf bias for cpu%d after " "suspend\n", pc->pc_cpuid); } } out: thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); return (ret); } diff --git a/sys/x86/cpufreq/p4tcc.c b/sys/x86/cpufreq/p4tcc.c index 22e9cad25d4a..c3c35dee832b 100644 --- a/sys/x86/cpufreq/p4tcc.c +++ b/sys/x86/cpufreq/p4tcc.c @@ -1,345 +1,345 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005 Nate Lawson * 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, 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. */ /* * Throttle clock frequency by using the thermal control circuit. This * operates independently of SpeedStep and ACPI throttling and is supported * on Pentium 4 and later models (feature TM). * * Reference: Intel Developer's manual v.3 #245472-012 * * The original version of this driver was written by Ted Unangst for * OpenBSD and imported by Maxim Sobolev. It was rewritten by Nate Lawson * for use with the cpufreq framework. */ #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include #include #include "acpi_if.h" struct p4tcc_softc { device_t dev; int set_count; int lowest_val; int auto_mode; }; #define TCC_NUM_SETTINGS 8 #define TCC_ENABLE_ONDEMAND (1<<4) #define TCC_REG_OFFSET 1 #define TCC_SPEED_PERCENT(x) ((10000 * (x)) / TCC_NUM_SETTINGS) static int p4tcc_features(driver_t *driver, u_int *features); static void p4tcc_identify(driver_t *driver, device_t parent); static int p4tcc_probe(device_t dev); static int p4tcc_attach(device_t dev); static int p4tcc_detach(device_t dev); static int p4tcc_settings(device_t dev, struct cf_setting *sets, int *count); static int p4tcc_set(device_t dev, const struct cf_setting *set); static int p4tcc_get(device_t dev, struct cf_setting *set); static int p4tcc_type(device_t dev, int *type); static device_method_t p4tcc_methods[] = { /* Device interface */ DEVMETHOD(device_identify, p4tcc_identify), DEVMETHOD(device_probe, p4tcc_probe), DEVMETHOD(device_attach, p4tcc_attach), DEVMETHOD(device_detach, p4tcc_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, p4tcc_set), DEVMETHOD(cpufreq_drv_get, p4tcc_get), DEVMETHOD(cpufreq_drv_type, p4tcc_type), DEVMETHOD(cpufreq_drv_settings, p4tcc_settings), /* ACPI interface */ DEVMETHOD(acpi_get_features, p4tcc_features), {0, 0} }; static driver_t p4tcc_driver = { "p4tcc", p4tcc_methods, sizeof(struct p4tcc_softc), }; DRIVER_MODULE(p4tcc, cpu, p4tcc_driver, 0, 0); static int p4tcc_features(driver_t *driver, u_int *features) { /* Notify the ACPI CPU that we support direct access to MSRs */ *features = ACPI_CAP_THR_MSRS; return (0); } static void p4tcc_identify(driver_t *driver, device_t parent) { if ((cpu_feature & (CPUID_ACPI | CPUID_TM)) != (CPUID_ACPI | CPUID_TM)) return; /* Make sure we're not being doubly invoked. */ - if (device_find_child(parent, "p4tcc", -1) != NULL) + if (device_find_child(parent, "p4tcc", DEVICE_UNIT_ANY) != NULL) return; /* * We attach a p4tcc child for every CPU since settings need to * be performed on every CPU in the SMP case. See section 13.15.3 * of the IA32 Intel Architecture Software Developer's Manual, * Volume 3, for more info. */ if (BUS_ADD_CHILD(parent, 10, "p4tcc", device_get_unit(parent)) == NULL) device_printf(parent, "add p4tcc child failed\n"); } static int p4tcc_probe(device_t dev) { if (resource_disabled("p4tcc", 0)) return (ENXIO); device_set_desc(dev, "CPU Frequency Thermal Control"); return (0); } static int p4tcc_attach(device_t dev) { struct p4tcc_softc *sc; struct cf_setting set; sc = device_get_softc(dev); sc->dev = dev; sc->set_count = TCC_NUM_SETTINGS; /* * On boot, the TCC is usually in Automatic mode where reading the * current performance level is likely to produce bogus results. * We record that state here and don't trust the contents of the * status MSR until we've set it ourselves. */ sc->auto_mode = TRUE; /* * XXX: After a cursory glance at various Intel specification * XXX: updates it seems like these tests for errata is bogus. * XXX: As far as I can tell, the failure mode is benign, in * XXX: that cpus with no errata will have their bottom two * XXX: STPCLK# rates disabled, so rather than waste more time * XXX: hunting down intel docs, just document it and punt. /phk */ switch (cpu_id & 0xff) { case 0x22: case 0x24: case 0x25: case 0x27: case 0x29: /* * These CPU models hang when set to 12.5%. * See Errata O50, P44, and Z21. */ sc->set_count -= 1; break; case 0x07: /* errata N44 and P18 */ case 0x0a: case 0x12: case 0x13: case 0x62: /* Pentium D B1: errata AA21 */ case 0x64: /* Pentium D C1: errata AA21 */ case 0x65: /* Pentium D D0: errata AA21 */ /* * These CPU models hang when set to 12.5% or 25%. * See Errata N44, P18l and AA21. */ sc->set_count -= 2; break; } sc->lowest_val = TCC_NUM_SETTINGS - sc->set_count + 1; /* * Before we finish attach, switch to 100%. It's possible the BIOS * set us to a lower rate. The user can override this after boot. */ set.freq = 10000; p4tcc_set(dev, &set); cpufreq_register(dev); return (0); } static int p4tcc_detach(device_t dev) { struct cf_setting set; int error; error = cpufreq_unregister(dev); if (error) return (error); /* * Before we finish detach, switch to Automatic mode. */ set.freq = 10000; p4tcc_set(dev, &set); return(0); } static int p4tcc_settings(device_t dev, struct cf_setting *sets, int *count) { struct p4tcc_softc *sc; int i, val; sc = device_get_softc(dev); if (sets == NULL || count == NULL) return (EINVAL); if (*count < sc->set_count) return (E2BIG); /* Return a list of valid settings for this driver. */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * sc->set_count); val = TCC_NUM_SETTINGS; for (i = 0; i < sc->set_count; i++, val--) { sets[i].freq = TCC_SPEED_PERCENT(val); sets[i].dev = dev; } *count = sc->set_count; return (0); } static int p4tcc_set(device_t dev, const struct cf_setting *set) { struct p4tcc_softc *sc; uint64_t mask, msr; int val; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); /* * Validate requested state converts to a setting that is an integer * from [sc->lowest_val .. TCC_NUM_SETTINGS]. */ val = set->freq * TCC_NUM_SETTINGS / 10000; if (val * 10000 != set->freq * TCC_NUM_SETTINGS || val < sc->lowest_val || val > TCC_NUM_SETTINGS) return (EINVAL); /* * Read the current register and mask off the old setting and * On-Demand bit. If the new val is < 100%, set it and the On-Demand * bit, otherwise just return to Automatic mode. */ msr = rdmsr(MSR_THERM_CONTROL); mask = (TCC_NUM_SETTINGS - 1) << TCC_REG_OFFSET; msr &= ~(mask | TCC_ENABLE_ONDEMAND); if (val < TCC_NUM_SETTINGS) msr |= (val << TCC_REG_OFFSET) | TCC_ENABLE_ONDEMAND; wrmsr(MSR_THERM_CONTROL, msr); /* * Record whether we're now in Automatic or On-Demand mode. We have * to cache this since there is no reliable way to check if TCC is in * Automatic mode (i.e., at 100% or possibly 50%). Reading bit 4 of * the ACPI Thermal Monitor Control Register produces 0 no matter * what the current mode. */ if (msr & TCC_ENABLE_ONDEMAND) sc->auto_mode = FALSE; else sc->auto_mode = TRUE; return (0); } static int p4tcc_get(device_t dev, struct cf_setting *set) { struct p4tcc_softc *sc; uint64_t msr; int val; if (set == NULL) return (EINVAL); sc = device_get_softc(dev); /* * Read the current register and extract the current setting. If * in automatic mode, assume we're at TCC_NUM_SETTINGS (100%). * * XXX This is not completely reliable since at high temperatures * the CPU may be automatically throttling to 50% but it's the best * we can do. */ if (!sc->auto_mode) { msr = rdmsr(MSR_THERM_CONTROL); val = (msr >> TCC_REG_OFFSET) & (TCC_NUM_SETTINGS - 1); } else val = TCC_NUM_SETTINGS; memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set)); set->freq = TCC_SPEED_PERCENT(val); set->dev = dev; return (0); } static int p4tcc_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_RELATIVE; return (0); } diff --git a/sys/x86/cpufreq/powernow.c b/sys/x86/cpufreq/powernow.c index 93b1386c754d..2f3044d6077a 100644 --- a/sys/x86/cpufreq/powernow.c +++ b/sys/x86/cpufreq/powernow.c @@ -1,966 +1,967 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004-2005 Bruno Ducrot * Copyright (c) 2004 FUKUDA Nobuhiko * * 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, 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. */ /* * Many thanks to Nate Lawson for his helpful comments on this driver and * to Jung-uk Kim for testing. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #define PN7_TYPE 0 #define PN8_TYPE 1 /* Flags for some hardware bugs. */ #define A0_ERRATA 0x1 /* Bugs for the rev. A0 of Athlon (K7): * Interrupts must be disabled and no half * multipliers are allowed */ #define PENDING_STUCK 0x2 /* With some buggy chipset and some newer AMD64 * processor (Rev. G?): * the pending bit from the msr FIDVID_STATUS * is set forever. No workaround :( */ /* Legacy configuration via BIOS table PSB. */ #define PSB_START 0 #define PSB_STEP 0x10 #define PSB_SIG "AMDK7PNOW!" #define PSB_LEN 10 #define PSB_OFF 0 struct psb_header { char signature[10]; uint8_t version; uint8_t flags; uint16_t settlingtime; uint8_t res1; uint8_t numpst; } __packed; struct pst_header { uint32_t cpuid; uint8_t fsb; uint8_t maxfid; uint8_t startvid; uint8_t numpstates; } __packed; /* * MSRs and bits used by Powernow technology */ #define MSR_AMDK7_FIDVID_CTL 0xc0010041 #define MSR_AMDK7_FIDVID_STATUS 0xc0010042 /* Bitfields used by K7 */ #define PN7_CTR_FID(x) ((x) & 0x1f) #define PN7_CTR_VID(x) (((x) & 0x1f) << 8) #define PN7_CTR_FIDC 0x00010000 #define PN7_CTR_VIDC 0x00020000 #define PN7_CTR_FIDCHRATIO 0x00100000 #define PN7_CTR_SGTC(x) (((uint64_t)(x) & 0x000fffff) << 32) #define PN7_STA_CFID(x) ((x) & 0x1f) #define PN7_STA_SFID(x) (((x) >> 8) & 0x1f) #define PN7_STA_MFID(x) (((x) >> 16) & 0x1f) #define PN7_STA_CVID(x) (((x) >> 32) & 0x1f) #define PN7_STA_SVID(x) (((x) >> 40) & 0x1f) #define PN7_STA_MVID(x) (((x) >> 48) & 0x1f) /* ACPI ctr_val status register to powernow k7 configuration */ #define ACPI_PN7_CTRL_TO_FID(x) ((x) & 0x1f) #define ACPI_PN7_CTRL_TO_VID(x) (((x) >> 5) & 0x1f) #define ACPI_PN7_CTRL_TO_SGTC(x) (((x) >> 10) & 0xffff) /* Bitfields used by K8 */ #define PN8_CTR_FID(x) ((x) & 0x3f) #define PN8_CTR_VID(x) (((x) & 0x1f) << 8) #define PN8_CTR_PENDING(x) (((x) & 1) << 32) #define PN8_STA_CFID(x) ((x) & 0x3f) #define PN8_STA_SFID(x) (((x) >> 8) & 0x3f) #define PN8_STA_MFID(x) (((x) >> 16) & 0x3f) #define PN8_STA_PENDING(x) (((x) >> 31) & 0x01) #define PN8_STA_CVID(x) (((x) >> 32) & 0x1f) #define PN8_STA_SVID(x) (((x) >> 40) & 0x1f) #define PN8_STA_MVID(x) (((x) >> 48) & 0x1f) /* Reserved1 to powernow k8 configuration */ #define PN8_PSB_TO_RVO(x) ((x) & 0x03) #define PN8_PSB_TO_IRT(x) (((x) >> 2) & 0x03) #define PN8_PSB_TO_MVS(x) (((x) >> 4) & 0x03) #define PN8_PSB_TO_BATT(x) (((x) >> 6) & 0x03) /* ACPI ctr_val status register to powernow k8 configuration */ #define ACPI_PN8_CTRL_TO_FID(x) ((x) & 0x3f) #define ACPI_PN8_CTRL_TO_VID(x) (((x) >> 6) & 0x1f) #define ACPI_PN8_CTRL_TO_VST(x) (((x) >> 11) & 0x1f) #define ACPI_PN8_CTRL_TO_MVS(x) (((x) >> 18) & 0x03) #define ACPI_PN8_CTRL_TO_PLL(x) (((x) >> 20) & 0x7f) #define ACPI_PN8_CTRL_TO_RVO(x) (((x) >> 28) & 0x03) #define ACPI_PN8_CTRL_TO_IRT(x) (((x) >> 30) & 0x03) #define WRITE_FIDVID(fid, vid, ctrl) \ wrmsr(MSR_AMDK7_FIDVID_CTL, \ (((ctrl) << 32) | (1ULL << 16) | ((vid) << 8) | (fid))) #define COUNT_OFF_IRT(irt) DELAY(10 * (1 << (irt))) #define COUNT_OFF_VST(vst) DELAY(20 * (vst)) #define FID_TO_VCO_FID(fid) \ (((fid) < 8) ? (8 + ((fid) << 1)) : (fid)) /* * Divide each value by 10 to get the processor multiplier. * Some of those tables are the same as the Linux powernow-k7 * implementation by Dave Jones. */ static int pn7_fid_to_mult[32] = { 110, 115, 120, 125, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 30, 190, 40, 200, 130, 135, 140, 210, 150, 225, 160, 165, 170, 180, 0, 0, }; static int pn8_fid_to_mult[64] = { 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 190, 195, 200, 205, 210, 215, 220, 225, 230, 235, 240, 245, 250, 255, 260, 265, 270, 275, 280, 285, 290, 295, 300, 305, 310, 315, 320, 325, 330, 335, 340, 345, 350, 355, }; /* * Units are in mV. */ /* Mobile VRM (K7) */ static int pn7_mobile_vid_to_volts[] = { 2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650, 1600, 1550, 1500, 1450, 1400, 1350, 1300, 0, 1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100, 1075, 1050, 1025, 1000, 975, 950, 925, 0, }; /* Desktop VRM (K7) */ static int pn7_desktop_vid_to_volts[] = { 2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650, 1600, 1550, 1500, 1450, 1400, 1350, 1300, 0, 1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100, 1075, 1050, 1025, 1000, 975, 950, 925, 0, }; /* Desktop and Mobile VRM (K8) */ static int pn8_vid_to_volts[] = { 1550, 1525, 1500, 1475, 1450, 1425, 1400, 1375, 1350, 1325, 1300, 1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100, 1075, 1050, 1025, 1000, 975, 950, 925, 900, 875, 850, 825, 800, 0, }; #define POWERNOW_MAX_STATES 16 struct powernow_state { int freq; int power; int fid; int vid; }; struct pn_softc { device_t dev; int pn_type; struct powernow_state powernow_states[POWERNOW_MAX_STATES]; u_int fsb; u_int sgtc; u_int vst; u_int mvs; u_int pll; u_int rvo; u_int irt; int low; int powernow_max_states; u_int powernow_state; u_int errata; int *vid_to_volts; }; /* * Offsets in struct cf_setting array for private values given by * acpi_perf driver. */ #define PX_SPEC_CONTROL 0 #define PX_SPEC_STATUS 1 static void pn_identify(driver_t *driver, device_t parent); static int pn_probe(device_t dev); static int pn_attach(device_t dev); static int pn_detach(device_t dev); static int pn_set(device_t dev, const struct cf_setting *cf); static int pn_get(device_t dev, struct cf_setting *cf); static int pn_settings(device_t dev, struct cf_setting *sets, int *count); static int pn_type(device_t dev, int *type); static device_method_t pn_methods[] = { /* Device interface */ DEVMETHOD(device_identify, pn_identify), DEVMETHOD(device_probe, pn_probe), DEVMETHOD(device_attach, pn_attach), DEVMETHOD(device_detach, pn_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, pn_set), DEVMETHOD(cpufreq_drv_get, pn_get), DEVMETHOD(cpufreq_drv_settings, pn_settings), DEVMETHOD(cpufreq_drv_type, pn_type), {0, 0} }; static driver_t pn_driver = { "powernow", pn_methods, sizeof(struct pn_softc), }; DRIVER_MODULE(powernow, cpu, pn_driver, 0, 0); static int pn7_setfidvid(struct pn_softc *sc, int fid, int vid) { int cfid, cvid; uint64_t status, ctl; status = rdmsr(MSR_AMDK7_FIDVID_STATUS); cfid = PN7_STA_CFID(status); cvid = PN7_STA_CVID(status); /* We're already at the requested level. */ if (fid == cfid && vid == cvid) return (0); ctl = rdmsr(MSR_AMDK7_FIDVID_CTL) & PN7_CTR_FIDCHRATIO; ctl |= PN7_CTR_FID(fid); ctl |= PN7_CTR_VID(vid); ctl |= PN7_CTR_SGTC(sc->sgtc); if (sc->errata & A0_ERRATA) disable_intr(); if (pn7_fid_to_mult[fid] < pn7_fid_to_mult[cfid]) { wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_FIDC); if (vid != cvid) wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_VIDC); } else { wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_VIDC); if (fid != cfid) wrmsr(MSR_AMDK7_FIDVID_CTL, ctl | PN7_CTR_FIDC); } if (sc->errata & A0_ERRATA) enable_intr(); return (0); } static int pn8_read_pending_wait(uint64_t *status) { int i = 10000; do *status = rdmsr(MSR_AMDK7_FIDVID_STATUS); while (PN8_STA_PENDING(*status) && --i); return (i == 0 ? ENXIO : 0); } static int pn8_write_fidvid(u_int fid, u_int vid, uint64_t ctrl, uint64_t *status) { int i = 100; do WRITE_FIDVID(fid, vid, ctrl); while (pn8_read_pending_wait(status) && --i); return (i == 0 ? ENXIO : 0); } static int pn8_setfidvid(struct pn_softc *sc, int fid, int vid) { uint64_t status; int cfid, cvid; int rvo; int rv; u_int val; rv = pn8_read_pending_wait(&status); if (rv) return (rv); cfid = PN8_STA_CFID(status); cvid = PN8_STA_CVID(status); if (fid == cfid && vid == cvid) return (0); /* * Phase 1: Raise core voltage to requested VID if frequency is * going up. */ while (cvid > vid) { val = cvid - (1 << sc->mvs); rv = pn8_write_fidvid(cfid, (val > 0) ? val : 0, 1ULL, &status); if (rv) { sc->errata |= PENDING_STUCK; return (rv); } cvid = PN8_STA_CVID(status); COUNT_OFF_VST(sc->vst); } /* ... then raise to voltage + RVO (if required) */ for (rvo = sc->rvo; rvo > 0 && cvid > 0; --rvo) { /* XXX It's not clear from spec if we have to do that * in 0.25 step or in MVS. Therefore do it as it's done * under Linux */ rv = pn8_write_fidvid(cfid, cvid - 1, 1ULL, &status); if (rv) { sc->errata |= PENDING_STUCK; return (rv); } cvid = PN8_STA_CVID(status); COUNT_OFF_VST(sc->vst); } /* Phase 2: change to requested core frequency */ if (cfid != fid) { u_int vco_fid, vco_cfid, fid_delta; vco_fid = FID_TO_VCO_FID(fid); vco_cfid = FID_TO_VCO_FID(cfid); while (abs(vco_fid - vco_cfid) > 2) { fid_delta = (vco_cfid & 1) ? 1 : 2; if (fid > cfid) { if (cfid > 7) val = cfid + fid_delta; else val = FID_TO_VCO_FID(cfid) + fid_delta; } else val = cfid - fid_delta; rv = pn8_write_fidvid(val, cvid, sc->pll * (uint64_t) sc->fsb, &status); if (rv) { sc->errata |= PENDING_STUCK; return (rv); } cfid = PN8_STA_CFID(status); COUNT_OFF_IRT(sc->irt); vco_cfid = FID_TO_VCO_FID(cfid); } rv = pn8_write_fidvid(fid, cvid, sc->pll * (uint64_t) sc->fsb, &status); if (rv) { sc->errata |= PENDING_STUCK; return (rv); } cfid = PN8_STA_CFID(status); COUNT_OFF_IRT(sc->irt); } /* Phase 3: change to requested voltage */ if (cvid != vid) { rv = pn8_write_fidvid(cfid, vid, 1ULL, &status); cvid = PN8_STA_CVID(status); COUNT_OFF_VST(sc->vst); } /* Check if transition failed. */ if (cfid != fid || cvid != vid) rv = ENXIO; return (rv); } static int pn_set(device_t dev, const struct cf_setting *cf) { struct pn_softc *sc; int fid, vid; int i; int rv; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); if (sc->errata & PENDING_STUCK) return (ENXIO); for (i = 0; i < sc->powernow_max_states; ++i) if (CPUFREQ_CMP(sc->powernow_states[i].freq / 1000, cf->freq)) break; fid = sc->powernow_states[i].fid; vid = sc->powernow_states[i].vid; rv = ENODEV; switch (sc->pn_type) { case PN7_TYPE: rv = pn7_setfidvid(sc, fid, vid); break; case PN8_TYPE: rv = pn8_setfidvid(sc, fid, vid); break; } return (rv); } static int pn_get(device_t dev, struct cf_setting *cf) { struct pn_softc *sc; u_int cfid = 0, cvid = 0; int i; uint64_t status; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); if (sc->errata & PENDING_STUCK) return (ENXIO); status = rdmsr(MSR_AMDK7_FIDVID_STATUS); switch (sc->pn_type) { case PN7_TYPE: cfid = PN7_STA_CFID(status); cvid = PN7_STA_CVID(status); break; case PN8_TYPE: cfid = PN8_STA_CFID(status); cvid = PN8_STA_CVID(status); break; } for (i = 0; i < sc->powernow_max_states; ++i) if (cfid == sc->powernow_states[i].fid && cvid == sc->powernow_states[i].vid) break; if (i < sc->powernow_max_states) { cf->freq = sc->powernow_states[i].freq / 1000; cf->power = sc->powernow_states[i].power; cf->lat = 200; cf->volts = sc->vid_to_volts[cvid]; cf->dev = dev; } else { memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); cf->dev = NULL; } return (0); } static int pn_settings(device_t dev, struct cf_setting *sets, int *count) { struct pn_softc *sc; int i; if (sets == NULL|| count == NULL) return (EINVAL); sc = device_get_softc(dev); if (*count < sc->powernow_max_states) return (E2BIG); for (i = 0; i < sc->powernow_max_states; ++i) { sets[i].freq = sc->powernow_states[i].freq / 1000; sets[i].power = sc->powernow_states[i].power; sets[i].lat = 200; sets[i].volts = sc->vid_to_volts[sc->powernow_states[i].vid]; sets[i].dev = dev; } *count = sc->powernow_max_states; return (0); } static int pn_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } /* * Given a set of pair of fid/vid, and number of performance states, * compute powernow_states via an insertion sort. */ static int decode_pst(struct pn_softc *sc, uint8_t *p, int npstates) { int i, j, n; struct powernow_state state; for (i = 0; i < POWERNOW_MAX_STATES; ++i) sc->powernow_states[i].freq = CPUFREQ_VAL_UNKNOWN; for (n = 0, i = 0; i < npstates; ++i) { state.fid = *p++; state.vid = *p++; state.power = CPUFREQ_VAL_UNKNOWN; switch (sc->pn_type) { case PN7_TYPE: state.freq = 100 * pn7_fid_to_mult[state.fid] * sc->fsb; if ((sc->errata & A0_ERRATA) && (pn7_fid_to_mult[state.fid] % 10) == 5) continue; break; case PN8_TYPE: state.freq = 100 * pn8_fid_to_mult[state.fid] * sc->fsb; break; } j = n; while (j > 0 && sc->powernow_states[j - 1].freq < state.freq) { memcpy(&sc->powernow_states[j], &sc->powernow_states[j - 1], sizeof(struct powernow_state)); --j; } memcpy(&sc->powernow_states[j], &state, sizeof(struct powernow_state)); ++n; } /* * Fix powernow_max_states, if errata a0 give us less states * than expected. */ sc->powernow_max_states = n; if (bootverbose) for (i = 0; i < sc->powernow_max_states; ++i) { int fid = sc->powernow_states[i].fid; int vid = sc->powernow_states[i].vid; printf("powernow: %2i %8dkHz FID %02x VID %02x\n", i, sc->powernow_states[i].freq, fid, vid); } return (0); } static int cpuid_is_k7(u_int cpuid) { switch (cpuid) { case 0x760: case 0x761: case 0x762: case 0x770: case 0x771: case 0x780: case 0x781: case 0x7a0: return (TRUE); } return (FALSE); } static int pn_decode_pst(device_t dev) { int maxpst; struct pn_softc *sc; u_int cpuid, maxfid, startvid; u_long sig; struct psb_header *psb; uint8_t *p; u_int regs[4]; uint64_t status; sc = device_get_softc(dev); do_cpuid(0x80000001, regs); cpuid = regs[0]; if ((cpuid & 0xfff) == 0x760) sc->errata |= A0_ERRATA; status = rdmsr(MSR_AMDK7_FIDVID_STATUS); switch (sc->pn_type) { case PN7_TYPE: maxfid = PN7_STA_MFID(status); startvid = PN7_STA_SVID(status); break; case PN8_TYPE: maxfid = PN8_STA_MFID(status); /* * we should actually use a variable named 'maxvid' if K8, * but why introducing a new variable for that? */ startvid = PN8_STA_MVID(status); break; default: return (ENODEV); } if (bootverbose) { device_printf(dev, "STATUS: 0x%jx\n", status); device_printf(dev, "STATUS: maxfid: 0x%02x\n", maxfid); device_printf(dev, "STATUS: %s: 0x%02x\n", sc->pn_type == PN7_TYPE ? "startvid" : "maxvid", startvid); } sig = bios_sigsearch(PSB_START, PSB_SIG, PSB_LEN, PSB_STEP, PSB_OFF); if (sig) { struct pst_header *pst; psb = (struct psb_header*)(uintptr_t)BIOS_PADDRTOVADDR(sig); switch (psb->version) { default: return (ENODEV); case 0x14: /* * We can't be picky about numpst since at least * some systems have a value of 1 and some have 2. * We trust that cpuid_is_k7() will be better at * catching that we're on a K8 anyway. */ if (sc->pn_type != PN8_TYPE) return (EINVAL); sc->vst = psb->settlingtime; sc->rvo = PN8_PSB_TO_RVO(psb->res1); sc->irt = PN8_PSB_TO_IRT(psb->res1); sc->mvs = PN8_PSB_TO_MVS(psb->res1); sc->low = PN8_PSB_TO_BATT(psb->res1); if (bootverbose) { device_printf(dev, "PSB: VST: %d\n", psb->settlingtime); device_printf(dev, "PSB: RVO %x IRT %d " "MVS %d BATT %d\n", sc->rvo, sc->irt, sc->mvs, sc->low); } break; case 0x12: if (sc->pn_type != PN7_TYPE) return (EINVAL); sc->sgtc = psb->settlingtime * sc->fsb; if (sc->sgtc < 100 * sc->fsb) sc->sgtc = 100 * sc->fsb; break; } p = ((uint8_t *) psb) + sizeof(struct psb_header); pst = (struct pst_header*) p; maxpst = 200; do { struct pst_header *pst = (struct pst_header*) p; if (cpuid == pst->cpuid && maxfid == pst->maxfid && startvid == pst->startvid) { sc->powernow_max_states = pst->numpstates; switch (sc->pn_type) { case PN7_TYPE: if (abs(sc->fsb - pst->fsb) > 5) continue; break; case PN8_TYPE: break; } return (decode_pst(sc, p + sizeof(struct pst_header), sc->powernow_max_states)); } p += sizeof(struct pst_header) + (2 * pst->numpstates); } while (cpuid_is_k7(pst->cpuid) && maxpst--); device_printf(dev, "no match for extended cpuid %.3x\n", cpuid); } return (ENODEV); } static int pn_decode_acpi(device_t dev, device_t perf_dev) { int i, j, n; uint64_t status; uint32_t ctrl; u_int cpuid; u_int regs[4]; struct pn_softc *sc; struct powernow_state state; struct cf_setting sets[POWERNOW_MAX_STATES]; int count = POWERNOW_MAX_STATES; int type; int rv; if (perf_dev == NULL) return (ENXIO); rv = CPUFREQ_DRV_SETTINGS(perf_dev, sets, &count); if (rv) return (ENXIO); rv = CPUFREQ_DRV_TYPE(perf_dev, &type); if (rv || (type & CPUFREQ_FLAG_INFO_ONLY) == 0) return (ENXIO); sc = device_get_softc(dev); do_cpuid(0x80000001, regs); cpuid = regs[0]; if ((cpuid & 0xfff) == 0x760) sc->errata |= A0_ERRATA; ctrl = 0; sc->sgtc = 0; for (n = 0, i = 0; i < count; ++i) { ctrl = sets[i].spec[PX_SPEC_CONTROL]; switch (sc->pn_type) { case PN7_TYPE: state.fid = ACPI_PN7_CTRL_TO_FID(ctrl); state.vid = ACPI_PN7_CTRL_TO_VID(ctrl); if ((sc->errata & A0_ERRATA) && (pn7_fid_to_mult[state.fid] % 10) == 5) continue; break; case PN8_TYPE: state.fid = ACPI_PN8_CTRL_TO_FID(ctrl); state.vid = ACPI_PN8_CTRL_TO_VID(ctrl); break; } state.freq = sets[i].freq * 1000; state.power = sets[i].power; j = n; while (j > 0 && sc->powernow_states[j - 1].freq < state.freq) { memcpy(&sc->powernow_states[j], &sc->powernow_states[j - 1], sizeof(struct powernow_state)); --j; } memcpy(&sc->powernow_states[j], &state, sizeof(struct powernow_state)); ++n; } sc->powernow_max_states = n; state = sc->powernow_states[0]; status = rdmsr(MSR_AMDK7_FIDVID_STATUS); switch (sc->pn_type) { case PN7_TYPE: sc->sgtc = ACPI_PN7_CTRL_TO_SGTC(ctrl); /* * XXX Some bios forget the max frequency! * This maybe indicates we have the wrong tables. Therefore, * don't implement a quirk, but fallback to BIOS legacy * tables instead. */ if (PN7_STA_MFID(status) != state.fid) { device_printf(dev, "ACPI MAX frequency not found\n"); return (EINVAL); } sc->fsb = state.freq / 100 / pn7_fid_to_mult[state.fid]; break; case PN8_TYPE: sc->vst = ACPI_PN8_CTRL_TO_VST(ctrl), sc->mvs = ACPI_PN8_CTRL_TO_MVS(ctrl), sc->pll = ACPI_PN8_CTRL_TO_PLL(ctrl), sc->rvo = ACPI_PN8_CTRL_TO_RVO(ctrl), sc->irt = ACPI_PN8_CTRL_TO_IRT(ctrl); sc->low = 0; /* XXX */ /* * powernow k8 supports only one low frequency. */ if (sc->powernow_max_states >= 2 && (sc->powernow_states[sc->powernow_max_states - 2].fid < 8)) return (EINVAL); sc->fsb = state.freq / 100 / pn8_fid_to_mult[state.fid]; break; } return (0); } static void pn_identify(driver_t *driver, device_t parent) { if ((amd_pminfo & AMDPM_FID) == 0 || (amd_pminfo & AMDPM_VID) == 0) return; switch (cpu_id & 0xf00) { case 0x600: case 0xf00: break; default: return; } - if (device_find_child(parent, "powernow", -1) != NULL) + if (device_find_child(parent, "powernow", DEVICE_UNIT_ANY) != NULL) return; if (BUS_ADD_CHILD(parent, 10, "powernow", device_get_unit(parent)) == NULL) device_printf(parent, "powernow: add child failed\n"); } static int pn_probe(device_t dev) { struct pn_softc *sc; uint64_t status; uint64_t rate; struct pcpu *pc; u_int sfid, mfid, cfid; sc = device_get_softc(dev); sc->errata = 0; status = rdmsr(MSR_AMDK7_FIDVID_STATUS); pc = cpu_get_pcpu(dev); if (pc == NULL) return (ENODEV); cpu_est_clockrate(pc->pc_cpuid, &rate); switch (cpu_id & 0xf00) { case 0x600: sfid = PN7_STA_SFID(status); mfid = PN7_STA_MFID(status); cfid = PN7_STA_CFID(status); sc->pn_type = PN7_TYPE; sc->fsb = rate / 100000 / pn7_fid_to_mult[cfid]; /* * If start FID is different to max FID, then it is a * mobile processor. If not, it is a low powered desktop * processor. */ if (sfid != mfid) { sc->vid_to_volts = pn7_mobile_vid_to_volts; device_set_desc(dev, "PowerNow! K7"); } else { sc->vid_to_volts = pn7_desktop_vid_to_volts; device_set_desc(dev, "Cool`n'Quiet K7"); } break; case 0xf00: sfid = PN8_STA_SFID(status); mfid = PN8_STA_MFID(status); cfid = PN8_STA_CFID(status); sc->pn_type = PN8_TYPE; sc->vid_to_volts = pn8_vid_to_volts; sc->fsb = rate / 100000 / pn8_fid_to_mult[cfid]; if (sfid != mfid) device_set_desc(dev, "PowerNow! K8"); else device_set_desc(dev, "Cool`n'Quiet K8"); break; default: return (ENODEV); } return (0); } static int pn_attach(device_t dev) { int rv; device_t child; - child = device_find_child(device_get_parent(dev), "acpi_perf", -1); + child = device_find_child(device_get_parent(dev), "acpi_perf", + DEVICE_UNIT_ANY); if (child) { rv = pn_decode_acpi(dev, child); if (rv) rv = pn_decode_pst(dev); } else rv = pn_decode_pst(dev); if (rv != 0) return (ENXIO); cpufreq_register(dev); return (0); } static int pn_detach(device_t dev) { return (cpufreq_unregister(dev)); } diff --git a/sys/x86/cpufreq/smist.c b/sys/x86/cpufreq/smist.c index 291c3b9af605..b84a4ba72a56 100644 --- a/sys/x86/cpufreq/smist.c +++ b/sys/x86/cpufreq/smist.c @@ -1,513 +1,515 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005 Bruno Ducrot * * 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, 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. */ /* * This driver is based upon information found by examining speedstep-0.5 * from Marc Lehman, which includes all the reverse engineering effort of * Malik Martin (function 1 and 2 of the GSI). * * The correct way for the OS to take ownership from the BIOS was found by * Hiroshi Miura (function 0 of the GSI). * * Finally, the int 15h call interface was (partially) documented by Intel. * * Many thanks to Jon Noack for testing and debugging this driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #if 0 #define DPRINT(dev, x...) device_printf(dev, x) #else #define DPRINT(dev, x...) #endif struct smist_softc { device_t dev; int smi_cmd; int smi_data; int command; int flags; struct cf_setting sets[2]; /* Only two settings. */ }; static char smist_magic[] = "Copyright (c) 1999 Intel Corporation"; static void smist_identify(driver_t *driver, device_t parent); static int smist_probe(device_t dev); static int smist_attach(device_t dev); static int smist_detach(device_t dev); static int smist_settings(device_t dev, struct cf_setting *sets, int *count); static int smist_set(device_t dev, const struct cf_setting *set); static int smist_get(device_t dev, struct cf_setting *set); static int smist_type(device_t dev, int *type); static device_method_t smist_methods[] = { /* Device interface */ DEVMETHOD(device_identify, smist_identify), DEVMETHOD(device_probe, smist_probe), DEVMETHOD(device_attach, smist_attach), DEVMETHOD(device_detach, smist_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, smist_set), DEVMETHOD(cpufreq_drv_get, smist_get), DEVMETHOD(cpufreq_drv_type, smist_type), DEVMETHOD(cpufreq_drv_settings, smist_settings), {0, 0} }; static driver_t smist_driver = { "smist", smist_methods, sizeof(struct smist_softc) }; DRIVER_MODULE(smist, cpu, smist_driver, 0, 0); struct piix4_pci_device { uint16_t vendor; uint16_t device; char *desc; }; static struct piix4_pci_device piix4_pci_devices[] = { {0x8086, 0x7113, "Intel PIIX4 ISA bridge"}, {0x8086, 0x719b, "Intel PIIX4 ISA bridge (embedded in MX440 chipset)"}, {0, 0, NULL}, }; #define SET_OWNERSHIP 0 #define GET_STATE 1 #define SET_STATE 2 static int int15_gsic_call(int *sig, int *smi_cmd, int *command, int *smi_data, int *flags) { struct vm86frame vmf; bzero(&vmf, sizeof(vmf)); vmf.vmf_eax = 0x0000E980; /* IST support */ vmf.vmf_edx = 0x47534943; /* 'GSIC' in ASCII */ vm86_intcall(0x15, &vmf); if (vmf.vmf_eax == 0x47534943) { *sig = vmf.vmf_eax; *smi_cmd = vmf.vmf_ebx & 0xff; *command = (vmf.vmf_ebx >> 16) & 0xff; *smi_data = vmf.vmf_ecx; *flags = vmf.vmf_edx; } else { *sig = -1; *smi_cmd = -1; *command = -1; *smi_data = -1; *flags = -1; } return (0); } /* Temporary structure to hold mapped page and status. */ struct set_ownership_data { int smi_cmd; int command; int result; void *buf; }; /* Perform actual SMI call to enable SpeedStep. */ static void set_ownership_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct set_ownership_data *data; data = arg; if (error) { data->result = error; return; } /* Copy in the magic string and send it by writing to the SMI port. */ strlcpy(data->buf, smist_magic, PAGE_SIZE); __asm __volatile( "movl $-1, %%edi\n\t" "out %%al, (%%dx)\n" : "=D" (data->result) : "a" (data->command), "b" (0), "c" (0), "d" (data->smi_cmd), "S" ((uint32_t)segs[0].ds_addr) ); } static int set_ownership(device_t dev) { struct smist_softc *sc; struct set_ownership_data cb_data; bus_dma_tag_t tag; bus_dmamap_t map; /* * Specify the region to store the magic string. Since its address is * passed to the BIOS in a 32-bit register, we have to make sure it is * located in a physical page below 4 GB (i.e., for PAE.) */ sc = device_get_softc(dev); if (bus_dma_tag_create(/*parent*/ NULL, /*alignment*/ PAGE_SIZE, /*no boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR_32BIT, /*highaddr*/ BUS_SPACE_MAXADDR, NULL, NULL, /*maxsize*/ PAGE_SIZE, /*segments*/ 1, /*maxsegsize*/ PAGE_SIZE, 0, NULL, NULL, &tag) != 0) { device_printf(dev, "can't create mem tag\n"); return (ENXIO); } if (bus_dmamem_alloc(tag, &cb_data.buf, BUS_DMA_NOWAIT, &map) != 0) { bus_dma_tag_destroy(tag); device_printf(dev, "can't alloc mapped mem\n"); return (ENXIO); } /* Load the physical page map and take ownership in the callback. */ cb_data.smi_cmd = sc->smi_cmd; cb_data.command = sc->command; if (bus_dmamap_load(tag, map, cb_data.buf, PAGE_SIZE, set_ownership_cb, &cb_data, BUS_DMA_NOWAIT) != 0) { bus_dmamem_free(tag, cb_data.buf, map); bus_dma_tag_destroy(tag); device_printf(dev, "can't load mem\n"); return (ENXIO); } DPRINT(dev, "taking ownership over BIOS return %d\n", cb_data.result); bus_dmamap_unload(tag, map); bus_dmamem_free(tag, cb_data.buf, map); bus_dma_tag_destroy(tag); return (cb_data.result ? ENXIO : 0); } static int getset_state(struct smist_softc *sc, int *state, int function) { int new_state; int result; int eax; if (!sc) return (ENXIO); if (function != GET_STATE && function != SET_STATE) return (EINVAL); DPRINT(sc->dev, "calling GSI\n"); __asm __volatile( "movl $-1, %%edi\n\t" "out %%al, (%%dx)\n" : "=a" (eax), "=b" (new_state), "=D" (result) : "a" (sc->command), "b" (function), "c" (*state), "d" (sc->smi_cmd) ); DPRINT(sc->dev, "GSI returned: eax %.8x ebx %.8x edi %.8x\n", eax, new_state, result); *state = new_state & 1; switch (function) { case GET_STATE: if (eax) return (ENXIO); break; case SET_STATE: if (result) return (ENXIO); break; } return (0); } static void smist_identify(driver_t *driver, device_t parent) { struct piix4_pci_device *id; device_t piix4 = NULL; if (resource_disabled("ichst", 0)) return; /* Check for a supported processor */ if (cpu_vendor_id != CPU_VENDOR_INTEL) return; switch (cpu_id & 0xff0) { case 0x680: /* Pentium III [coppermine] */ case 0x6a0: /* Pentium III [Tualatin] */ break; default: return; } /* Check for a supported PCI-ISA bridge */ for (id = piix4_pci_devices; id->desc != NULL; ++id) { if ((piix4 = pci_find_device(id->vendor, id->device)) != NULL) break; } if (!piix4) return; if (bootverbose) printf("smist: found supported isa bridge %s\n", id->desc); - if (device_find_child(parent, "smist", -1) != NULL) + if (device_find_child(parent, "smist", DEVICE_UNIT_ANY) != NULL) return; if (BUS_ADD_CHILD(parent, 30, "smist", device_get_unit(parent)) == NULL) device_printf(parent, "smist: add child failed\n"); } static int smist_probe(device_t dev) { struct smist_softc *sc; device_t ichss_dev, perf_dev; int sig, smi_cmd, command, smi_data, flags; int type; int rv; if (resource_disabled("smist", 0)) return (ENXIO); sc = device_get_softc(dev); /* * If the ACPI perf or ICH SpeedStep drivers have attached and not * just offering info, let them manage things. */ - perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1); + perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", + DEVICE_UNIT_ANY); if (perf_dev && device_is_attached(perf_dev)) { rv = CPUFREQ_DRV_TYPE(perf_dev, &type); if (rv == 0 && (type & CPUFREQ_FLAG_INFO_ONLY) == 0) return (ENXIO); } - ichss_dev = device_find_child(device_get_parent(dev), "ichss", -1); + ichss_dev = device_find_child(device_get_parent(dev), "ichss", + DEVICE_UNIT_ANY); if (ichss_dev && device_is_attached(ichss_dev)) return (ENXIO); int15_gsic_call(&sig, &smi_cmd, &command, &smi_data, &flags); if (bootverbose) device_printf(dev, "sig %.8x smi_cmd %.4x command %.2x " "smi_data %.4x flags %.8x\n", sig, smi_cmd, command, smi_data, flags); if (sig != -1) { sc->smi_cmd = smi_cmd; sc->smi_data = smi_data; /* * Sometimes int 15h 'GSIC' returns 0x80 for command, when * it is actually 0x82. The Windows driver will overwrite * this value given by the registry. */ if (command == 0x80) { device_printf(dev, "GSIC returned cmd 0x80, should be 0x82\n"); command = 0x82; } sc->command = (sig & 0xffffff00) | (command & 0xff); sc->flags = flags; } else { /* Give some default values */ sc->smi_cmd = 0xb2; sc->smi_data = 0xb3; sc->command = 0x47534982; sc->flags = 0; } device_set_desc(dev, "SpeedStep SMI"); return (-1500); } static int smist_attach(device_t dev) { struct smist_softc *sc; sc = device_get_softc(dev); sc->dev = dev; /* If we can't take ownership over BIOS, then bail out */ if (set_ownership(dev) != 0) return (ENXIO); /* Setup some defaults for our exported settings. */ sc->sets[0].freq = CPUFREQ_VAL_UNKNOWN; sc->sets[0].volts = CPUFREQ_VAL_UNKNOWN; sc->sets[0].power = CPUFREQ_VAL_UNKNOWN; sc->sets[0].lat = 1000; sc->sets[0].dev = dev; sc->sets[1] = sc->sets[0]; cpufreq_register(dev); return (0); } static int smist_detach(device_t dev) { return (cpufreq_unregister(dev)); } static int smist_settings(device_t dev, struct cf_setting *sets, int *count) { struct smist_softc *sc; struct cf_setting set; int first, i; if (sets == NULL || count == NULL) return (EINVAL); if (*count < 2) { *count = 2; return (E2BIG); } sc = device_get_softc(dev); /* * Estimate frequencies for both levels, temporarily switching to * the other one if we haven't calibrated it yet. */ for (i = 0; i < 2; i++) { if (sc->sets[i].freq == CPUFREQ_VAL_UNKNOWN) { first = (i == 0) ? 1 : 0; smist_set(dev, &sc->sets[i]); smist_get(dev, &set); smist_set(dev, &sc->sets[first]); } } bcopy(sc->sets, sets, sizeof(sc->sets)); *count = 2; return (0); } static int smist_set(device_t dev, const struct cf_setting *set) { struct smist_softc *sc; int rv, state, req_state, try; /* Look up appropriate bit value based on frequency. */ sc = device_get_softc(dev); if (CPUFREQ_CMP(set->freq, sc->sets[0].freq)) req_state = 0; else if (CPUFREQ_CMP(set->freq, sc->sets[1].freq)) req_state = 1; else return (EINVAL); DPRINT(dev, "requested setting %d\n", req_state); rv = getset_state(sc, &state, GET_STATE); if (state == req_state) return (0); try = 3; do { rv = getset_state(sc, &req_state, SET_STATE); /* Sleep for 200 microseconds. This value is just a guess. */ if (rv) DELAY(200); } while (rv && --try); DPRINT(dev, "set_state return %d, tried %d times\n", rv, 4 - try); return (rv); } static int smist_get(device_t dev, struct cf_setting *set) { struct smist_softc *sc; uint64_t rate; int state; int rv; sc = device_get_softc(dev); rv = getset_state(sc, &state, GET_STATE); if (rv != 0) return (rv); /* If we haven't changed settings yet, estimate the current value. */ if (sc->sets[state].freq == CPUFREQ_VAL_UNKNOWN) { cpu_est_clockrate(0, &rate); sc->sets[state].freq = rate / 1000000; DPRINT(dev, "get calibrated new rate of %d\n", sc->sets[state].freq); } *set = sc->sets[state]; return (0); } static int smist_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } diff --git a/sys/x86/pci/qpi.c b/sys/x86/pci/qpi.c index 94dd46d26a92..21bf1b6738bc 100644 --- a/sys/x86/pci/qpi.c +++ b/sys/x86/pci/qpi.c @@ -1,305 +1,305 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010 Hudson River Trading LLC * Written by: John H. Baldwin * 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, 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ /* * This driver provides a pseudo-bus to enumerate the PCI buses * present on a system using a QPI chipset. It creates a qpi0 bus that * is a child of nexus0 and then creates Host-PCI bridges as a * child of that. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" struct qpi_device { int qd_pcibus; }; static MALLOC_DEFINE(M_QPI, "qpidrv", "qpi system device"); static void qpi_identify(driver_t *driver, device_t parent) { int do_qpi; /* Check CPUID to ensure this is an i7 CPU of some sort. */ if (cpu_vendor_id != CPU_VENDOR_INTEL || CPUID_TO_FAMILY(cpu_id) != 0x6) return; /* Only discover buses with configuration devices if allowed by user */ do_qpi = 0; TUNABLE_INT_FETCH("hw.attach_intel_csr_pci", &do_qpi); if (!do_qpi) return; /* PCI config register access is required. */ if (pci_cfgregopen() == 0) return; /* Add a qpi bus device. */ - if (BUS_ADD_CHILD(parent, 20, "qpi", -1) == NULL) + if (BUS_ADD_CHILD(parent, 20, "qpi", DEVICE_UNIT_ANY) == NULL) panic("Failed to add qpi bus"); } static int qpi_probe(device_t dev) { device_set_desc(dev, "QPI system bus"); return (BUS_PROBE_SPECIFIC); } /* * Look for a PCI bus with the specified bus address. If one is found, * add a pcib device and return 0. Otherwise, return an error code. */ static int qpi_probe_pcib(device_t dev, int bus) { struct qpi_device *qdev; device_t child; uint32_t devid; int s; /* * If a PCI bus already exists for this bus number, then * fail. */ if (pci_find_bsf(bus, 0, 0) != NULL) return (EEXIST); /* * Attempt to read the device id for every slot, function 0 on * the bus. If all read values are 0xffffffff this means that * the bus is not present. */ for (s = 0; s <= PCI_SLOTMAX; s++) { devid = pci_cfgregread(0, bus, s, 0, PCIR_DEVVENDOR, 4); if (devid != 0xffffffff) break; } if (devid == 0xffffffff) return (ENOENT); if ((devid & 0xffff) != 0x8086) { if (bootverbose) device_printf(dev, "Device at pci%d.%d.0 has non-Intel vendor 0x%x\n", bus, s, devid & 0xffff); return (ENXIO); } child = BUS_ADD_CHILD(dev, 0, "pcib", DEVICE_UNIT_ANY); if (child == NULL) panic("%s: failed to add pci bus %d", device_get_nameunit(dev), bus); qdev = malloc(sizeof(struct qpi_device), M_QPI, M_WAITOK); qdev->qd_pcibus = bus; device_set_ivars(child, qdev); return (0); } static int qpi_attach(device_t dev) { int bus; /* * Each processor socket has a dedicated PCI bus, sometimes * not enumerated by ACPI. Probe all unattached buses from 0 * to 255. */ for (bus = PCI_BUSMAX; bus >= 0; bus--) qpi_probe_pcib(dev, bus); bus_attach_children(dev); return (0); } static int qpi_print_child(device_t bus, device_t child) { struct qpi_device *qdev; int retval = 0; qdev = device_get_ivars(child); retval += bus_print_child_header(bus, child); if (qdev->qd_pcibus != -1) retval += printf(" pcibus %d", qdev->qd_pcibus); retval += bus_print_child_footer(bus, child); return (retval); } static int qpi_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct qpi_device *qdev; qdev = device_get_ivars(child); switch (which) { case PCIB_IVAR_BUS: *result = qdev->qd_pcibus; break; default: return (ENOENT); } return (0); } static device_method_t qpi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, qpi_identify), DEVMETHOD(device_probe, qpi_probe), DEVMETHOD(device_attach, qpi_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, qpi_print_child), DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_read_ivar, qpi_read_ivar), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), { 0, 0 } }; DEFINE_CLASS_0(qpi, qpi_driver, qpi_methods, 0); DRIVER_MODULE(qpi, nexus, qpi_driver, 0, 0); static int qpi_pcib_probe(device_t dev) { device_set_desc(dev, "QPI Host-PCI bridge"); return (BUS_PROBE_SPECIFIC); } static int qpi_pcib_attach(device_t dev) { device_add_child(dev, "pci", DEVICE_UNIT_ANY); bus_attach_children(dev); return (0); } static int qpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { switch (which) { case PCIB_IVAR_DOMAIN: *result = 0; return (0); case PCIB_IVAR_BUS: *result = pcib_get_bus(dev); return (0); default: return (ENOENT); } } static struct resource * qpi_pcib_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { if (type == PCI_RES_BUS) return (pci_domain_alloc_bus(0, child, rid, start, end, count, flags)); return (bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags)); } static int qpi_pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data) { device_t bus; bus = device_get_parent(pcib); return (PCIB_MAP_MSI(device_get_parent(bus), dev, irq, addr, data)); } static device_method_t qpi_pcib_methods[] = { /* Device interface */ DEVMETHOD(device_probe, qpi_pcib_probe), DEVMETHOD(device_attach, qpi_pcib_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, qpi_pcib_read_ivar), DEVMETHOD(bus_alloc_resource, qpi_pcib_alloc_resource), DEVMETHOD(bus_adjust_resource, legacy_pcib_adjust_resource), DEVMETHOD(bus_release_resource, legacy_pcib_release_resource), DEVMETHOD(bus_activate_resource, legacy_pcib_activate_resource), DEVMETHOD(bus_deactivate_resource, legacy_pcib_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), /* pcib interface */ DEVMETHOD(pcib_maxslots, pcib_maxslots), DEVMETHOD(pcib_read_config, legacy_pcib_read_config), DEVMETHOD(pcib_write_config, legacy_pcib_write_config), DEVMETHOD(pcib_alloc_msi, legacy_pcib_alloc_msi), DEVMETHOD(pcib_release_msi, pcib_release_msi), DEVMETHOD(pcib_alloc_msix, legacy_pcib_alloc_msix), DEVMETHOD(pcib_release_msix, pcib_release_msix), DEVMETHOD(pcib_map_msi, qpi_pcib_map_msi), DEVMETHOD_END }; DEFINE_CLASS_0(pcib, qpi_pcib_driver, qpi_pcib_methods, 0); DRIVER_MODULE(pcib, qpi, qpi_pcib_driver, 0, 0);