Chinaunix首页 | 论坛 | 博客
  • 博客访问: 766944
  • 博文数量: 370
  • 博客积分: 2334
  • 博客等级: 大尉
  • 技术积分: 3222
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-06 16:56
文章分类

全部博文(370)

文章存档

2013年(2)

2012年(368)

分类:

2012-05-27 11:59:37

原文地址:Linux 驱动学习 ----LED驱动 作者:frankzfz

这是我的第一个驱动程序,控制LED灯的亮灭
先介绍一下我的开发环境是,一个mini2440开发板,使用的是Linux2.6.32的内核。本开发板上使用引脚GPB5--GPB8外接4个LED。要点亮一个LED灯,首先保证(3)引脚功能设置为输出(2)要点亮LED,令引脚输出0(3)要熄灭LED,令引脚输出1。
首先这是一个字符设备的驱动程序,字符设备驱动程序中最重要的一个结构体是下面的这个:在Linux内核的include/linux/fs.h中。

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,

unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t,

unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t,

unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};

当应用程序调用open函数打开某个设备时,设备驱动程序的file_operations结构中的相应的open成员就会被调用;其它相应的read,write,ioctl等函数读写、控制设备时,驱动的程序中的相应的read,write,ioctl函数会被调用。从这个角度来说,编写字符设备驱动程序就是为具体硬件的file_operations结构编写各个函数。
那么,当应用程序通过open,read,write等系统调用访问某个设备文件时,Linux系统怎么知道去调用哪个驱动程序的file_operations结构中的open,read,write等成员呢?
(1) 设备文件中有主/次设备号
设备文件分为字符设备、块设备,比如PC机上的串口设备属于字符设备,硬盘属于块设备。

#:/dev$ ls /dev/ttyS0 /dev/sda -l
brw-rw---- 1 root disk 8, 0 2010-06-08 08:40 /dev/sda
crw-rw---- 1 root dialout 4, 64 2010-06-08 11:34 /dev/ttyS0

"brw-rw----" 中的"b" 表示/dev/sda 是个块设备,它的主设备号为8,次设备为0;"crw-rw---- "中的"C"表示/dev/ttyS0 是个字符设备,它的主设备号为4,次设备号为64.
(2)模块初始化时,将主设备号与file_operations结构一起向内核注册。
驱动程序有一个初始化函数,在安装驱动程序时会调用它。在初始化函数中,会将驱动程序的
file_operations结构连同其主设备号一起向内核进行注册。对于设备使用如下的函数注册
int register_chrdev(unsigned int major,const char * name,struct file_operations *fops);
这样,应用程序操作设备文件时,Linux系统就会根据设备文件的类型(是字符设备还是块设备)、主设备号找到在内核中注册的file_operations结构,次设备号供驱动程序驱动自身来分辨它是同类设备中的第几个。
编写字符驱动程序的过程大概如下。
(1) 编写驱动程序初始化函数。
进行必要的初始化,包括硬件初始化,向内核注册驱动程序等
(2)构造file_operations结构中要用到的各个成员函数
在实际的驱动程序中当然比这两个步骤要复杂,还有其它的高级的技术,比如中断,select机制、fasync异步通知机制。
下面介绍一下主要的函数:

static int __init s3c2440_leds_init(void)
{
  int ret;
/* 注册字符设备驱动程序
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c2440_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0,表示由内核自动分配主设备号
     */

  ret = register_chrdev(LED_MAJOR,DEVICE_NAME,&s3c2440_leds_fops);
  if(ret<0)
  { 
    printk(DEVICE_NAME "can't register major number\n");
   return ret;
 }
printk(DEVICE_NAME "initialized\n");
return 0;
}
/*
 * 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数
 */

