【Linux】迷ったら先にライトをつければいい、LED駆動の進化---1

【Linux】迷ったら先にライトをつければいい、LED駆動の進化---1

序文:

1. 最も単純な LED ドライバー

1.1 キャラクターデバイスドライバーフレームワーク

1.2 プログラム戦闘

1.2.1 ドライバー (led_drive_simple.c)

1.2.2 アプリケーション (led_test_simple.c)

1.2.3 メイクファイルコード

1.3 テストの実行

1.3.1 最初にカーネルをコンパイルします (コンパイルされていない場合)

1.3.2 クロスコンパイル ツールチェーンのセットアップ (Ubuntu)

1.3.3 コンパイラ(無料)

1.3.4 オンマシンテスト(開発ボード)

2. LEDドライバー(積層型)

2.1 階層化されたデザインのアイデア

2.2 プログラム戦闘

2.2.1 ヘッダファイル(led_opr.h)

2.2.2 ドライバー (board_imx6ull.c)

2.2.3 ドライバー (led_drive.c)

2.2.4 アプリケーションプログラム(led_test.c)

2.2.5 メイクファイルコード

2.3 テストの実行

2.3.1 オンマシンテスト(開発ボード)

2.4 概要

3. LEDドライバー(分離)

3.1 設計アイデアの分離

3.2 プログラム戦闘

3.2.1 ヘッダーファイル(led_resource.h)

3.2.2 ドライバー (board_A_led.c)

3.2.3 ドライバー (chip_imx6ull_gpio.c)

3.2.4 メイクファイルコード

3.3 テストの実行

3.4 概要

4. LEDドライバー(バスデバイスドライバーモデル)

4.1 バスデバイスドライバーモデル

4.2 プログラム戦闘

4.2.1 ヘッダーファイル(led_drive.h)

4.2.2 ドライバー (led_drive.c)

4.2.3 ドライバー (board_A_led.c)

4.2.4 ドライバー (chip_imx6ull_gpio.c)

 4.2.5 メイクファイルコード

 4.3 試運転

4.4 概要 

5. LEDドライバー(デバイスツリー)

5.1 デバイスツリー

5.1.1 デバイスツリーの背景

5.1.2 デバイスツリーの簡単な説明

5.2 プログラム戦闘

5.2.1 デバイスツリー dts ファイル (100ask_led.dts)

 5.2.2 ドライバー (chip_imx6ull_gpio.c)

 5.2.3 メイクファイルコード

5.3 概要


序文:

この記事では、LED ドライバーの進化とアップグレードのプロセスを示し、ドライバー フレームワークの概念を浅いものから深いものまでさらに詳しく説明します。わからないことに出会ったら、まずランプを灯してみるのもいいかもしれません。

LEDドライバー進化の道:(レベル進行)

  1. 最も単純な LED ドライバー
  2. 多層的な考え方による LED ドライバー
  3. 分割された LED ドライバーを追加しました
  4. バスデバイスドライバーモデル下の LED ドライバー
  5. LEDドライバーがデバイスツリーに追加されました

参考:魏氏の講座、Linux Notes教師向け講座(デバイスツリー部分)

https://blog.csdn.net/qq_33487044/article/details/126325656

https://www.bilibili.com/video/BV14f4y1Q7ti?p=12&spm_id_from=pageDriver&vd_source=cf66c4035cd726f1d3cb6a42cfd6da5f

運転の枠組みを一通り理解したので、おおよその理解はできましたが、さらに実践的な経験が必要です。

https://blog.csdn.net/weixin_42373086/article/details/130521999

1. 最も単純な LED ドライバー

1.1 キャラクターデバイスドライバーフレームワーク

最も単純なドライバーでは、デバイス アプリが open を呼び出すと、ドライバー内で drv_open、read→drv_read、write→drv_write、ioctrl→drv_ioctl が提供されます。

ドライバーを作成する手順: hello ドライバー フレームワークとまったく同じ

  1. メインデバイス番号を決定します (自分で定義できます。0 に設定すると、カーネルによって割り当てられます)。
  2. 独自の file_operations 構造管理ドライバー プログラム drv_open/drv_write などを定義し、drv_open/drv_write などの対応する関数を実装します。---注: file_operations がコアです
  3.  file_operations 構造体をカーネルに伝えます: register_chrdev (メジャー デバイス番号を指定します)。
  4. エントリ関数を実装します。ドライバのインストール時にエントリ関数は register_chrdev を呼び出し、対応する終了関数があります。ドライバがアンインストールされると、終了関数は unregister_chrdev を呼び出します。
  5. 補助機能: デバイス情報の提供、デバイスノードの自動作成: class_write、device_create

1.2 プログラム戦闘

1.2.1 ドライバー (led_drive_simple.c)

具体的なコメントと分析はコード内にあります。

/* 说明:
	*1,本代码是跟学韦老师课程所编写,增加了注释和分析
	*2,采用的是UTF-8编码格式
	*3,简单LED驱动程序 led_drive_simple.c
	*4,参照内核字符设备驱动程序cm4040_cs.c
*/


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>



