i.MX6ULL(16) Linux デバイスドライバー

簡単な紹介

Linux デバイス ドライバーは、Linux カーネルを駆動してハードウェア デバイスと通信するソフトウェア モジュールを指します。デバイス ドライバーは通常、キャラクター デバイス ドライバーとブロック デバイス ドライバーの 2 つのカテゴリに分類されます。

デバイス ドライバーの主な機能は次のとおりです。

  1. デバイスの初期化: システムが起動すると、デバイスが正常に動作できるように、デバイス ドライバーは対応するハードウェア デバイスを初期化し、デバイスのレジスタとインターフェイスのパラメーターを設定する必要があります。
  2. デバイス制御: デバイス ドライバーは、デバイスのオープン、データの読み取り、データの書き込み、デバイスのクローズなど、デバイスのさまざまな操作を制御するためのいくつかのインターフェイスを提供する必要があります。
  3. 割り込み処理: デバイスドライバーは、ハードウェアデバイスの割り込み要求を処理し、割り込みが発生したときに対応する割り込み処理プログラムを実行して、デバイスのさまざまなイベントや要求に適時に応答する必要があります。
  4. データ送信: デバイス ドライバーは、ハードウェア デバイスからのデータの読み取りやハードウェア デバイスへのデータの書き込みなどの操作を含む、データ送信機能を実装する必要があります。
  5. エラー処理: デバイス ドライバーは、デバイスの読み取りおよび書き込みエラー、割り込み損失など、デバイスで発生するエラーと例外を処理する必要があります。

1.1 デバイスドライバーの分類

1.1.1 ブロックデバイスドライバー

ブロック デバイス ドライバーとは、ハード ドライブ、ソリッド ステート ドライブ、USB フラッシュ ドライブなどのストレージ デバイス ドライバーなど、ブロック単位でデバイスと通信するドライバー プログラムを指します。

ブロック デバイス ドライバーは通常、次のレベルを含む階層構造を採用します。

  1. デバイス ドライバー層: この層はハードウェア デバイスと直接通信し、デバイスのさまざまな動作を制御します。
  2. ストレージ ボリューム マネージャー層: この層は、ストレージ ボリューム (ハードディスク パーティション、論理ボリュームなど) を管理し、ボリュームへのアクセス インターフェイスを提供する役割を果たします。
  3. ファイル システム層: この層は、ボリュームへのファイル システム インターフェイスを提供し、ユーザーがファイル システムのディレクトリ構造に従ってファイルにアクセスし、管理できるようにします。

1.1.2 キャラクターデバイスドライバー

キャラクター デバイスは、 Linuxドライバー の最も基本的なタイプのデバイス ドライバーです。キャラクター デバイスは、バイトに応じてバイトです。
ストリームに対して読み取りおよび書き込み操作を実行するデバイスの場合、データは順次に読み取りおよび書き込みされます。たとえば、最も一般的な照明、ボタン、 IIC SPI
LCD などはすべてキャラクターデバイスであり、これらのデバイスのドライバーをキャラクターデバイスドライバーと呼びます。

I2C/SPI デバイスは、その機能とアプリケーション シナリオに応じて、キャラクター デバイス、ブロック デバイス、またはネットワーク デバイスになります。

ただし、一部の I2C デバイスについては、ブロック デバイスやネットワーク デバイスなど、他のタイプのデバイスとして分類される場合があります。たとえば、EEPROM などの特定の I2C メモリは、データをブロックで転送するため、ブロック デバイスの例とみなすことができます。温度センサーや光センサーなどの他の I2C デバイスは、データをパケットで送信するため、ネットワーク デバイスとみなすことができます。

したがって、すべての I2C/SPI デバイスがキャラクター デバイスであるわけではなく、機能やアプリケーション シナリオに応じてさまざまなタイプのデバイスに分類される場合があります。デバイスの種類が異なると、デバイスにアクセスして制御するために異なるドライバーが必要になります。

Linuxアプリケーションプログラムによるドライバプログラムの呼び出しを図40.1.1に示します。

Linux では、 すべてがファイルです。ドライバーが正常にロードされると、 対応するファイルが「 /dev 」ディレクトリに生成されます。
このプログラムは、「 /dev/xxx という名前のファイルに対して対応する操作を実行することで実装できます (xxx は特定のドライバー ファイルの名前です ) 。
次に、ハードウェアを操作します。たとえば、現在は /dev/ledというドライバー ファイルがあり 、これは LED ライトのドライバー ファイルです。アプリ
プログラムは、 open 関数を使用してファイル /dev/ledを開き 、使用後に close 関数を使用してファイル /dev/ledを閉じます open と closeは、 LEDドライバー をオンまたはオフにする関数です。LED オンまたはオフにしたい場合は、 write関数を使用して操作します。つまり、ドライバーにデータを書き込みます。このデータは制御です。パラメータを使用して LED をオフまたはオンにします。入手したい場合は
LED ライトの状態については、 読み取り 関数を使用してドライバーから対応する状態を読み取ります。
アプリケーションはユーザー空間で実行されますが、 Linux ドライバーはカーネルの一部であるため、ドライバーはカーネル空間で実行されます。
open関数を使用して /dev/ledドライバー を開く など、ユーザー空間のカーネルに操作を実装したい場合、
ユーザー空間はカーネル上で直接動作できないため、実装には「システムコール」と呼ばれるメソッドを使用する必要があります。
基盤となるドライバーの動作を実現するために、カーネル空間に時間を「閉じ込め」ます。

