Programmed I/O (PIO)を用いた簡単なPCIデバイスドライバ
ディスク上のディレクトリ/proc/pciにはLinuxが検出できたこの計算機にある
PCIデバイスの情報が入っています。
/proc/ioportsや/proc/interruptsには登録されたデバイスのI/Oアドレスや
割り込み番号が入っています。
デバイスドライバで使用されるpci_find_deviceのようなpciカーネル関数について
見てみましょう。
- /usr/src/linux/drivers/pci/pci.c
- pci_find_device(unsigned int vendor, unsigned int device, const struct pci_dev *from) :
* @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids
* @device: PCI device id to match, or %PCI_ANY_ID to match all vendor ids
* @from: Previous PCI device found in search, or %NULL for new search.
もしPCIデバイスがvendorとdeviceが一致して見付けられたら、
返り値としてそのデバイスの構造体が返される。見つからなかったら、
NULLが返る。NULLが返らなかったら、さらにその位置からサーチを続ける
ことができます。
- int pci_enable_device(struct pci_dev *dev);
@dev: PCI device to be initialized
pci_dev構造体は下記に説明されています。
ドライバを使用する前に初期化します。
- /usr/src/linux/include/linux/pci.h:
pci_dev構造体が定義されています。これはPCIを記述する基本的な構造体です。
pci_dev構造体の中身がkernel2.4.xから変更されました。例えば、
resource構造体が含まれるようになって、デバイスのベースアドレス取得の
方法が変わりました。
PCIインターフェースの存在を確認するための関数が用意されています。
コンフィギュレーション空間にread/writeアクセスするために
次のような関数が用意されています。
- int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
- int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
- int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
- int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
- int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
- int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
いままで使用して来たpcibios_はpci_になり、kernel2.4.xからは
上記のように変更されました。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <inux/delay.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <asm/ioctl.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "pciskeleton.h"
pciskeleton.hの中身は次のとおりです。
struct mydevice {
int csr; /* Control/Status Register */
int cnafr; /* Crate address or NAF Register */
int datar; /* Data Register */
int exer; /* EXEcute Register */
};
#define RESET 0x40
#define IOC_RESET _IO('ps', 1)
#define IOC_GET_CSR _IOR('ps', 2, int)
#define PCI_MAJOR 37
//Define our PCI vendor ID
#define PCI_VENDOR_MYDEV 1
//PCI identity for the card.
#define PCI_DEVICE_ID_MYDEV 0xcc77
// Define device name
#define CARD_NAME "pciskeleton"
struct devinfo {
char *io_base;
char *mem_base;
unsigned int irq;
char *name;
struct mydevice dev;
int driver_use_1;
int driver_use_2;
};
static struct devinfo device_data;
skeletonデバイスドライバと同じです。特に処理はありません。
static int pciskeleton_open(struct inode *inode, struct file * file)
{
MOD_INC_USE_COUNT;
return 0;
}
static void pciskeleton_release(struct inode * inode, struct file * file)
{
MOD_DEC_USE_COUNT;
}
static int pciskeleton_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int data;
device_data.dev.csr = (int)device_data.io_base;
switch ( cmd ) {
case IOC_RESET :
outl(RESET, device_data.dev.csr);
break;
case IOC_GET_CSR:
data = inl(device_data.dev.csr);
if (copy_to_user((int *) arg, &data,
sizeof(int)))
return -EFAULT;
break;
default:
return -EINVAL;
}
return 0;
}
static struct file_operations pciskeleton_fops = {
ioctl: pciskeleton_ioctl,
open: pciskeleton_open,
release: pciskeleton_release,
};
int init_module(void)
{
u8 w;
struct pci_dev *pcidev=NULL;
int status;
if(!pci_present())
return -ENODEV;
まずは操作すべき目的のPCIデバイスがあるかどうかチェックします。
この場合は、CAMACインターフェースCC7700の開発元である東陽テクニカ社の
IDとこのデバイスのIDを照合します。
pcidev = pci_find_device(PCI_VENDOR_MYDEV, PCI_DEVICE_ID_MYDEV, pcidev);
if(pcidev == NULL)
return -ENODEV;
status = pci_enable_device(pcidev);
if(status)
return status;
device_data.io_base = pci_resource_start(pcidev, 0);
device_data.mem_base = pci_resource_start(pcidev, 1);
CAMACインターフェースのIRQ番号やベースアドレスを得る方法は上記のような
仕方の他に、下記のように直接デバイスから情報を得る方法もあります。
pci_read_config_word(pcidev, PCI_INTERRUPT_LINE, &w);
printk("CC7x00:PCI_INTERRUPT_LINE = %x\n", w);
また場合によっては変更する必要が生じた場合、変更する手順は次の通りです。
device_data.io_base = 0x310;
pci_write_config_dword(pcidev, PCI_BASE_ADDRESS_0, (int)device_data.io_base);
printk("CC7x00:IO_BASE = %x\n", device_data.io_base);
次はこのドライバの登録をします。そしてドライバを正常終了させるときは
返り値を0にします。
status = register_chrdev(PCI_MAJOR,CARD_NAME, &pciskeleton_fops);
if (status) {
printk("unable to get major %d for %s\n", PCI_MAJOR, CARD_NAME);
return status;
}
printk("CC7x00 device driver has been installed...\n");
return 0;
}
void cleanup_module(void)
{
unregister_chrdev(PCI_MAJOR,CARD_NAME);
printk("CC7x00 device driver has been removed...\n");
}
pciskeletonデバイスドライバとユーザプログラムのために
Makefileを作ってみましょう。
INCDIR = -I/usr/src/linux/include
VERSIONINC = -include /usr/src/linux/include/linux/modversions.h
CFLAGS = -O2 -Wall -c -D__KERNEL__ -DMODULE -Wall $(INCDIR) $(VERSIONINC)
DRIVER = pciskeleton
TEST = test_pciskeleton
all: $(DRIVER).o $(TEST)
$(DRIVER).o: $(DRIVER).c $(DRIVER).h
gcc $(CFLAGS) $(DRIVER).c
device:
mknod -m 666 /dev/$(DRIVER) c 37 0
$(TEST): $(TEST).c $(DRIVER).h
gcc -o $(TEST) $(TEST).c
clean:
rm -f *.o *~ core $(TEST)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/wait.h>
#include "pciskeleton.h"
main() {
int fd;
int data;
fd = open("/dev/pciskeleton", O_RDWR);
if( fd == -1) {
printf("open error...\n");
exit(0);
}
ioctl(fd, IOC_RESET);
ioctl(fd, IOC_GET_CSR, &data);
printf("Contents of CSR = %x\n", data);
close(fd);
}