驱动_总线模型

本文详细介绍了Linux内核中关于总线、设备和驱动的交互机制,包括总线对象、设备对象和驱动对象的结构、注册与注销过程,以及平台总线和IIC驱动模型。通过示例代码展示了如何在内核中注册总线、设备和驱动,以及设备树的修改和IIC驱动的编写框架。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.理解图

 2. 相关API

2.1 总线对象

2.2 注册总线

2.3 注销总线

2.4 设备对象信息

2.5 注册和注销设备

2.6 驱动对象信息

 2.7 驱动注册到总线或驱动注销

2.8 程序示例

2.8.1 总线部分

2.8.2 设备部分

2.8.3 驱动部分

3. 平台总线

3.1 平台总线对象

3.2 注册平台总线

3.3 平台总线设备对象

3.3.1 设备信息

3.3.2 注册设备到平台总线

3.3.3 从平台总线注销设备

3.4 平台总线驱动对象

3.4.1 驱动对象信息

3.4.2 注册驱动到平台总线

3.4.3 从平台总线注销驱动

3.5 在多个平台上使用的LED驱动

3.5.1 思路

3.5.2 平台设备对象部分程序示例(plat_led_pdev.c)

3.5.3 平台驱动对象部分程序示例(plat_led_pdrv.c)

3.5.4 平台驱动对象部分程序示例(led_test.c)

3.5.5 Makefile

4.IIC驱动模型

4.1 理解图

4.2  设备树修改

4.3 编写框架

4.4 程序示例


1.理解图

 2. 相关API

2.1 总线对象

描述总线信息,管理设备和驱动匹配

struct bus_type
{
const char *name;//总线名字
int (*match)(struct device *dev, struct device_driver *drv);//总线进行匹配的
设备device:
驱动driver:
函数
//匹配时机:当总线上添加了新设备或新驱动,内核会调用一次或多次这个函数;匹配成功:
通过匹配函数的返回值来表示是否成功,返回值为0表示不成功,1表示成功
};

2.2 注册总线

int bus_register(struct bus_type *bus)
参数:
struct bus_type *bus:结构体的地址,结构体类型就是总线对象(描述总线)
返回值:
注册总线:成功返回0,失败返回错误码

2.3 注销总线

void bus_unregister(struct bus_type * bus)

2.4 设备对象信息

设备对象,描述一个设备,有设备信息内容,包括地址,中断号,甚至是其他自定义信息

struct device
{
struct kobject kobj;//所有对象的父对象
const char * init_name;//设备名字
struct bus_type *bus;//在设备对象中描述要注册到哪条总线(总线对象的地址)
void * platform_data;//自定义数据的地址,指向任意类型(表示设备信息内容)
void (*release)(struct device *dev);//在注销卸载设备时(device_unregister函数),就
会调用这个release函数
};

2.5 注册和注销设备

//注册设备到总线中
int device_register(struct device *dev)
参数:
struct device *dev:结构体的地址,结构体类型是设备对象(描述设备,包含设备信息)
返回值:
注册设备:成功返回0,失败返回错误码
//从总线注销设备
void device_unregister(struct device *dev)

2.6 驱动对象信息

描述驱动设备的方法

struct device_driver
{
const char * name;//驱动名字
struct bus_type * bus;//在驱动对象中描述要注册到哪条总线(总线对象的地址)3. 平台总线
int (*probe) (struct device *dev);//如果device和driver匹配成功,driver要做的事情
(驱动的方法:申请设备号、设备节点等)
int (*remove) (struct device *dev);//如果device或driver从总线移除,driver要做
的事情(驱动的方法:注销设备号、设备节点等)
};

 2.7 驱动注册到总线或驱动注销

//注册驱动到总线中
int driver_register(struct device_driver *drv)
参数:
struct device_driver *drv:结构体的地址,结构体类型是驱动对象(描述驱动)
返回值:
注册驱动:成功返回0,失败返回错误码
//从总线注销驱动
void driver_unregister(struct device *dev)

2.8 程序示例

2.8.1 总线部分


#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

int mybus_match(struct device *dev, struct device_driver *drv)
{
	printk("-----------%s----------\n",__FUNCTION__);

	//匹配
	//dev:匹配的设备对象
	//drv:匹配的驱动对象
	if( strcmp(drv->name,dev->kobj.name) == 0 )//自定义的匹配方式
	{
		printk("match ok\n");
		return 1;
	}
	else 
		return 0;

}

//总线对象
struct bus_type mybus = {

	.name = "mybus",
	.match = mybus_match,
	
};

EXPORT_SYMBOL(mybus);

static int __init mybus_init(void)
{

	printk("-----------%s----------\n",__FUNCTION__);

	//构建创建总线
	return bus_register(&mybus);

}

