インターフェイス社ドライバの再コンパイル

# この記事について
インターフェイス社のI/Oボードを使いたかったが，コンパイルしなおす必要があったのでその時のメモ。
[RTAIの記事](https://qiita.com/larking95/items/c9f23b40d630cc4c09b8)の方法で作った環境で，DAボードやカウンタボードを
使えるようにドライバのソースをあれこれ書き換えて悪戦苦闘した結果，何とか動作しました。

「過程はいいから結果をよこせ！」というかたは，[私のgithub](https://github.com/larking95/patch_for_interface_drivers)にpatchファイルとそれを自動で当てるスクリプトを用意したのでご利用ください

# 環境など
- PC
    - CPU: Intel pentium 4
    - RAM 1024GB
    - HDD: 500GB
    - Ubuntu 16.04 LTS (i386)
        - RTAI 5.1 適用済み
        - Kernel 4.9.80
- Interface社のドライバ
    - gpg3100 Ver. 5.60-46 (ADボード用)
    - gpg3300 Ver. 4.70-43 (DAボード用)
    - gpg6204 Ver. 4.40-20 (カウンタボード用)

# ソース修正とインストール
[こちらのサイト](https://www.rbt.his.u-fukui.ac.jp/~naniwa/comp/ubuntu-lowlatency.html)をベースに行っていきます。
ただし，このサイトではKernel 4.4.0を利用しており，Kernel 4.9.80では一部関数が変更されておりそのまま使えなかったです。

## dpg0100 (共通用ドライバ)
Makefileについては，参考サイトそのままでOKです。

```makefile:Makefile.dpg0100
obj-m	:= dpg0100.o
KDIR	:= /lib/modules/$(shell uname -r)/build
PWD	:= $(shell pwd)

default:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

install:
	$(MAKE) -C $(KDIR) M=$(PWD) modules_install

```
問題はソースファイルの方で，パッチを当ててもエラーが出ます。
エラー文を読む限り，どうやら，`get_user_pages()`の引数定義が変更されたことと，
`page_cache_release()`という関数が削除されたことが原因らしいです。

そこで，[Bootlin](https://elixir.bootlin.com/linux/latest/source)というサイトから，Linuxカーネルのソースのヘッダファイルを確認することにした。

### get_user_pages()
まずget_user_pagesについて定義を見ると，4.9.80では，
> `long get_user_pages(unsigned long start, unsigned long nr_pages,
>			    unsigned int gup_flags, struct page **pages,
>			    struct vm_area_struct **vmas);`
> (From https://elixir.bootlin.com/linux/v4.9.80/source/include/linux/mm.h#L1281)

となっている。一方で，元々ドライバが対応している2.6.33では，
> `int get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
>			unsigned long start, int nr_pages, int write, int force,
>			struct page **pages, struct vm_area_struct **vmas);`
> (From https://elixir.bootlin.com/linux/v2.6.33/source/include/linux/mm.h#L836)

となっていました。
ここで，対応関係が不明なのが，4.9.80の`gup_flags`と2.6.33の`write`と`force`です。
調べてみるとどうやら，ビット演算で`FOLL_WRITE`と`FOLL_FORCE`のORを取ったものを`gup_flags`に格納すればよい模様。

### page_cache_release()
次に，page_cache_releaseについて定義を見ると，2.6.33では，
> `#define page_cache_release(page)	put_page(page)`
> (From https://elixir.bootlin.com/linux/v2.6.33/source/include/linux/pagemap.h#L84)

となっており，ただ別名としてマクロを組んでいるだけのようでした。
`put_pages()`関数については4.9.80にも存在したので，こちらに置き換えるだけで良さそうです。

## パッチファイル
ということで，以上を反映した結果コンパイルが通りました。

```patch:dpg0100_v4980.patch
--- dpg0100.c.orig	2013-06-19 18:22:34.000000000 +0900
+++ dpg0100.c	2019-02-18 00:34:38.363152823 +0900
@@ -16,7 +16,7 @@
 #include <linux/pci.h>
 #include <linux/sched.h>
 #include <asm/delay.h>
-#include <asm/system.h>
+// #include <asm/system.h>
 #include <linux/hdreg.h>
 #include <linux/slab.h>
 
@@ -228,7 +228,8 @@
 }
 int gpg_MINOR_f(void *file)
 {
-	return MINOR(((struct file *)file)->f_dentry->d_inode->i_rdev);
+//	return MINOR(((struct file *)file)->f_dentry->d_inode->i_rdev);
+	return iminor(file_inode((struct file *) file));
 }
 void *gpg_get_file_private_data(void *file)
 {
@@ -974,15 +975,26 @@
 	}
 
 	down_read(&current->mm->mmap_sem);
+	/*
 	ret = get_user_pages(current, current->mm, (Buffer & PAGE_MASK),
 		mdl->NumberOfPages, Direction, 1,
 		(struct page **)mdl->PageList, NULL);
+	*/
+	if(Direction == 1) {
+		ret = get_user_pages((Buffer & PAGE_MASK), mdl->NumberOfPages,
+                        FOLL_FORCE | FOLL_WRITE, (struct page **)mdl->PageList, NULL);
+	} else {
+		ret = get_user_pages((Buffer & PAGE_MASK), mdl->NumberOfPages,
+                        FOLL_FORCE, (struct page **)mdl->PageList, NULL);
+	}
+	
 	up_read(&current->mm->mmap_sem);
 	if(ret < mdl->NumberOfPages) {
 		/* failed to lock pages */
 		if(ret > 0) {
 			for(i = 0; i < ret; i++) {
-				page_cache_release((struct page *)mdl->PageList[i]);
+				//page_cache_release((struct page *)mdl->PageList[i]);
+				put_page((struct page *)mdl->PageList[i]);
 			}
 			kfree(mdl->PageList);
 			mdl->PageList = NULL;
@@ -1004,7 +1016,8 @@
 			if(!PageReserved((struct page *)mdl->PageList[i])) {
 				SetPageDirty((struct page *)mdl->PageList[i]);
 			}
-			page_cache_release((struct page *)mdl->PageList[i]);
+			//page_cache_release((struct page *)mdl->PageList[i]);
+			put_page((struct page *)mdl->PageList[i]);
 		}
 		kfree(mdl->PageList);
 		mdl->PageList = NULL;
```

## dpg0101 (セットアップ用ドライバ)
ここは，修正なしでもエラーなくコンパイルが終了します。
ただし，[`file_operations`構造体](https://elixir.bootlin.com/linux/v4.9.80/source/include/linux/fs.h#L1701)の`.unlocked_ioctl`の型がlongで定義されていますが，ドライバソースではint型ですので警告が表示されます。
一応，以下のように変更すると警告はなくなりました。

```diff:dpg0101_v4980.patch
--- dpg0101.c	2019-02-15 17:33:55.076551578 +0900
+++ dpg0101.c.orig	2013-11-13 15:48:12.000000000 +0900
@@ -323,8 +323,8 @@
 /*  ioctl system call : ifdevmgr_ioctl 	                    */
 /* ******************************************************** */
 #if (LINUX_VERSION_CODE >= VERSION(2,6,36))
-long ifdevmgr_ioctl(struct file *file, unsigned int iocmd, unsigned long ioarg)
-#else
+int ifdevmgr_ioctl(struct file *file, unsigned int iocmd, unsigned long ioarg)
+#else
 int ifdevmgr_ioctl(struct inode *inode, struct file *file, unsigned int iocmd, unsigned long ioarg)
 #endif
 {
```

## gpg3100 (ADボード用ドライバ)
ここでも，gpg0100と同様の変更を追加しました。

```diff:gpg3100_v4980.patch
--- ./ad_entry.c.orig	2016-07-12 15:59:36.000000000 +0900
+++ ./ad_entry.c	2019-02-18 01:08:56.902702284 +0900
@@ -592,6 +592,7 @@
 	/* Try to fault in all of the necessary pages */
 	down_read(&current->mm->mmap_sem);
 	
+	/*
 	ret = get_user_pages(
 		current, 
 		current->mm, 
@@ -601,14 +602,25 @@
 		1,
 		(struct page **)mdl->PageList, 
 		NULL);
+	*/
+
+	if(Direction == WRITE) {
+		ret = get_user_pages((Buffer & PAGE_MASK), mdl->NumberOfPages,
+                        FOLL_FORCE | FOLL_WRITE, (struct page **)mdl->PageList, NULL);
+ 	} else {
+		ret = get_user_pages((Buffer & PAGE_MASK), mdl->NumberOfPages,
+			FOLL_FORCE, (struct page **)mdl->PageList, NULL);
+	}
+
 	up_read(&current->mm->mmap_sem);
-	
+
 	/* Errors and no page mapped should return here */
 	if (ret < mdl->NumberOfPages) {
 		/* failed to lock pages */
 		if (ret > 0) {
 			for (i = 0; i < ret; i++) {
-				page_cache_release((struct page *)mdl->PageList[i]);
+				//page_cache_release((struct page *)mdl->PageList[i]);
+				put_page((struct page *)mdl->PageList[i]);
 			}
 			kfree(mdl->PageList);
 			mdl->PageList = NULL;
@@ -635,7 +647,8 @@
 			if (!PageReserved((struct page *)mdl->PageList[i])) {
 				SetPageDirty((struct page *)mdl->PageList[i]);
 			}
-			page_cache_release((struct page *)mdl->PageList[i]);
+			//page_cache_release((struct page *)mdl->PageList[i]);
+			put_page((struct page *)mdl->PageList[i]);
 		}
 		kfree(mdl->PageList);
 		mdl->PageList = NULL;
@@ -874,6 +887,11 @@
 	}
 }
 
+inline long interruptible_sleep_on_timeout(wait_queue_head_t * q, long timeout)
+{
+	return wait_event_interruptible_timeout(*q, 0, timeout);
+}
+
 /************************************************/
 /* gpg_ad_interruptible_sleep_on_timeout        */
 /************************************************/
@@ -1894,7 +1912,8 @@
  ************************************************/
 int gpg_ad_MINOR_f(void *file)
 {
-	return MINOR(((struct file *)file)->f_dentry->d_inode->i_rdev);
+	//return MINOR(((struct file *)file)->f_dentry->d_inode->i_rdev);
+	return iminor(file_inode((struct file *) file));
 }
 
 /************************************************
```
## gpg3300 (DAボード用ドライバ)
ここは参考サイトのままで大丈夫でした。

## gpg6204 (カウンタボード用ドライバ)
ここは，今までの合わせ技で対応します。

```diff:gpg6204_v4980.patch
--- penc_drventry.c.orig	2019-02-18 01:18:42.336979926 +0900
+++ penc_drventry.c	2019-02-09 01:34:38.041634924 +0900
@@ -185,6 +185,10 @@
 	
 	return;
 }
+inline long interruptible_sleep_on_timeout(wait_queue_head_t * q, long timeout)
+{
+	return wait_event_interruptible_timeout(*q, 0, timeout);
+}
 
 /************************************************/
 /* gpg_penc_interruptible_sleep_on_timeout      */
@@ -489,7 +493,8 @@
 /* ioctl system call                            */
 /************************************************/
 #if (LINUX_VERSION_CODE >= VERSION(2,6,36))
-static int penc_ioctl(struct file *file, unsigned int iocmd, unsigned long ioarg)
+//static int penc_ioctl(struct file *file, unsigned int iocmd, unsigned long ioarg)
+static long penc_ioctl(struct file *file, unsigned int iocmd, unsigned long ioarg)
 #else
 static int penc_ioctl(struct inode *inode, struct file *file, unsigned int iocmd, unsigned long ioarg)
 #endif
@@ -540,7 +545,8 @@
 #else
 module_param(cp6204_debuglevel, ulong, 0);
 module_param(penc_major, int, 0);
-module_param(cp6204_version_disp, int, 1);
+// module_param(cp6204_version_disp, int, 1);
+module_param(cp6204_version_disp, int, 0);
 #endif
 
 #if (LINUX_VERSION_CODE >= VERSION(2,6,0))
```
また，ヘッダファイルなどのシンボリックリンクが必要です。

```terminal
ln -s /usr/include/dpg0100.h
ln -s /usr/include/fbipenc.h
ln -s ../ver26/bocp6204.o
```
この状態でもコンパイルは通りますが，多くの警告が出ます。
ここで，dpg0100のコンパイル時のModule.symversをコピーしておくと，警告が消えてくれます。

# 動作確認
全てのカーネルモジュールをmakeしてmake installしたら，認識できるかテストします。
まず,参考サイトに従って，depmodとmodprobeでモジュールを挿入します。

```terminal
depmod -a
modprobe dpg0100
modprobe dpg0101
modprobe cp3100
modprobe cp3300
modprobe cp6204

lsmod
```
lsmodで，きちんと挿入できていることが確認できます。

## エラー発生!!
次に，dpg0101を実行し，デバイスノードを生成させます。
以下のように入力することで，すべてのデバイスのノードを生成できるはずです。

```terminal
dpg0101
3100
3
3300　#ここでエラー
3
6204
99
```
が，しかし，以下のような画面となり，
２つ目以降のデバイスが設定できないことが分かります・・・。

```
**************************************************
 Setup Utility  
--------------------------------------------------
 Version: 1.50-09  
--------------------------------------------------
 Copyright 2003, 2011      Interface Corporation. 
                             All rights reserved. 
**************************************************

Enter the model number of the product: GPG/GPH-3300
 >> Another error
 No Interface PCI/CompactPCI board is found.
```
しかし，エラー前のcp3100についてはきちんとノードが作成されているようです。
また，dpg0101の実行前後でlsmodやdmesgすると，`dpg0101.ko`がなぜか勝手にunloadされているようです。
（どうやらこれがエラーの原因のようですが，解決策は不明です・・・。）

## とりあえずの対症療法
ということで，dpg0101.koを一回ずつロードしてデバイスノードを作成するようにしました。
以下のスクリプトで自動実行できます。

```bash:dpg0101_auto.sh
#!/bin/bash

modprobe dpg0101
dpg0101 -s 3100
modprobe dpg0101
dpg0101 -s 3300
modprobe dpg0101
dpg0101 -s 6204
```
後は，テキトーにサンプルプログラムを組んで，テスタやオシロなどで実際の入出力を確認しましょう。

# 後書き
なんだか怪しい部分もありますが，すべてのボードが正しく動作することは確認できました。
前に使っていたART-Linux(Ubuntu10.04)の時はすんなりとインストール出来て，
dpg0101でこんな苦労することは(多分)なかったので，なかなか対処が分からず困りました・・・。
原因や解決策が分かる方がいらっしゃれば，ぜひ教えてください。
また，間違いなどあれば遠慮なくご指摘ください。

~~にしても，RTAIにはせっかく[Comedi](https://www.comedi.org/)が対応しているんだから，
誰かComediドライバ書いてくれないかなぁなんて思ったり思わなかったり・・・。~~

# 追記
以上の手順を自動化するスクリプトを用意しました。
使いたい方は[こちらのページ](https://github.com/larking95/patch_for_interface_drivers)からダウンロードしてお使いください。

また，以下の記事でUbuntu 18.04でもドライバのコンパイルに成功されたようです。
カーネルのバージョンにもよりますが，参考になるかもしれません。
[dpg0100 on Ubuntu18.04](https://qiita.com/ProjectM_TACCMI/items/7360b26d70cc2c866efb)
[dpg0101 on Ubuntu18.04](https://qiita.com/ProjectM_TACCMI/items/12a771c454471dc90691)

#参考文献
[Ubuntu 16.04LTS でのインターフェース社ドライバの使用](https://www.rbt.his.u-fukui.ac.jp/~naniwa/comp/ubuntu-lowlatency.html) : 最も参考になるページ（日本語）
[Linux source code identifier (v2.6.33) - Bootlin](https://elixir.bootlin.com/linux/v2.6.33/ident) : 2.6.33のカーネルソース
[Linux source code identifier (v4.9.80) - Bootlin](https://elixir.bootlin.com/linux/v4.9.80/ident) : 4.9.80のカーネルソース