2009年5月12日星期二

Linux下USB驱动基础

作者:Sam (甄峰) sam_code@hotmail.com

USB是主机和外围设备之间的一种连接。USB最初是为了替代各种各样的不同的接口的低速总线而设计的。(例如:串口,并口,键盘连接等)。它以单一类型的总线连接各种不同类型的设备。

USB拓扑机构不是以总线方式的。而是一棵由几个点对点的连接构成的树。连接线由4根电缆组成(电源,地线,两个数据线)

USB主控制器(Host Controller)负责询问每一个USB设备是否有数据需要发送。也就是说:一个USB设备在没有主控制器要求的情况下是不能发送数据的。

USB协议规范定义了一套任何特定类型的设备都可以遵循的标准。如果一个设备遵循该设备,就不需要一个特殊的驱动程序。这些不同的特定类型称之为类(class).例如:存储设备,键盘,鼠标,游戏杆,网络设备等。对于不符合这些类的其他设备。则需要对此设备编写特定driver.



USB设备构成:
Linux Kernel提供了USB Core来处理大部分USB的复杂性。写USB驱动,Sam觉得就是把USB硬件设备和USB Core之间给沟通起来。

USB协议把一个硬件USB设备用以下各个定义勾画出来。

概念一. USB 端点(endpoint)
USB endpoint只能往一个方向传送数据。从主机到设备(输出Endpoint)或从设备到主机(输入Endpoint)。 一个Endpoint可以看作一个单向的管道。

有四种类型Endpoint,他们的区别在于传送数据的方式:
控制Endpoint:
用来控制对USB设备不同部分的访问。他们通常用于配置设备,获取设备信息,发送命令到设备,或者获取设备的状态报告。每个USB设备都有一个名为:Endpoint0的控制Endpoint。USB Core使用该Endpoint0在插入时进行设备的配置。

中断Endpoint:
每当USB主控制器要求设备传输数据时,中断Endpoint就以一个固定的速率来传送少量的数据。
USB Keyboard和Mouse通常使用中断Endpoint。
请注意,中断Endpoint和中断不同,它还是无法主动向USB主控制器发送数据。二是需要等待USB主控制器轮询。

批量Endpoint:
Bulk Endpoint用来传输大批量的数据。USB规范不保证数据能在规定时间内传输完成。但保证数据完整性。通常打印机,存储设备和网络设备使用之。

等时Endpoint:
用来传输大批量数据,但数据是否能够到达,怎无法保证。
通常数据收集设备会使用之。

Sam觉得,其实一个设备有多少个以及什么类型的Endpoint。其实是硬件设备在制造阶段已经定好的。 USB Core只是去读取了这些信息,并把这些信息传送给USB driver.

Linux Kernel中使用struct usb_host_endpoint来描述USB Endpoint。但其实其中的struct usb_endpoint_descriptor才是真正的描述Endpoint的。
struct usb_endpoint_descriptor
{
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));

bEndpointAddress:
//此Endpoint USB地址。它还包含了Endpoint方向信息。通过掩码USB_DIR_OUT和USB_DIR_IN判断是输出Endpoint还是输入Endpoint。

bmAttributes;
Endpoint Type,也可以通过掩码:USB_ENDPOINT_XFER_ISOC等判断此Endpoint是中断,等时,控制还是批量Endpoint。

wMaxPacketSize;
该Endpoint一次可以处理的最大字节数。虽然driver可以传送更大的数据,但实际传送时,还是会分割成这个大小。

bInterval:
如果是中断Endpoint,它就是Endpoint的间隔设置。也就是说,中断请求间隔时间。以毫秒为单位。


概念二:接口(Interface)
数个Endpoint被捆绑为一个USB Interface。
一个 USB Interface只对应一个逻辑连接,例如鼠标,键盘或者音频流。一个USB设备可以对应多个Interface。例如Sam见过的鼠标键盘一起的设备,就有2个Interface,一个键盘,一个鼠标。
另外,有些USB扬声器有2个Interface,一个键盘,一个音频流。

注意:每个USB drver只处理一个USB Interface。所以,一个设备也许会对应多个driver.
所以,USB Core在处理USB设备插入时,会针对不同的Interface唤醒它认为合适的driver。并以参数的形式把interface传递给drver.

Linux Kernel使用struct usb_interface来描述USB Interface。但Interface参数照例是其中的usb_interface_descriptor。

struct usb_interface
{
struct usb_host_interface *altsetting;
struct usb_host_interface *cur_altsetting;

unsigned num_altsetting;
struct usb_interface_assoc_descriptor *intf_assoc;
int minor;
enum usb_interface_condition condition;
unsigned is_active:1;
unsigned needs_remote_wakeup:1;
struct device dev;
struct device *usb_dev;
int pm_usage_cnt;
};

struct usb_host_interface *altsetting;
Interface结构体数组,包含了所有可能用于该Interface的可选配置。
struct usb_host_interface *cur_altsetting;
可选配置数
unsigned num_altsetting;
指向altsetting的指针。当前的Active 设置。


概念三:配置
一个或多个USB Interface被捆绑为配置。一个USB设备可以有多个配置,并且可以在多个配置之间切换。
配置:struct usb_host_config
USB设备:usb_device.

综上所述:
1个USB设备有一个或多个配置
1个配置有一个或多个Interface
一个Interface有一个或多个设置
Interface没有或有多个Endpoint



USB URB
Linux Kernel中的USB代码通过 urb(USB 请求快)与所有的USB设备通信。
urb被用来以异步方式从特定的USB设备的特定USB Endpoint上接收数据,或往特定的USB设备的特定USB Endpoint上发送数据。

urb是由USB driver创建的。并分配给特定USB设备的特定Endpoint。并由USB driver提交给USB Core。

一:创建urb.
urb不能在driver中静态的定义。因为这样会破坏USB Core对urb的计数机制。所以必须使用:
usb_alloc_urb函数来创建。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
第一个参数是:等时数据包的数量。
第二个参数是:memory申请类型。

二:初始化urb:
针对中断Endpoint的urb:usb_fill_int_urb()
针对批量Endpoint的urb: usb_fill_bulk_urb()
针对控制Endpoint的urb: usb_fill_control_urb()
针对等时Endpoint的urb: 需要手动初始化。


三:提交urb:
usb_smbmit_urb();
提交urb到USB Core

2009年5月5日星期二

Linux下USB HID device driver研究(二)

作者:Sam(甄峰) sam_code@hotmail.com

在drivers/hid/usbhid/hid-core.c中,有如下语句:
module_init(hid_init);
表明当hid-usb.o(hid-core.o等三个组成)添加入kernel core时,会调用hid_init.


1. hid_init分析:
hid_init首先调用usbhid_quirks_init();
1.1. usbhid_quirks_init() 解析:
其实就是查找insmod 时给的pid,vid参数在quirks列表中是否有,如果有,就替换。没有就创建。

1.2. hiddev_init();
此function只有在选中CONFIG_USB_HIDDEV才会真正做事。
也就是说:只有在配置kernel时选中下面条目才有效.
config USB_HIDDEV
bool "/dev/hiddev raw HID device support"
它只是简单的注册一个USB设备。但这个设备在USB 硬件插入时什么都不作。

1.3 usb_register(&hid_driver);
注册一个USB driver. 从这个driver的id_table来看,只要是hid设备,这个driver就会被唤醒。


2. HID USB设备被插入时的状况:
分析hid_driver->probe
第一个参数为USB Core传过来的USB设备Interface。第二个参数为本driver的id_table.






背景知识一:模块参数:
当使用insmod或modprobe安装模块时,可以通过模块参数给模块传递一些数值。这增加了模块的灵活性。但在使用模块参数之前,必须要让这些参数对insmod可见,则可以使用如下方式,让insmod知道参数名:
module_param_named(name, value, type, perm)
name是参数的名称
value是参数在模块中对应的变量
type是参数的类型
perm是参数的权限(其实就是/sys/module/[module_name]/parameters的权限)

例如:
int disk_size = 1024;
module_param_named(size, disk_size, int, S_IRUGO);
则给模块加上名称为"size"的参数,如果在加载模块是使用
insmod thismodule size=100,
那么在模块代码中disk_size的值就是100。相反,如果加载模块时没有指定参数,那么模块代码中disk_size的值仍是默认的1024。
注意,所有模块参数,都应该给定一个默认值。

Linux下USB HID device driver研究(一)

作者: Sam (甄峰) sam_code@hotmail.com

Sam需要研究一下USB Mouse device driver. 在Kernel 2.4时代,这块东西是由Input,event等配合组成的。而在kernel2.6中。Sam还不知道是如何运作的。

首先介绍HID:
HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HID设备

在make menuconfig中,选中USB Human Interface Device(full HID) support。则所有USB HID都会被驱动,其中包括USB Mouse。
在drivers/hid/usbhid/Kconfig看到这项对应的为:CONFIG_USB_HID
又在drivers/hid/usbhid/Makefile中看到:obj-$(CONFIG_USB_HID) += usbhid.o
也就是说:如果选中USB Human Interface Device(full HID) support为built-in.则usbhid.o会被built-in。
再在drivers/hid/usbhid/.usbhid.o.cmd中看到usbhid.o其实是由:hid-core.o,hid-quirks.o,hiddev.o组成的。
也就是说:当在Menuconfig中选中那一项后,这三个.o都会被built-in.