デバイスドライバーの2 つの原則

2.1 ドライバーモジュールのロードとアンロード

2.1.1 読み込み

Linux ドライバーを実行するには 2 つの方法があります。1 つ目は、ドライバーを Linuxカーネルにコンパイルして、 Linuxカーネルの起動 時に
起動時にドライバーが自動的に実行されます。2 つ目は、ドライバーをモジュールにコンパイルすることです ( Linux でのモジュールの拡張子は .ko です)
Linuxカーネルが起動し たら、「 insmod 」コマンドを使用してドライバー モジュールをロードします。ドライバーをデバッグするときは、通常、ドライバーをコンパイルすることを選択します。
これはモジュールであるため、ドライバーを変更した後は、 Linux コード全体をコンパイルするのではなく、ドライバー コードをコンパイルするだけで済みます。
デバッグ時には、ドライバー モジュールをロードまたはアンロードするだけで済みます。
module_init(xxx_init); //モジュールロード関数を登録
module_exit(xxx_exit); //モジュールアンインストール関数を登録

ドライバーがコンパイルされると、拡張子は.koになります。ドライバー モジュールをロードするには、insmodmodprobeの 2 つのコマンドがあります。

insmod コマンドはモジュールの依存関係を解決できません
たとえば、 drv.ko が first.koモジュール に依存している場合は 、まず insmod コマンドを使用してfirst.koモジュールをロードし、次にdrv.koモジュールをロードする必要があります。
modprobe はモジュールの依存関係を分析し、すべての依存モジュールをカーネルにロードします。
insmod drv.ko
modprobe   drv.ko

2.1.2 アンインストール

ドライバー モジュールをアンインストールするには、コマンド「 rmmod 」を使用します。たとえば、 drv.koをアンインストールするには
rmmod drv.ko
modprobe -r drv.ko //
modprobeコマンド を使用して 、ドライバー モジュールが依存する他のモジュールをアンインストールします (これらの依存モジュールが既に存在しない場合)。
これは他のモジュールによって使用されます。それ以外の場合、 modprobe を使用して ドライバー モジュールをアンロードすることはできません。したがって、モジュールをアンインストールするには、rmmod コマンドを使用することをお勧めします。

2.2 アドレスマッピング MMU

MMUの正式名称 はMemory Manage Unitと呼ばれ 、メモリ管理単位のことです。古いバージョンの Linux では、 プロセッサに MMU が必要でした が、現在は
Linuxカーネルは、 MMU のないプロセッサをすでにサポートしています MMUの主な機能は次のとおりです。
①. 仮想空間から物理空間へのマッピングを完了します。
②. メモリ保護、メモリのアクセス権の設定、仮想ストレージ空間のバッファリング特性の設定
32ビット プロセッサ の場合 、仮想アドレス範囲は 2^32=4GB です。
Linuxカーネルが起動すると、 MMU が 初期化され 、メモリ マップがセットアップされ、セットアップ後はすべてのCPUアクセスが仮想化されます。
提案された住所。
たとえば、I.MX6ULL の GPIO1_IO03 ピンの多重化レジスタ IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 のアドレスは 0X020E0068です
MMUが有効になっていない 場合は、レジスタ アドレス 0X020E0068 にデータを直接書き込み、 GPIO1_IO03の多重化機能を設定します。
これで、MMU がオンになり 、メモリ マッピングが設定されたため、 アドレス 0X020E0068にデータを直接書き込むことはできなくなります。 Linuxシステムの物理アドレス 0X020E0068に対応する仮想アドレスを取得する必要があります 。これには物理メモリと仮想メモリ間の変換が含まれ、 ioremapiounmapという 2 つの関数を使用する必要があります

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));
 }
この関数には 3 つのパラメータと戻り値があり、これらのパラメータと戻り値の意味は次のとおりです。
phys_addr : マップする物理的な開始アドレス。
size : マッピングされるメモリ空間のサイズ。
mtype : ioremapのタイプ MT_DEVICE MT_DEVICE_NONSHARED
MT_DEVICE_CACHED および MT_DEVICE_WCの場合 ioremap 関数は MT_DEVICEを選択します
戻り値: マッピングされた仮想空間の最初のアドレスを指す、 __iomem型のポインター。

