Linux platform 总线设备驱动模型

注:本文的参考文档《嵌入式Linux应用开发完全手册第2版_韦东山全系列视频文档全集.pdf》、《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.3.pdf》、宋宝华的《Linux设备驱动开发详解》,只用于学习记录。

    在Linux 2.6以后的设备驱动模型中,需关心总线设备驱动这3个实体,总线将设备和驱动绑定在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

1. 总线、设备、驱动

在 linux 内核中,总线、设备、驱动分别用bus_typedevicedevice_driver 三个结构体表示。

1.1 总线

Linux系统内核使用 bus_type 结构体表示总线,此结构体定义在文件include/linux/device.hbus_type 结构体内容如下:

struct bus_type {
    
    
	const char		*name;  /*总线名字*/
	const char		*dev_name;
	struct device		*dev_root;
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;
	
    /*match函数,匹配函数,每条总线里面都会有自己的match,但不同总线的match的匹配方法可能会不同*/
	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	
	/*当设备与驱动匹配成功的时候,便会调用probe函数, 不过,一般总线中不会实现probe函数,在匹配成功的时候,会直接调用设备驱动中的probe函数*/
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	int (*num_vf)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;

	bool force_dma;
};

1.2 设备

Linux系统内核使用 device 结构体表示设备,此结构体定义在文件include/linux/device.hdevice 结构体的部分代码如下:

struct device {
    
    
	struct device  *parent;   //父设备
	const struct device_type *type;  /*设备类型*/
	struct bus_type *bus;     //所属总线
	struct device_driver *driver;  //设备驱动
    dev_t   devt;     //设备号
    void  *platform_data;    //私有数据
    ....
 };

1.3 驱动

Linux系统内核使用 device_driver 结构体表示设备驱动,此结构体定义在文件include/linux/device.hdevice_driver 结构体的代码如下:

struct device_driver {
    
    
	const char		*name;     /*驱动名字*/
	struct bus_type		*bus;  /*所属总线*/

	struct module		*owner; /*拥有者*/
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
	enum probe_type probe_type;

	const struct of_device_id	*of_match_table; /*采用设备树的时候驱动使用的匹配表*/
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev); /*probe 函数,在设备与驱动匹配成功后被调用*/
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};

1.4 设备总线驱动模型里面的操作函数

/*1.总线注册*/
int bus_register(struct bus_type *bus)
void bus_unregister(struct bus_type *bus)

/*2.设备注册*/
int device_register(struct device *dev)
void device_unregister(struct device *dev)

/*3.设备驱动注册*/
int driver_register(struct device_driver *drv)
void driver_unregister(struct device_driver *drv)

注:(1) 设备总线驱动模型:以对象的思想来实现的;
    (2) 每一个设备对应唯一一个驱动,但每一个驱动可以服务多个设备;
    (3) Linux系统中有很多总线,分为物理总线(如:i2c总线,usb总线,SDIO总线,SPI总线…)和虚拟总线(只有一条:平台总线)。

2. platform 总线设备驱动

     platform 总线设备驱动机制采用了分离、对象的思想,实现了设备资源设备驱动的分离,以简化系统设备驱动的开发。

2.1 platform 总线

platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.cplatform 总线定义如下:

struct bus_type platform_bus_type = {
    
    
	.name		= "platform",   /*总线名字*/
	.dev_groups	= platform_dev_groups,
	.match		= platform_match, /*匹配函数,有两个参数 dev 、drv, 该函数的功能是完成dev 与 drv 的匹配*/
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
	.force_dma	= true,
};
2.1.1 platform_match 函数

platform_match 函数的主要功能是完成设备与驱动的匹配,那么驱动和设备是如何匹配的呢?platform_match 函数定义在文件 drivers/base/platform.c 中,它的代码如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
    
    
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