/*registers*/
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;




static struct class *led_class;
/*第一步:确定主设备号,这里由内核分配*/
static int major = 0;


/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:		led_drv_open
 *功能:		enable gpio				 使能gpio
 			configure pin as gpio    设置引脚为GPIO
 			configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
 			*flip:要打开的文件
*返回参数: 如果成功返回0
*/			

static int led_drv_open(struct inode *inode, struct file *filp)
{
	//imx6ull默认使能GPIO5.
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |=  0x5;

	*GPIO5_GDIR |= (1<<3);
	return 0;
}

/*
 *函数:      led_drv_write
 *功能:		copy_from_user:get data form app  获取app的数据,并设置gpio的寄存器
 			set gpio register: out 1/0
 *传入参数:
 			*flip:要写的文件
 			*buf: 写的数据来自于buf
 			*size:写多大的数据
 			*offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char val;
	int err;
	err = copy_from_user(&val, buf, 1);
	if(val)
	{
		*GPIO5_DR &= ~(1<<3);
	}else
	{
		*GPIO5_DR |= (1<<3);	
	}
	return -1;
	
}

			 
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_drv_write,
	.open		= led_drv_open,

};


/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *功能:		①注册主设备号	
 			②获取寄存器物理地址映射过来的虚拟地址
 			③辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
	/*
	 *printk:判断一下是否调用了入口函数
	 *__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/	
	printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);

	//ioremap:物理地址映射到虚拟地址
	IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
	GPIO5_GDIR = ioremap(0x020AC004, 4);
	GPIO5_DR  = ioremap(0x020AC000, 4);

	
	
	//0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
	major = register_chrdev(0,"xixiwuli_led", &led_fops);	
	led_class = class_create(THIS_MODULE, "led_2345");
	if (IS_ERR(led_class))
		return PTR_ERR(led_class);
	if (major < 0) {
		class_destroy(led_class);
		return major;
	}
	//创建/dev/myled的设备节点
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");
	return 0;
}


/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{
	iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
	iounmap(GPIO5_GDIR);
	iounmap(GPIO5_DR);
	
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major,"xixiwuli_led");
}


//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

1.2.2 アプリケーション (led_test_simple.c)

具体的なコメントと分析はコード内にあります。

/* 说明:
	*1,本代码是跟学韦老师课程所编,增加了注释和理解
	*2,采用的是UTF-8编码格式
	*3,简单LED应用程序 led_test_simple.c
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>


//led_test_simple /dev/myled on
//led_test_simple /dev/myled off

int main(int argc, char** argv)
{
	int fd;
	char status;
	
	if(argc != 3)
	{
		printf("usage: %s <dev> <on|off>\n", argv[0]);
		printf("eg: %s /dev/myled on\n", argv[0]);
		printf("eg: %s /dev/myled off\n", argv[0]);
	}

	//open
	fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		printf("can not open%s\n",argv[1]);
		return -1;
	}
	
	//write
	if(strcmp(argv[2], "on") == 0)
	{
		status = 1;
		write(fd, &status, 1);
	}	
	if(strcmp(argv[2], "off") == 0)
	{
		status = 0;
		write(fd, &status, 1);
	}
	return 0;

}

1.2.3 メイクファイルコード

具体的なコメントと分析はコード内にあります。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test_simple led_test_simple.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test_simple

obj-m	+= led_drive_simple.o

1.3 テストの実行

1.3.1 最初にカーネルをコンパイルします (コンパイルされていない場合)

この記事を参照してくださいhttps://blog.csdn.net/weixin_42373086/article/details/129796348?spm=1001.2014.3001.5501

1.3.2 クロスコンパイル ツールチェーンのセットアップ (Ubuntu)

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

1.3.3 コンパイラ(無料)

makeでコンパイルした後、.koファイルとled_test_simpleをnfsでマウントしたフォルダにコピーします。

make
cp *.ko led_test_simple ~/nfs_rootfs/

1.3.4 オンマシンテスト(開発ボード)

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
//复制到开发板上
cp /mnt/led_drive_simple.ko ./
cp /mnt/led_test_simple ./
//安装驱动模块
insmod led_drive_simple.ko
 
//查询是否有我们的hello程序
cat /proc/devices
lsmod
 
//查询是否有我们的设备节点
ls /dev/myled -l
//打开
./led_test_simple /dev/myled on
//关闭
./led_test_simple /dev/myled off

デバイスの結果をクエリします。

 ライトのオンとオフ:

2. LEDドライバー(積層型)

2.1 階層化されたデザインのアイデア

複数のボードで同じデバイスを駆動する(例えばランプを点灯する)場合、上記の方法で対応するドライバプログラムを最初から最後まで記述するのは非常に面倒です。

つまり、この LED ドライバーは複数のボードをサポートできますか? どうやって?

上記の状況を考慮すると、階層化の考え方を適用する必要があり、最初に、図に示すように、ドライバーを一般的なフレームワーク (leddrv.c) と特定のハードウェア操作 (board_X.c) に分解する必要があります。次の図:

ここでさらに一歩進んで、オブジェクト指向の考え方で構造を抽象化し、各ボードに関係するboardX.cは、上位のleddrv.cから呼び出される独自のled_operations構造体を実現します。

struct led_operations
{
	int (*init) (int which); /*初始化LED,which---哪个LED*/
	int (*ctl) (int which, int status);/*控制LED,which-哪个LED,status:1/0亮灭*/
};

