523 lines · 10.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * 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/slab.h>#include <linux/module.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/capability.h>#include <linux/uaccess.h>#include <linux/security.h>#include <linux/panic.h>#include <linux/sched.h>#include <asm/byteorder.h>#include "pci.h" static int proc_initialized;	/* = 0 */static DEFINE_MUTEX(pci_proc_lock); static loff_t proc_bus_pci_lseek(struct file *file, loff_t off, int whence){	struct pci_dev *dev = pde_data(file_inode(file));	return fixed_size_llseek(file, off, whence, dev->cfg_size);} static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,				 size_t nbytes, loff_t *ppos){	struct pci_dev *dev = pde_data(file_inode(file));	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 (file_ns_capable(file, &init_user_ns, CAP_SYS_ADMIN))		size = dev->cfg_size;	else if (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)		size = 128;	else		size = 64; 	if (!nbytes)		return 0; 	if (pos >= size)		return 0;	if (nbytes >= size)		nbytes = size;	if (pos + nbytes > size)		nbytes = size - pos;	cnt = nbytes; 	if (!access_ok(buf, cnt))		return -EINVAL; 	pci_config_pm_runtime_get(dev); 	if ((pos & 1) && cnt) {		unsigned char val;		pci_user_read_config_byte(dev, pos, &val);		__put_user(val, buf);		buf++;		pos++;		cnt--;	} 	if ((pos & 3) && cnt > 2) {		unsigned short val;		pci_user_read_config_word(dev, pos, &val);		__put_user(cpu_to_le16(val), (__le16 __user *) buf);		buf += 2;		pos += 2;		cnt -= 2;	} 	while (cnt >= 4) {		unsigned int val;		pci_user_read_config_dword(dev, pos, &val);		__put_user(cpu_to_le32(val), (__le32 __user *) buf);		buf += 4;		pos += 4;		cnt -= 4;		cond_resched();	} 	if (cnt >= 2) {		unsigned short val;		pci_user_read_config_word(dev, pos, &val);		__put_user(cpu_to_le16(val), (__le16 __user *) buf);		buf += 2;		pos += 2;		cnt -= 2;	} 	if (cnt) {		unsigned char val;		pci_user_read_config_byte(dev, pos, &val);		__put_user(val, buf);		pos++;	} 	pci_config_pm_runtime_put(dev); 	*ppos = pos;	return nbytes;} static ssize_t proc_bus_pci_write(struct file *file, const char __user *buf,				  size_t nbytes, loff_t *ppos){	struct inode *ino = file_inode(file);	struct pci_dev *dev = pde_data(ino);	int pos = *ppos;	int size = dev->cfg_size;	int cnt, ret; 	ret = security_locked_down(LOCKDOWN_PCI_ACCESS);	if (ret)		return ret; 	if (!nbytes)		return 0; 	if (resource_is_exclusive(&dev->driver_exclusive_resource, pos, nbytes)) {		pci_warn_once(dev, "%s: Unexpected write to kernel-exclusive config offset %x",			      current->comm, pos);		add_taint(TAINT_USER, LOCKDEP_STILL_OK);	} 	if (pos >= size)		return 0;	if (nbytes >= size)		nbytes = size;	if (pos + nbytes > size)		nbytes = size - pos;	cnt = nbytes; 	if (!access_ok(buf, cnt))		return -EINVAL; 	pci_config_pm_runtime_get(dev); 	if ((pos & 1) && cnt) {		unsigned char val;		__get_user(val, buf);		pci_user_write_config_byte(dev, pos, val);		buf++;		pos++;		cnt--;	} 	if ((pos & 3) && cnt > 2) {		__le16 val;		__get_user(val, (__le16 __user *) buf);		pci_user_write_config_word(dev, pos, le16_to_cpu(val));		buf += 2;		pos += 2;		cnt -= 2;	} 	while (cnt >= 4) {		__le32 val;		__get_user(val, (__le32 __user *) buf);		pci_user_write_config_dword(dev, pos, le32_to_cpu(val));		buf += 4;		pos += 4;		cnt -= 4;	} 	if (cnt >= 2) {		__le16 val;		__get_user(val, (__le16 __user *) buf);		pci_user_write_config_word(dev, pos, le16_to_cpu(val));		buf += 2;		pos += 2;		cnt -= 2;	} 	if (cnt) {		unsigned char val;		__get_user(val, buf);		pci_user_write_config_byte(dev, pos, val);		pos++;	} 	pci_config_pm_runtime_put(dev); 	*ppos = pos;	i_size_write(ino, dev->cfg_size);	return nbytes;} #ifdef HAVE_PCI_MMAPstruct pci_filp_private {	enum pci_mmap_state mmap_state;	int write_combine;};#endif /* HAVE_PCI_MMAP */ static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd,			       unsigned long arg){	struct pci_dev *dev = pde_data(file_inode(file));#ifdef HAVE_PCI_MMAP	struct pci_filp_private *fpriv = file->private_data;#endif /* HAVE_PCI_MMAP */	int ret = 0; 	ret = security_locked_down(LOCKDOWN_PCI_ACCESS);	if (ret)		return ret; 	switch (cmd) {	case PCIIOC_CONTROLLER:		ret = pci_domain_nr(dev->bus);		break; #ifdef HAVE_PCI_MMAP	case PCIIOC_MMAP_IS_IO:		if (!arch_can_pci_mmap_io())			return -EINVAL;		fpriv->mmap_state = pci_mmap_io;		break; 	case PCIIOC_MMAP_IS_MEM:		fpriv->mmap_state = pci_mmap_mem;		break; 	case PCIIOC_WRITE_COMBINE:		if (arch_can_pci_mmap_wc()) {			if (arg)				fpriv->write_combine = 1;			else				fpriv->write_combine = 0;			break;		}		/* If arch decided it can't, fall through... */		fallthrough;#endif /* HAVE_PCI_MMAP */	default:		ret = -EINVAL;		break;	} 	return ret;} #ifdef HAVE_PCI_MMAPstatic int proc_bus_pci_mmap(struct file *file, struct vm_area_struct *vma){	struct pci_dev *dev = pde_data(file_inode(file));	struct pci_filp_private *fpriv = file->private_data;	resource_size_t start, end;	int i, ret, write_combine = 0, res_bit = IORESOURCE_MEM; 	if (!capable(CAP_SYS_RAWIO) ||	    security_locked_down(LOCKDOWN_PCI_ACCESS))		return -EPERM; 	/* Skip devices with non-mappable BARs */	if (dev->non_mappable_bars)		return -EINVAL; 	if (fpriv->mmap_state == pci_mmap_io) {		if (!arch_can_pci_mmap_io())			return -EINVAL;		res_bit = IORESOURCE_IO;	} 	/* Make sure the caller is mapping a real resource for this device */	for (i = 0; i < PCI_STD_NUM_BARS; i++) {		if (dev->resource[i].flags & res_bit &&		    pci_mmap_fits(dev, i, vma,  PCI_MMAP_PROCFS))			break;	} 	if (i >= PCI_STD_NUM_BARS)		return -ENODEV; 	if (fpriv->mmap_state == pci_mmap_mem &&	    fpriv->write_combine) {		if (dev->resource[i].flags & IORESOURCE_PREFETCH)			write_combine = 1;		else			return -EINVAL;	} 	if (dev->resource[i].flags & IORESOURCE_MEM &&	    iomem_is_exclusive(dev->resource[i].start))		return -EINVAL; 	pci_resource_to_user(dev, i, &dev->resource[i], &start, &end); 	/* Adjust vm_pgoff to be the offset within the resource */	vma->vm_pgoff -= start >> PAGE_SHIFT;	ret = pci_mmap_resource_range(dev, i, vma,				  fpriv->mmap_state, 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_obj(*fpriv); 	if (!fpriv)		return -ENOMEM; 	fpriv->mmap_state = pci_mmap_io;	fpriv->write_combine = 0; 	file->private_data = fpriv;	file->f_mapping = iomem_get_mapping(); 	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 */ static const struct proc_ops proc_bus_pci_ops = {	.proc_lseek	= proc_bus_pci_lseek,	.proc_read	= proc_bus_pci_read,	.proc_write	= proc_bus_pci_write,	.proc_ioctl	= proc_bus_pci_ioctl,#ifdef CONFIG_COMPAT	.proc_compat_ioctl = proc_bus_pci_ioctl,#endif#ifdef HAVE_PCI_MMAP	.proc_open	= proc_bus_pci_open,	.proc_release	= proc_bus_pci_release,	.proc_mmap	= proc_bus_pci_mmap,#ifdef HAVE_ARCH_PCI_GET_UNMAPPED_AREA	.proc_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); 	/* only print standard and ROM resources to preserve compatibility */	for (i = 0; i <= PCI_ROM_RESOURCE; i++) {		resource_size_t start, end;		pci_resource_to_user(dev, i, &dev->resource[i], &start, &end);		seq_printf(m, "\t%16llx",			(unsigned long long)(start |			(dev->resource[i].flags & PCI_REGION_FLAG_MASK)));	}	for (i = 0; i <= PCI_ROM_RESOURCE; i++) {		resource_size_t start, end;		pci_resource_to_user(dev, i, &dev->resource[i], &start, &end);		seq_printf(m, "\t%16llx",			dev->resource[i].start < dev->resource[i].end ?			(unsigned long long)(end - start) + 1 : 0);	}	seq_putc(m, '\t');	if (drv)		seq_puts(m, drv->name);	seq_putc(m, '\n');	return 0;} static const struct seq_operations proc_bus_pci_devices_op = {	.start	= pci_seq_start,	.next	= pci_seq_next,	.stop	= pci_seq_stop,	.show	= show_device}; static struct proc_dir_entry *proc_bus_pci_dir; static int __pci_proc_attach_bus(struct pci_bus *bus){	struct proc_dir_entry *dir;	char name[16]; 	lockdep_assert_held(&pci_proc_lock); 	if (!proc_initialized)		return -EACCES; 	if (bus->procdir)		return 0; 	if (pci_proc_domain(bus))		sprintf(name, "%04x:%02x", pci_domain_nr(bus), bus->number);	else		sprintf(name, "%02x", bus->number); 	dir = proc_mkdir(name, proc_bus_pci_dir);	if (!dir)		return -ENOMEM; 	bus->procdir = dir; 	return 0;} int pci_proc_attach_device(struct pci_dev *dev){	struct pci_bus *bus = dev->bus;	struct proc_dir_entry *entry;	char name[16];	int ret; 	guard(mutex)(&pci_proc_lock); 	if (dev->procent)		return 0; 	ret = __pci_proc_attach_bus(bus);	if (ret)		return ret; 	sprintf(name, "%02x.%x", PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));	entry = proc_create_data(name, S_IFREG | S_IRUGO | S_IWUSR,				 bus->procdir, &proc_bus_pci_ops, dev);	if (!entry)		return -ENOMEM; 	proc_set_size(entry, dev->cfg_size);	dev->procent = entry; 	return 0;} int pci_proc_detach_device(struct pci_dev *dev){	guard(mutex)(&pci_proc_lock);	proc_remove(dev->procent);	dev->procent = NULL;	return 0;} int pci_proc_detach_bus(struct pci_bus *bus){	guard(mutex)(&pci_proc_lock);	proc_remove(bus->procdir);

Lines 1–500 of 523.