0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

linux驅(qū)動(dòng)編寫:從hello world到 LED驅(qū)動(dòng)

454398 ? 來源:AI加速 ? 作者:AI加速 ? 2020-11-29 10:28 ? 次閱讀

linux驅(qū)動(dòng)是連接軟件和硬件的一個(gè)中間介質(zhì),實(shí)現(xiàn)了對(duì)硬件的配置和控制。進(jìn)一步將硬件抽象化,為軟件操作硬件提供了簡(jiǎn)單的接口。不論硬件的具體形式如何,linux驅(qū)動(dòng)都將其映射到一個(gè)文件,軟件端對(duì)硬件的讀寫操作等都被抽象成文件操作了。本篇從hello world開始,簡(jiǎn)要介紹驅(qū)動(dòng)的基本結(jié)構(gòu),然后再進(jìn)一步介紹LED硬件的搭建,以及驅(qū)動(dòng)的編寫,設(shè)備樹的修改。讓大家對(duì)linux驅(qū)動(dòng)有一個(gè)基本的認(rèn)識(shí)。

1. Hello world驅(qū)動(dòng)

hello world幾乎成了所有編程書的第一個(gè)程序,用來介紹程序的大體結(jié)構(gòu)。一個(gè)簡(jiǎn)單的hello world讓人感覺這本書學(xué)起來真的很容易,不知不覺就進(jìn)入圈套。被誘導(dǎo)入坑的我,也來用一個(gè)hello world誘別人入坑。先上程序:

#include
#include
MODULE_LICENSE("GPL v2");

static int hello_init(void){

printk(KERN_INFO "Hello world/n");
return 0;
}

static void hello_exit(void){
printk(KERN_INFO "Goodbye, cruel world/n");
}

module_init(hello_init);
module_exit(hello_exit);

一個(gè)驅(qū)動(dòng)的使用過程包括:模塊的裝載,軟件調(diào)用,模塊卸載。程序中module_init和module_exit是內(nèi)核中的宏,是一個(gè)驅(qū)動(dòng)必須包含部分。當(dāng)驅(qū)動(dòng)被裝載時(shí),就會(huì)調(diào)用module_init指定的初始化函數(shù)hello_init,而當(dāng)驅(qū)動(dòng)被卸載時(shí),就會(huì)調(diào)用hello_exit函數(shù)。初始化函數(shù)通常都是進(jìn)行設(shè)備樹檢查,內(nèi)存分配映射,硬件配置,文件和硬件關(guān)聯(lián)等操作。清除函數(shù)用于釋放內(nèi)存,硬件的清零等操作。通常驅(qū)動(dòng)還會(huì)定義一些文件IO操作,比如write,read,ioctrl等。Hello world只給出一個(gè)驅(qū)動(dòng)編寫格式和流程,文件操作在LED驅(qū)動(dòng)中再介紹。MODULE_LISENCE用于告訴內(nèi)核該模塊采用的許可證類型,這個(gè)一般和linux內(nèi)核采用

的許可證一致就好了。

現(xiàn)在來看驅(qū)動(dòng)模塊是如何編譯的,看Makefile:

obj-m:=hello.o
ARCH=arm
CROSS_COMPILE=arm-xilinx-linux-gnueabi-
CC:=$(CROSS_COMPILE)gcc
LD:=$(CROSS_COMPILE)ld
KERNELDIR:=/home/anpingbo/Design/linux/linux-xlnx
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules

