Linux による USB デバイスドライバ作成と制御インタフェース開発
Development of a USB device driver working on Linux and Control Interface

福谷武司・小谷章二・高橋智一
Takeshi Fukutani, Shoji Kodani and Tomokazu Takahashi
近年、PC‑UNIX（主として Linux）は、携帯電話をはじめとする組み込み分野用途に使われることが増えつつある。この組み込み用途の目的として、最近のネットワークインタフェースやＵＳＢ等のシリアルインタフェースという複雑なプロトコルを持つ通信手段に対して、Ｌｉｎｕｘの資産を流用しようというねらいが垣間見られる。サーバ技術が脚光を浴びることが多いＬｉｎｕｘではあるが、ここで改めてハードウェア制御ができるという側面に注目してみたい。ひいては、ＬｉｎｕｘＯＳにおけるＵＳＢドライバとはどういうものかを確認し、外部ハードウェアとの連携を如何にとるべきなのか、基礎事項を確認し、実際に作成した基本的なＵＳＢ対応のキャラクタ型デバイスドライバの作成方法と、その動作例について述べる。
Recently, it’s becoming more popular to utilize Linux for controlling hardware like a cellular phone. Integrated protocol is not easy to develop, so people tend to make use of a ready-to-use environment. Most of the time, Linux serves
the role as a server such as a filesever, webserver and so on, but we focus on the aspect of hardware controlling instruments. We present the mechanism of a USB device driver in a Linux system, and pick up some points that should betaken into consideration.

1. はじめに取り上げ周辺装置とのデータ授受を行うＵＳＢデバイ

近年、多機能携帯電話やデジタル家電など、便利さスのインターフェース開発を行ったので報告する。

を追求して、色々な製品が登場している。またさらに、

2. 開発環境

それにも増して人間が使いやすいようなボタン操作や今回の開発環境構成は、以下の通りである。

画面上の指示が必要であったり、パソコンとのデータ授受などで USB や IEEE1394 など多様なデータのやりとりに対応した製品開発へのニーズが高まってきて【開発マシン】

いる。こういった状況の下、効率良く製品の操作系イ・ PC(Celeron300MHz,ﾒﾓﾘ 256MB,HDD:12GB)

ンタフェースを実現するには、何らかのサブルーチン・ OS: Vine Linux 3.2（カーネル 2.4.31）群を導入することが必要となってくる。

・ C コンパイラ：GCC 3.3.2

このサブルーチン群は、現在、有料および無料のものがあるが、有料のツールに関して言えば、その効果を開発前の段階で、判別することができないため、採【対象 USB デバイス】

用に踏み切るのはコスト的なリスクが伴う。

・ USB コントローラ：USBN9604

このサブルーチン群の動向としては、制御ＣＰＵそれ（ナショナル・セミコンダクター社）

ぞれに合わせたアセンブラプログラムが従来使われて・ コントローラ操作マイコン：H8/3048

きたが、開発効率向上のニーズから、サブルーチンを回路構成と装置外観を図１と図２に示す。

どのＣＰＵに対してもＣ言語のプログラムを使い、共通化する考え方が定着してきた。このように基本的なハードウェアとのやりとりの仕組みを体系化することにより、使いやすくパッケージングしたＯＳのひとつである、Ｌｉｎｕｘにつてい具体的なプログラム例を1

作方法である。この際、ハードウェアは、デバイスフ
ァイル（別名：スペシャルファイル）という、I/O をファイルに見立てたものをあらかじめ作成及び登録（その方法は後述）することが必要である。これに対して、
I/O データの定義関数を通して読み書きを行うことで、操作が可能となっている。この様子を図３に示す。

図１ 回路構成

図３ デバイスドライバの役割

デバイスドライバは、キャラクタ型、ブロック型、ネットワーク型の３つの型があるが、ここではキャラクタ型のデバイスドライバを扱う。

3.2 Ｌｉｎｕｘデバイスドライバ

図２ 装置外観

3.2.1 Linux デバイスドライバの様式

3. ＬｉｎｕｘにおけるＩ／Ｏ制御

Ｌｉｎｕｘのデバイスドライバの基本形を図４に示

3.1 カーネル空間とユーザ空間

す。

