Chinaunix首页 | 论坛 | 博客
  • 博客访问: 275155
  • 博文数量: 89
  • 博客积分: 1380
  • 博客等级: 中尉
  • 技术积分: 705
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-10 11:04
文章分类

全部博文(89)

文章存档

2014年(4)

2011年(1)

2010年(42)

2009年(42)

我的朋友

分类: 嵌入式

2010-06-02 16:15:04

uClinux上开发驱动程序的时候,可以将驱动程序静态编译进内核,也可以以模块的方式动态加载。由于我所用的开发板是S3C44B0X的,只能把驱动程序静态编译进内核。当然模块方式的驱动程序也可以静态编译进内核,这个以后我们再讨论,今天我们主要介绍一下驱动程序的结构和把编写好的驱动程序编译进内核的过程。
    首先我们以GPIO的驱动来介绍一下驱动程序编译进内核的过程。
    1,
  uClinux-dist/linux2.4.x/drivers/char 目录下编写驱动程序gpio.c。
    2,
修改uClinux-dist/linux2.4.x/dirvers/char/Makefile 在适当的位置添加一行:     obj-$(CONFIG_GPIO) += gpio.o
    3,
修改 uClinux-dist/linux2.4.x/dirvers/char/Config.in, 在适当的位置添加一行:
bool ‘gpio device’ CONFIG_GPIO //
make menuconfig 时可以进行配置。(注意这一句的单引号,最好自已输入,不要复制,复制的单引号不一样)
    4,
修改 uClinux-dist/linux2.4.x/dirvers/char/mem.c 在适当的位置添加:
#ifdef CONFIG_GPIO

extern int gpio_init(void);

#endif

chr_dev_init()函数中添加:
#ifdef CONFIG_GPIO

gpio_init();//
在内核启动的时候对GPIO驱动程序进行注册
#endif
    5,
修改uClinux-dist/vendors/Samsung/4510b/Makefile,建立设备节点,在DEVICE部分添加如下内容:gpio,c,126,0
    6,
UCLINUX系统在S3C44B0X上的移植》的第8步选择character devices回车选中gpio device,进行编译,下载内核,运行之后,输入cat /proc/devices可以看到gpio 126这一项。
    下面我们简单介绍一下驱动程序的结构。
    1,模块初始化函数init_module(),该函数主要完成对设备的注册:
int result;
result = register_chrdev(MAJOR_NR, DEVICE_NAME, &gpio_fops)
;
    2,模块卸载函数clearup_module(),该函数主要完成对设备的卸载:
unregister_chrdev(MAJOR_NR, DEVICE_NAME);
    3,设备驱动程序的file operations结构。File operations结构是一个定义在
