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 ,
2006-06-13 04:06:02 +04:00
resource_size_t size , resource_size_t align ,
resource_size_t min , unsigned int type_mask ,
void ( * alignf ) ( void * , struct resource * , resource_size_t ,
resource_size_t ) ,
void * alignf_data )
2005-04-17 02:20:36 +04:00
{
int i , ret = - ENOMEM ;
2009-04-24 07:48:32 +04:00
resource_size_t max = - 1 ;
2005-04-17 02:20:36 +04:00
type_mask | = IORESOURCE_IO | IORESOURCE_MEM ;
2009-04-24 07:48:32 +04:00
/* don't allocate too high if the pref mem doesn't support 64bit*/
if ( ! ( res - > flags & IORESOURCE_MEM_64 ) )
max = PCIBIOS_MAX_MEM_32 ;
2005-04-17 02:20:36 +04:00
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 ,
2009-04-24 07:48:32 +04:00
max , align ,
2005-04-17 02:20:36 +04:00
alignf , alignf_data ) ;
if ( ret = = 0 )
break ;
}
return ret ;
}
/**
2008-11-21 21:41:45 +03:00
* pci_bus_add_device - add a single device
2005-04-17 02:20:36 +04:00
* @ dev : device to add
*
* This adds a single pci device to the global
* device list and adds sysfs and procfs entries
*/
2007-03-27 09:53:30 +04:00
int pci_bus_add_device ( struct pci_dev * dev )
2005-04-17 02:20:36 +04:00
{
2006-08-28 22:43:25 +04:00
int retval ;
retval = device_add ( & dev - > dev ) ;
if ( retval )
return retval ;
2005-04-17 02:20:36 +04:00
2008-02-15 01:56:56 +03:00
dev - > is_added = 1 ;
2005-04-17 02:20:36 +04:00
pci_proc_attach_device ( dev ) ;
pci_create_sysfs_dev_files ( dev ) ;
2006-08-28 22:43:25 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2008-11-21 21:42:35 +03:00
/**
* pci_bus_add_child - add a child bus
* @ bus : bus to add
*
* This adds sysfs entries for a single bus
*/
int pci_bus_add_child ( struct pci_bus * bus )
{
int retval ;
if ( bus - > bridge )
bus - > dev . parent = bus - > bridge ;
retval = device_register ( & bus - > dev ) ;
if ( retval )
return retval ;
bus - > is_added = 1 ;
retval = device_create_file ( & bus - > dev , & dev_attr_cpuaffinity ) ;
if ( retval )
return retval ;
retval = device_create_file ( & bus - > dev , & dev_attr_cpulistaffinity ) ;
/* Create legacy_io and legacy_mem files for this bus */
pci_create_legacy_files ( bus ) ;
return retval ;
}
2005-04-17 02:20:36 +04:00
/**
* 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 .
*/
2009-02-04 02:45:26 +03:00
void pci_bus_add_devices ( const struct pci_bus * bus )
2005-04-17 02:20:36 +04:00
{
struct pci_dev * dev ;
2008-11-21 21:41:45 +03:00
struct pci_bus * child ;
2006-08-28 22:43:25 +04:00
int retval ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( dev , & bus - > devices , bus_list ) {
2008-02-15 01:56:56 +03:00
/* Skip already-added devices */
if ( dev - > is_added )
2005-04-17 02:20:36 +04:00
continue ;
2006-08-28 22:43:25 +04:00
retval = pci_bus_add_device ( dev ) ;
if ( retval )
dev_err ( & dev - > dev , " Error adding device, continuing \n " ) ;
2005-04-17 02:20:36 +04:00
}
list_for_each_entry ( dev , & bus - > devices , bus_list ) {
2008-02-15 01:56:56 +03:00
BUG_ON ( ! dev - > is_added ) ;
2005-04-17 02:20:36 +04:00
2008-11-21 21:41:45 +03:00
child = dev - > subordinate ;
2005-04-17 02:20:36 +04:00
/*
* If there is an unattached subordinate bus , attach
* it and then scan for unattached PCI devices .
*/
2008-11-21 21:41:45 +03:00
if ( ! child )
continue ;
if ( list_empty ( & child - > node ) ) {
down_write ( & pci_bus_sem ) ;
list_add_tail ( & child - > node , & dev - > bus - > children ) ;
up_write ( & pci_bus_sem ) ;
}
pci_bus_add_devices ( child ) ;
/*
* register the bus with sysfs as the parent is now
* properly registered .
*/
if ( child - > is_added )
continue ;
2008-11-21 21:42:35 +03:00
retval = pci_bus_add_child ( child ) ;
2008-11-21 21:41:45 +03:00
if ( retval )
2008-11-21 21:42:35 +03:00
dev_err ( & dev - > dev , " Error adding bus, continuing \n " ) ;
2005-04-17 02:20:36 +04:00
}
}
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 ) {
2009-04-03 11:41:46 +04:00
if ( ! pci_is_enabled ( dev ) ) {
2009-03-20 23:56:20 +03:00
retval = pci_enable_device ( dev ) ;
pci_set_master ( dev ) ;
}
2005-04-17 02:20:36 +04:00
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 .
2009-06-16 09:34:38 +04:00
*
* We check the return of @ cb each time . If it returns anything
* other than 0 , we break out .
*
2005-08-18 08:33:01 +04:00
*/
2009-06-16 09:34:38 +04:00
void pci_walk_bus ( struct pci_bus * top , int ( * cb ) ( struct pci_dev * , void * ) ,
2005-08-18 08:33:01 +04:00
void * userdata )
{
struct pci_dev * dev ;
struct pci_bus * bus ;
struct list_head * next ;
2009-06-16 09:34:38 +04:00
int retval ;
2005-08-18 08:33:01 +04:00
bus = top ;
2006-06-02 08:35:43 +04:00
down_read ( & pci_bus_sem ) ;
2005-08-18 08:33:01 +04:00
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 ) ;
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 ;
2006-06-02 08:35:43 +04:00
/* Run device routines with the device locked */
down ( & dev - > dev . sem ) ;
2009-06-16 09:34:38 +04:00
retval = cb ( dev , userdata ) ;
2006-06-02 08:35:43 +04:00
up ( & dev - > dev . sem ) ;
2009-06-16 09:34:38 +04:00
if ( retval )
break ;
2005-08-18 08:33:01 +04:00
}
2006-06-02 08:35:43 +04:00
up_read ( & pci_bus_sem ) ;
2005-08-18 08:33:01 +04:00
}
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 ) ;