Device driver to access PCI configuration space.

Rogier Wolff (R.E.Wolff@BitWizard.nl)
Thu, 3 Jul 1997 15:14:12 +0200 (MET DST)


Hi everyone,

Here is a device driver to access PCI configuration space.

The PCI spec says that there are 256 configuration space
registers. The first 64 are standardised, and the kernel prints
them in /proc/pci. When you have a chip that has more configuration
space registers than the first 64, you can use this device to read
them all. You can also change them from user space by writing to
the device. Intel PCI chipsets usually have a bunch of registers
in the "chip specific" region, and even some undocumented registers
(0xf8, 0xf9).

I am having trouble with a video chip not getting enough bandwidth to
dump a video frame into memory. The intel documentation about my
chipset talks about an "MTT" register, and I wanted to know what it
was set at, and see if changing it would help.

Linus: I would like this to go into the mainstream kernel. Changes
seem localized enough. I think I followed the Linux coding style close
enough. If not, tell me.

Regards,

Roger Wolff.

diff -ur linux-2.1.43.clean/CREDITS linux/CREDITS
--- linux-2.1.43.clean/CREDITS Tue Jul 1 09:15:06 1997
+++ linux/CREDITS Thu Jul 3 15:11:19 1997
@@ -1607,10 +1607,11 @@
S: Finland

N: Roger E. Wolff
-E: wolff@dutecai.et.tudelft.nl
+E: R.E.Wolff@BitWizard.nl
D: Written kmalloc/kfree
-S: Oosterstraat 23
-S: 2611 TT Delft
+D: Written pci_cfg device driver
+S: van Bronckhorststraat 12
+S: 2612 XV Delft
S: The Netherlands

N: Frank Xia
diff -ur linux-2.1.43.clean/Documentation/Configure.help linux/Documentation/Configure.help
--- linux-2.1.43.clean/Documentation/Configure.help Tue Jul 1 09:15:07 1997
+++ linux/Documentation/Configure.help Thu Jul 3 14:01:33 1997
@@ -651,6 +651,12 @@
servicing. Say Y here to enable the serial driver to take advantage
of those special I/O ports.

+Support for the PCI configuration space device
+CONFIG_PCI_CFG
+ /proc/pci shows you most of the interesting things from the PCI
+ configuration space in your PCI chips, but this device driver
+ allows you to view AND CHANGE all bytes in the configuration space.
+
Support the Bell Technologies HUB6 card
CONFIG_HUB6
Say Y here to enable support in the dumb serial driver to support
diff -ur linux-2.1.43.clean/drivers/char/Config.in linux/drivers/char/Config.in
--- linux-2.1.43.clean/drivers/char/Config.in Mon May 12 19:35:39 1997
+++ linux/drivers/char/Config.in Thu Jul 3 13:58:18 1997
@@ -17,6 +17,9 @@
bool ' Support the Bell Technologies HUB6 card' CONFIG_HUB6
bool ' Console on serial port' CONFIG_SERIAL_CONSOLE
fi
+
+tristate 'PCI configuration space device' CONFIG_PCI_CFG
+
bool 'Non-standard serial port support' CONFIG_SERIAL_NONSTANDARD
if [ "$CONFIG_SERIAL_NONSTANDARD" = "y" ]; then
tristate 'Digiboard Intelligent Async Support' CONFIG_DIGIEPCA
diff -ur linux-2.1.43.clean/drivers/char/Makefile linux/drivers/char/Makefile
--- linux-2.1.43.clean/drivers/char/Makefile Tue Jul 1 09:15:11 1997
+++ linux/drivers/char/Makefile Thu Jul 3 13:57:36 1997
@@ -67,6 +67,14 @@
endif
endif

+ifeq ($(CONFIG_PCI_CFG),y)
+L_OBJS += pci_cfg.o
+else
+ ifeq ($(CONFIG_PCI_CFG),m)
+ M_OBJS += pci_cfg.o
+ endif
+endif
+
ifeq ($(CONFIG_DIGIEPCA),y)
L_OBJS += epca.o
else
diff -ur linux-2.1.43.clean/drivers/char/mem.c linux/drivers/char/mem.c
--- linux-2.1.43.clean/drivers/char/mem.c Tue Jul 1 09:15:11 1997
+++ linux/drivers/char/mem.c Thu Jul 3 14:11:43 1997
@@ -34,6 +34,9 @@
#ifdef CONFIG_PCWATCHDOG
void pcwatchdog_init(void);
#endif
+#ifdef CONFIG_PCI_CFG
+int pci_cfg_init(void);
+#endif

