1. 程式人生 > >舊介面註冊LED字元驅動裝置(靜態對映)

舊介面註冊LED字元驅動裝置(靜態對映)

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

#include <linux/fs.h>          //file_operations

#include <asm/uaccess.h>          //copy_from_user  copy_to_user

#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>

#include 
<asm/string.h> #define rGPJ0CON *((volatile unsigned int *)S5PV210_GPJ0CON) #define rGPJ0DAT *((volatile unsigned int *)S5PV210_GPJ0DAT) static int led_open(struct inode *inode, struct file *file); ssize_t led_read(struct file *file, char __user *user, size_t count, loff_t *loff); ssize_t led_write(
struct file *file, const char __user *user, size_t count, loff_t *loff); static int led_release(struct inode *inode, struct file *file); static int led_major = -1; static char kbuf[100] = {0}; static const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .read
= led_read, .write = led_write, .release = led_release, }; int led_open(struct inode *inode, struct file *file) { printk(KERN_INFO "led_open successful\n"); return 0; } ssize_t led_read(struct file *file, char __user *user, size_t ucount, loff_t *loff) { printk(KERN_INFO "led_read successful\n"); if (copy_to_user(user,kbuf , ucount)) { printk(KERN_INFO "copy_to_user fail\n"); return -EINVAL; } printk(KERN_INFO "copy_to_user successful\n"); return strlen(kbuf); } ssize_t led_write(struct file *file, const char __user *user, size_t ucount, loff_t *loff) { printk(KERN_INFO "led_write successful\n"); memset(kbuf,0,sizeof(kbuf)); if (copy_from_user(kbuf, user, ucount)) { printk(KERN_INFO "copy_from_user fail\n"); return -EINVAL; } if(!strcmp(kbuf,"on")) { rGPJ0CON &=0xff000fff; rGPJ0CON |=0x00111000; rGPJ0DAT &=~((0x01<<3)|(0x01<<4)|(0x01<<5)); } else if(!strcmp(kbuf,"off")) { rGPJ0CON &=0xff000fff; rGPJ0CON |=0x00111000; rGPJ0DAT |=((0x01<<3)|(0x01<<4)|(0x01<<5)); } return ucount; printk(KERN_INFO "copy_from_user successful\n"); } int led_release(struct inode *inode, struct file *file) { printk(KERN_INFO "led_release successful\n"); return 0; } // 模組安裝函式 static int __init chrdev_init(void) { printk(KERN_INFO "chrdev_init successful\n"); if ((led_major = register_chrdev (0, "led_dev", &led_fops)) < 0) { printk(KERN_WARNING "led_module.c: Failed to register character device."); return -EINVAL; } return 0; } // 模組解除安裝函式 static void __exit chrdev_exit(void) { unregister_chrdev(led_major,"led_dev"); printk(KERN_INFO "chrdev_exit successful\n"); } module_init(chrdev_init); module_exit(chrdev_exit); // MODULE_xxx這種巨集作用是用來新增模組描述資訊 MODULE_LICENSE("GPL"); // 描述模組的許可證 MODULE_AUTHOR("Musk <qq:739112417>"); // 描述模組的作者 MODULE_DESCRIPTION("led driver"); // 描述模組的介紹資訊 MODULE_ALIAS("led test driver"); // 描述模組的別名資訊
View Code

    上述程式是led非常簡陋,手動註冊的字元驅動程式碼。

    此部分我從小往上分析。

    一. 模組部分

        1.1. 模組相關分析參考上篇文章

    二.驅動設備註冊

        2.1. 認識file_operations結構體

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};
View Code

         linux應用層使用file_operations結構訪問驅動程式的函式,這個結構的每一個成員的名字都對應著一個呼叫。       

        2.2. 註冊&解除安裝字元驅動函式

static inline int register_chrdev(unsigned int major, const char *name,
                  const struct file_operations *fops)
{
    return __register_chrdev(major, 0, 256, name, fops);
}

static inline void unregister_chrdev(unsigned int major, const char *name)
{
    __unregister_chrdev(major, 0, 256, name);
}
View Code

        2.2.1. 上述兩個函式一般是成對出現的,使用註冊了,在不使用時可以解除安裝。

        2.2.2. 需要注意的,register_chrdev註冊時,註冊的裝置子裝置號為0,@major引數對應需要註冊的裝置的主裝置號,當此引數為0時表示由核心分配主裝置號。

        2.2.3. 在核心管理裝置時,使用陣列管理所有裝置(1~255),其中主裝置號對應陣列的下標。

        2.2.4. 使用cat /proc/devices檢視核心中已經註冊過的字元裝置驅動(和塊裝置驅動)

    三. 核心空間與使用者空間資料互動

        3.1. 由於核心空間與使用者空間的記憶體不能直接互訪,因此藉助函式copy_to_user()完成使用者空間到核心空間的複製,函式copy_from_user()完成核心空間到使用者空間的複製

        3.2. 認識copy_to_user函式(函式在arch/arm/include/asm/uaccess.h)

