驅(qū)動開發(fā)

說明

問題:自己在學習linux的實話,發(fā)現(xiàn)很多時候,都是看別人的博客,沒有閱讀過代碼,理解其中的機制,更沒有進行實踐過,或者通過代碼驗證過。

作用:希望通過這個文檔,建立一套模板,可以快速的實踐,學到的東西,從而加深理解。

一、驅(qū)動模板

1.1 簡單驅(qū)動

hello.c

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

static int hello_init(void)

{

? ? printk(KERN_INFO "Hello - init\n");

? ? return 0;

}

static void hello_exit(void)

{

? ? printk(KERN_INFO "Hello - exit\n");

}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("WEI_TEST");

MODULE_DESCRIPTION("Hello World Module");

Makefile

PWD := $(shell pwd)

ANDROID_DIR := /home/user/code/xxxx/LINUX/android

KERNEL_OUT := $(ANDROID_DIR)/out/target/product/xxx/obj/KERNEL_OBJ

MODULE_SIGN_FILE := $(KERNEL_OUT)/scripts/sign-file

MODSECKEY := $(KERNEL_OUT)/certs/signing_key.pem

MODPUBKEY := $(KERNEL_OUT)/certs/signing_key.x509

obj-m := hello.o

.PHONY : hello clean

hello:

? ? ? ? make -C $(KERNEL_OUT) ARCH=arm64 CROSS_COMPILE=$(ANDROID_DIR)/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-androidkernel- REAL_CC=$(ANDROID_DIR)/vendor/qcom/proprietary/llvm-arm-toolchain-ship/10.0/bin/clang CLANG_TRIPLE=aarch64-linux-gnu- M=$(PWD) modules

? ? ? ? cp hello.ko hello.ko.unsigned

? ? ? ? $(MODULE_SIGN_FILE) sha512 $(MODSECKEY) $(MODPUBKEY) hello.ko

clean:

? ? ? ? @rm -f *.o *.ko *.order *.symvers *.unsigned *.mod.c

安裝與卸載:

安裝:

insmod? hello.ko

[ 1367.237628] Hello - init

[ 1367.237695] Loaded hello: module init layout addresses range: 0xffffffac34959000-0xffffffac3495afff

[ 1367.237709] hello: core layout addresses range: 0xffffffac3494d000-0xffffffac34950fff

查看:

lsmod

Module? ? ? ? ? ? ? ? Size? Used by

hello? ? ? ? ? ? ? ? ? 16384? 0

卸載:

rmmod hello

[ 1451.056954] Hello - exit

[ 1451.059449] Unloaded hello: module core layout address range: 0xffffffac3494d000-0xffffffac34950fff

modprobe可以根據(jù)依賴關系自動加載依賴的模塊,在編譯模塊時在make … modules后加上modules_install可調(diào)用depmod生成模塊依賴文件modules.dep

modprobe –d 模塊目錄 hello

modprobe –d 模塊目錄 –r hello

ko文件格式:ELF格式,包含代碼段(.txt),數(shù)據(jù)段(.data),符號表(.symtab),模塊信息(.modinfo)等內(nèi)容

動態(tài)加載模塊流程:

調(diào)用insmod命令后,讀取ko文件到內(nèi)存

調(diào)用init_module系統(tǒng)調(diào)用(syscall),傳入模塊的內(nèi)存地址,模塊長度,和參數(shù)地址。傳入的地址都是用戶態(tài)地址。

init_module根據(jù)內(nèi)核版本的不同實現(xiàn)的細節(jié)也有差別,主要的流程如下:

檢查運行權限( capabilities )

從用戶空間讀取模塊到內(nèi)核空間

檢查是否是有效的ELF文件

獲取模塊的基本信息,如名稱,section索引等

檢查是否在模塊黑名單,檢查簽名

為各section分配內(nèi)存

將模塊加入到模塊列表