platform_match函数的代码可知,设备与驱动的匹配方式有5种:
(1) 第一种匹配方式,使用设备 device 的成员driver_override 与 驱动device_driver 的名字 drv->name 匹配;
(2) 第二种匹配方式,OF 类型的匹配,也就是设备树采用的匹配方式,device_driver 结构体(表示设备驱动)中有个名为 of_match_table的成员变量,此成员变量保存着驱动的 compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行;
(3) 第三种匹配方式,ACPI 匹配方式;
(4) 第四种匹配方式,id_table 匹配,每个 platform_driver 结构体有一个 id_table 成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
(5) 第五种匹配方式,直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

2.2 platform 驱动

platform_driver 结构体表示 platform 驱动,此结构体定义在文 件include/linux/platform_device.h 中,内容如下:

struct platform_driver {
    
    
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};

(1) probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,该函数一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现;
(2) driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架;

2.3 platform 设备

platform_device 结构体表示 platform 设备, platform_device 结构体定义在文件 include/linux/platform_device.h 中,结构体内容如下:

struct platform_device {
    
    
	const char	*name;   /*平台设备名字,要与device_driver驱动名字相同,否则无法匹配到对应的驱动*/
	int		id;
	bool		id_auto;
	struct device	dev;    /*设备结构体*/
	u32		num_resources;  /*设备资源数量*/
	struct resource	*resource; /*设备资源*/

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};
2.3.1 resource 资源结构体

Linux 内核使用 resource 结构体表示资源, resource 结构体内容如下:

struct resource {
    
    
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};

startend 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址, name 表示资源名字, flags 表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h 里面,如下所示:

#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
....
4 /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
5 #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

2.4 platform 总线设备驱动模型的关键接口函数

/*1.注册平台设备*/
int platform_device_register(struct platform_device *pdev)
void platform_device_unregister(struct platform_device *pdev)

/*2.注册平台驱动*/
int platform_driver_register(struct platform_driver *drv)
void platform_driver_unregister(struct platform_driver *drv)

/*3.获取平台资源*/
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

2.5 platform 总线设备驱动模型

在这里插入图片描述
(1) 向platform总线注册一个 dev (device) 时,会遍历 platform_busdrv (device_driver) 链表逐一与 dev 比较匹配(使用platform_match(dev,drv)比较匹配),如果匹配成功,则调用 drv->probe 函数 (drv->probe = platform_drv_probe);

(2) 向platform总线注册一个 drv ( device_driver) 时,会遍历 platform_busdev (device) 链表逐一与 drv 比较匹配(使用platform_match(dev,drv)比较匹配),如果匹配成功,则调用 drv->probe 函数 (drv->probe = platform_drv_probe);
注:注册platform_device实际上是向platform总线注册dev(device),注册platform_driver实际上是向platform总线注册drv(device_driver);

(3) 虽然前面说 platform_match 函数的匹配方式有5种,但是常用的匹配方式是以下3种:
   a. 最先比较: platform_device. driver_override 和 platform_driver.driver.name,
    (可以设置 platform_device 的 driver_override,强制选择某个 platform_driver)
   b.然后比较: platform_device. name 和 platform_driver.id_table[i].name
   Platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的{ .name, .driver_data}, 其中的“name”表示该 drv 支持的设备的名字, driver_data 是些提供给该 device 的私有数据。
   c. 最后比较: platform_device.name 和 platform_driver.driver.name比较;
(4) 前面说 platform_match(dev,drv) 匹配成功后,调用 drv->probe 函数(注:这个 drvdevice_driver),其中 drv->probe 在注册 drv 时被初始化为 platform_drv_probe,如下图:
在这里插入图片描述
那么 platform_driver.probe 函数是如何被调用的呢?代码如下:

