1. 程式人生 > >初學Linux驅動編譯

初學Linux驅動編譯

       初學驅動編譯,各種不懂啊,記得有些東西曾經看到過有印象,但是還是不清晰,原因就是基礎不牢固,動手太少,中間又學學停停。內容多不要緊,重複是最好的老師!堅持能進步!記錄一下最近一週的收穫。

一、核心的編譯

分為為當前使用的系統編譯核心和為嵌入式單板編譯核心

1、準備工作

  a、安裝虛擬機器,在虛擬機器上安裝Linux

  b、安裝GCC,用來編譯;安裝make、ncurses等工具

  c、下載純淨的Linux核心原始碼包

  d、如果是為移植Linux到嵌入式系統,還要安裝交叉編譯工具鏈

2、設定編譯目標

在配置或編譯核心之前先確定CPU的型別和編譯時用什麼交叉編譯鏈

這裡以arm為例。有兩種方法

a)修改Makefile(推薦此法)

開啟核心原始碼根目錄下的Makefile,修改如下兩個Makefile變數,並儲存。

ARCH  :=arm

CROSS_COMPILE := arm-linux-

b)每次執行make時,通過命令列引數傳入這些資訊

3、配置核心

核心的功能很多,根據我們的需求進行裁剪編譯,需要哪些部分,每部分要編譯成什麼形式的,每部分的工作引數是如何的,這些都是可以配置的。在開始編譯之前我們需要一份配置清單,放到核心原始碼根目錄下,命名為.config檔案,然後根據此.config檔案編譯出我們的核心。

但是核心的配置項太多了,而且不同的CPU架構所配置的項集合是不一樣的,所以核心提供了一種簡單的配置方法。

以arm為例,做法如下:

a) 從/arch/arm/configs/找到相似的配置檔案xxx_deconfig,拷貝到核心原始碼根目錄下,命名為.config檔案(也可以在核心原始碼根目錄下執行make xxx_defconfig,生成.config檔案)。如果是給當前使用的PC機編譯核心,可以使用當前核心目錄的.config檔案

b) 執行make menuconfig ,對此配置做一些需要的修改,將新的配置更新到.config檔案中(核心打開了一組配置項集合,各層的kconfig檔案)

即使不需要對配置做任何的修改,都務必執行一次make menuconfig,進入配置介面後直接退出並儲存。

4、編譯

make

5、核心編譯機制

.config設定了將某個功能編譯進核心,某個功能編譯成模組,設定某個功能的引數,定義了Makefile變數。在make剛開始編譯的時候編譯系統還會生成2個config檔案,分別是include/config/auto.config和include/linux/autoconf.h。頂層的Makefile將包含auto.conf,這樣獲得Makefile的變數,從而知道如何編譯核心的各個部分。make工具帶著這些Makefile變數一層一層的進入各子系統或者模組中去給子Makefile。而核心原始碼.c檔案包含了autoconf.h檔案,就知道使用者有木有配置某功能了。

二、驅動開發環境的建立

同樣也分為為當前使用的系統使用和為嵌入式單板編譯驅動

為嵌入式單板編譯的話,需要在當前使用的Linux系統中,下載和該嵌入式單板一樣配置環境的核心(相同的核心原始碼、庫檔案、和各種核心配置等),編譯一次。然後將編譯好的.ko檔案通過ftp或者NFS到嵌入式單板中去

三、驅動的編譯

分為獨立編譯和將將模組程式碼整合到核心原始碼中跟隨Linux核心一起編譯兩種。後者就和編譯核心一樣了。

四、led驅動的製作

a) 直接使用file_operation結構體或者使用cdev結構體及其相關的初始化函式

b)使用write()函式或者ioctl()函式

c)通過給每個led編號和次裝置號都可以實現對每個led的操作

下面是led_drv.c

#include <linux/module.h>
#include <linux/kernel.h> 
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
/********************************
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
********************************/
//#define DEVICE_NAME "led_drv1"
int major;
static struct class *leds_class;
static struct class_device *leds_class_devs[4];

static char leds_status=0x0;
static DECLARE_MUTEX(leds_lock); //定義賦值
/**************
volatile unsigned long *gpbcon = NULL;
volatile unsigned long *gpbdat = NULL;
**************/
static unsigned long gpio_va;
#define GPIO_OFT(x) ((x)-0x56000000) //標準巨集定義
#define GPBCON (*(volatile unsigned long *)(gpio_va+GPIO_OFT(0x56000010)))
#define GPBDAT (*(volatile unsigned long *)(gpio_va+GPIO_OFT(0x56000014)))