根據(jù)內(nèi)核符號表設置模塊使用的符號對應的邏輯地址,然后刷新指令緩存

從用戶空間拷貝參數(shù)到內(nèi)核

鏈接模塊到sysfs

執(zhí)行模塊入口函數(shù)

說明:

如果是編譯成ko,就按上面的步驟寫makefile,如果在源碼目錄下編譯,直接修改makefile就行。

添加模塊源代碼的object文件到Makefile的obj-m變量中 (obj-m += hello.o)

如果創(chuàng)建了Makefile,需要將目錄添加到上級目錄Makefile的obj-m變量中(obj-m += hello? hello為目錄名稱)

1.2 字符驅(qū)動

上面的驅(qū)動,看起來沒啥價值,只能說,可以看下驅(qū)動的安裝和卸載,還有什么事模塊。有個概念。

cdev_hello.c

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

#include <linux/init.h>? ? ? ?

#include <linux/fs.h>

#include <linux/device.h>

#include <linux/uaccess.h>

//#include <asm/uaccess.h>

#include <asm/io.h>

#include <linux/slab.h>

struct led_desc{

? ? unsigned int dev_major;? ? ? ? //主設備號

? ? struct class* myclass;? ? ? ? ?

? ? struct device* mydev;? ? ? ? //創(chuàng)建設備文件的類和設備

? ? void *reg_virt_base;? ? ? ? ? //表示進行虛擬映射后寄存器地址的基準值

}*led_dev;? ? ? ? ? ? ? ? ? ? ? ? //聲明全局的設備對象

#define GPJ0CON 0xE0200240? ? ? // GPJ0CON寄存器物理地址

#define GPJ0_SIZE? 8

static int kernel_val = 555;? ? //內(nèi)核空間定義的一個值,可以看成是一段4字節(jié)的空間,模擬和用戶空間進行數(shù)據(jù)交互

ssize_t led_dev_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)

{

? ? int ret;

? ? printk("-----%s-----\n",__FUNCTION__);

? ? ret = copy_to_user(buf,&kernel_val, count);

? ? if(ret > 0)

? ? {

? ? ? ? printk("copy_to_user error.\n");

? ? ? ? return -EFAULT;

? ? }?

? ? return 0;

}

ssize_t led_dev_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)

{

? ? int ret;

? ? int value;

? ? printk("-----%s-----\n",__FUNCTION__);

? ? ret = copy_from_user(&value,buf,count);

? ? if(ret > 0)

? ? {

? ? ? ? printk("copy_from_user error.\n");

? ? ? ? return -EFAULT;

? ? }

? ? if(value)? ? ? ? //根據(jù)用戶空間傳過來的value實現(xiàn)LED的亮滅

? ? {

? ? ? ? writel(readl(led_dev->reg_virt_base + 4) & ~(1<<3),led_dev->reg_virt_base + 4);? ? //led亮

? ? }

? ? else

? ? {

? ? ? ? writel(readl(led_dev->reg_virt_base + 4) | (1<<3),led_dev->reg_virt_base + 4);? ? //led滅

? ? }


? ? return 0;

}

int led_dev_open (struct inode *inode, struct file *filp)

{

? ? printk("-----%s-----\n",__FUNCTION__);

? ? return 0;

}

int led_dev_close (struct inode *inode, struct file *filp)

{

? ? printk("-----%s-----\n",__FUNCTION__);

? ? return 0;

}

const struct file_operations my_fops = {

? ? .open = led_dev_open,

? ? .read = led_dev_read,

? ? .write = led_dev_write,

? ? .release = led_dev_close,

};

static int __init led_dev_init(void)? //一般都是申請系統(tǒng)資源

{?

? ? int ret;

? ? u32 value;

? ? //0-實例化全局的設備對象----分配空間

? ? led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);

? ? if(led_dev == NULL)

? ? {

? ? ? ? printk(KERN_ERR "malloc error.\n");

? ? ? ? return -ENOMEM;

? ? }


