簡単な紹介
Linux デバイス ドライバーは、Linux カーネルを駆動してハードウェア デバイスと通信するソフトウェア モジュールを指します。デバイス ドライバーは通常、キャラクター デバイス ドライバーとブロック デバイス ドライバーの 2 つのカテゴリに分類されます。
デバイス ドライバーの主な機能は次のとおりです。
- デバイスの初期化: システムが起動すると、デバイスが正常に動作できるように、デバイス ドライバーは対応するハードウェア デバイスを初期化し、デバイスのレジスタとインターフェイスのパラメーターを設定する必要があります。
- デバイス制御: デバイス ドライバーは、デバイスのオープン、データの読み取り、データの書き込み、デバイスのクローズなど、デバイスのさまざまな操作を制御するためのいくつかのインターフェイスを提供する必要があります。
- 割り込み処理: デバイスドライバーは、ハードウェアデバイスの割り込み要求を処理し、割り込みが発生したときに対応する割り込み処理プログラムを実行して、デバイスのさまざまなイベントや要求に適時に応答する必要があります。
- データ送信: デバイス ドライバーは、ハードウェア デバイスからのデータの読み取りやハードウェア デバイスへのデータの書き込みなどの操作を含む、データ送信機能を実装する必要があります。
- エラー処理: デバイス ドライバーは、デバイスの読み取りおよび書き込みエラー、割り込み損失など、デバイスで発生するエラーと例外を処理する必要があります。
1.1 デバイスドライバーの分類
1.1.1 ブロックデバイスドライバー
ブロック デバイス ドライバーとは、ハード ドライブ、ソリッド ステート ドライブ、USB フラッシュ ドライブなどのストレージ デバイス ドライバーなど、ブロック単位でデバイスと通信するドライバー プログラムを指します。
ブロック デバイス ドライバーは通常、次のレベルを含む階層構造を採用します。
- デバイス ドライバー層: この層はハードウェア デバイスと直接通信し、デバイスのさまざまな動作を制御します。
- ストレージ ボリューム マネージャー層: この層は、ストレージ ボリューム (ハードディスク パーティション、論理ボリュームなど) を管理し、ボリュームへのアクセス インターフェイスを提供する役割を果たします。
- ファイル システム層: この層は、ボリュームへのファイル システム インターフェイスを提供し、ユーザーがファイル システムのディレクトリ構造に従ってファイルにアクセスし、管理できるようにします。
1.1.2 キャラクターデバイスドライバー
I2C/SPI デバイスは、その機能とアプリケーション シナリオに応じて、キャラクター デバイス、ブロック デバイス、またはネットワーク デバイスになります。
ただし、一部の I2C デバイスについては、ブロック デバイスやネットワーク デバイスなど、他のタイプのデバイスとして分類される場合があります。たとえば、EEPROM などの特定の I2C メモリは、データをブロックで転送するため、ブロック デバイスの例とみなすことができます。温度センサーや光センサーなどの他の I2C デバイスは、データをパケットで送信するため、ネットワーク デバイスとみなすことができます。
したがって、すべての I2C/SPI デバイスがキャラクター デバイスであるわけではなく、機能やアプリケーション シナリオに応じてさまざまなタイプのデバイスに分類される場合があります。デバイスの種類が異なると、デバイスにアクセスして制御するために異なるドライバーが必要になります。
Linuxアプリケーションプログラムによるドライバプログラムの呼び出しを図40.1.1に示します。
アプリケーションはユーザー空間で実行されますが、 Linux ドライバーはカーネルの一部であるため、ドライバーはカーネル空間で実行されます。open関数を使用して /dev/ledドライバー を開く など、ユーザー空間のカーネルに操作を実装したい場合、ユーザー空間はカーネル上で直接動作できないため、実装には「システムコール」と呼ばれるメソッドを使用する必要があります。基盤となるドライバーの動作を実現するために、カーネル空間に時間を「閉じ込め」ます。
デバイスドライバーの2 つの原則
2.1 ドライバーモジュールのロードとアンロード
2.1.1 読み込み
module_init(xxx_init); //モジュールロード関数を登録module_exit(xxx_exit); //モジュールアンインストール関数を登録
ドライバーがコンパイルされると、拡張子は.koになります。ドライバー モジュールをロードするには、insmodとmodprobeの 2 つのコマンドがあります。
insmod drv.komodprobe drv.ko
2.1.2 アンインストール
rmmod drv.komodprobe -r drv.ko //modprobeコマンド を使用して 、ドライバー モジュールが依存する他のモジュールをアンインストールします (これらの依存モジュールが既に存在しない場合)。これは他のモジュールによって使用されます。それ以外の場合、 modprobe を使用して ドライバー モジュールをアンロードすることはできません。したがって、モジュールをアンインストールするには、rmmod コマンドを使用することをお勧めします。
2.2 アドレスマッピング MMU
Linuxカーネルが起動すると、 MMU が 初期化され 、メモリ マップがセットアップされ、セットアップ後はすべてのCPUアクセスが仮想化されます。提案された住所。
2.2.1 ioremap関数
ioremap関数は、指定された物理アドレス空間に対応する仮想アドレス空間を取得するために使用され、arch/arm/include/asm/io.h ファイルで次のように定義されます。
示例代码 41.1.1.1 ioremap 函数
#define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
}
2.2.2 iounmap関数
void iounmap (volatile void __iomem *addr)
2.2.3 I/Oメモリアクセス関数
Linux は仮想アドレスを操作することで物理アドレスを間接的に操作します
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
2 書き込み操作機能
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
2.3 デバイス番号
2.3.1 構成
dev_t __u32 タイプの上位 12 ビットはメジャー デバイス番号、下位 20 ビットはマイナー デバイス番号です。したがって、 Linuxシステム内の主装置番号の範囲は 0 ~ 4095である ため、主装置番号を選択する場合はこの範囲を超えないようにしてください。
2.3.2 デバイス番号の割り当てと解除
int alloc_chrdev_region(dev_t *dev, unsigned Baseminor, unsigned count, const char *name)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from、unsigned count)
2.4 キャラクタデバイスの新規登録方法
2.4.1 、キャラクターデバイスの構造
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
2.4.2 、cdev_init関数
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
2.4.3、cdev_add関数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
2.4.4、cdev_del関数
void cdev_del(struct cdev *p)
2.5 デバイスノードを自動作成する
2.5.1 mdevメカニズム
echo /sbin/mdev > /proc/sys/kernel/hotplug
2.5.1.1 クラスの作成と削除
struct class *class_create (struct module *owner, const char *name)
void class_destroy(struct class *cls);
2.5.1.2 デバイスの作成
struct device *device_create(struct クラス*クラス、構造体デバイス*親、開発者開発者、空所*drvdata、定数文字*ふむ、...)
void device_destroy(struct class *class, dev_t devt)
2.6 ファイルのプライベートデータを設定する
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
2.7 汎用デバイスドライバ作成テンプレートの 参考例
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct newchrled_dev newchrled; /* led设备 */
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static int __init led_init(void)
{
...
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (newchrled.major) { /* 定义了设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
}
printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
/* 3、添加一个cdev */
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
/* 4、创建类 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)) {
return PTR_ERR(newchrled.class);
}
/* 5、创建设备 */
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(newchrled.device)) {
return PTR_ERR(newchrled.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit led_exit(void)
{
....
/* 注销字符设备驱动 */
cdev_del(&newchrled.cdev);/* 删除cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
// MODULE_VERSION("4.1.15-g3dc0a4b SMP preempt mod_unload modversions ARMv7 p2v8")