Programmed I/O (PIO)を用いた簡単なPCIデバイスドライバ


はじめに

ディスク上のディレクトリ/proc/pciにはLinuxが検出できたこの計算機にある PCIデバイスの情報が入っています。 /proc/ioportsや/proc/interruptsには登録されたデバイスのI/Oアドレスや 割り込み番号が入っています。

デバイスドライバで使用されるpci_find_deviceのようなpciカーネル関数について 見てみましょう。

pciskeletonデバイスドライバコード

includeファイル

#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;

open/close

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;
}

ioctl

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;
}

file_operation構造体の定義

static struct file_operations pciskeleton_fops = {
ioctl:    pciskeleton_ioctl,
open:     pciskeleton_open,
release:  pciskeleton_release,
};

init_module

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;
}

cleanup_module

void cleanup_module(void)
{
  unregister_chrdev(PCI_MAJOR,CARD_NAME);
  printk("CC7x00 device driver has been removed...\n");
}

Makefile

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)

pciskeletonデバイスドライバを使ったユーザプログラム

#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);
}