static int leds_drv_open(struct inode *inode,struct file *file)
{
int minor=MINOR(inode->i_rdev);
switch(minor)
{
case 0: /*/dev/leds_tal*/
{
//s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP); /** 系統核心提供的封裝好的函式 **/
GPBCON &= ~(0x3<<(5*2)); //清零
GPBCON |= (0x1<<(5*2)); //設定為輸出

//s3c2410_gpio_cfgpin(S3C2410_GPB6,S3C2410_GPB6_OUTP);
GPBCON &= ~(0x3<<(6*2)); //清零
GPBCON |= (0x1<<(6*2)); //設定為輸出

//s3c2410_gpio_cfgpin(S3C2410_GPB7,S3C2410_GPB7_OUTP);
GPBCON &= ~(0x3<<(7*2)); //清零
GPBCON |= (0x1<<(7*2)); //設定為輸出

//s3c2410_gpio_cfgpin(S3C2410_GPB8,S3C2410_GPB8_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB8,1);

//s3c2410_gpio_setpin(S3C2410_GPB5,0);
/* open函式裡面為什麼要給0/1賦值?應該是初始化 */
GPBDAT &= (0x1<<5);

//s3c2410_gpio_setpin(S3C2410_GPB6,0);
GPBDAT &= (0x1<<6);

//s3c2410_gpio_setpin(S3C2410_GPB7,0);
GPBDAT &= (0x1<<7);

down(&leds_lock);
leds_status=0x0;
up(&leds_lock);

break;
}
case 1: /* /dev/leds_1 */
{
s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB5,0);

down(&leds_lock);
leds_status&=~(0x1<<0);
up(&leds_lock);

break;
}
case 2: /* /dev/leds_2 */
{
s3c2410_gpio_cfgpin(S3C2410_GPB6,S3C2410_GPB6_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB6,0);

down(&leds_lock);
leds_status&=~(0x1<<1);
up(&leds_lock);

break;
}
case 3: /* /dev/leds_3 */
{
s3c2410_gpio_cfgpin(S3C2410_GPB7,S3C2410_GPB7_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB7,0);

down(&leds_lock);
leds_status&=~(0x1<<2);
up(&leds_lock);

break;
}
}
/* 配置 GPBIO 5、6、7、為輸出*/
/**********************************************
*gpbcon&=~((0x3<<(5*2))|(0x3<<(6*2))|(0x3<<(7*2)));
*gpbcon|=((0x1<<(5*2))|(0x1<<(6*2))|(0x1<<(7*2)));
************************************************/
return 0;
}

static ssize_t leds_drv_read(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
int minor=MINOR(file->f_dentry->d_inode->i_rdev);
char val;

switch(minor)
{
case 0: /*/dev/leds_tal*/
{
copy_to_user(buf,(const void *)&leds_status,1);
break;
}
case 1: /* /dev/leds_1 */
{
down(&leds_lock);
val=leds_status&0x1;
up(&leds_lock);
copy_to_user(buf,(const void *)&val,1);
break;
}
case 2: /* /dev/leds_2 */
{
down(&leds_lock);
val=(leds_status>>1)&0x1;
up(&leds_lock);
copy_to_user(buf,(const void *)&val,1);
break;
}
case 3: /* /dev/leds_3 */
{
down(&leds_lock);
val=(leds_status>>2)&0x1;
up(&leds_lock);
copy_to_user(buf,(const void *)&val,1);
break;
}
}
return 1;
}

static ssize_t leds_drv_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{

/* GPBIO 5、6、7、 的高低電平資料 */
/******************
int val;
copy_from_user(&val,buf,count); //copy_to_user();
if(val==1)
{

*gpbdat&=~((0x1<<5)|(0x1<<6)|(0x1<<7));//滅燈
}
else
{
*gpbdat|=(0x1<<5)|(0x1<<6)|(0x1<<7);//點燈
}
***********/

int minor=MINOR(file->f_dentry->d_inode->i_rdev);
char val;

copy_from_user(&val,buf,1);
switch(minor)
{
case 0: /* /dev/leds */
{
s3c2410_gpio_setpin(S3C2410_GPB5,(val&0x1));
s3c2410_gpio_setpin(S3C2410_GPB6,(val&0x1));
s3c2410_gpio_setpin(S3C2410_GPB7,(val&0x1));
down(&leds_lock);
leds_status=val;
up(&leds_lock);
break;
}
case 1: /* /dev/leds_1 */
{
s3c2410_gpio_setpin(S3C2410_GPB5,val);
if(val == 0)
{
down(&leds_lock);
leds_status&=~(1<<0);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status|=(1<<0);
up(&leds_lock);
}
break;
}
case 2: /* /dev/leds_2 */
{
s3c2410_gpio_setpin(S3C2410_GPB6,val);
if(val == 0)
{
down(&leds_lock);
leds_status&=~(1<<1);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status|=(1<<1);
up(&leds_lock);
}
break;
}
case 3: /* /dev/leds_3 */
{
s3c2410_gpio_setpin(S3C2410_GPB7,val);
if(val == 0)
{
down(&leds_lock);
leds_status&=~(1<<2);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status|=(1<<2);
up(&leds_lock);
}
break;
}
}
return 1;
}