下一篇从driver角度学习。

2009年5月4日星期一

Linux kernel 2.6下的modules编译与KBuild

作者: Sam (甄峰) sam_code@hotmail.com


Sam之前在Linux kernel 2.4下写过一些driver.但自从转到kernel 2.6之后,再也没有写过driver.所以很多具体的东西并不清楚。今天看了看文档,觉得变化挺大的。记下来。

在2.4内核中,只要自己写了driver,最多需要kernel头文件来配合编译。通常的CFLAGS为:
=-D__KERNEL__ -I$(KDIR) /include -DMODULE

但在2.6下面,不再是这样。模块的编译需要依赖配置过的内核源码,编译过程首先会到内核源码目标下,读取顶层的Makefile文件
Module编译模板如下:
#Makefile for linux2.6
ifneq ($(KERNELRELEASE),)
# call from kernel build system
scull-objs := main.o pipe.o access.o
obj-m := scull.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
endif

解释:
KERNELRELEASE:由Kernel顶层Makefile中定义。
第一次运行中,KERNELRELEASE没有被定义,所以进入else
$(MAKE) -C $(KERNELDIR) M=$(PWD)
此处读取KERNELDIR中的顶层Makefile。读取后又返回本目录。继续执行本目录Makefile。
则此时KERNELRELEASE被定义。进入if.
if和else之间的为kbuild语法的语句.


2009年4月28日星期二

Linux kernel的Makefile和Kconfig

作者:Sam(甄峰) sam_code@hotmail.com


Sam需要看看2.6 kernel中USB Mouse的代码。顺便谈谈Kernel中Makefile和Kconfig文件的关系以及配合使用。




背景知识:
背景知识一:Kconfig介绍:
#make menuconfig 时,所显示的Menu list是由各层Kconfig组成的。
最底层Kconfig存放在 ~/arch/i386/Kconfig. 以此为头,它会一层层使用source来把需要加入的各个目录中Keconfig添加近来。
例如:source "drivers/Kconfig"
则将~/drivers/Kconfig添加进Menu list中。

背景知识二:Kconfig写法语义:
config HID
tristate "Generic HID support"
depends on INPUT
default y
---help---
A human interface device (HID) is a type of computer device that interacts directly with and takes input from humans. The term "HID" most commonly used to refer to the USB-HID specification, but other devices (such as, but not strictly limited to, Bluetooth) are designed using HID specification (this involves certain keyboards, mice, tablets, etc). This option compiles into kernel the generic HID layer code (parser, usages, etc.), which can then be used by transport-specific HID implementation (like USB or Bluetooth).
For docs and specs, see http://www.usb.org/developers/hidpage/
If unsure, say Y

解释如下:
config HID :表示此条目与CONFIG-HID对应。CONFIG-HID会在Makefile中用到。

tristate "Generic HID support" 引号内的内容是会显示到Menu list中的。tristate表示这一项是三态的。

depends on INPUT:依赖于INPUT这一项。如果没有选中INPUT,则Menu list不会显示这项。

default y :缺省被选中。



背景知识三:built-in.o
vmlinux是Linux源码编译后未压缩的内核, vmlinux是由arch/i386/kernel/head.o和arch/i386/kernel/init_task.o以及各个相关子目录下的built-in.o链接而成的。


背景知识四:Kernel Makefile
Kernel中Makefile的体系以及如何编译的,其实Sam一直是一知半解的。
其中,kernel目录中的Makefile被称为底层Makefile
当使用类似#make menuconfig配置内核成功后,会生成 .config文件。
换句话说:make menuconfig 时,Makefile会从~/arch/i386/Kconfig读取Kconfig.然后根据用户的选择。生成.config文件。
例如:在drivers/hid/Kconfig:
config HID
tristate "Generic HID support"
如果用户选中Y,则在.config中会反映出来:
CONFIG_HID=y
则在~/drivers/Makefile中可以看到:
obj-$(CONFIG_HID) += hid/
表明:如果CONFIG_HID是Y,则把hid目录添加到要编译的目录中了。
进入到/driver/hid目录,则看到:
hid-objs := hid-core.o hid-input.o
表明这两个.o文件是一定会被编译出的。
obj-$(CONFIG_HID) += hid.o
表明:如果CONFIG_HID是Y,则hid.o会被编译出来。并built-in.
如果是 =m. 则hid.o被编译出来,但最后被做成modules(ko)


背景知识五:KBuild Make:
Linux内核的Makefile与我们平时写的Makefile有所不同,它由五部分组成:
1.Makefile : 顶层Makefile。
2. .config: kernel配置文件。
3. arch/xxx/Makefile: 具体架构的Makefile。
4. scripts/Makefile.xxx : 通用规则。
5. kbuild Makefile: 整个kernel中大约有数百个这种文件。

#make menuconfig后,生成 kernel配置文件: .config。
顶层Makefile读取.config.
顶层Makefile通过解析 .config来决定递归访问哪些目录中的Kbuild Makefile .
这个过程中,Kbuild Makefile会按.config的设置,逐个添加文件列表,以供最后的编译使用。
最简单的KBuild Makefile如下:
obj-y += foo.o
表明:Kbuild在这目录里,有一个名为foo.o的目标文件。foo.o将从foo.c或foo.S文件编译得到。并且它会被包入built-in中去。
所有编译进内核的目标文件都存在$(obj-y)列表中。而这些列表依赖内核的配置。Kbuild编译所有的$(obj-y)文件。然后,调用"$(LD) -r"将它们合并到一个build-in.o文件中。稍后,该build-in.o会被其父Makefile联接进vmlinux中。

如果foo.o要编译成一模块,那就要用obj-m了。所采用的形式如下:
obj-m += foo.o



例一:
在 ~/driver/hid/hid-core.c中,有以下语句,即内核insmod接口hid_init.
module_init(hid_init);
也就是说,当此模块被buildin或者作为module insmod时,kernel会自动调用hid_init.
然后查看 ~/driver/hid/Makefile,发现
hid-objs := hid-core.o hid-input.o
表明只要hid这个目录被加入,就会生成hid-core.o.
只好去看上一层目录中怎样会进入hid目录:
obj-$(CONFIG_HID) += hid/
表明只要CONFIG_HID=Y,m. 则hid目录被加入。
但用户作了什么,hid目录被加入编译呢?则看~/drivers/hid/Kconfig
config HID
tristate "Generic HID support"
以此得之只要在make menuconfig中选中此项,则hid-core.o被编译出来。


例2:
Sam想要研究USB Keyboard & Mouse driver. 在 make menuconfig时,需要选中:
config USB_HID
tristate "USB Human Interface Device (full HID) support"
则查看Makefile。发现只要选中CONFIG_USB_HID.则会编译出usb_hid.o
但~/drivers/hid/usbhid目录中却没有usbhid.c。那usbhid.o如何生成的呢?
drivers/hid/usbhid目录中,有个.usbhid.o.cmd文件。

2009年4月20日星期一

X5平台上几个问题的思考

作者:Sam(甄峰) sam_code@hotmail.com



问题1:
Sam在使用X5平台时,发现如果kernel升级一次(kernel版本没有变化),则之前编译好的modules insmod时会出现错误:
Unknown symbol in module (-1): No such file or directory

Sam刚开始想当然的认为是因为有人对kernel中版本信息作了变化。于是打开:include/linux/version.h。却发现其中UTS_RELEASE并没变化。
感觉非常奇怪。这个问题等待之后解决。

问题2:
Sam在使用X5平台时。通常是使用芯片提供商提供的Makefile直接编译出kernel.后来有一次无意中自己进入kernel目录,make menuconfig;make clean;make dep;make bzImage
得到的Image直接烧入,发现出错。
HiBoot 1.1.0.0 (Jan 8 2009 - 12:16:13)
HiBoot code: 60E00000 -> 60E37254
BSS: -> 60E62888
RAM Configuration: Bank #0: 60000000 128 MB Flash: 16 MB MAC: 00-00-00-00-00-02
Hit any key to stop autoboot: 0
Bad Magic Number

Sam检查多次,也没发现自己做错了什么。于是去查看芯片提供商给的外部Makefile.
其实很简单,就是copy 他们的config到linux-2.6.14/.config
然后编译kernel.最后则作了个特殊处理:
#mk-ulinux $(LINUXDIR) 0x60a00000 kernel-2.6.14-hi
Sam怀疑这是对kernel Image做了重新编排,加入一些标志然后作成kernel-2.6.14-hi。然后在u-boot中检查这些标志。
这是嵌入式系统中常用的一些做法,Sam竟然没想到,真不应该。