static void __exit mybus_exit(void)
{
	//注销总线
	bus_unregister(&mybus);
}

module_init(mybus_init);
module_exit(mybus_exit);

MODULE_LICENSE("GPL");

2.8.2 设备部分

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

extern struct bus_type mybus;


struct devinfo//设备信息
{
	int irqno;//中断号
	char * name;//设备名字
	unsigned long addr;//表示寄存器地址
	unsigned int bit;
};

struct devinfo info = {
	.name = "led3",
	.addr = 0x11000c20,
	.bit = 0,
};

void mydev_release(struct device *dev)
{
	printk("------------%s------------\n",__FUNCTION__);
}


//要往总线上注册的设备对象(描述设备)
struct device mydev = {

	.init_name = "myled",
	.bus = &mybus,
	.platform_data = &info,
	.release = mydev_release,
};


static int __init mydev_init(void)
{
	//在总线上注册设备
	return device_register(&mydev);

}

static void __exit mydev_exit(void)
{
	//注销设备
	device_unregister(&mydev);
}


module_init(mydev_init);
module_exit(mydev_exit);

MODULE_LICENSE("GPL");

2.8.3 驱动部分


#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>


extern struct bus_type mybus;


struct devinfo//设备信息
{
	int irqno;//中断号
	char * name;//设备名字
	unsigned long addr;//表示寄存器地址
	unsigned int bit;
};
	
struct myled
{
	unsigned int major;
	struct class * cls;
	struct device * dev;
	unsigned int * addr;
};

struct myled led;

const struct file_operations fops;

	
//创建驱动的方法
int mydrv_probe (struct device *dev)
{

	//dev:匹配成功的设备对象
	
	printk("---------%s--------\n",__FUNCTION__);
	//驱动申请
	//申请设备号
	led.major = register_chrdev(0,"led",&fops);
	//创建设备节点
	led.cls = class_create(THIS_MODULE,"led cls");
	device_create(led.cls,NULL,MKDEV(led.major,0),NULL,"led3");

	//硬件初始化
	struct devinfo * info = dev->platform_data;//设备信息的地址
	
	led.addr = ioremap(info->addr,8);
	//writel(led.addr,xxx);
	//writel(led.addr+1,xxxx)
	return 0;
	
}

//卸载驱动的方法
int mydrv_remove (struct device *dev)
{	
	printk("---------%s--------\n",__FUNCTION__);
	//驱动卸载
	//iounmap()
	//device_destory();
	//class_destory()l

	return 0;
}


//驱动对象
struct device_driver mydrv = {

	.name = "myled",
	.bus = &mybus,
	.probe = mydrv_probe,
	.remove = mydrv_remove,
	
};

static int __init mydrv_init(void)
{
	//注册驱动到总线
	return driver_register(&mydrv);
}

static void __exit mydrv_exit(void)
{
	
	driver_unregister(&mydrv);
	
}

module_init(mydrv_init);
module_exit(mydrv_exit);

MODULE_LICENSE("GPL");

3. 平台总线

在内核代码当中,已经创建了平台总线,不需要自己创建,而是在内核调用,开机的时候自动创建。

3.1 平台总线对象

struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};

3.2 注册平台总线

bus_register(&platform_bus_type);

3.3 平台总线设备对象

3.3.1 设备信息

struct platform_device {
const char *name;//设备名字,用于匹配
int id;//一般直接写-1
struct device dev;//继承了device父类,包含设备对象的所有信息
u32 num_resources;//资源的个数
struct resource *resource;//设备信息资源的首地址
};

3.3.2 注册设备到平台总线

int platform_device_register(struct platform_device * pdev)

3.3.3 从平台总线注销设备

void platform_device_unregister(struct platform_device * pdev)

3.4 平台总线驱动对象

3.4.1 驱动对象信息

struct platform_driver {
int (*probe)(struct platform_device *);//匹配成功调用
int (*remove)(struct platform_device *);//移除时调用
struct device_driver driver;//平台驱动继承的父类(通用的驱动对象信息)
const char *name;//驱动名字
const struct platform_device_id *id_table;//如果driver支持多个平台,在列表中列
举支持平台,是一个数组,数组中每一个元素就是一个支持的平台信息,id_table就是存储首地址
};

3.4.2 注册驱动到平台总线

int platform_driver_register(struct platform_driver * pdrv)

3.4.3 从平台总线注销驱动

void platform_driver_unregister(struct platform_driver * drv)

3.5 在多个平台上使用的LED驱动

3.5.1 思路

首先注册platform_driver,实现操作设备方法 const struct platform_device_id *id_table:平
台设备支持
注册完毕,匹配成功调用probe函数:
probe方法: 对硬件进行操作
申请设备号
设备节点
初始化硬件
实现文件IO接口
然后 注册platform_device,设备信息资源
struct resource * resource;//设备信息资源的首地址