? ? // 1-申請設備號

? ? led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);? //動態(tài)分配主設備號,且分配成功返回主設備號

? ? if(led_dev->dev_major < 0)

? ? {

? ? ? ? printk(KERN_ERR "register_chrdev error.\n");

? ? ? ? ret = -ENODEV;

? ? ? ? goto err_0;

? ? }

? ? // 2-創(chuàng)建設備結(jié)點

? ? led_dev->myclass = class_create(THIS_MODULE, "dev_class");

? ? if(IS_ERR(led_dev->myclass))? //IS_ERR判斷指針是否出錯

? ? {

? ? ? ? printk(KERN_ERR "class_create error.\n");

? ? ? ? ret = PTR_ERR(led_dev->myclass);? //PTR_ERR將指針的錯誤原因轉(zhuǎn)換成錯誤碼

? ? ? ? goto err_1;

? ? }


? ? led_dev->mydev = device_create(led_dev->myclass, NULL, MKDEV(led_dev->dev_major,0), NULL, "led%d",0);

? ? if(IS_ERR(led_dev->mydev))?

? ? {

? ? ? ? printk(KERN_ERR "device_create error.\n");

? ? ? ? ret = PTR_ERR(led_dev->mydev);

? ? ? ? goto err_2;

? ? }


? ? // 3-硬件初始化

? ? //對地址進行映射

? ? led_dev->reg_virt_base = ioremap(GPJ0CON, GPJ0_SIZE);

? ? if(led_dev->reg_virt_base == NULL)?

? ? {

? ? ? ? printk(KERN_ERR "ioremap error.\n");

? ? ? ? ret = -ENOMEM;

? ? ? ? goto err_3;

? ? }


? ? //gpio的輸出功能的配置

? ? value = readl(led_dev->reg_virt_base);

? ? value &= ~(0xf<<12);? //先清零

? ? value |= (0x1<<12);? ?

? ? writel(value,led_dev->reg_virt_base);? //設置GPJ0的[15:12]為輸出模式

? ? return 0;

? //錯誤處理

? ? err_3:

? ? ? ? device_destroy(led_dev->myclass, MKDEV(led_dev->dev_major,0));

? ? err_2:

? ? ? ? class_destroy(led_dev->myclass);

? ? err_1:

? ? ? ? unregister_chrdev(led_dev->dev_major,"led_dev_test");

? ? err_0:

? ? ? ? kfree(led_dev);

? ? return 0;

}

static void __exit led_dev_exit(void)

{

? ? //一般都是釋放資源

? ? iounmap(led_dev->reg_virt_base);

? ? device_destroy(led_dev->myclass, MKDEV(led_dev->dev_major,0));

? ? class_destroy(led_dev->myclass);

? ? unregister_chrdev(led_dev->dev_major,"led_dev_test");

? ? kfree(led_dev);

? ? printk("--------%s-------\n",__FUNCTION__);

}

module_init(led_dev_init);

module_exit(led_dev_exit);

MODULE_LICENSE("GPL");? ? ? ? ? ? ? // 描述模塊的許可證

Makefile

obj-m:=cdev_hello.o

KERNELDIR:=/lib/modules/`uname -r`/build

PWD :=$(shell pwd)

modules:

$(MAKE)? -C? $(KERNELDIR)? M=$(PWD)? modules

clean:

rm -rf *o *.mod.c *.order *.symvers *.ur-safe

測試結(jié)果:

電腦端:編譯ok,安裝也ok

嵌入式端:編譯ok,安裝出現(xiàn)下面的錯誤。

(這個錯誤原因是,在iomap的時候出錯,因為每個板子的的io地址不一樣,所以可能導致地址沖突,需要看手冊確認io地址,這樣才能操作硬件。)

[? 204.175630] ------------[ cut here ]------------