问题3:
在X5平台上,Sam使用动态库,编译时没问题,但运行时会报错:
Can't modify xxx.so's text section. Use GCC option -fPIC for shared objects, please.
但Sam其实在编译SO时使用了参数 -shared -fpic.
所以怀疑是编译器的问题。
Sam之前做交叉编译器都是用工具作的,对里面很多东西并不了解。看来什么时候需要具体作一个了。把类似:缺省 -I, -L目录,CPU类型等等全搞清楚。

2009年4月14日星期二

RedBoot常用命令(转载)

常用命令:
1 cache
使用格式:cache [on off]
功能描述:cache命令用于管理微处理器的cache。在传输大容量的文件时,最好是把cache打开。
Redboot>cache //显示系统当前cache状态
Redboot>cache on //打开cache
Redboot>cache off //关闭cache

2 channel
使用格式:channel [-l channel number]
功能描述:如果不带任何参数,channel命令会显示当前的控制台通道号;如果参数为-1,则将控制台通道切换到默认的控制台通道;若参数为硬件平台所支持的其他控制台号,则channel命令就对控制台作相应的切换。

3 dump
使用格式:dump [-b location] [-l length] [-s] [-1 -2 -4]
功能描述: 显示参数指定区域的数据,显示方式由参数指定。
-b 存储器的起始位置
-l 显示的长度
-s 使用Motorala S-reconds格式显示数据
-1 按单字节显示数据
-2 按双字节显示数据
-4 按四字节显示数据

4 exec
使用格式:exec [-w timeout] [-r ramdisk_address] [-s ramdisk_length] [-b load_address] [-l load_length] [-c kernel_command_line] [entry_point]
功能描述:执行一个映象文件,如引导Linux内核
-w 执行映象文件之前的等待时间
-r 传递给内核的ramdisk_address起始地址
-s 传递给内核的ramdisk_address长度
-b 内核映象文件地址
-l 内核映象文件长度
-c 传递给内核的命令行

5 fis creat
使用格式:fis creat [-b data_address] [-l length] [-f flash_address] [-e entry] [-r relocation_address] [-s data_length] [-n] [name]
功能描述:在FIS(Flash Image System)目录中创建一个映象,将当前RAM中的数据写入FLASH存储器中。因此,在使用该命令之前,映象文件数据必须已经保存在RAM中。
-b 待写入flash数据的存放地址
-f flash地址
-e 可执行映象地址
-r 执行fis load命令时,可执行映象的重定位地址
-s 写入flash中的可执行映象的实际长度
-n 用于更新FIS目录
name 创建映象的名称

6 fis init
使用格式:fis init [-f]
功能描述:初始化FIS目录,-f表示将所有的flash空间初始化

7 fis list
使用格式:fis list [-c] [-d]
功能描述:显示FIS中当前的所有映象文件
-c 显示映象的校验和
-d 显示映象的长度

8 fis free
使用格式:fis free
功能描述:显示flash当前的空闲空间

9 fis delete
使用格式:fis delete [name]
功能描述:删除FIS目录中的映象。name为需要删除映象的名称。
举例:
Redboot>fis delete ramdisk.gz

10 fis lock
使用格式:fis lock [-f flash_address] [-l length]
功能描述:锁定flash空间
-f 锁定flash空间的起始地址

11 fis unlock
使用格式:fis unlock [-f flash_address] [-l length]
功能描述:解除flash空间的锁定

12 fis erase
使用格式:fis erase [-f flash_address] [-l length]
功能描述:擦除指定的flash空间

13 fis write
使用格式:fis write [-b mem_address] [-l length] [-f flash_address]
功能描述:将数据由RAM写入FLASH中
-b 待写数据在RAM中的起始地址
-f 写入Flash的起始地址

14 fconfig
使用格式:fconfig
功能描述:对已保存在flash中的配置选项进行管理和重配置。

15 go
使用格式:go [-w timeout] [start_address]
功能描述:执行放在某一位置的可执行代码
-w 执行代码前的等待时间
start_address 可执行代码的起始地址

16 ip_address
使用格式:ip_address [-l local_ip_address] [-h server_ip_address] [-d DNS_server_ip_address]
功能描述:设置或改变系统使用的IP地址

17 load
使用格式:load [-r] [-v] [-h host] [-m varies] [-c channel_number] [-b base_address] [file_name]
功能描述:下载数据到目标系统RAM中
-r 下载未处理的数据到RAM
-v 下载过程显示进度
-b 数据下载到RAM的地址
file_name 下载的文件名

18 mcmp
使用格式:mcmp [-s location] [-d location] [-l length] [-1 -2 -4]
功能描述:比较两个存储区域的内容
-s 源区域起始地址
-d 目的区域起始地址
-l 需要比较数据的长度
-1 单字节读取
-2 双字节读取
-4 四字节读取

19 mcopy
使用格式同mcmp,功能就是将数据从一个存储区域复制到另一个存储区域

20 mfill
使用格式:mfill [-b location] [-l length] [-p value] [-1 -2 -4]
功能描述:将给定的数值填充到指定的存储区域

21 reset
功能描述:复位系统

22 ping
使用格式:ping [-v] [-n count] [-l length] [-t timeout] [-r rate] [-i IP_addr] [-h IP_addr]
功能描述:向指定主机发送ICMP报文,用于检查网络是否正常。
-v 显示数据包信息
-n 发送数据包的数目
-l 发送报文的长度
-t 设置超时时间
-r 发送数据包的间隔时间
-i 本机IP地址
-h 远端主机IP地址

23 help

24 version

2009年4月12日星期日

Intel CE2110上Redboot的使用

作者:Sam(甄峰) sam_code@hotmail.com


Sam很早之前使用过redboot。后来很快忘记如何使用了。 之后在Intel CE2110以及 CE3100上使用了redboot.结果现在有段时间不用,又忘记了。只好再看一遍。这次能记得住吗?光头葛说:我看(不)行!

Redboot简介:
Redboot是Redhat公司随eCos发布的一个BOOT方案,是一个开源项目。
Redboot支持的处理器构架有ARM,MIPS,MN10300,PowerPC, Renesas SHx,v850,x86等,是一个完善的嵌入式系统Boot Loader。
Redboot是在ECOS的基础上剥离出来的,继承了ECOS的简洁、轻巧、可灵活配置、稳定可靠等
品质优点。它可以使用X-modem或Y-modem协议经由串口下载,也可以经由以太网口通过
BOOTP/DHCP服务获得IP参数,使用TFTP方式下载程序映像文件,常用于调试支持和系统初始化。


RedBoot的基本用法:
1. 进入Redboot 设置界面:
在Intel-CE2110重新启动时,按下Ctrl+C。 则进入Redboot配置模式下。
出现以下提示符,表明进入RedBoot设置界面。
RedBoot>
在此界面下,可以使用RedBoot命令执行操作。

2. 修改RedBoot boot script。此script将被RedBoot在启动时使用。
首先进入RedBoot boot script edit界面。
RedBoot> fconfig
则出现:
>>
在这里输入: RedBoot boot script内容。

若发现fconfig后,出现莫名其妙的错误,可以清空boot script,方法:
RedBoot>fconfig -i



Intel CE2110上,按照kernel和rootfs放置的不同方式,有3种启动方式(与CE3100很类似):
1. kernel和rootfs全部放置在tftp server上。
2. kernel烧入到NOR flash中,rootfs则使用 nfs server中的。
3. kernel和rootfs全部烧入NOR flash中
前2种方式在开发阶段非常有用。可以快速修改kernel和rootfs.



1. kernel和rootfs全部放置在tftp server上:
1.1: copy zImage(kernel)和initrd_media.gz(rootfs)到/tftpboot
1.2:
RedBoot> fconfig
>>load –v –r –m tftp –h 172.16.1.61 –b 0x200000 zImage_olo
>>load –v –r –m tftp –h 172.16.1.61 –b 0x1000000 initrd_media.gz
>>exec –r 0x1000000 –s 0x12B2BDD –l 0x300000 –c “console=ttyS0, 115200 mem=100M@0 root=/dev/ram0 init=/linuxrc” 0x200000
则可以使用了。(但Sam使用这个办法时总有问题。显示乱码。)
现讲解命令如下:
load: 下载数据到目标系统RAM中.
-v: 下载过程显示进度
-r: 下载未处理的数据到RAM
-b: 数据下载到RAM的地址
-m: 方式
-h: host

exec: 执行一个映象文件,如引导Linux内核
-r: 传递给内核的ramdisk_address起始地址
-s: 传递给内核的ramdisk_address长度
-b: 内核映象文件地址
-l: 内核映象文件长度
-c: 传递给内核的命令行

2. kernel烧入到NOR flash中,rootfs则使用 nfs server中的。 (Sam还是有问题)
2.1. kernel burn 到flash。
2.1.1 把新kernel--zImage放到/tftpboot
2.1.2 Ctrl-c 进入redboot模式。
2.1.3烧入:
RedBoot>fis init
RedBoot>load -v -r -m tftp -h 172.16.1.61 -b 0x00200000 zImage_olo
RedBoot>fis unlock -f 0xc0040000 -l 0x00200000
RedBoot>fis create -b 0x00200000 -l 0x00200000 zImage

