首先声明一下工作环境,Beaglebone Black(以下简称BBB)运行:ti-sdk-
am335x-evm-08的linux-3.14.26。虚拟机是ubuntu12.04 假定工作环
境都已经搭建完成。我们需要做的是编写一个LED驱动。并写一个简单的
APP去测试驱动工作状况。
Linux设备分为三类:
1 字符设备
2 块设备
3 网络设备
LED明显归属于第一类,字符设备。接下来先从加载驱动的首先执行的init函数讲解,讲解将主要解释重要函数。完整代码在最后面给出。
/*************************************************
*MAJOR提取主设备号,如果不为零,即手动定义了,那么使用*register_chrdev_region进行注册,第一参数是设备号,在这里只需要注
*册一个设备号,因此第二个参数为1,第三个参数就是起一个名字。如果没有手动
*定义。那么使用alloc_chrdev_region分配。一般推荐使用手动分配,这样
*代码可移植性高。
************************************************/
if(MAJOR(dev->devt))
ok = register_chrdev_region(dev->devt, 1, dev->name);
} else {
ok = alloc_chrdev_region(&dev->devt, MINOR(dev->devt), 1, dev->name);
}
/********************************************************
*创建设备类,创建成功后可以在sysfs/class/见到dev->name名字的文件夹
*设备类创建成功后,使用device_create创建设备节点。创建成功后我们就能
*在/dev下找到我们的dev->name名称的LED设备。这样就不需要再使用mknod
*手动创建了。
********************************************************/
dev->dev_class = class_create(THIS_MODULE, dev->name);
dev->device = device_create(dev->dev_class, NULL, dev->devt, dev, dev->name);
/******************************************************
*dev->dev是struct cdev类型,它代表了一个字符设备。首先我们使用
*cdev_init进行初始化,并把dev->fop我们提供的调用函数与字符设备
*关联起来。接下来利用cdev_add加入系统中。在cdev_add中将会将设备加入
*kobject结构里进行管理,并调用kobject_get()增加引用计数。想了解这
*部分内容的可看设备模型。这样系统调用就可以找到我们提供的dev->fop操作
*函数了。
******************************************************/
cdev_init(&dev->dev, &dev->fop);
ok = cdev_add(&dev->dev, dev->devt, 1);
exit()函数则执行与init相反的的工作,注意销毁顺序最好与init的相反。
接下来的要探讨open函数。在open函数里我们进行对LED的GPIO管脚注册初始化。
//gpio_request()为GPIO注册函数,我理解的硬件资源需要注册是由于
//比如UART1资源,我们现有驱动A,对UART1进行使用寄存器操作初始化
//然后再进行收发,从功能上说是实现了的。但是假若此时驱动B也对
//UART1进行寄存器初始化,然后收发,就会发生冲突。但是内核对这些
//冲突一无所知。因此我们将资源都统一管理起来,当这个驱动需要时进行
//注册。当另外一个驱动也需要同一个资源时注册发现资源已被占用而失败
ret = gpio_request(dev->led_io.io_num, dev-
//设定管脚方向,0为设定的初始值
ret = gpio_direction_output(dev->led_io.io_num, 0);
现在LED驱动主要已经讲解完,接下来是写个应用测试一下。应用代码如下。
#include
#include
#include
#include
#include
#include
#define LED_DEV "/dev/led_dev"
int main(int argc, char **argv)
{
int fd;
unsigned int state = 0;
fd = open(LED_DEV, O_RDWR);
if(fd < 0){
printf("led app: can't open file %s\n", LED_DEV);
exit(1);
}
for(;;){
write(fd, &state, sizeof(unsigned int));
state = !state;
sleep(1);
}
close(fd);
exit(0);
}
将驱动,应用编译完成后,可以在BBB端使用tftp命令下载。BBB端tftp是busybox的tftp。用法与ubuntu里的不太一样。也可以在ubuntu端使用scp命令发送过去。insmod驱动后,再执行应用就能看到D31那个LED闪烁。实际过程发现,第一次加载驱动后应用一直提示打不开设备。卸载后重新加载驱动,再次执行应用又可以。
同时发现应用里不能使用fwrite对驱动进行写。测试发现这种情况下驱动端write函数不会得到调用。怀疑由于缓冲没有真正写入。于是改了fwrite()+sync()。也还是不行,希望有人能解答一下疑惑。
下面是完整的LED驱动,写的不好之处请多指教。不胜感激!
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led.h"
static int __init led_module_init(void);
static int __exit led_module_exit(void);
static int led_module_open (struct inode *, struct file *);
static int led_module_release (struct inode *, struct file *);
static ssize_t led_module_read (struct file *, char __user *, size_t, loff_t *);
static ssize_t led_module_write (struct file *, const char __user *, size_t, loff_t *);
static long led_module_ioctl (struct file *, unsigned int, unsigned long);
struct led_io_resource_t{
char name[16]; //端口名称
uint8_t state; //记录端口状态信息,已经注册/已释放
uint32_t io_num; //端口号
uint8_t io_dir; //方向0输出1输入
struct led_io_resource_t *next; //下一个申请的IO资源
};
struct led_module_t{
const char name[16]; //注册使用的名称
dev_t devt; //设备号
struct class *dev_class; //设备类
struct device *device; //设备节点
struct cdev dev;
struct file_operations fop;
struct led_io_resource_t led_io;
};
static struct led_module_t led_module = {
.name = "led_dev",
.devt = MKDEV(LED_DEV_MAJOR, LED_DEV_MINOR),
.fop = {
.owner = THIS_MODULE,
.open = led_module_open,
.release = led_module_release,
.read = led_module_read,
.write = led_module_write,
.unlocked_ioctl = led_module_ioctl,
},
.led_io = {
.name = "",
.state = GPIO_RELEASE,
.io_num = LED_GPIO_NUM,
.io_dir = OUT_PUT,
.next = NULL,
},
};
static int __init led_module_init(void)
{
int ok;
struct led_module_t *dev = &led_module;
if(MAJOR(dev->devt)){
//注册手动分配的设备号
ok = register_chrdev_region(dev->devt, 1, dev->name);
} else {
//申请设备号
ok = alloc_chrdev_region(&dev->devt, MINOR(dev->devt), 1, dev->name);
}
if(ok != 0){
printk(KERN_ERR "CUSE: failed to register chrdev region\n");
goto register_region_error;
}
//创建设备类
dev->dev_class = class_create(THIS_MODULE, dev->name);
if(IS_ERR(dev->dev_class)){
printk(KERN_ERR "CUSE: failed to create chrdev class\n");
goto class_create_error;
}
//创建设备节点
dev->device = device_create(dev->dev_class, NULL, dev->devt, dev, dev->name);
if(IS_ERR(dev->device)){
printk(KERN_ERR "CUSE: failed to create device\n");
goto device_create_error;
}
cdev_init(&dev->dev, &dev->fop);
//加入散列表中,提供调用管理
ok = cdev_add(&dev->dev, dev->devt, 1);
if(ok != 0){
printk(KERN_ERR "CUSE: failed to add cdev\n");
goto cdev_add_error;
}
return 0;
cdev_add_error:
device_destroy(dev->dev_class, dev->devt);
device_create_error:
class_destroy(dev->dev_class);
class_create_error:
unregister_chrdev_region(dev->devt, 1);
register_region_error:
return -1;
}
static int __exit led_module_exit(void)
{
struct led_module_t *dev = &led_module;
cdev_del(&dev->dev);
device_destroy(dev->dev_class, dev->devt);
class_destroy(dev->dev_class);
unregister_chrdev_region(dev->devt, 1);
gpio_free(dev->led_io.io_num);
dev->led_io.state = GPIO_RELEASE;
return 0;
}
static int led_module_open (struct inode *fi, struct file *fp)
{
int ret;
struct led_module_t *dev;
//fi->i_cdev在cdev_add时传入
dev = container_of(fi->i_cdev, struct led_module_t, dev);
//已经打开,直接返回成功
if(dev->led_io.state == GPIO_REGISTER){
return 0;
}
sprintf(dev->led_io.name, "gpio-%d", dev->led_io.io_num);
//申请GPIO
ret = gpio_request(dev->led_io.io_num, dev->led_io.name);
if(ret < 0){
printk(KERN_ERR "CUSE: failed to request gpio %d\n", dev->led_io.io_num);
goto gpio_request_error;
}
//设定管脚方向
ret = gpio_direction_output(dev->led_io.io_num, 0); //0为初始输出值
if(ret < 0){
goto set_gpio_direction_failed;
}
dev->led_io.state = GPIO_REGISTER;
return 0;
set_gpio_direction_failed:
gpio_free(dev->led_io.io_num);
gpio_request_error:
return ret;
}
static int led_module_release (struct inode *fi, struct file *fp)
{
struct led_module_t *dev;
dev = container_of(fi->i_cdev, struct led_module_t, dev);
//释放管脚占用
gpio_free(dev->led_io.io_num);
dev->led_io.state = GPIO_RELEASE;
return 0;
}
static ssize_t led_module_read (struct file *fp, char __user *buff, size_t size, loff_t *foff)
{
uint8_t ret = 0;
struct led_module_t *dev = &led_module;;
ret = gpio_get_value(dev->led_io.io_num);
return copy_to_user(buff, &ret, sizeof(uint8_t));
}
static ssize_t led_module_write (struct file *fp, const char __user *buff, size_t size, loff_t *foff)
{
uint32_t value = 0;
struct led_module_t *dev = &led_module;
copy_from_user(&value, buff, 1);
gpio_set_value(dev->led_io.io_num, value?1:0);
printk(KERN_ALERT "%s: write data %d\n", dev->name, value);
return 0;
}
static long led_module_ioctl (struct file *fp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
unsigned int data;
struct led_module_t *dev = &led_module;
if(_IOC_TYPE(cmd) != LED_IOCTL_MAGIC){
printk(KERN_ALERT "%s: cmd error\n", dev->name);
return -ENOTTY;
}
if(_IOC_NR(cmd) > LED_IOC_MAXNR){
printk(KERN_ALERT "%s: cmd error\n", dev->name);
return -ENOTTY;
}
if(_IOC_DIR(cmd) & _IOC_READ){
switch(cmd){
case LED_IOCREAD:
data = gpio_get_value(dev->led_io.io_num);
if(put_user(data, (unsigned int __user*)arg)){
ret = -EFAULT;
goto cap_err;
}
break;
default:
ret = -EINVAL;
}
} else if(_IOC_DIR(cmd) & _IOC_WRITE){
switch(cmd){
case LED_IOCWRITE:
if(get_user(data, (unsigned int __user *)arg)){
printk(KERN_ALERT "%s: bad address\n", dev->name);
ret = -EFAULT;
goto cap_err;
}
gpio_set_value(dev->led_io.io_num, data?1:0);
break;
default:
ret = -EINVAL;
}
}else{
switch(cmd){
case LED_IOCRESET:
//nothing to do
break;
default:
ret = -EINVAL;
}
}
cap_err:
return ret;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kwanson");
module_init(led_module_init);
module_exit(led_module_exit);