static int platform_drv_probe(struct device *_dev)
{
    
    
	struct platform_driver *drv = to_platform_driver(_dev->driver); /*获取与 dev 绑定的 driver 所属的 platform_driver 结构体*/
	struct platform_device *dev = to_platform_device(_dev);
	int ret;

	ret = of_clk_set_defaults(_dev->of_node, false);
	if (ret < 0)
		return ret;

	ret = dev_pm_domain_attach(_dev, true);
	if (ret != -EPROBE_DEFER) {
    
    
		if (drv->probe) {
    
    
			ret = drv->probe(dev); /*被调用,注:这个 drv 是 platform_driver 结构体,platform_driver 结构体有probe成员*/
			if (ret)
				dev_pm_domain_detach(_dev, true);
		} else {
    
    
			/* don't fail if just dev_pm_domain_attach failed */
			ret = 0;
		}
	}

	if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
    
    
		dev_warn(_dev, "probe deferral not supported\n");
		ret = -ENXIO;
	}

	return ret;
}
2.5.1 函数调用过程
platform_device_register
	platform_device_add
		device_add
			bus_add_device // 放入链表
			bus_probe_device // probe 枚举设备,即找到匹配的(dev, drv)
				device_initial_probe
					__device_attach
						bus_for_each_drv(...,__device_attach_driver,...)
							__device_attach_driver
								driver_match_device(drv, dev) // 是否匹配
								driver_probe_device // 调用 drv 的 probe
									really_probe(dev, drv) /*drv->probe 在 really_probe 被调用*/
platform_driver_register
	__platform_driver_register
		driver_register
			bus_add_driver // 放入链表
				driver_attach(drv)
					bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
						__driver_attach
							driver_match_device(drv, dev) // 是否匹配
							driver_probe_device // 调用 drv 的 probe
								really_probe(dev, drv)  /*drv->probe 在 really_probe 被调用*/

really_probe 函数有如下代码:

	if (dev->bus->probe) {
    
           /*platform_bus_type.probe 一般没有实现,为空 */
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
    
        /*drv->probe 在注册 platform_driver 时,被初始化为platform_drv_probe*/
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}

3. platform 总线设备驱动代码框架

3.1 platform 驱动代码框架

/* 设备结构体 */
struct xxx_dev{
    
    
	struct cdev cdev;
	/* 设备结构体其他具体内容 */
};

struct xxx_dev xxxdev; /* 定义个设备结构体变量 */

static int xxx_open(struct inode *inode, struct file *filp)
{
    
    
	/* 函数具体内容 */
	return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf,
						size_t cnt, loff_t *offt)
{
    
    
	/* 函数具体内容 */
	return 0;
}

/*
* 字符设备驱动操作集
*/
static struct file_operations xxx_fops = {
    
    
	.owner = THIS_MODULE,
	.open = xxx_open,
	.write = xxx_write,
};

/*
* platform 驱动的 probe 函数
* 驱动与设备匹配成功以后此函数就会执行
*/
static int xxx_probe(struct platform_device *dev)
{
    
    
	......
	cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
	/* 函数具体内容 */
	return 0;
}

static int xxx_remove(struct platform_device *dev)
{
    
    
	......
	cdev_del(&xxxdev.cdev);/* 删除 cdev */
	/* 函数具体内容 */
	return 0;
}

/* 匹配列表(使用设备树),如果不适用设备树,则不用定义 */
static const struct of_device_id xxx_of_match[] = {
    
    
	{
    
     .compatible = "xxx-gpio" },
	{
    
     /* Sentinel */ }
};

/*
* platform 平台驱动结构体
*/
static struct platform_driver xxx_driver = {
    
    
	.driver = {
    
    
	.name = "xxx",
	.of_match_table = xxx_of_match, 
	},
	.probe = xxx_probe,
	.remove = xxx_remove,
};