中的数据结构,内核通过它访问驱动程序:
structfile operations{
int(*seek)(structinode* ,structfile*,offt,int);
int(*read)(structinode* ,structfile*,char,int);
int(*write)(structinode* ,structfile*,off t,int);
int(*readdir)(structinode* ,structfile*,structdirent* ,int);
int(*select)(structinode* ,structfile*,int,select table*);
int(*ioctl)(structinode* ,structfile*,unsinedint,unsignedlong);
int(*mmap)(structinode* ,structfile*,structvm area struct*);
int(*open)(structinode* ,structfile*);
int(*release)(structinode* ,structfile*);
int(*fsync)(structinode* ,structfile*);
int(*fasync)(structinode* ,structfile*,int);
int(*check media change)(structinode*,structfile*);
int(*revalidate)(dev tdev);

    下面是一个网友写的gpio的驱动程序和应用程序,供大家学习:
******************************************************************************

 熟悉ioctl

在编写ioctl代码之前,需要选择对应不同命令的编号。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一,这种错误匹配并不是不会发生,程序可能发现自己正在试图对FIFOaudio等这类非串行设备输入流修改波特率,如果每一个ioctl命令都是唯一的,应用程序进行这种操作时就会得到一个EINVAL错误,而不是无意间成功地完成了意想不到的操作。

   要按Linux内核的约定方法为驱动程序选择ioctl编号,应该首先看看include/asm/ioctl.hDoucumention/ioctl-number.txt这两个文件。头文件定义了要使用的位字段:类型(幻数)、序数、传送方向以及参数大小等。ioctl-number.txt文件中罗列了内核所使用的幻数,选择自己的幻数要避免和内核冲突。以下是对include/asm/ioctl.h中定义的宏的注释:

#define         _IOC_NRBITS          8                               //序数(number)字段的字位宽度,8bits

#define         _IOC_TYPEBITS      8                               //幻数(type)字段的字位宽度,8bits

#define         _IOC_SIZEBITS       14                              //大小(size)字段的字位宽度,14bits

#define         _IOC_DIRBITS         2                               //方向(direction)字段的字位宽度,2bits

 

#define         _IOC_NRMASK        ((1 << _IOC_NRBITS)-1)    //序数字段的掩码,0x000000FF

#define         _IOC_TYPEMASK   ((1 << _IOC_TYPEBITS)-1)  //幻数字段的掩码,0x000000FF

#define         _IOC_SIZEMASK     ((1 << _IOC_SIZEBITS)-1)   //大小字段的掩码,0x00003FFF

#define         _IOC_DIRMASK      ((1 << _IOC_DIRBITS)-1)    //方向字段的掩码,0x00000003

 

#define        _IOC_NRSHIFT       0                                                         //序数字段在整个字段中的位移,0

#define        _IOC_TYPESHIFT   (_IOC_NRSHIFT+_IOC_NRBITS)         //幻数字段的位移,8

#define        _IOC_SIZESHIFT    (_IOC_TYPESHIFT+_IOC_TYPEBITS)  //大小字段的位移,16

#define        _IOC_DIRSHIFT      (_IOC_SIZESHIFT+_IOC_SIZEBITS)    //方向字段的位移,30

 

/*

 * Direction bits.

 */

#define _IOC_NONE     0U     //没有数据传输

#define _IOC_WRITE   1U     //向设备写入数据,驱动程序必须从用户空间读入数据

#define _IOC_READ     2U     //从设备中读取数据,驱动程序必须向用户空间写入数据

 

 

/*

*_IOC 宏将dirtypenrsize四个参数组合成一个cmd参数,如下图:

*

*/

 

#define _IOC(dir,type,nr,size) \

       (((dir)  << _IOC_DIRSHIFT) | \

        ((type) << _IOC_TYPESHIFT) | \

        ((nr)   << _IOC_NRSHIFT) | \

        ((size) << _IOC_SIZESHIFT))

 

/*

* used to create numbers 

*/

//构造无参数的命令编号

#define _IO(type,nr)             _IOC(_IOC_NONE,(type),(nr),0) 

//构造从驱动程序中读取数据的命令编号

 

#define _IOR(type,nr,size)     _IOC(_IOC_READ,(type),(nr),sizeof(size)) 

//用于向驱动程序写入数据命令

#define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))

//用于双向传输

#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

 

/* 

*used to decode ioctl numbers..

 */

//从命令参数中解析出数据方向,即写进还是读出

#define _IOC_DIR(nr)          (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)

//从命令参数中解析出幻数type

#define _IOC_TYPE(nr)              (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)

//从命令参数中解析出序数number

#define _IOC_NR(nr)           (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)

//从命令参数中解析出用户数据大小

#define _IOC_SIZE(nr)         (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

 

/* ...and for the drivers/sound files... */

 

#define IOC_IN            (_IOC_WRITE << _IOC_DIRSHIFT)

#define IOC_OUT         (_IOC_READ << _IOC_DIRSHIFT)

#define IOC_INOUT     ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)

#define IOCSIZE_MASK      (_IOC_SIZEMASK << _IOC_SIZESHIFT)

#define IOCSIZE_SHIFT      (_IOC_SIZESHIFT)

 
 

********************************************************************************
//gpio.h

#ifndef __GPIO_H
#define __GPIO_H
#include<linux/ioctl.h>

#define GPIO_PIN_LOW 0
#define GPIO_PIN_HIGH 1
#define GPIO_PIN_ERR (~0)
#define GPIO_IOC_MAGIC 0xd0

#define GPIO_SET_PIN _IO(GPIO_IOC_MAGIC,0)
#define GPIO_SET_ALL_PIN _IO(GPIO_IOC_MAGIC,1)
#define GPIO_CLR_PIN _IO(GPIO_IOC_MAGIC,2)
#define GPIO_CLR_ALL_PIN _IO(GPIO_IOC_MAGIC,3)

#define GPIO_SET_PIN_OUT _IO(GPIO_IOC_MAGIC,4)
#define GPIO_SET_PIN_IN _IO(GPIO_IOC_MAGIC,5)
#define GPIO_MAXNR 6
#endif
********************************************************************************
//gpio.c

#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#include<linux/module.h>
#include<linux/sched.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include"gpio.h"

static int gpio_open(struct inode *inode,struct file *filp);
static int gpio_release(struct inode *inode,struct file *filp);
static int gpio_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,
                     unsigned long param);

