Raspberry Piに外部スイッチを扱うドライバを実装する

この記事は　**[Raspberry PiとMIDIインターフェースでデバイス開発をする](https://qiita.com/ixsiid/items/d126b1d6a073373c2143)** のシリーズ記事です。

### シリーズ目次
1. [Raspberry PiとMIDIインターフェースでデバイス開発をする](https://qiita.com/ixsiid/items/d126b1d6a073373c2143)
2. [RtMidiライブラリを使って、MIDIインターフェースを作成する](https://qiita.com/ixsiid/items/dfd093732d05260ed174)
3. [SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む（プロトタイプ編）](https://qiita.com/ixsiid/items/e3633bc0a594dc5fb972)
4. [SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む（クラス編）](https://qiita.com/ixsiid/items/0d45cbeffeacd7739bc9)
5. ***Raspberry Piに外部スイッチを扱うドライバを実装する***
6. 考案中

### やりたいこと
スイッチとLEDをGPIOに接続するデバイスドライバを作成し、コンソールモードで起動したFluidsynthと接続する。
スイッチが押されると、Fluidsynthに音色（サウンドフォント）を変更するコマンドを送信する

### コード
コードの作成には、[組み込みLinuxデバイスドライバの作り方 (9)](https://qiita.com/take-iwiw/items/e92a950a2cf57d1a3ded)を大いに参考にしました。
ほとんどコピペです。Fluidsynthの操作に関わるところは次の関数部を参考にしてください。

- gpio_intr
- fluidsynth_open
- fluidsynth_close
- device_read

Linux (Raspberry Pi) のドライバ作成にかかわるところは、元記事を参考にしてください。

```driver.cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/hardirq.h>
#include <asm/current.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/*** このデバイスに関する情報 ***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "FluidSynthController"  /* /proc/devices等で表示されるデバイス名 */
static const unsigned int MINOR_BASE = 0;   /* このデバイスドライバで使うマイナー番号の開始番号と個数(=デバイス数) */
static const unsigned int MINOR_NUM = 1;	/* マイナー番号は 0のみ */
static unsigned int mydevice_major;			/* このデバイスドライバのメジャー番号(動的に決める) */
static struct cdev mydevice_cdev;			/* キャラクタデバイスのオブジェクト */
static struct class *mydevice_class = NULL; /* デバイスドライバのクラスオブジェクト */


/* 操作用ボタン、表示灯、押されたときにFluidsynthに
   送信するコマンドなどをまとめた構造体 */
typedef struct {
	char message[128];
	int length;
	int pinButton;
	int pinLed;
	int irq;
} Button;

#define BUTTON_COUNT 3

static Button buttons[BUTTON_COUNT] = {
	// デバイス接続時に実行するコマンド
	{"load /usr/share/sounds/sf2/TimGM6mb.sf2", -1, -1, -1, -1},
	// 音色変更
	{"select 0 1 0 0", -1, 6, 12, -1},
	{"select 0 2 0 16", -1, 13, 16, -1},
};
static int selected_button = 0;
static int next_selected = -1;
static int writed_count = 0;

/* 割り込み発生時(= ボタンが押されたとき)に呼ばれる関数 */
static irqreturn_t gpio_intr(int irq, void *dev_id) {
	printk("fluidsynth_controller_gpio_intr\n");

	int index = -1;
	// irqからどのボタンが押されたかを調べる
	for (int i = 0; i < BUTTON_COUNT; i++) {
		if (buttons[i].irq == irq)
		{
			index = i;
			break;
		}
	}
	if (index < 0) {
		printk("not found irq\n");
		return IRQ_HANDLED;
	}

	int value = gpio_get_value(buttons[index].pinButton);
	printk("button %d = %d\n", buttons[index].pinButton, value);

	if (value > 0) {
		for (int i=0; i < BUTTON_COUNT; i++) {
			if (buttons[index].pinLed >= 0) {
				gpio_set_value(button[index].pinLed, 0);
			}
		}
		gpio_set_value(buttons[index].pinLed, 1);
		next_selected = index
	}

	return IRQ_HANDLED;
}

/* open時に呼ばれる関数 */
static int fluidsynth_open(struct inode *inode, struct file *file) {
	printk("fluidsynth controller open");

	for (int i = 0; i < BUTTON_COUNT; i++)
	{
		/**GPIOピンの入出力設定
		   LEDピンを出力に設定 */
		if (buttons[i].pinLed >= 0) gpio_direction_output(buttons[i].pinLed, 0);

		if (buttons[i].pinButton >= 0)
		{
			/* BUTTONピンを入力に設定 */
			gpio_direction_input(buttons[i].pinButton);
			/* BUTTONの割り込み処理を設定 */
			buttons[i].irq = gpio_to_irq(buttons[i].pinButton);
			if (request_irq(buttons[i].irq, (void *)gpio_intr,
							IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
							"fluidsynth_controller_gpio_intr", (void *)gpio_intr) < 0)
			{
				printk(KERN_ERR "request_irq\n");
				return -1;
			}
		}
	}

	return 0;
}

/* close時に呼ばれる関数 */
static int fluidsynth_close(struct inode *inode, struct file *file) {
	printk("fluidsynth controller close");

	// 割り込み処理の解放
	for (int i = 0; i < BUTTON_COUNT; i++)
	{
		if (buttons[i].pinButton >= 0)
		{
			free_irq(buttons[i].irq, (void *)gpio_intr);
		}
	}

	// コマンド送信状態を初期化
	selected_button = 0;
	next_selected = -1;
	writed_count = 0;
	return 0;
}

/* read時に呼ばれる関数 */
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
	if (selected_button < 0)
	{
		// 送信するコマンドが残っていない場合
		put_user(0, &buf[0]);

		// 割り込みによって設定される、次のコマンドをカレントに移す
		selected_button = next_selected;
		next_selected = -1;
		writed_count = 0;
		return 1;
	}

	/* 読み込み待ちになっているコマンドの残り長さ */
	int len = buttons[selected_button].length - writed_count;
	if (count >= len)
	{
		copy_to_user(buf, &buttons[selected_button].message[writed_count], len);
		selected_button = next_selected;
		next_selected = -1;
		writed_count = 0;
		return len;
	}
	else
	{
		copy_to_user(buf, &buttons[selected_button].message[writed_count], count);
		writed_count += count;
		return count;
	}
	
	return -1;
}

/* 各種システムコールに対応するハンドラテーブル */
struct file_operations s_mydevice_fops = {
	.open = fluidsynth_open,
	.release = fluidsynth_close,
	.read = device_read,
	.write = NULL,
};

/* ロード(insmod)時に呼ばれる関数 */
static int device_init(void) {
	printk("fluidsynth_controller_init\n");

	int alloc_ret = 0;
	int cdev_err = 0;
	dev_t dev;

	/* 1. 空いているメジャー番号を確保する */
	alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
	if (alloc_ret != 0) {
		printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
		return -1;
	}

	/* 2. 取得したdev( = メジャー番号 + マイナー番号)からメジャー番号を取得して保持しておく */
	mydevice_major = MAJOR(dev);
	dev = MKDEV(mydevice_major, MINOR_BASE); /* 不要? */

	/* 3. cdev構造体の初期化とシステムコールハンドラテーブルの登録 */
	cdev_init(&mydevice_cdev, &s_mydevice_fops);
	mydevice_cdev.owner = THIS_MODULE;

	/* 4. このデバイスドライバ(cdev)をカーネルに登録する */
	cdev_err = cdev_add(&mydevice_cdev, dev, MINOR_NUM);
	if (cdev_err != 0) {
		printk(KERN_ERR "cdev_add = %d\n", alloc_ret);
		unregister_chrdev_region(dev, MINOR_NUM);
		return -1;
	}

	/* 5. このデバイスのクラス登録をする(/sys/class/___/ を作る) */
	mydevice_class = class_create(THIS_MODULE, "fluidsynth_controller");
	if (IS_ERR(mydevice_class)) {
		printk(KERN_ERR "class_create\n");
		cdev_del(&mydevice_cdev);
		unregister_chrdev_region(dev, MINOR_NUM);
		return -1;
	}

	/* 6. /sys/class/___/___* を作る */
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_create(mydevice_class, NULL, MKDEV(mydevice_major, minor), NULL, "fluidsynth_controller%d", minor);
	}

	for (int i = 0; i < BUTTON_COUNT; i++) {
		if (buttons[i].length >= 0) continue;
		for (buttons[i].length = 0; buttons[i].message[buttons[i].length] != '\0'; buttons[i].length++) ;
		buttons[i].message[buttons[i].length++] = '\n';
		buttons[i].message[buttons[i].length] = '\0';
	}

	return 0;
}

/* アンロード(rmmod)時に呼ばれる関数 */
static void device_exit(void) {
	printk("fluidsynth_controller_exit\n");

	dev_t dev = MKDEV(mydevice_major, MINOR_BASE);

	/* 7. /sys/class/___/___* を削除する */
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_destroy(mydevice_class, MKDEV(mydevice_major, minor));
	}

	/* 8. このデバイスのクラス登録を取り除く(/sys/class/___/を削除する) */
	class_destroy(mydevice_class);

	/* 9. このデバイスドライバ(cdev)をカーネルから取り除く */
	cdev_del(&mydevice_cdev);

	/* 10. このデバイスドライバで使用していたメジャー番号の登録を取り除く */
	unregister_chrdev_region(dev, MINOR_NUM);
}

module_init(device_init);
module_exit(device_exit);

```

```Makefile.mk
CFILES = driver.c

obj-m := mkt.o
mkt-objs := $(CFILES:.c=.o)

ccflags-y += -std=gnu99 -Wall -Wno-declaration-after-statement

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
```

### 動かす

```bash
$ sudo insmod mkt.ko
$ fluidsynth -a alsa -g 6 /usr/share/sounds/sf2/FluidR3_GM.sf2 < /dev/mydevice0
FluidSynth version 1.1.6
Copyright (C) 2000-2012 Peter Hanappe and others.
Distributed under the LGPL license.
SoundFont(R) is a registered trademark of E-mu Systems, Inc.

fluidsynth: warning: Failed to pin the sample data to RAM; swapping is possible.
fluidsynth: warning: Requested a period size of 64, got 256 instead
fluidsynth: warning: Failed to set thread to high priority
fluidsynth: warning: Failed to set thread to high priority
Type 'help' for help topics.

> load /usr/share/sounds/sf2/TimGM6mb.sf2
fluidsynth: warning: Failed to pin the sample data to RAM; swapping is possible.
loaded SoundFont has ID 2
> select 0 1 0 0
> select 0 2 0 16
> select 0 2 0 16
> select 0 2 0 16
> ^C
$ sudo rmmod mkt.ko
```

\> に続く文字（load ~~~やselect ~~~~）がデバイスドライバからFluidsynthに送信されているコマンドです。
スイッチ（上のコードではGPIO6と13）のHIGH/LOWを切り替えると、コマンドが送信されます。


前回作った$play.o$を組み合わせて、Fluidsynthを起動しながらSMFファイルを再生した状態で、
スイッチを操作するとスイッチの操作に合わせて再生される音色が変わります。

```bash
$ sudo insmod mkt.ko
$ fluidsynth -a alsa -g 6 /usr/share/sounds/sf2/FluidR3_GM.sf2 < /dev/mydevice0 &　./play.o
FluidSynthの出力 (省略)

play.oの出力 parse: (省略)
> p # play.o に再生コマンドを送る
> send: 144 37 51
(省略)
^C
$ sudo rmmod mkt.ko
```

### 解説
`buttons[0]` はFluidsynthと接続直後の初期化コマンドです。
今回は追加したいサウンドフォントを読み込む処理を記述しています。
`selected_button, next_selected, writed_count`という3つの変数で、Fluidsynthへのコマンド送信状態を管理しています。
コマンド送信が中途半端な状態にスイッチが押されても、不正なコマンド送信されないように注意が必要です。

今までのシステム図に今回のドライバが相当する部分は含まれていなかったので、システム図も更新します。

![MKT (3).png](https://qiita-image-store.s3.amazonaws.com/0/256650/403adcb2-49fd-aa87-b272-b83118e01a3f.png)


|前|次|
|---|---|
|[SMF (Standard Midi File)を解析し、MIDIインターフェースに流し込む（クラス編）](https://qiita.com/ixsiid/items/0d45cbeffeacd7739bc9)|***作成中***|