如果我們要構(gòu)造的模塊名稱為hello.ko,那么就需要指定obj-m為hello.o,這個(gè)是hello.ko生成的依賴選項(xiàng)。是在zynq平臺(tái)上編譯模塊,需要制定ARCH類型為arm,以及交叉編譯工具,如果使用本機(jī)linux系統(tǒng)默認(rèn)gcc就不能在zynq平臺(tái)下加載模塊,這里的交叉工具鏈為arm-xilinx-linux-gnueabi-gcc,arm-xilinx-linux-gnueabi-ld是交叉連接器,把程序鏈接成可以在arm平臺(tái)運(yùn)行的模塊。同時(shí)還需要制定內(nèi)核系統(tǒng)文件夾,因?yàn)槟K編譯要用到內(nèi)核的庫。All下就是編譯模塊了。設(shè)置好交叉工具的環(huán)境變量后,直接執(zhí)行make,機(jī)會(huì)生成hello.ko,這個(gè)就是編譯好的驅(qū)動(dòng)模塊。

圖1.1 編譯hello驅(qū)動(dòng)過程

我們打開zynq系統(tǒng),加載hello.ko,加載使用insmod命令,卸載使用rmmod命令。我們發(fā)現(xiàn)沒有打印出任何信息,這是不是模塊編譯有錯(cuò)誤,其實(shí)模塊沒有錯(cuò)誤,流程也都對(duì)。因?yàn)閜rintk打印函數(shù)是有級(jí)別的,只有低于這個(gè)級(jí)別值才能打印到terminal中。我們可以修改內(nèi)核級(jí)別,比如我們將級(jí)別降低:

echo 8 > /proc/sys/kernel/printk

我們還可以使用dmesg來查看驅(qū)動(dòng)打印的日志。其會(huì)打印所有驅(qū)動(dòng)加載和卸載的打印日志。

圖1.2 insmod hello.ko

2. LED驅(qū)動(dòng)

2.1 vivado工程

我們通過axi_gpio來連接4個(gè)LED燈,通過linux驅(qū)動(dòng)來點(diǎn)亮LED燈。Block如圖2.1,我們?cè)O(shè)置gpio寬度為4。還有一個(gè)我們需要用到的就是LED映射到的內(nèi)存,可以在address editor中看到,gpio0是LED的。接下來就是一般的流程,管腳約束,綜合,編譯。然后導(dǎo)出工程,打開SDK,新建fsbl,編譯,生成設(shè)備樹,制作boot.bin。



圖2.1 LED硬件工程

2.2 LED驅(qū)動(dòng)

LED屬于字符類驅(qū)動(dòng),符合字符類驅(qū)動(dòng)的寫法。Linux內(nèi)核中每個(gè)模塊實(shí)際上是以文件形式存在的,這些文件存放于/dev目錄下。而不同的驅(qū)動(dòng)模塊具有不同的主設(shè)備號(hào)和次設(shè)備號(hào)。主設(shè)備號(hào)用于區(qū)別不同的驅(qū)動(dòng),而次設(shè)備號(hào)用于更具體的指向驅(qū)動(dòng)指向的設(shè)備。設(shè)備號(hào)就相當(dāng)于門牌號(hào),用于唯一區(qū)別不同驅(qū)動(dòng)。編寫驅(qū)動(dòng)過程中就需要為驅(qū)動(dòng)分配設(shè)備號(hào)?,F(xiàn)在進(jìn)一步分析LED驅(qū)動(dòng)代碼:

#define LED_DATA 0x41200000
#define LED_CTRL 0x41200004

這兩行定義了LED數(shù)據(jù)控制寄存器和數(shù)據(jù)讀寫寄存器的內(nèi)存地址。實(shí)際上驅(qū)動(dòng)對(duì)LED硬件的配置和讀寫都是通過配置其寄存器實(shí)現(xiàn)的。具體要看GPIO的硬件信息。

dev_t led_devt;
void __iomem *baseaddr;

dev_t定義了設(shè)備編號(hào),__iomem定義了linux內(nèi)核的存儲(chǔ)指針。硬件的內(nèi)存需要映射到linux內(nèi)核空間才能操作。在使用和配置LED時(shí),需要先將其物理內(nèi)存映射到linux內(nèi)核的內(nèi)存空間。映射函數(shù)ioremap就是專門用于IO端口內(nèi)存映射的。