2.2.2  iounmap関数

ドライバーをアンインストールする場合は、 iounmap関数を使用し て、 ioremap関数で作成したマッピング を解除する必要があります。
次のように入力します
void iounmap (volatile void __iomem *addr)
iounmap に はパラメータ addr が1 つだけあり 、これはマップ解除される仮想アドレス空間の最初のアドレスです。

2.2.3 I/Oメモリアクセス関数

Linux は仮想アドレスを操作することで物理アドレスを間接的に操作します

1. リード操作機能
 u8 readb(const volatile void __iomem *addr)
 u16 readw(const volatile void __iomem *addr)
 u32 readl(const volatile void __iomem *addr)

書き込み操作機能

 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 デバイス番号

Linux の各デバイスにはデバイス番号があります。デバイス番号は、メジャー デバイス番号とマイナー デバイス番号の 2 つの部分で構成されます。メジャー デバイス番号は特定のドライバを表し、マイナー デバイス番号はこのドライバを使用する各デバイスを表します。

2.3.1 構成

Linux では、 デバイス番号を表す dev_t という名前のデータ型が 提供されています 。dev_tはファイルinclude/linux/types.h で定義されています。
dev_t __u32 タイプの上位 12 ビットはメジャー デバイス番号、下位 20 ビットはマイナー デバイス番号です。したがって、 Linux
システム内の主装置番号の範囲は 0 ~ 4095である ため、主装置番号を選択する場合はこの範囲を超えないようにしてください。

2.3.2 デバイス番号の割り当てと解除

静的割り当て
一般的に使用されるデバイス番号の一部は Linuxカーネル開発者によって 割り当てられており、具体的な割り当て内容については Documentation/devices.txt を参照してください カーネル開発者によって割り当てられたメインデバイス番号が使用できないわけではありません。使用できるかどうかは、ハードウェアプラットフォームの動作中にメインデバイス番号が使用されるかどうかによって決まります。「cat /」を使用します。 proc/devices」 コマンド 現在のシステムで使用されているすべてのデバイス番号を表示できます。
動的割り当て
デバイス番号を静的に割り当てるには、現在のシステムで使用されているすべてのデバイス番号を確認し、使用されていないデバイス番号を選択する必要があります。
の。さらに、デバイス番号を静的に割り当てると競合が発生する可能性が高いため、 Linux コミュニティではデバイス番号を動的に割り当てることを推奨しています。
デバイスに署名する前にデバイス番号を申請すると、システムが未使用のデバイス番号を自動的に付与するため、競合が回避されます。
ドライバーのアンインストール時にデバイス番号を解除するだけで、デバイス番号の適用機能は次のようになります。
アプリケーション機能
int alloc_chrdev_region(dev_t *dev, unsigned Baseminor, unsigned count, const char *name)
Baseminor : マイナーデバイス番号の開始アドレス 。alloc_chrdev_region は連続する複数のデバイス番号に適用できます。
これらのデバイス番号のメジャーデバイス番号は同じですが、マイナーデバイス番号は異なり、マイナーデバイス番号は開始アドレスとして baseminorで 始まります。
増加。 一般に、baseminor は 0 です。つまり、マイナー デバイス番号は 0 から始まります。
count : 申請するデバイス番号の数。
登録機能
デバイスのメジャーデバイス番号とマイナーデバイス番号が指定されている場合は、次の関数を使用してデバイス番号を登録します。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
3 ログアウト機能
キャラクタデバイスの登録を解除した後、デバイス番号を解放する必要があります。デバイス番号の解放機能は次のとおりです。
void unregister_chrdev_region(dev_t from、unsigned count)
from : 解放するデバイス番号。
count : から解放されるデバイス番号の数を 示します。

2.4 キャラクタデバイスの新規登録方法

2.4.1 、キャラクターデバイスの構造

Linux キャラクターデバイスを表すにはcdev構造 を使用します 。cdev構造はinclude/linux/cdev.hファイルにあります
は次のように定義されます。
 struct cdev {
 struct kobject kobj;
 struct module *owner;
 const struct file_operations *ops;
 struct list_head list;
 dev_t dev;
 unsigned int count;
 };
cdevには opsdev という 2 つの重要なメンバー変数があります 。これら 2 つは、キャラクタ デバイス ファイル操作関数のコレクションです。
file_operations とデバイス番号 dev_t

2.4.2 cdev_init関数

cdev変数 を定義した後、cdev_init関数で初期化する 必要があります。cdev_init 関数のプロトタイプは次のとおりです。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
パラメータ cdev は 初期化される cdev 構造体変数、パラメータ fops はキャラクタデバイスファイル操作関数のセットです。
cdev_init関数 を使用してcdev変数を 初期化するサンプル コード は次のとおりです。

