星期六, 4月 19, 2008

Linux 2.6 驱动设计快速入门

引用自: http://dev.csdn.net/article/67/67489.shtm


Linux 2.6 和 2.4 的比较我不想废话,总体来说 2.6 功能更强,但是资源消耗更多。

由于 2.6 内核在驱动框架,底层调用上和 2.4 内核有很多差别,所以本文主要是为程序员提供 2.4 到 2.6 迁移的指导。

2.6 和 2.4 主要的不同在于

• 内核的 API 变化,增加了不少新功能(例如 mem pool )

• 提供 sysfs 用于描述设备树

• 驱动模块从 .o 变为 .ko

移植 hello word

下面是一个最简单的 2.4 驱动:

#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
int init_module(void)
{
printk(KERN_INFO "Hello, worldn");
return 0;
}
void cleanup_module(void)
{
printk(KERN_INFO "Goodbye cruel worldn");
}

2.6的hello world版本!

#include < linux/module.h>
#include < linux/config.h>
#include < linux/init.h>
MODULE_LICENSE("GPL");// 新,否则有 waring, 去掉了 #define MODULE, 自动定义
static int hello_init(void)
{
printk(KERN_ALERT "Hello, worldn");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel worldn");
}
module_init(hello_init);// 必须!!
module_exit(hello_exit); // 必须!!

注意,在 2.4 中 module_init 不是必须的,只要驱动的初始化函数以及析沟函数命名使用了缺省的 init_module 和 cleanup_module

编译生成:

2.4

gcc -D__KERNEL__ -DMODULE -I/usr/src/linux- 2.4.27 /include -O2 -c testmod.c

2.6

makefile 中要有 obj-m:= hello.o

然后:

make -C /usr/src/linux- 2.6.11 SUBDIRS=$PWD modules (当然简单的 make 也可以)

哈哈够简单!!

其他不同:

计数器:

以前 2.4 内核使用 MOD_INC_USE_COUNT 增加计数例如:

void

inc_mod(void)

{

MOD_INC_USE_COUNT;

} /* end inc_mod */

/************************************************************************

* Calculator DEC

************************************************************************/

void

dec_mod(void)

{

MOD_DEC_USE_COUNT;

} /* end dec_mod */

现在这个也过时了 !!

2.6 ,用户函数要加载模块,使用:

int try_module_get(&module);

函数卸载模块使用

module_put()

而驱动中不必显示定义 inc 和 dec

没有 kdev_t 了

现在都流行用 dev_t 了 , 而且是 32 位的

结构体初始化

老版本使用:

static struct some_structure = {

field1: value,

field2: value

};

现在流行用:

static struct some_structure = {

.field1 = value,

.field2 = value,

...

};

malloc.h

要用核态内存?用 <linux/slab.h> 好了,

内存池

内 存管理变化不大,添加了memory pool*(#include<linux/mempool.h> )。(现在什么都是pool,内存 线程 ....)这是为块设备提供的,使用mempool最大的优点是分配内存不会错,也不会等待(怎么这个也和RTEMS学)。对于embed的设备还是有些 用处的。标准调用方式:

mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data);

使用mempool的函数 :

mempool_alloc_t / mempool_free_t

mempool_alloc_slab / mempool_free_slab

mempool_alloc / mempool_free

mempool_resize

结构体赋值

以前,驱动中结构体赋值使用:

static struct some_structure = {

field1: value,

field2: value

};

现在要用:

static struct some_structure = {

.field1 = value,

.field2 = value,

...

};

例如 :

static struct file_operations yourdev_file_ops = {

.open = yourdev_open,

.read = yourdev_read_file,

.write = yourdev_write_file,

};

min() , max()

不少程序员定义自己的 min 和 max 宏,大家也知道宏不安全, Linux 定义了 min 和 max 函数(注意不是模板函数,需要自己制定比较类型!)

原型变化

call_usermodehelper()

request_module()

函数原型变化了,用到的赶快查 !!!

Devfs