static int led_major=25;
struct cdev *led_dev;
int led_value;

上述定義了一個(gè)led主設(shè)備號(hào)。通過ls –l命令可以查看/dev下驅(qū)動(dòng)的設(shè)備號(hào)。當(dāng)然這是一種不方便的方式,正常情況下設(shè)備號(hào)也可以自動(dòng)分配。Cdev是字符設(shè)備的結(jié)構(gòu)體,定義如下:
struct cdev {
struct kobject kobj; // 內(nèi)嵌的kobject對(duì)象
struct module *owner; // 所屬模塊
const struct file_operations *ops; // 文件操作結(jié)構(gòu)體
struct list_head list; //linux內(nèi)核所維護(hù)的鏈表指針
dev_t dev; //設(shè)備號(hào)
unsigned int count; //設(shè)備數(shù)目
};

文件操作結(jié)構(gòu)體實(shí)際上提供了軟件操作LED的接口,上文講過驅(qū)動(dòng)都被映射成/dev下的一個(gè)文件,軟件調(diào)用驅(qū)動(dòng)的時(shí)候,以打開,讀寫,關(guān)閉對(duì)應(yīng)設(shè)備文件來進(jìn)行操控。我們看一下LED驅(qū)動(dòng)中的文件結(jié)構(gòu):

struct file_operations led_fops={
.owner=THIS_MODULE,
.read=led_read,
.write=led_write,
.unlocked_ioctl=led_ioctl,
.open=led_open,
.release=led_release,
};

其中包含了文件應(yīng)該有的四種基本操作open, read, write, release實(shí)際上是close。文件結(jié)構(gòu)體還提供了ioctrl函數(shù),這個(gè)函數(shù)為軟件提供了一種更為靈活的操縱底層硬件的方法。

接下來對(duì)文件結(jié)構(gòu)體中的每個(gè)函數(shù)進(jìn)行分析。

1) led_open
static int led_open(struct inode *inode, struct file *filp){
struct resource *res;
int reg;
printk("begin: open led/n");
filp->private_data=inode->i_cdev;
res=request_mem_region(LED_DATA, 0x10000, "LED");
if(!res){
printk("failed requesting resource/n");
return 0;
}

printk("begin: remap led/n");
baseaddr=ioremap(LED_DATA, 0x10000);
if(!baseaddr){
printk("ERROR: couldn't allocate baseaddr/n");
return 0;
}
printk("baseaddr is %x/n", baseaddr);
printk("begin: read led/n");
reg=ioread32(baseaddr);
printk("begin: write led %d/n", reg);
reg &= 0xFFFFFFF0;
iowrite32(reg, baseaddr+4);
printk("SUCCESS: gpio init/n");
return 0;
}

內(nèi)核用inode結(jié)構(gòu)在內(nèi)部表示文件,inode結(jié)構(gòu)中包含了大量有關(guān)文件的信息。當(dāng)我們?cè)趌inux中創(chuàng)建一個(gè)文件時(shí),就會(huì)在相應(yīng)的文件系統(tǒng)創(chuàng)建一個(gè)inode與之對(duì)應(yīng)。文件實(shí)體和文件的inode是一一對(duì)應(yīng)的。當(dāng)打開文件時(shí),就獲得了inode。通過inode可以獲得字符設(shè)備結(jié)構(gòu)體i_cdev。Inode在驅(qū)動(dòng)開發(fā)中很少進(jìn)行填充,通常都是用于查看。在使用LED時(shí),需要為其分配內(nèi)存,首先通過函數(shù)request_mem_region來看是否有空閑linux內(nèi)核內(nèi)存可分配,如果可以就通過ioremap進(jìn)行分配,返回linux內(nèi)存首地址。之后讀寫LED的時(shí)候就可以通過向這個(gè)地址寫數(shù)據(jù)來控制LED了。iowrite32(reg, baseaddr+4)是在配置LED,使能了LED。

