x210項目重新回顧之十內核-簡單字符驅動程序

源代碼:https://github.com/jimingkang/news5pv210/tree/master/study/zhulaoshi/code/linux_driver/3.CharDevSenior/5.3.7

A)對比新老接口

(1)老接口:register_chrdev
(2)新接口:register_chrdev_region/alloc_chrdev_region

                     + cdev_alloc/cdev_init/  cdev_add                

                      +class_create/device create    //藉助udev生成/dev/test111設備文件

 

B)/etc/init.d/rcS裏面添加mdev(爲udev的嵌入式版本)支持

mount -a
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s

 

 

C)驅動文件module_test.c如下:

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

 

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>        // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
#include <linux/device.h>


//#define MYMAJOR        200
#define MYCNT        1
#define MYNAME        "testchar"

#define GPJ0CON        S5PV210_GPJ0CON
#define GPJ0DAT        S5PV210_GPJ0DAT

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

#define GPJ0CON_PA    0xe0200240
#define GPJ0DAT_PA     0xe0200244

unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;


//int mymajor;
static dev_t mydev;
//static struct cdev test_cdev;
static struct cdev *pcdev;
static struct class *test_class;

char kbuf[100];            // 內核空間的buf


static int test_chrdev_open(struct inode *inode, struct file *file)
{
    // 這個函數中真正應該放置的是打開這個設備的硬件操作代碼部分
    // 但是現在暫時我們寫不了這麼多,所以用一個printk打印個信息來做代表。
    printk(KERN_INFO "test_chrdev_open\n");
    
    rGPJ0CON = 0x11111111;
    rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));        // 亮
    
    return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    
    rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    
    return 0;
}

ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    
    printk(KERN_INFO "test_chrdev_read\n");
    
    ret = copy_to_user(ubuf, kbuf, count);
    if (ret)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success..\n");
    
    
    return 0;
}

// 寫函數的本質就是將應用層傳遞過來的數據先複製到內核中,然後將之以正確的方式寫入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
    size_t count, loff_t *ppos)
{
    int ret = -1;
    
    printk(KERN_INFO "test_chrdev_write\n");

    // 使用該函數將應用層傳過來的ubuf中的內容拷貝到驅動空間中的一個buf中
    //memcpy(kbuf, ubuf);        // 不行,因爲2個不在一個地址空間中
    memset(kbuf, 0, sizeof(kbuf));
    ret = copy_from_user(kbuf, ubuf, count);
    if (ret)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success..\n");
    
    if (kbuf[0] == '1')
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
    }
    else if (kbuf[0] == '0')
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    }
    
    
/*
    // 真正的驅動中,數據從應用層複製到驅動中後,我們就要根據這個數據
    // 去寫硬件完成硬件的操作。所以這下面就應該是操作硬件的代碼
    if (!strcmp(kbuf, "on"))
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
    }
    else if (!strcmp(kbuf, "off"))
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    }
*/

    
    
    return 0;
}


// 自定義一個file_operations結構體變量,並且去填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,                // 慣例,直接寫即可
    
    .open        = test_chrdev_open,            // 將來應用open打開這個設備時實際調用的
    .release    = test_chrdev_release,        // 就是這個.open對應的函數
    .write         = test_chrdev_write,
    .read        = test_chrdev_read,
};


// 模塊安裝函數
static int __init chrdev_init(void)
{    
    int retval;
    
    printk(KERN_INFO "chrdev_init helloworld init\n");

    // 使用新的cdev接口來註冊字符設備驅動
    // 新的接口註冊字符設備驅動需要2步
    
    // 第1步:分配主次設備號
    retval = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME);
    if (retval < 0) 
    {
        printk(KERN_ERR "Unable to alloc minors for %s\n", MYNAME);
        goto flag1;
    }
    printk(KERN_INFO "alloc_chrdev_region success\n");
    printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev), MINOR(mydev));
    
    
    // 第2步:註冊字符設備驅動
    pcdev = cdev_alloc();            // 給pcdev分配內存,指針實例化
    //cdev_init(pcdev, &test_fops);
    pcdev->owner = THIS_MODULE;
    pcdev->ops = &test_fops;

    retval = cdev_add(pcdev, mydev, MYCNT);
    if (retval) {
        printk(KERN_ERR "Unable to cdev_add\n");
        goto flag2;
    }
    printk(KERN_INFO "cdev_add success\n");
    
    // 註冊字符設備驅動完成後,添加設備類的操作,以讓內核幫我們發信息
    // 給udev,讓udev自動創建和刪除設備文件
    test_class = class_create(THIS_MODULE, "aston_class");
    if (IS_ERR(test_class))
        return -EINVAL;
    // 最後1個參數字符串,就是我們將來要在/dev目錄下創建的設備文件的名字
    // 所以我們這裏要的文件名是/dev/test
    device_create(test_class, NULL, mydev, NULL, "test111");
    
    
    // 使用動態映射的方式來操作寄存器
    if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