/* 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関数

cdev_add関数は 、キャラクター デバイス(cdev構造変数)をLinuxシステム に追加するために使用されます
まず cdev_init 関数を使用して cdev 構造体変数の初期化を完了し 、次に cdev_add関数を使用して このキャラクター デバイスをLinuxシステム に追加します。
cdev_add 関数のプロトタイプは次のとおりです。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
パラメータ p は 追加するキャラクタ デバイス (cdev 構造変数 )を指し 、パラメータ dev はデバイスで使用されるデバイス番号です。を参照してください。
count カウントは 、追加するデバイスの数です。

2.4.4、cdev_del関数

ドライバーをアンインストールするときは、必ず cdev_del 関数を使用して 、対応するキャラクター デバイスを Linuxカーネル cdev_delから削除してください。
関数のプロトタイプは次のとおりです。
void cdev_del(struct cdev *p)
パラメータ p は 、削除する文字デバイスです。

2.5 デバイスノードを自動作成する

前回の Linux ドライバーの実験では、 modprobeを使用して ドライバーをロードするときに、次のコマンドを使用する必要がありました。
mknod 」はデバイス ノードを手動で作成します。このセクションでは、デバイス ノードを自動的に作成し、ドライバーに実装する方法について説明します。
デバイス ノードの自動作成機能の後、 modprobe を使用して ドライバー モジュールが正常にロードされると、ドライバー モジュールは自動的に /dev ディレクトリに配置されます。
対応するデバイスファイルを作成します。

2.5.1 mdevメカニズム

udev はユーザー プログラムです。Linux では udev はデバイス ファイルの作成と削除に使用されます。udevチェックできます。
システム内のハードウェアデバイスの状態を検出し、システム内のハードウェアデバイスの状態に応じてデバイスファイルを作成または削除します。使用するなど
modprobe コマンドがドライバー モジュールを正常にロードすると、対応するデバイス ノード ファイルが /dev ディレクトリに自動的に 作成され ます
rmmodコマンドがドライバー モジュールをアンインストールした後、 /devディレクトリ内のデバイス ノード ファイルは 削除されます。 Busybox を使用してルート ファイルを構築する
システムでは、busybox は udev の簡易バージョン ( mdev ) を作成するため 組み込み Linux では次のように使用します。
mdev はデバイス ノード ファイルの自動作成と削除を実現し、Linux システムのホット プラグ イベントも mdev によって管理されます。
/etc/init.d/rcS ファイル内の次のステートメント:
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdevを使用して デバイス ファイル ノードの自動作成と削除を実現するにはどうすればよいですか?
2.5.1.1 クラスの作成と削除
デバイス ノードを自動的に作成する作業は、ドライバーのエントリ関数で (通常は cdev_add 関数の後に) 行われます。
デバイスノード関連のコードの自動作成を追加します。まず、 クラス class を作成します。class ファイルで定義された構造体です。
/linux/device.h を 内部に含めます。 class_create はクラス作成関数、 class_create はマクロ定義、
struct class *class_create (struct module *owner, const char *name)
ドライバーをアンインストールする場合はクラスを削除する必要がありますが、クラス削除関数は class_destroyであり 、関数のプロトタイプは次のとおりです。
void class_destroy(struct class *cls);
2.5.1.2 デバイスの作成
device_create関数 を使用して クラスの下にデバイスを作成する
struct device *device_create(struct クラス
*クラス、
構造体デバイス*親、
開発者
開発者、
空所
*drvdata、
定数文字
*ふむ、...)
device_create は可変パラメータ関数であり、パラメータ クラスは デバイスが作成されるクラスであり、パラメータ parent は親です。
デバイス、通常は NULL 、つまり親デバイスがありません。パラメータ devt はデバイス番号です。パラメータ drvdata は使用できるデバイスです。
一部のデータ (通常は NULL )。パラメータ fmt は デバイス名です。 fmt=xxxが設定されている場合、 /dev/xxx が生成されます。
このデバイスファイル。戻り値は作成されたデバイスです。
ドライバーをアンインストールする場合も同様に、作成したデバイスを削除する必要がありますが、デバイス削除関数は device_destroy 、本来の関数は
種類は次のとおりです。
void device_destroy(struct class *class, dev_t devt)

2.6 ファイルのプライベートデータを設定する

各ハードウェア デバイスには、メイン デバイス番号 (dev_t) 、クラス (class) 、デバイス (device) 、スイッチ状態 (state) などのいくつかの属性があります。
待ってください。次のように、 ドライバーを作成するときにこれらすべてのプロパティを変数として書き込み、ドライバーのオープン関数を作成するときにデバイス構造をプライベート データとしてデバイス ファイルに追加できます。
/* 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")

おすすめ

転載: blog.csdn.net/TyearLin/article/details/131745751