嵌入式Linux之设备驱动程序

嵌入式Linux之设备驱动程序


本文档以一个简单的字符设备——LED驱动设备为例,阐述Linux系统下设备驱动程序的基本原理以及设备驱动程序的编程方法。

1. 设备驱动简介

1.1 linux设备驱动分类

在Linux操作系统中,一个核心的思想就是“一切皆文件”。对于驱动设备来讲,也是将其作为文件,通过open/close/read/write/cioctl(字符设备相关操作除外)进行相应的控制。在Linux系统中,设备驱动程序可以分为三类:
字符设备:
字符设备是能够像字节流一样被访问的设备,对字符设备发出的读写请求,相应的IO操作立即发生。Linux系统中很多设备都是字符设备,例如串口,键盘,鼠标等。
块设备:
在Linux系统中进行IO以块为单位的设备被称作块设备,块设备能够安装文件系统。块设备能够安装文件系统。块设备会利用一块系统内存作为缓冲区,因此对块设备的访问并不一定会立即产生IO操作,Linux环境下常见的块设备有硬盘,软驱等。
网络设备:
网络设备既可以是网卡这样的硬件设备,也可是纯软件的设备如回环设备。网络设备由Linux的网络子系统驱动,负责数据包的发送和接收,而不是面向流设备,因此在Linux系统下网络设备并没有节点。对网络设备的访问是通过socket产生的,而不是通过文件操作如read/write产生。

1.2 设备号和设备节点

Linux系统中的设备都被当做文件来处理,被称作设备文件或者设备节点,所有的设备文件都放在系统/dev目录下,如;

#ls /dev
consol null tty ttyS0 zero ……

在看一下设备文件的详细信息:

ls –al /dev/ttyS0
crw-rw—- 1 root root 4.64 2009-03-27 09:27 /dev/ttyS0

块设备与普通文件不同,信息的第一个字符c表示这个设备是字符型设备,后面的编号4表示该设备的主设备号,64表示该设备的从设备号。主设备号用于标示设备的驱动程序,从设备号表示该设备文件所指定的设备。

1.3 file_operations结构

Linux操作系统采用文件操作的方式对设备进行管理。在用户级层面上,这些调用函数通常是open/close/write/read之类。而在设备驱动程序中,驱动对底层硬件的操作也是通过类似的读写操作实现的,这些函数通常命名为xxx_open,xxx_close,xxx_write等等,xxx一般表示设备名。这样的一组函数通常组织在结构体file_operations中,通过它把系统调用和驱动程序关联起来。注意,file_operations结构体成员函数属于内核空间。用户程序空间并不能直接调用这些函数。整个系统驱动程序调用关系如下图所示:

2. Linux驱动框架

linux2.6引入了platfrom_device的概念,在驱动的注册和管理上带来了变化,使用platform_device描述设备,通过platform_driver描述设备,注册和销毁驱动都有一系列接口函数。

2.1 Platform_device

platform_device是在系统中以独立实体出现的设备,包括传统的基于端口的设备,主机到外设的总线以及大部分片内集成的控制器等。这些设备的共同点是CPU可以通过总线直接对它们进行访问。
描述platform_device的结构体定义如下,是对传统设备的device的封装:

struct flatfrom_device{
const char *name;
u32 id;
struct device dev;
u32 num_resource;
struct resource *resource;
}

可以看出该结构体内包含的信息,包括:设备名,设备id等等。

2.2 platform_driver结构

platform_driver是对device_driver的封装,提供了驱动probe和remove的方法,也提供了与电源管理相关的shutdown和suspend的方法。

struct platform_driver{
  int (*probe) (struct flatfrom_device *);
  int (*remove)( struct flatfrom_device *);
  void (*shutdown)( struct flatfrom_device *);
  int (*suspend)( struct flatfrom_device*);
  ……
}

在定义过上述struct flatfrom_device之后,platform_driver结构体主要完成对device的各种操作。

3. LED驱动编程实例

在明白了Linux驱动程序的工作原理及主要数据结构之后,以一个简单的字符设备,LED驱动设备为例,阐述Linux环境下设备编程的思路和流程。
整体上来讲,驱动程序编程分为两大步:

  • 设备的申请和注册
  • 定义对设备的相关操作

第一步,主要是透过上面提到的flatfrom_device和platform_driver数据结构来实现的,
主要分为以下四部:定义flatfrom_device,注册flatfrom_device,定义platform_driver,注册platform_driver。
第二步,对设备相关操作的定义,就需要根据设备类型以及需要对设备进行的操作,编写相对应的操作函数。