static struct file_operations leds_drv_fops= /* 這是定義一個結構體變數first_drv_fops */
{
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的 */
.open = leds_drv_open,
.read = leds_drv_read,
.write = leds_drv_write,
};

static int __init leds_drv_init(void) //驅動的入口函式,呼叫註冊函式
{
int ret;
int minor = 0;

gpio_va = (volatile unsigned long)ioremap(0x56000000,16);
if(!gpio_va)
{
return -EIO ;
}

/*註冊字元裝置,引數為裝置號、裝置名字、file_operation結構
*這樣,將定義的這個結構體變數告訴核心,主裝置號就可以和這個結構聯絡起來
*操作主裝置led_major的裝置檔案時,就可以呼叫file_operation結構中相應的函式
*led_major為0時,核心自動分配主裝置號
*/

//major=register_chrdev(0,DEVICE_NAME,&leds_drv_fops);
major=register_chrdev(0,"leds_drv1",&leds_drv_fops);
/* 寫零,自動生成裝置號,自動建立節點 */
// //註冊,

if(major<0)
{
//printk(DEVICE_NAME "can't register major number!\n");
printk("leds_drv1 can't register major number!\n");
return major;
}
leds_class = class_create(THIS_MODULE,"leds_drv1");/* 先建立一個類,聯絡/sys/class/下的資訊 */
if(IS_ERR(leds_class))
return PTR_ERR(leds_class);

/** 在類下建立結點,建立 /dev/leds_tal /dev/leds_1 /dev/leds_2 /dev/leds_3 4個裝置 **/
leds_class_devs[0]=
device_create(leds_class,NULL,MKDEV(major,0),NULL,"leds_tal");
for(minor=1;minor<4;minor++)
{
leds_class_devs[minor] =
device_create(leds_class,NULL,MKDEV(major,minor),NULL,"leds_%d",minor);
if(unlikely(IS_ERR(leds_class_devs[minor])))
return PTR_ERR(leds_class_devs[minor]);
}

//printk(DEVICE_NAME "initialized !\n");
printk("leds_drv1 initialized !\n");
return 0;
/****
gpbcon= (volatile unsigned long)ioremap(0x56000010,16);
gpbdat=gpbcon+1; //這裡是加1,不是加4
******/
}

void __exit leds_drv_exit(void)
{
int minor;
unregister_chrdev(major,"leds_drv1"); //解除安裝模組函式
for(minor=0;minor<4;minor++)
{
device_unregister(leds_class_devs[minor]);
}
class_destroy(leds_class);
iounmap(gpio_va);

}

module_init(leds_drv_init);
/*
修飾一下,所謂修飾就是用一個巨集來定義一個結構體,這個結構體裡面的某個函式指標指向shangmiandehanshu*/
module_exit(leds_drv_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("xiaoyu 937");
MODULE_DESCRIPTION("A simple LEDS_DRV Module ");
MODULE_VERSION("V1.0");

以下是Makefile檔案

CURR_DIR:=$(shell pwd)
KERN_DIR:=/home/xy/TQ2440/My_kernel/opt/EmbedSky/linux-2.6.30.4
# when it is ".../EmbedSky/linux-2.6.30.4/kernel",it appeared "nu rule to make target modules"
all:
	make -C $(KERN_DIR) SUBDIRS=$(CURR_DIR) modules 
clean:
	make -C $(KERN_DIR) SUBDIRS=$(CURR_DIR) clean
	rm -rf modules.order
obj-m :=led_drv.o

以下是測試程式

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

/*
 * ./led_drv1_test <dev> <on|off>
 */

void print_usage(char * file)
{
	printf("Usage:\n");
	printf("%s <dev> <on|off>\n",file);
	printf("eg:\n");
	printf("%s /dev/leds_tal on\n",file);
	printf("%s /dev/leds_tal off\n",file);
	printf("%s /dev/leds_1 on\n",file);
	printf("%s /dev/leds_1 off\n",file);
}

int main(int argc,char **argv)
{
	int fd;
	char *filename;
	char val;

	char buf1;
	if(argc!=3)
	{
		print_usage(argv[0]);
		return 0;
	}

	filename=argv[1];
	fd=open(filename,O_RDWR);
	if(fd<0)
	{
		printf("error! can't open %s\n",filename);
		return 0;
	}
	if(strcmp(argv[2],"on")==0)
	{
		val=0;	//點燈
		write(fd,&val,1);
		read(fd,&buf1,1);
		printf("leds_status is %d \n",buf1);
	}
	else if(strcmp(argv[2],"off")==0)
	{

		val = 1;
		write(fd,&val,1);
		printf("leds_status is %d \n",buf1);
	}
	else 
	{
		print_usage(filename);
	}
	return 0;
}

問題:程式碼是參考別人的,自己已經理解、編譯成功的。但是還是有些地方暫時還不明白:

a) up()和down()訊號量機制函式不太明白在這裡的用途

