精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

linux驅動編寫:從hello world到 LED驅動

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

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

1. Hello world驅動

hello world幾乎成了所有編程書的第一個程序,用來介紹程序的大體結構。一個簡單的hello world讓人感覺這本書學起來真的很容易,不知不覺就進入圈套。被誘導入坑的我,也來用一個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);

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

的許可證一致就好了。

現在來看驅動模塊是如何編譯的,看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

如果我們要構造的模塊名稱為hello.ko,那么就需要指定obj-m為hello.o,這個是hello.ko生成的依賴選項。是在zynq平臺上編譯模塊,需要制定ARCH類型為arm,以及交叉編譯工具,如果使用本機linux系統默認gcc就不能在zynq平臺下加載模塊,這里的交叉工具鏈為arm-xilinx-linux-gnueabi-gcc,arm-xilinx-linux-gnueabi-ld是交叉連接器,把程序鏈接成可以在arm平臺運行的模塊。同時還需要制定內核系統文件夾,因為模塊編譯要用到內核的庫。All下就是編譯模塊了。設置好交叉工具的環境變量后,直接執行make,機會生成hello.ko,這個就是編譯好的驅動模塊。

圖1.1 編譯hello驅動過程

我們打開zynq系統,加載hello.ko,加載使用insmod命令,卸載使用rmmod命令。我們發現沒有打印出任何信息,這是不是模塊編譯有錯誤,其實模塊沒有錯誤,流程也都對。因為printk打印函數是有級別的,只有低于這個級別值才能打印到terminal中。我們可以修改內核級別,比如我們將級別降低:

echo 8 > /proc/sys/kernel/printk

我們還可以使用dmesg來查看驅動打印的日志。其會打印所有驅動加載和卸載的打印日志。

圖1.2 insmod hello.ko

2. LED驅動

2.1 vivado工程

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



圖2.1 LED硬件工程

2.2 LED驅動

LED屬于字符類驅動,符合字符類驅動的寫法。Linux內核中每個模塊實際上是以文件形式存在的,這些文件存放于/dev目錄下。而不同的驅動模塊具有不同的主設備號和次設備號。主設備號用于區別不同的驅動,而次設備號用于更具體的指向驅動指向的設備。設備號就相當于門牌號,用于唯一區別不同驅動。編寫驅動過程中就需要為驅動分配設備號。現在進一步分析LED驅動代碼:

#define LED_DATA 0x41200000
#define LED_CTRL 0x41200004

這兩行定義了LED數據控制寄存器和數據讀寫寄存器的內存地址。實際上驅動對LED硬件的配置和讀寫都是通過配置其寄存器實現的。具體要看GPIO的硬件信息。

dev_t led_devt;
void __iomem *baseaddr;

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

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

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

文件操作結構體實際上提供了軟件操作LED的接口,上文講過驅動都被映射成/dev下的一個文件,軟件調用驅動的時候,以打開,讀寫,關閉對應設備文件來進行操控。我們看一下LED驅動中的文件結構:

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

其中包含了文件應該有的四種基本操作open, read, write, release實際上是close。文件結構體還提供了ioctrl函數,這個函數為軟件提供了一種更為靈活的操縱底層硬件的方法。

接下來對文件結構體中的每個函數進行分析。

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;
}

內核用inode結構在內部表示文件,inode結構中包含了大量有關文件的信息。當我們在linux中創建一個文件時,就會在相應的文件系統創建一個inode與之對應。文件實體和文件的inode是一一對應的。當打開文件時,就獲得了inode。通過inode可以獲得字符設備結構體i_cdev。Inode在驅動開發中很少進行填充,通常都是用于查看。在使用LED時,需要為其分配內存,首先通過函數request_mem_region來看是否有空閑linux內核內存可分配,如果可以就通過ioremap進行分配,返回linux內存首地址。之后讀寫LED的時候就可以通過向這個地址寫數據來控制LED了。iowrite32(reg, baseaddr+4)是在配置LED,使能了LED。

2) led_write

因為點亮LED是輸出數據,所以實際上用不上讀操作,只有寫操作。

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內核從用戶空間獲得要寫入LED的數據。Led_gpio_set函數中通過iowrite函數將用戶空間的數據寫入LED。

3) led_release

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

這個是在調用close函數的時候會調用這個函數來釋放內存,解除LED內存映射。

現在再來看初始化和驅動清除函數:
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驅動初始化操作首先要為led_devt動態分配設備號,然后可以通過MAJOR來得到主設備號。這個主設備號在將字符設備添加到驅動中會用到。獲得了設備號后就對led字符設備結構體led_dev分配空間,然后調用函數led_setup_sev完成對LED設備的初始化,添加等。這些就是一般過程了。作為初學者,先會用,以后在驅動的調試中會深入去理解。