[? 204.175647] WARNING: CPU: 6 PID: 8273 at arch/arm64/mm/ioremap.c:58 __ioremap_caller+0xc4/0xcc

[? 204.175651] Modules linked in: hello(O+) wlan(O) cpe_lsm_dlkm(O) wcd937x_slave_dlkm(O) machine_dlkm(O) wcd937x_dlkm(O) wcd934x_dlkm(O) wcd9335_dlkm(O) mbhc_dlkm(O) wcd9xxx_dlkm(O) wcd_cpe_dlkm(O) tx_macro_dlkm(O) rx_macro_dlkm(O) va_macro_dlkm(O) wsa_macro_dlkm(O) swr_ctrl_dlkm(O) bolero_cdc_dlkm(O) wsa881x_dlkm(O) wcd_core_dlkm(O) stub_dlkm(O) wcd_spi_dlkm(O) hdmi_dlkm(O) swr_dlkm(O) pinctrl_wcd_dlkm(O) usf_dlkm(O) native_dlkm(O) platform_dlkm(O) q6_dlkm(O) adsp_loader_dlkm(O) apr_dlkm(O) snd_event_dlkm(O) q6_notifier_dlkm(O) q6_pdr_dlkm(O) wglink_dlkm(O) msm_11ad_proxy

[? 204.175722] CPU: 6 PID: 8273 Comm: insmod Tainted: G S? ? ? ? O? ? 4.14.190+ #1

[? 204.175725] Hardware name: Qualcomm Technologies, Inc. trinket pm6125 + pmi632 Lenovo PVT1 (DT)

[? 204.175730] task: 00000000c84407f8 task.stack: 0000000020fdc8d7

[? 204.175735] pc : __ioremap_caller+0xc4/0xcc

[? 204.175739] lr : __ioremap_caller+0x60/0xcc

[? 204.175742] sp : ffffff80240ebad0 pstate : 00400145

[? 204.175745] x29: ffffff80240ebad0 x28: ffffff80240ebe18

[? 204.175754] x27: 00000000014080c0 x26: 0000000000000002

[? 204.175763] x25: 0000000000000002 x24: 0000000000000240

[? 204.175771] x23: 00000000e0200240 x22: 0000000000001000

[? 204.175779] x21: ffffff9f151c41ac x20: 00000000e0200000

[? 204.175787] x19: 0068000000000f07 x18: 0000000000000006

[? 204.175796] x17: 0000000000aab4cc x16: ffffff8022eaa648

[? 204.175804] x15: d29ced2031b099b6 x14: 88584c0fe2ffffff

[? 204.175812] x13: 0000000080000000 x12: 00000000ffc00000

[? 204.175820] x11: 0000000000000018 x10: 00000000ffffffff

[? 204.175828] x9 : 0000000000000001 x8 : 0000000000000000

[? 204.175836] x7 : bbbbbbbbbbbbbbbb x6 : 0000000000000040

[? 204.175844] x5 : 0000000000000000 x4 : 0000000000000001

[? 204.175853] x3 : ffffff9f151c41ac x2 : 0068000000000f07

[? 204.175861] x1 : 00000000e0200000 x0 : 0000000000000000

[? 204.175869] \x0aPC: 0xffffff8022eaa714:

[? 204.175872] a714? aa1303e3 8b1502c1 aa1503e0 944736ff 34000120 aa1503e0 9406a915 aa1f03e0

[? 204.175900] a734? a9434ff4 a94257f6 a9415ff8 a8c47bfd d65f03c0 8b150300 17fffffa aa1f03e0

[? 204.175928] a754? d4210000 17fffff7 a9be7bfd f9000bf3 910003fd aa0003f3 d503201f 9274ce73

[? 204.175955] a774? aa1303e0 9406a145 34000060 aa1303e0 9406a8ff f9400bf3 a8c27bfd d65f03c0

[? 204.175982] \x0aLR: 0xffffff8022eaa6b0:

