概述: 字符设备驱动程序: 是按照字符设备要求完成的由操作系统调用的代码。
重点理解以下内容:
 1. 驱动是写给操作系统的代码,它不是直接给用户层程序调用的,而是给系统调用的
 2. 所以驱动要向系统注册。
 3. 注册的时候,要求驱动必须符合一定的规范,否则系统就会不认识。这就是程序架构。
 4. 字符设备驱动对应一个cdev 结构, 需要向系统注册或申请设备号,注册cdev设备,
    完成cdev 设备需要的操作,诸如读,写,ioctl操作等。
 5. 系统下驱动以模块的形式而存在
 6. 用户空间验证,需要先建立设备节点
    例如 mknod /dev/gpio c 126 0  将创建/dev/gpio 节点, 主设备号126,从设备号0
    然后用 echo 'a' >/dev/gpio 查看写入
           cat /dev/gpio       查看读出。

你也可以书些标准的文件访问来测试 /dev/gpio, 这里从略。

这个126 如果是系统申请的,则是动态的,你需要用cat /proc/devices 去查询系统给你的驱动分配了什么设备号。

然后再创建设备结点。

如果启用了sysfs, 则在 /sys/module/gpio 目录下有相应的属性信息描述。

补充:

1。可以用cat /proc/devices | grep <设备名> 查看系统分配(或自己指定)的主设备号。

2. 当mknod 以后,可以用 ll /dev | grep <设备名> 查看设备的主设备号,从设备号

--------------------------------------------------------------------------------
 下面给出一个实例:
 gpioadaptor.c 演示字符设备向系统注册的情景。
 gpio.c 是真正的硬件驱动代码。(这里只是用printk 打印了相关信息)
 在centos 3.10 内核上测试通过
--------------------------------------------------------------------------------

/*======================================================================
  A gpio driver as an example of char device drivers  
  author: hjjdebug
  date: Fri May  9 17:54:33 CST 2014

======================================================================*/

// gpioadaptor.c

#include <linux/module.h>        // module 架构及宏定义
// struct cdev 定义, 字符型设备结构体。标准化结构
#include <linux/cdev.h>            
// struct file 定义, 设备操作是按文件来操作的,所以用到文件指针
#include <linux/fs.h>            
#include <linux/slab.h>            // kmalloc, kfree 声明, 为设备变量分配缓存
#include <asm/uaccess.h>        // copy_to_user , copy_from_user 声明, 数据copy
#include "gpio.h"

#define GPIO_SIZE    0x4            // 4字节做为gpio 缓存, 你可以定义的更大一些。
#define GPIO_MAJOR 254            /*预设的gpio的主设备号*/

// IOCTL 命令定义
#define ALL_MEM_CLEAR 0x1  /*清0全部内存*/
#define SET_MEM_ADDR  0x2   // 设置操作的gpio 地址
#define WRITE_DATA      0x3   // 写io 端口
#define READ_DATA          0x4   // 读io 端口

/*gpio设备结构体, 定义自己使用的变量,并要包含一个cdev 成员,与系统字符设备接口*/
struct gpio_dev                                     
{                                                        
    struct cdev cdev; /*cdev结构体*/                       
    unsigned char mem[GPIO_SIZE]; /*全局内存*/        
    int addr;        // gpio 地址, 操作哪一个gpio
};

// 全局变量定义
static int gpio_major = GPIO_MAJOR;  // 保留申请的主设备号
struct gpio_dev *gpio_devp; /*设备结构体指针*/
/*文件打开函数, 将gpio_devp 传递给file 结构的私有数据*/
int gpio_open(struct inode *inode, struct file *filp)
{
    /*将设备结构体指针赋值给文件私有数据指针*/
    filp->private_data = gpio_devp;
    return 0;
}
/*文件释放函数*/
int gpio_release(struct inode *inode, struct file *filp)
{
    return 0;
}

