3.第一個字符設備驅動(虛擬設備)框架搭建、驅動模塊加載、驅動函數實現、應用程序編寫;

一、字符設備驅動框架

具體的對應關係見上一篇文章,這裏只對需要實現的部分進行說明
2.字符設備驅動開發基礎(一個虛擬的字符設備驅動開發流程)
字符設備驅動編寫,主要工作就是驅動對應的open close read write函數的編寫說白了,就是
file_operations結構體的成員變量的實現;
在linux內核代碼中,file_operations的定義在/include/linux/fs.h這在個.h文件中定義了很多對於linux文件很重要的概念,包括inode、file_operations等等;

在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述

1.1 使用vscode作爲linux下編輯器的設置

在當前目錄下創建.vscode文件夾,其下創建如下設置當前目錄的頭文件搜索路徑(注意選擇自己的內核源碼路徑)
c_cpp_properties.json

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", 
                "/home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", 
                "/home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

1.2 驅動模塊加載卸載測試(僅含有模塊註冊和卸載函數的驅動測試,具體框架在1.4中)

內容:編寫一個僅含有驅動的註冊和卸載函數的.c文件,將其編譯成驅動模塊後加載到內核,觀察是否加載成功
目的:驗證環境搭建是否存在問題;
printk()的頭文件:#include <linux/kernel.h>
chrdevbase.c

#include <linux/module.h>
#include <linux/kernel.h>

static int __init chrdevbase_init(void)
{
    printk("chrdevbase_init\r\n");
    return 0;
}
static void __exit chrdevbase_exit(void)
{
    printk("chrdevbase_exit\r\n");
}
/**
 * 模塊入口與出口
 */

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("QJY");

1.3 Makefile

KERNELDIR := /home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

CURRENT_PATH := $(shell pwd)

obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

編譯之後的目錄爲:(編譯——拷貝到ubuntu nfs下的rootfs中)
在這裏插入圖片描述

開發板中加載模塊:
在這裏插入圖片描述

1.4 字符設備驅動框架搭建與函數實現

下圖所示是本實驗使用的字符設備驅動框架,並不完善;
在這裏插入圖片描述

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>  // copy from user

#define CHRDEVBASE_MAJOR    200             // 主設備號
#define CHRDEVBASE_NAME     "chrdevbase"    // 名字

static char readbuf[100];   // 讀緩衝區
static char writebuf[100];  // 寫緩衝區
static char kernel_data[] = {"kernel data!"};


static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("chrdevbase_open\r\n");
    return 0;
}

static int chrdevbase_close(struct inode *inode, struct file *filp)
{
    printk("chrdevbase_release\r\n");
    return 0;
}

static ssize_t chrdevbase_read(struct file *filp, __user char *buf, 
                                size_t cnt, loff_t *ppos)
{
    int retval = 0;
    printk("chrdevbase_reade\r\n");
    // 從內核中讀取數據
    memcpy(readbuf, kernel_data, sizeof(kernel_data));
    retval = copy_to_user(buf, readbuf, cnt);
    if(retval == 0) {
        printk("kernel data sended! OK\r\n");
    }else {
        printk("kernel data sended failed!\r\n");
    }

    return 0;
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, 
                                size_t cnt, loff_t *ppos)
{
    int retval = 0;
    printk("chrdevbase_write\r\n");
    // 從用戶空間向內核空間寫數據
    copy_from_user(writebuf, buf, cnt);
    if(retval == 0) {
        printk("kernel reveived OK\r\n");
    }else {
        printk("kernel reveived failed!\r\n");
    }
    return 0;
}


/* 字符設備操作集合*/
static struct file_operations chrdevbase_fops={
    .owner = THIS_MODULE,
    .open  = chrdevbase_open,
    .release = chrdevbase_close,
    .read = chrdevbase_read,
    .write = chrdevbase_write, 
};



static int __init chrdevbase_init(void)
{   
    int ret = 0;
    printk("chrdevbase_init\r\n");

    /* 註冊字符設備*/
    ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, 
                    &chrdevbase_fops);
    if(ret < 0) {
        printk("chrdevbase init failed!\r\n");
    }
    
    return 0;
}

static void __exit chrdevbase_exit(void)
{
    printk("chrdevbase_exit\r\n");
    /* 註銷字符設備*/
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}

/**
 * 模塊入口與出口
 */

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("QJY");

二、驅動模塊的加載和卸載

驅動的加載有兩種方式:

  1. 直接寫進內核文件:編譯內核後內核(zImage)與驅動融爲一體
  2. 編譯成.ko模塊文件,使用modprobe指令加載模塊;(我們使用這種)

第二種方法的好處在於,方便調試,可以隨時加載和卸載模塊,而不是重新編譯整個內核文件;

2.1 開發板的準備工作(alpha-i.mx6ull)

  1. 啓動:uboot代碼編譯後,燒寫在sd卡中,開發板從sd卡啓動
  2. 內核zImage和設備樹文件.dtb:使用tftp從ubuntu中讀取;
  3. rootfs根文件系統在ubuntu中,使用nfs掛載;
  4. 設置uboot變量 bootargs 和 bootcmd完成2、3的設置;