[? 204.175985] a6b0? aa0003f7 d503201f 92402ef8 8b180288 9274cef4 913ffd08 9274cd16 8b160288

[? 204.176012] a6d0? d1000508 d370fd09 f100013f fa400ac4 fa541100 54000263 d34cfee0 97fffddd

[? 204.176039] a6f0? 35000300 aa1603e0 52800021 aa1503e2 9406a84b b4000180 f9400415 aa1403e2

[? 204.176066] a710? f9001814 aa1303e3 8b1502c1 aa1503e0 944736ff 34000120 aa1503e0 9406a915

[? 204.176094] \x0aSP: 0xffffff80240eba90:

[? 204.176096] ba90? 22eaa754 ffffff80 00400145 00000000 e0200000 00000000 000e0200 00000000

[? 204.176124] bab0? ffffffff 0000007f 22eaa6f0 ffffff80 240ebad0 ffffff80 22eaa754 ffffff80

[? 204.176151] bad0? 240ebb10 ffffff80 22eaa680 ffffff80 00000002 00000000 00000000 00000000

[? 204.176178] baf0? e0200240 00000000 00000008 00000000 00000f07 00680000 151c41ac ffffff9f

[? 204.176205]

[? 204.176209] Call trace:

[? 204.176214] __ioremap_caller+0xc4/0xcc

[? 204.176218] __ioremap+0x38/0x48

[? 204.176226] init_module+0x1ac/0x1000 [hello]

[? 204.176232] do_one_initcall+0xe0/0x1b8

[? 204.176237] do_init_module+0x60/0x1f8

[? 204.176241] load_module+0x2424/0x27e4

[? 204.176245] SyS_finit_module+0xc0/0x114

[? 204.176248] el0_svc_naked+0x34/0x38

[? 204.176252] ---[ end trace 16b224f349a8d83e ]---

[? 204.176256] ioremap error.

[? 204.177299] Loaded hello: module init layout addresses range: 0xffffff9f151c4000-0xffffff9f151c5fff

[? 204.177305] hello: core layout addresses range: 0xffffff9f151bf000-0xffffff9f151c2fff

二、應用模板

2.1 源代碼

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

void print_info()

{

? ? ? ? printf("this file is %s,line is %d\n",__FILE__,__LINE__);

? ? ? ? printf("this file build time is:%s %s\n",__DATE__,__TIME__);

}

int main(int argc, char const *argv[])

{

? ? ? ? int fd;

? ? ? ? int value;

? ? ? ? printf("this is a test\n");

? ? ? ? print_info();

? ? ? ? fd = open("/dev/led0",O_RDWR);

? ? ? ? //根據(jù) device_create的最后一個參數(shù)確定設備結(jié)點的名字

? ? ? ? if(fd < 0)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? ? ? perror("open");

? ? ? ? ? ? ? ? ? ? ? ? exit(-1);

? ? ? ? ? ? ? ? }

? ? ? ? read(fd,&value,4);

? ? ? ? printf("---USER----:%d\n",value);

? ? ? ? //應用程序去控制LED的亮滅

? ? ? ? while(1)

? ? ? ? {

? ? ? ? ? ? ? ? value = 0;

? ? ? ? ? ? ? ? write(fd,&value,4);

? ? ? ? ? ? ? ? sleep(1);

? ? ? ? ? ? ? ? value = 1;

? ? ? ? ? ? ? ? write(fd,&value,4);

? ? ? ? ? ? ? ? sleep(1);

? ? ? ? ? ? ? ? }

? ? ? ? close(fd);

? ? ? ? return 0;

}

這里面我用了編譯器相關的宏__FILE__? __LINE__? __DATE__? __TIME__,可以用于調(diào)試,非常方便。

2.2 交叉編譯器的安裝

這部分,主要看正點原子的教程,我只簡單的寫一下步驟。

Linaro Releases下載:gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar

