著作権については前述
この記事は、Linux デバイス ドライバー開発の学習に関するメモであり、時間厳守のアトムの読み取りに関するチュートリアルの要約です。著作権は再版としてマークされます。私たちまずキャラクターデバイスドライバーの開発プロセスを整理し、これに基づいて最も単純なデバイスドライバーLEDの開発プロセスを分析します。。最後に補足として、少し付け加えておきます新しいドライバーを作成するプロセス。
時間厳守に関するチュートリアルについていくつか苦情を言わせてください。ロジックがごちゃごちゃしていて、見ていると遠回りしてしまいます。
1. キャラクターデバイスドライバーの重要な手順 - トップダウンビュー
1.1 ドライバーモジュールのロードとアンロード
ドライバーがコンパイルされた後、キャラクターデバイスドライバーモジュールがすでにあると仮定します。モジュールのサフィックスは .ko です、ドライバー モジュールをロードするには、 insmod と modprobeの 2 つの方法があり、それらの基本的な使用法は次のとおりです。
# insmod加载模块
insmod drv.ko
# rmmod卸载模块
rmmod drv.ko
# -------------------------------
# modprobe加载模块
modprobe drv.ko
# modprobe 卸载模块
modprobe -r drv.ko
insmod と rmmod はペアになっており、モジュールのロード時にモジュールの依存関係を自動的に分析できませんが、modprobe は分析できます。ドライバーモジュールをロードする場合は Modprobe を推奨します。そしてmodprobe -r を使用してモジュールをアンインストールする場合は、まずそのモジュールに依存する他のモジュールをアンインストールする必要がありますそのため、モジュールをアンインストールする場合は rmmod コマンドを使用することをお勧めします。
1.2 キャラクターデバイスの登録と登録解除
ドライバー コードを作成するときは、module_init と module_exit を使用して、モジュールの開始関数と終了関数を Linux カーネルに登録する必要があります。上記の insmod または modprobe コマンドを使用してドライバー モジュールをロードまたはアンロードする場合、module_init と module_exit で登録された入口関数と出口関数は自動的に実行されます。、デバイスの初期化またはログアウトを完了するため、entry 関数と exit 関数の記述を詳しく見てみましょう。
作成関数とエクスポート関数で行うことは、キャラクタ デバイスを初期化して登録するか、ログアウトすることです。旧バージョンのドライバーでは、対応する登録・解除機能を完了する機能がありませんでした。それぞれ register_chrdev と unregister_chrdev です。。
// @param major:要注册的设备的主设备号
// @param name:要注册的设备名,帮助debug
// @param fops:设备驱动包含的动作结构体
static inline int register_chrdev(
unsigned int major,
const char *name,
const struct file_operations *fops);
//---------分割线-----------
// @param major: 要注销的设备的主设备号
// @param name: 要注销的设备的名字
static inline void unregister_chrdev(
unsigned int major,
const char *name);
1.3 デバイスの特定のドライバー関数を作成する
デバイスを登録するときregister_chrdev 関数が使用されますこの関数は、デバイス ドライバーでサポートする必要がある操作を記述するために、デバイスによってサポートされる操作を記述する構造体 fops を受け取る必要があります。したがって、ここでは、ファイルを開く、ファイルの読み取りと書き込みなどの一連の操作をドライバーに実装する独自のコードを記述する必要があります。これは次のとおりです。駆動装置の開発で最も難しく、常に変化する部分、私たちはする必要がありますこれらの機能を完了するには、マニュアルを自分で読み、レジスタを正しく設定してください。。
特定の関数の作成と実装が完了したら、次のような構文を使用します。file_operations タイプの構造体に関数を登録します。を指定し、 register_chrdev 関数を呼び出すときにこの構造体を渡します。次のようになります。仮想キャラクターデバイスドライバー機能登録コード、参考までに。
static struct file_operations test_fops = {
.owner = THIS_MODULE,
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
};
1.4 ライセンスと作成者情報を追加する
上記のプロセスを開発した後、ほとんどの作業は完了しましたが、最後に、このドライバーが従うライセンス情報と作成者の関連情報を追加する必要があります。これらの情報は、次のマクロを使用して定義されます。
// 给驱动程序附加LICENSE和作者相关信息,其中LICENSE是必须的
MODULE_LICENSE();
MODULE_AUTHOR();
// 例子:添加模块LICENSE信息和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tom");
2. デバイス番号とその割り当て
Linux システムでは、Linux 内の各デバイスにデバイス番号があり、デバイス番号は次のように決定されます。メジャー番号とマイナー番号2つの部分で構成されており、メジャー デバイス番号は特定のドライバーを示し、スレーブ デバイス番号はこのドライバーを使用する各デバイスを示します。。デバイス番号の本質は、符号なし整数データ (unsigned int32)、で上位12ビットがマスターデバイス番号、下位20ビットがスレーブデバイス番号です。。したがって、メジャーデバイス番号の値の範囲は 0 ~ 4095 であり、メジャーデバイス番号を選択する場合はこの範囲を超えてはなりません。
メインデバイス番号を割り当てるとき (上記のデバイスを register_chrdev 関数で登録するときなど)渡される最初のパラメータ) には次の 2 つの方法があります。
- 静的割り当て: 使用cat /proc/devices コマンド現在占有されているすべてのデバイス番号をリストしてから、空いているメジャーデバイス番号を手動で選択します。
- 動的割り当て (推奨!) : 可能です。alloc_chrdev_region と unregister_chrdev_region を使用するこれら 2 つの機能により、Linux はデバイス番号を自動的に申請し、再利用することができます。これら 2 つの関数のプロトタイプは次のとおりです。両方とも、ある範囲のデバイス番号の割り当てと再利用に使用できることに注意してください。
// @param dev: 申请到的主设备号,带出参数
// @param baseminor: 从设备号开始值,一般从0开始
// @param count: 要申请的设备号数量
// @param name: 设备名称
int alloc_chrdev_region( dev_t *dev,
unsigned baseminor,
unsigned count,
const char *name);
// -------------------------------------------------
// @param from: 要释放的初始主设备号
// @param count: 要释放的设备号数量
void unregister_chrdev_region(dev_t from, unsigned count);
3. 例と詳細 - LED ドライバーの開発を例に挙げます。
3.1 ioremap 関数と iounmap 関数
上記では、キャラクター デバイス ドライバーの開発プロセスについて簡単に説明しました。次に、上記のプロセスを接続する例を使用します。この例は、最も単純なキャラクター デバイスです。LEDライト。ボード上の LED ライトを点灯します。これは基本的に GPIO ピンを介してハイ レベルを出力することで行われます。そのため、ここで行う必要があることは実際には非常に簡単です。MMIO で指定されたアドレスに値 (レベル) を書き込むだけです。は、できます。
ただし、MMU が存在するため、仮想アドレスを物理アドレスにマッピングするという重要なリンクを回避することは避けられません。これは MMU によって自動的に完了します。ここでの問題は、ポートの物理アドレスはわかっていますが (ベアメタルで開発しているときと同じように)、それに対応する仮想アドレスがわからないため、プログラム内のポインタを介してこのアドレスにアクセスできないことです。。つまり、アンチマッピングのプロセスを実現することができません。この問題を解決するために、Linux システムでは、IO の物理アドレスを仮想アドレス空間にマッピングするのに役立つ 2 つの関数が提供されています。: ioremap と iounmap
前者は指定された物理アドレス空間に対応する仮想アドレス空間を取得するために使用され、後者はioremap によって作成されたマッピングを解放するために使用されます。
// ioremap完成物理地址到虚拟地址的反映射
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
// ioremap函数本质上是以下__arm_ioremap函数的简单封装
// @param phys_addr : 要反映射的物理地址起始位置
// @param size : 要反映射的地址空间大小
// @param mtype : ioremap的类型,默认传入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));
}
以下は iounmap の関数宣言です。
// 解除ioremap构建的映射关系,只需要将ioremap返回的指针传入即可
// @param addr : 要释放的IO设备在虚拟地址空间中的首地址
void iounmap (volatile void __iomem *addr)
おそらく、奇妙な修飾子 __iomem に気づいたかもしれません。このシンボルに関する記事は数多くあります。一般的に言えば、これは、現在ポイントされているアドレス空間が IO アドレス空間に属していることを示します。また、__iomem 修飾子が付いているポインターは逆参照できません。では、逆参照できないので、このアドレスにアクセスするにはどうすればよいでしょうか? Linux では、次の関数を使用して __iomem を変更したポインターにアクセスする必要があります。そうしないと、コンパイラーがエラーまたは警告を報告する可能性があります。
// 以不同大小来读取__iomem指针
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
// 以不同大小来写入__iomem指针
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
3.2 例 - デバイスの初期化コードの解析
ドライバプログラムの書き方としては、実は非常に簡単なのですが、ここで興味深いのは初期化関数で、そのソースコードがここに掲載されています。最初に、コード内で ioremap を使用して物理アドレスを仮想アドレスにマッピングし、次にGPIO レジスタの構成がベア メタル開発と同様に実行されることがわかります。レジスタの読み取りと書き込みはこれらはすべて、上記の特殊な読み取りおよび書き込み関数によって実行されます。これらは非常に日常的な操作であるため、これ以上の説明は行いません。
やっとregister_chrdev 関数を使用してデバイスドライバーを登録しますこれにより、初期化プロセス全体が完了します。led_init 関数は module_init にエントリ関数として登録されます。、ドライバーモジュールをロードするときにこの関数が実行されるようにします。
static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0;
/* 初始化LED */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清楚以前的设置 */
val |= (3 << 26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* 3、设置GPIO1_IO03的复用功能,将其复用为
* GPIO1_IO03,最后设置IO属性。
*/
writel(5, SW_MUX_GPIO1_IO03);
/*寄存器SW_PAD_GPIO1_IO03设置IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 5、默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/* 6、注册字符设备驱动 */
retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if(retvalue < 0){
printk("register chrdev failed!\r\n");
return -EIO;
}
return 0;
}
上記のプロセスに対応して、エクスポート関数も必要です。コードは次のとおりです。最初に iounmap を使用してマップを解除し、最後にドライバー モジュールの登録を解除します。。
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备驱动 */
unregister_chrdev(LED_MAJOR, LED_NAME);
}
3.3 ボードの検証
上記で書かれたコード (実際には、時間厳守アトム... カーンによって書かれたコード) をコンパイルすると、次のようになります。サフィックス .ko が付いたドライバー モジュール (カーネル オブジェクト)。このモジュールを取得したら、nfs を介して開発ボードに直接マッピングし、配置します。ロード可能なカーネル モジュール フォルダー内, このフォルダーの名前はカーネル バージョンによって異なります。私のパスは/lib/modules/4.1.15-g3dc0a4bです。カーネル ドライバー モジュールをコンパイルするときに使用される Linux カーネル ソース バージョンも /4.1.15-g3dc0a4b である必要があることに注意してください。それ以外の場合はエラーが報告されます。詳細については、この WeChat 記事を参照してください。ボードの検証プロセス中にピットを踏んでしまいました。
検証手順は次のとおりです。
# 1.此命令用于检测模块之间的相互依赖性,应该在调用modprobe之前执行
depmod
# 2.然后调用modprobe进行驱动模块加载
modprobe led.ko
# 这里可能会报错
# root@ATK-IMX6U:/lib/modules/4.1.15-g3dc0a4b# modprobe led.ko
# modprobe: FATAL: Module led.ko not found in directory /lib/modules/4.1.15-g3dc0a4b
# 这种情况只需要去掉.ko的后缀即可
# 3.为设备创建设备节点
# c表示当前设备是字符设备,200和0分别是主设备号和从设备号
mknod /dev/led c 200 0
実際、modprobe コマンドの実行後、ドライバー モジュールがロードされており、次の図に示すように、cat /proc/devices コマンドを通じて確認できます。そしてデバイス ノードの作成は、ファイルの読み取りと書き込みと同様に、このデバイスの使用を容易にするためだけです。。
4. 新しいバージョンのドライバーのコンパイル
ここでは、punctual atom では、新しいキャラクター デバイス ドライバーの開発プロセスが導入されています。ここでは、従来のプロセスとの違いを簡単に書きます。以下の点を要約します。
4.1 最適化 1 - デバイス番号の割り当てプロセス
従来の開発プロセスでは、 register_chrdev 関数を使用してデバイス番号を手動で指定します。特定のデバイス番号が使用されているかどうかを確認するには、cat /proc/devices を使用する必要があります。、これは比較的不便で柔軟性がありません。
retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
新しいバージョンのデバイス ドライバーでは、alloc_chrdev_region および register_chrdev_region などの 2 つの関数Linux システムからデバイス番号を申請するには。これら 2 つの関数の違いは、alloc_chrdev_region 関数がLinux システムにアイドル状態のデバイス番号を自動的に割り当てさせます。register_chrdev_region は、指定されたデバイス番号に対する Linux システムに対するユーザーのアプリケーションです。。これは、新しいバージョンのドライバー コードで行われます。
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);
// ...
}
つまり、ユーザーが事前にデバイス番号を指定している場合は、 register_chrdev_region 関数を使用して直接適用します。それ以外の場合は、 alloc_chrdev_region 関数を使用して、Linux システムが自動的にデバイスの割り当てを支援できるようにします。newchrled.devid の量を介して直接引き出す。
もちろん、システムからデバイス番号を申請した後にデバイス番号を解放する必要があるため、終了関数コードは次のようになります。デバイス番号を解放するプロセスがあることがわかります。この操作は unregister_chrdev_region 関数を使用して行われます。 、この機能特に、alloc_chrdev_region 関数および register_chrdev_region 関数によって割り当てられたデバイス番号を解放するために使用されます。。
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备驱动 */
cdev_del(&newchrled.cdev);/* 删除cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
4.2 最適化2 - 新しいキャラクターデバイスの登録方法
以前のバージョンでは、 register_chrdev 関数を使用してキャラクタ デバイスをカーネルに登録していましたが、新しいバージョンのドライバを作成する場合、このプロセスは次のようになります。cdev の特殊な構造を使用して直列に接続されたプロセス。つまり、Linux カーネルには、キャラクター デバイス、つまり cdev を表す特別なデータ構造があります。
struct cdev {
struct kobject kobj;
struct module *owner; // 一般是THIS_MODULE
const struct file_operations *ops; // 字符设备的各个动作都记录在此
struct list_head list;
dev_t dev; // 设备号
unsigned int count;
};
カーネルにデバイスを登録するアクションは次の手順になります。
// 1.使用cdev_init函数初始化一个cdev设备
// @param cdev : 指向设备的指针
// @param fops : 设备支持的驱动操作
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
// 2.使用cdev_add函数将上面初始化好的字符设备添加到内核中
// @param p : 指向字符设备的指针
// @param dev : 设备号
// @param count : 要添加的设备数量
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
// 3.在出口函数中,删除掉这个字符设备,将设备指针传入即可
// @param p : 指向字符设备的指针
void cdev_del(struct cdev *p)
4.3 最適化 3 - デバイスノードを自動作成する
古いバージョンのドライバー コードでは、.ko モジュールを取得した後、mknod コマンドを使用してデバイス ノードを自分で作成する必要がありますが、新しいバージョンのコードでは、mdev メカニズムを使用してノードを自動的に作成できます。。ここでは mdev メカニズムを深く理解していませんが、開発プロセスを直接示します。cdev_add操作の後に class_create 関数と device_create 関数を使用してこのデバイス ノードを作成する必要があります。
// 1.在cdev_add操作之后创建一个类
// @param owner :此模块属于什么机构,一般写为THIS_MODULE
// @param name : 类的名称
struct class *class_create (struct module *owner, const char *name)
// 2.使用device_create函数创建一个设备
// @param class : 设备所属的类,就用上面创建出来的类
// @param parent : 该设备的父设备,如果是单一模块开发,则设为NULL
// @param devt : 设备号
// @param drvdata : 设备的一些附加数据,一般设为NULL
// @param fmt : 设备名字,传入“xxx”时,会生成名为/dev/xxx的设备
struct device *device_create( struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
// 2.与上述函数对应的,在出口函数中应该将创建的设备和类都销毁
void class_destroy(struct class *cls);
void device_destroy(struct class *class, dev_t devt);
4.4 データの保護
上記の新しい関数と構造の導入後、関数に関連付けられた多くの情報が存在します。キャラクターデバイスは cdev のような構造に抽象化されます。、クラス class にも属しており、デバイス自体として、独自の関連情報も持っています。したがって、この情報の論理的関連性を確保するために、通常は次のように構造として宣言して使用します。
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
// 使用上面的结构体声明一个设备,这就是我们要操作的设备实例
// 注意,最好将它注册进flip->private_data
struct newchrled_dev newchrled; /* led设备 */
このような構造は、実際に定義したデバイス全体を抽象化します。デバイスの open 関数では、そのアドレスは通常、flip->private_data フィールドに登録されます。たとえば、新しいバージョンのドライバー コードには次の実装があります。
// 将led设备对应的结构体注册到private_data字段
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
4.5 概要
ここでは、exit 関数とentry 関数のコード実装を示します。古いバージョンのコード実装との比較に注意してください。
4.5.1 新規エントリー機能の実装
/* 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设备 */
static int __init led_init(void)
{
u32 val = 0;
/* 初始化LED */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清楚以前的设置 */
val |= (3 << 26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* 3、设置GPIO1_IO03的复用功能,将其复用为
* GPIO1_IO03,最后设置IO属性。
*/
writel(5, SW_MUX_GPIO1_IO03);
/*寄存器SW_PAD_GPIO1_IO03设置IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 5、默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
// ----------------------------以下代码和老版本实现不一致----------------------
// ---------------------------4.1 优化1——设备号的分配过程----------------------------
/* 注册字符设备驱动 */
/* 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);
// ---------------------------4.2 优化2——新的字符设备注册方法------------------------
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
/* 3、添加一个cdev */
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
// ---------------------------4.3 优化3——自动创建设备节点------------------------
/* 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;
}
4.5.2 新しいエクスポート機能の実装
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
// ---------------新版出口函数要做的事情-------------
// 1.注销字符设备
// 2.注销设备号
// 3.注销设备节点
/* 注销字符设备驱动 */
cdev_del(&newchrled.cdev);/* 删除cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}