注解如下:
fis 是RedBoot FIS(Flash Image System)相关命令。
RedBoot>fis init
初始化FIS目录,-f表示将所有的flash空间初始化

RedBoot>load -v -r -m tftp -h 172.16.1.61 -b 0x00200000 zImage_olo
将zImage_olo这个文件从tftp上copy到RAM 0x00200000 处。

RedBoot>fis unlock -f 0xc0040000 -l 0x00200000
解除flash空间的锁定,-f flash_address,-l length

RedBoot>fis create -b 0x00200000 -l 0x00200000 zImage
在FIS(Flash Image System)目录中创建一个映象,将当前RAM中的数据写入FLASH存储器中。因此,在使用该命令之前,映象文件数据必须已经保存在RAM中。
-b:待写入flash数据的存放地址(RAM地址)
-l:长度
zImage:创建映象的名称
Sam想,为什么没有用 -f(flash地址),是因为上一句指令指定了unlock的flash.所以缺省放在那了。

2.2 准备rootfs在nfs中:


进入redboot config
RedBoot>fconfig
>>fis load zImage
>>exec -c "console=ttyS0,115200 mem=100M@0 root=/dev/nfs nfsroot=172.16.1.61:/home/sam/Intel, nolock ip=dhcp" 0x200000


3. kernel和rootfs全部烧入NOR flash中(重点)
3.1把Kernel(zImage)和rootfs(busybox_media.jffs2)放到/tftpboot中。

3.2ctrl-c进入redboot模式。
3.3烧入:
RedBoot〉fis init
RedBoot〉load -v -r -m tftp -h 172.16.1.61 -b 0x00200000 zImage_olo
RedBoot〉load -v -r -m tftp -h 172.16.1.61 -b 0x01000000 busybox_media.jffs2
RedBoot〉fis unlock -f 0xc0040000 -l 0x00200000
RedBoot〉fis create -b 0x00200000 -l 0x00200000 zImage
RedBoot〉fis create -f 0xc0240000 -l 0x01d80000 -n root
RedBoot〉fis unlock -f 0xc0240000 -l 0x01d80000
RedBoot〉fis erase -f 0xc0240000 -l 0x01d80000
RedBoot〉fis write -f 0xc0240000 -l xxxxxx -b 0x01000000


注意:xxxxxx是rootfs的长度,需要经过计算得到
也就是在load -v -r -m tftp -h 172.16.1.61 -b 0x01000000 busybox_media.jffs2之后,
看到其输出:Raw file loaded 0x01000000-0x01e5ffff
则长度为:0x01e5ffff-0x01000000+1=0xE60000
RedBoot〉fis write -f 0xc0240000 -l 0xE60000 -b 0x01000000


讲解如下:
RedBoot〉fis init
RedBoot〉load -v -r -m tftp -h 172.16.1.61 -b 0x00200000 zImage_olo
RedBoot〉load -v -r -m tftp -h 172.16.1.61 -b 0x01000000 busybox_media.jffs2

初始化FIS目录。并把zImage_olo和rootfs分别放到RAM 0x200000和0x1000000位置。

fis unlock -f 0xc0040000 -l 0x00200000
解除flash空间的锁定

RedBoot〉fis create -b 0x00200000 -l 0x00200000 zImage
RedBoot〉fis create -f 0xc0240000 -l 0x01d80000 -n root

将RAM中0x00200000处的东西(zImage)放到Flash xC0040000处. 并创建名为zImage的分区。
在Flash起始地址0xc0240000 创建为root分区。

RedBoot〉fis unlock -f 0xc0240000 -l 0x01d80000
RedBoot〉fis erase -f 0xc0240000 -l 0x01d80000
RedBoot〉fis write -f 0xc0240000 -l xxxxxx -b 0x01000000

把root分区所在flash解压缩。
把root分区所在Flash Erase.
把RAM 0x01000000的东西(rootfs)放到0xc0240000的FLASH中。

至此:把kernel和rootfs放到flash各自区域中了。


3.4进入redboot config
RedBoot〉fconfig
>>fis load zImage
>> exec -c "console=ttyS0,115200 mem=100M@0 rootfstype=jffs2 rootflags=noatime root=/dev/mtdblock2 rw" 0x200000

一切正常。

2009年4月10日星期五

Intel CE2110 (Olo) PDK Install and system build

作者:Sam(甄峰) sam_code@hotmail.com

Sam之前在Intel CE2110(Olo)上作程序。后来转到Intel CE3100(Canmore) 上。可天有不测风云,今天又需要回到Intel CE2110上编译一个程序。却发现连编译环境都忘记了。只好老老实实再作一次。

1. 安装PDK
如果为ISO格式文件:
#mount -t iso9660 -o loop Intel_CE_2110-2-1[1].358.iso mount_dir
#sh installer.sh /home/sam/work/current/Intel_CE_2110/Intel_CE_2110_Dev/
于是PDK被安装到指定目录。

2. 安装Toolchain。
toolchain为:gcc-3.4.5-glibc-2.3.6.tar.gz。
把它解压后放到/usr/local/下即可。

3. 整体编译:
#make all

4. 编译我们所需要的driver, protocol module.
#cd ~/Intel_CE_2110-1.x.xxx/linux-2.6.16.16
#make menuconfig
network->bluetooth中选中Modules

5. 编译Bluetooth 库文件:
Sam暂时使用老的bluetooth lib库。编译出 bluetooth.so

6. 编译我们的程序:
修改Makefile。很容易就可以编译了。

7. 架设Olo板:

2009年4月1日星期三

MTK平台程序开发-3

作者:Sam(甄峰) sam_code@hotmail.com

MTK平台程序开发-2

作者:Sam(甄峰) sam_code@hotmail.com

MTK程序将数据分为动态和静态数据。
动态数据指程序运行时才知道,由程序动态生成的。
静态数据指固定不变的数据,可以在程序编译时转换为二进制数据存入烧录文件中。

静态数据被称为资源。常见的资源有以下几种:字串,图像,菜单,字库,主题,声音等。

添加一个资源需要3个步骤:原料,ID,装载
原料其实就是数据源,如图片,字串等。
ID:资源的别名,程序只能通过ID得到资源。
装载:将原料转换为二进制数据,并与ID联系起来。

资源装载预编译程序是plutommi\Customer\ResGenerator\mtk_resgenerator.exe,这个程序在每次编译目标烧入文件之前临时编译生成。

添加资源步骤1:添加增加资源入口:
在plutommi\Customer\CustResource\PLUTO_MMI\Res_MMI添加一个Res_BluetoothUI.c 。
这个程序提供一个如何添加以及添加何种资源的function.

void PopulateBluetoothUIRes(void)
{
}
注意,这个C文件是预编译时提供给预编译程序。用来指定怎么添加以及添加何种资源的。

添加资源步骤2:修改Makefile
在plutommi\Customer\ResGenerator\Makefile中。
-I "plutommi/mmi/MainMenu/MainMenuInc" \
-I "plutommi\mmi\BluetoothUI\BluetoothUIInc" \
注意:此Makefile是资源预编译程序mtk_resgenerator.exe的Makefile

添加资源步骤3:修改PopulateRes.c
把PopulateBluetoothUIRes() 添加到PopulateResDate()中。当预编译程序mtk_resgenerator.exe运行时,会按照我们提供的接口PopulateBluetoothUIRes()中的内容把资源添加上去。

添加资源步骤4:添加资源ID:
因为每个类型资源的ID都在同一个取值空间。所以不能重复。
plutommi\mmi\Inc\MMIDataType.h
它实际是是提供了每个程序的BASE-ID.


现在以字串资源为例:
1. 在BluetoothUIDefs.h中添加字串ID:
STR_BLUETOOTHUI_STRING = BLUETOOTHUI_BASE+1,
注意:BLUETOOTHUI_BASE是在上面plutommi\mmi\Inc\MMIDataType.h中添加的。

2. 将ID与资源内容对应起来:
plutommi\Customer\CustResource\PLUTO_MMI\ref_list.txt中添加:
STR_BLUETOOTHUI_STRING 3Dijoy 鼎亿科技
表示:STR_BLUETOOTHUI_STRING这个ID与3Dijoy(英文) ,鼎亿科技(中文对应)

3. 字串装载:
在plutommi\Customer\CustResource\PLUTO_MMI\Res_MMI\Res_BluetoothUI.c

void PopulateBluetoothUIRes(void)
{
ADD_APPLICATION_STRING2(STR_BLUETOOTHUI_STRING,"3DiJoy", "");
}
第一个参数为:ID
第二个参数为:缺省资源(当ref_list.txt中没有此ID对应的资源时,使用这个)
第三个参数:字串描述,可以忽略

4. 字串读取:
GetString()可以从ID中读取数据。

gui_print_text((UI_string_type)GetString(STR_BLUETOOTHUI_STRING));



以下添加菜单:

2009年3月30日星期一

MTK平台程序开发-1

作者:Sam(甄峰) sam_code@hotmail.com


Sam以MediaTek MT62xx为平台开发。首先介绍MediaTek开发平台。