Linux においては、カーネル空間とユーザ空間が存在する。カーネル空間とは、ハードウェア管理に使われるアドレスを直接取り扱うことのできるモードで、外部機器とつながるＩ／Ｏは、このカーネル空間内でのやりとりによってのみ操作が可能である。カーネル空間は、ユーザプログラムが直接操作することはできないため、カーネル操作用の低レベル関数（read(),write(),ioctl()など）をデバイスドライバプログラムに記述して、カーネル空間に常駐させる。ユーザ空間からカーネル空間への操作は、派生した定義関数をユーザ空間のプログラムから呼び出すことで実行される。そのデバイスドライバ内の低レベル関数から

図４ デバイスドライバの基本形派生した定義関数をユーザ空間のプログラムから呼び出すことが唯一のユーザ空間からカーネル空間への操

2

NULL, /* skeleton_llseek */skeleton_read, /* skeleton_read */skeleton_write, /* skeleton_write */NULL, /* skeleton_readdir */NULL, /* skeleton_poll */skeleton_ioctl, /* skeleton_ioctl */NULL, /* skeleton_mmap */skeleton_open, /* skeleton_open */NULL, /* skeleton_flush */skeleton_release, /* skeleton_release */NULL, /* skeleton_fsync */NULL, /* skeleton_fasync */NULL, /* skeleton_check_media_change */NULL, /* skeleton_revalidate */NULL /* skeleton_lock */#else /* for LINUX_VERSION_CODE 2.4.0 and later*/
THIS_MODULE, /* struct module *owner;*/
NULL, /* skeleton_llseek */skeleton_read, /* skeleton_read */skeleton_write, /* skeleton_write */NULL, /* skeleton_readdir */NULL, /* skeleton_poll */skeleton_ioctl, /* skeleton_ioctl */NULL, /* skeleton_mmap */skeleton_open, /* skeleton_open */NULL, /* skeleton_flush */skeleton_release, /* skeleton_release */NULL, /* skeleton_fsync */NULL, /* skeleton_fasync */NULL, /* skeleton_lock */NULL, /* skeleton_readv */NULL /* skeleton_writev */#endif};

基本的なＣ言語プログラムソースを次に示す。
#include <linux/errno.h>#include <linux/fs.h>#include <linux/module.h>#include <linux/mm.h>#include <asm/uaccess.h>#define CASE1 1#define CASE2 2
static unsigned int counter = 0;
static char string [132];
static int data;
static int skeleton_open (struct inode *inode,
struct file *file){
MOD_INC_USE_COUNT;
return 0;}
static int skeleton_release (struct inode *inode,struct file *file){
MOD_DEC_USE_COUNT;
return 0;}
static ssize_t skeleton_read (struct file *file,char *buf, size_t count, loff_t *ppos){
int len, err;
if( counter <= 0 )return 0;

int init_module (void){
int i;

err = copy_to_user(buf,string,counter);if (err != 0)
return -EFAULT;

i = register_chrdev (63, "test", & test_fops);
if (i != 0) return - EIO;

len = counter;counter = 0;

return 0;}

return len;void cleanup_module (void){
unregister_chrdev (63, "test");}}static ssize_t skeleton_write (struct file *file,const char *buf, size_t count, loff_t *ppos){
int err;

図５ デバイスドライバの基本プログラム

err = copy_from_user(string,buf,count);
if (err != 0)
return -EFAULT;
counter += count;

3.2.2 Linux デバイスドライバのコンパイルと組み込

return count;み}Ｌｉｎｕｘのデバイスドライバをコンパイルし、組

static int skeleton_ioctl(struct inode *inode,struct file *file,unsigned int cmd, unsigned long arg){
int retval = 0;

み込むには次のように行う。まず、プログラムをＧＣ
Ｃ（ＧＮＵコンパイラコレクション。フリーのＣ言語

switch ( cmd ) {case CASE1:/* for writing data to arg */if (copy_from_user(&data, (int *) arg,sizeof(int)))
return -EFAULT;
break;
case CASE2:/* for reading data from arg */if (copy_to_user((int *) arg, &data,sizeof(int)))
return -EFAULT;
break;
default:
retval = -EINVAL;}
return retval;}

コンパイラを含む様々な言語のコンパイラをセットにしたもの）にてコンパイルする。その際にオブジェク
トファイル作成にとどめる。具体的には、シェル（＝コマンドライン）から次のようなコマンドを実行する。
$ gcc –Wall –O2 -c –D__KERNEL__ -DMODULE test.c

ここで、＄はユーザー権限でコマンドラインを入力する際の入力プロンプトを表している。gcc に続く文字