唉,还没怎么用,就过时了的技术,这就是 linux 。不是我们落伍,是世界变化快

字符设备

2.6 中主从设备编号不再局限于原来的 8bit

register_chrdev() 可以升级为:

int register_chrdev_region(dev_t from, // 设备号 unsigned count, // 注册号 char *name); // 名称

该函数的动态版本(不知道主设备号时使用)

int alloc_chrdev_region(dev_t *dev, unsigned baseminor,

unsigned count, char *name);

新的 file_operations

register_chrdev_region 没有使用 register_chrdev_region 参数,因为设备现在使用 struct cdev 来定义他在 <linux/cdev.h> 中定义。

struct cdev {

struct kobject kobj;

struct module *owner;

struct file_operations *ops;

struct list_head list;

dev_t dev;

unsigned int count;

};

使用时首先需要分配空间: struct cdev *cdev_alloc(void);

然后对其初始化:

void cdev_init(struct cdev *cdev, struct file_operations *fops);

cdev 使用 kobject_set_name 设置名称:例如:

struct cdev *my_cdev = cdev_alloc(); kobject_set_name(&cdev->kobj, "my_cdev%d", devnum);

此后就可以使用 int cdev_add(struct cdev *cdev, dev_t dev, unsigned count); 将 cdev 加入到系统中。

删除 cdev 使用

void cdev_del(struct cdev *cdev);

对 于没有使用 cdev_add 添加的 cdev 使用 kobject_put(&cdev->kobj); 删除 也就是使用: struct kobject *cdev_get(struct cdev *cdev); void cdev_put(struct cdev *cdev);

太极链;

struct inode -> i_cdev -> cdev

这样就从 inode 链接到 cdev

驱动体系:

/dev 到 /sys

/dev 也过时了,设备文件都跑到 /sys 里了!

# cd /sys

# ls

block bus class devices firmware


星期五, 4月 18, 2008

Build Kernel Module as KO files


Show build progress in verbose

make V=1

lib path for glibc

/opt/microtime/pro/devkit/arm/pxa270/gcc-4.0.2-glibc-2.3.3/arm-unknown-linux-gnu/arm-unknown-linux-gnu/lib

bin path of gcc

/opt/microtime/pro/devkit/arm/pxa270/gcc-4.0.2-glibc-2.3.3/arm-unknown-linux-gnu/bin

header file path of Linux Kernel

/usr/src/creator/pxa270/linux/include

obj-y

cmd_drivers/char/creator-pxa270-lcd.o := arm-unknown-linux-gnu-gcc -Wp,-MD,drivers/char/.creator-pxa270-lcd.o.d -nostdinc -isystem /opt/microtime/pro/devkit/arm/pxa270/gcc-4.0.2-glibc-2.3.3/arm-unknown-linux-gnu/bin/../lib/gcc/arm-unknown-linux-gnu/4.0.2/include -D__KERNEL__ -Iinclude -include include/linux/autoconf.h -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -ffreestanding -fno-inline -Os -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -fno-omit-frame-pointer -mapcs -mno-sched-prolog -mabi=apcs-gnu -mno-thumb-interwork -D__LINUX_ARM_ARCH__=5 -march=armv5te -mtune=xscale -Wa,-mcpu=xscale -msoft-float -Uarm -Wdeclaration-after-statement -Wno-pointer-sign -DKBUILD_BASENAME=creator_pxa270_lcd -DKBUILD_MODNAME=creator_pxa270_lcd -c -o drivers/char/creator-pxa270-lcd.o drivers/char/creator-pxa270-lcd.c

obj-m

step1:
cmd_drivers/char/creator-pxa270-lcd.o := arm-unknown-linux-gnu-gcc -Wp,-MD,drivers/char/.creator-pxa270-lcd.o.d -nostdinc -isystem /opt/microtime/pro/devkit/arm/pxa270/gcc-4.0.2-glibc-2.3.3/arm-unknown-linux-gnu/bin/../lib/gcc/arm-unknown-linux-gnu/4.0.2/include -D__KERNEL__ -Iinclude -include include/linux/autoconf.h -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -ffreestanding -fno-inline -Os -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -fno-omit-frame-pointer -mapcs -mno-sched-prolog -mabi=apcs-gnu -mno-thumb-interwork -D__LINUX_ARM_ARCH__=5 -march=armv5te -mtune=xscale -Wa,-mcpu=xscale -msoft-float -Uarm -Wdeclaration-after-statement -Wno-pointer-sign -DMODULE -DKBUILD_BASENAME=creator_pxa270_lcd -DKBUILD_MODNAME=creator_pxa270_lcd -c -o drivers/char/creator-pxa270-lcd.o drivers/char/creator-pxa270-lcd.c

step2:
scripts/mod/modpost -o /usr/src/creator/pxa270/pro/devkit/lsp/create-pxa270/linux-2.6.15.3/Module.symvers vmlinux drivers/char/creator-pxa270-lcd.o

step3:
arm-unknown-linux-gnu-gcc -Wp,-MD,drivers/char/.creator-pxa270-lcd.mod.o.d -nostdinc -isystem /opt/microtime/pro/devkit/arm/pxa270/gcc-4.0.2-glibc-2.3.3/arm-unknown-linux-gnu/bin/../lib/gcc/arm-unknown-linux-gnu/4.0.2/include -D__KERNEL__ -Iinclude -include include/linux/autoconf.h -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -ffreestanding -fno-inline -Os -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -fno-omit-frame-pointer -mapcs -mno-sched-prolog -mabi=apcs-gnu -mno-thumb-interwork -D__LINUX_ARM_ARCH__=5 -march=armv5te -mtune=xscale -Wa,-mcpu=xscale -msoft-float -Uarm -Wdeclaration-after-statement -Wno-pointer-sign -DKBUILD_BASENAME=creator_pxa270_lcd -DKBUILD_MODNAME=creator_pxa270_lcd -DMODULE -c -o drivers/char/creator-pxa270-lcd.mod.o drivers/char/creator-pxa270-lcd.mod.c

step4:
arm-unknown-linux-gnu-ld -EL -r -o drivers/char/creator-pxa270-lcd.ko drivers/char/creator-pxa270-lcd.o drivers/char/creator-pxa270-lcd.mod.o



星期六, 4月 12, 2008

arm linux 启动流程之 解压内核

引用自: http://blog.csdn.net/dansen_xu/archive/2007/08/13/1740738.aspx

Author-------Dansen-----xzd2734@163.com

从后往前看下编译生成zImage的过程,我们可以找到程序的入口还是那个很重要
链接文件,找到它,生成zImage所在的目录是kernelarcharmbootcompressed
Make过程为....ld -p -X -T vmlinux.lds head.o misc.o head-s3c2410.o piggy.o
libgcc.o -o vmlinux
然后是用二进制工具objcopy把vmlinux制作成可执行的二进制映像文件zImage
这样在我们就去kernelarcharmbootcompressed目录下去找到vmlinux.lds文件
如果没有编译就不会有这个文件,因为它也是在编译过程生成的,由同一目录下的
vmlinux.lds.in生成,打开这个文件
ENTRY(_start)
SECTIONS
{
. = LOAD_ADDR;
_load_addr = .;

. = TEXT_START;
_text = .;

.text : {
_start = .;
*(.start)
*(.text)
........
入口是_start,而且入口就直接定义在这个文件中了
入口直接接着.start段,所以程序开始是从.start段开始执行的
如果看看vmlinux.lds的生成过程就应该能找到LOAD_ADDR和TEXT_START的值
实际上这两个值是由其他两个变量赋给的 ZRELADDR 和 ZTEXTADDR
在kernelarcharmbootMakefile中我们可以找到这两个变量的值
ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif
所以
LOAD_ADDR = 0x30008000
TEXT_START = 0x30008000
看一下vmlinux.lds吧
ENTRY(_start)
SECTIONS
{
. = 0x30008000;
_load_addr = .;

. = 0;
_text = .;
显然LOAD_ADDR被赋值了0x30008000
看一下TEXT_START怎么成0了,我想这应该是一个偏移吧,偏移是0
所以它还是0x30008000
接着下来就从head.s来开始看代码吧
.section ".start", #alloc, #execinstr
/*
* sort out different calling conventions
*/
.align
start:
.type start,#function
.rept 8
mov r0, r0
.endr

b 1f
.word 0x016f2818 @ Magic numbers to help the loader
.word start @ absolute load/run zImage address
.word _edata @ zImage end address
1: mov r7, r1 @ save architecture ID
这里一定就是程序的入口了,一般汇编程序的含义就看看英文注释就是了
有一个要注意的地方,不是一个汇编文件就是属于一个段的,不是说先执行完了
head.s再去执行head-s3c2410.s,还是要注意链接的段,显然head.s
不一会就开始了另一个段.text
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
subs r0, r0, r1 @ calculate the delta offset
而我们的head-s3c2410.s呢
.section ".start", #alloc, #execinstr
__S3C2410_start:
bic r2, pc, #0x1f
add r3, r2, #0x4000 @ 16 kb is quite enough...
还是属于.start段的,所以顺序执行下来时先执行head-s3c2410.s,然后再去执行
.text段。head-s3c2410.s主要是cpu的一些初始化工作。接着下来我们会需要把内核
接压缩,先说说为什么吧。还是注意到上面生成zImage的文件中有一个piggy.o,往上
追寻可以看到是piggy.o由那个真正的内核vmlinux生成的,这个vmlinux才是启动后一直在
运行的内核,原本很大,压缩以后可以方便地放在flash中,当然其实不压缩跳到它的
入口也就可以运行了。解压的内核是准备从LOAD_ADDR = 0x30008000开始的4M空间,会覆盖
我们的当前运行的代码,那样就先把内核解压到我们这个zImage+分配堆栈0x10000的最后
cmp r4, r2 //r4 是LOAD_ADDR=0x30008000
bhs wont_overwrite //r2 是当前代码的最底部 这里当然不会跳转
add r0, r4, #4096*1024 @ 4MB largest kernel size
cmp r0, r5 //r5 也是0x30008000
bls wont_overwrite //不会跳转

mov r5, r2 //r2是(user_stack+4096)在zImage的最后+0x10000
mov r0, r5
mov r3, r7 //machine type
bl decompress_kernel
有了r5,r0,r7作为参数,就可以调用misc.c中的decompress_kernel函数进行解压缩了
这个函数调用的gunzip函数时gcc的库函数,所以在源码中找不到的
解压在r5开始的地方,函数返回的是r0解压得到的长度。这时候我们需要对代码经行调整
add r1, r5, r0 @ end of decompressed kernel
adr r2, reloc_start
ldr r3, LC1 //LC1: .word reloc_end - reloc_start
add r3, r2, r3
1: ldmia r2!, {r8 - r13} @ copy relocation code
stmia r1!, {r8 - r13}
ldmia r2!, {r8 - r13}
stmia r1!, {r8 - r13}
cmp r2, r3 //这里就把从reloc_start到reloc_end这段我们需要的代码放到了
blo 1b //解压内核的最后,而在下面我们会将zImage都覆盖掉
bl cache_clean_flush
add pc, r5, r0 //调到调整后的reloc_start,在decompressed kernel后
reloc_start: add r8, r5, r0 //r5解压内核开始的地方 r0解压内核的长度
debug_reloc_start
mov r1, r4 //r4=0x30008000
1:
.rept 4
ldmia r5!, {r0, r2, r3, r9 - r13} @ relocate kernel
stmia r1!, {r0, r2, r3, r9 - r13}
.endr

cmp r5, r8

blo 1b //这样就又把解压的真正内核移到了0x30008000处

call_kernel: bl cache_clean_flush

bl cache_off

mov r0, #0

mov r1, r7 @ restore architecture number

mov pc, r4 @ call kernel

上面就是跳到0x30008000这里去执行真正的内核了吧