diff options
Diffstat (limited to 'drivers/pnp/manager.c')
-rw-r--r-- | drivers/pnp/manager.c | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/drivers/pnp/manager.c b/drivers/pnp/manager.c new file mode 100644 index 00000000000..639e0425348 --- /dev/null +++ b/drivers/pnp/manager.c @@ -0,0 +1,566 @@ +/* + * manager.c - Resource Management, Conflict Resolution, Activation and Disabling of Devices + * + * based on isapnp.c resource management (c) Jaroslav Kysela <perex@suse.cz> + * Copyright 2003 Adam Belay <ambx1@neo.rr.com> + * + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> + +#ifdef CONFIG_PNP_DEBUG + #define DEBUG +#else + #undef DEBUG +#endif + +#include <linux/pnp.h> +#include "base.h" + +DECLARE_MUTEX(pnp_res_mutex); + +static int pnp_assign_port(struct pnp_dev *dev, struct pnp_port *rule, int idx) +{ + unsigned long *start, *end, *flags; + + if (!dev || !rule) + return -EINVAL; + + if (idx >= PNP_MAX_PORT) { + pnp_err("More than 4 ports is incompatible with pnp specifications."); + /* pretend we were successful so at least the manager won't try again */ + return 1; + } + + /* check if this resource has been manually set, if so skip */ + if (!(dev->res.port_resource[idx].flags & IORESOURCE_AUTO)) + return 1; + + start = &dev->res.port_resource[idx].start; + end = &dev->res.port_resource[idx].end; + flags = &dev->res.port_resource[idx].flags; + + /* set the initial values */ + *flags |= rule->flags | IORESOURCE_IO; + *flags &= ~IORESOURCE_UNSET; + + if (!rule->size) { + *flags |= IORESOURCE_DISABLED; + return 1; /* skip disabled resource requests */ + } + + *start = rule->min; + *end = *start + rule->size - 1; + + /* run through until pnp_check_port is happy */ + while (!pnp_check_port(dev, idx)) { + *start += rule->align; + *end = *start + rule->size - 1; + if (*start > rule->max || !rule->align) + return 0; + } + return 1; +} + +static int pnp_assign_mem(struct pnp_dev *dev, struct pnp_mem *rule, int idx) +{ + unsigned long *start, *end, *flags; + + if (!dev || !rule) + return -EINVAL; + + if (idx >= PNP_MAX_MEM) { + pnp_err("More than 8 mems is incompatible with pnp specifications."); + /* pretend we were successful so at least the manager won't try again */ + return 1; + } + + /* check if this resource has been manually set, if so skip */ + if (!(dev->res.mem_resource[idx].flags & IORESOURCE_AUTO)) + return 1; + + start = &dev->res.mem_resource[idx].start; + end = &dev->res.mem_resource[idx].end; + flags = &dev->res.mem_resource[idx].flags; + + /* set the initial values */ + *flags |= rule->flags | IORESOURCE_MEM; + *flags &= ~IORESOURCE_UNSET; + + /* convert pnp flags to standard Linux flags */ + if (!(rule->flags & IORESOURCE_MEM_WRITEABLE)) + *flags |= IORESOURCE_READONLY; + if (rule->flags & IORESOURCE_MEM_CACHEABLE) + *flags |= IORESOURCE_CACHEABLE; + if (rule->flags & IORESOURCE_MEM_RANGELENGTH) + *flags |= IORESOURCE_RANGELENGTH; + if (rule->flags & IORESOURCE_MEM_SHADOWABLE) + *flags |= IORESOURCE_SHADOWABLE; + + if (!rule->size) { + *flags |= IORESOURCE_DISABLED; + return 1; /* skip disabled resource requests */ + } + + *start = rule->min; + *end = *start + rule->size -1; + + /* run through until pnp_check_mem is happy */ + while (!pnp_check_mem(dev, idx)) { + *start += rule->align; + *end = *start + rule->size - 1; + if (*start > rule->max || !rule->align) + return 0; + } + return 1; +} + +static int pnp_assign_irq(struct pnp_dev * dev, struct pnp_irq *rule, int idx) +{ + unsigned long *start, *end, *flags; + int i; + + /* IRQ priority: this table is good for i386 */ + static unsigned short xtab[16] = { + 5, 10, 11, 12, 9, 14, 15, 7, 3, 4, 13, 0, 1, 6, 8, 2 + }; + + if (!dev || !rule) + return -EINVAL; + + if (idx >= PNP_MAX_IRQ) { + pnp_err("More than 2 irqs is incompatible with pnp specifications."); + /* pretend we were successful so at least the manager won't try again */ + return 1; + } + + /* check if this resource has been manually set, if so skip */ + if (!(dev->res.irq_resource[idx].flags & IORESOURCE_AUTO)) + return 1; + + start = &dev->res.irq_resource[idx].start; + end = &dev->res.irq_resource[idx].end; + flags = &dev->res.irq_resource[idx].flags; + + /* set the initial values */ + *flags |= rule->flags | IORESOURCE_IRQ; + *flags &= ~IORESOURCE_UNSET; + + if (bitmap_empty(rule->map, PNP_IRQ_NR)) { + *flags |= IORESOURCE_DISABLED; + return 1; /* skip disabled resource requests */ + } + + /* TBD: need check for >16 IRQ */ + *start = find_next_bit(rule->map, PNP_IRQ_NR, 16); + if (*start < PNP_IRQ_NR) { + *end = *start; + return 1; + } + for (i = 0; i < 16; i++) { + if(test_bit(xtab[i], rule->map)) { + *start = *end = xtab[i]; + if(pnp_check_irq(dev, idx)) + return 1; + } + } + return 0; +} + +static int pnp_assign_dma(struct pnp_dev *dev, struct pnp_dma *rule, int idx) +{ + unsigned long *start, *end, *flags; + int i; + + /* DMA priority: this table is good for i386 */ + static unsigned short xtab[8] = { + 1, 3, 5, 6, 7, 0, 2, 4 + }; + + if (!dev || !rule) + return -EINVAL; + + if (idx >= PNP_MAX_DMA) { + pnp_err("More than 2 dmas is incompatible with pnp specifications."); + /* pretend we were successful so at least the manager won't try again */ + return 1; + } + + /* check if this resource has been manually set, if so skip */ + if (!(dev->res.dma_resource[idx].flags & IORESOURCE_AUTO)) + return 1; + + start = &dev->res.dma_resource[idx].start; + end = &dev->res.dma_resource[idx].end; + flags = &dev->res.dma_resource[idx].flags; + + /* set the initial values */ + *flags |= rule->flags | IORESOURCE_DMA; + *flags &= ~IORESOURCE_UNSET; + + if (!rule->map) { + *flags |= IORESOURCE_DISABLED; + return 1; /* skip disabled resource requests */ + } + + for (i = 0; i < 8; i++) { + if(rule->map & (1<<xtab[i])) { + *start = *end = xtab[i]; + if(pnp_check_dma(dev, idx)) + return 1; + } + } + return 0; +} + +/** + * pnp_init_resources - Resets a resource table to default values. + * @table: pointer to the desired resource table + * + */ +void pnp_init_resource_table(struct pnp_resource_table *table) +{ + int idx; + for (idx = 0; idx < PNP_MAX_IRQ; idx++) { + table->irq_resource[idx].name = NULL; + table->irq_resource[idx].start = -1; + table->irq_resource[idx].end = -1; + table->irq_resource[idx].flags = IORESOURCE_IRQ | IORESOURCE_AUTO | IORESOURCE_UNSET; + } + for (idx = 0; idx < PNP_MAX_DMA; idx++) { + table->dma_resource[idx].name = NULL; + table->dma_resource[idx].start = -1; + table->dma_resource[idx].end = -1; + table->dma_resource[idx].flags = IORESOURCE_DMA | IORESOURCE_AUTO | IORESOURCE_UNSET; + } + for (idx = 0; idx < PNP_MAX_PORT; idx++) { + table->port_resource[idx].name = NULL; + table->port_resource[idx].start = 0; + table->port_resource[idx].end = 0; + table->port_resource[idx].flags = IORESOURCE_IO | IORESOURCE_AUTO | IORESOURCE_UNSET; + } + for (idx = 0; idx < PNP_MAX_MEM; idx++) { + table->mem_resource[idx].name = NULL; + table->mem_resource[idx].start = 0; + table->mem_resource[idx].end = 0; + table->mem_resource[idx].flags = IORESOURCE_MEM | IORESOURCE_AUTO | IORESOURCE_UNSET; + } +} + +/** + * pnp_clean_resources - clears resources that were not manually set + * @res - the resources to clean + * + */ +static void pnp_clean_resource_table(struct pnp_resource_table * res) +{ + int idx; + for (idx = 0; idx < PNP_MAX_IRQ; idx++) { + if (!(res->irq_resource[idx].flags & IORESOURCE_AUTO)) + continue; + res->irq_resource[idx].start = -1; + res->irq_resource[idx].end = -1; + res->irq_resource[idx].flags = IORESOURCE_IRQ | IORESOURCE_AUTO | IORESOURCE_UNSET; + } + for (idx = 0; idx < PNP_MAX_DMA; idx++) { + if (!(res->dma_resource[idx].flags & IORESOURCE_AUTO)) + continue; + res->dma_resource[idx].start = -1; + res->dma_resource[idx].end = -1; + res->dma_resource[idx].flags = IORESOURCE_DMA | IORESOURCE_AUTO | IORESOURCE_UNSET; + } + for (idx = 0; idx < PNP_MAX_PORT; idx++) { + if (!(res->port_resource[idx].flags & IORESOURCE_AUTO)) + continue; + res->port_resource[idx].start = 0; + res->port_resource[idx].end = 0; + res->port_resource[idx].flags = IORESOURCE_IO | IORESOURCE_AUTO | IORESOURCE_UNSET; + } + for (idx = 0; idx < PNP_MAX_MEM; idx++) { + if (!(res->mem_resource[idx].flags & IORESOURCE_AUTO)) + continue; + res->mem_resource[idx].start = 0; + res->mem_resource[idx].end = 0; + res->mem_resource[idx].flags = IORESOURCE_MEM | IORESOURCE_AUTO | IORESOURCE_UNSET; + } +} + +/** + * pnp_assign_resources - assigns resources to the device based on the specified dependent number + * @dev: pointer to the desired device + * @depnum: the dependent function number + * + * Only set depnum to 0 if the device does not have dependent options. + */ +static int pnp_assign_resources(struct pnp_dev *dev, int depnum) +{ + struct pnp_port *port; + struct pnp_mem *mem; + struct pnp_irq *irq; + struct pnp_dma *dma; + int nport = 0, nmem = 0, nirq = 0, ndma = 0; + + if (!pnp_can_configure(dev)) + return -ENODEV; + + down(&pnp_res_mutex); + pnp_clean_resource_table(&dev->res); /* start with a fresh slate */ + if (dev->independent) { + port = dev->independent->port; + mem = dev->independent->mem; + irq = dev->independent->irq; + dma = dev->independent->dma; + while (port) { + if (!pnp_assign_port(dev, port, nport)) + goto fail; + nport++; + port = port->next; + } + while (mem) { + if (!pnp_assign_mem(dev, mem, nmem)) + goto fail; + nmem++; + mem = mem->next; + } + while (irq) { + if (!pnp_assign_irq(dev, irq, nirq)) + goto fail; + nirq++; + irq = irq->next; + } + while (dma) { + if (!pnp_assign_dma(dev, dma, ndma)) + goto fail; + ndma++; + dma = dma->next; + } + } + + if (depnum) { + struct pnp_option *dep; + int i; + for (i=1,dep=dev->dependent; i<depnum; i++, dep=dep->next) + if(!dep) + goto fail; + port =dep->port; + mem = dep->mem; + irq = dep->irq; + dma = dep->dma; + while (port) { + if (!pnp_assign_port(dev, port, nport)) + goto fail; + nport++; + port = port->next; + } + while (mem) { + if (!pnp_assign_mem(dev, mem, nmem)) + goto fail; + nmem++; + mem = mem->next; + } + while (irq) { + if (!pnp_assign_irq(dev, irq, nirq)) + goto fail; + nirq++; + irq = irq->next; + } + while (dma) { + if (!pnp_assign_dma(dev, dma, ndma)) + goto fail; + ndma++; + dma = dma->next; + } + } else if (dev->dependent) + goto fail; + + up(&pnp_res_mutex); + return 1; + +fail: + pnp_clean_resource_table(&dev->res); + up(&pnp_res_mutex); + return 0; +} + +/** + * pnp_manual_config_dev - Disables Auto Config and Manually sets the resource table + * @dev: pointer to the desired device + * @res: pointer to the new resource config + * + * This function can be used by drivers that want to manually set thier resources. + */ +int pnp_manual_config_dev(struct pnp_dev *dev, struct pnp_resource_table * res, int mode) +{ + int i; + struct pnp_resource_table * bak; + if (!dev || !res) + return -EINVAL; + if (!pnp_can_configure(dev)) + return -ENODEV; + bak = pnp_alloc(sizeof(struct pnp_resource_table)); + if (!bak) + return -ENOMEM; + *bak = dev->res; + + down(&pnp_res_mutex); + dev->res = *res; + if (!(mode & PNP_CONFIG_FORCE)) { + for (i = 0; i < PNP_MAX_PORT; i++) { + if(!pnp_check_port(dev,i)) + goto fail; + } + for (i = 0; i < PNP_MAX_MEM; i++) { + if(!pnp_check_mem(dev,i)) + goto fail; + } + for (i = 0; i < PNP_MAX_IRQ; i++) { + if(!pnp_check_irq(dev,i)) + goto fail; + } + for (i = 0; i < PNP_MAX_DMA; i++) { + if(!pnp_check_dma(dev,i)) + goto fail; + } + } + up(&pnp_res_mutex); + + kfree(bak); + return 0; + +fail: + dev->res = *bak; + up(&pnp_res_mutex); + kfree(bak); + return -EINVAL; +} + +/** + * pnp_auto_config_dev - automatically assigns resources to a device + * @dev: pointer to the desired device + * + */ +int pnp_auto_config_dev(struct pnp_dev *dev) +{ + struct pnp_option *dep; + int i = 1; + + if(!dev) + return -EINVAL; + + if(!pnp_can_configure(dev)) { + pnp_info("Device %s does not support resource configuration.", dev->dev.bus_id); + return -ENODEV; + } + + if (!dev->dependent) { + if (pnp_assign_resources(dev, 0)) + return 0; + } else { + dep = dev->dependent; + do { + if (pnp_assign_resources(dev, i)) + return 0; + dep = dep->next; + i++; + } while (dep); + } + + pnp_err("Unable to assign resources to device %s.", dev->dev.bus_id); + return -EBUSY; +} + +/** + * pnp_activate_dev - activates a PnP device for use + * @dev: pointer to the desired device + * + * does not validate or set resources so be careful. + */ +int pnp_activate_dev(struct pnp_dev *dev) +{ + if (!dev) + return -EINVAL; + if (dev->active) { + return 0; /* the device is already active */ + } + + /* ensure resources are allocated */ + if (pnp_auto_config_dev(dev)) + return -EBUSY; + + if (!pnp_can_write(dev)) { + pnp_info("Device %s does not supported activation.", dev->dev.bus_id); + return -EINVAL; + } + + if (dev->protocol->set(dev, &dev->res)<0) { + pnp_err("Failed to activate device %s.", dev->dev.bus_id); + return -EIO; + } + + dev->active = 1; + pnp_info("Device %s activated.", dev->dev.bus_id); + + return 1; +} + +/** + * pnp_disable_dev - disables device + * @dev: pointer to the desired device + * + * inform the correct pnp protocol so that resources can be used by other devices + */ +int pnp_disable_dev(struct pnp_dev *dev) +{ + if (!dev) + return -EINVAL; + if (!dev->active) { + return 0; /* the device is already disabled */ + } + + if (!pnp_can_disable(dev)) { + pnp_info("Device %s does not supported disabling.", dev->dev.bus_id); + return -EINVAL; + } + if (dev->protocol->disable(dev)<0) { + pnp_err("Failed to disable device %s.", dev->dev.bus_id); + return -EIO; + } + + dev->active = 0; + pnp_info("Device %s disabled.", dev->dev.bus_id); + + /* release the resources so that other devices can use them */ + down(&pnp_res_mutex); + pnp_clean_resource_table(&dev->res); + up(&pnp_res_mutex); + + return 1; +} + +/** + * pnp_resource_change - change one resource + * @resource: pointer to resource to be changed + * @start: start of region + * @size: size of region + * + */ +void pnp_resource_change(struct resource *resource, unsigned long start, unsigned long size) +{ + if (resource == NULL) + return; + resource->flags &= ~(IORESOURCE_AUTO | IORESOURCE_UNSET); + resource->start = start; + resource->end = start + size - 1; +} + + +EXPORT_SYMBOL(pnp_manual_config_dev); +EXPORT_SYMBOL(pnp_auto_config_dev); +EXPORT_SYMBOL(pnp_activate_dev); +EXPORT_SYMBOL(pnp_disable_dev); +EXPORT_SYMBOL(pnp_resource_change); +EXPORT_SYMBOL(pnp_init_resource_table); |