下面是設置之後的uboot環境變量

=> print
baudrate=115200
board_name=EVK
board_rev=14X14
boot_fdt=try
bootargs=console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.5.103:/home/qjy/linux/nfs/rootfs ip=192.168.5.111:192.168.5.103:192.168.5.1:255.255.255.0::eth0:off
bootcmd=tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000
bootcmd_mfg=run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
bootdelay=3
bootscript=echo Running bootscript from mmc ...; source
console=ttymxc0
ethact=FEC1
ethaddr=00:04:9f:04:d2:35
ethprime=FEC
fdt_addr=0x83000000
fdt_file=imx6ull-alientek-emmc.dtb
fdt_high=0xffffffff
fileaddr=83000000
filesize=8d32
findfdt=if test $fdt_file = undefined; then if test $board_name = EVK && test $board_rev = 9X9; then setenv fdt_file imx6ull-9x9-evk.dtb; fi; if test $board_name = EVK && test $board_rev = 14X14; then setenv fdt_file imx6ull-14x14-evk.dtb; fi; if test $fdt_file = undefined; then echo WARNING: Could not determine dtb to use; fi; fi;
gatewayip=192.168.5.1
image=zImage
initrd_addr=0x83800000
initrd_high=0xffffffff
ip_dyn=yes
ipaddr=192.168.5.111
loadaddr=0x80800000
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc g_mass_storage.stall=0 g_mass_storage.removable=1 g_mass_storage.file=/fat g_mass_storage.ro=1 g_mass_storage.idVendor=0x066F g_mass_storage.idProduct=0x37FF g_mass_storage.iSerialNumber="" clk_ignore_unused
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
mmcautodetect=yes
mmcboot=echo Booting from mmc ...; run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
mmcdev=0
mmcpart=1
mmcroot=/dev/mmcblk0p2 rootwait rw
netargs=setenv bootargs console=${console},${baudrate} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp
netboot=echo Booting from net ...; run netargs; if test ${ip_dyn} = yes; then setenv get_cmd dhcp; else setenv get_cmd tftp; fi; ${get_cmd} ${image}; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if ${get_cmd} ${fdt_addr} ${fdt_file}; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
netmask=255.255.255.0
panel=TFT7016
script=boot.scr
serverip=192.168.5.103

如果重新配置,需要更新的環境變量爲:bootcmd、bootargs、ipaddr、ethaddr、gatewayip、netmask、serverip;

2.2 驅動模塊的加載卸載

將編譯出來的.ko模塊放到根文件系統中;
使用depmod指令分析模塊依賴心,爲使用modprobe準備;
使用modprobe / insmod指令完成對模塊的加載;
使用modprobe -r / rmmod指令完成對模塊的卸載;
使用lsmod查看已經加載的模塊;
如果使用modprobe出現問題,見
Linux驅動加載問題“.ko模塊無法加載modprobe: module ‘xxx.ko’ not found”解決方法
Linux depmod命令用於分析可載入模塊的相依性。
depmod(depend module)可檢測模塊的相依性,供modprobe在安裝模塊時使用。

3. 應用程序的編寫

/**
 * 本文件對應驅動模塊CharDevBase的測試APP
 * argv[1] : filename:要進行互動的驅動設備名稱(通過/dev/
 * argv[2]  : 1:表示從驅動中讀取數據操作;   2:表示向驅動中寫數據操作
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#define READ_SIZE   50
#define WRITE_SIZE  50

int main(int argc,char* argv[])
{
    int fd;
    int read_ret = 0, write_ret = 0;
    char read_buf[100];     // 用戶空間讀取數據緩衝區
    char write_buf[100];    // 用戶空間寫入數據緩衝區
    char write_data[] = "Im the written data, I come from UserSapce!\n";
    memcpy(write_buf,write_data, sizeof(write_data));
    if(argc != 3){
        printf("參數不足!\n");
        return -1;
    }

    fd = open(argv[1], O_RDWR);
    if(fd < 0){
        printf("Now in userSpace: open error!\n");
        return errno;
    }

    if(atoi(argv[2]) == 1){// 讀操作
        if((read_ret = read(fd, read_buf,READ_SIZE)) == -1){
            printf("Now in userSpace: reading error~\n");
            return errno;
        }else{
            printf("Now in userSpace: the received data: %s\n", read_buf);
        }
    }
    
    if(atoi(argv[2]) == 2){// 寫操作
        if((write_ret = write(fd, write_buf,WRITE_SIZE)) == -1){
            printf("Now in userSpace: reading error~\n");
            return errno;
        }
    }
    usleep(200);
    if(-1 == close(fd)){
        printf("Now in userSpace: close error!\n");
        return errno;
    }
    return 0;
}

3.1編譯應用程序並將.ko與App文件拷貝到rootfs中

注意,驅動模塊的編譯方法在之前的makefile中

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
sudo cp ./*.ko ./chrdevbaseApp ../../../nfs/rootfs/lib/modules/4.1.15

4. 測試

4.1 首先在系統中創建設備結點

4.2 測試驅動程序

在這裏插入圖片描述

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