3.1 设备的申请和注册

根据上面的思路,首先需要进行的是设备的申请和注册,主要是借助上面提到的两个结构体进行的。

static struct miscdevice led_miscdev =
{
  .minor = MISC_DYNAMIC_MINOR,
  .name = DEV_NAME,
  .fops = &led_fops,
};
struct platform_device *led_device;
static struct device_driver led_driver = {
  .name = DEV_NAME,
  .owner = THIS_MODULE,
  .bus = &platform_bus_type,
  .probe = led_probe,
  .remove = led_remove,
};

3.2 设备操作函数的实现

对于一个LED驱动程序来讲,我们对其需要的功能应该包括打开,关闭,控制,因此需要有open,close,write,以及ioctl函数,至于read函数则不需要。因而file_operations中的函数应该包括:

static struct file_operations led_fops = {
  .owner = THIS_MODULE,
  .open = led_open,
  .release = led_release,
  .write = led_write,
  .ioctl = led_ioctl,
};

填充了file_operations结构体之后,接下来要进行的就是针对该结构体内的函数进行相应的编程,这里仅仅led_open和led_write函数列写如下。

static int led_open(struct inode *inode, struct file *filp)
{
   __raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));/
  try_module_get(THIS_MODULE);
  printk( KERN_INFO DEV_NAME ” opened!/n”);
  return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buff, size_t count, loff_t *ppos)
{
   int i;
   unsigned char ctrl=0;
  if (count > 1) {
  return -EFBIG;
}
if (down_interruptible(&led_sem))
   return -ERESTARTSYS;
get_user(ctrl, (u8 *)buff);
i = (ctrl-0x30)&0x03;
if(i==0) {
   __raw_writel(_BIT(5), GPIO_P3_OUTP_CLR(GPIO_IOBASE));
} else {
  __raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));
}
up(&led_sem);
return count;
}

完成上面的4个函数之后,我们就可以填充led_fops这个结构体了,下面就是为了应和platform结构体系,所以我们还要编写另外的两个函数probe和remove,实现如下:

static int led_probe(struct device *dev)
{
  int ret;
  printk(KERN_INFO DEV_NAME ” probing…/n”);
  ret = misc_register(&led_miscdev);
  if (ret)
   printk(KERN_ERR “Failed to register miscdev./n”);
  return ret;
}
static int led_remove(struct device *dev)
{
  misc_deregister(&led_miscdev);
  printk(KERN_INFO DEV_NAME ” removed!/n”);
  return 0;
}

驱动都是以模块的形式来实现的,所以就要有模块的入口和出口,就是init和exit。

static int __init led_init(void)
{
  int rc = 0;
  printk(KERN_INFO DEV_NAME ” init…/n”);
   __raw_writel(_BIT(5), GPIO_P3_MUX_CLR(GPIO_IOBASE));
  __raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));
  led_device = platform_device_register_simple(DEV_NAME, -1, NULL, 0);
  if(IS_ERR(led_device)) {
    goto out;
  }
  rc = driver_register(&led_driver);
  if (rc < 0) {
    platform_device_unregister(led_device);
}
sema_init(&led_sem, 1);
out:
return rc;
}

编译之后,在相应的文件夹中生成leddrv.ko文件,即为LED驱动模块。

4. 验证

驱动模块加载如内核之后,需要用户层的应用程序调用才能得到验证。应用层程序如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include “../leddrv.h”
#define DEV_NAME “/dev/led”
int main(int argc, char *argv[])
{
  int fd;
  int dat=0;
  int i;
  fd=open(DEV_NAME, O_RDWR);
  if(fd<0) {
   perror(“can not open device”);
  exit(1);
  }
  printf(“Test write method……/n”);
  for (i=0; i<3; i++) {
  dat = 0;
  write(fd, &dat, 1);
  usleep(300000);
  dat = 1;
  write(fd, &dat, 1);
  usleep(300000);
}
printf(“/nTest write method OK!/n”);
printf(“Test ioctl method start……/n”);
for (i=0; i<3; i++) {
  ioctl(fd, SET_LED_ON, NULL);
  usleep(300000);
  ioctl(fd, SET_LED_OFF, NULL);
  usleep(300000);
  }
printf(“/n”);
printf(“/nTest ioctl method OK!/n”);
close(fd);
return 0;
}

编译之后,生成可执行文件main,在验证时,首先要动态加载LED驱动模块,然后运行该用户程序。

insmod leddrv.ko
./main

可以看到开发板上LED灯连续闪烁六次,证明LED驱动程序正确且加载成功。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值