static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
    if (access_ok(VERIFY_WRITE, to, n))
        n = __copy_to_user(to, from, n);
    return n;
}
View Code

            3.2.1. 此函式表示從核心空間拷貝n個位元組到使用者空間。             

            3.2.2. 先看函式的三個引數:*to是核心空間的指標,*from是使用者空間指標,n表示從核心空間拷貝到使用者空間資料的位元組數。如果成功執行拷貝操作,則返回0,否則返回還沒有完成拷貝的位元組數

        3.3. 認識copy_from_user函式(函式在arch/arm/include/asm/uaccess.h)

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
    if (access_ok(VERIFY_READ, from, n))
        n = __copy_from_user(to, from, n);
    else /* security hole - plug it */
        memset(to, 0, n);
    return n;
}
View Code

            3.3.1. 此函式表示從使用者空間拷貝n個位元組到核心空間。

            3.3.2. 先看函式的三個引數:*to是核心空間的指標,*from是使用者空間指標,n表示從使用者空間拷貝核心空間到資料的位元組數。如果成功執行拷貝操作,則返回0,否則返回還沒有完成拷貝的位元組數

    四. 裝置節點(裝置檔案)的手動建立

        4.1. 關於裝置節點(裝置檔案)

            4.1.1. 在Linux中,所有的裝置訪問都是通過檔案的方式,一般的資料檔案程式普通檔案,裝置節點稱為裝置檔案

            4.1.2. 對應字元類裝置檔案的關鍵資訊是:裝置號 = 主裝置號 + 次裝置號,使用ls -l去檢視裝置檔案,就可以得到這個裝置檔案對應的主次裝置號。

        4.2. 建立裝置節點

            4.2.1. 使用mknod建立裝置檔案:mknod /dev/xxx c 主裝置號 次裝置號,其中xxx:表示裝置節點名稱(應用層可以使用open開啟此檔案)

    五. 驅動中操作硬體

        5.1. 虛擬地址

            5.1.1. 由於核心層使用虛擬地址,無法位元組使用實體地址操作暫存器。

            5.1.2. 核心中實體地址對映分為:動態和靜態

            5.1.3. 靜態對映方法的特點:

                a.  在核心啟動時建立靜態對映表,在核心關機時銷燬,中間一直有效,優點是執行效率高,缺點是始終佔用虛擬地址空間,空間利用率低。

                b. 不同版本核心靜態對映表位置,檔名可能不同

                c. 不同的SOC靜態對映表位置,檔名可能不同

                d. 所謂的對映表其實是標頭檔案中的巨集定義

            5.1.4. 動態對映方法的特點:

                a. 驅動程式根據需要隨時動態建立使用和銷燬對映,對映是短期臨時的。類似c語言中的malloc分配記憶體。

                b. 優點是按需使用地址空間,空間利用率高。缺點是每次使用前後都要去建立和銷燬對映,操作繁瑣

            5.1.5. 虛擬地址到實體地址的對映可以多對1,但是不能1對多

        5.2. 驅動中操作暫存器

            5.2.1. 使用暫存器對應的虛擬地址直接讀取。

            5.2.2. 操作相關檔案

                5.2.2.1.  主對映表位於:arch/arm/plat-s5p/include/plat/map-s5p.h

                        a. CPU在安排暫存器地址時不是隨意亂序分佈的,而是按照模組去區分的。每一個模組內部的很多個暫存器的地址是連續的。所以核心在定義暫存器地址時都是先找到基地址,然後再用基地址+偏移量來尋找具體的一個暫存器。

                        b. map-s5p.h中定義的就是要用到的幾個模組的暫存器基地址。

                        c. map-s5p.h中定義的是模組的暫存器基地址的虛擬地址。

                5.2.2.2. 虛擬地址基地址定義在:arch/arm/plat-samsung/include/plat/map-base.h

                        a. #define S3C_ADDR_BASE(0xFD000000)// 三星移植時確定的靜態對映表的基地址,表中的所有虛擬地址都是以這個地址+偏移量來指定的

                5.2.2.3. GPIO相關的主對映表位於:arch/arm/mach-s5pv210/include/mach/regs-gpio.h

                        a. 表中是GPIO的各個埠的基地址的定義

                5.2.2.4. GPIO的具體暫存器定義位於:arch/arm/mach-s5pv210/include/mach/gpio-bank.h

         六. 應用層呼叫核心

            6.1. 相關程式碼如下               

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <string.h>

#define DEVFILE "/dev/led_dev"

int main(void)
{
    char buf[100] = {0};
    int fd = -1;
    if((fd =open(DEVFILE, O_RDWR))<0)
    {
        perror("open");
        return -1;   
    }
    printf("open successful fd = %d\n",fd);
    if(write(fd, "on", strlen("on"))<0)
    {
        perror("write");
        return -1;
    }   
    sleep(3);
    memset(buf,0,sizeof(buf));
    if(read(fd, buf, 3)<0)
    {
        perror("read");
        return -1;
    }
    printf("read data = %s\n",buf);
    
    
    if(write(fd, "off", strlen("off"))<0)
    {
        perror("write");
        return -1;
    }   
    sleep(3);
    memset(buf,0,sizeof(buf));
    if(read(fd, buf, 4)<0)
    {
        perror("read");
        return -1;
    }
    printf("read data = %s\n",buf);
    
    close(fd);
    return 0;
}
View Code