2.2 プログラム戦闘

 最も単純なフレームワークのドライバー プログラムと比較すると、コード内のアノテーションに違いがあります。

2.2.1 ヘッダファイル(led_opr.h)

このヘッダー ファイルは、board_imx6ull.c と led_drive.c の間のブリッジとして使用されます。

//定义这个宏,防止二次调用
//例子LED调用LEDA,LEDA调用这个头文件,未加时,会二次调用这个头文件
//加了之后,第二次调用时,就不再其效用了。
#ifndef _LED_OPR_H
#define _LED_OPR_H

struct led_operations
{
	int num;
	int (*init) (int which); /*初始化LED,which---哪个LED*/
	int (*ctl) (int which, char status);/*控制LED,which-哪个LED,status:1/0亮灭*/
};

struct led_operations *get_board_led_opr(void);

#endif

2.2.2 ドライバー (board_imx6ull.c)

ここから、対応するボード上のハードウェア操作がすべてこのプログラム (この層) に実装されていることがわかります。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

#include "led_opr.h"


/*registers*/
//CCM_CCGR1:0x20C406C
static volatile unsigned int* CCM_CCGR1;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;

/*
 *函数:    	board_demo_led_init
 *功能:		enable gpio				 使能gpio
 			configure pin as gpio    设置引脚为GPIO
 			configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
 			*which:哪个LED
 *返回参数:如果成功返回0
*/


static int board_demo_led_init(int which)
{
	unsigned int val;
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
	if(which == 0)
	{
		if(!CCM_CCGR1)
		{
			//ioremap:物理地址映射到虚拟地址
			CCM_CCGR1 = ioremap(0x20C406C, 4);
			IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
			GPIO5_GDIR = ioremap(0x020AC004, 4);
			GPIO5_DR  = ioremap(0x020AC000, 4);
		}

		
		*CCM_CCGR1 |= (3<<30);
		val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
		val &= ~0xf;
		val |=  0x5;
		*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

		*GPIO5_GDIR |= (1<<3);
	}
	return 0;
}


/*
 *函数:    	board_demo_led_init
 *功能:		set gpio register: out 1/0

 *传入参数:
 			*which:哪个LED
 			*status: 状态1/0,亮灭
 *返回参数:如果成功返回0
*/
static int board_demo_led_ctl(int which, char status)
{
	if(which == 0)
	{
		if(status)
		{
			*GPIO5_DR &= ~(1<<3);
		}else
		{
			*GPIO5_DR |= (1<<3);
		}
	}
	return 0;
}


static struct led_operations board_demo_led_opr =
{
	.num  = 1,
	.init = board_demo_led_init,
	.ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
	return &board_demo_led_opr;
}

2.2.3 ドライバー (led_drive.c)

以前のフレームワークでは、ここでデバイスの登録とハードウェア操作を実装していましたが、現在はデバイス ドライバーの登録などの一般的な機能のみを実装しています。

/* 说明:
	*1,本代码是跟学韦老师课程所编写,增加了注释和分析
	*2,采用的是UTF-8编码格式
	*3,LED驱动程序(分层思想) led_drive.c
*/

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>


#include "led_opr.h"



static struct class *led_class;
struct led_operations *p_ledopr;


/*第一步:确定主设备号,这里由内核分配*/
static int major = 0; 

/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:		led_drv_open
 *差异点:      根据次设备号初始化LED
 *传入参数:
 			*flip:要打开的文件
 *返回参数:如果成功返回0
*/			

static int led_drv_open(struct inode *inode, struct file *filp)
{
	int minor = iminor(inode);/*次设备号*/

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	p_ledopr->init(minor);
	return 0;
}

/*
 *函数:       led_drv_write
 *差异点:		根据次设备号和status控制LED
 *功能:		copy_from_user,从app中获取数据
 *传入参数:
 			*flip:要写的文件
 			*buf: 写的数据来自于buf
 			*size:写多大的数据
 			*offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char status;
	int err;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);
	
	/*根据次设备号控制LED*/
	struct inode* inode = file_inode(filp);
	int minor = iminor(inode) & 0x0f;
	p_ledopr->ctl(minor, status);
	return -1;
	
}

			 
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_drv_write,
	.open		= led_drv_open,

};