MediaTek提供标准软件包,它包含完整的一体化软件,由GSM/GPRS L1和协议栈,device driver,应用程序和MMI组成。通常情况下,采用MTK解决方案的用户,只需要修改一小部分代码来配合他们的硬件修改和定制。目录位于:.../mcu/custom.

程序安装:
1.ADS安装。
2.copy ADS_1.2 Others\ADS_1.2\ADS Patch\armAdsBuild842\bin中内容到 C:\Program Files\ARM\ADSv1_2\Bin
3.copy ADS_1.2 Others\ADS_1.2\ADS Patch\armlib_build_837\Adsv1_2\Lib\armlib内容到 C:\Program Files\ARM\ADSv1_2\Lib\armlib
就是修改编译器和lib.
4. ActivePerl安装:
ActivePerl-5.8.4.810-MSWin32-x86

驱动安装:

程序编译:
打开cmd,进入字符界面。进入source code 目录。
1>. make ivy grps r
然后开始编译。编译完成后,会生成目录 ~\build\IVY\IVY_PCB01_gprs_MT6235B_S01_IVY_SW_0.9.bin, image文件就放在其中。

编译命令的解释:
make xxx gprs new
make xxx gprs update
make xxx gprs remake
make xxx gprs clean
编译成功后,在build目录下生成所有的.obj,编译信息log文件、下载.bin文件等;编译失败时,到build目录下查看相应的log文件,可看到出错信息。

几个关键字解释如下:
1). xxx,Project名,对应make目录下的xxx_GPRS.mak文件;
2) 最后一个关键字:
a. new:不管资源、代码是否有改变,全部重编;
b. update:扫描资源、代码的改变,有改变的重编,无改变的不编;
c. remake:不扫描资源,只扫描代码的改变,有改变的重编,资源和无改变的代码不编;
d. clean:清除build目录下的所有内容,保留目录结构。



程序烧入:
将手机和PC用烧入线连接起来。
运行FlashTool_v3.0836.00\Flash_tool.exe
选择Scatter-loading, 选择~build\IVY\IVY_PCB01_gprs_MT6235B_S01_IVY_SW_0.9.bin\scatIVY.txt文件
Option-〉选择对应的serial port.
关机
按下download
开机,之后开始download



Sam的工作从MMI开始。
MMI:Man Machine Interface。即人机界面。
首先加入一个UI,上面写上:Hello World!


建议加入的代码放到新目录内:
一:目录结构的创建:
首先在plutommi\mmi目录中创建BluetoothUI目录,并在其中创建BluetoothUIInc目录和BluetoothUISrc。顾名思义,里面分别放头文件和源文件。

Sam在plutommi\mmi\BluetoothUI\BluetoothUISrc中创建了一个源文件:BluetoothUISrc.c
又在plutommi\mmi\BluetoothUI\BluetoothUIInc中创建了:
BluetoothUIProt.h :本程序所有函数声明,但只被本程序include
BluetoothUIDefs.h: 本程序的资源ID.
BluetoothUITypes.h:放置本程序使用的所有类型,结构,常量。
BluetoothUIGprot.h:需要被别的程序使用的函数声明,被别的程序include。

二:程序的编写:
void mmi_BluetoothUI_entry(void)
{
//从前一个Screen中退出,进入当前Screen,如果不这样,那前一个Screen还在响应一些消息。
EntryNewScreen(SCR_3DIJOY_SCREEN, NULL, mmi_BluetoothUI_entry, NULL);
//清屏
clear_screen();
//指针位置
gui_move_text_cursor(2, 100);
//字符颜色
gui_set_text_color(UI_COLOR_GREEN);
//写字符入屏幕
gui_print_text(L"Bluetooth Game Controller");

gui_move_text_cursor(60, 200);
gui_set_text_color(UI_COLOR_BLACK);
gui_print_text(L"3DiJoy Inc.");

//刷新,否则字符没有显示
gui_BLT_double_buffer(0, 0, UI_device_width - 1, UI_device_height - 1);
return;
}

并且:SCR_3DIJOY_SCREEN需要被加入到ConnectivityResDef.h中。Screen ID

三:程序加入主程序:
MTK程序与PC或其它Linux嵌入式程序不同,它的程序与整个系统是固定在一起的。Nuclues类似于UC/OS-II。所以Sam只提供了一个 mmi_BluetoothUI_entry()供别人调用。为了测试这个函数,Sam将它加入到:
plutommi\mtkapp\AudioPlayer\AudioPlayerSrc\AudioPlayerSrc.c中的mmi_audply_app_pre_entry()中。也就是“多媒体-〉音乐播放器”的入口程序中。

如同Makefile中需要指定某个obj需要被编译出一样。我们也需要指出BluetoothUISrc.c需要被编译。
Sam先查看AudioPlayerSrc.c在make目录中是被加入到哪里的。发现是:
make\plutommi\media_app\media_app.lis中。于是需要将BluetoothUISrc.c也加入到这个文件中以指出这个.c需要被编译。
并在make\plutommi\media_app\media_app.pth中加入源码路径:
plutommi\mmi\BluetoothUI\BluetoothUISrc,
编译时会报一些东西未定义,只需要加入相应头文件即可。
注[1]
编译成功,烧入手机,进入多媒体-〉音乐播放器,则显示自己的UI。









注1:之前Sam严格按照目录创建了头文件,然后在AudioPlayerSrc.c中include。但在make\plutommi\media_app\media_app.inc指定头文件目录后,还是无法找到头文件,估计是需要 make ivy gprs n
但我们并没有所有的源码,所以无法成功编译。

2009年3月25日星期三

SDL学习

作者:Sam(甄峰) sam_code@hotmail.com

Sam在移植SDL时遇到一些问题,虽然解决了。但还是有点知其然,不知其所以然的感觉。主要原因是对SDL不熟悉造成的。所以抽空利用SDL的test程序学习一下SDL编程。


先看几个常用的structure:
1. SDL_Surface: Graphical Surface Structure
这个结构体实际上是给出了一个可以显示的Surface。其中包含了surface大小,bit_per_pixel,以及pixel data.
它通常是由SDL_SetVideoMode()创建。


以testsprite为例:
1.初始化Video
SDL_Init(SDL_INIT_VIDEO);

2.使用SDL_SetVideoMode()创建一个定制长宽,以及bit-per-pixel的surface。
screen = SDL_SetVideoMode(width, height, video_bpp, videoflags);
创建了一个Surface_A(背景 surface).

3. 使用SDL_LoadBMP()load 图像,并把数据存到一个SDL_Surface ---Surface_B(笑脸surface)中。

4. 使用SDL_DisplayFormat copy Surface_B到一个可以被快速显示的Surface_C(将要显示的笑脸surface)中。

5. 使用SDL_MapRGB()将Surface_A(背景 surface)的 format设置。

6. 使用SDL_BlitSurface()将Surface_C(将要显示的笑脸surface) 快速copy到Surface_A(背景 surface)的0,0位置,大小与Surface_C大小一致。

7. 使用SDL_FillRect()将Surface_A(背景 surface)刚才被copy的位置给盖起来了。Sam:等于没copy.

8. 在loop中:
8.1 首先使用SDL_FillRect()将Surface_A(背景 surface)重新用背景色填充。Sam:Erase

8.2 使用SDL_BlitSurface()将Surface_C(将要显示的笑脸surface) 快速copy到Surface_A(背景 surface)的某个位置(Rand)。

8.3 调用SDL_Flip()或者SDL_UpdateRects()重新画。

GCC,glibc, uclibc,stdc的一些基本概念

作者:Sam(甄峰) sam_code@hotmail.com

Sam有一个C++程序,其中用到:
std::wstring mbstowcs(std::string str);
使用X5平台的编译器编译时,遇到如下问题:
error: expected constructor, destructor, or type conversion before "mbstowcs"
error: expected `,' or `;' before "mbstowcs"

Sam就开始寻找原因。顺便把gcc,glibc,uclibc,stdc++等东西的概念性东西放在这里。

GCC:gcc(gnu collect compiler)是一组编译工具的总称。它主要完成的工作任务是“预处理”和“编译”,以及提供了与编译器紧密相关的运行库的支持,如libgcc_s.so、libstdc++.so等。

glibc:glibc是gnu发布的libc库,也即c运行库。glibc是linux系统中最底层的api,几乎其它任何的运行库都会倚赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现.

uclibc:uclibc是另一c运行库,与glibc对应。它比glibc小。虽然uClibc和Glibc在已有的接口上是兼容的,但有些接口并没有实现。

libstdc++:libstdc++ 是GNU C++ standard Library .