2) led_write

因?yàn)辄c(diǎn)亮LED是輸出數(shù)據(jù),所以實(shí)際上用不上讀操作,只有寫操作。

ssize_t led_write(struct file * filp, const char __user *buf, size_t cnt, loff_t *f_ops){
if(copy_from_user(&led_value, buf, cnt))
return -EFAULT;

led_gpio_set();
return 1;
}
void led_gpio_set(void){
iowrite32(led_value, baseaddr);

}

Copy_from_user是linux內(nèi)核從用戶空間獲得要寫入LED的數(shù)據(jù)。Led_gpio_set函數(shù)中通過iowrite函數(shù)將用戶空間的數(shù)據(jù)寫入LED。

3) led_release

static int led_release(struct inode *inode, struct file *filp){
iounmap(baseaddr);
release_mem_region(LED_DATA, 0x10000);
return 0;
}

這個(gè)是在調(diào)用close函數(shù)的時(shí)候會(huì)調(diào)用這個(gè)函數(shù)來釋放內(nèi)存,解除LED內(nèi)存映射。

現(xiàn)在再來看初始化和驅(qū)動(dòng)清除函數(shù):
static void led_setup_dev(int index){
int err;
int devno;
devno=MKDEV(led_major, index);
printk("MKDEV devno %d/n", devno);
cdev_init(led_dev, &led_fops);
printk("cdev_init %d/n", devno);
led_dev->owner=THIS_MODULE;
led_dev->ops=&led_fops;
err=cdev_add(led_dev, devno, 1);
if(err)
printk(KERN_ERR "ERROR: %d adding LED%d", err, index);
printk("SUCCESS: add dev %d/n", devno);
}

static int __init led_init(void){
int result;
printk("INIT:------------/n");
result=alloc_chrdev_region(&led_devt, 0, 1, "LED");
led_major=MAJOR(led_devt);

if(result printk(KERN_ERR "ERROR: allocate chrdev %d", led_devt);
return result;
}
printk("SUCCESS: allocate chrdev %d/n", led_devt);

led_dev=cdev_alloc();
if(!led_dev){
printk(KERN_ERR "ERROR: allocate device mem %d", led_devt);
result=-ENOMEM;
goto fail_malloc;
}
printk("SUCCESS: allocate device mem %d/n", led_devt);

led_setup_dev(0);
printk("SUCCESS: init device %d/n", led_devt);
return 0;

fail_malloc:
unregister_chrdev_region(led_devt, 1);
return result;
}

static void __exit led_cleanup(void){
cdev_del(led_dev);
unregister_chrdev_region(led_devt, 1);
printk("SUCCESS: exit device %d/n", led_devt);
}

LED驅(qū)動(dòng)初始化操作首先要為led_devt動(dòng)態(tài)分配設(shè)備號(hào),然后可以通過MAJOR來得到主設(shè)備號(hào)。這個(gè)主設(shè)備號(hào)在將字符設(shè)備添加到驅(qū)動(dòng)中會(huì)用到。獲得了設(shè)備號(hào)后就對(duì)led字符設(shè)備結(jié)構(gòu)體led_dev分配空間,然后調(diào)用函數(shù)led_setup_sev完成對(duì)LED設(shè)備的初始化,添加等。這些就是一般過程了。作為初學(xué)者,先會(huì)用,以后在驅(qū)動(dòng)的調(diào)試中會(huì)深入去理解。

清除函數(shù)就是釋放LED字符設(shè)備空間。

2.3 LED軟件操作

現(xiàn)在來看如何在用戶端操作LED,上代碼:

#include
#include
#include
#include

int main(int argc, char *argv[]){
int fd;
fd=open("/dev/LED", O_RDWR);
if(fd printf("ERROR: cannot open/n");
return 0;
}

int value=0xF;
write(fd, &value, 4);
//close(fd);
return 0;
}