/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *差异点:      有多个次设备号
 *功能:		①注册主设备号	
 			②辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
	/*
	 *printk:判断一下是否调用了入口函数
	 *__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/	
	printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
	

		
	//0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
	major = register_chrdev(0,"xixiwuli_led", &led_fops);	
	led_class = class_create(THIS_MODULE, "led_2345_class");
	int i;
	if (IS_ERR(led_class))
		return PTR_ERR(led_class);
	if (major < 0) {
		class_destroy(led_class);
		return major;
	}
	
	//通过p_ledopr,可以操作调用单板相关的代码
	p_ledopr = get_board_led_opr();
	
	//创建/dev/myled2的设备节点,多个次设备号控制多个LED
	for(i = 0;i < p_ledopr->num; i++)
	{
		device_create(led_class, NULL, MKDEV(major, i), NULL, "myled%d",i);
	}



	return 0;
}


/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{

	int i;
	for(i = 0; i < p_ledopr->num; i++)
	{
		device_destroy(led_class, MKDEV(major, i));
	}
	class_destroy(led_class);
	unregister_chrdev(major,"xixiwuli_led");
}


//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

2.2.4 アプリケーションプログラム(led_test.c)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>


//led_test /dev/myled0 on
//led_test /dev/myled0 off

int main(int argc, char** argv)
{
	int fd;
	char status = 0;
	
	if(argc != 3)
	{
		printf("usage: %s <dev> <on|off>\n", argv[0]);
		printf("eg: %s /dev/myled on\n", argv[0]);
		printf("eg: %s /dev/myled off\n", argv[0]);
	}

	//open
	fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		printf("can not open %s\n",argv[1]);
		return -1;
	}
	
	//write
	if (strcmp(argv[2], "on") == 0)
	{
		status = 1;
	}

	write(fd, &status, 1);
	close(fd);
	return 0;

}

2.2.5 メイクファイルコード

ここで小さな変更を加え、board_imx6ull.c と led_drive.c を一緒にコンパイルします。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test


#led_drive.c和board_demo.c编译成xixiwuli_led.ko
xixiwuli_led-y := led_drive.o board_imx6ull.o
obj-m	+= xixiwuli_led.o

2.3 テストの実行

最初の 3 つの手順は上記とまったく同じなので、ここでは繰り返しません。 

2.3.1 オンマシンテスト(開発ボード)

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
//复制到开发板上
cp /mnt/xixiwuli_led.ko ./
cp /mnt/led_test ./
//安装驱动模块
insmod xixiwuli_led.ko
 
//查询是否有我们的hello程序
cat /proc/devices
lsmod
 
//查询是否有我们的设备节点
ls /dev/myled0 -l
//打开
./led_test /dev/myled0 on
//关闭
./led_test /dev/myled0 off
//程序运行完,可以卸载相应的模块
rmmod xixiwuli_led.ko

デバイスの結果をクエリします。

ライトのオンとオフ:

2.4 概要

結果から、単純なドライバー フレームワークのドライバー プログラムと比較して、ライトのオン/オフも非常にうまく行うことができます。

しかし構造的には、一般機能とハードウェア動作部分を分離し、それぞれled_drive_stra.cとboard_imx6ull.cに相当し、デバイスを追加する場合はboard_X.cを作成するだけで済みます。

このようなドライバーは拡張性が高く、複数のボードをサポートできます。ここから、Linuxドライバー=ドライバーフレームワーク+ハードウェア動作の理解が深まり、徐々にドライバーフレームワークの魅力を感じていただけると思います

3. LEDドライバー(分離)

3.1 設計アイデアの分離

上記の方法では、board_X.c がチップのハードウェアに強く結合されすぎていることがわかります。ライトを変更したい場合は、コードを変更して再コンパイルする必要があります。これを解決するにはどうすればよいでしょうか?

つまり、柔軟性に欠けるハードウェア運用の問題をどのように解決すればよいのでしょうか?

上記の問題を考慮すると、分離という設計思想を適用する必要があります。あるチップの場合、端子の動作は同様で、汎用ドライバを書いて左右分離し、リソースを定義(board_X.c)し、ハードウェアの汎用動作を定義(chipY_gpio.c)することができます。 )、以下の図に示すように:

 ここでは、特定のリソースがどのようなものであるかを表現するために、led_resource 構造体が抽象化されています。(オブジェクト指向の考え方に従う)

3.2 プログラム戦闘

2 番目のセクションのプログラムと比較すると、board_imx6ull.c ファイルは、board_A_led.c と Chip_imx6ull_gpio.c の 2 つのファイルに分割する必要があります。そこで、ここでは主にled_resource.h、board_A_led.c、chip_imx6ull_gpio.c、Makefileコードについて説明します。具体的な実装フレームワークは次のとおりです。

3.2.1 ヘッダーファイル(led_resource.h)

led_resource.h の構造体 led_resource はリソースを定義します。

#ifndef _LED_RE_H
#define _LED_RE_H

/*GPIO5*/
/*bit[31:16] = group*/
/*bit[15:0]  = pin*/

//可以用以下的宏,表示GPIO引脚
#define GROUP(x) 		(x>>16)
#define PIN(x)			(x&0xFFFF)
#define GROUP_PIN(g,p)	((g<<16) | (p))

struct led_resource
{
	int pin;
};

struct led_resource *get_led_resource(void);

#endif

3.2.2 ドライバー (board_A_led.c)