static long do_write_mem(struct file * file,
void *p, unsigned long realp,
@@ -537,6 +540,9 @@
#endif
#if CONFIG_ISDN
isdn_init();
+#endif
+#ifdef CONFIG_PCI_CFG
+ pci_cfg_init();
#endif
#ifdef CONFIG_FTAPE
ftape_init();
diff -ur linux-2.1.43.clean/drivers/char/pci_cfg.c linux/drivers/char/pci_cfg.c
--- linux-2.1.43.clean/drivers/char/pci_cfg.c Thu Jul 3 14:37:45 1997
+++ linux/drivers/char/pci_cfg.c Thu Jul 3 15:03:14 1997
@@ -0,0 +1,242 @@
+/*
+ * pci_cfg.c V1.00 -- Works as far as it has been tested.
+ *
+ * This patch was sponsored by BitWizard B.V., the Netherlands.
+ * Contact BitWizard if you require a linux device driver or other
+ * linux related support.
+ *
+ * Copyright (C) by R.E.Wolff -- R.E.Wolff@BitWizard.nl
+ *
+ * I prefer if you try to contact me if you have enhancements,
+ * instead of forking off a different branch.....
+ *
+ * date by what
+ * Written: Jul 3 1997 REW Initial revision.
+ * changed: Jul 3 1997 REW Cleanup for publication.
+ *
+ *
+ *
+ * who-is-who:
+ * initials full name Email address
+ * REW Roger E. Wolff R.E.Wolff@BitWizard.nl
+ *
+ * This file implements a kernel driver for pci configuration space.
+ * You can read/write the configuration space of PCI chips this way.
+ *
+ * insmod with
+ * pci_cfg_major=<some other major number>
+ * busno=<busnumber>
+ * for access to PCIbus != 0. Or use the ioctl to change the bus number.
+ *
+ * If anybody has a better idea, which doesn't require more than one
+ * major number, I would appreciate it if you share it with me.
+ *
+ * You can cut-and-paste this into a shell to make the devices.
+ * skip the "cd /dev" if you don't want them there.
+
+ cd /dev
+ bash
+ for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \
+ 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
+ do
+ for j in 0 1 2 3 4 5 6 7
+ do
+ m=`expr $i \* 8 + $j`
+ mknod pci_cfg_"$i"_"$j" c 63 $m
+ done
+ done
+
+ *
+ * Bugs and restrictions:
+ * - The IOCTL hasn't been tested yet.
+ * - writing to config space has only been tested superficially.
+ * - the boardno variable is global, so if you use the ioctl, you
+ * might interfere with other programs accessing this driver.
+ * */
+
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ ************************************************************* */
+
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/bios32.h>
+
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/segment.h>
+#include <linux/major.h>
+
+#ifndef PCI_CFG_MAJOR
+#define PCI_CFG_MAJOR 63 /* Allocated as "experimental". HPA can submit
+ the change to major.h whenever he assigns
+ a real major number to this device. */
+#endif
+
+
+/* Should I make a header file just to export this? */
+#define PCI_CFG_CHANGE_BUS 0x5043 /* PC */
+
+
+
+int pci_cfg_major=PCI_CFG_MAJOR;
+int busno=0;
+
+static long pci_cfg_write(struct inode * inode, struct file * file,
+ const char * buf, unsigned long count);
+static long pci_cfg_read (struct inode * inode, struct file * file,
+ char * buf, unsigned long count);
+static int pci_cfg_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static int pci_cfg_open(struct inode * inode, struct file * file);
+static int pci_cfg_release(struct inode * inode, struct file * file);
+
+
+static struct file_operations pci_cfg_fops = {
+ NULL, /* lseek */
+ pci_cfg_read, /* read */
+ pci_cfg_write, /* write */
+ NULL, /* readdir */
+ NULL, /* select */
+ pci_cfg_ioctl, /* ioctl */
+ NULL, /* mmap */
+ pci_cfg_open, /* open */
+ pci_cfg_release /* release */
+};
+
+
+
+static long pci_cfg_write(struct inode * inode, struct file * file,
+ const char * buf, unsigned long count)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ unsigned char ch;
+ int i, err;
+
+ if (file -> f_pos > 256)
+ return 0;
+
+ if (file->f_pos + count > 256)
+ count = 256 - file->f_pos;
+
+ for (i=0;i < count;i++) {
+ err = get_user (ch, buf++);
+ if (err) return err;
+ pcibios_write_config_byte (busno, minor, file->f_pos++, ch);
+ }
+
+ return count;
+}
+
+
+
+static long pci_cfg_read (struct inode * inode, struct file * file, char * buf, unsigned long count)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ unsigned char ch;
+ int i, err;
+
+ if (file -> f_pos > 256)
+ return 0;
+
+ if (file->f_pos + count > 256)
+ count = 256 - file->f_pos;
+
+ for (i=0;i < count;i++) {
+ pcibios_read_config_byte (busno, minor, file->f_pos++, &ch);
+ err = put_user (ch, buf++);
+ if (err) return err;
+ }
+
+ return count;
+}
+
+
+static int pci_cfg_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case PCI_CFG_CHANGE_BUS:
+ busno = arg;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+
+static int pci_cfg_open(struct inode * inode, struct file * file)
+{
+#ifdef DEBUG
+ unsigned int minor = MINOR(inode->i_rdev);
+#endif
+
+ pr_debug ("pci_cfg%d: open.\n",minor);
+ MOD_INC_USE_COUNT;
+ return (0);
+}
+
+
+static int pci_cfg_release(struct inode * inode, struct file * file)
+{
+#ifdef DEBUG
+ unsigned int minor = MINOR(inode->i_rdev);
+#endif
+ MOD_DEC_USE_COUNT;
+ pr_debug ("pci_cfg%d: close.\n", minor);
+ return 0;
+}
+
+
+#ifdef MODULE
+#define module_init pci_cfg_init
+#endif
+
+
+int pci_cfg_init (void)
+{
+ printk ("PCI configuration space access device by R.E.Wolff@BitWizard.nl\n");
+
+ /* then see if the MAJOR number is free, .... */
+ if (register_chrdev(pci_cfg_major,"pci_cfg", &pci_cfg_fops)) {
+ pr_debug("pci_cfg: unable to get major %d\n", pci_cfg_major);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+#ifdef MODULE
+
+void cleanup_module(void)
+{
+ unregister_chrdev(pci_cfg_major, "pci_cfg");
+ pr_debug ("pci_cfg: cleanup: Bye bye...\n");
+}
+#endif
+
+
+