2018年5月24日木曜日

linux kernel 4.xでパラレルポートのLEDを制御してみる (module版)

Linuxでパラレルポートを制御するmoduleをもぞもぞと弄ろうとしています.しかしわたしはmoduleは良く判らんのです...

残念なコトに、linux moduleについて解説された書籍は2000年頃に出版された古い本が多く、そういう本はkernel2.4をベースに書かれていたりします.ところがkernel2.6でパラレルポートmodule関連libraryが刷新されたようで、2.4の書籍に掲載されたsample codeはそのままでは動かなかったりします.
2.6以降は4.xに至るまでさほど変わってない印象です.ヘッダファイルの置き場所が変わってたりはしたけど.

新しい情報を求めてnetを徘徊しまして、パラレルポートに取り付けた8ヶのLEDを任意に点灯できるようになったので以下に書き記しておきます.

ここで使っているLinuxは、Kona-Linux 4.0で、kernel versionは4.16です.
Linux 4.16.7-rt1-kona-rt #1 SMP PREEMPT RT
あれれ、RT patchあてたっけかなぁ? まぁいいや.

※誤りのため貴殿が損害を受けても知らんので素直に死んでくださいね.

【パラレルポートのハードウエア的なこと】
延長ケーブルの末端にLEDを8ヶつけています.この写真は'1'=$31を出力している場面です.00110001が発光しています.
回路はこうなってます.470Ωじゃなくて1kΩにしましたが.
【ソースコード】
こちらに置いときます.実験用なのでprintk()がたくさん入ってます.

参考にしたのはこちらのページですが、同ページのcodeそのままではちょっち不具合があります.正しくrmmodできないので、2度目のinsmodで死んでしまうんです.何か追加したんだけど忘れちゃった.

なお、module programmingについてはこちらなどを参考にどうぞどうぞ.

【Makefile】
obj-m := led.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

$(PWD)じゃ良くないとかあるみたいですがとりあえずこれでよろしく.

Makefileって何?という方は申し訳ないですが自力でなんとかしてください.

わたしのマシンは自分でKernelをビルドした環境なのでこのMakefileでmakeできますけど、ディストリをinstallしただけの環境だと動かないのかも.自力でなんとかしてください、よろしく.

【当moduleの使い方例】
% make                      ビルド
% insmod led.ko      当moduleの登録
% echo 1 > /dev/parportled         parallel portへ出力
% rmmod led            当moduleの削除

・insmodコマンドでled.koをkernelに登録します.自動的に/dev/parportledが作られます.
・当moduleの仕様は、/dev/parportledに文字を流し込むと、パラレルポートに出てくるというものです.上の例では、キャラクタを1つだけの'1'を/dev/parportledに流し込んでいるので、ASCIIコードで'1'=$31=00110001で、上の写真の様にLEDが点灯する結果になります.
・最後にrmmodして当moduleをKernelから削除しています.それと同時に/dev/parportledも削除されます.

【いきなりですが、当moduleがどんな動きをしているのか?】
当moduleのsource codeには、様々なcallback関数が記述されています.どの関数がどのtimingで呼び出されるのかを実際に調べてみました.dmesgコマンドの表示結果を脚色したものを以下に示します.

・moduleを登録する際に以下の関数が呼ばれています.
% insmod led.ko
=>ENTER led_init()
 alloc_chrdev_region()
 class_create()
 device_create()
 cdev_init()
 cdev_add()
 parport_register_driver()
=>ENTER led_attach()
 parport_register_device()
=>CMPL led_attach()
=>CMPL led_init()

・'1'=$31を出力する際に以下の関数が呼ばれています.
% echo 1 > /dev/parportled
=>led_open() (nothing to do)
=>ENTER led_write()
 parport_write_data()
=>CMPL led_write()
=>led_release() (nothing to do)

・moduleの削除の際に以下の関数が呼ばれています.
% rmmod led
=>ENTER led_exit()
 parport_unregister_driver()
=>led_detach() (nothing to do)
 device_destroy()
 class_destroy()
 cdev_del()
 unregister_chrdev_region()
=>CMPL led_exit()


【ソースコードについて】
いろんなライブラリ関数が登場して幻惑されてしまいます.

