Linux Kernel Device Drivers
Linux Kernel Device Drivers - 包括的技術ガイド
最終更新日: 2026-04-10 対象カーネルバージョン: Linux 6.x 系 対象読者: Linux カーネル開発者、システムプログラマ、組み込みエンジニア
目次
- はじめに
- Linux デバイスモデル
- デバイスの分類: キャラクタ・ブロック・ネットワーク
- デバイスドライバフレームワーク
- PCI/PCIe ドライバサブシステム
- USB ドライバサブシステム
- プラットフォームデバイスとデバイスツリー
- DMA (Direct Memory Access)
- IOMMU とデバイス分離
- 割り込みハンドリング
- sysfs とデバイス属性
- udev ルールとデバイス管理
- DKMS (Dynamic Kernel Module Support)
- 実践: シンプルなキャラクタデバイスドライバ
- ioctl インターフェース
- カーネルモジュールの Makefile とビルドシステム
- 診断ツール群
- まとめとベストプラクティス
1. はじめに
Linux カーネルにおけるデバイスドライバは、ハードウェアとユーザ空間アプリケーションの間を橋渡しするソフトウェアコンポーネントである。Linux カーネルのソースコードの約 70% 以上がドライバコードで占められており、カーネル開発において最も重要な領域の一つである。
1.1 デバイスドライバの役割
デバイスドライバは以下の主要な機能を提供する:
- ハードウェア抽象化: 多種多様なハードウェアに対して統一的なインターフェースを提供
- リソース管理: メモリマッピング、割り込み、DMA チャネルなどのハードウェアリソースを管理
- 並行制御: 複数のプロセスからの同時アクセスを安全に処理
- 電源管理: デバイスのサスペンド/レジュームを制御
- エラーハンドリング: ハードウェア障害の検出と回復
1.2 カーネルモジュールとしてのドライバ
Linux のデバイスドライバは、カーネルに静的にリンクする方法と、ローダブルカーネルモジュール (LKM) として動的にロードする方法がある。
/*
* 最も基本的なカーネルモジュールの構造
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example Linux module");
MODULE_VERSION("1.0");
static int __init example_init(void)
{
pr_info("Example module loaded\n");
return 0;
}
static void __exit example_exit(void)
{
pr_info("Example module unloaded\n");
}
module_init(example_init);
module_exit(example_exit);
# モジュールのビルドと操作
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# モジュールのロード
sudo insmod example.ko
# モジュール情報の表示
modinfo example.ko
# モジュールの一覧
lsmod | grep example
# モジュールのアンロード
sudo rmmod example
# 依存関係を自動解決してロード
sudo modprobe example
1.3 カーネル空間とユーザ空間
Linux ではメモリ空間が明確に分離されている:
+-----------------------------------------------+
| ユーザ空間 |
| +----------+ +----------+ +----------+ |
| | アプリA | | アプリB | | アプリC | |
| +----------+ +----------+ +----------+ |
| | | | |
| システムコール (open, read, write, ioctl) |
+-----------------------------------------------+
| カーネル空間 |
| +------------------------------------------+ |
| | VFS (Virtual Filesystem) | |
| +------------------------------------------+ |
| | キャラクタ | ブロック | ネットワーク | |
| | デバイス層 | デバイス層 | デバイス層 | |
| +------------------------------------------+ |
| | デバイスドライバ | |
| +------------------------------------------+ |
| | ハードウェア抽象化層 | |
| +------------------------------------------+ |
+-----------------------------------------------+
| ハードウェア |
+-----------------------------------------------+
1.4 ドライバ開発に必要な知識
ドライバ開発に携わる前に、以下の知識が必要である:
- C 言語プログラミング (ポインタ、構造体、関数ポインタ)
- Linux カーネルの基本概念 (プロセス管理、メモリ管理)
- コンピュータアーキテクチャの基礎 (メモリマッピング、割り込み、バス)
- Make および GCC ツールチェーン
- デバッグ技法 (printk, ftrace, kgdb)
2. Linux デバイスモデル
Linux デバイスモデルは、カーネル 2.6 で導入された統一的なデバイス管理フレームワークである。バス、デバイス、ドライバの3つの主要コンポーネントで構成される。
2.1 バス (Bus)
バスはデバイスとドライバを結びつける仲介者である。
/*
* バス型の定義 (include/linux/device/bus.h)
*/
struct bus_type {
const char *name; /* バスの名前 */
const char *dev_name; /* デバイスの命名規則 */
const struct attribute_group **bus_groups; /* バス属性 */
const struct attribute_group **dev_groups; /* デバイス属性 */
const struct attribute_group **drv_groups; /* ドライバ属性 */
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(const struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
void (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
int (*num_vf)(struct device *dev);
int (*dma_configure)(struct device *dev);
void (*dma_cleanup)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
bool need_parent_lock;
};
/*
* カスタムバスの登録例
*/
static int my_bus_match(struct device *dev, struct device_driver *drv)
{
/* デバイスとドライバのマッチングロジック */
return !strcmp(dev_name(dev), drv->name);
}
static struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_bus_match,
};
/* バスの登録 */
int ret = bus_register(&my_bus_type);
if (ret) {
pr_err("Failed to register bus: %d\n", ret);
return ret;
}
/* バスの登録解除 */
bus_unregister(&my_bus_type);
2.2 デバイス (Device)
struct device はすべてのデバイスの基底構造体である。
/*
* デバイス構造体の主要フィールド (include/linux/device.h)
*/
struct device {
struct kobject kobj; /* sysfs 表現 */
struct device *parent; /* 親デバイス */
struct device_private *p; /* プライベートデータ */
const char *init_name; /* 初期名 */
const struct device_type *type; /* デバイスタイプ */
const struct bus_type *bus; /* 所属バス */
struct device_driver *driver; /* バインドされたドライバ */
void *platform_data; /* プラットフォーム固有データ */
void *driver_data; /* ドライバ固有データ */
struct dev_pm_info power; /* 電源管理情報 */
struct dev_pm_domain *pm_domain; /* PM ドメイン */
u64 *dma_mask; /* DMA マスク */
u64 coherent_dma_mask; /* コヒーレント DMA マスク */
const struct dma_map_ops *dma_ops; /* DMA 操作 */
struct dma_pool *dma_pools; /* DMA プール */
struct dev_archdata archdata; /* アーキテクチャ固有データ */
struct device_node *of_node; /* デバイスツリーノード */
struct fwnode_handle *fwnode; /* ファームウェアノード */
dev_t devt; /* デバイス番号 (major:minor) */
u32 id; /* デバイス ID */
struct class *class; /* デバイスクラス */
const struct attribute_group **groups; /* 属性グループ */
void (*release)(struct device *dev); /* 解放コールバック */
struct iommu_group *iommu_group; /* IOMMU グループ */
struct dev_iommu *iommu; /* IOMMU データ */
};
/*
* デバイスの登録例
*/
static void my_device_release(struct device *dev)
{
pr_info("my_device released\n");
}
static struct device my_device = {
.init_name = "my_device0",
.bus = &my_bus_type,
.release = my_device_release,
};
/* デバイスの登録 */
int ret = device_register(&my_device);
if (ret) {
pr_err("Failed to register device: %d\n", ret);
return ret;
}
/* デバイスの登録解除 */
device_unregister(&my_device);
2.3 ドライバ (Driver)
struct device_driver はドライバの基底構造体である。
/*
* ドライバ構造体 (include/linux/device/driver.h)
*/
struct device_driver {
const char *name; /* ドライバ名 */
const struct bus_type *bus; /* 所属バス */
struct module *owner; /* THIS_MODULE */
const char *mod_name; /* モジュール名 */
bool suppress_bind_attrs; /* bind/unbind 属性の抑制 */
enum probe_type probe_type; /* プローブタイプ */
const struct of_device_id *of_match_table; /* DT マッチテーブル */
const struct acpi_device_id *acpi_match_table; /* ACPI マッチテーブル */
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct attribute_group **groups; /* ドライバ属性 */
const struct attribute_group **dev_groups; /* デバイス属性 */
const struct dev_pm_ops *pm; /* 電源管理操作 */
void (*coredump)(struct device *dev);
struct driver_private *p; /* プライベートデータ */
};
2.4 kobject と sysfs
kobject はカーネルオブジェクトの基底構造であり、sysfs を通じてユーザ空間に情報を公開する。
/*
* kobject の基本構造
*/
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
const struct kobj_type *ktype;
struct kernfs_node *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
# sysfs でのデバイスモデルの確認
# バスの一覧
ls /sys/bus/
# 出力例: acpi cpu i2c pci platform scsi spi usb virtio
# PCI デバイスの一覧
ls /sys/bus/pci/devices/
# 出力例: 0000:00:00.0 0000:00:01.0 0000:00:1f.0 ...
# デバイスの詳細情報
cat /sys/bus/pci/devices/0000:00:00.0/vendor
cat /sys/bus/pci/devices/0000:00:00.0/device
cat /sys/bus/pci/devices/0000:00:00.0/class
# ドライバの一覧
ls /sys/bus/pci/drivers/
# 出力例: ahci e1000e i915 xhci_hcd ...
# ドライバとデバイスのバインド/アンバインド
echo "0000:00:1f.0" > /sys/bus/pci/drivers/lpc_ich/unbind
echo "0000:00:1f.0" > /sys/bus/pci/drivers/lpc_ich/bind
2.5 デバイスモデルの階層構造
/sys/
├── bus/ # バスタイプごとの分類
│ ├── pci/
│ │ ├── devices/ # PCI デバイスへのシンボリックリンク
│ │ └── drivers/ # PCI ドライバ
│ ├── usb/
│ │ ├── devices/
│ │ └── drivers/
│ └── platform/
│ ├── devices/
│ └── drivers/
├── class/ # 機能クラスごとの分類
│ ├── net/ # ネットワークデバイス
│ ├── block/ # ブロックデバイス
│ ├── input/ # 入力デバイス
│ ├── tty/ # TTY デバイス
│ └── gpio/ # GPIO
├── devices/ # デバイスツリーの実体
│ ├── system/
│ │ └── cpu/
│ ├── pci0000:00/
│ └── platform/
└── module/ # ロード済みモジュール
├── e1000e/
└── snd_hda_intel/
3. デバイスの分類
Linux では、デバイスを大きく3つに分類する: キャラクタデバイス、ブロックデバイス、ネットワークデバイスである。
3.1 キャラクタデバイス (Character Device)
キャラクタデバイスはバイトストリームとしてアクセスされるデバイスである。シーケンシャルまたはランダムアクセスが可能で、バッファリングは行われない。
代表例:
- シリアルポート (
/dev/ttyS0) - ターミナル (
/dev/tty) - マウス (
/dev/input/mouse0) - カメラ (
/dev/video0) - GPU (
/dev/dri/card0) - ランダム数生成器 (
/dev/random,/dev/urandom) - null デバイス (
/dev/null)
# キャラクタデバイスの確認
ls -la /dev/null
# crw-rw-rw- 1 root root 1, 3 Apr 10 00:00 /dev/null
# 'c' はキャラクタデバイスを示す
# 1 = メジャー番号, 3 = マイナー番号
# メジャー番号の割り当て一覧
cat /proc/devices | head -30
/*
* キャラクタデバイスの登録 (基本的な流れ)
*/
#include <linux/cdev.h>
#include <linux/fs.h>
static dev_t dev_number; /* デバイス番号 */
static struct cdev my_cdev; /* cdev 構造体 */
static struct class *my_class; /* デバイスクラス */
/* file_operations 構造体 */
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};
static int __init my_init(void)
{
int ret;
/* デバイス番号の動的割り当て */
ret = alloc_chrdev_region(&dev_number, 0, 1, "my_chardev");
if (ret < 0) {
pr_err("Failed to allocate device number\n");
return ret;
}
pr_info("Device number: major=%d, minor=%d\n",
MAJOR(dev_number), MINOR(dev_number));
/* cdev の初期化と登録 */
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
ret = cdev_add(&my_cdev, dev_number, 1);
if (ret < 0) {
unregister_chrdev_region(dev_number, 1);
return ret;
}
/* デバイスクラスの作成 (/sys/class/my_chardev/) */
my_class = class_create("my_chardev");
if (IS_ERR(my_class)) {
cdev_del(&my_cdev);
unregister_chrdev_region(dev_number, 1);
return PTR_ERR(my_class);
}
/* デバイスノードの作成 (/dev/my_chardev0) */
device_create(my_class, NULL, dev_number, NULL, "my_chardev0");
return 0;
}
static void __exit my_exit(void)
{
device_destroy(my_class, dev_number);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev_number, 1);
}
3.2 ブロックデバイス (Block Device)
ブロックデバイスは固定サイズのブロック単位でアクセスされるデバイスである。I/O スケジューラによるバッファリングとリオーダリングが行われる。
代表例:
- ハードディスク (
/dev/sda) - SSD (
/dev/nvme0n1) - USB メモリ (
/dev/sdb) - ループデバイス (
/dev/loop0) - RAM ディスク (
/dev/ram0)
# ブロックデバイスの確認
ls -la /dev/sda
# brw-rw---- 1 root disk 8, 0 Apr 10 00:00 /dev/sda
# 'b' はブロックデバイスを示す
# ブロックデバイスの一覧
lsblk
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
# sda 8:0 0 100G 0 disk
# ├─sda1 8:1 0 512M 0 part /boot/efi
# ├─sda2 8:2 0 1G 0 part /boot
# └─sda3 8:3 0 98.5G 0 part
# └─root 253:0 0 98.5G 0 lvm /
# ブロックデバイスの詳細情報
lsblk -f # ファイルシステム情報
lsblk -t # トポロジ情報
lsblk -d # ディスクのみ表示
/*
* ブロックデバイスドライバの基本構造 (Linux 6.x)
*/
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
struct my_block_dev {
struct gendisk *disk;
struct blk_mq_tag_set tag_set;
struct request_queue *queue;
unsigned char *data; /* デバイスデータ (RAM ディスクの場合) */
size_t size; /* デバイスサイズ */
};
/* blk-mq 操作 */
static blk_status_t my_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct request *rq = bd->rq;
struct my_block_dev *dev = rq->q->queuedata;
struct bio_vec bvec;
struct req_iterator iter;
sector_t sector = blk_rq_pos(rq);
blk_mq_start_request(rq);
rq_for_each_segment(bvec, rq, iter) {
unsigned long offset = sector * 512;
unsigned long nbytes = bvec.bv_len;
void *buffer = page_address(bvec.bv_page) + bvec.bv_offset;
if (rq_data_dir(rq) == READ)
memcpy(buffer, dev->data + offset, nbytes);
else
memcpy(dev->data + offset, buffer, nbytes);
sector += nbytes / 512;
}
blk_mq_end_request(rq, BLK_STS_OK);
return BLK_STS_OK;
}
static const struct blk_mq_ops my_mq_ops = {
.queue_rq = my_queue_rq,
};
static const struct block_device_operations my_block_ops = {
.owner = THIS_MODULE,
};
3.3 ネットワークデバイス (Network Device)
ネットワークデバイスはパケット単位でデータを送受信するデバイスである。/dev にデバイスファイルは作成されず、ソケットインターフェースを通じてアクセスする。
代表例:
- イーサネット (
eth0,enp0s3) - Wi-Fi (
wlan0,wlp2s0) - ループバック (
lo) - ブリッジ (
br0) - VLAN (
eth0.100) - トンネル (
tun0,tap0)
# ネットワークデバイスの確認
ip link show
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 ...
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
# 詳細な統計情報
ip -s link show eth0
cat /sys/class/net/eth0/statistics/rx_bytes
cat /sys/class/net/eth0/statistics/tx_bytes
# ドライバ情報
ethtool -i eth0
# driver: e1000e
# version: 6.1.0-k
# firmware-version: 0.4-4
# bus-info: 0000:00:19.0
/*
* ネットワークデバイスドライバの基本構造
*/
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
struct my_net_priv {
struct net_device_stats stats;
struct napi_struct napi;
/* ドライバ固有のデータ */
};
/* パケット送信 */
static netdev_tx_t my_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct my_net_priv *priv = netdev_priv(dev);
/* ハードウェアへのパケット転送処理 */
/* ... */
priv->stats.tx_packets++;
priv->stats.tx_bytes += skb->len;
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
/* デバイスオープン */
static int my_net_open(struct net_device *dev)
{
struct my_net_priv *priv = netdev_priv(dev);
napi_enable(&priv->napi);
netif_start_queue(dev);
return 0;
}
/* デバイスクローズ */
static int my_net_stop(struct net_device *dev)
{
struct my_net_priv *priv = netdev_priv(dev);
netif_stop_queue(dev);
napi_disable(&priv->napi);
return 0;
}
static const struct net_device_ops my_netdev_ops = {
.ndo_open = my_net_open,
.ndo_stop = my_net_stop,
.ndo_start_xmit = my_net_xmit,
.ndo_get_stats = my_net_get_stats,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
};
/* NAPI ポーリング */
static int my_napi_poll(struct napi_struct *napi, int budget)
{
struct my_net_priv *priv = container_of(napi, struct my_net_priv, napi);
struct net_device *dev = priv->napi.dev;
int work_done = 0;
while (work_done < budget) {
struct sk_buff *skb;
/* ハードウェアからパケットを受信 */
/* ... */
if (!skb)
break;
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
napi_gro_receive(napi, skb);
work_done++;
}
if (work_done < budget) {
napi_complete_done(napi, work_done);
/* 割り込みの再有効化 */
}
return work_done;
}
/* デバイスの登録 */
static int __init my_net_init(void)
{
struct net_device *dev;
struct my_net_priv *priv;
dev = alloc_etherdev(sizeof(struct my_net_priv));
if (!dev)
return -ENOMEM;
priv = netdev_priv(dev);
dev->netdev_ops = &my_netdev_ops;
/* NAPI の初期化 */
netif_napi_add(dev, &priv->napi, my_napi_poll);
/* MAC アドレスの設定 */
eth_hw_addr_random(dev);
if (register_netdev(dev)) {
free_netdev(dev);
return -ENODEV;
}
return 0;
}
3.4 デバイスタイプの比較表
| 特性 | キャラクタデバイス | ブロックデバイス | ネットワークデバイス |
|---|---|---|---|
| アクセス単位 | バイト | ブロック (通常512B/4KB) | パケット |
/dev エントリ | あり (c) | あり (b) | なし |
| バッファリング | なし | あり (ページキャッシュ) | あり (ソケットバッファ) |
| ランダムアクセス | 可能 | 可能 | 不可 |
| ユーザ空間 I/F | open/read/write/ioctl | open/read/write + FS | socket API |
| I/O スケジューラ | なし | あり (mq-deadline等) | なし |
| 登録 API | cdev_add() | add_disk() | register_netdev() |
4. デバイスドライバフレームワーク
4.1 struct cdev (キャラクタデバイス)
struct cdev はキャラクタデバイスを表現するカーネル内部構造体である。
/*
* cdev 構造体 (include/linux/cdev.h)
*/
struct cdev {
struct kobject kobj; /* kobject (参照カウント) */
struct module *owner; /* 所有モジュール */
const struct file_operations *ops; /* ファイル操作 */
struct list_head list; /* 関連 inode リスト */
dev_t dev; /* デバイス番号 */
unsigned int count; /* デバイス数 */
};
4.2 struct file_operations
struct file_operations はデバイスファイルに対する操作を定義する最も重要な構造体である。
/*
* file_operations 構造体の主要メンバ (include/linux/fs.h)
*/
struct file_operations {
struct module *owner;
/* ファイル位置の移動 */
loff_t (*llseek)(struct file *, loff_t, int);
/* データの読み取り */
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
/* データの書き込み */
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
/* 非同期 I/O 読み取り */
ssize_t (*read_iter)(struct kiocb *, struct iov_iter *);
/* 非同期 I/O 書き込み */
ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
/* ディレクトリ読み取り */
int (*iterate_shared)(struct file *, struct dir_context *);
/* ポーリング (select/poll/epoll) */
__poll_t (*poll)(struct file *, struct poll_table_struct *);
/* デバイス制御 */
long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
/* 32-bit 互換 ioctl */
long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
/* メモリマッピング */
int (*mmap)(struct file *, struct vm_area_struct *);
/* ファイルのオープン */
int (*open)(struct inode *, struct file *);
/* 最後のクローズ時のフラッシュ */
int (*flush)(struct file *, fl_owner_t id);
/* ファイルのクローズ */
int (*release)(struct inode *, struct file *);
/* データの同期 */
int (*fsync)(struct file *, loff_t, loff_t, int datasync);
/* 非同期通知 */
int (*fasync)(int, struct file *, int);
/* ファイルロック */
int (*lock)(struct file *, int, struct file_lock *);
/* ページの転送 */
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,
loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *,
struct pipe_inode_info *, size_t, unsigned int);
};
4.3 ユーザ空間とカーネル空間のデータ転送
ドライバのデータ転送では、ユーザ空間のメモリに直接アクセスできないため、専用の関数を使用する。
/*
* データ転送関数
*/
/* ユーザ空間からカーネル空間へコピー */
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
/* カーネル空間からユーザ空間へコピー */
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
/* 単一値の転送 */
int get_user(type val, const type __user *addr);
int put_user(type val, type __user *addr);
/*
* read 実装例
*/
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device *dev = filp->private_data;
ssize_t retval = 0;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
/* データサイズのチェック */
if (*f_pos >= dev->size)
goto out;
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
/* カーネル空間からユーザ空間へコピー */
if (copy_to_user(buf, dev->data + *f_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
mutex_unlock(&dev->lock);
return retval;
}
/*
* write 実装例
*/
static ssize_t my_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct my_device *dev = filp->private_data;
ssize_t retval = -ENOMEM;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
/* バッファサイズのチェック */
if (*f_pos + count > BUFFER_SIZE)
count = BUFFER_SIZE - *f_pos;
if (count == 0)
goto out;
/* ユーザ空間からカーネル空間へコピー */
if (copy_from_user(dev->data + *f_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
if (*f_pos > dev->size)
dev->size = *f_pos;
retval = count;
out:
mutex_unlock(&dev->lock);
return retval;
}
4.4 同期プリミティブ
ドライバ開発では適切な同期が不可欠である。
/*
* 主要な同期プリミティブ
*/
/* ミューテックス (スリープ可能なコンテキストで使用) */
#include <linux/mutex.h>
static DEFINE_MUTEX(my_mutex);
mutex_lock(&my_mutex); /* ロック取得 (スリープ可能) */
mutex_lock_interruptible(&my_mutex); /* シグナルで中断可能 */
mutex_trylock(&my_mutex); /* ノンブロッキングロック */
mutex_unlock(&my_mutex); /* ロック解放 */
/* スピンロック (割り込みコンテキストでも使用可能) */
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(my_spinlock);
spin_lock(&my_spinlock); /* ロック取得 */
spin_unlock(&my_spinlock); /* ロック解放 */
spin_lock_irqsave(&my_spinlock, flags); /* 割り込み禁止 + ロック */
spin_unlock_irqrestore(&my_spinlock, flags); /* ロック解放 + 割り込み復元 */
/* セマフォ */
#include <linux/semaphore.h>
static DEFINE_SEMAPHORE(my_sem, 1);
down(&my_sem); /* P 操作 */
down_interruptible(&my_sem); /* シグナルで中断可能 */
up(&my_sem); /* V 操作 */
/* RW ロック */
#include <linux/rwlock.h>
static DEFINE_RWLOCK(my_rwlock);
read_lock(&my_rwlock); /* 読み取りロック */
read_unlock(&my_rwlock);
write_lock(&my_rwlock); /* 書き込みロック */
write_unlock(&my_rwlock);
/* アトミック操作 */
#include <linux/atomic.h>
static atomic_t counter = ATOMIC_INIT(0);
atomic_inc(&counter); /* インクリメント */
atomic_dec(&counter); /* デクリメント */
atomic_read(&counter); /* 読み取り */
atomic_set(&counter, 0); /* 設定 */
/* 完了変数 (Completion) */
#include <linux/completion.h>
static DECLARE_COMPLETION(my_completion);
wait_for_completion(&my_completion); /* 完了を待機 */
wait_for_completion_timeout(&my_completion, timeout); /* タイムアウト付き */
complete(&my_completion); /* 完了を通知 */
complete_all(&my_completion); /* 全待機者に通知 */
5. PCI/PCIe ドライバサブシステム
PCI (Peripheral Component Interconnect) および PCIe (PCI Express) は、最も広く使われるハードウェアバス規格である。グラフィックスカード、ネットワークカード、ストレージコントローラなど、多くのデバイスが PCI/PCIe で接続される。
5.1 PCI デバイスの識別
PCI デバイスは以下の情報で一意に識別される:
- ドメイン (16bit): PCI ドメイン番号
- バス (8bit): バス番号 (0-255)
- デバイス (5bit): デバイス番号 (0-31)
- ファンクション (3bit): ファンクション番号 (0-7)
- ベンダー ID (16bit): ベンダーの識別子
- デバイス ID (16bit): デバイスの識別子
# PCI デバイスの一覧
lspci
# 00:00.0 Host bridge: Intel Corporation Device 9a14 (rev 01)
# 00:02.0 VGA compatible controller: Intel Corporation Device 9a49 (rev 03)
# 00:14.0 USB controller: Intel Corporation Device a0ed (rev 20)
# 00:1f.0 ISA bridge: Intel Corporation Device a082 (rev 20)
# 詳細表示
lspci -v
lspci -vv # さらに詳細
lspci -vvv # 最大詳細
# ツリー表示
lspci -t
# -[0000:00]-+-00.0
# +-02.0
# +-14.0
# \-1f.0
# 特定のデバイスの詳細
lspci -s 00:02.0 -v
# ベンダーID/デバイスIDの数値表示
lspci -nn
# 00:02.0 VGA compatible controller [0300]: Intel Corporation Device [8086:9a49]
# カーネルドライバの確認
lspci -k
# 00:02.0 VGA compatible controller: Intel Corporation Device 9a49
# Subsystem: Lenovo Device 2267
# Kernel driver in use: i915
# Kernel modules: i915
# PCI コンフィグレーション空間のダンプ
sudo lspci -s 00:02.0 -xxx
5.2 PCI ドライバの構造
/*
* PCI ドライバの基本構造
*/
#include <linux/pci.h>
#include <linux/module.h>
/* デバイス固有のデータ構造 */
struct my_pci_device {
void __iomem *mmio_base; /* MMIO ベースアドレス */
int irq; /* IRQ 番号 */
struct pci_dev *pdev; /* PCI デバイス */
/* ... */
};
/* サポートするデバイスの ID テーブル */
static const struct pci_device_id my_pci_ids[] = {
{ PCI_DEVICE(0x1234, 0x5678) }, /* ベンダー:デバイス */
{ PCI_DEVICE(0x1234, 0x5679) },
{ PCI_DEVICE_CLASS(PCI_CLASS_NETWORK_ETHERNET, ~0) }, /* クラスベース */
{ PCI_DEVICE_SUB(0x1234, 0x5678, 0xABCD, 0xEF01) }, /* サブシステムID付き */
{ 0, } /* 終端 */
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);
/*
* プローブ関数 - デバイスが検出された時に呼ばれる
*/
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct my_pci_device *mydev;
int ret;
/* 1. デバイスの有効化 */
ret = pci_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "Failed to enable PCI device\n");
return ret;
}
/* 2. バスマスターの有効化 (DMA 使用時に必要) */
pci_set_master(pdev);
/* 3. DMA マスクの設定 */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "No suitable DMA available\n");
goto err_disable;
}
}
/* 4. PCI リソース (BAR) の要求 */
ret = pci_request_regions(pdev, "my_pci_driver");
if (ret) {
dev_err(&pdev->dev, "Failed to request PCI regions\n");
goto err_disable;
}
/* 5. デバイス固有データの割り当て */
mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
if (!mydev) {
ret = -ENOMEM;
goto err_regions;
}
mydev->pdev = pdev;
/* 6. MMIO 領域のマッピング */
mydev->mmio_base = pci_iomap(pdev, 0, 0); /* BAR 0 */
if (!mydev->mmio_base) {
dev_err(&pdev->dev, "Failed to map MMIO\n");
ret = -EIO;
goto err_regions;
}
/* 7. 割り込みの設定 */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate IRQ vectors\n");
goto err_iomap;
}
mydev->irq = pci_irq_vector(pdev, 0);
ret = request_irq(mydev->irq, my_irq_handler, 0, "my_pci_driver", mydev);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", mydev->irq);
goto err_irq;
}
/* 8. ドライバデータの保存 */
pci_set_drvdata(pdev, mydev);
dev_info(&pdev->dev, "Device probed successfully\n");
return 0;
err_irq:
pci_free_irq_vectors(pdev);
err_iomap:
pci_iounmap(pdev, mydev->mmio_base);
err_regions:
pci_release_regions(pdev);
err_disable:
pci_disable_device(pdev);
return ret;
}
/*
* リムーブ関数 - デバイスが取り外された時に呼ばれる
*/
static void my_pci_remove(struct pci_dev *pdev)
{
struct my_pci_device *mydev = pci_get_drvdata(pdev);
free_irq(mydev->irq, mydev);
pci_free_irq_vectors(pdev);
pci_iounmap(pdev, mydev->mmio_base);
pci_release_regions(pdev);
pci_disable_device(pdev);
dev_info(&pdev->dev, "Device removed\n");
}
/*
* 電源管理
*/
static int my_pci_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct my_pci_device *mydev = pci_get_drvdata(pdev);
/* デバイスの状態を保存 */
/* 割り込みの無効化 */
free_irq(mydev->irq, mydev);
return 0;
}
static int my_pci_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct my_pci_device *mydev = pci_get_drvdata(pdev);
int ret;
/* デバイスの状態を復元 */
ret = request_irq(mydev->irq, my_irq_handler, 0, "my_pci_driver", mydev);
if (ret)
return ret;
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(my_pci_pm_ops,
my_pci_suspend, my_pci_resume);
/*
* エラーリカバリ
*/
static pci_ers_result_t my_pci_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
dev_err(&pdev->dev, "PCI error detected: %d\n", state);
if (state == pci_channel_io_perm_failure)
return PCI_ERS_RESULT_DISCONNECT;
return PCI_ERS_RESULT_NEED_RESET;
}
static pci_ers_result_t my_pci_slot_reset(struct pci_dev *pdev)
{
dev_info(&pdev->dev, "PCI slot reset\n");
if (pci_enable_device(pdev)) {
dev_err(&pdev->dev, "Cannot re-enable PCI device\n");
return PCI_ERS_RESULT_DISCONNECT;
}
pci_set_master(pdev);
return PCI_ERS_RESULT_RECOVERED;
}
static void my_pci_io_resume(struct pci_dev *pdev)
{
dev_info(&pdev->dev, "PCI I/O resumed\n");
}
static const struct pci_error_handlers my_pci_err_handler = {
.error_detected = my_pci_error_detected,
.slot_reset = my_pci_slot_reset,
.resume = my_pci_io_resume,
};
/*
* PCI ドライバ構造体の定義
*/
static struct pci_driver my_pci_driver = {
.name = "my_pci_driver",
.id_table = my_pci_ids,
.probe = my_pci_probe,
.remove = my_pci_remove,
.driver.pm = pm_sleep_ptr(&my_pci_pm_ops),
.err_handler = &my_pci_err_handler,
};
/* マクロを使った簡潔な登録 */
module_pci_driver(my_pci_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example PCI driver");
5.3 PCI BAR (Base Address Register)
/*
* BAR の操作
*/
/* BAR の情報取得 */
resource_size_t start = pci_resource_start(pdev, bar_num);
resource_size_t end = pci_resource_end(pdev, bar_num);
resource_size_t len = pci_resource_len(pdev, bar_num);
unsigned long flags = pci_resource_flags(pdev, bar_num);
/* BAR が MMIO かポートI/O かの判定 */
if (flags & IORESOURCE_MEM) {
pr_info("BAR %d is MMIO: 0x%llx - 0x%llx (len: 0x%llx)\n",
bar_num, start, end, len);
} else if (flags & IORESOURCE_IO) {
pr_info("BAR %d is PIO: 0x%llx - 0x%llx (len: 0x%llx)\n",
bar_num, start, end, len);
}
/* MMIO レジスタの読み書き */
u32 val = ioread32(mydev->mmio_base + REG_OFFSET);
iowrite32(new_val, mydev->mmio_base + REG_OFFSET);
/* 8/16/32 ビット版 */
u8 val8 = ioread8(mydev->mmio_base + offset);
u16 val16 = ioread16(mydev->mmio_base + offset);
u32 val32 = ioread32(mydev->mmio_base + offset);
iowrite8(val8, mydev->mmio_base + offset);
iowrite16(val16, mydev->mmio_base + offset);
iowrite32(val32, mydev->mmio_base + offset);
/* PCI コンフィグレーション空間の読み書き */
u16 vendor_id;
pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor_id);
u32 status_command;
pci_read_config_dword(pdev, PCI_COMMAND, &status_command);
5.4 MSI/MSI-X 割り込み
/*
* MSI-X 割り込みの設定
*/
#define MAX_MSIX_VECTORS 16
struct my_pci_device {
struct msix_entry msix_entries[MAX_MSIX_VECTORS];
int num_msix;
/* ... */
};
/* MSI-X の設定 (推奨方法) */
static int setup_msix(struct pci_dev *pdev, struct my_pci_device *mydev)
{
int ret;
int i;
/* MSI-X ベクターの割り当て (最小1, 最大MAX_MSIX_VECTORS) */
ret = pci_alloc_irq_vectors(pdev, 1, MAX_MSIX_VECTORS,
PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to allocate IRQ vectors: %d\n", ret);
return ret;
}
mydev->num_msix = ret;
dev_info(&pdev->dev, "Allocated %d MSI-X vectors\n", mydev->num_msix);
/* 各ベクターに割り込みハンドラを登録 */
for (i = 0; i < mydev->num_msix; i++) {
int irq = pci_irq_vector(pdev, i);
ret = request_irq(irq, my_msix_handler, 0,
"my_pci_msix", mydev);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", irq);
goto err_irq;
}
}
return 0;
err_irq:
while (--i >= 0)
free_irq(pci_irq_vector(pdev, i), mydev);
pci_free_irq_vectors(pdev);
return ret;
}
6. USB ドライバサブシステム
USB (Universal Serial Bus) は、ホットプラグ対応のバス規格であり、キーボード、マウス、ストレージ、ネットワークアダプタなど幅広いデバイスで使用される。
6.1 USB の基本概念
USB ホストコントローラ (xHCI/EHCI/OHCI)
│
├── USB ハブ (ルートハブ)
│ ├── USB デバイス (キーボード)
│ ├── USB デバイス (マウス)
│ └── USB ハブ (外部ハブ)
│ ├── USB デバイス (ストレージ)
│ └── USB デバイス (ネットワーク)
│
USB デバイスの構造:
デバイス
├── コンフィグレーション 1
│ ├── インターフェース 0
│ │ ├── エンドポイント 1 (IN, Bulk)
│ │ └── エンドポイント 2 (OUT, Bulk)
│ └── インターフェース 1
│ ├── エンドポイント 3 (IN, Interrupt)
│ └── エンドポイント 4 (OUT, Interrupt)
└── コンフィグレーション 2
└── ...
# USB デバイスの一覧
lsusb
# Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
# Bus 001 Device 002: ID 046d:c52b Logitech, Inc. Unifying Receiver
# Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
# 詳細表示
lsusb -v
# ツリー表示
lsusb -t
# /: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p
# /: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/12p
# |__ Port 1: Dev 2, If 0, Class=HID, Driver=usbhid
# 特定のデバイスの詳細
lsusb -s 001:002 -v
# USB デバイスの sysfs 情報
ls /sys/bus/usb/devices/
cat /sys/bus/usb/devices/1-1/idVendor
cat /sys/bus/usb/devices/1-1/idProduct
cat /sys/bus/usb/devices/1-1/manufacturer
cat /sys/bus/usb/devices/1-1/product
6.2 USB ドライバの構造
/*
* USB ドライバの基本構造
*/
#include <linux/usb.h>
#include <linux/module.h>
/* デバイス固有のデータ構造 */
struct my_usb_device {
struct usb_device *udev; /* USB デバイス */
struct usb_interface *interface; /* USB インターフェース */
unsigned char *bulk_in_buffer; /* 受信バッファ */
size_t bulk_in_size; /* バッファサイズ */
__u8 bulk_in_endpointAddr; /* IN エンドポイント */
__u8 bulk_out_endpointAddr; /* OUT エンドポイント */
struct urb *int_in_urb; /* 割り込み URB */
struct kref kref; /* 参照カウント */
struct mutex io_mutex; /* I/O ミューテックス */
};
/* サポートするデバイスの ID テーブル */
static const struct usb_device_id my_usb_ids[] = {
{ USB_DEVICE(0x1234, 0x5678) }, /* ベンダー:プロダクト */
{ USB_DEVICE_AND_INTERFACE_INFO(0x1234, 0x5678,
USB_CLASS_HID, 0, 0) }, /* インターフェースクラス付き */
{ USB_INTERFACE_INFO(USB_CLASS_PRINTER, 1, 2) }, /* クラスベース */
{ } /* 終端 */
};
MODULE_DEVICE_TABLE(usb, my_usb_ids);
/*
* プローブ関数
*/
static int my_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct my_usb_device *dev;
struct usb_endpoint_descriptor *bulk_in, *bulk_out;
int ret;
/* デバイス構造体の割り当て */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
kref_init(&dev->kref);
mutex_init(&dev->io_mutex);
dev->udev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = usb_get_intf(interface);
/* エンドポイントの検索 */
ret = usb_find_common_endpoints(interface->cur_altsetting,
&bulk_in, &bulk_out, NULL, NULL);
if (ret) {
dev_err(&interface->dev, "Could not find bulk endpoints\n");
goto error;
}
/* バルク IN エンドポイントの設定 */
dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
if (!dev->bulk_in_buffer) {
ret = -ENOMEM;
goto error;
}
/* バルク OUT エンドポイントの設定 */
dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
/* インターフェースデータの保存 */
usb_set_intfdata(interface, dev);
/* USB クラスデバイスの登録 */
ret = usb_register_dev(interface, &my_usb_class_driver);
if (ret) {
dev_err(&interface->dev, "Failed to register USB device\n");
usb_set_intfdata(interface, NULL);
goto error;
}
dev_info(&interface->dev, "USB device connected (minor=%d)\n",
interface->minor);
return 0;
error:
kref_put(&dev->kref, my_usb_delete);
return ret;
}
/*
* 切断関数
*/
static void my_usb_disconnect(struct usb_interface *interface)
{
struct my_usb_device *dev = usb_get_intfdata(interface);
usb_deregister_dev(interface, &my_usb_class_driver);
usb_set_intfdata(interface, NULL);
mutex_lock(&dev->io_mutex);
dev->interface = NULL;
mutex_unlock(&dev->io_mutex);
kref_put(&dev->kref, my_usb_delete);
dev_info(&interface->dev, "USB device disconnected\n");
}
/*
* USB ドライバ構造体
*/
static struct usb_driver my_usb_driver = {
.name = "my_usb_driver",
.id_table = my_usb_ids,
.probe = my_usb_probe,
.disconnect = my_usb_disconnect,
.suspend = my_usb_suspend,
.resume = my_usb_resume,
.supports_autosuspend = 1,
};
module_usb_driver(my_usb_driver);
6.3 URB (USB Request Block)
/*
* URB の使用例
*/
/* バルク転送 (同期) */
static ssize_t my_usb_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct my_usb_device *dev = file->private_data;
int actual_length;
int ret;
/* バルク IN 転送 */
ret = usb_bulk_msg(dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(count, dev->bulk_in_size),
&actual_length,
5000); /* タイムアウト 5秒 */
if (ret)
return ret;
if (copy_to_user(buffer, dev->bulk_in_buffer, actual_length))
return -EFAULT;
return actual_length;
}
/* バルク転送 (非同期) */
static void my_bulk_complete(struct urb *urb)
{
struct my_usb_device *dev = urb->context;
if (urb->status) {
dev_err(&dev->interface->dev,
"Bulk transfer error: %d\n", urb->status);
return;
}
/* 受信データの処理 */
pr_info("Received %d bytes\n", urb->actual_length);
}
static int submit_bulk_urb(struct my_usb_device *dev)
{
struct urb *urb;
int ret;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return -ENOMEM;
usb_fill_bulk_urb(urb,
dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
dev->bulk_in_size,
my_bulk_complete,
dev);
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
usb_free_urb(urb);
return ret;
}
return 0;
}
/* コントロール転送 */
static int my_usb_control(struct my_usb_device *dev)
{
int ret;
unsigned char *buffer;
buffer = kzalloc(64, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
/* GET_DESCRIPTOR リクエスト */
ret = usb_control_msg(dev->udev,
usb_rcvctrlpipe(dev->udev, 0),
USB_REQ_GET_DESCRIPTOR, /* bRequest */
USB_DIR_IN | USB_TYPE_STANDARD, /* bmRequestType */
USB_DT_DEVICE << 8, /* wValue */
0, /* wIndex */
buffer, /* data */
64, /* wLength */
5000); /* timeout */
if (ret < 0) {
dev_err(&dev->interface->dev,
"Control transfer failed: %d\n", ret);
}
kfree(buffer);
return ret;
}
7. プラットフォームデバイスとデバイスツリー
7.1 プラットフォームデバイス
プラットフォームデバイスは、PCI や USB のようなディスカバリメカニズムを持たないデバイスのために設計されたフレームワークである。SoC 上の周辺デバイス (UART, SPI, I2C, GPIO など) に広く使用される。
/*
* プラットフォームデバイスの構造体 (include/linux/platform_device.h)
*/
struct platform_device {
const char *name; /* デバイス名 */
int id; /* デバイス ID */
bool id_auto; /* ID 自動割当 */
struct device dev; /* デバイス構造体 */
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources; /* リソース数 */
struct resource *resource; /* リソース配列 */
const struct platform_device_id *id_entry;
const char *driver_override;
struct mfd_cell *mfd_cell;
struct pdev_archdata archdata;
};
/*
* プラットフォームドライバの完全な例
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/interrupt.h>
struct my_platform_dev {
void __iomem *regs;
int irq;
struct clk *clk;
struct reset_control *rst;
};
static int my_platform_probe(struct platform_device *pdev)
{
struct my_platform_dev *mydev;
struct resource *res;
int ret;
/* デバイス管理メモリの割り当て */
mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
if (!mydev)
return -ENOMEM;
/* MMIO リソースの取得とマッピング */
mydev->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mydev->regs))
return PTR_ERR(mydev->regs);
/* IRQ の取得 */
mydev->irq = platform_get_irq(pdev, 0);
if (mydev->irq < 0)
return mydev->irq;
/* 割り込みハンドラの登録 */
ret = devm_request_irq(&pdev->dev, mydev->irq,
my_irq_handler, 0,
dev_name(&pdev->dev), mydev);
if (ret)
return ret;
/* クロックの取得と有効化 */
mydev->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(mydev->clk))
return PTR_ERR(mydev->clk);
ret = clk_prepare_enable(mydev->clk);
if (ret)
return ret;
/* リセットコントローラの取得と解除 */
mydev->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(mydev->rst)) {
clk_disable_unprepare(mydev->clk);
return PTR_ERR(mydev->rst);
}
reset_control_deassert(mydev->rst);
/* デバイスツリーからプロパティを読み取り */
u32 fifo_size;
ret = of_property_read_u32(pdev->dev.of_node, "fifo-size", &fifo_size);
if (ret)
fifo_size = 256; /* デフォルト値 */
dev_info(&pdev->dev, "FIFO size: %u\n", fifo_size);
platform_set_drvdata(pdev, mydev);
dev_info(&pdev->dev, "Platform device probed\n");
return 0;
}
static void my_platform_remove(struct platform_device *pdev)
{
struct my_platform_dev *mydev = platform_get_drvdata(pdev);
reset_control_assert(mydev->rst);
clk_disable_unprepare(mydev->clk);
dev_info(&pdev->dev, "Platform device removed\n");
}
/* デバイスツリーマッチテーブル */
static const struct of_device_id my_platform_of_match[] = {
{ .compatible = "vendor,my-device" },
{ .compatible = "vendor,my-device-v2", .data = (void *)2 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_platform_of_match);
/* ACPI マッチテーブル (x86 環境用) */
static const struct acpi_device_id my_platform_acpi_match[] = {
{ "MYDEV001", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, my_platform_acpi_match);
/* プラットフォーム ID テーブル */
static const struct platform_device_id my_platform_ids[] = {
{ "my-device", 0 },
{ "my-device-v2", 2 },
{ }
};
MODULE_DEVICE_TABLE(platform, my_platform_ids);
static struct platform_driver my_platform_driver = {
.probe = my_platform_probe,
.remove = my_platform_remove,
.id_table = my_platform_ids,
.driver = {
.name = "my-platform-device",
.of_match_table = my_platform_of_match,
.acpi_match_table = my_platform_acpi_match,
.pm = &my_platform_pm_ops,
},
};
module_platform_driver(my_platform_driver);
7.2 デバイスツリー (Device Tree)
デバイスツリーは、ARM や RISC-V などのアーキテクチャで使用されるハードウェア記述フォーマットである。
/*
* デバイスツリーソース (.dts) の例
*/
/dts-v1/;
/ {
model = "My Custom Board";
compatible = "vendor,my-board";
#address-cells = <2>;
#size-cells = <2>;
/* メモリ定義 */
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>; /* 1GB @ 0x80000000 */
};
/* SoC バス */
soc {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* UART コントローラ */
uart0: serial@10000000 {
compatible = "vendor,my-uart";
reg = <0x0 0x10000000 0x0 0x1000>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_uart>;
clock-names = "uart_clk";
resets = <&rst_ctrl 5>;
fifo-size = <64>;
status = "okay";
};
/* SPI コントローラ */
spi0: spi@10001000 {
compatible = "vendor,my-spi";
reg = <0x0 0x10001000 0x0 0x1000>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_spi>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
/* SPI スレーブデバイス */
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <50000000>;
};
};
/* I2C コントローラ */
i2c0: i2c@10002000 {
compatible = "vendor,my-i2c";
reg = <0x0 0x10002000 0x0 0x1000>;
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_i2c>;
#address-cells = <1>;
#size-cells = <0>;
clock-frequency = <400000>;
status = "okay";
/* I2C スレーブデバイス */
temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
eeprom@50 {
compatible = "atmel,24c256";
reg = <0x50>;
pagesize = <64>;
};
};
/* GPIO コントローラ */
gpio0: gpio@10003000 {
compatible = "vendor,my-gpio";
reg = <0x0 0x10003000 0x0 0x1000>;
interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
ngpios = <32>;
};
/* クロックコントローラ */
clock-controller@10004000 {
compatible = "vendor,my-clk";
reg = <0x0 0x10004000 0x0 0x1000>;
#clock-cells = <1>;
clk_uart: uart_clk {
compatible = "fixed-clock";
clock-frequency = <115200>;
#clock-cells = <0>;
};
clk_spi: spi_clk {
compatible = "fixed-clock";
clock-frequency = <100000000>;
#clock-cells = <0>;
};
clk_i2c: i2c_clk {
compatible = "fixed-clock";
clock-frequency = <50000000>;
#clock-cells = <0>;
};
};
};
/* LED */
leds {
compatible = "gpio-leds";
led0 {
label = "status";
gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
led1 {
label = "activity";
gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "disk-activity";
};
};
};
# デバイスツリーの操作コマンド
# DTS から DTB へのコンパイル
dtc -I dts -O dtb -o my_board.dtb my_board.dts
# DTB から DTS へのデコンパイル
dtc -I dtb -O dts -o my_board.dts my_board.dtb
# 実行中のシステムのデバイスツリーの確認
ls /proc/device-tree/
cat /proc/device-tree/model
# dtc を使ったデバイスツリーの検証
dtc -I dts -O dtb -f my_board.dts -o /dev/null
# デバイスツリーオーバーレイの適用
dtc -I dts -O dtb -@ overlay.dts -o overlay.dtbo
# (ブートローダーまたは ConfigFS で適用)
7.3 devm_ (デバイス管理リソース) API
devm_ プレフィックスの API は、デバイスのライフサイクルに紐づいたリソース管理を提供する。ドライバのリムーブ時に自動的に解放される。
/*
* devm_ API の一覧と使用例
*/
/* メモリ割り当て */
void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp);
void *devm_kcalloc(struct device *dev, size_t n, size_t size, gfp_t gfp);
/* I/O メモリマッピング */
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
resource_size_t size);
void __iomem *devm_ioremap_resource(struct device *dev,
const struct resource *res);
void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev,
unsigned int index);
/* 割り込み */
int devm_request_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, unsigned long irqflags,
const char *devname, void *dev_id);
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long irqflags,
const char *devname, void *dev_id);
/* クロック */
struct clk *devm_clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get_optional(struct device *dev, const char *id);
/* レギュレータ */
struct regulator *devm_regulator_get(struct device *dev, const char *id);
/* GPIO */
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
enum gpiod_flags flags);
/* リセット */
struct reset_control *devm_reset_control_get_exclusive(struct device *dev,
const char *id);
/*
* devm_ を使うことで、エラーパスでのクリーンアップが大幅に簡略化される
*/
static int my_probe(struct platform_device *pdev)
{
struct my_dev *dev;
int ret;
/* すべてのリソースが自動管理される */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dev->regs))
return PTR_ERR(dev->regs); /* 自動クリーンアップ */
dev->irq = platform_get_irq(pdev, 0);
if (dev->irq < 0)
return dev->irq; /* 自動クリーンアップ */
ret = devm_request_irq(&pdev->dev, dev->irq, my_isr, 0,
"my-device", dev);
if (ret)
return ret; /* 自動クリーンアップ */
dev->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(dev->clk))
return PTR_ERR(dev->clk); /* 自動クリーンアップ */
/* すべて成功 - goto error 不要 */
return 0;
}
8. DMA (Direct Memory Access)
DMA は、CPU を介さずにデバイスとメモリ間でデータを転送する仕組みである。大量のデータ転送において CPU 負荷を大幅に削減できる。
8.1 DMA の概念
通常のI/O (PIO - Programmed I/O):
CPU が1バイトずつデータを転送
CPU ──── read ────► デバイス
CPU ◄─── data ──── デバイス
CPU ──── write ───► メモリ
(繰り返し)
DMA 転送:
DMA コントローラがデータを直接転送
CPU: DMAコントローラに転送を指示
↓
DMAコントローラ ◄──► デバイス
DMAコントローラ ◄──► メモリ
↓
DMAコントローラ: CPU に完了を通知 (割り込み)
8.2 DMA マッピングの種類
/*
* 1. コヒーレント DMA マッピング (Coherent/Consistent)
* - キャッシュコヒーレンシが保証される
* - 長期間使用するバッファに適する
* - DMA 記述子テーブルなどに使用
*/
#include <linux/dma-mapping.h>
dma_addr_t dma_handle;
void *cpu_addr;
/* コヒーレントバッファの確保 */
cpu_addr = dma_alloc_coherent(&pdev->dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr) {
dev_err(&pdev->dev, "Failed to allocate DMA coherent buffer\n");
return -ENOMEM;
}
/* cpu_addr: CPU 側の仮想アドレス (カーネルから読み書き) */
/* dma_handle: デバイス側の DMA アドレス (デバイスレジスタに設定) */
/* ハードウェアレジスタに DMA アドレスを設定 */
iowrite32(lower_32_bits(dma_handle), regs + DMA_ADDR_LOW);
iowrite32(upper_32_bits(dma_handle), regs + DMA_ADDR_HIGH);
/* コヒーレントバッファの解放 */
dma_free_coherent(&pdev->dev, size, cpu_addr, dma_handle);
/*
* 2. ストリーミング DMA マッピング (Streaming)
* - 一時的なデータ転送に使用
* - 転送方向の指定が必要
* - 手動でのキャッシュ同期が必要
*/
/* 単一バッファのマッピング */
dma_addr_t dma_addr;
void *buffer = kmalloc(size, GFP_KERNEL);
/* DMA_TO_DEVICE: CPU → デバイス (送信) */
/* DMA_FROM_DEVICE: デバイス → CPU (受信) */
/* DMA_BIDIRECTIONAL: 双方向 */
dma_addr = dma_map_single(&pdev->dev, buffer, size, DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_addr)) {
dev_err(&pdev->dev, "DMA mapping failed\n");
kfree(buffer);
return -ENOMEM;
}
/* DMA 転送の実行 */
/* ... */
/* マッピングの解除 */
dma_unmap_single(&pdev->dev, dma_addr, size, DMA_TO_DEVICE);
/*
* 3. Scatter-Gather DMA マッピング
* - 物理的に不連続なメモリを一度にマッピング
* - 大容量データ転送に効率的
*/
struct scatterlist sg[NUM_SG];
int nents;
sg_init_table(sg, NUM_SG);
/* 各散布リストエントリの設定 */
sg_set_buf(&sg[0], buffer0, len0);
sg_set_buf(&sg[1], buffer1, len1);
sg_set_buf(&sg[2], buffer2, len2);
/* SG リストのマッピング */
nents = dma_map_sg(&pdev->dev, sg, NUM_SG, DMA_TO_DEVICE);
if (nents == 0) {
dev_err(&pdev->dev, "SG DMA mapping failed\n");
return -ENOMEM;
}
/* 各エントリの DMA アドレスとサイズを使用 */
for (int i = 0; i < nents; i++) {
dma_addr_t addr = sg_dma_address(&sg[i]);
unsigned int len = sg_dma_len(&sg[i]);
/* ハードウェアの SG テーブルに設定 */
}
/* SG リストのアンマッピング */
dma_unmap_sg(&pdev->dev, sg, NUM_SG, DMA_TO_DEVICE);
8.3 DMA マスクの設定
/*
* DMA マスクの設定
*/
/* 64-bit DMA アドレス対応 */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
/* 64-bit に失敗した場合、32-bit にフォールバック */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "No suitable DMA configuration\n");
return ret;
}
dev_info(&pdev->dev, "Using 32-bit DMA\n");
} else {
dev_info(&pdev->dev, "Using 64-bit DMA\n");
}
/* ストリーミングマスクのみ設定 */
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(48));
/* コヒーレントマスクのみ設定 */
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
8.4 DMA プール
/*
* DMA プール - 小さなコヒーレントバッファの効率的な管理
*/
#include <linux/dmapool.h>
struct dma_pool *pool;
/* プールの作成 */
pool = dma_pool_create("my_dma_pool",
&pdev->dev,
256, /* 各エントリのサイズ */
16, /* アライメント */
4096); /* バウンダリ (ページ境界をまたがない) */
if (!pool) {
return -ENOMEM;
}
/* プールからバッファを確保 */
dma_addr_t dma_handle;
void *buffer = dma_pool_alloc(pool, GFP_KERNEL, &dma_handle);
/* バッファの返却 */
dma_pool_free(pool, buffer, dma_handle);
/* プールの破棄 */
dma_pool_destroy(pool);
8.5 DMA エンジン API
/*
* DMA エンジン (dmaengine) API
* 汎用 DMA コントローラを使用する場合
*/
#include <linux/dmaengine.h>
struct dma_chan *chan;
struct dma_async_tx_descriptor *desc;
dma_cookie_t cookie;
/* DMA チャネルの取得 */
chan = dma_request_chan(&pdev->dev, "tx");
if (IS_ERR(chan)) {
dev_err(&pdev->dev, "Failed to request DMA channel\n");
return PTR_ERR(chan);
}
/* メモリ間転送の設定 */
desc = dmaengine_prep_dma_memcpy(chan, dst_dma, src_dma, len,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
if (!desc) {
dev_err(&pdev->dev, "Failed to prepare DMA descriptor\n");
return -ENOMEM;
}
/* コールバックの設定 */
desc->callback = my_dma_complete;
desc->callback_param = mydev;
/* DMA 転送の送信 */
cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie)) {
dev_err(&pdev->dev, "Failed to submit DMA\n");
return -EIO;
}
/* 転送の開始 */
dma_async_issue_pending(chan);
/* 転送完了の待機 (同期的に行う場合) */
enum dma_status status;
status = dma_sync_wait(chan, cookie);
if (status != DMA_COMPLETE) {
dev_err(&pdev->dev, "DMA transfer failed: %d\n", status);
}
/* DMA チャネルの解放 */
dma_release_channel(chan);
9. IOMMU とデバイス分離
IOMMU (Input/Output Memory Management Unit) は、デバイスのメモリアクセスを仮想化・制御するハードウェアコンポーネントである。
9.1 IOMMU の役割
IOMMU なし:
デバイス ──── 物理アドレス ────► 物理メモリ
(デバイスは任意の物理メモリにアクセス可能 → セキュリティリスク)
IOMMU あり:
デバイス ──── IOVA ────► IOMMU ──── 物理アドレス ────► 物理メモリ
(デバイスのメモリアクセスが IOMMU テーブルで制限される)
IOVA = I/O Virtual Address
IOMMU の主な利点:
- デバイス分離: デバイスが他のデバイスやカーネルのメモリにアクセスすることを防止
- DMA リマッピング: 物理的に不連続なメモリを連続したアドレス空間として見せる
- デバイスの仮想化: VT-d/AMD-Vi を通じた PCI デバイスの直接パススルー
- DMA 保護: 悪意のあるデバイスからのメモリ攻撃を防止
9.2 IOMMU の設定
# IOMMU の有効化 (カーネルパラメータ)
# Intel VT-d
intel_iommu=on
# AMD-Vi
amd_iommu=on
# IOMMU グループの確認
ls /sys/kernel/iommu_groups/
for g in /sys/kernel/iommu_groups/*/devices/*; do
echo "IOMMU Group $(basename $(dirname $(dirname $g))):"
echo " $(lspci -nns $(basename $g))"
done
# IOMMU ドメインの確認
dmesg | grep -i iommu
# [ 0.000000] DMAR: IOMMU enabled
# [ 0.012345] DMAR: Host address width 39
# [ 0.012345] DMAR: DRHD base: 0x000000fed90000 flags: 0x0
# IOMMU のモード確認
cat /sys/kernel/iommu_groups/0/type
# DMA-FQ (default), identity, or DMA
9.3 VFIO (Virtual Function I/O)
# VFIO を使った PCI デバイスのパススルー (仮想化環境向け)
# 1. IOMMU グループの確認
readlink -f /sys/bus/pci/devices/0000:01:00.0/iommu_group
# /sys/kernel/iommu_groups/1
# 2. 現在のドライバからアンバインド
echo "0000:01:00.0" > /sys/bus/pci/devices/0000:01:00.0/driver/unbind
# 3. vfio-pci ドライバへのバインド
modprobe vfio-pci
echo "8086 1533" > /sys/bus/pci/drivers/vfio-pci/new_id
# 4. QEMU/KVM での使用
qemu-system-x86_64 \
-device vfio-pci,host=0000:01:00.0 \
-m 4G \
-cpu host \
-enable-kvm \
...
9.4 IOMMU API (ドライバ内)
/*
* IOMMU API の使用例
*/
#include <linux/iommu.h>
/* IOMMU ドメインの確保 */
struct iommu_domain *domain;
domain = iommu_domain_alloc(dev->bus);
if (!domain)
return -ENOMEM;
/* デバイスの IOMMU ドメインへのアタッチ */
ret = iommu_attach_device(domain, dev);
if (ret)
goto err_domain;
/* IOVA マッピングの作成 */
ret = iommu_map(domain,
iova, /* I/O 仮想アドレス */
phys_addr, /* 物理アドレス */
size, /* サイズ */
IOMMU_READ | IOMMU_WRITE, /* パーミッション */
GFP_KERNEL);
if (ret)
goto err_detach;
/* マッピングの解除 */
iommu_unmap(domain, iova, size);
/* デバイスのデタッチ */
iommu_detach_device(domain, dev);
/* ドメインの解放 */
iommu_domain_free(domain);
10. 割り込みハンドリング
割り込み (Interrupt) は、ハードウェアが CPU に非同期的にイベントを通知する仕組みである。デバイスドライバにおいて、割り込み処理は最も重要な要素の一つである。
10.1 割り込みの種類
ハードウェア割り込み (IRQ):
├── レガシー割り込み (INTx)
│ ├── レベルトリガ
│ └── エッジトリガ
├── MSI (Message Signaled Interrupt)
│ └── 最大32ベクター
└── MSI-X (Extended MSI)
└── 最大2048ベクター
ソフトウェア割り込み:
├── softirq
├── tasklet
└── workqueue
10.2 割り込みハンドラの登録
/*
* 基本的な割り込みハンドラ
*/
#include <linux/interrupt.h>
/* ハードウェア割り込みハンドラ (トップハーフ) */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
u32 status;
/* 割り込みステータスの確認 */
status = ioread32(dev->regs + IRQ_STATUS_REG);
if (!(status & MY_IRQ_MASK))
return IRQ_NONE; /* この割り込みは自分のデバイスではない */
/* 割り込みのクリア */
iowrite32(status, dev->regs + IRQ_CLEAR_REG);
/* 最小限の処理のみ行う */
/* (スリープ不可、ロック最小限) */
/* 下半分 (ボトムハーフ) の処理をスケジュール */
/* 方法1: tasklet */
tasklet_schedule(&dev->my_tasklet);
/* 方法2: workqueue */
schedule_work(&dev->my_work);
return IRQ_HANDLED;
}
/* 割り込みハンドラの登録 */
ret = request_irq(irq_num,
my_irq_handler,
IRQF_SHARED, /* 共有割り込み */
"my_device", /* /proc/interrupts での名前 */
my_dev); /* dev_id (共有割り込み時に識別) */
if (ret) {
pr_err("Failed to request IRQ %d: %d\n", irq_num, ret);
return ret;
}
/* 割り込みハンドラの解除 */
free_irq(irq_num, my_dev);
10.3 スレッド化された割り込み (Threaded IRQ)
/*
* スレッド化された割り込み
* ハードIRQ部分は最小限、メイン処理はカーネルスレッドで実行
*/
/* ハードIRQ ハンドラ (高速部分) */
static irqreturn_t my_irq_quick(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
u32 status;
status = ioread32(dev->regs + IRQ_STATUS_REG);
if (!(status & MY_IRQ_MASK))
return IRQ_NONE;
/* 割り込みの無効化 (スレッドハンドラで再有効化) */
iowrite32(0, dev->regs + IRQ_ENABLE_REG);
/* ステータスを保存 */
dev->irq_status = status;
return IRQ_WAKE_THREAD; /* スレッドハンドラを起動 */
}
/* スレッドハンドラ (プロセスコンテキスト、スリープ可能) */
static irqreturn_t my_irq_thread(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
/* ここではスリープ可能な処理ができる */
mutex_lock(&dev->lock);
/* データの処理 */
if (dev->irq_status & DATA_READY) {
/* バッファの読み取り */
/* ユーザ空間への通知 (wait_queue) */
wake_up_interruptible(&dev->wait_queue);
}
if (dev->irq_status & ERROR_FLAG) {
/* エラー処理 */
dev_err(dev->dev, "Device error: 0x%x\n", dev->irq_status);
}
mutex_unlock(&dev->lock);
/* 割り込みの再有効化 */
iowrite32(MY_IRQ_MASK, dev->regs + IRQ_ENABLE_REG);
return IRQ_HANDLED;
}
/* スレッド化された割り込みの登録 */
ret = request_threaded_irq(irq_num,
my_irq_quick, /* ハードIRQ ハンドラ */
my_irq_thread, /* スレッドハンドラ */
IRQF_SHARED | IRQF_ONESHOT,
"my_device",
my_dev);
/* devm_ バージョン */
ret = devm_request_threaded_irq(&pdev->dev, irq_num,
my_irq_quick,
my_irq_thread,
IRQF_SHARED | IRQF_ONESHOT,
dev_name(&pdev->dev),
my_dev);
10.4 ボトムハーフメカニズム
/*
* Tasklet - ソフトIRQ コンテキストで実行 (スリープ不可)
*/
static void my_tasklet_fn(unsigned long data)
{
struct my_device *dev = (struct my_device *)data;
/* 割り込みの延期処理 */
}
DECLARE_TASKLET(my_tasklet, my_tasklet_fn, (unsigned long)my_dev);
/* スケジュール */
tasklet_schedule(&my_tasklet);
/* 無効化と破棄 */
tasklet_disable(&my_tasklet);
tasklet_kill(&my_tasklet);
/*
* Workqueue - プロセスコンテキストで実行 (スリープ可能)
*/
static void my_work_fn(struct work_struct *work)
{
struct my_device *dev = container_of(work, struct my_device, my_work);
/* スリープ可能な処理 */
mutex_lock(&dev->lock);
/* ... */
mutex_unlock(&dev->lock);
}
INIT_WORK(&my_dev->my_work, my_work_fn);
/* スケジュール (デフォルトの workqueue) */
schedule_work(&my_dev->my_work);
/* 遅延付きスケジュール */
INIT_DELAYED_WORK(&my_dev->delayed_work, my_delayed_fn);
schedule_delayed_work(&my_dev->delayed_work, msecs_to_jiffies(100));
/* カスタム workqueue の作成 */
struct workqueue_struct *my_wq;
my_wq = alloc_workqueue("my_wq",
WQ_UNBOUND | WQ_HIGHPRI,
0); /* max_active: 0 = デフォルト */
queue_work(my_wq, &my_dev->my_work);
/* workqueue の破棄 */
flush_workqueue(my_wq);
destroy_workqueue(my_wq);
10.5 割り込み情報の確認
# 割り込みの統計
cat /proc/interrupts
# CPU0 CPU1 CPU2 CPU3
# 0: 44 0 0 0 IO-APIC 2-edge timer
# 8: 0 0 0 0 IO-APIC 8-edge rtc0
# 16: 12345 0 0 0 IO-APIC 16-fasteoi ehci_hcd
# 127: 0 0 0 0 PCI-MSI 327680-edge xhci_hcd
# NMI: 5 5 5 5 Non-maskable interrupts
# LOC: 987654 876543 765432 654321 Local timer interrupts
# IRQ アフィニティの確認と設定
cat /proc/irq/127/smp_affinity
# f (すべての CPU)
echo 2 > /proc/irq/127/smp_affinity
# IRQ 127 を CPU 1 にピン留め (ビットマスク: 0010)
# IRQ バランシング
cat /proc/irq/127/smp_affinity_list
echo "0-1" > /proc/irq/127/smp_affinity_list
# irqbalance デーモンの確認
systemctl status irqbalance
11. sysfs とデバイス属性
sysfs は、カーネルオブジェクト (デバイス、ドライバ、バスなど) の情報をユーザ空間に公開する仮想ファイルシステムである。
11.1 デバイス属性の作成
/*
* sysfs 属性の定義
*/
/* 読み取り専用属性 */
static ssize_t my_attr_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct my_device *mydev = dev_get_drvdata(dev);
return sysfs_emit(buf, "%d\n", mydev->value);
}
/* 読み書き属性 */
static ssize_t my_attr_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct my_device *mydev = dev_get_drvdata(dev);
int ret;
unsigned int val;
ret = kstrtouint(buf, 10, &val);
if (ret)
return ret;
if (val > MAX_VALUE)
return -EINVAL;
mydev->value = val;
return count;
}
/* DEVICE_ATTR マクロ */
static DEVICE_ATTR_RO(status); /* 読み取り専用 (status_show) */
static DEVICE_ATTR_WO(reset); /* 書き込み専用 (reset_store) */
static DEVICE_ATTR_RW(my_attr); /* 読み書き (my_attr_show, my_attr_store) */
/*
* 属性グループ
*/
static struct attribute *my_device_attrs[] = {
&dev_attr_status.attr,
&dev_attr_reset.attr,
&dev_attr_my_attr.attr,
NULL,
};
/* 可視性制御 (条件に応じて属性の表示/非表示を制御) */
static umode_t my_attrs_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct my_device *mydev = dev_get_drvdata(dev);
/* 特定の条件で属性を非表示 */
if (attr == &dev_attr_reset.attr && !mydev->supports_reset)
return 0;
return attr->mode;
}
static const struct attribute_group my_attr_group = {
.attrs = my_device_attrs,
.is_visible = my_attrs_visible,
};
static const struct attribute_group *my_attr_groups[] = {
&my_attr_group,
NULL,
};
/*
* probe 関数で属性グループを指定する方法
*/
static int my_probe(struct platform_device *pdev)
{
struct my_device *mydev;
mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
/* ... */
/* 方法1: デバイス作成時に指定 */
pdev->dev.groups = my_attr_groups;
/* 方法2: 手動で追加 */
ret = sysfs_create_group(&pdev->dev.kobj, &my_attr_group);
if (ret)
return ret;
return 0;
}
/* 手動追加した場合の解除 */
static void my_remove(struct platform_device *pdev)
{
sysfs_remove_group(&pdev->dev.kobj, &my_attr_group);
}
11.2 バイナリ属性
/*
* バイナリ (raw) sysfs 属性
* ファームウェアデータなど、テキストでない属性に使用
*/
static ssize_t firmware_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct my_device *mydev = dev_get_drvdata(dev);
if (off >= mydev->fw_size)
return 0;
if (off + count > mydev->fw_size)
count = mydev->fw_size - off;
memcpy(buf, mydev->fw_data + off, count);
return count;
}
static ssize_t firmware_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct my_device *mydev = dev_get_drvdata(dev);
/* ファームウェアの書き込み処理 */
memcpy(mydev->fw_data + off, buf, count);
return count;
}
static BIN_ATTR_RW(firmware, 0); /* サイズ 0 = 動的サイズ */
11.3 sysfs の活用例
# デバイス属性の読み書き
cat /sys/class/net/eth0/mtu
echo 9000 > /sys/class/net/eth0/mtu
# CPU 情報
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# ブロックデバイスのスケジューラ
cat /sys/block/sda/queue/scheduler
echo "mq-deadline" > /sys/block/sda/queue/scheduler
# PCI デバイスの電源管理
cat /sys/bus/pci/devices/0000:00:02.0/power/runtime_status
echo "auto" > /sys/bus/pci/devices/0000:00:02.0/power/control
# GPU の情報 (Intel)
cat /sys/class/drm/card0/gt_cur_freq_mhz
cat /sys/class/drm/card0/gt_max_freq_mhz
# バッテリー情報
cat /sys/class/power_supply/BAT0/capacity
cat /sys/class/power_supply/BAT0/status
# メモリ情報
cat /sys/devices/system/memory/memory0/state
12. udev ルールとデバイス管理
udev は Linux のデバイスマネージャであり、カーネルからの uevent をもとに /dev ディレクトリのデバイスノードを動的に管理する。
12.1 udev の動作原理
カーネル空間:
デバイスの検出/切断
│
▼
uevent の生成 (kobject_uevent)
│
▼
netlink ソケット経由で送信
ユーザ空間:
udevd デーモン
│
▼
uevent の受信
│
▼
udev ルールの評価
│
▼
デバイスノードの作成/削除
シンボリックリンクの作成
権限の設定
外部コマンドの実行
12.2 udev ルールの書き方
# ルールファイルの場所
# /etc/udev/rules.d/ # ユーザ定義 (優先)
# /usr/lib/udev/rules.d/ # システムデフォルト
# 番号順に評価される (例: 10-xxx.rules, 50-xxx.rules, 99-xxx.rules)
# 基本的なルール構文:
# マッチキー == 値, アクションキー = 値
# --- 例1: USB デバイスの権限設定 ---
# /etc/udev/rules.d/51-my-usb.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", \
MODE="0666", GROUP="plugdev"
# --- 例2: シリアルデバイスのシンボリックリンク ---
# /etc/udev/rules.d/52-serial.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", \
SYMLINK+="myserial", MODE="0666"
# --- 例3: ブロックデバイスのカスタム命名 ---
# /etc/udev/rules.d/53-disk.rules
SUBSYSTEM=="block", ATTR{size}!="0", \
ATTRS{serial}=="WD-XXXX1234", \
SYMLINK+="disk/my_data_disk"
# --- 例4: ネットワークデバイスの命名 ---
# /etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", \
ATTR{address}=="00:11:22:33:44:55", \
NAME="lan0"
# --- 例5: デバイス接続時のスクリプト実行 ---
# /etc/udev/rules.d/80-my-device.rules
SUBSYSTEM=="usb", ACTION=="add", \
ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", \
RUN+="/usr/local/bin/my-device-setup.sh"
# --- 例6: 条件分岐を含む高度なルール ---
# /etc/udev/rules.d/90-custom.rules
ACTION=="add", SUBSYSTEM=="block", \
KERNEL=="sd[a-z]", \
PROGRAM="/bin/sh -c 'echo %k | sed s/sd/my_disk/'", \
SYMLINK+="%c"
# --- 例7: NVIDIA GPU の権限設定 ---
# /etc/udev/rules.d/60-nvidia.rules
KERNEL=="nvidia[0-9]*", MODE="0666"
KERNEL=="nvidiactl", MODE="0666"
KERNEL=="nvidia-modeset", MODE="0666"
KERNEL=="nvidia-uvm", MODE="0666"
12.3 udevadm コマンド
# デバイス情報の取得
udevadm info /dev/sda
udevadm info -a /dev/sda # 親デバイスの属性も表示
# 出力例:
# looking at device '/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda':
# KERNEL=="sda"
# SUBSYSTEM=="block"
# DRIVER==""
# ATTR{alignment_offset}=="0"
# ATTR{discard_alignment}=="0"
# ATTR{events}==""
# ATTR{ext_range}=="256"
# ATTR{range}=="16"
# ATTR{removable}=="0"
# ATTR{ro}=="0"
# ATTR{size}=="209715200"
# イベントの監視 (リアルタイム)
udevadm monitor
udevadm monitor --property # プロパティも表示
udevadm monitor --kernel # カーネルイベントのみ
udevadm monitor --udev # udev イベントのみ
# ルールのテスト (実際には適用しない)
udevadm test /sys/class/net/eth0
# ルールの再読み込み
sudo udevadm control --reload-rules
# デバイスイベントのトリガー
sudo udevadm trigger
sudo udevadm trigger --subsystem-match=usb
# ルール処理の完了を待機
udevadm settle
udevadm settle --timeout=30
# デバイスパスから sysfs パスの解決
udevadm info --query=path --name=/dev/sda
# /devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
# 環境変数の確認
udevadm info --query=property --name=/dev/sda
# DEVPATH=/devices/pci0000:00/.../block/sda
# DEVNAME=/dev/sda
# DEVTYPE=disk
# MAJOR=8
# MINOR=0
# SUBSYSTEM=block
# ID_SERIAL=VBOX_HARDDISK_VB12345678-12345678
13. DKMS (Dynamic Kernel Module Support)
DKMS は、カーネルのアップデート時にサードパーティのカーネルモジュールを自動的に再ビルドする仕組みである。
13.1 DKMS の概要
# DKMS のインストール
sudo apt install dkms # Debian/Ubuntu
sudo dnf install dkms # Fedora/RHEL
sudo pacman -S dkms # Arch Linux
# DKMS の状態確認
dkms status
# my-driver/1.0, 6.1.0-15-amd64, x86_64: installed
# nvidia/535.129.03, 6.1.0-15-amd64, x86_64: installed
13.2 DKMS モジュールの作成
# ディレクトリ構造
/usr/src/my-driver-1.0/
├── dkms.conf
├── Makefile
├── my_driver.c
└── my_driver.h
# /usr/src/my-driver-1.0/dkms.conf
PACKAGE_NAME="my-driver"
PACKAGE_VERSION="1.0"
BUILT_MODULE_NAME[0]="my_driver"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"
# ビルドコマンド (デフォルトは make)
MAKE[0]="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build"
CLEAN="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean"
# 複数モジュールの場合
# BUILT_MODULE_NAME[1]="my_driver_helper"
# DEST_MODULE_LOCATION[1]="/updates"
# DKMS 操作
# ソースの追加
sudo dkms add -m my-driver -v 1.0
# モジュールのビルド
sudo dkms build -m my-driver -v 1.0
# モジュールのインストール
sudo dkms install -m my-driver -v 1.0
# モジュールの削除
sudo dkms remove -m my-driver -v 1.0 --all
# 特定のカーネルバージョン用にビルド
sudo dkms build -m my-driver -v 1.0 -k 6.1.0-15-amd64
# 全操作を一括実行
sudo dkms add -m my-driver -v 1.0
sudo dkms autoinstall -m my-driver -v 1.0
14. 実践: シンプルなキャラクタデバイスドライバ
完全に動作するキャラクタデバイスドライバの実装例を示す。
14.1 完全なソースコード
/*
* simple_chardev.c - シンプルなキャラクタデバイスドライバ
*
* このドライバは、カーネル内バッファへの読み書きを提供する
* 教育目的のサンプルドライバである。
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/poll.h>
#define DEVICE_NAME "simple_chardev"
#define CLASS_NAME "simple"
#define BUFFER_SIZE 4096
#define MAX_DEVICES 4
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux Kernel Developer");
MODULE_DESCRIPTION("A simple character device driver example");
MODULE_VERSION("1.0");
/* モジュールパラメータ */
static int buffer_size = BUFFER_SIZE;
module_param(buffer_size, int, 0644);
MODULE_PARM_DESC(buffer_size, "Size of internal buffer (default: 4096)");
static int num_devices = 1;
module_param(num_devices, int, 0444);
MODULE_PARM_DESC(num_devices, "Number of devices to create (default: 1, max: 4)");
/* デバイス構造体 */
struct simple_dev {
char *data; /* データバッファ */
size_t size; /* 現在のデータサイズ */
size_t buffer_size; /* バッファの最大サイズ */
struct cdev cdev; /* キャラクタデバイス */
struct device *device; /* デバイス */
struct mutex lock; /* 排他ロック */
wait_queue_head_t read_queue; /* 読み取り待ちキュー */
wait_queue_head_t write_queue; /* 書き込み待ちキュー */
int open_count; /* オープン回数 */
};
static dev_t dev_number; /* 最初のデバイス番号 */
static struct class *simple_class; /* デバイスクラス */
static struct simple_dev *devices; /* デバイス配列 */
/*
* open - ファイルオープン時の処理
*/
static int simple_open(struct inode *inode, struct file *filp)
{
struct simple_dev *dev;
/* container_of で cdev からデバイス構造体を取得 */
dev = container_of(inode->i_cdev, struct simple_dev, cdev);
filp->private_data = dev;
mutex_lock(&dev->lock);
dev->open_count++;
pr_info("simple_chardev: device opened (count: %d)\n", dev->open_count);
mutex_unlock(&dev->lock);
return 0;
}
/*
* release - ファイルクローズ時の処理
*/
static int simple_release(struct inode *inode, struct file *filp)
{
struct simple_dev *dev = filp->private_data;
mutex_lock(&dev->lock);
dev->open_count--;
pr_info("simple_chardev: device closed (count: %d)\n", dev->open_count);
mutex_unlock(&dev->lock);
return 0;
}
/*
* read - データの読み取り
*/
static ssize_t simple_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct simple_dev *dev = filp->private_data;
ssize_t retval = 0;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
/* データがない場合の処理 */
while (dev->size == 0) {
mutex_unlock(&dev->lock);
/* ノンブロッキングモードの場合 */
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
/* データが到着するまで待機 */
pr_info("simple_chardev: read waiting for data\n");
if (wait_event_interruptible(dev->read_queue, dev->size > 0))
return -ERESTARTSYS;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
}
/* データのコピー */
if (count > dev->size - *f_pos)
count = dev->size - *f_pos;
if (count == 0)
goto out;
if (copy_to_user(buf, dev->data + *f_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
pr_info("simple_chardev: read %zu bytes from offset %lld\n",
count, *f_pos - count);
out:
mutex_unlock(&dev->lock);
return retval;
}
/*
* write - データの書き込み
*/
static ssize_t simple_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct simple_dev *dev = filp->private_data;
ssize_t retval = -ENOMEM;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
/* バッファサイズのチェック */
if (*f_pos + count > dev->buffer_size)
count = dev->buffer_size - *f_pos;
if (count == 0) {
retval = -ENOSPC;
goto out;
}
/* データのコピー */
if (copy_from_user(dev->data + *f_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
if (*f_pos > dev->size)
dev->size = *f_pos;
retval = count;
pr_info("simple_chardev: wrote %zu bytes at offset %lld\n",
count, *f_pos - count);
/* 読み取り待ちプロセスを起床 */
wake_up_interruptible(&dev->read_queue);
out:
mutex_unlock(&dev->lock);
return retval;
}
/*
* llseek - ファイル位置の変更
*/
static loff_t simple_llseek(struct file *filp, loff_t offset, int whence)
{
struct simple_dev *dev = filp->private_data;
loff_t new_pos;
mutex_lock(&dev->lock);
switch (whence) {
case SEEK_SET:
new_pos = offset;
break;
case SEEK_CUR:
new_pos = filp->f_pos + offset;
break;
case SEEK_END:
new_pos = dev->size + offset;
break;
default:
mutex_unlock(&dev->lock);
return -EINVAL;
}
if (new_pos < 0 || new_pos > dev->buffer_size) {
mutex_unlock(&dev->lock);
return -EINVAL;
}
filp->f_pos = new_pos;
mutex_unlock(&dev->lock);
return new_pos;
}
/*
* poll - select/poll/epoll のサポート
*/
static __poll_t simple_poll(struct file *filp,
struct poll_table_struct *wait)
{
struct simple_dev *dev = filp->private_data;
__poll_t mask = 0;
mutex_lock(&dev->lock);
poll_wait(filp, &dev->read_queue, wait);
poll_wait(filp, &dev->write_queue, wait);
if (dev->size > 0)
mask |= EPOLLIN | EPOLLRDNORM; /* 読み取り可能 */
if (dev->size < dev->buffer_size)
mask |= EPOLLOUT | EPOLLWRNORM; /* 書き込み可能 */
mutex_unlock(&dev->lock);
return mask;
}
/*
* file_operations 構造体
*/
static const struct file_operations simple_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.release = simple_release,
.read = simple_read,
.write = simple_write,
.llseek = simple_llseek,
.poll = simple_poll,
};
/*
* sysfs 属性
*/
static ssize_t data_size_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct simple_dev *sdev = dev_get_drvdata(dev);
return sysfs_emit(buf, "%zu\n", sdev->size);
}
static DEVICE_ATTR_RO(data_size);
static ssize_t buffer_size_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct simple_dev *sdev = dev_get_drvdata(dev);
return sysfs_emit(buf, "%zu\n", sdev->buffer_size);
}
static DEVICE_ATTR_RO(buffer_size);
static ssize_t clear_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct simple_dev *sdev = dev_get_drvdata(dev);
mutex_lock(&sdev->lock);
memset(sdev->data, 0, sdev->buffer_size);
sdev->size = 0;
mutex_unlock(&sdev->lock);
wake_up_interruptible(&sdev->write_queue);
return count;
}
static DEVICE_ATTR_WO(clear);
static struct attribute *simple_dev_attrs[] = {
&dev_attr_data_size.attr,
&dev_attr_buffer_size.attr,
&dev_attr_clear.attr,
NULL,
};
ATTRIBUTE_GROUPS(simple_dev);
/*
* モジュール初期化
*/
static int __init simple_init(void)
{
int ret;
int i;
if (num_devices < 1 || num_devices > MAX_DEVICES) {
pr_err("simple_chardev: num_devices must be 1-%d\n", MAX_DEVICES);
return -EINVAL;
}
/* デバイス番号の動的割り当て */
ret = alloc_chrdev_region(&dev_number, 0, num_devices, DEVICE_NAME);
if (ret < 0) {
pr_err("simple_chardev: failed to allocate device numbers\n");
return ret;
}
/* デバイスクラスの作成 */
simple_class = class_create(CLASS_NAME);
if (IS_ERR(simple_class)) {
ret = PTR_ERR(simple_class);
goto err_chrdev;
}
simple_class->dev_groups = simple_dev_groups;
/* デバイス配列の確保 */
devices = kcalloc(num_devices, sizeof(struct simple_dev), GFP_KERNEL);
if (!devices) {
ret = -ENOMEM;
goto err_class;
}
/* 各デバイスの初期化 */
for (i = 0; i < num_devices; i++) {
devices[i].buffer_size = buffer_size;
devices[i].data = kzalloc(buffer_size, GFP_KERNEL);
if (!devices[i].data) {
ret = -ENOMEM;
goto err_devices;
}
mutex_init(&devices[i].lock);
init_waitqueue_head(&devices[i].read_queue);
init_waitqueue_head(&devices[i].write_queue);
/* cdev の初期化と追加 */
cdev_init(&devices[i].cdev, &simple_fops);
devices[i].cdev.owner = THIS_MODULE;
ret = cdev_add(&devices[i].cdev, MKDEV(MAJOR(dev_number), i), 1);
if (ret) {
pr_err("simple_chardev: failed to add cdev %d\n", i);
kfree(devices[i].data);
goto err_devices;
}
/* デバイスノードの作成 */
devices[i].device = device_create(simple_class, NULL,
MKDEV(MAJOR(dev_number), i),
&devices[i],
"%s%d", DEVICE_NAME, i);
if (IS_ERR(devices[i].device)) {
ret = PTR_ERR(devices[i].device);
cdev_del(&devices[i].cdev);
kfree(devices[i].data);
goto err_devices;
}
}
pr_info("simple_chardev: loaded with %d device(s), major=%d\n",
num_devices, MAJOR(dev_number));
return 0;
err_devices:
while (--i >= 0) {
device_destroy(simple_class, MKDEV(MAJOR(dev_number), i));
cdev_del(&devices[i].cdev);
kfree(devices[i].data);
}
kfree(devices);
err_class:
class_destroy(simple_class);
err_chrdev:
unregister_chrdev_region(dev_number, num_devices);
return ret;
}
/*
* モジュール終了
*/
static void __exit simple_exit(void)
{
int i;
for (i = 0; i < num_devices; i++) {
device_destroy(simple_class, MKDEV(MAJOR(dev_number), i));
cdev_del(&devices[i].cdev);
kfree(devices[i].data);
}
kfree(devices);
class_destroy(simple_class);
unregister_chrdev_region(dev_number, num_devices);
pr_info("simple_chardev: unloaded\n");
}
module_init(simple_init);
module_exit(simple_exit);
14.2 テスト方法
# ビルドとロード
make
sudo insmod simple_chardev.ko num_devices=2 buffer_size=8192
# デバイスの確認
ls -la /dev/simple_chardev*
# crw------- 1 root root 240, 0 Apr 10 12:00 /dev/simple_chardev0
# crw------- 1 root root 240, 1 Apr 10 12:00 /dev/simple_chardev1
# データの書き込み
echo "Hello, Kernel!" > /dev/simple_chardev0
# データの読み取り
cat /dev/simple_chardev0
# sysfs 属性の確認
cat /sys/class/simple/simple_chardev0/data_size
cat /sys/class/simple/simple_chardev0/buffer_size
# バッファのクリア
echo 1 > /sys/class/simple/simple_chardev0/clear
# カーネルログの確認
dmesg | tail -20
# アンロード
sudo rmmod simple_chardev
15. ioctl インターフェース
ioctl (Input/Output Control) は、デバイスに対する制御コマンドを発行するためのインターフェースである。read/write では表現しにくいデバイス固有の操作に使用される。
15.1 ioctl コマンドの定義
/*
* ioctl コマンド番号の定義
* include/uapi/asm-generic/ioctl.h で定義されたマクロを使用
*/
#include <linux/ioctl.h>
/* マジックナンバー (デバイス固有の識別子) */
#define MY_IOC_MAGIC 'M'
/* コマンド定義マクロ:
* _IO(type, nr) - データ転送なし
* _IOR(type, nr, datatype) - 読み取り (カーネル → ユーザ)
* _IOW(type, nr, datatype) - 書き込み (ユーザ → カーネル)
* _IOWR(type, nr, datatype) - 双方向
*/
/* デバイス情報の取得 */
struct my_device_info {
__u32 version;
__u32 capabilities;
char name[64];
};
#define MY_IOC_GET_INFO _IOR(MY_IOC_MAGIC, 0, struct my_device_info)
/* デバイス設定の変更 */
struct my_device_config {
__u32 mode;
__u32 speed;
__u32 flags;
};
#define MY_IOC_SET_CONFIG _IOW(MY_IOC_MAGIC, 1, struct my_device_config)
#define MY_IOC_GET_CONFIG _IOR(MY_IOC_MAGIC, 2, struct my_device_config)
/* デバイスのリセット */
#define MY_IOC_RESET _IO(MY_IOC_MAGIC, 3)
/* バッファサイズの変更 */
#define MY_IOC_SET_BUFSIZE _IOW(MY_IOC_MAGIC, 4, __u32)
#define MY_IOC_GET_BUFSIZE _IOR(MY_IOC_MAGIC, 5, __u32)
/* ステータスの読み取り */
struct my_device_status {
__u32 rx_count;
__u32 tx_count;
__u32 error_count;
__u32 flags;
};
#define MY_IOC_GET_STATUS _IOR(MY_IOC_MAGIC, 6, struct my_device_status)
/* DMA バッファの設定 */
struct my_dma_buffer {
__u64 address;
__u32 size;
__u32 direction;
};
#define MY_IOC_SET_DMA_BUF _IOW(MY_IOC_MAGIC, 7, struct my_dma_buffer)
#define MY_IOC_MAXNR 7
15.2 ioctl ハンドラの実装
/*
* ioctl ハンドラの実装
*/
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct my_device *dev = filp->private_data;
int ret = 0;
void __user *argp = (void __user *)arg;
/* コマンドの検証 */
if (_IOC_TYPE(cmd) != MY_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > MY_IOC_MAXNR)
return -ENOTTY;
/* アクセス権の検証 */
if (_IOC_DIR(cmd) & _IOC_READ) {
if (!access_ok(argp, _IOC_SIZE(cmd)))
return -EFAULT;
}
if (_IOC_DIR(cmd) & _IOC_WRITE) {
if (!access_ok(argp, _IOC_SIZE(cmd)))
return -EFAULT;
}
switch (cmd) {
case MY_IOC_GET_INFO:
{
struct my_device_info info;
mutex_lock(&dev->lock);
info.version = dev->hw_version;
info.capabilities = dev->capabilities;
strscpy(info.name, dev->name, sizeof(info.name));
mutex_unlock(&dev->lock);
if (copy_to_user(argp, &info, sizeof(info)))
return -EFAULT;
break;
}
case MY_IOC_SET_CONFIG:
{
struct my_device_config config;
if (copy_from_user(&config, argp, sizeof(config)))
return -EFAULT;
/* 入力値の検証 */
if (config.mode > MAX_MODE || config.speed > MAX_SPEED)
return -EINVAL;
mutex_lock(&dev->lock);
dev->config = config;
/* ハードウェアに設定を反映 */
apply_config(dev, &config);
mutex_unlock(&dev->lock);
break;
}
case MY_IOC_GET_CONFIG:
{
mutex_lock(&dev->lock);
if (copy_to_user(argp, &dev->config, sizeof(dev->config)))
ret = -EFAULT;
mutex_unlock(&dev->lock);
break;
}
case MY_IOC_RESET:
mutex_lock(&dev->lock);
ret = device_reset(dev);
mutex_unlock(&dev->lock);
break;
case MY_IOC_SET_BUFSIZE:
{
__u32 new_size;
if (get_user(new_size, (__u32 __user *)arg))
return -EFAULT;
if (new_size == 0 || new_size > MAX_BUFFER_SIZE)
return -EINVAL;
mutex_lock(&dev->lock);
ret = resize_buffer(dev, new_size);
mutex_unlock(&dev->lock);
break;
}
case MY_IOC_GET_BUFSIZE:
{
if (put_user(dev->buffer_size, (__u32 __user *)arg))
return -EFAULT;
break;
}
case MY_IOC_GET_STATUS:
{
struct my_device_status status;
mutex_lock(&dev->lock);
status.rx_count = dev->rx_count;
status.tx_count = dev->tx_count;
status.error_count = dev->error_count;
status.flags = ioread32(dev->regs + STATUS_REG);
mutex_unlock(&dev->lock);
if (copy_to_user(argp, &status, sizeof(status)))
return -EFAULT;
break;
}
default:
return -ENOTTY; /* 未知のコマンド */
}
return ret;
}
/* 32-bit 互換 ioctl (64-bit カーネルで 32-bit アプリを動かす場合) */
static long my_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
/* 多くの場合、通常の ioctl にリダイレクト可能 */
return my_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
}
15.3 ユーザ空間からの ioctl 呼び出し
/*
* ユーザ空間プログラム (user_app.c)
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
/* ヘッダファイルの共有 (カーネル/ユーザ共通) */
#include "my_ioctl.h"
int main(int argc, char *argv[])
{
int fd;
int ret;
fd = open("/dev/my_device0", O_RDWR);
if (fd < 0) {
perror("open");
return EXIT_FAILURE;
}
/* デバイス情報の取得 */
struct my_device_info info;
ret = ioctl(fd, MY_IOC_GET_INFO, &info);
if (ret < 0) {
perror("ioctl GET_INFO");
close(fd);
return EXIT_FAILURE;
}
printf("Device: %s, Version: %u, Caps: 0x%x\n",
info.name, info.version, info.capabilities);
/* デバイス設定の変更 */
struct my_device_config config = {
.mode = 1,
.speed = 115200,
.flags = 0x03,
};
ret = ioctl(fd, MY_IOC_SET_CONFIG, &config);
if (ret < 0) {
perror("ioctl SET_CONFIG");
}
/* ステータスの取得 */
struct my_device_status status;
ret = ioctl(fd, MY_IOC_GET_STATUS, &status);
if (ret < 0) {
perror("ioctl GET_STATUS");
} else {
printf("RX: %u, TX: %u, Errors: %u\n",
status.rx_count, status.tx_count, status.error_count);
}
/* デバイスリセット */
ret = ioctl(fd, MY_IOC_RESET);
if (ret < 0) {
perror("ioctl RESET");
}
close(fd);
return EXIT_SUCCESS;
}
16. カーネルモジュールの Makefile とビルドシステム
16.1 基本的な Makefile
# Makefile - 外部カーネルモジュール用
#
# モジュール名
obj-m := my_driver.o
# 複数ソースファイルのモジュール
# obj-m := my_complex_driver.o
# my_complex_driver-objs := main.o hardware.o utils.o
# コンパイラフラグ
ccflags-y := -DDEBUG -Wall -Werror
# ccflags-y += -I$(src)/include
# カーネルソースディレクトリ
KDIR ?= /lib/modules/$(shell uname -r)/build
# ビルドディレクトリ
PWD := $(shell pwd)
# デフォルトターゲット
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
# クリーン
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# インストール
install:
$(MAKE) -C $(KDIR) M=$(PWD) modules_install
# モジュール情報の表示
info:
modinfo my_driver.ko
# テスト (ロードとアンロード)
test: all
sudo insmod my_driver.ko
dmesg | tail -5
sudo rmmod my_driver
dmesg | tail -5
.PHONY: all clean install info test
16.2 複数モジュールの Makefile
# 複数モジュールを含む Makefile
# カーネルビルドシステムから呼ばれた場合
ifneq ($(KERNELRELEASE),)
obj-m := module_a.o module_b.o
# module_a は単一ファイル
# (module_a.c から module_a.ko が生成される)
# module_b は複数ファイル
module_b-objs := module_b_main.o module_b_hw.o module_b_util.o
# 条件付きコンパイル
ccflags-y := -DCONFIG_MY_DEBUG
ifdef CONFIG_MY_FEATURE_X
module_b-objs += module_b_feature_x.o
ccflags-y += -DFEATURE_X_ENABLED
endif
# 直接呼ばれた場合
else
KDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
rm -f *.o *.ko *.mod.c *.mod *.order *.symvers
endif
16.3 Kbuild ファイル
# Kbuild - より正式なビルド設定ファイル
# Makefile とは別に Kbuild ファイルを作成可能
obj-m := my_driver.o
my_driver-y := core.o init.o io.o
my_driver-$(CONFIG_MY_DRIVER_DEBUG) += debug.o
ccflags-y := -I$(src)/include
ccflags-$(CONFIG_MY_DRIVER_VERBOSE) += -DVERBOSE
16.4 クロスコンパイル
# ARM 向けクロスコンパイル
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
KDIR=/path/to/arm-kernel-source \
M=$(pwd) modules
# ARM64 向けクロスコンパイル
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
KDIR=/path/to/arm64-kernel-source \
M=$(pwd) modules
# RISC-V 向けクロスコンパイル
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- \
KDIR=/path/to/riscv-kernel-source \
M=$(pwd) modules
16.5 カーネルビルドシステムとの統合
# Kconfig ファイル (カーネルツリーに統合する場合)
config MY_DRIVER
tristate "My Custom Device Driver"
depends on PCI
select CRC32
help
This is a driver for My Custom Device.
It supports PCI devices with vendor ID 0x1234.
To compile this driver as a module, choose M here:
the module will be called my_driver.
config MY_DRIVER_DEBUG
bool "Enable debug output for My Driver"
depends on MY_DRIVER
default n
help
Enable verbose debug output for troubleshooting.
17. 診断ツール群
17.1 lspci - PCI デバイスの調査
# 基本的な使用法
lspci # すべての PCI デバイスを一覧表示
lspci -v # 詳細表示
lspci -vv # さらに詳細
lspci -vvv # 最大詳細 (root 権限推奨)
# フィルタリング
lspci -s 00:02.0 # 特定のデバイス
lspci -d 8086: # ベンダー ID で絞り込み
lspci -d :1533 # デバイス ID で絞り込み
lspci -d 8086:1533 # ベンダー:デバイス
# 表示形式
lspci -t # ツリー表示
lspci -nn # デバイス名 + 数値ID
lspci -n # 数値IDのみ
lspci -k # カーネルドライバ情報
lspci -mm # マシンリーダブル形式
# コンフィグレーション空間
sudo lspci -xxx # 256バイトダンプ
sudo lspci -xxxx # 4096バイトダンプ (PCIe 拡張)
# 実用的な例
# VGA コントローラの確認
lspci | grep -i vga
# ネットワークデバイスの確認
lspci | grep -i ethernet
# NVMe デバイスの確認
lspci | grep -i nvme
# ドライバの確認
lspci -k -s 00:02.0
# 00:02.0 VGA compatible controller: Intel Corporation Device 9a49
# Subsystem: Lenovo Device 2267
# Kernel driver in use: i915
# Kernel modules: i915
17.2 lsusb - USB デバイスの調査
# 基本的な使用法
lsusb # すべての USB デバイスを一覧表示
lsusb -v # 詳細表示
lsusb -t # ツリー表示
# フィルタリング
lsusb -s 001:002 # バス:デバイス
lsusb -d 046d:c52b # ベンダー:プロダクト ID
# 詳細な記述子情報
sudo lsusb -v -d 046d:c52b
# 実用的な例
# USB 3.0 デバイスの確認
lsusb -t | grep -i "5000M\|10000M"
# USB デバイスの電力情報
cat /sys/bus/usb/devices/1-1/power/active_duration
cat /sys/bus/usb/devices/1-1/power/connected_duration
17.3 lsblk - ブロックデバイスの調査
# 基本的な使用法
lsblk # ブロックデバイスのツリー表示
lsblk -a # 空のデバイスも表示
lsblk -f # ファイルシステム情報
lsblk -d # ディスクのみ (パーティションなし)
lsblk -p # フルパス表示
# カスタム出力
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL
lsblk -o NAME,MAJ:MIN,RM,SIZE,RO,TYPE,MOUNTPOINTS
# JSON 出力
lsblk -J
# 特定のデバイス
lsblk /dev/sda
# トポロジ情報
lsblk -t
# NAME ALIGNMENT MIN-IO OPT-IO PHY-SEC LOG-SEC ROTA SCHED
# ディスカードサポートの確認
lsblk -D
17.4 modinfo / modprobe - モジュール管理
# modinfo - モジュール情報の表示
modinfo e1000e
# filename: /lib/modules/6.1.0/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko
# license: GPL v2
# description: Intel(R) PRO/1000 Network Driver
# author: Intel Corporation
# alias: pci:v00008086d0000...
# depends:
# retpoline: Y
# intree: Y
# name: e1000e
# vermagic: 6.1.0 SMP preempt mod_unload
# parm: debug:Debug level (0=none,...,16=all) (int)
# 特定の情報のみ表示
modinfo -F parm e1000e # パラメータのみ
modinfo -F depends e1000e # 依存関係のみ
modinfo -F alias e1000e # エイリアスのみ
modinfo -F filename e1000e # ファイルパスのみ
# modprobe - モジュールの管理
sudo modprobe e1000e # ロード (依存関係自動解決)
sudo modprobe -r e1000e # アンロード
sudo modprobe e1000e debug=16 # パラメータ付きロード
# 依存関係の確認
modprobe --show-depends e1000e
# ドライランモード
modprobe -n -v e1000e
# モジュールのブラックリスト
echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
echo "options nouveau modeset=0" >> /etc/modprobe.d/blacklist.conf
# モジュールの自動ロード設定
echo "my_driver" >> /etc/modules-load.d/my_driver.conf
# モジュールパラメータの永続設定
echo "options my_driver param1=value1 param2=value2" \
>> /etc/modprobe.d/my_driver.conf
17.5 udevadm - udev 管理
# デバイス情報
udevadm info /dev/sda
udevadm info --query=all /dev/sda
udevadm info --attribute-walk /dev/sda
# イベント監視
udevadm monitor # すべてのイベント
udevadm monitor --property # プロパティ付き
udevadm monitor --subsystem-match=block # ブロックデバイスのみ
udevadm monitor --tag-match=systemd # タグフィルタ
# ルール管理
sudo udevadm control --reload-rules # ルール再読み込み
sudo udevadm trigger # イベント再発火
sudo udevadm settle # 処理完了待機
# テスト
sudo udevadm test /sys/class/net/eth0 # ルール適用テスト
sudo udevadm test-builtin input_id /sys/class/input/input0
17.6 その他の有用なツール
# デバイスマッパー
dmsetup ls # デバイスマッパーデバイスの一覧
dmsetup info # 詳細情報
dmsetup table # テーブル情報
# SCSI デバイス
lsscsi # SCSI デバイスの一覧
lsscsi -v # 詳細
# NVMe デバイス
nvme list # NVMe デバイスの一覧
nvme id-ctrl /dev/nvme0 # コントローラ識別
nvme smart-log /dev/nvme0 # SMART 情報
# カーネルログ
dmesg | grep -i "driver\|error\|fail"
journalctl -k # カーネルメッセージ
journalctl -k -f # リアルタイム監視
# /proc からのデバイス情報
cat /proc/devices # メジャー番号の一覧
cat /proc/interrupts # 割り込みの統計
cat /proc/ioports # I/O ポートの使用状況
cat /proc/iomem # メモリマッピングの使用状況
cat /proc/dma # DMA チャネルの使用状況
cat /proc/modules # ロード済みモジュール
# sysfs からの情報取得
find /sys/devices -name "driver" -exec readlink -f {} \;
find /sys/bus/pci/devices -maxdepth 1 -exec basename {} \;
18. まとめとベストプラクティス
18.1 ドライバ開発のベストプラクティス
- エラーハンドリング: すべての API 呼び出しの戻り値を確認し、適切にエラー処理する
- リソース管理:
devm_API を積極的に使用し、リソースリークを防止する - 同期: 適切なロックメカニズムを選択し、デッドロックを回避する
- メモリ管理: カーネルメモリの割り当てと解放を慎重に行う
- 電源管理: サスペンド/レジュームを適切に実装する
- コーディングスタイル: Linux カーネルコーディングスタイルに従う
- テスト: 静的解析 (sparse, smatch)、動的テスト (kasan, lockdep) を活用する
- ドキュメント: コードコメントとドキュメントを充実させる
18.2 デバッグ技法
# printk レベル
# KERN_EMERG (0) - システムが使用不能
# KERN_ALERT (1) - 即座の対応が必要
# KERN_CRIT (2) - クリティカルな状態
# KERN_ERR (3) - エラー状態
# KERN_WARNING (4) - 警告状態
# KERN_NOTICE (5) - 通常だが重要な状態
# KERN_INFO (6) - 情報メッセージ
# KERN_DEBUG (7) - デバッグメッセージ
# 推奨: dev_xxx() 系マクロの使用
dev_err(&pdev->dev, "Error: %d\n", ret);
dev_warn(&pdev->dev, "Warning: low memory\n");
dev_info(&pdev->dev, "Device initialized\n");
dev_dbg(&pdev->dev, "Register value: 0x%x\n", val);
# 動的デバッグ
echo 'module my_driver +p' > /sys/kernel/debug/dynamic_debug/control
echo 'file my_driver.c +p' > /sys/kernel/debug/dynamic_debug/control
echo 'func my_probe +p' > /sys/kernel/debug/dynamic_debug/control
# ftrace の使用
echo function > /sys/kernel/debug/tracing/current_tracer
echo my_driver_* > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
echo 0 > /sys/kernel/debug/tracing/tracing_on
18.3 セキュリティ考慮事項
- ユーザ入力 (ioctl パラメータ等) の厳密な検証
- バッファオーバーフローの防止
- 権限チェック (
capable()) の実施 - IOMMU の活用によるデバイス分離
- 署名済みモジュールの使用 (Secure Boot 環境)
18.4 参考リソース
| リソース | URL |
|---|---|
| Linux Kernel Documentation | https://www.kernel.org/doc/html/latest/ |
| Linux Device Drivers (LDD3) | https://lwn.net/Kernel/LDD3/ |
| Linux カーネルソース | https://elixir.bootlin.com/linux/latest/source |
| KernelNewbies | https://kernelnewbies.org/ |
| LWN.net | https://lwn.net/ |
注意: 本ドキュメントに含まれるコード例は教育目的であり、プロダクション環境での使用前に十分なテストとレビューを行うこと。カーネルバージョンにより API が変更される場合がある。