2005-04-17 02:20:36 +04:00
/*
* drivers / pci / bus . c
*
* From setup - res . c , by :
* Dave Rusling ( david . rusling @ reo . mts . dec . com )
* David Mosberger ( davidm @ cs . arizona . edu )
* David Miller ( davem @ redhat . com )
* Ivan Kokshaysky ( ink @ jurassic . park . msu . ru )
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/pci.h>
# include <linux/errno.h>
# include <linux/ioport.h>
# include <linux/proc_fs.h>
# include <linux/init.h>
# include "pci.h"
/**
* pci_bus_alloc_resource - allocate a resource from a parent bus
* @ bus : PCI bus
* @ res : resource to allocate
* @ size : size of resource to allocate
* @ align : alignment of resource to allocate
* @ min : minimum / proc / iomem address to allocate
* @ type_mask : IORESOURCE_ * type flags
* @ alignf : resource alignment function
* @ alignf_data : data argument for resource alignment function
*
* Given the PCI bus a device resides on , the size , minimum address ,
* alignment and type , try to find an acceptable resource allocation
* for a specific device resource .
*/
int
pci_bus_alloc_resource ( struct pci_bus * bus , struct resource * res ,
unsigned long size , unsigned long align , unsigned long min ,
unsigned int type_mask ,
void ( * alignf ) ( void * , struct resource * ,
unsigned long , unsigned long ) ,
void * alignf_data )
{
int i , ret = - ENOMEM ;
type_mask | = IORESOURCE_IO | IORESOURCE_MEM ;
for ( i = 0 ; i < PCI_BUS_NUM_RESOURCES ; i + + ) {
struct resource * r = bus - > resource [ i ] ;
if ( ! r )
continue ;
/* type_mask must match */
if ( ( res - > flags ^ r - > flags ) & type_mask )
continue ;
/* We cannot allocate a non-prefetching resource
from a pre - fetching area */
if ( ( r - > flags & IORESOURCE_PREFETCH ) & &
! ( res - > flags & IORESOURCE_PREFETCH ) )
continue ;
/* Ok, try it out.. */
2005-08-03 01:55:40 +04:00
ret = allocate_resource ( r , res , size ,
r - > start ? : min ,
- 1 , align ,
2005-04-17 02:20:36 +04:00
alignf , alignf_data ) ;
if ( ret = = 0 )
break ;
}
return ret ;
}
/**
* add a single device
* @ dev : device to add
*
* This adds a single pci device to the global
* device list and adds sysfs and procfs entries
*/
void __devinit pci_bus_add_device ( struct pci_dev * dev )
{
device_add ( & dev - > dev ) ;
spin_lock ( & pci_bus_lock ) ;
list_add_tail ( & dev - > global_list , & pci_devices ) ;
spin_unlock ( & pci_bus_lock ) ;
pci_proc_attach_device ( dev ) ;
pci_create_sysfs_dev_files ( dev ) ;
}
/**
* pci_bus_add_devices - insert newly discovered PCI devices
* @ bus : bus to check for new devices
*
* Add newly discovered PCI devices ( which are on the bus - > devices
* list ) to the global PCI device list , add the sysfs and procfs
* entries . Where a bridge is found , add the discovered bus to
* the parents list of child buses , and recurse ( breadth - first
* to be compatible with 2.4 )
*
* Call hotplug for each new devices .
*/
void __devinit pci_bus_add_devices ( struct pci_bus * bus )
{
struct pci_dev * dev ;
list_for_each_entry ( dev , & bus - > devices , bus_list ) {
/*
* Skip already - present devices ( which are on the
* global device list . )
*/
if ( ! list_empty ( & dev - > global_list ) )
continue ;
pci_bus_add_device ( dev ) ;
}
list_for_each_entry ( dev , & bus - > devices , bus_list ) {
BUG_ON ( list_empty ( & dev - > global_list ) ) ;
/*
* If there is an unattached subordinate bus , attach
* it and then scan for unattached PCI devices .
*/
2005-04-28 11:25:49 +04:00
if ( dev - > subordinate ) {
if ( list_empty ( & dev - > subordinate - > node ) ) {
spin_lock ( & pci_bus_lock ) ;
list_add_tail ( & dev - > subordinate - > node ,
& dev - > bus - > children ) ;
spin_unlock ( & pci_bus_lock ) ;
}
2005-04-17 02:20:36 +04:00
pci_bus_add_devices ( dev - > subordinate ) ;
sysfs_create_link ( & dev - > subordinate - > class_dev . kobj , & dev - > dev . kobj , " bridge " ) ;
}
}
}
void pci_enable_bridges ( struct pci_bus * bus )
{
struct pci_dev * dev ;
2005-07-28 22:37:33 +04:00
int retval ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( dev , & bus - > devices , bus_list ) {
if ( dev - > subordinate ) {
2005-07-28 22:37:33 +04:00
retval = pci_enable_device ( dev ) ;
2005-04-17 02:20:36 +04:00
pci_set_master ( dev ) ;
pci_enable_bridges ( dev - > subordinate ) ;
}
}
}
2005-08-18 08:33:01 +04:00
/** pci_walk_bus - walk devices on/under bus, calling callback.
* @ top bus whose devices should be walked
* @ cb callback to be called for each device found
* @ userdata arbitrary pointer to be passed to callback .
*
* Walk the given bus , including any bridged devices
* on buses under this bus . Call the provided callback
* on each device found .
*/
void pci_walk_bus ( struct pci_bus * top , void ( * cb ) ( struct pci_dev * , void * ) ,
void * userdata )
{
struct pci_dev * dev ;
struct pci_bus * bus ;
struct list_head * next ;
bus = top ;
spin_lock ( & pci_bus_lock ) ;
next = top - > devices . next ;
for ( ; ; ) {
if ( next = = & bus - > devices ) {
/* end of this bus, go up or finish */
if ( bus = = top )
break ;
next = bus - > self - > bus_list . next ;
bus = bus - > self - > bus ;
continue ;
}
dev = list_entry ( next , struct pci_dev , bus_list ) ;
pci_dev_get ( dev ) ;
if ( dev - > subordinate ) {
/* this is a pci-pci bridge, do its devices next */
next = dev - > subordinate - > devices . next ;
bus = dev - > subordinate ;
} else
next = dev - > bus_list . next ;
spin_unlock ( & pci_bus_lock ) ;
/* Run device routines with the bus unlocked */
cb ( dev , userdata ) ;
spin_lock ( & pci_bus_lock ) ;
pci_dev_put ( dev ) ;
}
spin_unlock ( & pci_bus_lock ) ;
}
EXPORT_SYMBOL_GPL ( pci_walk_bus ) ;
2005-04-17 02:20:36 +04:00
EXPORT_SYMBOL ( pci_bus_alloc_resource ) ;
EXPORT_SYMBOL_GPL ( pci_bus_add_device ) ;
EXPORT_SYMBOL ( pci_bus_add_devices ) ;
EXPORT_SYMBOL ( pci_enable_bridges ) ;