ここのリソース: GPIO ピン、ここでは初期設定は GPIO5_3 です。 

#include "led_resource.h"

static struct led_resource board_A_led = {
	.pin = GROUP_PIN(5,3),	
};


struct led_resource * get_led_resource(void)
{
	return &board_A_led;
};

3.2.3 ドライバー (chip_imx6ull_gpio.c)

ここでは GPIO の一般的なハードウェア動作が具体的に実装されており、GPIO5_3 を例に取ると、複雑なハードウェア動作は必要ありません。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

#include "led_opr.h"
#include "led_resource.h"


static struct led_resource *led_rsc;


/*阐述说明:
 *现阶段仅以展示分离设计思想
 *以下的为GPIO5_3需要设置的寄存器绝对物理地址,后续可以按照基址表示GPIO组内多引脚。
 *定义好多个基址,可以实现表示多个GPIO组
*/

/*
//CCM_CCGR1:0x20C406C
static volatile unsigned int* CCM_CCGR1;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;
*/


/*
 *函数:    	board_demo_led_init
 *功能:		enable gpio				 使能gpio
 			configure pin as gpio    设置引脚为GPIO
 			configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
 			*which:哪个LED
 *返回参数:如果成功返回0
*/


static int board_demo_led_init(int which)
{
	unsigned int val;
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);

	if(!led_rsc)
	{
		led_rsc = get_led_resource();
	}
	printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin));
	switch(GROUP(led_rsc->pin))
	{
		case 0:
		{
			printk("init pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3 ...\n");
			break;
		}
	}
/*
	if(which == 0)
	{
		if(!CCM_CCGR1)
		{
			//ioremap:物理地址映射到虚拟地址
			CCM_CCGR1 = ioremap(0x20C406C, 4);
			IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
			GPIO5_GDIR = ioremap(0x020AC004, 4);
			GPIO5_DR  = ioremap(0x020AC000, 4);
		}

		
		*CCM_CCGR1 |= (3<<30);
		val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
		val &= ~0xf;
		val |=  0x5;
		*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

		*GPIO5_GDIR |= (1<<3);
	}
*/
	return 0;
}


/*
 *函数:    	board_demo_led_init
 *功能:		set gpio register: out 1/0
 *传入参数:
 			*which:哪个LED
 			*status: 状态1/0,亮灭
 *返回参数:如果成功返回0
*/
static int board_demo_led_ctl(int which, char status)
{
	printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin));
	switch(GROUP(led_rsc->pin))
	{
		case 0:
		{
			printk("set pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("set pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("set pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("set pin of group 3 ...\n");
			break;
		}
	}

/*
	if(which == 0)
	{
		if(status)
		{
			*GPIO5_DR &= ~(1<<3);
		}else
		{
			*GPIO5_DR |= (1<<3);
		}
	}
*/
	return 0;
}


static struct led_operations board_demo_led_opr =
{
	.num  = 1,
	.init = board_demo_led_init,
	.ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
	return &board_demo_led_opr;
}

3.2.4 メイクファイルコード

ここには小さな変更があり、led_drive.c、board_A_led.c、chip_imx6ull_gpio.c を一緒にコンパイルします。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test


#led_drive.c、board_A_led.c、chip_imx6ull_gpio.c编译成xixiwuli.ko
xixiwuli_led-y := led_drive.o board_A_led.o chip_imx6ull_gpio.o
obj-m	+= xixiwuli_led.o

3.3 テストの実行

基本的には上記と同じです。 

//打开
./led_test /dev/myled0 on
//关闭
./led_test /dev/myled0 off
dmesg

印刷デモ: 

3.4 概要

上記の分離設計アイデアを実践すると、制御するピンを変更する必要がある場合 (別の LED が点灯する場合)、board_A_led.c を変更するだけで済むことがわかります。

第 2 セクションと比較して、拡張性と柔軟性がさらに向上しました。

4. LEDドライバー(バスデバイスドライバーモデル)

4.1 バスデバイスドライバーモデル

前のセクションの内容では、LED、LCD などの複数の異なるデバイスを扱う場合、対応する resource.h を定義する必要があり、これは非現実的であることがさらにわかります。 

上記の問題に基づいて、分離のアイデアをさらに実現するバス デバイス ドライバー モデルが提案されます。

将来的には、platform_device/platform_driver の管理にバスが使用される予定です。以下に示すように: 

4.2 プログラム戦闘

3 番目のセクションのプログラムに基づいて、バス デバイス ドライバー フレームワークにさらに進むには、 platform_device 構造体と platform_driver構造体を抽象化し、それらの間のマッチングを実現する必要があります。

具体的な手順は次のとおりです。 

1. platform_device構造体の確保・設定・登録

  • その中で使用されるリソースを定義し、デバイス名を指定します

2. platform_driver構造体の割り当て・設定・登録

  • プローブ関数内で、file_operations構造体を割り当て/設定/登録します。
  • そして、platform_deviceから使用するハードウェアリソースを決定し、device_createを動的に実装します。
  • platform_driver の名前を指定します

第3部の内容と比較すると、内容を示す部分が変わります。 

4.2.1 ヘッダーファイル(led_drive.h)

下から上への登録関数を宣言します。

#ifndef _LEDDRV_H
#define _LEDDRV_H

#include "led_opr.h"

void led_device_create(int minor);
void led_device_destory(int minor);
void register_led_operations(struct led_operations *opr);


#endif /* _LEDDRV_H */

4.2.2 ドライバー (led_drive.c)

これはドライバーの最上位層に属します。具体的な説明と分析については、コードを参照してください。

/* 说明:
	*1,本代码是跟学韦老师课程所编写,增加了注释和分析
	*2,采用的是UTF-8编码格式
	*3,LED驱动程序(总线设备驱动模型) led_drive.c
*/

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>


#include "led_opr.h"

/*第一步:确定主设备号,这里由内核分配*/
static int major = 0; 
static struct class *led_class;
struct led_operations *p_ledopr;


/*
 *阐述说明
 *定义给底层(chip_imx6ull_gpio.c)去调用的
*/
void led_device_create(int minor)
{

	device_create(led_class, NULL, MKDEV(major, minor), NULL, "myled%d", minor);
}

void led_device_destory(int minor)
{
	
	device_destroy(led_class, MKDEV(major, minor));
}


//定义底层向上层注册函数,避免交叉编译
void register_led_operations(struct led_operations *opr)
{
	p_ledopr = opr;
}
EXPORT_SYMBOL(led_device_create);
EXPORT_SYMBOL(led_device_destory);
EXPORT_SYMBOL(register_led_operations);


/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:		led_drv_open
 *差异点:      根据次设备号初始化LED
 *传入参数:
 			*flip:要打开的文件
 *返回参数:如果成功返回0
*/			

static int led_drv_open(struct inode *inode, struct file *filp)
{
	int minor = iminor(inode);/*次设备号*/

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	p_ledopr->init(minor);
	return 0;
}

/*
 *函数:       led_drv_write
 *差异点:		根据次设备号和status控制LED
 *功能:		copy_from_user,从app中获取数据
 *传入参数:
 			*flip:要写的文件
 			*buf: 写的数据来自于buf
 			*size:写多大的数据
 			*offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char status;
	int err;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);
	
	/*根据次设备号控制LED*/
	struct inode* inode = file_inode(filp);
	int minor = iminor(inode) & 0x0f;
	p_ledopr->ctl(minor, status);
	return 1;
	
}

			 
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_drv_write,
	.open		= led_drv_open,

};


