1. 程式人生 > >基於ubuntu14.04 Linux核心驅動的編寫

基於ubuntu14.04 Linux核心驅動的編寫

           兩種方式:(1)靜態申請 函式

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版本中,對於不同平臺、不同硬體,往往存在著大量的不同的、移植性差的板級描述程式碼,以達到對這些不同平臺和不同硬體特殊適配的需求。但是