3.5.2 平台设备对象部分程序示例(plat_led_pdev.c)


#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define GPX1CON 0x11000c20
#define GPX1CON_SIZE 4

#define GPX1DAT 0x11000c24
#define GPX1DAT_SIZE 4


//数组的每一个成员是一个结构体(资源信息)
struct resource	led_resources[] = {
		[0] = {
			.start = GPX1CON,
			.end = GPX1CON + GPX1CON_SIZE - 1,
			.flags = IORESOURCE_REG,
		},
		[1] = {			
			.start = GPX1DAT,
			.end = GPX1DAT + GPX1DAT_SIZE - 1,
			.flags = IORESOURCE_REG,
		},
		//假设led设备有中断,用资源来说明中断,注意在本驱动设备没有任何意义
		[2] = {
			.start = 67,
			.end = 67,
			.flags = IORESOURCE_IRQ,
		},

};//当前平台的led设备的资源

//平台设备对象
struct platform_device ledpdev = {

	.name = "exynos4412_led",
	.id = -1,
	.num_resources = ARRAY_SIZE(led_resources),
	.resource = led_resources,

};

static int __init plat_led_init(void)
{
	return platform_device_register(&ledpdev);
}

static void __exit plat_led_exit(void)
{
	platform_device_unregister(&ledpdev);
}

module_init(plat_led_init);
module_exit(plat_led_exit);

MODULE_LICENSE("GPL");

3.5.3 平台驱动对象部分程序示例(plat_led_pdrv.c)


#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>

struct led_drv
{
	unsigned int major;
	struct class * cls;
	struct device * dev;
	struct resource * con;
	struct resource * dat;
	unsigned int * led_con;
	unsigned int * led_dat;
};

struct led_drv led;

ssize_t led_write (struct file *filp, const char __user * buf, size_t size, loff_t *fops)
{
	printk("--------------%s------------\n",__FUNCTION__);
	int num;
	copy_from_user(&num,buf,size);
	
	if(num == 1)
	{
		writel(readl(led.led_dat) | 1 , led.led_dat);
	}
	else
	{
		writel(readl(led.led_dat) & ~1 , led.led_dat);
	}
	return 0;
}
int led_open (struct inode * inode, struct file *filp)
{
	printk("--------------%s------------\n",__FUNCTION__);
	return 0;
}
int led_release (struct inode *inode, struct file *filp)
{
	printk("--------------%s------------\n",__FUNCTION__);
	return 0;
}

const struct file_operations fops = {
	.release = led_release,
	.open = led_open,
	.write = led_write,

};


int plat_led_probe(struct platform_device * pdev)
{
	printk("--------------%s------------\n",__FUNCTION__);
	//得到设备信息,申请驱动------创建设备驱动
	/*
		1、注册设备号
		2、创建设备节点
		3、初始化硬件
			ioremap();---readl()----writel()
			register_irq();
		4、实现各种文件io接口
	*/
	led.major = register_chrdev(0,"led",&fops);
	//if(){}

	led.cls = class_create(THIS_MODULE,"led cls");
	//if()
	led.dev = device_create(led.cls,NULL,MKDEV(led.major,0),NULL,"led");
	//if()

	//硬件初始化,需要设备信息资源
	//pdev就是对应平台的设备

	//获取平台对象中指定的资源,返回值就是需要的资源的地址
	//参数1:从哪个pdev获取资源
	//参数2:获取资源的类型
	//参数3:同种资源的第几个
	led.con = platform_get_resource(pdev,IORESOURCE_REG,0);
	led.dat = platform_get_resource(pdev,IORESOURCE_REG,1);

	//获取中断资源
	//platform_get_resource(pdev,IORESOURCE_IRQ,0);

	//resource_size(const struct resource * res)
	led.led_con = ioremap(led.con->start,led.con->end - led.con->start + 1);
	led.led_dat = ioremap(led.dat->start,resource_size(led.dat));

	writel(readl(led.led_con) & ~0xf | 0x1 , led.led_con);

	writel(readl(led.led_dat) | 1 , led.led_dat);
	
	return 0;
}

int plat_led_remove(struct platform_device * pdev)
{
	printk("--------------%s------------\n",__FUNCTION__);
		
	return 0;
}

//支持 struct platform_device_id 平台信息
const struct platform_device_id led_id_table[] = {

	{"exynos4412_led",0x1111},//平台信息-----平台名字
	{"s5pv210_led",0x2222},
	{"s3c2410_led",0x3333},
	{"",0x0},
};

//驱动对象---平台驱动
struct platform_driver led_pdrv = {

	.driver = {
		.name = "led_pdrv",//驱动名字
	},