/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *功能:		①注册主设备号	
 			②辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
	/*
	 *printk:判断一下是否调用了入口函数
	 *__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/	
	printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
	
		
	//0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
	major = register_chrdev(0,"100ask_led", &led_fops);	
	led_class = class_create(THIS_MODULE, "led_2345_class");
	int i;
	if (IS_ERR(led_class))
		return PTR_ERR(led_class);
	if (major < 0) {
		class_destroy(led_class);
		return major;
	}
	
	return 0;
}


/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{

	class_destroy(led_class);
	unregister_chrdev(major,"100ask_led");
}

//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

4.2.3 ドライバー (board_A_led.c)

 このプログラムは主にいくつかのリソースを定義し、platform_device構造体を記述して実装します。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>


#include "led_resource.h"

static void led_dev_release(struct device *dev)
{
}

//定义一个资源数组
static struct resource resources[] = {
        {
                .start = GROUP_PIN(5,3),
                .flags = IORESOURCE_IRQ,
        },
        {
                .start = GROUP_PIN(3,1),
                .flags = IORESOURCE_IRQ,
        },

};

/*
 *name:平台名称
 *num_resources:资源个数
 *resource:引入资源数组
*/
static struct platform_device board_A_led_dev =
{
	.name = "100ask_led",
	.num_resources = ARRAY_SIZE(resources),
	.resource = resources,
	.dev = {
                .release = led_dev_release,
         },
		
	
};
/*入口函数,patform_device
 *功能:注册设备
*/
static int led_dev_init(void)
{
	int err;
	err = platform_device_register(&board_A_led_dev);
	return 0;
}

/*出口函数,platform_device*/
static void led_dev_exit(void)
{
	platform_device_unregister(&board_A_led_dev);
}


module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

4.2.4 ドライバー (chip_imx6ull_gpio.c)

ここで、本体はplatform_driver構造体を実装します。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>


#include "led_drive.h"
#include "led_opr.h"
#include "led_resource.h"


static int g_ledpins[100];
static int g_ledcnt = 0;


/*阐述说明:
 *现阶段仅以示意总线设备驱动模型框架
*/