// ioctl设备控制函数
// 对于简单的gpio. 也许ioctl就已经足够了,而不许要read,write 接口了。
// 这里为了完整,仍然写了read, write,等,完成批量内存操作
long gpio_ioctl(struct file *filp, unsigned
        int cmd, unsigned long arg)
{
    struct gpio_dev *pDev = filp->private_data;/*获得设备结构体指针*/

switch (cmd)
    {
        case ALL_MEM_CLEAR:
            memset(pDev->mem, 0, GPIO_SIZE);      
            printk(KERN_INFO "all gpio is set to zero\n");
            break;
        case SET_MEM_ADDR:
            pDev->addr = arg;
            printk(KERN_INFO "addr is %d\n",pDev->addr);
            break;
        case WRITE_DATA:
            pDev->mem[pDev->addr]=arg;
            GPIOSetData(pDev->addr, arg);
            printk(KERN_INFO "Data Write: %d\n",(int)arg);
            break;
        case READ_DATA:
            arg=pDev->mem[pDev->addr];
            printk(KERN_INFO "Data Read: %d\n",(int)arg);
            break;

default:
            return  - EINVAL;
    }
    return 0;
}

//读函数, 可以一次读多个gpio 的数值,似乎有些多余,但体现read 的能力
static ssize_t gpio_read(struct file *filp, char __user *buf, size_t size,
        loff_t *ppos)
{
    unsigned long offset =  *ppos;
    unsigned int count = size;
    int ret = 0;
    struct gpio_dev *pDev = filp->private_data; /*获得设备结构体指针*/

printk("need size:%ld, offset:%ld\n",size,offset);

/*分析和获取有效的写长度*/
    if (offset > GPIO_SIZE)
    {
        return count ?  - ENXIO: 0;
    }
    else if(offset == GPIO_SIZE)
    {
        return 0;   // 防止测试cat /dev/gpio 时 文件尾出现错误提示
    }
    if (count > GPIO_SIZE - offset)
    {
        count = GPIO_SIZE - offset;
    }

/*内核空间->用户空间*/
    if (!copy_to_user(buf, (void*)(pDev->mem + offset), count))
    {
        *ppos += count;
        printk(KERN_INFO "read %d bytes(s) from %ld addr\n", count, offset);
        ret = count;
    }
    else
    {
        ret =  - EFAULT;
    }

return ret;
}

/*写函数*/
static ssize_t gpio_write(struct file *filp, const char __user *buf,
        size_t size, loff_t *ppos)
{
    unsigned long offset =  *ppos;
    unsigned int count = size;
    int ret = 0;
    int i;
    struct gpio_dev *pDev = filp->private_data; /*获得设备结构体指针*/

/*分析和获取有效的写长度*/
    if (offset >= GPIO_SIZE)
    {
        return count ?  - ENXIO: 0;
    }
    if (count > GPIO_SIZE - offset)
    {
        count = GPIO_SIZE - offset;
    }

/*用户空间->内核空间*/
    if (!copy_from_user(pDev->mem + offset, buf, count))
    {
        *ppos += count;
        for(i=0; i< count; i++)
        {
            GPIOSetData(offset+i, pDev->mem[offset+i]);
        }    
        printk(KERN_INFO "written %d bytes(s) to %ld addr\n", count, offset);
        ret = count;
    }
    else
    {
        ret =  - EFAULT;
    }

return ret;
}

/* seek文件定位函数 */
static loff_t gpio_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret = 0;
    switch (orig)
    {
        case 0:   /*相对文件开始位置偏移*/
            if (offset < 0)
            {
                ret =  - EINVAL;
                break;
            }
            if ((unsigned int)offset > GPIO_SIZE)
            {
                ret =  - EINVAL;
                break;
            }
            filp->f_pos = (unsigned int)offset;
            ret = filp->f_pos;
            break;
        case 1:   /*相对文件当前位置偏移*/
            if ((filp->f_pos + offset) > GPIO_SIZE)
            {
                ret =  - EINVAL;
                break;
            }
            if ((filp->f_pos + offset) < 0)
            {
                ret =  - EINVAL;
                break;
            }
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;
        default:
            ret =  - EINVAL;
            break;
    }
    return ret;
}

/*文件操作结构体*/
static const struct file_operations gpio_fops =
{
    .owner = THIS_MODULE,
    .llseek = gpio_llseek,
    .read = gpio_read,
    .write = gpio_write,
    .compat_ioctl = gpio_ioctl,
    .open = gpio_open,
    .release = gpio_release,
};