Sam觉得既然编译就通不过,那说明X5的编译器本身就不认识,有可能是X5平台的交叉编译器在创建时没有添加stdc支持?后来觉得不是这样,因为STL完全是以头文件形式提供的。
1.所以只需要指定头文件路径,就应该可以编译。于是Sam添加了:
-I/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/c++/3.4.3/
(Sam觉得海思的交叉编译器做得不是特别规范,为什么呢。因为它放头文件,库文件的地点多变)
添加这个之后,理论上应该是可以编译了。
可发现还是不认识wstring.
2.于是查看/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/c++/3.4.3/bits/stringfwd.h。
发现要定义_GLIBCXX_USE_WCHAR_T才会有wstring.
3.于是在编译程序时添加了 -D_GLIBCXX_USE_WCHAR_T
还是通不过,说没有wint_t。
4.于是又查STL. 添加了 -D__WINT_TYPE__
又通不过, 说没有 btowc,Sam查遍了toolchain. 也没找到这个类型的定义。感觉很奇怪,就去查/usr/include. 发现这个类型有定义。但toolchain中没有对应的头文件。

所以,Sam觉得很多做嵌入式程序的工程师都不愿使用STL,喜欢用标准C,是有原因的,因为toolchain限制太大。很多提供toolchain的公司对类似STL的提供都不是很全。

2009年3月19日星期四

scons 学习

作者:Sam(甄峰) sam_code@hotmail.com

http://www.scons.org/

Sam有个好朋友是做游戏的,整天嘲笑做嵌入式的用的工具多土多原始。gdb不是图形化,DDD又没VC调试好用,只会写Makefile等等。
刚好最近有需要使用scons。所以Sam准备学习之以回击这种挑衅。呵呵。

scons简介:
scons是一个Python写的自动化构建工具,从构建这个角度说,它跟GNU make是同一类的工具。

scons与其它工具最显著的差别就是:scons配置文件是Python script.

它的思想是跟GNU make完全不同的。GNU make的核心是“依赖关系”,我要做的事情,就是告诉系统,一个目标依赖什么东西,并且,当被依赖的东西发生变化时,我要做什么。
例如:
all: main.c
$(CC) $(CFLAGS) $(LFLAGS) main.c -o BT_remote -lbluetooth -lBluetooth_remote -lm -lpthread
意思是:all依赖于main.c。 如果main.c有了变化,作下面这些事。


这样做可以解决相当多的问题,但是也带来了一个最大的问题:我如何判别这个目标依赖什么?

对于一个两个,甚至十几个文件,我当然还比较容易搞清楚,谁依赖谁。但是当文件有成百上千个时,要分清楚谁依赖谁可就没这么容易了。尤其是C/C++头文件的依赖,如果手工分析的话,工程量可是不小。为了解决这个问题,GNU又提供了另外一套工具:Automake,使用程序来分析依赖性,然后辅助你产生makefile。

于是乎,就有人想了,既然如此,我干吗费那劲,用程序分析依赖性,然后生成一个文件,再交给另外一个程序去处理呢?既然依赖性需要用程序来分析,那么就直接交给构建工具本身去做不就好了吗?对的,这是一个非常自然的思路,于是,Java世界有了Ant,而Python世界有了scons。

scons就是这样一个构建工具:你告诉它要做的任务,以及完成这个任务需要的输入,以及这个任务产生的输出,怎么做这个任务(当然其中就包括依赖性分析),就交给工具本身完成。


例1:
只有一个 test.c文件,需要编译成可执行文件。
创建SConstruct,内容为:
Program('test.c')
这句话透露2层意思:
1.编译方法:想要编译成什么类型。(Program:可执行文件)
2.哪个东西想编译(test.c)
#scons
% scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o test.o -c test.c
cc -o test test.o
scons: done building targets.

clean:
#scons -c
则把编译出的东西又删除了,类似make clean


例2:
只有一个文件,想编译成object文件。
Object('test.c')
1.编译方法:想要编译成什么类型。(Object: .o文件)
2.哪个东西想编译(test.c)

#scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
scons: done building targets.

清空:
#scons -c
则把编译出的东西又删除了,类似make clean

例3:
有多个.c文件,并想指定生成的程序名:
Program('program', ['prog.c', 'file1.c', 'file2.c'])
生成可执行文件 program, 由prog.c file1.c file2.c 生成。
#scons -Q
cc -o file1.o -c file1.c
cc -o file2.o -c file2.c
cc -o prog.o -c prog.c
cc -o program prog.o file1.o file2.o

甚至可以这样写:
common_sources = ['file1.c', 'file2.c']
Program('program2', common_sources + ['program2.c'])



例4:
有很多个.c文件,不想一个一个指定,类似Makefile中 *.o: *.c
Program('test', Glob('*.c'))


不管使用common_sources = ['file1.c', 'file2.c'] 还是Program('test1.c'....'testn.c')
每个文件名都需要被单或者双引号包起来。这太不方便了。
SCons 提供了Split函数
Program('program', Split('main.c file1.c file2.c'))
或:
src_files = Split('main.c file1.c file2.c')
Program('program', src_files)



例5:
SCons还对output file和source file有关键字,可以用来指定

src_files = Split('main.c file1.c file2.c')
Program(target = 'program', source = src_files)
因为有关键字指定,多以可以换前后:
Program(source = src_files, target = 'program')


例6:
创建库:
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
#scons -Q
cc -o f1.o -c f1.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o f4.o
ranlib libfoo.a

例7:建立静态和动态库:
静态库:其实使用Library也是建立静态库。
StaticLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])
动态库:
SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])

% scons -Q
cc -o f1.os -c f1.c
cc -o f2.os -c f2.c
cc -o f3.os -c f3.c
cc -o libfoo.so -shared f1.os f2.os f3.os

例8:使用静态和动态库:
使用$LIBS 来指定库,使用$LIBPATH 指定库位置。
Library('foo', ['f1.c', 'f2.c', 'f3.c'])
Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.')
% scons -Q
cc -o f1.o -c f1.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
cc -o prog.o -c prog.c
cc -o prog prog.o -L. -lfoo -lbar


Program('prog.c', LIBS = ['m'], LIBPATH = ['/usr/lib', '/usr/local/lib'])
% scons -Q
cc -o prog.o -c prog.c
cc -o prog prog.o -L/usr/lib -L/usr/local/lib -lm




以BTX为例使用scons:
Sam创建了一个SConstruct,(类似于GNU make的Makefile文件,是scons的默认文件名).

