在Linux设备驱动模型中,需要关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统注册一个设备的时候,会寻找与之匹配的驱动;相反的,在注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。Linux实现了一种虚拟的总线,称为platform
总线,相应的设备称为platform_device
,而驱动称为platform_driver
。
Linux内核使用bus_type结构体表示总线,由内核统一管理,驱动程序一般不牵扯到修改总线,只需要使用即可。
include <linux/device.h>
struct bus_type {
const char *name; // 总线名称
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups; // 总线属性
const struct attribute_group **dev_groups; // 设备属性
const struct attribute_group **drv_groups; // 驱动属性
//匹配函数,用于匹配设备和驱动
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
};
platform总线是bus_type
的一个具体实例,定义在文件drivers/base/platform.c文件中。
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match, // platform_device和platform_driver匹配函数
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
Linux内核使用platform_driver
结构体表示platform驱动。
include <linux/platform_device.h>
struct platform_driver {
// 当驱动和设备匹配成功后,probe函数就会执行,类似于初始化函数
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;
// 用于匹配的id_table指针,一般指向一个platform_device_id数组
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
// id_table实质上就是一个字符串数组
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
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 */
// 采用设备树时,采用of_match_table进行匹配
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
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;
};
compatible非常重要,对于设备树而言,就是通过设备节点的compatible
属性值和of_match_table
数组中的每个compatible
成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。
include <linux/mod_devicetable.h>
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
在编写platform驱动的时候,需要定义一个platform_driver
结构体,然后实现结构体中的各个变量成员,重点是实现匹配方法及probe
函数。当我们定义好platform_driver
结构体后,需要使用platform_driver_register
函数向内核注册一个platform驱动。drv
为platform_driver
结构体结构体指针,返回值为0成功,返回值为负值表示失败。使用platform_driver_unregister
注销。
include <linux/platform_device.h>
#define platform_driver_register(drv) __platform_driver_register(drv, THIS_MODULE)
void platform_driver_unregister(struct platform_driver *drv)
Linux内核使用struct platform_device
结构体表示platform设备。
include <linux/platform_device.h>
struct platform_device {
const char *name; // 设备名字,要和platform驱动的name字段一致
int id;
bool id_auto;
struct device dev;
u32 num_resources; // 表示资源数量,即resource指向的数组长度
struct resource *resource; // 表示资源,是一个数组,如IO、内存、寄存器、中断等资源
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;
};
include <linux/ioport.h>
struct resource {
resource_size_t start; // 资源的起始信息,如内存或寄存器的起始地址
resource_size_t end; // 资源的结束信息,如内存或寄存器的结束地址
const char *name; // 资源名称
unsigned long flags; // 资源类型
struct resource *parent, *sibling, *child;
};
// 资源类型宏定义
#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
// 获取资源信息,type为资源类型,num为资源序号index
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
// 获取中断资源
int platform_get_irq(struct platform_device *dev, unsigned int num)
Linux内核不支持设备树的时候,需要编写platform_device
变量来描述设备信息,然后使用platform_device_register
函数将设备信息注册到内核中,模块退出时使用使用platform_device_unregister
注销。当linux内核支持设备树后,就不需要手动注册platform_device
了。Linux内核启动的时候会从设备树中读取设备信息,然后将其组织成platform_device
形式,在驱动中只需要提取这些设备信息即可。
include <linux/platform_device.h>
int platform_device_register(struct platform_device *pdev)
void platform_device_unregister(struct platform_device *pdev)
在设备树中创建的设备要被platform总线正确匹配,需要正确设置好compatible
属性。下面是编写的rgbled的设备节点,compatible
属性为"my,rgb-led"
。
/* 在根节点添加rgb led节点 */
rgb_led {
#address-cells = <1>;
#size-cells = <1>;
pinctrl-names = "default";
compatible = "my,rgb-led";
pinctrl-0 = <&pinctrl_rgb_led>;
red = <&gpio1 4 GPIO_ACTIVE_LOW>;
green = <&gpio4 20 GPIO_ACTIVE_LOW>;
blue = <&gpio4 19 GPIO_ACTIVE_LOW>;
status = "okay";
};
platform驱动想要正确匹配platform设备,需要正确设置compatible
属性,使用设备树时,需要定义struct of_device_id
变量,并设置compatible
成员的值,同时在注册之前正确设置struct platform_driver
结构体。
MODULE_DEVICE_TABLE
展开后生成了一个_mod_type__name_device_table
的符号表,其中type
为类型,name
是这个驱动的名称。在内核编译的时候将这部分符号单独放置在一个区域。当内核运行的时,用户通过类型tpye
和设备表中名称name
在表中查找MODULE_DEVICE_TABLE
定义的符号,找到之后可以动态的加载驱动。MODULE_DEVICE_TABLE
一般用于动态加载驱动也就是热插拔的时候使用。
static const struct of_device_id my_led_of_match[] = {
{ .compatible = "my,rgb-led" }, // 兼容属性,与设备树中的兼容属性一致
{ /* Sentinel */ } // 最后一个元素一定为空
};
include <linux/module.h>
MODULE_DEVICE_TABLE(of, my_led_of_match);
static struct platform_driver my_led_platform_driver = {
.driver = {
.name = "imx6ull-rgb-led", // 设置驱动的名称
.of_match_table = my_led_of_match, // 驱动的设备的匹配数组信息,一个驱动可以和多个设备匹配
},
.probe = my_led_probe, // 初始化probe函数,驱动和设备匹配成功后此函数会执行
.remove = my_led_remove, // 退出remove函数
};
Linux内核在启动的时候会处理设备树,首先将设备树节点转换为device_node
,然后视情况将device_node
转换为platform_device
。
dtb -> device_node -> platform_device // 内核处理dtb文件的过程
设备树中的节点被内核解析成struct device_node
结构体,所有的struct device_node
结构体组成一棵树,树的根节点保存在of_root
变量中,类型为struct device_node*
。
struct device_node {
const char *name; // 设备树节点中的name属性
const char *type; // 设备树节点中的device_type属性
phandle phandle;
const char *full_name; // 设备树节点的名称
struct fwnode_handle fwnode;
struct property *properties; // 节点内的所有属性,所有属性会组成一个链表
struct property *deadprops; /* removed properties */
struct device_node *parent; // 父节点
struct device_node *child; // 子节点
struct device_node *sibling; // 兄弟节点,同级节点
struct kobject kobj;
unsigned long _flags;
void *data;
};
内核函数of_platform_default_populate_init
遍历device_node
树, 生成platform_device
,但不是所有的device_node
都会被要转化为platform_device
,有以下几个条件。
(1) 根节点的子节点(节点必须含有compatible
属性)
(2) 含有特殊compatible
属性节点的子节点(子节点必须含有compatible
属性),这些特殊的compatilbe
属性为"simple-bus",“simple-mfd”,“isa”,“arm,amba-bus”。
(3)i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device
。
属性的转换方法如下:
(1)platform_device
中含有resource
数组, 它来自device_node
的reg
, interrupts
等属性。
(2)可以通过platform_device.dev.of_node
指向device_node
获得其他属性。
/*===========================my_led_platform_driver.h================================*/
#include
#include
#include
#include
#include
#include // 设备树
// GPIO1_4 红灯
// GPIO4_20 绿灯
// GPIO4_19 蓝灯
#define LED_ON 0
#define LED_OFF 1
#define READ_PIN_NAME "read_pin"
#define GREEN_PIN_NAME "green_pin"
#define BLUE_PIN_NAME "blue_pin"
// 设备结构体
struct my_led_dev {
struct cdev cdev; // 字符设备结构体
struct device_node* node; // rgb_led设备树节点指针
int r_gpio; // 红色led引脚的GPIO编号
int g_gpio; // 绿色led引脚的GPIO编号
int b_gpio; // 蓝色led引脚的GPIO编号
char r_name[12]; // 红色led引脚的名字
char g_name[12]; // 绿色led引脚的名字
char b_name[12]; // 蓝色led引脚的名字
int r_status; // 红色led状态,0亮,1灭
int g_status; // 绿色led状态,0亮,1灭
int b_status; // 蓝色led状态,0亮,1灭
dev_t devno; // 设备号
struct class* my_led_class;
struct device* my_led_device;
struct mutex mutex; // 用于同步的互斥体
unsigned int r_on_cnt; // 红色led亮的次数
unsigned int g_on_cnt; // 红色led亮的次数
unsigned int b_on_cnt; // 红色led亮的次数
};
/*===========================my_led_platform_driver.c================================*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "my_led_platform_driver.h"
static struct my_led_dev* my_led = NULL;
static int my_led_open(struct inode* inode, struct file* filp)
{
struct my_led_dev* dev = container_of(inode->i_cdev,
struct my_led_dev, cdev);
filp->private_data = dev;
return 0;
}
// 设置led对应的gpio值
static void set_led_gpio(int gpio, int val, int* status,int* cnt)
{
mutex_lock(&my_led->mutex);
// 非0-灯亮,0-灯灭,GPIO引脚输出低电平灯亮,输出高点平灯灭
if (val == 0) {
gpio_set_value(gpio, LED_OFF);
*status = LED_OFF;
}
else {
gpio_set_value(gpio, LED_ON);
if (*status != LED_ON) {
*cnt = *cnt + 1;
*status = LED_ON;
}
}
mutex_unlock(&my_led->mutex);
}
static ssize_t my_led_write(struct file* filp, const char __user* buf,
size_t size, loff_t* ppos)
{
struct my_led_dev* dev = filp->private_data;
int ret, val[3];
if (size != sizeof(val)) return -EINVAL;
// 成功返回0,失败返回失败的数目
ret = copy_from_user(&val, buf, sizeof(val));
if (0 != ret) return -EFAULT;
set_led_gpio(dev->r_gpio, val[0], &dev->r_status, &dev->r_on_cnt);
set_led_gpio(dev->g_gpio, val[1], &dev->g_status, &dev->g_on_cnt);
set_led_gpio(dev->b_gpio, val[2], &dev->b_status, &dev->b_on_cnt);
return sizeof(val);
}
// 文件操作函数结构体
static const struct file_operations my_led_fops = {
.owner = THIS_MODULE,
.write = my_led_write,
.open = my_led_open,
};
// 初始化和注册cdev结构体
static int set_up_my_led_cdev(struct my_led_dev* dev, int cnt)
{
int err;
cdev_init(&dev->cdev, &my_led_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, dev->devno, cnt); // 出错返回负值
if (err < 0)
printk(KERN_ERR "adding my_led cdev %d error, errno %d\n", cnt, err);
return err;
}
// 定义设备的属性
static ssize_t red_show(struct device* dev, struct device_attribute* attr,char* buf)
{
return sprintf(buf, "red led lighting times %u\n", my_led->r_on_cnt);
} // echo 非0,灯亮,echo 0,灯灭
static ssize_t red_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
// 将echo输入得字符串转换为无符号整数,10表示十进制
err = kstrtouint(buf, 10, &val);
if (err)
return err;
set_led_gpio(my_led->r_gpio, val, &my_led->r_status, &my_led->r_on_cnt);
return count;
}
static ssize_t green_show(struct device *dev, struct device_attribute *attr,char *buf)
{
return sprintf(buf, "green led lighting times %u\n", my_led->g_on_cnt);
}
static ssize_t green_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
err = kstrtouint(buf, 10, &val);
if (err)
return err;
set_led_gpio(my_led->g_gpio, val, &my_led->g_status, &my_led->g_on_cnt);
return count;
}
static ssize_t blue_show(struct device *dev, struct device_attribute *attr,char *buf)
{
return sprintf(buf, "blue led lighting times %u\n", my_led->b_on_cnt);
}
static ssize_t blue_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
err = kstrtouint(buf, 10, &val);
if (err)
return err;
set_led_gpio(my_led->b_gpio, val, &my_led->b_status, &my_led->b_on_cnt);
return count;
}
// 红色led设备属性,生成的属性结构体名称为dev_attr_red,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(red, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, red_show, red_store);
// 绿色led设备属性,生成的属性结构体名称为dev_attr_green,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(green, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, green_show, green_store);
// 蓝色led设备属性,生成的属性结构体名称为dev_attr_blue,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(blue, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, blue_show, blue_store);
static struct attribute* led_colour_attrs[] = {
&dev_attr_red.attr,
&dev_attr_green.attr,
&dev_attr_blue.attr,
NULL,
};
/* 创建led颜色属性组,生成的属性结构体名称为led_colour_group,类型为static const
struct attribute_group,使用led_colour_attrs初始化
ATTRIBUTE_GROUPS(led_colour); */
// 使用ATTRIBUTE_GROUPS宏创建属性组时编译会出现警告,这里直接定义led颜色属性组
static struct attribute_group led_colour_group = {
.attrs = led_colour_attrs,
};
// 从设备树中提取信息,初始化gpio
static int led_gpio_init(struct my_led_dev* dev)
{
int ret = 0, ret1 = 0, ret2 = 0, ret3 = 0;
struct device_node* n;
const char* status;
// 设置led引脚的名字
memcpy(dev->r_name, READ_PIN_NAME, strlen(READ_PIN_NAME) + 1);
memcpy(dev->g_name, GREEN_PIN_NAME, strlen(GREEN_PIN_NAME) + 1);
memcpy(dev->b_name, BLUE_PIN_NAME, strlen(BLUE_PIN_NAME) + 1);
// 从设备树中获取设备节点
n = of_find_node_by_path("/rgb_led");
if (NULL == n) {
printk(KERN_ERR "find led node error by /rgb_led\n");
return -EINVAL;
}
dev->node = n;
// 根据设备树中的gpio属性,获取led对应的gpio编号
dev->r_gpio = of_get_named_gpio(dev->node, "red", 0);
if (dev->r_gpio < 0) {
printk(KERN_ERR "can't get %s gpio number\n", dev->r_name);
return -EINVAL;
}
printk(KERN_INFO "%s gpio number %#x\n", dev->r_name, (unsigned int)dev->r_gpio);
dev->g_gpio = of_get_named_gpio(dev->node, "green", 0);
if (dev->g_gpio < 0) {
printk(KERN_ERR "can't get %s gpio number\n", dev->g_name);
return -EINVAL;
}
printk(KERN_INFO "%s gpio number %#x\n", dev->g_name, (unsigned int)dev->g_gpio);
dev->b_gpio = of_get_named_gpio(dev->node, "blue", 0);
if (dev->b_gpio < 0) {
printk(KERN_ERR "can't get %s gpio number\n", dev->b_name);
return -EINVAL;
}
printk(KERN_INFO "%s gpio number %#x\n", dev->b_name, (unsigned int)dev->b_gpio);
// 申请gpio
ret = gpio_request(dev->r_gpio, dev->r_name);
if (ret < 0) {
printk(KERN_ERR "request %s gpio error\n", dev->r_name);
return -EINVAL;
}
ret = gpio_request(dev->g_gpio, dev->g_name);
if (ret < 0) {
printk(KERN_ERR "request %s gpio error\n", dev->g_name);
ret = -EINVAL;
goto free_read_gpio;
}
ret = gpio_request(dev->b_gpio, dev->b_name);
if (ret < 0) {
printk(KERN_ERR "request %s gpio error\n", dev->b_name);
ret = -EINVAL;
goto free_green_gpio;
}
// 从设备树中获取status属性
ret = of_property_read_string(dev->node, "status", &status);
if (ret < 0) {
printk(KERN_ERR "find rgb_led node status property error\n");
ret = -EINVAL;
goto free_blue_gpio;
}
// 设置GPIO为输入,并根据status属性设置输出值
ret = strcmp(status, "okay");
if (ret == 0) {
ret1 = gpio_direction_output(dev->r_gpio, LED_ON);
dev->r_status = LED_ON;
ret2 = gpio_direction_output(dev->g_gpio, LED_ON);
dev->g_status = LED_ON;
ret3 = gpio_direction_output(dev->b_gpio, LED_ON);
dev->b_status = LED_ON;
} else {
ret1 = gpio_direction_output(dev->r_gpio, LED_OFF);
dev->r_status = LED_OFF;
ret2 = gpio_direction_output(dev->g_gpio, LED_OFF);
dev->g_status = LED_OFF;
ret3 = gpio_direction_output(dev->b_gpio, LED_OFF);
dev->b_status = LED_OFF;
}
if (ret1 < 0 || ret2 < 0 || ret3 < 0) {
printk(KERN_ERR "gpio set direction output error\n");
ret = -EINVAL;
goto free_blue_gpio;
}
return 0;
free_blue_gpio:
gpio_free(dev->b_gpio);
free_green_gpio:
gpio_free(dev->g_gpio);
free_read_gpio:
gpio_free(dev->r_gpio);
return ret;
}
/****************************模块初始化************************************/
static int my_led_probe(struct platform_device* dev)
{
int ret = 0;
dev_t devno = 0;
printk(KERN_INFO "my led driver and device was matched\n");
// 动态分配设备号,传入的devno参数为0,使用unregister_chrdev_region注销动态分配的设备号
ret = alloc_chrdev_region(&devno, 0, 1, "my_led");
if (ret < 0) {
printk(KERN_ERR "alloc_chrdev_region() failed %d\n", ret);
return ret;
}
// 分配设备结构体内存并将分配的内存清0
my_led = kzalloc(sizeof(struct my_led_dev), GFP_KERNEL);
if (NULL == my_led) {
ret = -ENOMEM;
printk(KERN_ERR "kzalloc() failed %d\n", ret);
goto unreg_chrdev;
}
my_led->devno = devno;
// 从设备树获取资源
ret = led_gpio_init(my_led);
if (0 != ret) goto free_dev;
ret = set_up_my_led_cdev(my_led, 1);
if (ret < 0) goto free_dev;
// 创建类和设备,当模块加载后会自动在/dev目录下生成设备节点
my_led->my_led_class = class_create(THIS_MODULE, "my_led_class");
if (IS_ERR(my_led->my_led_class)) {
ret = PTR_ERR(my_led->my_led_class);
printk(KERN_ERR "class_create() failed %d\n", ret);
goto del_cdev;
}
my_led->my_led_device = device_create(my_led->my_led_class, NULL,
devno, NULL, "my_led");
if (IS_ERR(my_led->my_led_device)) {
ret = PTR_ERR(my_led->my_led_device);
printk(KERN_ERR "device_create() failed %d\n", ret);
goto clean_class;
}
// 使用sysfs_create_group可以创建一组属性文件,sysfs_remove_group移除一组属性文件
// 使用sysfs_create_groups可以创建多组属性文件,ysfs_remove_groups移除多组属性文件
ret = sysfs_create_group(&my_led->my_led_device->kobj, &led_colour_group);
if(ret != 0) goto clean_device;
// 初始化互斥体
mutex_init(&my_led->mutex);
printk(KERN_INFO "my_led module init OK, major %u, minor %u\n",
MAJOR(devno), MINOR(devno));
return 0;
clean_device:
device_destroy(my_led->my_led_class, devno);
clean_class:
class_destroy(my_led->my_led_class);
del_cdev:
cdev_del(&my_led->cdev);
free_dev:
kfree(my_led);
my_led = NULL;
unreg_chrdev:
unregister_chrdev_region(devno, 1);
return ret;
}
// 模块注销
static int my_led_remove(struct platform_device* dev)
{
set_led_gpio(my_led->r_gpio, 0, &my_led->r_status, &my_led->r_on_cnt);
set_led_gpio(my_led->g_gpio, 0, &my_led->g_status, &my_led->g_on_cnt);
set_led_gpio(my_led->b_gpio, 0, &my_led->b_status, &my_led->b_on_cnt);
sysfs_remove_group(&my_led->my_led_device->kobj, &led_colour_group);
device_destroy(my_led->my_led_class, my_led->devno);
class_destroy(my_led->my_led_class);
cdev_del(&my_led->cdev);
gpio_free(my_led->b_gpio); // 释放gpio
gpio_free(my_led->g_gpio);
gpio_free(my_led->r_gpio);
unregister_chrdev_region(my_led->devno, 1);
kfree(my_led);
my_led = NULL;
printk(KERN_INFO "my_led module exit\n");
return 0;
}
// 定义设备和驱动匹配需要的of_device_id匹配表变量,
static const struct of_device_id my_led_of_match[] = {
{ .compatible = "my,rgb-led" }, // 兼容属性,需要与设备树中的兼容属性一致
{ /* Sentinel */ } // 最后一个元素一定为空
};
MODULE_DEVICE_TABLE(of, my_led_of_match);
// 定义注册驱动时需要的platform_driver结构体变量
static struct platform_driver my_led_platform_driver = {
.driver = {
.name = "imx6ull-rgb-led", // 设置驱动的名称
// 驱动的设备的匹配数组信息,一个驱动可以和多个设备匹配
.of_match_table = my_led_of_match,
},
.probe = my_led_probe, // 设置初始化probe函数,驱动和设备匹配成功后此函数会执行
.remove = my_led_remove, // 设置退出remove函数
};
// 模块初始化
static int __init my_led_init(void)
{
int ret = 0;
ret = platform_driver_register(&my_led_platform_driver);
if (ret < 0) printk(KERN_ERR "my_led register platform driver error\n");
return ret;
}
/***********************模块注销***************************/
static void __exit my_led_exit(void)
{
platform_driver_unregister(&my_led_platform_driver);
}
module_init(my_led_init);
module_exit(my_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");
MODULE_VERSION("v1.00");
#include
#include
#include
#include
#include
#include
#include
#define PATH "/dev/my_led"
#define OPT_STRING ":r:g:b:" // 选项字符串
void print_usage()
{
printf("Usage: ./test -r -g -b \n"
"-r: Control red led\n"
"-g: Control green led\n"
"-b: Control blue led\n"
"on: Light on(default)\n"
"off: Light off\n");
}
extern char* optarg; // 指向参数值
extern int optind; // 记录getopt函数处理argv[]数组的位置,一般不需要设置
extern int opterr; // 保存出错信息,非0 getopt会向stderr打印出错信息,如为0时则不打印
/* 遇到无法识别的选项,getopt返回?号,并将?存储到optopt中,如果将选项字符串第一个字符设置
为:号,那么getopt函数在用户未提供值的情况下返回:号而不是?号 */
extern int optopt;
int parse_option(int argc, char* argv[], int* rgb)
{
int opt;
while (-1 != (opt = getopt(argc, argv, OPT_STRING))) {
switch (opt) {
case 'r':
if(0 == strcmp(optarg, "on")) rgb[0] = 1;
else if (0 == strcmp(optarg, "off")) rgb[0] = 0;
else return -1;
break;
case 'g':
if(0 == strcmp(optarg, "on")) rgb[1] = 1;
else if (0 == strcmp(optarg, "off")) rgb[1] = 0;
else return -1;
break;
case 'b':
if(0 == strcmp(optarg, "on")) rgb[2] = 1;
else if (0 == strcmp(optarg, "off")) rgb[2] = 0;
else return -1;
break;
case ':':
print_usage();
return -1;
break;
case '?':
print_usage();
return -1;
break;
default:
print_usage();
return -1;
break;
}
}
return 0;
}
int main(int argc, char* argv[])
{
int fd, ret, val[3] = {0};
if (7 != argc) {
print_usage();
return -1;
}
ret = parse_option(argc, argv, val);
if (0 != ret) {
print_usage();
printf("parse option error\n");
return -1;
}
fd = open(PATH, O_RDWR);
if (fd < 0){
printf("my_led open error\n");
return -1;
}
ret = write(fd, &val, sizeof(val));
if (ret < 0) {
printf("write error\n");
close(fd);
return -1;
}
close(fd);
return 0;
}