//        return -EINVAL;
        goto flag3;
    if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
//        return -EINVAL;
        goto flag3;
    
    pGPJ0CON = ioremap(GPJ0CON_PA, 4);
    pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
    
    *pGPJ0CON = 0x11111111;
    *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));        // 亮
    
    //goto flag0:
    return 0;
    
// 如果第4步纔出錯跳轉到這裏來    
    release_mem_region(GPJ0CON_PA, 4);
    release_mem_region(GPJ0DAT_PA, 4);

// 如果第3步纔出錯跳轉到這裏來
flag3:
    cdev_del(pcdev);

// 如果第2步纔出錯跳轉到這裏來
flag2:
    // 在這裏把第1步做成功的東西給註銷掉
    unregister_chrdev_region(mydev, MYCNT);
// 如果第1步纔出錯跳轉到這裏來
flag1:    
    return -EINVAL;
//flag0:    
//    return 0;
}

// 模塊下載函數
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");

    *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));    
    
    // 解除映射
    iounmap(pGPJ0CON);
    iounmap(pGPJ0DAT);
    release_mem_region(GPJ0CON_PA, 4);
    release_mem_region(GPJ0DAT_PA, 4);

/*    
    // 在module_exit宏調用的函數中去註銷字符設備驅動
    unregister_chrdev(mymajor, MYNAME);
*/    


    device_destroy(test_class, mydev);
    class_destroy(test_class);

    // 使用新的接口來註銷字符設備驅動
    // 註銷分2步:
    // 第一步真正註銷字符設備驅動用cdev_del
    cdev_del(pcdev);
    // 第二步去註銷申請的主次設備號
    unregister_chrdev_region(mydev, MYCNT);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx這種宏作用是用來添加模塊描述信息
MODULE_LICENSE("GPL");                // 描述模塊的許可證
MODULE_AUTHOR("aston");                // 描述模塊的作者
MODULE_DESCRIPTION("module test");    // 描述模塊的介紹信息
MODULE_ALIAS("alias xxx");            // 描述模塊的別名信息


D)Makefile 

#ubuntu的內核源碼樹,如果要編譯在ubuntu中安裝的模塊就打開這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build


# 開發板的linux內核的源碼樹目錄
KERN_DIR = /home/jimmy/news5pv210/x210ii_kernel

obj-m   += module_test.o

all:
        make -C $(KERN_DIR) M=`pwd` modules
        arm-linux-gcc app.c -o app

cp:
        cp *.ko /tftpboot/nfs/driver_test/
        cp app /tftpboot/nfs/driver_test/app-chrdevsenior


.PHONY: clean
clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf app

E) app文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FILE    "/dev/test111"            // 剛纔mknod創建的設備文件名

char buf[100];

int main(void)
{
    int fd = -1;
    int i = 0;
    
    fd = open(FILE, O_RDWR);
    if (fd < 0)
    {
        printf("open %s error.\n", FILE);
        return -1;
    }
    printf("open %s success..\n", FILE);

/*    
    // 讀寫文件
    write(fd, "on", 2);
    sleep(2);
    write(fd, "off", 3);
    sleep(2);
    write(fd, "on", 2);
    sleep(2);
*/
/*
    write(fd, "1", 1);
    sleep(2);
    write(fd, "0", 1);
    sleep(2);
    write(fd, "1", 1);
    sleep(2);
*/
    while (1)
    {
        memset(buf, 0 , sizeof(buf));
        printf("請輸入 on | off \n");
        scanf("%s", buf);
        if (!strcmp(buf, "on"))
        {
            write(fd, "1", 1);
        }
        else if (!strcmp(buf, "off"))
        {
            write(fd, "0", 1);
        }
        else if (!strcmp(buf, "flash"))
        {
            for (i=0; i<3; i++)
            {
                write(fd, "1", 1);
                sleep(1);
                write(fd, "0", 1);
                sleep(1);
            }
        }    
        else if (!strcmp(buf, "quit"))
        {
            break;
        }
    }

    
    // 關閉文件
    close(fd);
    
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章