b) led_status的作用

五、由於核心版本的不同,一些標頭檔案的路徑存在變化,可以再大路徑下查詢檔案的位置

六、雜項裝置

裝置驅動一般分為3種:字元型裝置、塊裝置和網路裝置,但是不能將所有的裝置全部概括,不屬於這3種的成為雜項裝置(misc裝置,miscellaneous)或者混雜裝置。對於雜項裝置,Linux核心專門提供了這樣一個結構體,其有很強的包容性,如下:

該檔案包含在Linux2.6.32.6/include/linux/Miscdevice.h中

struct miscdevice {

int minor;

const char *name;

const struct file_operations *fops;

struct list_head list;

struct device *parent;

struct device *this_device;

const char *nodename;

mode_t mode;

};

同時提供的miscdevice註冊和登出函式如下所示。

int misc_register(struct miscdevice * misc);

int misc_deregister(struct miscdevice *misc);

其本質還是仍然還是字元裝置,只不過將這種裝置驅動增加了一層封裝。其主體還是file_operation實現的



相關推薦

初學Linux驅動編譯

       初學驅動編譯,各種不懂啊,記得有些東西曾經看到過有印象,但是還是不清晰,原因就是基礎不牢固,動手太少,中間又學學停停。內容多不要緊,重複是最好的老師!堅持能進步!記錄一下最近一週的收穫。 一、核心的編譯 分為為當前使用的系統編譯核心和為嵌入式單板編譯核心 1

linux驅動編譯時make -C M= 解釋