Sam首先想生成BTX library.
然后想将library 和 main.c和成一个可执行文件:BTX_Test
内容如下:
#指定生成文件名为:libBTX.a. 由BTX.c生成的。-I'/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include -I../include
StaticLibrary('BTX', ['BTX.c'], CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include'])
Program('BTX_Test', ['main.c'], CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include'], LIBS=['BTX','blu\etooth','pthread'], LIBPATH=['.', '/usr/lib'])

#scons
提示之不到bluetooth库,Sam在/usr/lib中将 libbluetooth.so 软连接到libbluetooth.so.2。
于是编译成功,所以,Sam觉得Scons和Makefile很类似,只需要编译时指定-I ,-L, -l就好了。对应关系如下:
-I:CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include']
-L:LIBPATH=['.', '/usr/lib']
-l: LIBS=['BTX','bluetooth','pthread']
CPPPATH,LIBPATH,LIBS,CC等从:
http://www.scons.org/doc/1.2.0/HTML/scons-user/a4774.html#cv-CPPPATH 中找到
同时请注意,最好在每个关键字赋值后面都跟 [],里面有多个时用逗号分开。


X5 Version:
StaticLibrary('BTX', ['BTX.c'], CC=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc'], CPPPATH=['/opt/his\ilicon/toolchains/arm-uclibc-linux-soft/include', '../include'])
Program('BTX_Test', ['main.c'], CC=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc'], CPPPATH=['/opt/his\ilicon/toolchains/arm-uclibc-linux-soft/include', '../include'], LIBS=['BTX','bluetooth','pthread'], LIBPATH=['./', '../resource/']\)



scons只需要描述任务,并不需要指定依赖关系,甚至我们都没有指出头文件,但是你修改BTX.h的时候仍然会触发构建。这是因为scons内部有个scanner,可以帮助扫描包含文件的关系。
我们还发现,这里我们根本没有指定编译器,也没有指定编译选项,但scons仍然很聪明的选择了gcc. 这是因为scons内置提供了很多编译器及其对应选项的选择,然后对于不同的平台,会有一个默认项。



Environments:
Scons有3种环境变量:
1. External Environment
2. Construction Environment
3. Execution Environment

当想要使用External Environment,必须通过os.environ。这意味着首先要加入:
import os
之后就可以在SConscript文件中使用os.environ来初始化construction environments 为用户环境变量了。

1.创建Construction Environment
env = Environment()

例如:
import os
env = Environment(CC = 'gcc', CCFLAGS = '-O2')
env.Program('foo.c')



最重要的环境变量是:Construction Environment
它在复杂的工程中非常重要,例如,不同的源文件需要不同的编译选项。或者不同的可执行程序需要链接不同的库。scons提供Construction Environment。让我们可以创建不同的Construction Environment来创建不同的编译选项。

1. 创建Construction Environment ,使用Environment function。
env = Environment() 所有的Construction Environment 项目值全用scons自己找到的。
env = Environment(CC = 'gcc', CCFLAGS = '-O2') ,其他的与上一个相同,但只有CC和CCFLAGS 自定义。

2. 察看Construction Environment 每个项目值:注[1]
需要使用Dictionary() function
env = Environment()
dict = env.Dictionary()
keys = dict.keys()
keys.sort()
for key in keys:
print "construction variable = '%s', value = '%s'" % (key, dict[key])
打印所有Construction Environment项目。








scons中Python模块的导入:
1. import os
os模块包含普遍的操作系统功能。如果你希望你的程序能够与平台无关的话,这个模块是尤为重要的。即它允许一个程序在编写后不需要任何改动,也不会发生任何问题,就可以在Linux和Windows下运行。一个例子就是使用os.sep可以取代操作系统特定的路径分割符。


os.name字符串指示你正在使用的平台。比如对于Windows,它是'nt',而对于Linux/Unix用户,它是'posix'。
os.getcwd()函数得到当前工作目录,即当前Python脚本工作的目录路径。
os.getenv()和os.putenv()函数分别用来读取和设置环境变量。
os.listdir()返回指定目录下的所有文件和目录名。
os.remove()函数用来删除一个文件。
os.system()函数用来运行shell命令。
os.linesep字符串给出当前平台使用的行终止符。例如,Windows使用'\r\n',Linux使用'\n'而Mac使用'\r'。
os.path.split()函数返回一个路径的目录名和文件名。
os.environ Shell variables
这里重点说os.environ:
它应该是Linux环境变量。

例1:
想要得到当前Linux环境变量PATH,LD_LIBRARY_PATH,HOME等。(这与前面讲的环境变量对应起来了)

#导入OS模块
import os
#创建一个Construction Environment
env=Environment()
print "HOME: ", os.environ["HOME"]
则打印结果为HOME:/home/sam。这样就得到了Linux环境变量。

例2:
想要用当前Linux环境变量ARMGCC来设置toolchain.

首先:在Linux下:
export ARMGCC=arm-linux-gcc

之后,修改config
#导入OS模块
import os
#创建一个Construction Environment
env=Environment()
if os.environ.has_key('ARMGCC'):
env.Replace(CC = os.environ['ARMGCC'])
则使用arm-linux-gcc取代了Construction Environment中的CC。



2. import sys
sys模块包含系统对应的功能.sys模块包含了与Python解释器和它的环境有关的函数.
import sys
if sys.byteorder == 'little' or env['PLATFORM'] == 'win32':
env.Append(CPPDEFINES = ['LSB_FIRST'])


3. import platform
platform得到编译信息:
platform.processor() ---i686
platform.python_build()---('r251:54863', 'Jun 4 2007 15:03:52')
platform.python_compiler()----'GCC 3.4.4 20050721 (Red Hat 3.4.4-2)'
platform.python_version()----'2.5.1'
platform.release()----'2.6.9-22.ELsmp'
platform.system()----'Linux'
platform.version()----'#1 SMP Mon Sep 19 18:32:14 EDT 2005'
platform.uname()----('Linux', 'nec', '2.6.9-22.ELsmp', '#1 SMP Mon Sep 19 18:32:14 EDT 2005', 'i686' , 'i686')
platform.machine()----'i686'
platform.libc_ver()----('glibc', '2.0')




注[1]:
所有Construction Variables可以在下面找到:
Construction Variables可用在:
1. CPPPATH,LIBS,LIBPATH。(在BTX例子中可见)
2. Construction Environment(env=Environment)中的项目。

Appendix A. Construction Variables

2009年3月11日星期三

Linux下Framebuffer学习


作者:Sam(甄峰) sam_code@hotmail.com


Sam之前写过vfb(虚拟framebuffer) driver. 但现在什么都不记得了。 现在需要在X5平台上使用framebuufer. 只好再学习一次。



零:Framebuffer 简介:

Frame Buffer设备为图形硬件提供抽象。它表示一些视频硬件的Frame Buffer,允许应用软件通过定义好的接口访问图形硬件,所以这些软件不必知道任何关于低层(硬件寄存器)的知识。


使用frame buffer设备的应用程序(例如 X server)默认使用/dev/fb0(老一点的软件使用 /dev/fb0current)。你可以通过设定环境变量$FRAMEBUFFER等于frame buffer设备的路径名来改变frame buffer设备,例如(针对 sh/bash用户):export FRAMEBUFFER=/dev/fb1或者(针对 csh 用户):setenv FRAMEBUFFER /dev/fb1之后,X server将使用第二个frame buffer。


大家都已经知道,frame buffer设备是一个内存设备,就像/dev/mem, 所以他拥有相同的特征。你可以读他,写他,在他里面定位和mmap他(主要用途)。


/dev/fb*也允许在他之上做一些ioctls操作,通过这种方法硬件的许多信息可以获取和设置。颜色映射处理(the color map handling)也通过ioctls工作。


- 你可以取得硬件的不可更改信息,例如名字,屏幕内存组织方式(位面,象素格式,……)和地址和屏幕内存的长度。

- 你可以获取和修改硬件的可变信息,例如可见,虚拟几何,深度,颜色映射格式,定时,等等。如果你试图修改这些信息,驱动可能规整一些值以适应硬件的能力(或者返回EINVAL 如果这个操作不可能) (译注:the driver maybe will round up some values to meet the hardware's capabilities)

- 你可以取得和设置部分颜色映射。通信使用16位每一颜色(红,绿,蓝,透明)来支持所有存在的硬件。驱动处理所有应用于硬件的计算(截取他们到较少的位,可能会舍弃透明)。



视频模式计时:(非常重要)

显示器使用电子束(彩色模式使用3个电子束,单色模式使用1个电子束)绘制屏幕上的图像。屏幕的前面被颜色荧光(象素)覆盖.如果荧光被电子击中,它就会发出光子从而可见.


电子束从左至右绘制水平行,从上到下绘制整个屏幕.通过改变电子束的亮度,便可以显示各种颜色和亮度的像素


每条扫描线完成后电子束必须重新移回到下一行的左端:这称做水平回扫.等到完成整个屏幕(帧)的绘制,电子束会移回到左上方点:这被称做垂直回扫.在水平和垂直回扫期间,电子束被关闭(空).


电子束绘制像素的速度由图形板的时钟决定(译注:dotclock).

例如:时钟是28.37516MHZ(每秒百万周期)。也就是说每秒可以绘制28.37516M像素。

每像素需35242 ps(微秒):1/(28.37516E6 HZ) = 35.242E-9 s


如果屏幕的分辨率是640x480, 将需要640*35.242E-9 s = 22.555E-6 s来绘制一条扫描线上640(xres)个像素.但是水平回扫也需要时间(例如 272 像素), 所以一条完整的水平扫描线需要:(640+272)*35.242E-9 s = 32.141E-6 s

我们称水平刷新率约是31 kHz:1/(32.141E-6 s) = 31.113E3 Hz


整个屏幕有480(yres)条扫描线,但是我们必须还要考虑垂直回扫(例如 49 条扫描线). 所以完整的一屏需要:(480+49)*32.141E-6 s = 17.002E-3 s垂直刷新率约是 59 Hz:1/(17.002E-3 s) = 48.815 Hz这意味着屏幕上的数据每秒钟刷新约59。


从最上面的图可以看出:
水平回扫时间是左边缘(left_margin),右边缘(right_margin)和 hsync 长度(hsync_len)的总和. 而垂直回扫时间是上边缘(upper_margin),下边缘(lower_margin)和 vsync长度(vsync_len)的总和。这些值与fb_var_screeninfo结构体中的项目对应。
frame buffer设备期待所有的水平计时以时钟(以picoseconds, 1E-12 s)为单位,垂直计时则以扫描线数目为单位.
pixclock(以ps为单位的像素时钟)
- pixclock: 以ps为单位的像素时钟(pico seconds)
- left_margin: 从sync到画面的时间
- right_margin: 从画面到sync的时间
- upper_margin: 从sync到画面的时间
-lower_margin: 从画面到sync的时间
- hsync_len: 水平sync的长度
- vsync_len: 垂直sync的长度








一. 基本使用方法:


(之后再补上)





二. 最重要的结构体:


fb_var_screeninfo
简单的项如下:
__u32 xres; /* visible resolution */
__u32 yres; //可见部分的长宽
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual; //虚拟部分的长宽
__u32 xoffset; /* offset from virtual to visible */ //可见和虚拟部分的offset
__u32 yoffset; /* resolution */


__u32 bits_per_pixel; //每个象素占多少位
__u32 grayscale; //不等于0的话,灰度


struct fb_bitfield red; /* bitfield in fb mem if true color, */

struct fb_bitfield green; /* else only length is significant */

struct fb_bitfield blue;

struct fb_bitfield transp; /* transparency */

fb_bitfield:结构很重要:

struct fb_bitfield

{

__u32 offset; /* beginning of bitfield */

__u32 length; /* length of bitfield */

__u32 msb_right; /* != 0 : Most significant bit is */

};

像素详解:

以上的结构其实与像素有关,这个结构是说:这种色值(RGB或者transp=alpha)在一个像素点结构中的offset,length

---------------- 一个像素点

R G B a

这样,上面这个结构就明白了吧。

其它机构的含义,请看上面的视频模式部分。呵呵

2009年2月26日星期四

Nucleus学习

作者:Sam (甄峰) sam_code@hotmail.com

公司想MTK解决方案中添加手柄概念进去。MTK使用Nucleus 操作系统,它是由ATI开发的RTOS.

MTK使用了nucleus实时操作系统,在其上做了个内核抽象层的封装(Kal) ,以适应多种实时操作系统,如oscar、ThreadX、nucleus。整个软件系统包括nucleus操作系统、平台设备驱动、协议栈、文件系统、WGUI、MMI、J2ME等。在这里MMI部分几乎包括了操作系统内核、协议栈、文件系统之上的所有部分,其中WGUI也在其中。

Nucleus是单一地址空间操作系统的一种,作为商业化的嵌入式操作系统产品,曾被广泛使用。在MIPS架构中,其操作系统和用户程序完全工作在内核模式,且只占用和访问0x80000000以上的线性地址空间。因此,在Nucleus中,操作系统和用户程序工作在线性地址空间中,且用户程序与内核服务之间没有明显的区分,进入内核服务更像是调用API(Application Program Interface)而不需要上下文切换。其优点是限制少,编程方便,但系统健壮性差。

MTK手机软件系统的构建使用了GNU的make,使得整个工程的构建可以自动进行,且可以灵活控制。整个构建过程由Make.bat、make2.pl、MoDIS.dsw、Gsm2.mak、Option.mak、_.mak等文件控制。


在MTK手机软件系统的根目录下有一个批处理文件Make.bat,这个批处理文件启动了整个工程的构建过程。在windows的命令行下,在该系统的根目录下输入命令make和相应的参数即可开始工程的构建,该批处理文件的使用方法如下所示。
Usage: make ["customer""mt62xx"] "project" "action" ["modules"]



增加模块的配置实例
若我们需要加入zlib这么一个模块(zlib包含了很多程序都用到的压缩和解压函数),我们可以按如下步骤进行。
1. 把zlib的源代码包拷贝到你的MTK软件系统根目录下,这样所有的zlib代码都在zlib目录或zlib-1.2.3目录下(用哪个目录名由你的喜好来确定),在此为简单起见就放在zlib目录。
2. 在make目录下新增一个zlib的目录(最好和根目录下的目录名一样),增加四个文件,分别是zlib.def、zlib.inc、zlib.lis、zlib.pth。zlib.def文件只要加上APCS_INTWORK就可以了,其他三个文件中加上源文件及其目录、头文件目录即可。
3. 在REL_CR_MMI_.mak这个文件的合适位置加上如下语句。CUS_REL_SRC_COMP += zlib
4. 把这个工程remake一下,若没有错误,zlib模块就成功加上去了。其他模块就可以调用zlib提供的压缩和解压缩函数了。

Nucleus内核的主要目的是管理实时任务的调度运行,共享处理器资源,为应用提供各种便利,快速响应外部事件,实现实时性。利用Nucleus开发平台,用户只需要编写任务代码(Task)和中断服务程序代码(ISR),二者利用系统调用实现与Nucleus的交互,由Nucleus内核来调度多个任务并行执行,实现处理器共享。

2009年2月23日星期一

嵌入式系统中的usleep延迟

作者:Sam(甄峰) sam_code@hotmail.com

Sam在作嵌入式系统中,发现类似usleep这样的函数在不同平台上延迟并不相同。所以如果想要延迟一段时间,尤其是比较精确的延迟,应慎重使用类似函数。

举例而言:
例1:
usleep(5000); //延迟5ms.

在PC-linux中,延迟时间大约在5020-5030微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在8010微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。

当然,这不排除Sam使用gettimeofday() 函数所用时间,但可以肯定的是,在不同CPU速度下,usleep() 延迟造成的效果应该差别很大。


例2:
usleep(50); //延迟50微秒
在PC-linux中,延迟时间大约在59-60微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在4000微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。


例3:
usleep(50000); //延迟50000微秒
在PC-linux中,延迟时间大约在50076微秒左右。
在Canmore(800M CPU)中,延迟则会大很多, 在56047微秒左右。
在X5平台(300M CPU)中, 延迟到60000微秒左右。


从上面的数据看,在嵌入式系统中,最好不用时用usleep这类函数,因为他们特别不准确。当延迟比较大时,稍微准确一些,但延迟小到一定程度时,延迟会非常不准确。好像有一个延迟最低点。低于这个数据后,不管你想要延迟多少,系统都会延迟这么久。例如X5平台的20000微秒。

2009年2月19日星期四

高速数据交换时的lock问题

作者:Sam(甄峰) sam_code@hotmail.com

Sam在作Bluetooth Remote Control程序时,遇到一个问题。
手柄每5ms发送一个数据包。
而Sam程序是这样设计的:
在手柄连接时,BTRC创建Read thread. 在Read thread中会读取手柄数据并解析包。解析结果放到一个buffer(结构体)中。
另外一边,应用程序会使用BTRC的read接口去读取这个buffer的内容。这个应用程序应该会用一个thread去不断的调用BTRC的read接口去读取。


方案一:
Sam在解析包部分向buffer中写东西时会加锁,写完会解锁。BTRC的read接口读取buffer时加锁,读完会解锁。
但这样有个后果:
1. 如果 双方都是全速运行,也就是说:如果双方都不添加sleep.则会出现:程序先进入BTRC的read接口读取buffer很多次(20次)左右。这个时候,BTRC的使用BTX去读取并解析然后写入buffer的程序的lock无法进入。
然后,在某一时刻,BTRC使用BTX区读取并解析然后写入buffer的程序又集中运行很多次(15-20次)。
这就造成上层程序使用BTRC的读取接口读到的解析后的数据完全不正确。

2. 如果上层程序调用BTRC的读取接口时作一个延迟,比如4ms.
这样的话,因为程序使用BTRC 的读取接口有一个延迟,所以BTRC是用BTX去读取并解析然后写入buffer的程序有更大机会去写入。但会造成写入多个才能够读取一次的情况。例如:A包过来了,BTRC使用BTX去读取,解析后写入buffer. A+1包又过来了,然后BTRC使用BTX读取时,BTRC的读接口来读buffer,但buffer已经被lock了。所以只能等下一次去读取了。这造成只能读取第二个包。所以还是会造成丢包。

3. 如果去掉lock,并全速运行:
现象与情况一相同。


方案二:
将BTRC的读接口改造,每次不是再去读当前的一份数据,而是如下去做:
用返回值表示从上次读取到此次读取这段时间内,buffer中新的数据包个数。参数为指针的指针。指向数据头。





注[1]:Sam有个感觉,在不同速度的CPU上,尤其是一些嵌入式平台中,thread的schedule速度好像比较慢,所以在不同thread之前的切换速度比较慢。像在X5平台上,BTRC的Read接口去read buffer, 竟然只能读到30%-40%的包。Canmore可以读到70%的包。而PC上则可以读到100%的包,还有重复读到的。





2009年2月18日星期三

Hi3110 EV 100平台上Bluetooth的移植

作者:Sam(甄峰) sam_code@hotmail.com


Sam想把手柄库移植到海思 Hi3100 EV 100平台上去。先进行评估如下:



1. Hi3100 EV 100为ARM平台。
2. Hi3100 EV 100使用uclibc。
这两个差异决定了需要从bluetooth 底层库开始移植。


一:
Sam从linux kernel中编译出了bluetooth.ko, hci_usb.ko, l2cap.ko, hidp.ko。

二:
下载新版bluez.
http://www.bluez.org/


三:
交叉编译bluez:
交叉编译时发现,新版bluez要基于D-BUS等库。所以Sam暂时使用bluez 1.0版本。
#cd bluez-libs-2.12
# CC=/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc ./configure --host=arm-linux
#make clean;make
#cd bluez-libs-2.12/src/.libs
看到libbluetooth.so就在这里。


四:
交叉编译BTX:
在BTX Makefile中,
CC := /opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc
CFLAGS := -D_SHOW -D_DEBUGTIMEOUT -Wall -I../include -I/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/
LFLAGS := -L./ -L../resource -L/opt/hisilicon/toolchains/sysroot-uclibc-soft/usr/lib
编译libBTX.so和BTX_Test成功。
其中libBTX.so编译选项如下:
$(CC) $(CFLAGS) $(LFLAGS) -fPIC -shared BTX.o -o libBTX.so -lbluetooth

但奇怪的是,在Hi3110 EV100平台上,运行BTX_Test会出现如下信息:
Can't modify .//libBTX.so's text section. Use GCC option -fPIC for shared objects, please.

Sam只好把BTX并入程序中。


五:
编译Bluetooth_remote:
CC := /opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc CFLAGS := -D_SHOW -D_DEBUGTIMEOUT -Wall -I../include -I/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/ LFLAGS := -L./ -L../resource -L/opt/hisilicon/toolchains/sysroot-uclibc-soft/usr/lib
并将BTX.c编译进去。
编译成功。
但运行时会有问题,反应比较慢。Sam还在查。