Open函數(shù)打開LED字符設(shè)備,然后通過write函數(shù)來點(diǎn)亮LED。

2.4 實(shí)驗(yàn)

首先加載led.ko:

圖2.2 加載led驅(qū)動(dòng)

ls查看/dev下發(fā)現(xiàn)并沒有LED文件,還需要通過mknod命令為其添加設(shè)備節(jié)點(diǎn),我們才能在用戶空間進(jìn)行操作。但是如何知道主設(shè)備號(hào)呢?cat /proc/devices可以看到LED的主設(shè)備號(hào)為245。完成LED設(shè)備節(jié)點(diǎn)分配后,就看到/dev下有個(gè)文件LED,這時(shí)候就可以通過在用戶空間進(jìn)行寫操作了。

圖2.3 查看LED的主設(shè)備號(hào)

圖2.4 分配設(shè)備節(jié)點(diǎn)

總結(jié)

本篇通過兩個(gè)簡(jiǎn)單的驅(qū)動(dòng)程序,介紹了驅(qū)動(dòng)的基本結(jié)構(gòu),編寫方法和編譯過程??偨Y(jié)一下就是:通過init來進(jìn)行設(shè)備的基本配置,和硬件配置,然后定義文件操作結(jié)構(gòu)體,以及結(jié)構(gòu)體中函數(shù),通過open,read,write,release等函數(shù)來控制對(duì)LED的讀寫操作。

