1. 程式人生 > >字元型裝置驅動程式--gpio 驅動例項

字元型裝置驅動程式--gpio 驅動例項


概述: 字元裝置驅動程式: 是按照字元裝置要求完成的由作業系統呼叫的程式碼。
重點理解以下內容:
 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. 但寫一個字元還是可以的
[[email protected]]# 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, 就可以操作不同的資料。

補充:

也可以自動建立建立裝置節點,省去了手工建立裝置節點的過程,具體程式碼請搜尋網際網路