	.id_table = led_id_table,//支持的平台信息数组的首地址
	.probe = plat_led_probe,
	.remove = plat_led_remove,
};

static int __init plat_led_init(void)
{
	printk("--------------%s------------\n",__FUNCTION__);
	return platform_driver_register(&led_pdrv);//向平台总线注册一个驱动
}

static void __exit plat_led_exit(void)
{
	printk("--------------%s------------\n",__FUNCTION__);
	platform_driver_unregister(&led_pdrv);//从平台总线注销一个驱动
}

module_init(plat_led_init);
module_exit(plat_led_exit);
MODULE_LICENSE("GPL");

3.5.4 平台驱动对象部分程序示例(led_test.c)


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

int main()
{

	int fd = open("/dev/led0",O_RDWR);

	int value = 0;
	while(1)
	{
		scanf("%d",&value);
		if(value > 1)
			break;
		write(fd,&value,4);
	}
	close(fd);

	return 0;
}


3.5.5 Makefile

KERNEL_PATH = /home/yky/Code/linux-3.14-fs4412//内核路径

CORSS_COMPILE = /home/yky/Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-//交叉编译工具链
CC = $(CORSS_COMPILE)gcc

obj-m += plat_led_pdev.o//设备
obj-m += plat_led_pdrv.o//驱动
APP_NAME = led_test//应用程序

all:
	make modules -C $(KERNEL_PATH) M=$(shell pwd)
	$(CC) $(APP_NAME).c -o $(APP_NAME)


install:
	cp *.ko $(APP_NAME) /source/rootfs  //
	cp $(APP_NAME) /source/rootfs

clean:
	make clean -C $(KERNEL_PATH) M=$(shell pwd)
	rm $(APP_NAME)

4.IIC驱动模型

4.1 理解图

 

4.2  设备树修改

一般买来的硬件有IIC的控制,在设备树中:例如exynos4.dtsi
存在对iic控制器的描述:

i2c_0: i2c@13860000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x13860000 0x100>;
interrupts = <0 58 0>;
clocks = <&clock 317>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_bus>;
status = "disabled";
};
i2c_5: i2c@138B0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138B0000 0x100>;
interrupts = <0 63 0>;
clocks = <&clock 322>;
clock-names = "i2c";
status = "disabled";
};

所以我们对iic从设备的信息描述进行添加
新增一个i2c5的从设备 exynos4412-fs4412.dts 添加包括i2c5控制器及从设备信息

i2c@138B0000 {
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay";
mpu6050@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
};
};

4.3 编写框架

构造i2c_driver,注册到i2c总线

//i2c驱动对象
struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver;//继承的父类(i2c驱动对象中包含的父类信息)
|
const char * name;//驱动名字
const struct of_device_id *of_match_table;//设备树的设备匹配
const struct i2c_device_id *id_table;//用于与设备对象进行匹配,非设备树
};
//注册iic驱动到iic总线
int i2c_add_driver(struct i2c_driver *driver);
//从i2c总线注销i2c驱动
void i2c_del_driver(struct i2c_driver * driver)

接下来就是:

实现probe函数
申请设备号
创建设备节点
iic控制器与从设备通信
实现文件io接口


4.4 程序示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/device.h>

unsigned int major;

struct class * i2c_cls;
struct device * i2c_dev;

const struct file_operations fops;

int i2c_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
	printk("------------%s-----------\n",__FUNCTION__);

	major = register_chrdev(0,"i2c driver",&fops);

	i2c_cls = class_create(THIS_MODULE,"cls");
	i2c_dev = device_create(i2c_cls,NULL,MKDEV(major,0),NULL,"mpu605");

	//i2c_transfer(struct i2c_adapter * adap,struct i2c_msg * msgs,int num)
	
	return 0;
}

int i2c_remove(struct i2c_client * client)
{

	return 0;
}


const struct of_device_id i2c_table[] = {
	{
		.compatible = "invensense,mpu6050",
	},
	{},//空元素

};

const struct i2c_device_id id_table[] = {
	
	{"mpu6050drv",0x1111},
	{},//空元素

};


//驱动对象
struct i2c_driver i2c5_driver = {
	.probe = i2c_probe,
	.remove = i2c_remove,
	.driver = {
		.name = "mpu6050drv",
		.of_match_table = i2c_table,	
	},
	.id_table = id_table,
};


static int __init iic_init(void)
{
	//把i2c驱动注册到i2c总线
	return i2c_add_driver(&i2c5_driver);
}

static void __exit iic_exit(void)
{
	//从i2c总线注销i2c驱动
	i2c_del_driver(&i2c5_driver);
}


module_init(iic_init);
module_exit(iic_exit);

MODULE_LICENSE("GPL");


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Y_寒酥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值