編輯:hfy


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • led
    led
    +關(guān)注

    關(guān)注

    240

    文章

    22907

    瀏覽量

    650121
  • Linux驅(qū)動(dòng)
    +關(guān)注

    關(guān)注

    0

    文章

    43

    瀏覽量

    9922
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux系統(tǒng)中驅(qū)動(dòng)格式基本編寫方法

    今天主要和大家聊一聊,編寫Linux驅(qū)動(dòng)格式與方法。
    發(fā)表于 12-02 09:34 ?524次閱讀

    Linux模塊相關(guān)命令 Linux驅(qū)動(dòng)模塊的編寫與掛載

    Linux模塊相關(guān)命令 Linux驅(qū)動(dòng)模塊的編寫與掛載
    發(fā)表于 10-01 12:20 ?361次閱讀
    <b class='flag-5'>Linux</b>模塊相關(guān)命令 <b class='flag-5'>Linux</b><b class='flag-5'>驅(qū)動(dòng)</b>模塊的<b class='flag-5'>編寫</b>與掛載

    【OK210試用體驗(yàn)】linux字符驅(qū)動(dòng)框架

    app.ko打印出:“Hello World linux-driver-module”查看驅(qū)動(dòng)信息:lsmod app/app.koapp 583 0 - Live 0xbf0180
    發(fā)表于 10-13 17:03

    【BPI-M64試用體驗(yàn)】linuxHELLO驅(qū)動(dòng)編寫

    linux下,對(duì)A53進(jìn)行了簡(jiǎn)單的HELLO驅(qū)動(dòng)編寫!如下圖:
    發(fā)表于 06-09 15:56

    Arduino Hello World實(shí)驗(yàn)

    的Arduino 聽從你的指令了,我們?cè)俳栌靡幌翧rduino 自帶的數(shù)字13 口LED,讓Arduino 接受到指令時(shí)LED 閃爍一下,再顯示“Hello World!”下面給大家一
    發(fā)表于 08-06 09:06

    Linux嵌入式驅(qū)動(dòng)開發(fā)

    全部傳送門Linux嵌入式驅(qū)動(dòng)開發(fā)01——第一個(gè)驅(qū)動(dòng)Hello World(附源碼)Linux
    發(fā)表于 12-17 06:22

    第9章 Linux驅(qū)動(dòng)程序設(shè)計(jì)

    9.1 Linux 設(shè)備驅(qū)動(dòng)程序 9.2 Linux經(jīng)典Hello world驅(qū)動(dòng)程序 9.
    發(fā)表于 04-11 14:56 ?3次下載

    Qt圖形編程基礎(chǔ)之使用Qt編寫Hello,World”程序?qū)嶒?yàn)

    分享:標(biāo)簽:Qt圖形編程 Linux 操作系統(tǒng) 12.3 實(shí)驗(yàn)內(nèi)容使用Qt編寫Hello,World程序 1.實(shí)驗(yàn)?zāi)康?通過
    發(fā)表于 10-18 14:44 ?1次下載
    Qt圖形編程基礎(chǔ)之使用Qt<b class='flag-5'>編寫</b>“<b class='flag-5'>Hello</b>,<b class='flag-5'>World</b>”程序?qū)嶒?yàn)

    如何編寫Linux 下Nand Flash驅(qū)動(dòng)

    如何編寫Linux 下Nand Flash驅(qū)動(dòng)
    發(fā)表于 10-30 08:36 ?15次下載
    如何<b class='flag-5'>編寫</b><b class='flag-5'>Linux</b> 下Nand Flash<b class='flag-5'>驅(qū)動(dòng)</b>

    Linux系統(tǒng)網(wǎng)絡(luò)驅(qū)動(dòng)程序的編寫

    驅(qū)動(dòng)程序編寫 一.Linux系統(tǒng)設(shè)備驅(qū)動(dòng)程序概述 1.1 Linux設(shè)備驅(qū)動(dòng)程序分類 1.2
    發(fā)表于 11-07 10:40 ?0次下載

    linux驅(qū)動(dòng)編寫簡(jiǎn)單的開發(fā)步驟分享

    我們今天所要說的是Linux驅(qū)動(dòng)編寫?,F(xiàn)在Linux驅(qū)動(dòng)比較流行,主要有幾個(gè)方面的原因: 1)linux
    發(fā)表于 04-09 05:51 ?1.3w次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>驅(qū)動(dòng)</b><b class='flag-5'>編寫</b>簡(jiǎn)單的開發(fā)步驟分享

    Linux嵌入式驅(qū)動(dòng)開發(fā)01——第一個(gè)驅(qū)動(dòng)Hello World(附源碼)

    文章目錄驅(qū)動(dòng)介紹Hello World1. 包含頭文件編譯第一種方法第二種方法之前也算是一直在學(xué)習(xí)嵌入式Linux的開發(fā),裸機(jī)開發(fā),uboot配置,系統(tǒng)編譯,
    發(fā)表于 11-03 14:51 ?12次下載
    <b class='flag-5'>Linux</b>嵌入式<b class='flag-5'>驅(qū)動(dòng)</b>開發(fā)01——第一個(gè)<b class='flag-5'>驅(qū)動(dòng)</b><b class='flag-5'>Hello</b> <b class='flag-5'>World</b>(附源碼)

    HELLO WORLD!

    HELLO WORLD!
    發(fā)表于 12-03 16:21 ?8次下載
    <b class='flag-5'>HELLO</b> <b class='flag-5'>WORLD</b>!

    如何編寫第一個(gè)hello world程序

    本文簡(jiǎn)單介紹如何編寫第一個(gè)hello world程序,以及程序是如何被執(zhí)行的
    的頭像 發(fā)表于 03-02 17:31 ?8158次閱讀
    如何<b class='flag-5'>編寫</b>第一個(gè)<b class='flag-5'>hello</b> <b class='flag-5'>world</b>程序

    c語言hello world程序編寫

    語言"Hello world"程序的編寫過程,并提供一些實(shí)用技巧和注意事項(xiàng)。 首先,我們需要一個(gè)C語言開發(fā)環(huán)境來編寫和運(yùn)行代碼。在這里,我們可以選擇一款集成開發(fā)環(huán)境(IDE)或者一個(gè)文
    的頭像 發(fā)表于 11-26 09:23 ?1840次閱讀