/*
 *函数:    	board_imx6ull_led_init
 *功能:		获取gpio引脚信息
 *输入参数:which---哪个引脚
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_init(int which)
{
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
	printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


/*
 *函数:    	board_imx6ull_led_init
 *功能:		打印某个gpio引脚设置信息
 *输入参数:which---哪一个引脚
 			status:LED状态,1/0亮灭
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_ctl(int which, char status)
{
	printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("set pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("set pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("set pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("set pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


static struct led_operations board_imx6ull_led_opr =
{
	.init = board_imx6ull_led_init,
	.ctl  = board_imx6ull_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_imx6ull_led_opr;
}


/*
 *函数:chip_imx6ull_gpio_led_probe
 *功能:记录引脚
 		创建设备,device_create
*/
static int chip_imx6ull_gpio_led_probe(struct platform_device *dev)
{
	int i = 0;
	struct resource *res;
	while(1)
	{
		//设备、哪一类资源、第几个资源
		res = platform_get_resource(dev,IORESOURCE_IRQ, i++);
		if(!res)
			break;
		g_ledpins[g_ledcnt] = res->start;

		/*创建设备*/
		led_device_create(g_ledcnt);
		g_ledcnt++;
		
	}
	return 0;
}

/*
 *函数:chip_imx6ull_gpio_led_remove
 *功能:注销设备,device_destory
*/
static int chip_imx6ull_gpio_led_remove(struct platform_device *dev)
{
	int i;
	for(i = 0; i < g_ledcnt; i++)
	{
		led_device_destory(i);
	}
	g_ledcnt = 0;
	return 0;
}


static struct platform_driver chip_imx6ull_gpio_driver = {
    .probe      = chip_imx6ull_gpio_led_probe,
    .remove     = chip_imx6ull_gpio_led_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};


/*入口函数,platform_driver
 *功能:注册设备
 		底层到上层的注册
*/
static int chip_imx6ull_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_imx6ull_gpio_driver);
	register_led_operations(&board_imx6ull_led_opr);
	return 0;
}

/*出口函数,paltform_driver*/
static void chip_imx6ull_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_imx6ull_gpio_driver);
}