static struct file_operations skeleton_fops ={#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)

列は、コンパイルオプションであるが、-Wall は、ワ3

ーニング表示のオプション、-O2 は最適化レベルをクウントアップされ標準出力（コンソール）に表示されラス２までにとどめておくためのオプション、-c は実る。このプログラムだけではハードウェアにアクセス行ファイルを作らずオブジェクトファイルまでを作成しているわけではないが、カーネル空間に属するデバするオプション、-D はＣ言語の#define にあたる変数

イスファイルをソフトウェアで呼ぶ動作の実証になっ定義のオプションである。ここでは__KERNEL__といている。これはつまり、デバイスファイルの先にハーう変数と MODULE という変数を定義したことを示す。

ドウェアの処理を配置できるところを、あえて標準出作成したオブジェクトファイルはモジュールと呼ばれ、力をターゲットとして、その基本動作を実行し、ＯＳこのモジュールをカーネル空間とユーザ空間の仲立ち内部の不具合が無いことを確認するプログラムとしてとして組み込む作業を行う。Ｌｉｎｕｘのデバイスド活用できる。

ライバはデバイスファイルで管理されており、既存のデバイスファイルを使用することもできるが、全く新

4. USB 用 Linux デバイスドライバ

規のデバイスとして登録することもできる。その際に4.1 USB デバイスドライバに必要な事前知識は次のようなコマンドを管理者権限にて入力し、デバＵＳＢ用Ｌｉｎｕｘのデバイスドライバを作成する

イスファイルを作成する。

にあたり、ＵＳＢの内部的な動作やコントローラデバ# mknod –m 444 /dev/test u 63 0

イスＵＳＢＮ９６０４について説明する。

ここで＃は管理者権限で入力する際の入力プロンプ
4.1.1 エンドポイント・パイプ・インタフェース

トを表している。最後の２つの数字はＯＳで用意されエンドポイントとは、シリアル通信であるＵＳＢにているデバイス番号で１番目がメジャー番号、２番目おいて、パラレル通信のポートに似せた、一種の仮想がマイナー番号と呼ばれている。メジャー番号は既存ポートである。そのエンドポイントへのデータ出入口のデバイスとの競合を避けて割り当てなければならなとしての配線的役割をするものとしてパイプが存在す

いが、６０番から６３番は、実験用の番号として使うる。エンドポイントはデバイス上では通し番号をあらことが決められている。続いて、ドライバの組み込みかじめ割り振られている。この数はコントローラに設は次のようなコマンドを入力する。

定を送ることで決まる。参考までに、今回使用したＵ# insmod test.o

ＳＢＮ９６０４のエンドポイントの数は最大で６であこれにより、ドライバテーブルへの登録が行われる。

る。パイプはエンドポイントよりも通常少ない本数をこれでデバイスドライバの組み込みが完了し、使用で持っており、エンドポイントへのつなぎ換えのようなきる状態となった。逆に登録を解除する場合は、イメージで、データ入出力の切替を行う。最初から数# rmmod test.oが決まっているエンドポイントに対して、パイプは機としてドライバテーブルへの登録を解除する。

能ベースで数が決まる。実際のデータ要求／転送は、パイプに対して行われる。このエンドポイントとパイ

3.2.3 Linux デバイスドライバの使用

プにおける機能ベースの数本のまとまりはインタフェ

図３−３のプログラムは次のようなシェルコマンドースと呼ばれる。データのやりとりは、ホスト側からでその動作を確認することができる。

御用聞きのようにターゲット側になされる。これをポ$ cat /dev/testーリングという。デバイス側からこれを要求することはない。

cat コマンドにより、標準出力への書き込みに対応するデバイスドライバの関数（図５内 skeleton_write関数）が呼び出される。その結果、変数 counter がカ4

4.1.2 ディスクリプタ
ディスクリプタとはターゲット機器の設定情報であり、ターゲット機器内のＵＳＢをコントロールするチップにファームウェアとして書き込まれている。
（今回の場合はＨ８／３０４８）その構造は図６のような親子関係がある。エンドポイント、インタフェースに関しては、前節に説明した通りだが、さらに大きな括りとして、デバイスの構成としてのコンフィグレーシ
ョンディスクリプタとデバイス全般の一般的なＩＤ情報を含むデバイスディスクリプタが存在する。これらの設定情報は、ＵＳＢ機器が端子に接続されたときに、デバイスドライバを呼び出す機能として必要であり、デバイス側のファームウェアに書き込まれている。ホストマシンはターゲットデバイスが接続したときに、図６ ＵＳＢ用Ｌｉｎｕｘデバイスドライバの基本形これらの情報を受け取り、その後の通信を支障なく行実際のソースは長くなるので抜粋とその役目を記載する。

うようにセットアップされる。

:#define USBTEST_MINOR 32:

最初の方で、見られる define 文でマイナー番号の記載ができる。ＵＳＢデバイスドライバのメジャー番号は１８０であるが、これは決まっているのでデバイスドライバには書かれない。また、マイナー番号は１６の倍数にする決まりがある。
static int usbtest_set_led(usbtest_data_t* pdata,int led){
int r, rlen,i;unsigned long int pipe;
unsigned char buf[2];

図６ ディスクリプタの体系

if (pdata->dev == NULL) {return -1;}
buf[0] = (unsigned char)(0x03);
buf[1] = (unsigned char)(led & 0xff);

デバイスドライバにもこれらの情報をＰＣに取り込むためのルーチンが用意されている。

pipe=
usb_sndbulkpipe(pdata->dev,pdata->ep_bulk_out);

4.2 USB 用 Linux デバイスドライバの様式

r = usb_bulk_msg(pdata->dev, pipe, buf, 2,&rlen, HZ*10);

ＵＳＢ用Ｌｉｎｕｘのデバイスドライバにおいても、通常のＬｉｎｕｘデバイスドライバと同様に、if (r < 0) {return -1;}
return 0;

その様式を見ることにする。
}

このコードは、ＬＥＤを点灯するルーチンである。

図７にその基本形を示した。ＵＳＢに関するルー

usb_sndbulkpipe()関数でパイプを作り、送信用としてチンが追加されていることが見て取れる。

設定した上で、usb_bulk_msg()関数で指定したデバイ5

スにデータを送る様子を表している。

コロン（：）を挟んで左にラベル、右に関数名の構成であり、ioctl のラベルと対応する関数に ioctl の役目

static usbtest_data_t* g_usbtest_data;をさせるという意味である。この書き方は、ＧＣＣのstatic struct usb_device_id usbtest_table[] = {{USB_DEVICE(VID_USBTEST,PID_USBTEST)},{}};

機能拡張によるものである。これ以外に、ドット（．
）で始めるＣ９９形式と、単に決められた順番で並べる

MODULE_DEVICE_TABLE (usb, usbtest_table);

形式のものがある。

デバイスディスクリプタと関連するルーチンである。

少々長くなるが、次のコードはＵＳＢデバイス検出

ＶＩＤはベンダーＩＤ、ＰＩＤはプロダクトＩＤであルーチンである。

り、これらの設定値をＰＣに取り込む様子を表している。

static void* usbtest_probe(struct usb_device *dev, unsigned int ifnum,const structusb_device_id *id){
struct usb_interface_descriptor *inf;static int usbtest_ioctl(struct inode *inode,
struct file *file,unsigned int cmd, unsigned long arg){
int led, dip;
usbtest_data_t* pdata;

struct usb_config_descriptor*config;

unsigned int pipe;
int n, intr_interval;if ((dev->descriptor.idVendor != VID_USBTEST) ||(dev->descriptor.idProduct != PID_USBTEST)){
return NULL;

int err = 0, tmp;
int retval = 0;}
if (g_usbtest_data) {printk(KERN_INFO "USBTEST: no more probe.¥n");pdata = file->private_data;
printk("file->private_data=%x¥n",file->private_data);
printk("pdata->ep_bulk_out=%d¥n",pdata->ep_bulk_out);

return NULL;}
config = dev->actconfig;
inf= &config->interface[ifnum].altsetting[0];g_usbtest_data = kmalloc(sizeof(usbtest_data_t), GFP_KERNEL);

switch(cmd) {case USBTEST_SET_LED:
led = (int)arg;
if (usbtest_set_led(pdata, led) < 0)return -EIO;
break;

if (g_usbtest_data == NULL) {printk(KERN_INFO "no memory¥n");return NULL;}
intr_interval = 0;
g_usbtest_data->ep_intr_in = -1;

case USBTEST_GET_DIPSW:
if ((dip = usbtest_get_dipsw(pdata)) < 0)return -EIO;
if(copy_to_user((int*)arg,&dip,sizeof(led)))return -EFAULT;
break;

g_usbtest_data->ep_bulk_out = -1;
g_usbtest_data->ep_bulk_in = -1;
for(n = 0; n < inf->bNumEndpoints; n++) {switch(inf->endpoint[n].bEndpointAddress&USB_ENDPOINT_NUMBER_MASK) {case 1:
if (! (inf->endpoint[n].bEndpointAddress&USB_ENDPOINT_DIR_MASK))
break;
ifcase USBTEST_WAIT_INTERRUPT:
return usbtest_intr_wait(pdata);((inf->endpoint[n].bmAttributes&USB_ENDPOINT_XFERTYPE_MASK)==

USB_ENDPOINT_XFER_INT) {g_usbtest_data->ep_intr_in = 1;
intr_interval = inf->endpoint[n].bInterval;

default:
return -ENOIOCTLCMD;}
return 0;}
break;
case 2:}if (inf->endpoint[n].bEndpointAddress&USB_ENDPOINT_DIR_MASK)
break;

実際のハードウェア動作は ioctl（アイオーコントロ

if((inf->endpoint[n].bmAttributes&USB_ENDPOINT_XFER_INT)==

USB_ENDPOINT_XFER_BULK) {g_usbtest_data->ep_bulk_out = 2;

ール＝入出力制御）ルーチンで行う。中程の switch 文}
break;

で入力時の動作、出力時の動作を両方決めている。関case 5:

数名を usbtest_ioctl()としているが、この名前は同じプ

if (! (inf->endpoint[n].bEndpointAddress&USB_ENDPOINT_DIR_MASK))
break;
if

ログラム内に登場する、ファイルオペレーション構造((inf->endpoint[n].bmAttributes&USB_ENDPOINT_XFER_INT)

USB_ENDPOINT_XFER_BULK) {g_usbtest_data->ep_bulk_in = 5;

体に登録することで役割をもたせることができる。

}
break;}

static struct file_operations usbtest_fileopes ={owner:
THIS_MODULE,
ioctl:
usbtest_ioctl,open:usbtest_open,
release:usbtest_close,};}if (g_usbtest_data->ep_intr_in == -1) {printk("USBTEST:1: endpoint error!!¥n");return NULL;}
if (g_usbtest_data->ep_bulk_out == -1) {printk("USBTEST:2: endpoint error!!¥n");6==

return NULL;

文 献}
if (g_usbtest_data->ep_bulk_in == -1) {1)Jonathan Corbet 他：ＬＩＮＵＸデバイスドライバ

printk("USBTEST:5: endpoint error!!¥n");return NULL;

第３版,株式会社オライリージャパン,(2007)}g_usbtest_data->dev= dev;

g_usbtest_data->isopen = 0;2)平田 豊：Ｌｉｎｕｘデバイスドライバプログラミ