#define GPIO_MAJOR_NR 126
//主设备号

#define MAJOR_NR major
//主设备号

#define DEVICE_NAME "gpio"
//设备名称


#define MAX_PORT 1                /* 1 devices */
#define GPIO_CONC_ADDR 0x01d20010
//PortC的配置寄存器地址

#define GPIO_DATA_ADDR 0x01d20014
//PortC的数据寄存器地址

static int major=GPIO_MAJOR_NR;
MODULE_LICENSE("GPL");

static struct file_operations gpio_fops=
  {
    
//owner:THIS_MODULE,

    ioctl:gpio_ioctl,
    open:gpio_open,
    release:gpio_release
  };
int __init gpio_init(void)
{
  int result;
  result = register_chrdev(MAJOR_NR, DEVICE_NAME, &gpio_fops);
  if (result < 0) {
    printk(KERN_ERR DEVICE_NAME ":Unable to get major %d\n", MAJOR_NR);
    return result;
  }
  if (MAJOR_NR == 0) {
    MAJOR_NR = result;
  }
  printk(KERN_INFO DEVICE_NAME ": init OK\n");
  return 0;
    
}

void __exit gpio_cleanup(void)
{
  unregister_chrdev(MAJOR_NR, DEVICE_NAME);
}

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

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

static int gpio_ioctl(struct inode *inode, struct file *filp,
                     unsigned int cmd, unsigned long arg)
{
  int num;
  volatile u32 *Regconc,*Regdata;
  num = MINOR(inode->i_rdev);
  if (num >= MAX_PORT) {
    return -ENODEV;
  }
    
  if (_IOC_TYPE(cmd) != GPIO_IOC_MAGIC) {
    return -ENOTTY;
  }

  if (_IOC_NR(cmd) >= GPIO_MAXNR) {
    return -ENOTTY;
  }

  Regconc = (volatile u32 *)(GPIO_CONC_ADDR);
  Regdata = (volatile u32 *)(GPIO_DATA_ADDR);
//需要查看PortC的PCONC和PDATC寄存器的相关参数来对I/O引脚进行配置

  switch(cmd){
  case GPIO_SET_PIN:
//设置某个I/O引脚为为高电平

    if (arg < 16) {
     *Regdata |= 1u << arg;
    }
    break;
  case GPIO_SET_ALL_PIN:
//设置多个I/O引脚为高电平

    *Regdata |= arg;
    break;
  case GPIO_CLR_PIN:
// 设置某个I/O引脚为为低电平

    if (arg < 16) {
     *Regdata &= ~(1u << arg);
    }
    break;
  case GPIO_CLR_ALL_PIN:
//设置多个I/O引脚为低电平

    *Regdata &= ~arg;
    break;
  case GPIO_SET_PIN_OUT:
//设置某个I/O引脚为输出引脚

    if (arg < 16) {
     *Regconc &= ~(1u << (2*arg + 1));
     *Regconc |= 1u << (2*arg);    
    }
    break;
  case GPIO_SET_PIN_IN:
//设置某个I/O引脚为输入引脚

    if (arg < 32) {
     *Regconc &= ~(1u << (2*arg));
     *Regconc &= ~(1u << (2*arg + 1));    
    }
    break;
  }
  return 0;
}
*******************************************************************************
//test.c PortC驱动程序的测试程序。循环点亮Led0,Led1,Led2 10次

#include<stdio.h>
#include<fcntl.h>
#include"gpio.h"
int main(int argc, char * argv[])
{
    int fd;
    int loop;
    fd = open("/dev/gpio",O_RDONLY);
    if (fd < 0) {
     printf("open error\n");
     return 0;
    }
    ioctl(fd,GPIO_SET_PIN_OUT,1);
    ioctl(fd,GPIO_SET_PIN_OUT,2);
    ioctl(fd,GPIO_SET_PIN_OUT,3);
    loop = 0;
    while (loop < 10) {
        ioctl(fd,GPIO_CLR_PIN,3);        
        ioctl(fd,GPIO_SET_PIN,1);
        sleep(1);
        ioctl(fd,GPIO_CLR_PIN,1);
        ioctl(fd,GPIO_SET_PIN,2);        
        sleep(1);
        ioctl(fd,GPIO_CLR_PIN,2);
        ioctl(fd,GPIO_SET_PIN,3);
        sleep(1);
        loop++;
    }

    close(fd);
    return 0;
}
************************************************************************


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