2005-04-17 02:20:36 +04:00
/*
* $ Id : proc . c , v 1.13 1998 / 05 / 12 07 : 36 : 07 mj Exp $
*
* Procfs interface for the PCI bus .
*
* Copyright ( c ) 1997 - - 1999 Martin Mares < mj @ ucw . cz >
*/
# include <linux/init.h>
# include <linux/pci.h>
# include <linux/module.h>
# include <linux/proc_fs.h>
# include <linux/seq_file.h>
2008-01-10 16:27:12 +03:00
# include <linux/smp_lock.h>
2007-07-16 10:40:39 +04:00
# include <linux/capability.h>
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
# include <asm/byteorder.h>
2005-04-08 09:53:31 +04:00
# include "pci.h"
2005-04-17 02:20:36 +04:00
static int proc_initialized ; /* = 0 */
static loff_t
proc_bus_pci_lseek ( struct file * file , loff_t off , int whence )
{
loff_t new = - 1 ;
2006-12-08 13:37:28 +03:00
struct inode * inode = file - > f_path . dentry - > d_inode ;
2005-04-17 02:20:36 +04:00
2006-01-10 02:59:24 +03:00
mutex_lock ( & inode - > i_mutex ) ;
2005-04-17 02:20:36 +04:00
switch ( whence ) {
case 0 :
new = off ;
break ;
case 1 :
new = file - > f_pos + off ;
break ;
case 2 :
new = inode - > i_size + off ;
break ;
}
if ( new < 0 | | new > inode - > i_size )
new = - EINVAL ;
else
file - > f_pos = new ;
2006-01-10 02:59:24 +03:00
mutex_unlock ( & inode - > i_mutex ) ;
2005-04-17 02:20:36 +04:00
return new ;
}
static ssize_t
proc_bus_pci_read ( struct file * file , char __user * buf , size_t nbytes , loff_t * ppos )
{
2006-12-08 13:37:28 +03:00
const struct inode * ino = file - > f_path . dentry - > d_inode ;
2005-04-17 02:20:36 +04:00
const struct proc_dir_entry * dp = PDE ( ino ) ;
struct pci_dev * dev = dp - > data ;
unsigned int pos = * ppos ;
unsigned int cnt , size ;
/*
* Normal users can read only the standardized portion of the
* configuration space as several chips lock up when trying to read
* undefined locations ( think of Intel PIIX4 as a typical example ) .
*/
if ( capable ( CAP_SYS_ADMIN ) )
2007-09-28 00:41:16 +04:00
size = dp - > size ;
2005-04-17 02:20:36 +04:00
else if ( dev - > hdr_type = = PCI_HEADER_TYPE_CARDBUS )
size = 128 ;
else
size = 64 ;
if ( pos > = size )
return 0 ;
if ( nbytes > = size )
nbytes = size ;
if ( pos + nbytes > size )
nbytes = size - pos ;
cnt = nbytes ;
if ( ! access_ok ( VERIFY_WRITE , buf , cnt ) )
return - EINVAL ;
if ( ( pos & 1 ) & & cnt ) {
unsigned char val ;
2005-09-27 12:21:55 +04:00
pci_user_read_config_byte ( dev , pos , & val ) ;
2005-04-17 02:20:36 +04:00
__put_user ( val , buf ) ;
buf + + ;
pos + + ;
cnt - - ;
}
if ( ( pos & 3 ) & & cnt > 2 ) {
unsigned short val ;
2005-09-27 12:21:55 +04:00
pci_user_read_config_word ( dev , pos , & val ) ;
2005-04-17 02:20:36 +04:00
__put_user ( cpu_to_le16 ( val ) , ( unsigned short __user * ) buf ) ;
buf + = 2 ;
pos + = 2 ;
cnt - = 2 ;
}
while ( cnt > = 4 ) {
unsigned int val ;
2005-09-27 12:21:55 +04:00
pci_user_read_config_dword ( dev , pos , & val ) ;
2005-04-17 02:20:36 +04:00
__put_user ( cpu_to_le32 ( val ) , ( unsigned int __user * ) buf ) ;
buf + = 4 ;
pos + = 4 ;
cnt - = 4 ;
}
if ( cnt > = 2 ) {
unsigned short val ;
2005-09-27 12:21:55 +04:00
pci_user_read_config_word ( dev , pos , & val ) ;
2005-04-17 02:20:36 +04:00
__put_user ( cpu_to_le16 ( val ) , ( unsigned short __user * ) buf ) ;
buf + = 2 ;
pos + = 2 ;
cnt - = 2 ;
}
if ( cnt ) {
unsigned char val ;
2005-09-27 12:21:55 +04:00
pci_user_read_config_byte ( dev , pos , & val ) ;
2005-04-17 02:20:36 +04:00
__put_user ( val , buf ) ;
buf + + ;
pos + + ;
cnt - - ;
}
* ppos = pos ;
return nbytes ;
}
static ssize_t
proc_bus_pci_write ( struct file * file , const char __user * buf , size_t nbytes , loff_t * ppos )
{
2007-09-28 00:41:17 +04:00
struct inode * ino = file - > f_path . dentry - > d_inode ;
2005-04-17 02:20:36 +04:00
const struct proc_dir_entry * dp = PDE ( ino ) ;
struct pci_dev * dev = dp - > data ;
int pos = * ppos ;
2007-09-28 00:41:16 +04:00
int size = dp - > size ;
2005-04-17 02:20:36 +04:00
int cnt ;
if ( pos > = size )
return 0 ;
if ( nbytes > = size )
nbytes = size ;
if ( pos + nbytes > size )
nbytes = size - pos ;
cnt = nbytes ;
if ( ! access_ok ( VERIFY_READ , buf , cnt ) )
return - EINVAL ;
if ( ( pos & 1 ) & & cnt ) {
unsigned char val ;
__get_user ( val , buf ) ;
2005-09-27 12:21:55 +04:00
pci_user_write_config_byte ( dev , pos , val ) ;
2005-04-17 02:20:36 +04:00
buf + + ;
pos + + ;
cnt - - ;
}
if ( ( pos & 3 ) & & cnt > 2 ) {
unsigned short val ;
__get_user ( val , ( unsigned short __user * ) buf ) ;
2005-09-27 12:21:55 +04:00
pci_user_write_config_word ( dev , pos , le16_to_cpu ( val ) ) ;
2005-04-17 02:20:36 +04:00
buf + = 2 ;
pos + = 2 ;
cnt - = 2 ;
}
while ( cnt > = 4 ) {
unsigned int val ;
__get_user ( val , ( unsigned int __user * ) buf ) ;
2005-09-27 12:21:55 +04:00
pci_user_write_config_dword ( dev , pos , le32_to_cpu ( val ) ) ;
2005-04-17 02:20:36 +04:00
buf + = 4 ;
pos + = 4 ;
cnt - = 4 ;
}
if ( cnt > = 2 ) {
unsigned short val ;
__get_user ( val , ( unsigned short __user * ) buf ) ;
2005-09-27 12:21:55 +04:00
pci_user_write_config_word ( dev , pos , le16_to_cpu ( val ) ) ;
2005-04-17 02:20:36 +04:00
buf + = 2 ;
pos + = 2 ;
cnt - = 2 ;
}
if ( cnt ) {
unsigned char val ;
__get_user ( val , buf ) ;
2005-09-27 12:21:55 +04:00
pci_user_write_config_byte ( dev , pos , val ) ;
2005-04-17 02:20:36 +04:00
buf + + ;
pos + + ;
cnt - - ;
}
* ppos = pos ;
2007-09-28 00:41:17 +04:00
i_size_write ( ino , dp - > size ) ;
2005-04-17 02:20:36 +04:00
return nbytes ;
}
struct pci_filp_private {
enum pci_mmap_state mmap_state ;
int write_combine ;
} ;
2008-01-10 16:27:12 +03:00
static long proc_bus_pci_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2008-01-10 16:27:12 +03:00
const struct proc_dir_entry * dp = PDE ( file - > f_dentry - > d_inode ) ;
2005-04-17 02:20:36 +04:00
struct pci_dev * dev = dp - > data ;
# ifdef HAVE_PCI_MMAP
struct pci_filp_private * fpriv = file - > private_data ;
# endif /* HAVE_PCI_MMAP */
int ret = 0 ;
2008-01-10 16:27:12 +03:00
lock_kernel ( ) ;
2005-04-17 02:20:36 +04:00
switch ( cmd ) {
case PCIIOC_CONTROLLER :
ret = pci_domain_nr ( dev - > bus ) ;
break ;
# ifdef HAVE_PCI_MMAP
case PCIIOC_MMAP_IS_IO :
fpriv - > mmap_state = pci_mmap_io ;
break ;
case PCIIOC_MMAP_IS_MEM :
fpriv - > mmap_state = pci_mmap_mem ;
break ;
case PCIIOC_WRITE_COMBINE :
if ( arg )
fpriv - > write_combine = 1 ;
else
fpriv - > write_combine = 0 ;
break ;
# endif /* HAVE_PCI_MMAP */
default :
ret = - EINVAL ;
break ;
} ;
2008-01-10 16:27:12 +03:00
unlock_kernel ( ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
# ifdef HAVE_PCI_MMAP
static int proc_bus_pci_mmap ( struct file * file , struct vm_area_struct * vma )
{
2006-12-08 13:37:28 +03:00
struct inode * inode = file - > f_path . dentry - > d_inode ;
2005-04-17 02:20:36 +04:00
const struct proc_dir_entry * dp = PDE ( inode ) ;
struct pci_dev * dev = dp - > data ;
struct pci_filp_private * fpriv = file - > private_data ;
int ret ;
if ( ! capable ( CAP_SYS_RAWIO ) )
return - EPERM ;
ret = pci_mmap_page_range ( dev , vma ,
fpriv - > mmap_state ,
fpriv - > write_combine ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static int proc_bus_pci_open ( struct inode * inode , struct file * file )
{
struct pci_filp_private * fpriv = kmalloc ( sizeof ( * fpriv ) , GFP_KERNEL ) ;
if ( ! fpriv )
return - ENOMEM ;
fpriv - > mmap_state = pci_mmap_io ;
fpriv - > write_combine = 0 ;
file - > private_data = fpriv ;
return 0 ;
}
static int proc_bus_pci_release ( struct inode * inode , struct file * file )
{
kfree ( file - > private_data ) ;
file - > private_data = NULL ;
return 0 ;
}
# endif /* HAVE_PCI_MMAP */
2007-02-12 11:55:34 +03:00
static const struct file_operations proc_bus_pci_operations = {
2005-04-17 02:20:36 +04:00
. llseek = proc_bus_pci_lseek ,
. read = proc_bus_pci_read ,
. write = proc_bus_pci_write ,
2008-01-10 16:27:12 +03:00
. unlocked_ioctl = proc_bus_pci_ioctl ,
2005-04-17 02:20:36 +04:00
# ifdef HAVE_PCI_MMAP
. open = proc_bus_pci_open ,
. release = proc_bus_pci_release ,
. mmap = proc_bus_pci_mmap ,
# ifdef HAVE_ARCH_PCI_GET_UNMAPPED_AREA
. get_unmapped_area = get_pci_unmapped_area ,
# endif /* HAVE_ARCH_PCI_GET_UNMAPPED_AREA */
# endif /* HAVE_PCI_MMAP */
} ;
/* iterator */
static void * pci_seq_start ( struct seq_file * m , loff_t * pos )
{
struct pci_dev * dev = NULL ;
loff_t n = * pos ;
for_each_pci_dev ( dev ) {
if ( ! n - - )
break ;
}
return dev ;
}
static void * pci_seq_next ( struct seq_file * m , void * v , loff_t * pos )
{
struct pci_dev * dev = v ;
( * pos ) + + ;
dev = pci_get_device ( PCI_ANY_ID , PCI_ANY_ID , dev ) ;
return dev ;
}
static void pci_seq_stop ( struct seq_file * m , void * v )
{
if ( v ) {
struct pci_dev * dev = v ;
pci_dev_put ( dev ) ;
}
}
static int show_device ( struct seq_file * m , void * v )
{
const struct pci_dev * dev = v ;
const struct pci_driver * drv ;
int i ;
if ( dev = = NULL )
return 0 ;
drv = pci_dev_driver ( dev ) ;
seq_printf ( m , " %02x%02x \t %04x%04x \t %x " ,
dev - > bus - > number ,
dev - > devfn ,
dev - > vendor ,
dev - > device ,
dev - > irq ) ;
/* Here should be 7 and not PCI_NUM_RESOURCES as we need to preserve compatibility */
2005-05-13 11:44:10 +04:00
for ( i = 0 ; i < 7 ; i + + ) {
2006-06-13 04:06:02 +04:00
resource_size_t start , end ;
2005-05-13 11:44:10 +04:00
pci_resource_to_user ( dev , i , & dev - > resource [ i ] , & start , & end ) ;
2006-06-13 02:14:29 +04:00
seq_printf ( m , " \t %16llx " ,
( unsigned long long ) ( start |
( dev - > resource [ i ] . flags & PCI_REGION_FLAG_MASK ) ) ) ;
2005-05-13 11:44:10 +04:00
}
for ( i = 0 ; i < 7 ; i + + ) {
2006-06-13 04:06:02 +04:00
resource_size_t start , end ;
2005-05-13 11:44:10 +04:00
pci_resource_to_user ( dev , i , & dev - > resource [ i ] , & start , & end ) ;
2006-06-13 02:14:29 +04:00
seq_printf ( m , " \t %16llx " ,
2005-04-17 02:20:36 +04:00
dev - > resource [ i ] . start < dev - > resource [ i ] . end ?
2006-06-13 02:14:29 +04:00
( unsigned long long ) ( end - start ) + 1 : 0 ) ;
2005-05-13 11:44:10 +04:00
}
2005-04-17 02:20:36 +04:00
seq_putc ( m , ' \t ' ) ;
if ( drv )
seq_printf ( m , " %s " , drv - > name ) ;
seq_putc ( m , ' \n ' ) ;
return 0 ;
}
2008-01-22 22:53:43 +03:00
static const struct seq_operations proc_bus_pci_devices_op = {
2005-04-17 02:20:36 +04:00
. start = pci_seq_start ,
. next = pci_seq_next ,
. stop = pci_seq_stop ,
. show = show_device
} ;
static struct proc_dir_entry * proc_bus_pci_dir ;
int pci_proc_attach_device ( struct pci_dev * dev )
{
struct pci_bus * bus = dev - > bus ;
struct proc_dir_entry * e ;
char name [ 16 ] ;
if ( ! proc_initialized )
return - EACCES ;
if ( ! bus - > procdir ) {
if ( pci_proc_domain ( bus ) ) {
sprintf ( name , " %04x:%02x " , pci_domain_nr ( bus ) ,
bus - > number ) ;
} else {
sprintf ( name , " %02x " , bus - > number ) ;
}
bus - > procdir = proc_mkdir ( name , proc_bus_pci_dir ) ;
if ( ! bus - > procdir )
return - ENOMEM ;
}
sprintf ( name , " %02x.%x " , PCI_SLOT ( dev - > devfn ) , PCI_FUNC ( dev - > devfn ) ) ;
e = create_proc_entry ( name , S_IFREG | S_IRUGO | S_IWUSR , bus - > procdir ) ;
if ( ! e )
return - ENOMEM ;
e - > proc_fops = & proc_bus_pci_operations ;
e - > data = dev ;
e - > size = dev - > cfg_size ;
dev - > procent = e ;
return 0 ;
}
int pci_proc_detach_device ( struct pci_dev * dev )
{
struct proc_dir_entry * e ;
if ( ( e = dev - > procent ) ) {
2008-02-21 09:24:16 +03:00
if ( atomic_read ( & e - > count ) > 1 )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
remove_proc_entry ( e - > name , dev - > bus - > procdir ) ;
dev - > procent = NULL ;
}
return 0 ;
}
2005-12-22 03:08:52 +03:00
#if 0
2005-04-17 02:20:36 +04:00
int pci_proc_attach_bus ( struct pci_bus * bus )
{
struct proc_dir_entry * de = bus - > procdir ;
if ( ! proc_initialized )
return - EACCES ;
if ( ! de ) {
char name [ 16 ] ;
sprintf ( name , " %02x " , bus - > number ) ;
de = bus - > procdir = proc_mkdir ( name , proc_bus_pci_dir ) ;
if ( ! de )
return - ENOMEM ;
}
return 0 ;
}
2005-12-22 03:08:52 +03:00
# endif /* 0 */
2005-04-17 02:20:36 +04:00
int pci_proc_detach_bus ( struct pci_bus * bus )
{
struct proc_dir_entry * de = bus - > procdir ;
if ( de )
remove_proc_entry ( de - > name , proc_bus_pci_dir ) ;
return 0 ;
}
static int proc_bus_pci_dev_open ( struct inode * inode , struct file * file )
{
return seq_open ( file , & proc_bus_pci_devices_op ) ;
}
2007-02-12 11:55:34 +03:00
static const struct file_operations proc_bus_pci_dev_operations = {
2005-04-17 02:20:36 +04:00
. open = proc_bus_pci_dev_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = seq_release ,
} ;
static int __init pci_proc_init ( void )
{
struct proc_dir_entry * entry ;
struct pci_dev * dev = NULL ;
proc_bus_pci_dir = proc_mkdir ( " pci " , proc_bus ) ;
entry = create_proc_entry ( " devices " , 0 , proc_bus_pci_dir ) ;
if ( entry )
entry - > proc_fops = & proc_bus_pci_dev_operations ;
proc_initialized = 1 ;
while ( ( dev = pci_get_device ( PCI_ANY_ID , PCI_ANY_ID , dev ) ) ! = NULL ) {
pci_proc_attach_device ( dev ) ;
}
return 0 ;
}
__initcall ( pci_proc_init ) ;