清除函數就是釋放LED字符設備空間。

2.3 LED軟件操作

現在來看如何在用戶端操作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函數打開LED字符設備,然后通過write函數來點亮LED。

2.4 實驗

首先加載led.ko:

圖2.2 加載led驅動

ls查看/dev下發現并沒有LED文件,還需要通過mknod命令為其添加設備節點,我們才能在用戶空間進行操作。但是如何知道主設備號呢?cat /proc/devices可以看到LED的主設備號為245。完成LED設備節點分配后,就看到/dev下有個文件LED,這時候就可以通過在用戶空間進行寫操作了。

圖2.3 查看LED的主設備號

圖2.4 分配設備節點

總結

本篇通過兩個簡單的驅動程序,介紹了驅動的基本結構,編寫方法和編譯過程。總結一下就是:通過init來進行設備的基本配置,和硬件配置,然后定義文件操作結構體,以及結構體中函數,通過open,read,write,release等函數來控制對LED的讀寫操作。

編輯:hfy


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • led
    led
    +關注

    關注

    240

    文章

    23134

    瀏覽量

    658418
  • Linux驅動
    +關注

    關注

    0

    文章

    43

    瀏覽量

    9950
收藏 人收藏

    評論

    相關推薦

    Linux系統中驅動格式基本編寫方法

    今天主要和大家聊一聊,編寫Linux驅動格式與方法。
    發表于 12-02 09:34 ?560次閱讀

    Linux模塊相關命令 Linux驅動模塊的編寫與掛載

    Linux模塊相關命令 Linux驅動模塊的編寫與掛載
    發表于 10-01 12:20 ?451次閱讀
    <b class='flag-5'>Linux</b>模塊相關命令 <b class='flag-5'>Linux</b><b class='flag-5'>驅動</b>模塊的<b class='flag-5'>編寫</b>與掛載

    【OK210試用體驗】linux字符驅動框架

    app.ko打印出:“Hello World linux-driver-module”查看驅動信息:lsmod app/app.koapp 583 0 - Live 0xbf0180
    發表于 10-13 17:03

    【BPI-M64試用體驗】linuxHELLO驅動編寫

    linux下,對A53進行了簡單的HELLO驅動編寫!如下圖:
    發表于 06-09 15:56

    Arduino Hello World實驗

    的Arduino 聽從你的指令了,我們再借用一下Arduino 自帶的數字13 口LED,讓Arduino 接受到指令時LED 閃爍一下,再顯示“Hello World!”下面給大家一
    發表于 08-06 09:06

    Linux嵌入式驅動開發

    全部傳送門Linux嵌入式驅動開發01——第一個驅動Hello World(附源碼)Linux
    發表于 12-17 06:22

    第9章 Linux驅動程序設計

    9.1 Linux 設備驅動程序 9.2 Linux經典Hello world驅動程序 9.
    發表于 04-11 14:56 ?3次下載

    Qt圖形編程基礎之使用Qt編寫HelloWorld”程序實驗

    分享:標簽:Qt圖形編程 Linux 操作系統 12.3 實驗內容使用Qt編寫HelloWorld程序 1.實驗目的 通過
    發表于 10-18 14:44 ?1次下載
    Qt圖形編程基礎之使用Qt<b class='flag-5'>編寫</b>“<b class='flag-5'>Hello</b>,<b class='flag-5'>World</b>”程序實驗

    如何編寫Linux 下Nand Flash驅動

    如何編寫Linux 下Nand Flash驅動
    發表于 10-30 08:36 ?15次下載
    如何<b class='flag-5'>編寫</b><b class='flag-5'>Linux</b> 下Nand Flash<b class='flag-5'>驅動</b>

    Linux系統網絡驅動程序的編寫

    驅動程序編寫 一.Linux系統設備驅動程序概述 1.1 Linux設備驅動程序分類 1.2
    發表于 11-07 10:40 ?0次下載

    linux驅動編寫簡單的開發步驟分享

    我們今天所要說的是Linux驅動編寫。現在Linux驅動比較流行,主要有幾個方面的原因: 1)linux
    發表于 04-09 05:51 ?1.3w次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>驅動</b><b class='flag-5'>編寫</b>簡單的開發步驟分享

    Linux嵌入式驅動開發01——第一個驅動Hello World(附源碼)

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

    HELLO WORLD!

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

    如何編寫第一個hello world程序

    本文簡單介紹如何編寫第一個hello world程序,以及程序是如何被執行的
    的頭像 發表于 03-02 17:31 ?8335次閱讀
    如何<b class='flag-5'>編寫</b>第一個<b class='flag-5'>hello</b> <b class='flag-5'>world</b>程序

    c語言hello world程序編寫

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