module_init(chip_imx6ull_gpio_drv_init);
module_exit(chip_imx6ull_gpio_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

 4.2.5 メイクファイルコード

上記の手順の変更に伴い、Makefile にも特定の変更が加えられています。 

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test
	
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m	+= led_drive.o chip_imx6ull_gpio.o board_A_led.o

 4.3 試運転

//复制到开发板上
cp /mnt/board_A_led.ko ./
cp /mnt/chip_imx6ull_gpio.ko ./
cp /mnt/led_drive.ko ./
cp /mnt/led_test ./

//安装驱动模块
insmod board_A_led.ko
insmod led_drive.ko
insmod chip_imx6ull_gpio.ko
//GPIO5_3
./led_test  /dev/myled0 on
./led_test  /dev/myled0 off

//GPIO3_1
./led_test  /dev/myled1 on
./led_test  /dev/myled1 off
dmesg

印刷結果: 

4.4 概要 

3 番目のセクションのコード変更と比較すると、バス デバイス ドライバー モデル フレームワークの利点が感じられ、独自のベースでデバイスとドライバーの分離がさらに実現されています。

これにより、さまざまなデバイスを扱う際の管理が向上します。

5. LEDドライバー(デバイスツリー)

5.1 デバイスツリー

5.1.1 デバイスツリーの背景

4 番目のセクションで、LED で使用される GPIO ピンを変更する場合は、board_A_led.c コードを変更し、ドライバーを再コンパイルしてロードする必要があることがわかりました。

ARM チップの人気に伴い、さまざまなメーカーによる開発用に、類似した非技術的なファイルが多数カーネルに保存されています。

では、上記の問題に対して、良い解決策はあるのでしょうか?

上記の問題の核心は、リソースを構成するための .c ファイルの使用にあります。ここでは特別な構成ファイルが導入され、これを実現するためにデバイス ツリーが使用されます。

デバイス ツリーを採用すると、カーネルで多くの冗長コーディングを行わずに、多くのハードウェアの詳細を Linux に直接渡すことができ、ブートローダーを通じてハードウェア リソースがカーネルに転送され、カーネルとファイル リソースの記述が比較的独立したものになります。

 最終的な効果は、デバイスがスクリプト内にあり、ドライバーが c 内にあることです。

 

5.1.2 デバイスツリーの簡単な説明

デバイス ツリーには、DTC (デバイス ツリー コンパイラ)、DTS (デバイス ツリー ソース)、および DTB (デバイス ツリー BLOB) が含まれます。

dtc、dts/dtsi、および dtb の関係:
dts および dtsi ソース ファイルは、dtc コンパイラによって dtb バイナリ ファイルにコンパイルされ、dtb ファイルはカーネルによって解析されるためにシステムに置かれます。

特定の構文とデバイス ツリーの分析については、以下を参照してください。

https://blog.csdn.net/qq_33487044/article/details/126325656

 デバイスツリーを使用してドライバーを作成するにはどうすればよいですか?

ノート: 

  • 1. デバイス ツリー ノードは platform_driver と一致することができます
  • 2. デバイス ツリー ノードがリソースを指定し、platform_driver がリソースを取得します

5.2 プログラム戦闘

デバイス ツリーの内容は複雑なので、ここでは詳しく説明しません。ここのプログラムは単純な関数を実装し、プロセス全体を実行します。

以下の部分は主にプログラムの変更部分、デバイスツリーファイル、ドライバ(chip_imx6ull_gpio.c)を示しており、board_A_led.cファイルは不要です。

5.2.1 デバイスツリー dts ファイル (100ask_led.dts)

デバイス ツリーを変更し、デバイス ノード 100ask_led@0 および 100ask_led@1 を追加します。

#define GROUP_PIN(g,p) ((g<<16) | (p))

/ {
	100ask_led@0 {
		compatible = "100as,leddrv";
		pin = <GROUP_PIN(3, 1)>;
	};

	100ask_led@1 {
		compatible = "100as,leddrv";
		pin = <GROUP_PIN(5, 8)>;
	};

};

imx6ull pro 開発ボードのカーネル ソース ディレクトリで、arch/arm/boot/dts/100ask_imx6ull-14x14.dts が変更およびコンパイルされ、arch/arm/boot/dts/100ask_imx6ull-14x14.dtb が取得されます。 

詳しい手順:(PC側) 

 5.2.2 ドライバー (chip_imx6ull_gpio.c)

ここでは主に、デバイス ツリー ノードが platform_driver と一致した後に使用される of_match_table メンバーが設定されます。プローブ機能でのリソースの取得方法が変わり、デバイスツリーファイルを読み込んでリソースを取得します。

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>


#include "led_drive.h"
#include "led_opr.h"
#include "led_resource.h"


static int g_ledpins[100];
static int g_ledcnt = 0;


/*阐述说明:
 *现阶段仅以示意总线设备驱动模型框架
*/

/*
 *函数:    	board_imx6ull_led_init
 *功能:		获取gpio引脚信息
 *输入参数:which---哪个引脚
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_init(int which)
{
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
	printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


/*
 *函数:    	board_imx6ull_led_init
 *功能:		打印某个gpio引脚设置信息
 *输入参数:which---哪一个引脚
 			status:LED状态,1/0亮灭
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_ctl(int which, char status)
{
	printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("set pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("set pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("set pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("set pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


static struct led_operations board_imx6ull_led_opr =
{
	.init = board_imx6ull_led_init,
	.ctl  = board_imx6ull_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_imx6ull_led_opr;
}


/*
 *函数:chip_imx6ull_gpio_led_probe
 *功能:记录引脚
 		创建设备,device_create
*/
static int chip_imx6ull_gpio_led_probe(struct platform_device *dev)
{
	int i = 0;
	struct resource *res;
	struct device_node *p;
	int led_pin;

	p = dev->dev.of_node;
	if(!p)
		return -1;

	//读取pin属性的值保存在led_pin变量里
	int err = of_property_read_u32(p, "pin", &led_pin);	
	g_ledpins[g_ledcnt] = led_pin;
	
	/*创建设备*/
	led_device_create(g_ledcnt);
	g_ledcnt++;
		
	return 0;
}

/*
 *函数:chip_imx6ull_gpio_led_remove
 *功能:注销设备,device_destory
*/
static int chip_imx6ull_gpio_led_remove(struct platform_device *dev)
{
	int i;
	for(i = 0; i < g_ledcnt; i++)
	{
		led_device_destory(i);
	}
	g_ledcnt = 0;
	return 0;
}

//of_match_table成员,用于设备树节点和platform_driver的匹配上
static const struct of_device_id ask100_leds[] = {
    { .compatible = "100as,leddrv" },
    { },
};

static struct platform_driver chip_imx6ull_gpio_driver = {
    .probe      = chip_imx6ull_gpio_led_probe,
    .remove     = chip_imx6ull_gpio_led_remove,
    .driver     = {
        .name   = "100ask_led",
		.of_match_table = ask100_leds,
    },
};


/*入口函数,platform_driver
 *功能:注册设备
 		底层到上层的注册
*/
static int chip_imx6ull_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_imx6ull_gpio_driver);
	register_led_operations(&board_imx6ull_led_opr);
	return 0;
}

/*出口函数,paltform_driver*/
static void chip_imx6ull_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_imx6ull_gpio_driver);
}


module_init(chip_imx6ull_gpio_drv_init);
module_exit(chip_imx6ull_gpio_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

 5.2.3 メイクファイルコード

前のセクションの内容と比較すると、ここではboard_A_led.c は必要ありません。 

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test
	
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m	+= led_drive.o chip_imx6ull_gpio.o

5.3 概要

テストプログラム:(デバイスツリーの読み込み状況)

//加载设备树文件
cp /mnt/100ask_imx6ull-14x14.dtb /boot/
reboot
cd /sys/firmware/devicetree/base/
ls -ld *100ask*
cd 100ask_led@0
ls
cat compatible
hexdump pin

後続の led_test テスト プログラムの実行結果は、前の章のものとまったく同じです。ここでは、リソースの管理方法が変更されています。

 皆様も上記のLED駆動進化の道を通って、胸が熱くなることと思います。

おすすめ

転載: blog.csdn.net/weixin_42373086/article/details/130659276