static void __exit s3c2440_leds_exit(void)
{
/* 卸载驱动程序 */
  unregister_chrdev(LED_MAJOR,DEVICE_NAME); 
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("Frankzfz"); // 驱动程序的作者
MODULE_DESCRIPTION("S3C2440 LED Driver");// 一些描述信息
MODULE_LICENSE("GPL");

执行“insmod s3c2440_leds.ko”命令时就会调用这个函数register_chrdev 函数向内核注册驱动程序:将设备号LED_MAJOR与file_operations结构s3c2440_leds_fops联系起来,以后应用程序操作主设备号为LED_MAJOR的设备文件时,比如open,read,write、ioctl,s3c2440_leds_fops中的相应成员函数就会被调用。但是,在s3c2440_leds_fops中并不会全部实现这些函数s3c2440_leds_init s3c2440_leds_exit函数前的"__init"、"__exit"只有在将驱动程序静态链接进内核时才有意义。前者表示s3c2440_leds_init函数的代码被放在".init.text"段中,这个段在使用一次后被释放;后者表示s3c2440_leds_exit函数的代码被放在".exit.data"段中。
下面这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数

static struct file_operations s3c2440_leds_fops =
  {
    .owner = THIS_MODULE,/* 这是一个宏,推向编译模块时自动创建的__this_module变量 在文件
include/linux/module.h*/

    .open = s3c2440_leds_open,//用来初始化LED所用的GPIO引脚
    .ioctl = s3c2440_leds_ioctl,//用来根据用户传入的参数设置用户传入的参数设置GPIO的输出电平
  };

/* 应用程序对设备文件/dev/leds执行open(...)时,
 * 就会调用s3c2440_leds_open函数
 */

static int s3c2440_leds_open(struct inode *inode,struct file *file)
{
  int i;
  for(i=0;i<4;i++)
  {
 // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能

    s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
  }
  return 0;
}

不在模块的初始化函数中进行这些设置的原因是:虽然加载了模块,但是这个模块却不一定会被用到,就是说这些引脚不一定用于这些用途,它们可能在其他模块中另作他用。所以,在使用时才设置它,我们把对引脚的初始化放在open操作中。s3c2410_gpio_cfgpin函数是内核里实现的,它被用来选择引脚的功能,其实现原理就是设置GPIO的控制寄存器。

static int s3c2440_leds_ioctl(struct inode *inode,struct file *file,unsigned int

cmd,unsigned long arg)
{
  if(arg>4)
  {
    return -EINVAL;
  }
  switch(cmd)
  {
  case IOCTL_LED_ON:
// 设置指定引脚的输出电平为0
   s3c2410_gpio_setpin(led_table[arg],0);
    return 0;
  case IOCTL_LED_OFF:
// 设置指定引脚的输出电平为1
    s3c2410_gpio_setpin(led_table[arg],1);
    return 0;
  default:
    return -EINVAL;
  }
}

应用程序执行系统调用ioclt(fd,cmd,arg)时,s3c2440_leds_ioctl函数将被调用。根据传入的cmd,arg参数调用s3c2410_gpio_setpin函数,来设置引脚的输出电平:输出0时点高LED,输出1时熄灭LED。s3c2410_gpio_setpin也是内核中实现的,它通过GPIO的数据寄存器来设置输出电平。应用程序执行open、ioctl等系统调用,它们的参数和驱动程序中相应函数的参数不是一一对应的,其中经过了内核文件系统层的转换。
系统调用函数原型如下:

int open(const char *pathname,int flags)
int ioctl(int d,int request,....)
ssize_t
read(int fd,void *buf,size_t count)
ssize_t
write(int fd,const void *buf,size_t count);

file_operations结构中的成员如下:

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);

可以看到,这些参数有很大一部分是相似的
(1) 系统调用 open传入的参数已经被内核文件系统层处理了,在驱动程序中看不出原来的参数了
(2)系统调用ioclt的参数个数可变,一般最多传入3个:后面两个参数与file_operations结构中ioctl成员的后面两个参数对应
(3) 系统调用read传入的buf、count参数,对应file_operation结构中read成员的buf、count参数.而参数offp表示用户在文件中进行存取操作的位置,当执行完写操作后由驱动程序设置
(4)系统调用write与file_operations结构中write成员的参数关系。
驱动程序的编译
把s3c2440_leds.c文件放入内核的drivers/char子目录下,在drivers/char/Makefile中增加下面一行:
obj-m +=s3c2440_leds.o
然后在内核的根目录下执行"make modules",就可以生成模块drivers/char/s3c2440_leds.ko.把它放到目标板根文件系统的/lib/modules/2.6.32目录下,就可以使用如下命令加载了

[root@Frankzfz 2.6.32.2]$insmod ./2440_leds.ko
ledsinitialized

驱动程序的测试程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1