/*向系统注册设备*/
static void gpio_setup_cdev(struct gpio_dev *pDev, int index)
{
    int err, devno = MKDEV(gpio_major, index);

cdev_init(&pDev->cdev, &gpio_fops);
    pDev->cdev.owner = THIS_MODULE;
    pDev->cdev.ops = &gpio_fops;
    err = cdev_add(&pDev->cdev, devno, 1);
    if (err)
        printk(KERN_NOTICE "Error %d adding CDEV%d", err, index);
}

/*模块加载函数*/
int gpio_init(void)
{
    int result = -1;
    dev_t devno = MKDEV(gpio_major, 0);

/* 申请设备号*/
    if (gpio_major)
    {
        result = register_chrdev_region(devno, 1, "gpio");
    }
    if (result < 0) // 设备号已被占用等
    {
        /* 动态申请设备号 */
        result = alloc_chrdev_region(&devno, 0, 1, "gpio");
        gpio_major = MAJOR(devno);
    }  
    if (result < 0)
    {
        printk("gpio module register devno failed!, result:%d\n",result);
        return result;
    }

/* 动态申请设备结构体的内存*/
    gpio_devp = kmalloc(sizeof(struct gpio_dev), GFP_KERNEL);
    if (!gpio_devp)    /*申请失败*/
    {
        result =  - ENOMEM;
        goto fail_malloc;
    }
    memset(gpio_devp, 0, sizeof(struct gpio_dev));

gpio_setup_cdev(gpio_devp, 0);
    // 调用硬件层初始化
    GPIOInit(NULL, GPIO_SIZE);
    printk("gpio module installed!\n");
    return 0;

fail_malloc: unregister_chrdev_region(devno, 1);
             return result;
}

/*模块卸载函数*/
void gpio_exit(void)
{
    if(gpio_devp)
    {
        cdev_del(&gpio_devp->cdev);   /*注销cdev*/
        kfree(gpio_devp);     /*释放设备结构体内存*/
        unregister_chrdev_region(MKDEV(gpio_major, 0), 1); /*释放设备号*/
    }
    gpio_devp = 0;
    printk(KERN_INFO "gpio module released!\n");
}

module_init(gpio_init);
module_exit(gpio_exit);

MODULE_AUTHOR("HJJDEBUG");
MODULE_LICENSE("GPL");
--------------------------------------------------------------------------------
// 真正的驱动,虚拟
#include <linux/kernel.h>
#include "gpio.h"
/***************************************************
 * 这里是个虚拟的驱动, 所有的硬件寄存器操作全部忽略。
 ***************************************************/
int GPIOInit(int *pAddr, int size)
{
    printk("all gpio has inited ok!\n");
    return 0;
}
int GPIOSetData(int addr, int data)
{
    printk("gpio addr:%d, data:%d\n", addr, data);
    return 0;
}

--------------------------------------------------------------------------------

Makefile:

ifneq ($(KERNELRELEASE),)

#    obj-m := test.o
    obj-m := m_gpio.o
    m_gpio-y:= gpioadaptor.o gpio.o

else
    PWD=$(shell pwd)
    KVER=$(shell uname -r)
    KDIR=/lib/modules/$(KVER)/build
all:
    make -C $(KDIR) M=$(PWD)
clean:
    rm *.o *.ko modules.* Module.symvers *.mod.c
endif

--------------------------------------------------------------------------------

补充一个字符设备测试程序,注意open 的模式, 如果写成0(RD_ONLY), 写两个字符会出错。

错误号为9, Bad file descriptor. 但写一个字符还是可以的
[root@hjj]# cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

char *data="ab";
int main(int argc, char *argv[])
{
    int fd = open("/dev/udp",O_WRONLY); // 注意文件打开模式
    if(fd== -1)
    {
        printf("error open device.\n");
        exit(1);
    }
    printf("fd:%d\n",fd);
    ssize_t size=write(fd,data,strlen(data));
    if(size==-1)
    {
        printf("errno:%d string:%s\n",errno,strerror(errno));
        perror("reason:");
    }
    else
    {
        printf("size:%d bytes write\n",size);
    }
    close(fd);
    return 0;

}

