基於ubuntu14.04 Linux核心驅動的編寫
int register_chrdev_region(dev_t from,unsigned count, const char *name);
/ * register_chrdev_region() - register arange of device numbers
* @from: the first in the desired range of devicenumbers; must include
*the major number.
* @count: the number of consecutive device numbersrequired
* @name: the name of the device or driver.
*Return value is zero on success, a negative error code on failure.*/
這種方式主要用於,驅動開發者事先知道該驅動主裝置號的情況
(2)動態申請
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
/* alloc_chrdev_region() - register a rangeof char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minornumbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
*Allocates a range of char device numbers.The major number will be
*chosen dynamically, and returned (along with the first minor number)
* [email protected]Returns zero or a negative errorcode.*/
這種方式由系統動態分配一個裝置號,返回的裝置號儲存在引數dev中。
Step 2:註冊字元裝置
在Linux核心中庸struct cdev表示一個字元裝置。
字元裝置的註冊與登出分別通過下面的兩個函式來實現:
int cdev_add(structcdev *p, dev_t dev, unsigned count);
/**
*cdev_add() - add a char device to the system
*@p: the cdev structure for the device
*@dev: the first device number for which this device is responsible
*@count: the number of consecutive minor numbers corresponding to this
*device
*
*cdev_add() adds the device represented by @p to the system, making it
*live immediately.A negative error codeis returned on failure.
*/
void cdev_del(structcdev *p);
不過,在註冊一個字元裝置之前,要呼叫下面這個函式來初始化struct cdev結構體:
void cdev_init(structcdev *cdev, const struct file_operations *fops)
/**
*cdev_init() - initialize a cdev structure
*@cdev: the structure to initialize
*@fops: the file_operations for this device
*
*Initializes @cdev, remembering @fops, making it ready to add to the
*system with cdev_add().
*/
另外,struct cdev結構體變數可以宣告為一個指標,核心提供了一個函式來申請:
struct cdev *cdev_alloc(void);
Step 3:建立裝置節點
有兩種方法:
一是通過 mknod命令來建立。如:
mknod /dev/yourname c major minor
其中“yourname”可以是任意符合unix下路徑名的名字,不一定要是你程式碼裡定義的驅動或裝置的名字;c 表示建立字元裝置節點,major是你成功申請的主裝置號,minor是次裝置號,這個可以是任意的(在次裝置號範圍內)
另外一種方法是通過udev自動生成。這種方法需要在你的程式碼裡建立一個裝置類,然後在這個裝置類的基礎上,建立一個裝置;另外應用程式需要跑一個udevd的後臺程式。
struct class*class_create(owner, name);
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata,const char *fmt, ...)
這樣Linux驅動編寫的一般步驟就完成了,我們閱讀Linux核心驅動的程式碼,應該先找到程式的入口函式module_init(char_test_init);引數中的就是函式入口,函式名可以自己定義,從入口進入,根據以上的步驟閱讀程式碼,那麼Linux核心驅動的框架就顯得簡單明瞭了。
接下來以LED驅動為例子,閱讀下LED驅動的程式碼。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/uaccess.h>//kmalloc函式頭文
#include <linux/slab.h>
#include <linux/mutex.h>
//驅動標頭檔案
#include <mach/gpio.h> /* \arch\arm\mach-s5pv210\include\ */
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h> /* \arch\arm\plat-samsung\include\ */
#define LED_ON _IOW('a',2,int)
#define LED_OFF _IOW('a',3,int)
#define LED_ALL_ON _IO('a',0xF)
#define LED_ALL_OFF _IO('a',0)
#define LED_LIUSHUI_ON _IO('a',98)
#define LED_LIUSHUI_OFF _IO('a',99)
MODULE_LICENSE("GPL");
int devno_major=0;
int devno_minor=0;
int init_gpio_led(void)
{
if(!gpio_request(S5PV210_GPJ2(0), "led_1"))
{
return -1;
}
if(!gpio_request(S5PV210_GPJ2(1), "led_2"))
{
return -1;
}
if(!gpio_request(S5PV210_GPJ2(2), "led_3"))
{
return -1;
}
if(!gpio_request(S5PV210_GPJ2(3), "led_4"))
{
return -1;
}
gpio_direction_output(S5PV210_GPJ2(0), 1);
gpio_direction_output(S5PV210_GPJ2(1), 1);
gpio_direction_output(S5PV210_GPJ2(2), 1);
gpio_direction_output(S5PV210_GPJ2(3), 1);
return 0;
}
module_param(devno_major, int, 0440);
struct cdev *pdev=NULL;
struct class * myclass = NULL;
struct device *mdevice = NULL;
int test_open(struct inode *_inode,struct file *_file)
{
printk(KERN_INFO "%s\n", __FUNCTION__);
return 0;
}
int test_close(struct inode *_inode,struct file *_file)
{
printk(KERN_INFO "%s\n", __FUNCTION__);
return 0;
}
ssize_t test_read (struct file *_file, char __user * buf, size_t count, loff_t * offset)
{
return 0;
}
ssize_t test_write (struct file *_file, const char __user * buf, size_t count, loff_t * offset)
{
return 0;
}
long test_ioctl (struct file * _file, unsigned int cmd, unsigned long arg)
{
int *args=(int *)arg;
int k;
if (_IOC_DIR(cmd) == _IOC_READ) //該命令,是使用者想從核心讀一個數據
{
//我就必須要驗證你提供的地址,是否可寫
if (!access_ok(VERIFY_WRITE, arg, _IOC_SIZE(cmd)) )
{
return -EFAULT;
}
}
else if (_IOC_DIR(cmd) == _IOC_WRITE)
{
if (!access_ok(VERIFY_READ, arg, _IOC_SIZE(cmd)))
{
return -EFAULT;
}
}
get_user(k,args);
switch(cmd)
{
case LED_ON:
__gpio_set_value(S5PV210_GPJ2(k),0);
break;
case LED_OFF:
__gpio_set_value(S5PV210_GPJ2(k),1);
break;
case LED_ALL_ON:
__gpio_set_value(S5PV210_GPJ2(0),0);
__gpio_set_value(S5PV210_GPJ2(1),0);
__gpio_set_value(S5PV210_GPJ2(2),0);
__gpio_set_value(S5PV210_GPJ2(3),0);
break;
case LED_ALL_OFF:
__gpio_set_value(S5PV210_GPJ2(0),1);
__gpio_set_value(S5PV210_GPJ2(1),1);
__gpio_set_value(S5PV210_GPJ2(2),1);
__gpio_set_value(S5PV210_GPJ2(3),1);
break;
case LED_LIUSHUI_ON :
__gpio_set_value(S5PV210_GPJ2(0),1);
__gpio_set_value(S5PV210_GPJ2(0),0);
__gpio_set_value(S5PV210_GPJ2(1),1);
__gpio_set_value(S5PV210_GPJ2(1),0);
__gpio_set_value(S5PV210_GPJ2(2),1);
__gpio_set_value(S5PV210_GPJ2(2),0);
__gpio_set_value(S5PV210_GPJ2(3),1);
__gpio_set_value(S5PV210_GPJ2(3),0);
break;
case LED_LIUSHUI_OFF :
break;
default:
break;
}
return -1;
}
const struct file_operations fops= //傳統的字元裝置訪問方式 這裡我偷懶只用ioctl實現對硬體的訪問
{
.open =test_open,
.release=test_close,
.read=test_read,
.write=test_write,
.unlocked_ioctl = test_ioctl,
};
int char_test_init(void)
{
int r,res;
dev_t devno; //32位數,其中的12位用來表示主裝置號,其餘的20位表示次裝置號
//申請裝置號
if(devno_major>0)//靜態指定
{
devno =MKDEV(devno_major,devno_minor);
r=register_chrdev_region(devno,1,"test");
}
else//動態申請
{
r=alloc_chrdev_region(&devno, devno_minor, 1, "test");
}
if(r!=0)
{
printk(KERN_ERR "register char dev number failed\n");
return -1;
}
devno_major=MAJOR(devno); //獲取主裝置號
devno_minor=MINOR(devno); //獲取次裝置號
printk(KERN_INFO"major: %d minor: %d\n", devno_major, devno_minor);
//註冊字元裝置
pdev =cdev_alloc();
cdev_init(pdev,&fops); //字元裝置初始化
cdev_add(pdev,devno,1); //通過此函式告訴核心該結構的資訊
//生成裝置節點
myclass=class_create(THIS_MODULE, "char_test");
mdevice=device_create(myclass,NULL,devno,NULL,"test");
res=init_gpio_led();//初始化埠
if(res!=0)
return -1;
return 0;
}
void char_test_exit(void)
{
dev_t devno=MKDEV(devno_majkfree(pt->p_buf);
//釋放裝置號
device_destroy(myclass,devno);
class_destroy(myclass);
//登出字元裝置
cdev_del(pdev);
gpio_free(S5PV210_GPJ2(0));
gpio_free(S5PV210_GPJ2(1));
gpio_free(S5PV210_GPJ2(2));
gpio_free(S5PV210_GPJ2(3));
unregister_chrdev_region(devno,1);
}
module_init(char_test_init); //這裡為函式入口
module_exit(char_test_exit); //這裡為退出函式
理解了上述程式碼後,那麼如何將自己編寫的驅動寫進Linux核心呢?
我們在linux核心中編寫驅動,一般都是在/kernel/driver/ 下建立自己的目錄,在新建的目錄下建立C檔案編寫。比如說新建hello目錄,在hello目錄下新建hello.c及hello.h一些相關的標頭檔案。驅動編寫完成後,還需要配置Kconfig以及Makefile檔案。以hello為列:
其中Kconfig是在編譯前執行配置命令make menuconfig時用到的,而Makefile是執行編譯命令make是用到的:
Kconfig檔案的內容
config HELLO
tristate "First Android Driver"
default n
help
This is the first android driver.
Makefile檔案的內容
obj-$(CONFIG_HELLO) += hello.o
在Kconfig檔案中,tristate表示編譯選項HELLO支援在編譯核心時,hello模組支援以模組、內建和不編譯三種編譯方法,預設是不編譯,因此,在編譯核心前,我們還需要執行make menuconfig命令來配置編譯選項,使得hello可以以模組或者內建的方法進行編譯。
在Makefile檔案中,根據選項HELLO的值,執行不同的編譯方法
修改arch/arm/Kconfig和drivers/kconfig兩個檔案,在menu "Device Drivers"和endmenu之間新增一行:
source "drivers/hello/Kconfig"(有些原始碼在arch/arm/Kconfig中沒有menu "Device Drivers"和endmenu,那是因為在drivers/kconfig已經包含。所以只需要在drivers/kconfig新增即可。)
這樣,執行make menuconfig時,就可以配置hello模組的編譯選項了。
修改drivers/Makefile檔案,新增一行:
obj-$(CONFIG_HELLO) += hello/
配置編譯選項:
/Android-5.0.2/kernel/common$ make menuconfig
找到"Device Drivers" => "First Android Drivers"選項,設定為y
注意,如果核心不支援動態載入模組,這裡不能選擇m,雖然我們在Kconfig檔案中配置了HELLO選項為tristate。要支援動態載入模組選項,必須要在配置選單中選擇Enable loadable module support選項;在支援動態解除安裝模組選項,必須要在Enable loadable module support選單項中,選擇Module unloading選項。
這些工作做完之後,就可以直接在/kernel目錄下make了。
編譯成功後,就可以在hello目錄下看到hello.o檔案了,這時候編譯出來的zImage已經包含了hello驅動。
參考部落格:http://blog.csdn.net/luoshengyang/article/details/6568411 羅老師
相關推薦
基於ubuntu14.04 Linux核心驅動的編寫
兩種方式:(1)靜態申請 函式 int register_chrdev_region(dev_t from,unsigned count, const char *name); / * register_chrdev_region() - register arange of devi
i.mx6ul linux驅動開發—基於Device tree機制的驅動編寫
前言 Device Tree是一種用來描述硬體的資料結構,類似板級描述語言,起源於OpenFirmware(OF)。在目前廣泛使用的Linux kernel 2.6.x版本中,對於不同平臺、不同硬體,往往存在著大量的不同的、移植性差的板級描述程式碼,以達到對這些不同平臺和不同硬體特殊適配的需求
【Linux核心驅動】編寫I2C外設驅動讀取觸控式螢幕韌體版本
編寫I2C外設驅動步驟 註冊I2C裝置,一般在板級檔案中,定義i2c_board_info 註冊I2C驅動:i2c_register_driver,i2c_del_driver 利用i2c_client中的addr(裝置地址)和adapter(主機驅動)實現
linux ffmpeg開發環境搭建(基於ubuntu14.04和ffmpeg3.2)
本文將介紹ffmpeg開發環境的安裝測試和更新的步驟(基於ubuntu14.04和ffmpeg3.2) 1.安裝x264 1)libx264需要yasm,所以先安裝yasm sudo apt-get install yasm 2)安裝libx264-d
在Ubuntu上為Android系統編寫Linux核心驅動程式
在智慧手機時代,每個品牌的手機都有自己的個性特點。正是依靠這種與眾不同的個性來吸引使用者,營造品牌凝聚力和使用者忠城度,典型的代表非iphone莫屬了。據統計,截止2011年5月,AppStore的應用軟體數量達381062個,位居第一,而Android Ma
Disconf 學習系列之全網最詳細的最新穩定Disconf 搭建部署(基於Ubuntu14.04 / 16.04)(圖文詳解)
class 6.0 conf ubuntu14 穩定 div ubun 搭建 學習 不多說直接上幹貨! https://www.cnblogs.com/wuxiaofeng/p/6882596.html (ubuntu16.04) https
Android/Linux核心驅動相關經典書籍大合集(Linux驅動工程師必備)
博主從事嵌入式Linux核心驅動開發工作,在工作學習中收集了一些Linux核心驅動開發相關的經典書籍,最近將這些經典書籍陸續以資源的形式傳到了CSDN上,希望能給同行以幫助,但因為博主下載積分級別關係,還有些經典書籍(像《深入Linux核心架構中文版》(現已經傳上見第
基於tiny4412的Linux核心移植(支援device tree)(三)
https://www.cnblogs.com/pengdonglin137/p/5146791.html 閱讀目錄(Content) 作者資訊 平臺簡介 注意 一、裝置樹反編譯 二、在u-boot列印資訊 三、開啟Linux核心啟動早期的log 四、在核心自解壓時dump記憶體 五、C
基於tiny4412的Linux核心移植(支援device tree)(二)
https://www.cnblogs.com/pengdonglin137/p/5143516.html 閱讀目錄(Content) 作者資訊 平臺簡介 步驟 回到頂部(go to top) 作者資訊 作者: 彭東林 郵箱:[email protected] QQ:4
基於tiny4412的Linux核心移植(支援device tree)(一)
https://www.cnblogs.com/pengdonglin137/p/5137941.html 閱讀目錄(Content) 作者資訊 平臺簡介 概述 步驟 回到頂部(go to top) 作者資訊 作者: 彭東林 郵箱:[email protected] Q
基於ubuntu14.04 編譯webrtc android 原始碼
一.如何訪問google伺服器 1.購買v-p-s伺服器 由於webrtc 原始碼在國外的網站,下載原始碼需要訪問谷歌的伺服器,訪問谷歌的伺服器可以用vpn或者購買vps自己搭建vpn伺服器,我是購買的搬瓦工 作為
lenovo 邵陽E42-80 Ubuntu14.04.5 wireless 驅動安裝
裝完系統後, $sudo apt-get upgrade 系統版本核心變為: [email protected]:~$ uname -a Linux lenovo 4.4.0-138-generic #164~14.04.1-Ubuntu SMP Fr
linux核心驅動重要的資料結構
檔案操作 迄今為止, 我們已經保留了一些裝置編號給我們使用, 但是我們還沒有連線任何我們裝置操作到這些編號上. file_operation 結構是一個字元驅動如何建立這個連線. 這個結構, 定義在 , 是一個函式指標的集合. 每個開啟檔案(內部用一個 file
Linux 核心驅動中對檔案的讀寫
有時候需要在Linux kernel–大多是在需要除錯的驅動程式–中讀寫檔案資料。在kernel中操作檔案沒有標準庫可用,需要利用kernel的一些函式,這些函式主 要有: filp_open() filp_close(), vfs_read() vfs_write
基於Ubuntu14.04版本的Redis 4.0.9 版本安裝
本文就Redis的安裝和安裝過程中可能出現的問題做一個簡單的介紹。1.新建Redis目錄,下載Redis 安裝包:mkdir redis使用如下命令,下載Redis:wget http://download.redis.io/releases/redis-4.0.9.tar.
基於arm的Linux核心編譯
我的Ubuntu版本是14.04 1、在官網下載Linux核心原始碼 官網地址:https://www.kernel.org/ 2、解壓Linux核心原始碼 3、安裝arm-gcc交叉編譯工具鏈:sudo apt-get install arm-linux-gn
Eclipse 搭建 Linux 核心驅動程式開發環境
1、開發工具 eclipse 、arm-linux-gcc交叉工具鏈、對應開發板的Linux 核心原始碼。2、安裝開發工具,並將核心原始碼包解壓到指定路徑中,並編譯。 eg:/usr/local/arm/linux_E9_3.0.35_for_Linux3、利用eclipse
基於ubuntu14.04的ambari安裝及叢集部署
第一節. Ambari簡介Ambari跟Hadoop等開源軟體一樣,也是Apache Software Foundation中的一個專案,並且是頂級專案。目前最新的釋出版本是2.4.1。就Ambari的作用來說,就是建立、管理、監視Hadoop的叢集,但是這裡的Hadoop是廣義,指的是Hadoop整個生態圈
Linux核心驅動之GPIO子系統(一)GPIO的使用
四 使用者態使用gpio監聽中斷 首先需要將該gpio配置為中斷 echo "rising" > /sys/class/gpio/gpio12/edge 以下是虛擬碼 int gpio_id; struct pollfd fds[1]; gpio_fd = open("/s
基於Device tree機制的驅動編寫
前言 Device Tree是一種用來描述硬體的資料結構,類似板級描述語言,起源於OpenFirmware(OF)。在目前廣泛使用的Linux kernel 2.6.x版本中,對於不同平臺、不同硬體,往往存在著大量的不同的、移植性差的板級描述程式碼,以達到對這些不同平臺和不同硬體特殊適配的需求。但是