Makefile為 PWD = $(shell pwd) KERNEL_SRC =/usr/src/linux-3.0/ obj-m :=test.o module-objs : =test.o all:               $(MAKE)   -C  $(KER

Linux驅動靜態編譯和動態編譯方法詳解

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android 驅動開發---Android Linux 核心編譯 Nexus 5x

本文以Nexus 5X為例,講解Android Linux 驅動開發的準備驅動開發瞭解:Android對硬體的支援分成了兩層,一層放在使用者空間(User Space),一層放在核心空間(Kernel Space),其中,硬體抽象層執行在使用者空間,而Linux 核心驅動程式執行在核心空間。 所以Andro

Linux CAN驅動編譯記錄

1.編譯環境所需工具: libsocketcan-0.0.10.tar.bz2 canutils-4.0.6.tar.bz2 交叉編譯工具鏈根據每個開發板所提供的型別進行設定,我的工具鏈名稱為 arm-fsl-linux-gnueabi-gcc 後面

linux編譯新核心,解決無法找到eth0裝置問題,安裝eth0網絡卡驅動

由於我的blktrace執行時出現問題,只能對裝置測試一次,第二次的時候就會報如下錯誤:no such file or directory google瞭解決方案,很多都說是核心版本的問題,簡單的方法解決不了啊,測試不能不做啊,所以今天只能果斷換核心版本了(不過我想說每編一次核心,都會遇到新的

Linux編譯mongodb以及C++客戶端驅動

想在Linux下編譯一下mongo和它的C++驅動,網上一堆教程,官方一堆文件,實現起來依舊困難重重。我就很納悶了,本來mongodb就是C++寫的,為什麼不直接提供出驅動,而java,C#之類的,倒是直接提供了jar包和dll檔案。今天終於編譯成功,趕緊記錄下來。 Lin

mongodb資料庫 linux編譯mongodb的c++驅動

由於工作專案開發的需要 要使用mongodb資料庫  需要手動編譯mongo的c++庫,花費了大概兩天的時間,終於搞定,主要是上網搜資料。以下是本人收藏的幾篇部落格 https://mongodb.g

舊手機android的linux核心編譯2-Wifi驅動加入。

經過一些時間的分析與除錯,還是把wifi的驅動調通了。 首先要分析舊手機的wifi。 1,要分析舊手機的wifi,在recovery下配通wifi 是一個不錯的選擇。在recovery已經配通了adb 介面,其實它除去沒有應用軟體外,與硬體系統相關的內容都是完整的。經過我多次償試,對我的MS

linux 核心編譯驅動模組ko的配置以及載入模組解除安裝模組例項測試

linux 核心編譯驅動模組ko的配置以及載入模組、解除安裝模組例項測試一、要讓linux系統支援動態載入驅動模組必須先對linux 核心進行相關的配置,不然編譯不過,載入模組也會載入失敗甚至導致裝置重啟。1、勾選核心Enable loadable module suppor

Linux核心模組(驅動)編譯詳解

本文主要說說如何編譯自己開發的核心模組。由於驅動通常也被編譯成核心模組,因此文章的內容也適用於驅動的編譯。 由於在下能力相當有限,有不當之處,還望大家批評指正^_^ 一、準備工作 準備工作如何做,

linux驅動部分:交叉編譯驅動模組&模組驅動的相關操作&Makefile講解

根據下面的提醒,在自己電腦是的情況是:b1、模組整個操作流程 (1)載入模組:insmod globalfifo_zs_fzs.ko (2)檢視模組是否被載入:lsmod (3)檢視裝置號:cat /proc/devices (4)建立裝置節點:mknod /dev/

Linux 驅動開發之核心模組開發 (二)—— 核心模組編譯 Makefile 入門

一、模組的編譯  我們在前面核心編譯中驅動移植那塊,講到驅動編譯分為靜態編譯和動態編譯;靜態編譯即為將驅動直接編譯進核心,動態編譯即為將驅動編譯成模組。 而動態編譯又分為兩種: a -- 內部編譯        在核心原始碼目錄內編譯 b -- 外部編譯        在核

交叉編譯linux驅動程式

1,獲取原始碼odroid開發板核心原始碼(已經打了preempt_rt-3.14.79-rt84實時補丁),並編譯核心和驅動模組,並安裝驅動模組:       $cd ~/odroid_c2       $git clone https://github.com/moo

linux核心檔案中新增自己的驅動,新增自己的linux驅動編譯自己的linux驅動程式方法和例子圖文

此文章為done原創,轉載請寫明出處,尊重原創。 寫這個文章,我參考了網上的一些部落格: http://bbs.chinaunix.net/thread-3634524-1-1.html http://www.bkjia.com/gjrj/800182.html 也參考

Linux編譯驅動程式碼時smp_lock.h檔案找不到的解決方案

專案中有個驅動程式碼之前在老版本linux系統中編寫的 在移植到linux核心3.2.0後,編譯時出現一處錯誤“fatal error: linux/smp_lock.h: No such file or directory” 解決辦法是將 #include <linux/smp_lock.h&g

linux 核心編譯 (如何正確選擇核心驅動

[[email protected] ~]# lspci [-vvn]選項與引數:-v     :顯示更多的 PCI 介面裝置的詳細資訊-vv :比 -v 還要更詳細的資訊-n     :直接觀察 PCI 的 ID 而不是廠商名稱查閱您系統內的 PCI 裝置:[[email protect

linux驅動模組編譯(初學者)

現在我來說明一下這個Makefile。請記住是大寫的Makefile而不是小寫的makefile;obj-m :這個變數是指定你要聲稱哪些模組模組的格式為 obj-m := <模組名>.omodules-objs :這個變數是說明聲稱模組modules需要的目標檔案 格式要求   <模組名&

Linux裝置驅動--Linux驅動模組交叉編譯的注意事項

在linux系統下編寫好目標開發板的驅動程式後,需要以模組的方式加入核心。期間交叉編譯適合目標板Kernel的模組尤為關鍵。在此記錄編譯方法,以免看客和我一樣走彎路。 1、在宿主機上安裝交叉編譯器 arm-linux,具體方法百度,一大筐。 2、將和目標板的核心原檔案在宿主

linux編譯C語言程序

編輯器 ins spa include all 運行程序 gcc linux中 style 1.首先安裝gcc編輯器 yum install gcc* -y 2.編寫C語言程序 [[email protected]/* */ ~]# vim aa.c #i