概述: 字符设备驱动程序: 是按照字符设备要求完成的由操作系统调用的代码。
重点理解以下内容:
 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. mysql_DML_update

    update  表名  set  字段=XX where....;(记得加条件不安全改了) 多个字段: update  表名  set  字段1=XX,字段2= where....;(记得加条件不安全 ...

  2. 20145102 《Java程序设计》第5周学习总结

    20145102 <Java程序设计>第5周学习总结 教材学习内容总结 数组在内存中会是连续的线性空间,根据索引随机取回时速度快,如果操作上有这类需求时,像是排序,就可以使用;ArrayL ...

  3. 管理后台-第一部分:Creating custom sections in Umbraco 7 - Part 1(翻译文档)

    在Umbraco上每个部分都可以被称为一个应用程序,所以这些部分和应用程序基本上是一样的.我们首先要做的事情是需要创建应用程序.在这个例子中,我不会去摆弄xml文件或是数据库——我将使用类来创建我的内 ...

  4. Umbraco(2) - Creating Your First Template and Content Node(翻译文档)

    创建(编辑)你的第一个模板(Template) 展开 Settings > Templates文件夹 - 然后你应该看到子节点名为"Homepage" - 这是我们在创建Do ...

  5. CF Soldier and Cards (模拟)

    Soldier and Cards time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...

  6. Atom 下载、安装

    Atom工具的使用 由github发布的前端开发工具 非常强大的开发工具 官网下载地址:https://atom.io Atom的插件和主题安装和配置

  7. Student

    using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace PersonD ...

  8. The Art of Computer Programming

    <计算机程序设计艺术>即<The Art of Computer Programming>是计算机领域里颠峰级的里程碑,加上国外人士对它的推崇,所以提起它的大名简直就象法律书籍 ...

  9. 图解win7中IIS7.0的安装及配置ASP环境

    控制面板中“程序”的位置 “程序”中“打开或关闭Windows功能”的位置 如图,安装IIS7时需要选择要使用的功能模块 IIS7安装完成之后可以在开始菜单的所有程序中看到“管理工具”,其中有一个“I ...

  10. Professional iOS Network Programming Connecting the Enterprise to the iPhone and iPad

    Book Description Learn to develop iPhone and iPad applications for networked enterprise environments ...