/* 驱动模块加载 */
static int __init xxxdriver_init(void)
{
    
    
	return platform_driver_register(&xxx_driver);
}
/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{
    
    
	platform_driver_unregister(&xxx_driver);
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");

3.2 platform 设备代码框架

/* 寄存器地址定义*/
#define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */
#define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
#define REGISTER_LENGTH 4
/* 资源 */
static struct resource xxx_resources[] = {
    
    
	[0] = {
    
    
		.start = PERIPH1_REGISTER_BASE,
		.end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[1] = {
    
    
		.start = PERIPH2_REGISTER_BASE,
		.end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
};
/* platform 设备结构体 */
static struct platform_device xxxdevice = {
    
    
	.name = "xxx-gpio",
	.id = -1,
	.num_resources = ARRAY_SIZE(xxx_resources),
	.resource = xxx_resources,
};

/* 设备模块加载 */
static int __init xxxdevice_init(void)
{
    
    
	return platform_device_register(&xxxdevice);
}
/* 设备模块注销 */
static void __exit xxx_resourcesdevice_exit(void)
{
    
    
	platform_device_unregister(&xxxdevice);
}

module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");

4. platform 设备与驱动程序编写 (LED 驱动)

4.1 实验环境

开发板:Jz2440 开发板
linux内核: linux-4.15

4.2 Linux 下 LED 灯驱动原理

可以阅读之前写的博客Linux字符设备驱动之LED驱动

4.3 程序编写

4.3.1 led_dev.c 程序编写
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>

#include <asm/mach/map.h>
#include <asm/io.h>


#define GPF_BASE_ADDR  0x56000050
#define REGISTER_LENGTH 8

static struct resource led_resources[] = {
    
    
	[0] = {
    
    
			.start = GPF_BASE_ADDR, 	
			.end   = GPF_BASE_ADDR + REGISTER_LENGTH - 1,
			.flags  = IORESOURCE_MEM,
		},
	
};

static void led_device_release(struct device *dev)
{
    
    
}

static struct platform_device led_device = {
    
    
	.name = "led",
	.resource = led_resources,
	.num_resources = ARRAY_SIZE(led_resources),
	.dev = {
    
    
		.release = led_device_release,
	},
};


static int __init led_device_init(void)
{
    
    
	return platform_device_register(&led_device);
}

static void __exit led_device_exit(void)
{
    
    
	platform_device_unregister(&led_device);
}

module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
4.3.2 led_drv.c 程序编写
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>

#include <asm/mach/map.h>
#include <asm/io.h>

#define DEVICE_NAME    "led"
#define LED_ON         1
#define LED_OFF        0



static struct led_dev_t{
    
    
	struct cdev cdev;/*定义字符设备*/
	int major;       /*主设备号*/
	struct class *class;  /*类*/
	struct device *device;/*设备*/

	struct device_node *nd; /*设备节点*/

	unsigned int __iomem *gpiocon;  /*gpfcon 寄存器*/
	unsigned int __iomem *gpiodat;  /*gpfdat 寄存器*/
}led_dev;


static void led_switch(u32 on_off)
{
    
    
	u32 val;
	
	if(on_off == 1){
    
    
		val = readl(led_dev.gpiodat);
		val &= ~((1<<4) | (1<<5) | (1<<6));
	    writel(val,led_dev.gpiodat);
	}else{
    
    
		val = readl(led_dev.gpiodat);
		val |= ((1<<4) | (1<<5) | (1<<6));
	    writel(val,led_dev.gpiodat);
	}
}

static int led_open(struct inode *inode, struct file *filp)
{
    
    
	u32 val;

	filp->private_data = &led_dev;
		
	val = readl(led_dev.gpiocon);
	val &= ~(3<<(4*2) | 3<<(5*2) | 3<<(6*2));
	val |= 1<<(4*2)| 1<<(5*2)| 1<<(6*2);
	writel(val, led_dev.gpiocon);

	//printk("gpfcon:0x%x\n",*led_dev.gpfcon);

	return 0;
}


static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    
    
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    
    	
	u32 val,ret;

	ret = copy_from_user(&val,buf,cnt);

	if(ret < 0){
    
    
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}
	
	if (val == 1){
    
    
		led_switch(LED_ON);
	}else{
    
    
		led_switch(LED_OFF);
	}

	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    
    
	
	return 0;
}


static struct file_operations led_fops = {
    
    
	.owner   = THIS_MODULE,
	.open    = led_open,
	.read    = led_read,
	.write   = led_write,
	.release = led_release,

};

static int led_probe(struct platform_device *dev)
{
    
    
	struct resource *ledsource;
	
	dev_t devid;
    /*1.创建设备号*/
	if(led_dev.major){
    
    
		devid = MKDEV(led_dev.major,0);
		register_chrdev_region(devid, 1, DEVICE_NAME);
	}else{
    
    
		alloc_chrdev_region(&devid, 0, 1, DEVICE_NAME);
		led_dev.major = MAJOR(devid);
	}

	/*2.初始化字符设备cdev*/
	cdev_init(&led_dev.cdev, &led_fops);

	/*3.向内核添加一个cdev*/
	cdev_add(&led_dev.cdev, devid, 1);

	/*4.创建类*/
	led_dev.class = class_create(THIS_MODULE, DEVICE_NAME);
	if(IS_ERR(led_dev.class)){
    
    
		return PTR_ERR(led_dev.class);
	}

	/*5.创建设备*/
	led_dev.device = device_create(led_dev.class, NULL, devid, NULL, DEVICE_NAME);
	if(IS_ERR(led_dev.device)){
    
    
		return PTR_ERR(led_dev.device);
	}

	ledsource = platform_get_resource(dev, IORESOURCE_MEM, 0);

	led_dev.gpiocon = (unsigned int __iomem*)ioremap(ledsource->start, resource_size(ledsource));
	led_dev.gpiodat = led_dev.gpiocon + 1;
	
	return 0;
}

int led_remove(struct platform_device *dev)
{
    
    
	cdev_del(&led_dev.cdev); /*删除cdev*/
	unregister_chrdev_region(MKDEV(led_dev.major,0),1); /*注销设备号*/

	device_destroy(led_dev.class, MKDEV(led_dev.major,0)); /*删除设备*/
	class_destroy(led_dev.class);

	
	return 0;
}


static struct platform_driver led_driver = {
    
    
	.driver = {
    
    
	.name = "led",
	},
	.probe = led_probe,
	.remove = led_remove,
};

static int __init led_driver_init(void)
{
    
    
	return platform_driver_register(&led_driver);
}

static void __exit led_driver_exit(void)
{
    
    
	platform_driver_unregister(&led_driver);
}

module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
4.3.3 led_app.c 程序编写
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

/*int main(int argc,char **argv) 参数详解
 * argc:命令行总的参数个数
 * argv:对应argc个参数,其中argv[0]是程序的全名,后面的参数对应着用户输入的参数
 * 
 *例如:应用程序:first_drv on 
 *      其中:first_drv是应用程序的名称,on是用户输入的参数
 *      此时:argc = 2, argv[0] = "first_drv" , argv[1] = "on"
 */
int main(int argc,char **argv)
{
    
    
	int val;
    int fd;

	fd = open(argv[1],O_RDWR);

	if(fd<0) printf("can't open!\n");
	if (argc != 3)
	{
    
    
		printf("Usage :\n");
		printf("%s <on|off>\n", argv[0]);
		return 0;
	}

	if(strcmp(argv[2],"on") == 0) val = 1;
	else                          val = 0;

    write(fd,&val,sizeof(val));
	close(fd);
	
	return 0;	
}
4.3.4 Makefile 编写
KERN_DIR = /home/book/works/linux-4.15

all:
	make -C $(KERN_DIR) M=`pwd` modules
	arm-linux-gcc -o led_app led_app.c

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

obj-m  += led_drv.o led_dev.o

测试时输入命令:./led_app /dev/led on,点亮LED;输入命令:./led_app /dev/led off,熄灭LED。

最后,想对linux 总线设备驱动模型有更深入的了解可以阅读这博客:https://blog.csdn.net/lizuobin2/article/details/51570196

猜你喜欢

转载自blog.csdn.net/qq_35031421/article/details/105335441
今日推荐