void usage(char * exename)
{
  printf("Usage:\n");
  printf("%s \n",exename);
  printf("led_no = 1 ,2,3 or 4\n");
}
int main(int argc,char ** argv)
{
  unsigned int led_no;
  int fd = -1;
  if(argc !=3)
    goto err;
  fd = open("/dev/leds",0);//打开设备

  if(fd <0)
  {
    printf("Can't open /dev/leds \n");
    return -1;
  }
  led_no = strtoul (argv[1],0,0) -1; //操作哪个LED灯

  if(led_no > 3)
    goto err;
  if(!strcmp(argv[2],"on"))
  {
    ioctl(fd,IOCTL_LED_ON,led_no); //点亮它

  }
  else if(!strcmp(argv[2],"off"))
  {
    ioctl(fd,IOCTL_LED_OFF,led_no); //熄灭它

  }
  else
  {
    goto err;
  }
  close(fd);
  return 0;
 err:
  if(fd>0)
    close(fd);
  usage(argv[0]);
        return -1;
        }

把编译生成的可执行程序led_test放入目标板根文件系统/usr/bin目录下后。然后,在目标板根文件系统中如下建立设备文件

mknod /dev/leds c 231 0

现在就可以参照led_test的使用说明操作LED了,以下两条命令点亮熄灭LED

led_test 1 on
led_test 1 off

下面是完整的LED驱动的程序:

#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>

#define DEVICE_NAME "leds" // 加载模式后,执行”cat /proc/devices”命令看到的设备名称 也是在mknod /dev/leds 用到的名字
#define LED_MAJOR 231 //主设备号
/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
/* 用来指定LED所用的GPIO引脚 arch/arm/mach-s3c2410/include/mach/gpio-nrs.h
#define S3C2410_GPB(_nr)    (S3C2410_GPIO_A_START + (_nr))*/

static unsigned long led_table [] =
  {
    S3C2410_GPB(5),
    S3C2410_GPB(6),
    S3C2410_GPB(7),
    S3C2410_GPB(8),
  };
/* 用来指定GPIO引脚的功能:输出 arch/arm/mach-s3c2410/include/mach/gpio-nrs.h
#define S3C2410_GPIO_OUTPUT (0xFFFFFFF1)*/

static unsigned int led_cfg_table [] =
  {
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
  };
/* 应用程序对设备文件/dev/leds执行open(...)时,
 * 就会调用s3c2440_leds_open函数
 */

static int s3c2440_leds_open(struct inode *inode,struct file *file)
{
  int i;
  for(i=0;i<4;i++)
  {
 // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能
    s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
  }
  return 0;
}
/* 应用程序对设备文件/dev/leds执行ioclt(...)时,
 * 就会调用s3c2440_leds_ioctl函数
 */

static int s3c2440_leds_ioctl(struct inode *inode,struct file *file,unsigned int
cmd,unsigned long arg)
{
  if(arg>4)
  {
    return -EINVAL;
  }
  switch(cmd)
  {
  case IOCTL_LED_ON:
// 设置指定引脚的输出电平为0
   s3c2410_gpio_setpin(led_table[arg],0);
    return 0;
  case IOCTL_LED_OFF:
// 设置指定引脚的输出电平为1
    s3c2410_gpio_setpin(led_table[arg],1);
    return 0;
  default:
    return -EINVAL;
  }
}
/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */

static struct file_operations s3c2440_leds_fops =
  {
    .owner = THIS_MODULE,/* 这是一个宏,推向编译模块时自动创建的__this_module变量 在文件include/linux/module.h*/
    .open = s3c2440_leds_open,//用来初始化LED所用的GPIO引脚

    .ioctl = s3c2440_leds_ioctl,//用来根据用户传入的参数设置用户传入的参数设置GPIO的输出电平

  };
/*
 * 执行“insmod s3c2440_leds.ko”命令时就会调用这个函数
 */

static int __init s3c2440_leds_init(void)
{
  int ret;
/* 注册字符设备驱动程序
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c2440_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0,表示由内核自动分配主设备号
     */

  ret = register_chrdev(LED_MAJOR,DEVICE_NAME,&s3c2440_leds_fops);
  if(ret<0)
  {   
    printk(DEVICE_NAME "can't register major number\n");
   return ret;
 }
printk(DEVICE_NAME "initialized\n");
return 0;
}
/*
 * 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数
 */

static void __exit s3c2440_leds_exit(void)
{
/* 卸载驱动程序 */
  unregister_chrdev(LED_MAJOR,DEVICE_NAME);  
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("Frankzfz"); // 驱动程序的作者
MODULE_DESCRIPTION("S3C2440 LED Driver");// 一些描述信息
MODULE_LICENSE("GPL");


阅读(1670) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~