当moduleは、たぶん以下のような構造になっているのだと思っています.飽くまでもわたしの想像ですので.
・ハードウエアのパラレルポートに近いところにparportというデバドラが居る.
・その上位にキャラクタデバイスを意味するcdevというデバドラが居る.kernel2.6以降ではcdevを使うよう推奨されているらしい.
・両デバドラに、/dev/parportledというデバイスファイルを紐付けする必要がある.
・/dev/parportledは、下図の3つの関数を使って生成される.(mknodは使わない)
・parportに、led_attach(),led_detach()を紐付けする必要がある.
・cdevに、led_open(),led_write(),led_release()を紐付けする必要がある.
・moduleがkernelに登録・削除されたらled_attach(),led_detach()がcallされる.
・/dev/parportledに文字が流し込まれるとled_open(),led_write(),led_release()が順番にcallされる.

これを念頭に置いて、以下のソースコードを眺めてみます.
なお、エラー処理は割愛してありますんでー.

↓デバイスファイル名を/dev/parportledとする.
#define DEVICE_NAME  "parportled"

↓たかがパラレルポートを動かすのに何かいろいろと必要でめんどくさー.
static dev_t dev_number;               デバイスファイルのmajor/minor番号
static struct class *led_class;     デバイスファイルの属性
struct cdev led_cdev;                   キャラクタデバイス
struct pardevice *pdev;               Parallel portデバイス

↓/dev/parportledに文字が流し込まれた時にcallされopenする.中身無し.
int led_open(struct inode *inode, struct file *file){ return 0; }

↓openの後にcallされる.文字をパラレルポートにwriteする.
ssize_t led_write(struct file *file, const char *buf, size_t count, loff_t *ppos){
  char kbuf;
  copy_from_user(&kbuf, buf, 1);
  parport_claim_or_block(pdev);
  parport_write_data(pdev->port, kbuf);
  parport_release(pdev);
  return count;
}

↓writeが終わったらcallされcloseする.中身無し.
int led_release(struct inode *inode, struct file *file){ return 0;}

↓上記led_open/led_write/led_releaseを紐付けるところ.
static struct file_operations led_fops = {
  .owner = THIS_MODULE,
  .open = led_open,
  .write = led_write,
  .release = led_release,
};

以上は、デバイスファイル/dev/parportledをwriteするための関数達でした.

以下に登場するのは、moduleの登録・削除時にcallされる関数達です.

↓他のデバドラがparalell portを占有していて困ったときにcallされる.中身無し.
static int led_preempt(void *handle){  return 1; }

↓当moduleの登録時にcallされる.デバドラparportと/dev/parportledを接続する役目と思う.他のデバドラがparallel portを占有中の際はled_preempt()がcallされる.
static void led_attach(struct parport *port){
  pdev = parport_register_device(port, DEVICE_NAME, led_preempt, NULL, NULL, 0, NULL);
}

↓当moduleの削除時にcallされる.中身無し.
static void led_detach(struct parport *port){ ; }

↓led_attach(),led_detach()をデバドラparportに紐付けるところ.
static struct parport_driver led_driver = {
  .name   = "parportled",
  .attach = led_attach,
  .detach = led_detach,
};


↓当moduleの登録時に最初にcallされる関数.
int __init led_init(void)
{
  ↓/dev/parportledを自動的に作る.major番号はお任せ.かつてはmajor番号決め撃ちでデバイスファイルを別途作る必要があったが、kernel2.6以降では動的なmajor番号が推奨されるらしい.そのためにここがある.
  alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME);
  led_class = class_create(THIS_MODULE, DEVICE_NAME);
  device_create(led_class, NULL, dev_number, NULL, DEVICE_NAME);

  ↓led_open(),led_write(),led_release()をキャラクタデバイスcdevに紐付ける.
  cdev_init(&led_cdev, &led_fops);
  led_cdev.owner = THIS_MODULE;

  ↓cdevに/dev/parportledを紐付ける.dev_numberには/dev/parportledのmajor,minor番号が格納されている.
  cdev_add(&led_cdev, dev_number, 1);

  ↓デバドラparportを登録する.
  parport_register_driver(&led_driver);
  return 0;
}

↓当moduleの削除時にcallされる関数.諸々を清算してます.
void __exit led_exit(void){
  parport_unregister_driver(&led_driver);
  device_destroy(led_class, MKDEV(MAJOR(dev_number), 0));
  class_destroy(led_class);
  cdev_del(&led_cdev);
  unregister_chrdev_region(dev_number, 1);
  return;
}

module_init(led_init);     init()を登録
module_exit(led_exit);     exit()を登録


以上でソースコードはおしまいです.
moduleというものは、callback関数を記述して組み込むのが主な作業なので、慣れると比較的追いやすいように思いました.

かしこ

0 件のコメント:

コメントを投稿