一个驱动可以被多次打开,会返回不同的fd, 不同的fd, 会对应不同的filp.从而可以存储各自的数据

这样依据fd, 就可以操作不同的数据。

转字符驱动实例gpio的更多相关文章

  1. Linux 驱动学习笔记05--字符驱动实例,实现一个共享内存设备的驱动

    断断续续学驱动,好不容易有空,做了段字符驱动的例子.主要还是跟书上学习在此记录下来,以后说不定能回过头来温故知新. 首先上驱动源码 gmem.c: /************************* ...

  2. linux驱动初探之字符驱动

    关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...

  3. 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl

    基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl 0. 导语 在嵌入式的道路上寻寻觅觅很久,进入嵌入式这个行业也有几年的时间了,从2011年后 ...

  4. 基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ

    基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ 0. 导语 学习进入到了下一个阶段,还是以AD9833为例,这次学习是向设备申请中断,实现触发,在未来很多场景,比如做用 ...

  5. 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read、write

    基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read.write 0. 导语 在上一篇博客里面,基于OMAPL138的字符驱动_GPIO驱动AD9833(一)之 ...

  6. Linux内核驱动之GPIO子系统API接口概述

    1.前言 在嵌入式Linux开发中,对嵌入式SoC中的GPIO进行控制非常重要,Linux内核中提供了GPIO子系统,驱动开发者在驱动代码中使用GPIO子系统提供的API函数,便可以达到对GPIO控制 ...

  7. 旧接口注册LED字符驱动设备(静态映射)

    #include <linux/init.h> // __init __exit #include <linux/module.h> // module_init module ...

  8. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  9. liunx中字符驱动编写的简单模板

    下面是关于字符驱动两个程序,主要是说明驱动编写的思想,理解驱动是怎么一步一步被实现的. 驱动的第一个实现程序,是相对于裸机编程的,主要是体会一下驱动编程思想: cdev.h: 所包含的头文件 #ifn ...

随机推荐

  1. python编写telnet登陆出现TypeError:'str' does not support the buffer interface

    python3支持byte类型,python2不支持.在python3中,telnet客户端向远程服务器发送的str要转化成byte,从服务器传过来的byte要转换成str,但是在python2不清楚 ...

  2. Oracle数据库作业-6 查询成绩比该课程平均成绩低的同学的成绩表

    33. 查询成绩比该课程平均成绩低的同学的成绩表. select * from score a where a.degree between 0 and( select avg(degree) fro ...

  3. Linux之用户管理

    1.添加普通用户 [root@server ~]# useradd chenjiafa   //添加一个名为chenjiafa的用户[root@server ~]# passwd chenjiafa  ...

  4. 开启Microsoft SQL Management时,如果出现"未能加载包

    Ms Sql server 2005在开启Microsoft SQL Management时,如果出现"未能加载包“Microsoft SQL Management Studio Packa ...

  5. EL表达式隐含对象

    EL表达式语言中定义了11个隐含对象,使用这些隐含对象可以很方便地获取web开发中的一些常见对象,并读取这些对象的数据. 语法:${隐式对象名称}  :获得对象的引用 <%@ page lang ...

  6. JOSN传字符串方法

    #region 提示信息 /// <summary> /// 操作失败(无参数) /// </summary> /// <returns></returns& ...

  7. Ajax请求ashx 返回 json 格式数据常见问题

    问题:ashx 返回的字符串json格式,在前台ajax自动解析失败. 问题分析:经过排查,发现是拼接json时出现” ’  “单引号,jquery无法解析,用” “ “双引号才可以.例如: stri ...

  8. 【C语言】02-函数

    一.函数的分类 前面已经说过,C语言中的函数就是面向对象中的"方法",C语言的函数可以大概分为3类: 1.主函数,也就是main函数.每个程序中只能有一个.也必须有一个主函数.无论 ...

  9. Access时间日期比较查询的方法总结

    Access日期时间比较查询语句困扰过很多网友,种豆网整理了一下Access日期比较查询的几种方法,假定数据表明为TblName,日期/时间字段名为FDate(这里不能讲FDate设置为字符串,否则比 ...

  10. 20141109--SQL 练习题-1

    create database xinxiku go use xinxiku go create table Student ( Sno ) primary key, Sname ) not null ...