pipe = usb_rcvintpipe(g_usbtest_data->dev, g_usbtest_data->ep_intr_in);

ング,ソフトバンククリエイティブ株式会社,(2008)

FILL_INT_URB(&g_usbtest_data->irq, dev, pipe, g_usbtest_data->data, 2,usbtest_irq, g_usbtest_data, intr_interval);

3) イ ンター フェー ス , ＣＱ 出版社 ,(3),(2000) 、

return g_usbtest_data;}(1),(2001)

中程にあるcase 文で各エンドポイントに対して入力

4)インターフェース編集部編：改訂新版ＵＳＢハード用か出力用かなどの設定を行う。

＆ソフト開発のすべて,ＣＱ出版社,(2007)

次のコードはＵＳＢドライバ登録構造体である。さきほどのファイルオペレーション構造体に似ているが、ＵＳＢ用のドライバに特有の構造体である。
static struct usb_driver usbtest_data = {owner: THIS_MODULE,
name:"usbtest",
id_table:
usbtest_table,probe: usbtest_probe,
disconnect:
usbtest_disconnect,
fops:&usbtest_fileopes,
minor: USBTEST_MINOR,};

ＵＳＢとして使えるように、デバイスドライバを登録するためには、このＵＳＢドライバ登録構造体へのポインタを usb_register()関数の引数として代入する。
static int __init usbtest_init(void){
if (usb_register(&usbtest_data) < 0) {return -1;}
return 0;}

5. まとめ
このように、ＵＳＢの内部動作とデバイスドライバの構造についての理解を通じてＵＳＢを使ったＩ／Ｏ制御をＬｉｎｕｘを利用して実施した。今回はターゲットデバイス制御用のドライバであるが、ホスト側についても組み込みＬｉｎｕｘ基板にＵＳＢホスト機能が備わるなど、対応機器が増えつつあるのでこの動きと呼応して、開発手法の取得および普及に努めたい。
そして、シンプルで使い勝手の良いものを提供するために、開発側としての技術的課題を乗り越えていきたい。今後は、ＵＳＢカメラのＬｉｎｕｘ制御を用いた、画像処理と制御の連携や、組み込みＬｉｎｕｘを使った制御系の小型化等につなげていきたい。

7