2005-04-17 02:20:36 +04:00
/*
* 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
2005-05-01 19:59:26 +04:00
* @ res : the resources to clean
2005-04-17 02:20:36 +04:00
*
*/
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
2005-06-24 09:05:21 +04:00
* @ mode : 0 or PNP_CONFIG_FORCE
2005-04-17 02:20:36 +04:00
*
* 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 ) ;