sudo mkdir /usr/local/arm

sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f

sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

sudo vi /etc/profile

export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin

sudo apt-get install lsb-core lib32stdc++6? (在使用交叉編譯器之前還需要安裝一下其它的庫)

arm-linux-gnueabihf-gcc -v? 有顯示gcc的版本號,就代表交叉編譯環(huán)境安裝搭建成功。

2.3 測試結(jié)果

makefile

ARCH ?= x86

ifeq ($(ARCH),x86) #注意看?。?!ifeq后邊有空格,沒有不對

? ? cc=gcc

else

? ? cc=arm-linux-gnueabihf-gcc

endif

TARGET=test

OBJS=test.o

$(TARGET):$(OBJS)

? ? ? ? $(cc) $^ -o $@ --static

%.o:%.c

? ? ? ? $(cc) -c $< -o $@

.PHONY:clean

clean:

? ? ? ? rm $(TARGET) $(OBJS)

注意:這里,我在生成可執(zhí)行文件的時候,加了參數(shù)--static,目前還不知道為什么,因為只有這樣,編譯出的可執(zhí)行文件在嵌入式端能使用

2.3.1 電腦端

編譯:gcc? xxx.c

測試:ok

2.3.2 嵌入式端

編譯:arm-linux-gnueabihf-gcc -o helloworld3 test3.c -static? ? (暫且還不知道,這個staic 是什么意思,如果不加它,編譯出來的程序無法運行)

測試:ok

待補充:

嵌入式端makefile的編寫

嵌入式端如何編譯庫的呢?如何使用庫的呢?

三、hal庫的引入

3.1 引入概述

上面的應用程序,就可以操作操作到硬件了,似乎可以萬事大吉了。那么如果出現(xiàn)以下情況呢?

如果驅(qū)動程序的節(jié)點有變化,那用戶的程序是不是要跟著變化?

由于linux是開源的,對于數(shù)據(jù)的一些處理可能需要用到算法,如果寫在驅(qū)動里也必須開源,但我這些算法不想讓別人知道,那怎么辦?

遇到這些情況,該怎么辦?我們可以將上面的部分抽取出來,編譯成庫,用戶只需要我提供給他的接口就行,所以就有了HAL 庫的出現(xiàn)。

3.2 hal 庫的幾個點

用頭文件暴露接口給用戶。

內(nèi)部使用的函數(shù),需要加限制。

3.3 簡單的例子

四、什么是分層

五、再講驅(qū)動

5.1 字符設備驅(qū)動開發(fā)框架

要素:

必須有一個設備號,用于在眾多設備驅(qū)動中進行區(qū)分。

用戶必須知道設備驅(qū)動對應的設備節(jié)點。(設備文件)

對設備操作,就是對文件操作,用戶空間的open、read等函數(shù)是和驅(qū)動中的open、read對應的。

5.2 IO 有哪些接口

驅(qū)動填充這些接口,用戶程序操作這些接口,從而就操作了硬件。

5.3 用戶程序和硬件的交互

5.3.1 用戶空間和內(nèi)核空間的數(shù)據(jù)交互

copy_to_user(void __user * to, const void * from, unsigned long n)

copy_from_user(void * to, const void __user * from, unsigned long n)

5.3.2 用戶程序操作硬件

內(nèi)核驅(qū)動中是通過虛擬地址轉(zhuǎn)換操作寄存器:

void* ioremap(cookie, size);? ? //映射函數(shù)

void iounmap(void __iomen*addr);? //去映射函數(shù)

映射函數(shù)參數(shù):

參數(shù)1:物理地址

參數(shù)2:長度

返回值:虛擬地址

去映射函數(shù)參數(shù):映射成功的虛擬地址

頭文件包含:#include

參考鏈接

Linux驅(qū)動開發(fā)之字符設備驅(qū)動_big__C的博客-CSDN博客_linux字符設備驅(qū)動

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容