2004年10月13日

extern struct list_head hdev_list;系统所有的hci设备都在这个链表上

rwlock_t hdev_list_lock = RW_LOCK_UNLOCKED;保证hdev_list链表操作的原子性;

/*
 * Simple doubly linked list implementation.
 *
 * Some of the internal functions (“__xxx”) are useful when
 * manipulating whole lists rather than single entries, as
 * sometimes we already know the next/prev entries and we can
 * generate better code by using them directly rather than
 * using the generic single-entry routines.
 */

struct list_head {
 struct list_head *next, *prev;
};

SkyEye硬件模拟平台,第二部分: 安装与使用

内容:


一. SkyEye配置选项
二. 安装使用SkyEye
2. SkyEye的使用
三.小结
关于作者
对本文的评价

级别: 中级

陈渝yuchen@tsinghua.edu.cn
清华大学
2004 年 9 月

本文主要介绍SkyEye硬件模拟平台的安装与使用。对SkyEye的使用,可以调试和开发多种嵌入式操作系统和系统软件,有助于对操作系统、驱动程序如何与嵌入式硬件系统进行交互有更深刻的了解。

一. SkyEye配置选项
SkyEye模拟的硬件配置和模拟执行行为由配置文件skyeye.conf中的选项确定。根据选项的功能,skyeye.conf的选项分为硬件配置选项和模拟执行选项。根据参数的个数,skyeye.conf的选项主要由两种组成:

  • 单参数选项行
    格式为:
    symbol: value
    symbol可以是硬件定义,如cpu、mach等,也可以是执行控制定义,如log等。
    value是symbol对应的值,可能是数字也可能是字符串。
  • 多参数选项行
    格式为:
    symbol:opt1=value1,opt2=value2,……
    opt1是参数名,value1是opt1对应的值,可能是数字也可能是字符串。

目前skyeye.conf的配置定义如下:

  • 基本CPU核配置选项
    目前存在的选项有:arm710、arm7tdmi、arm720t、arm920t、sa1100、sa1110、xscale
    格式为
    cpu: cpuname
    注:cpuname表示一个代表cpu名字的字符串。
    例如:
    cpu: arm7tdmi
  • 具体的开发板(包括CPU扩展)配置选项
    目前存在的选项有:at91、ep7312、adsbitsy,pxa_Lubbock、lpc、s3c4510b、s3c44b0、cs89712、sa1100、at91rm92、sharp_lh7a400
    格式为:
    mach: machinename
    注:cpuname表示一个代表基于特定CPU的开发板名字的字符串。
    例如:
    mach: at91
  • 内存组配置选项
    一个内存组内的地址是连续的,类型分为RAM SPACE, ROM SPACE, mapped IO SPACE
    格式为:
    mem_bank: map=M|I, type=RW|R, addr=0xXXXXXXXX, size=0xXXXXXXXX,file=imagefilename,boot=yes|no
    • map=M 表示 RAM/ROM SPACE, map=I 表示 mapped IO SPACE。
    • type=RW ,且如果map=M则表示RAM SPACE, type=R,且如果map=M则表示ROM SPACE。
    • addr=0xXXXXXX 表示内存组的起始物理地址(32bit,16进制)。
    • size =0xXXXXXX 表示内存组的大小(32bit,16进制) 。
    • file =imagefilename
      file的值imagefilename是一个字符串,实际上表示了一个文件,一般是一个可以执行的binary image格式的可执行程序或OS内核文件或是一个binary image格式的根文件系统。如果存在这个文件,SkyEye会把文件的内容直接写到对应的模拟内存组地址空间中。
    • boot=yes/no,如果boot=yes,则SkyEye会把模拟硬件启动后的第一条指令的地址定位到对应的内存组的起始地址。

    例如:
    mem_bank: map=M, type=RW, addr=0×00000000, size=0×00004000
    mem_bank: map=M, type=R, addr=0×01400000, size=0×00400000, file=./boot.rom
    mem_bank: map=M, type=RW, addr=0×10000000, size=0×00000800, file=./loader.bin,boot=yes

  • 网络芯片8019AS的配置
    格式为 nic: state=on/off mac=xx:xx:xx:xx:xx:xx ethmod=tuntap/vnet hostip=dd.dd.dd.dd
    注:xx表示两位16进制数,dd表示两位10进制数
    • state表示模拟开始后,网络芯片是否开始工作
    • mac表示模拟的nic的mac地址
    • ethmod表示skyeye所处主机上的网络模拟方式, 目前有tuptap和vnet两种模式。tuntap是linux kernel支持的一个点到点虚拟网络实现,vnet是skyeye实现的一个功能更多的一个基于虚拟HUB的网络。
    • hostip表示主机方与skyeye交互用的ip地址

    例如:
    net: state=on, mac=0:4:3:2:1:f, ethmod=tuntap, hostip=10.0.0.1

  • LCD的配置
    格式为 lcd: state=on/off
    state=on表示LCD处于接通状态; state=off 表示LCD处于关闭状态。
    例如:
    lcd: state=on
  • skyeye的UART控制选项
    uart选项可以控制skyeye在另一个与某个串口连接的终端上输入/输出字符
    格式:
    uart: fd_in=indevname, fd_out=outdevname
    • fd_in=indevname indevname表示用于输入的设备文件名,其值为实际的串口设备文件/dev/ttySx
    • fd_out=outdevname outdevname表示用于输出的设备文件名,其值为实际的串口设备文件/dev/ttySx

    例如:
    uart: fd_in=/dev/ttyS0, fd_out=/dev/ttyS0

  • skyeye的log控制选项
    log选项用于控制skyeye输出硬件系统的执行状态信息,包括每次执行指令时的执行指令值、寄存器值、各种硬件状态等。
    格式:
    log: logon=0|1, logfile=filename, start=number1, end=number2, length=number3
    • logon=0|1|2|3,如果值等于0表示不进行记录,如果值等于1表示记录指令和指令地址流,如果值等于2表示记录指令和指令地址和主要寄存器内容,如果值等于3表示记录指令和指令地址和所有的寄存器内容。
    • logfile=filename 其值是一个字符串,表示用于记录信息的文件名
    • start=number1 其值是一个>=0的十进制整数,表示系统执行到第number1条指令时开始进行记录
    • end =number2其值是一个>=0的十进制整数,表示系统执行到第number2条指令时停止记录
    • length =number3其值是一个>=0的十进制整数,表示只记录系统最近执行的number3条指令时的信息

    格式:
    log: logon=0, logfile=/tmp/sk1.log, start=100000, end=200000, length=100

    图 0 1 SkyEye硬件模拟逻辑结构图
    图 0 1 SkyEye硬件模拟逻辑结构图

    SkyEye目标模拟模块模拟的硬件逻辑结构图如图 0-1所示。

二. 安装使用SkyEye

1. SkyEye的安装
目前SkyEye在Linux操作系统下运行得很好, SkyEye通过MingW或cywgin软件也可以在Windows环境中运行。

下面我们以在RedHat 9.0环境的SkyEye安装为例:

首先需要从解开skyeye-x.x.x.src.tar.bz2软件包(x.x.x代表SkyEye的版本号)开始,按照软件包中的SkyEye.README或README文件中介绍的步骤进行(下面命令中的”#”是shell提示符,用户不用输入):

(1) 解压源码包(假定skyeye-x.x.x.src.tar.bz2放在本机的”/usr/src”目录下),会在当前目录生成一个skyeye的目录
#cd /usr/src
# tar xjvf skyeye-x.x.x.src.tar.bz2

(2) 进入解压SkyEye目录,配置SkyEye
#cd skyeye
如果是SkyEye 0.6.0以前的版本,运行下面的命令进行配置:
# ./configure --target=arm-elf --prefix=/usr/local --without-gtk-prefix --without-gtk-exec-prefix --disable-gtktest
如果你的SkyEye版本大于0.6.0,则带有LCD仿真支持。为了增加对LCD仿真的支持,则运行如下命令:
# ./configure --target=arm-elf --prefix=/usr/local

(3) 然后是编译和安装(正确执行完下面的指令且命令正常结束后,系统中的/usr/local/bin/skyeye 执行程序就是安装好的SkyEye软件。)


# make
# make install

在编译安装过程中,须注意以下几点:

  • 如果你使用的是Mandrake Linux,那么有可能你在编译SkyEye时会出现有关readline, ncurse, termcap等库的一些错误,那么你需要运行下面的命令:
    ln -s /usr/include/ncurses/termcap.h /usr/local/include/termcap.h
  • 然后再重新编译
  • 如果你使用的是Debian Linux, 不要用gcc 2.95 或 gcc 3.0, 要使用gcc 3.2或更高版本。
  • 在你系统中所使用的gcc 版本号应该要大于或等于2.96
  • 如果SkyEye版本号大于0.6.0, 那么你需要在你的系统中安装GTK (LCD模拟要用到) 。

2. SkyEye的使用
下面我们以SkyEye模拟基于Atmel AT91X40的开发板,并运行μClinux为例来讲解SkyEye的具体使用。

第1步 安装交叉编译环境并配置μClinux
先安装交叉编译器。注意这里最好用root用户来执行如下操作。将arm-elf-tools-20011219.tar.gz(20011219是该工具的产生时间,也可以选择更新的arm-elf-tools。)在本机的根目录下用tar命令解开(假定arm-elf-tools-20011219.tar.gz放在本机的根目录”/”下)。具体命令(下面命令中的”#”是shell提示符,用户不用输入)如下:


#cd /
# tar /zxvf arm-elf-tools-20011219.tar.gz

然后将uClinux-dist-20020927.tar.gz(假定该软件包放在本机的根目录下)解开,比如解开在/usr/src/uClinux-dist/下,需要执行如下命令:

# cd /usr/src
# tar zxvf /uClinux-dist-20020927.tar.gz

然后在/usr/src/uClinux-dist/目录下执行如下命令:

  • 在图形方式下可用命令
    # make xconfig
  • 在命令行方式下可用命令
    # make menuconfig

在这两种界面下,在vendor/product选项中选择GDB/ARMulator,kernel版本选择2.4.x,其它选项不变,然后保存配置退出,即选择save and exit选项。

第2步 编译生成μClinux和包含应用程序的文件系统
方案A

如果要生成带调试信息的linux kernel执行文件,在第一步执行完后,继续执行如下命令:
# make dep; make linux
这时在/usr/src/uClinux-dist/linux-2.4.x目录下生成具有ELF执行文件格式的linux内核文件。到这一步还没有生成文件系统,为了生成文件系统,还需要执行如下命令:
# make romfs; make image
如果在/usr/src/uClinux-dist/images/下存在文件 romfs.img,表示文件系统生成成功。

方案B

如果想一步生成带调试信息的linux kernel执行文件和linux kenel 执行文件映像和磁盘映像文件romfs.img,可简单地运行命令:
# make dep; make
就行了。可查看/usr/src/uClinux-dist/images/下是否有文件 romfs.img等在/usr/src/uClinux-dist目录下,如果有,表示编译安装成功。

第3步 用SkyEye运行和调试μClinux内核
假定SkyEye的工作目录位于/usr/src/uClinux-dist下,首先要进行一些准备工作:

在工作目录下建立专门用于基于AT91X40开发板的SkyEye硬件配置文件skyeye.conf。一个简单的方法是从SkyEye软件包中的README文件中截取一块内容(以”FOR AT91 (1) special for uclinux”开始的一行,从它下面第三行开始截取,共取9行)作为skyeye.conf的内容,具体内容如下:



 cpu: arm7tdmi mach: at91 mem_bank: map=M, type=RW, addr=0x00000000, size=0x00004000 mem_bank: map=M, type=RW, addr=0x01000000, size=0x00400000 mem_bank: map=M, type=R, addr=0x01400000, size=0x00400000, file=./boot.rom mem_bank: map=M, type=RW, addr=0x02000000, size=0x00400000 mem_bank: map=M, type=RW, addr=0x02400000, size=0x00008000 mem_bank: map=M, type=RW, addr=0x04000000, size=0x00400000 mem_bank: map=I, type=RW, addr=0xf0000000, size=0x10000000 

这样我们可以通过如下命令看看在/usr/src/uClinux-dist目录下的skyeye.conf文件是否与上面的内容一致:
# more /usr/src/uClinux-dist/skyeye.conf
然后是建立文件系统的联接,需要在目录/usr/src/uClinux-dist目录下执行如下命令:
#ln -s images/romfs.img boot.rom
这主要是与skyeye.conf中的内容保持一致,大家可注意skyeye.conf中的第5行。
完成上述准备工作后,就可以用SkyEye来运行和调试μClinux内核了,我们可以尝试如下命令(”#”和”(SkyEye)”后面跟着的字符串是需要使用者输入的):



 # cd /usr/src/uClinux-dist # /usr/local/bin/skyeye linux-2.4.x/linux (SkyEye)target sim cpu info: armv3, arm7tdmi, 41007700, fff8ff00, 0 mach info: name at91, mach_init addr 0x813ebc0 SKYEYE: use arm7100 mmu ops Loaded ROM ./boot.rom Connected to the simulator. (SkyEye)load Loading section .init, size 0xa000 vma 0x1000000 Loading section .text, size 0xc5cb0 vma 0x100a000 Loading section .data, size 0x8320 vma 0x10d0000 Start address 0x1000000 Transfer rate: 7077504 bits/sec. (SkyEye)run Starting program: /usr/src/uClinux-dist/linux-2.4.x/linux Linux version 2.4.19-uc1 (root@hpclab.cs.tsinghua.edu.cn) (gcc version 2.95.3 20010315 (release)(ColdFire patches - 20010318 from http://fiddes.net/coldfire/) (uClinux XIP and shared lib patches from http://www.snapgear.com/)) #1 Sun Sep 5 12:00:39 HKT 2004 Processor: Atmel AT91M40xxx revision 0 Architecture: EB01 …… 

你还可以在run之前给kernel设置断点来调试。

在使用SkyEye时要注意以下几点:

a .注意要在 memmap.conf(skyeye-0.2以前版本的配置文件)或skyeye.conf(skyeye-0.2以后版本的配置文件)和boot.rom所在目录下执行skyeye。

b. memmap.conf 和 skyeye.conf都是skyeye的硬件配置文件。memmap.conf 适用于skyeye0.2以下版本,而skyeye.conf 用于skyeye0.2以上版本。

c. 运行带网络选项的硬件配置时,注意用户必须为root用户,而且当前的可执行路径中可以搜索到ifconfig命令

d. 运行带网络选项的硬件配置时,需要编译安装内核模块tuo.o (在RedHat系统中,该模块在/lib/modules/2.4.x/kernel/drivers/net/tun.o)

e. 请阅读skyeye.conf.txt来获得skyeye.conf的配置选项的含义

f. 可参考skyeye-binary-testutils-x.x.x.tar.bz2软件包(x.x.x代表其的版本号)中已经设定好的skyeye.conf来配置特定的硬件模拟环境,并可尝试用SkyEye运行这个软件包中编译好的操作系统内核。

下面是一些skyeye.conf的例子:



 FOR AT91 (0) special for ucosii ------------------------------------------- cpu: arm7tdmi mach: at91 mem_bank: map=M, type=RW, addr=0x00000000, size=0x00004000 mem_bank: map=M, type=RW, addr=0x01000000, size=0x00400000 mem_bank: map=M, type=R, addr=0x01400000, size=0x00400000 mem_bank: map=M, type=RW, addr=0x02000000, size=0x00400000 mem_bank: map=M, type=RW, addr=0x02400000, size=0x00008000 mem_bank: map=M, type=RW, addr=0x04000000, size=0x00400000 mem_bank: map=I, type=RW, addr=0xf0000000, size=0x10000000 FOR AT91 (1) special for μClinux ------------------------------------------- #skyeye config file sample cpu: arm7tdmi mach: at91 mem_bank: map=M, type=RW, addr=0x00000000, size=0x00004000 mem_bank: map=M, type=RW, addr=0x01000000, size=0x00400000 mem_bank: map=M, type=R, addr=0x01400000, size=0x00400000, file=./boot.rom mem_bank: map=M, type=RW, addr=0x02000000, size=0x00400000 mem_bank: map=M, type=RW, addr=0x02400000, size=0x00008000 mem_bank: map=M, type=RW, addr=0x04000000, size=0x00400000 mem_bank: map=I, type=RW, addr=0xf0000000, size=0x10000000 #set nic info state=on/off mac=xx:xx:xx:xx:xx:xx ethmod=tuntap/vnet hostip=dd.dd.dd.dd net: state=on, mac=0:5:3:2:1:f, ethmod=tuntap, hostip=192.168.2.1 FOR ep7312 (1) special for linux ------------------------------------------- cpu: arm720t mach: ep7312 mem_bank: map=I, type=RW, addr=0x80000000, size=0x00010000 mem_bank: map=M, type=RW, addr=0xc0000000, size=0x00200000 mem_bank: map=M, type=RW, addr=0xc0200000, size=0x00200000, file=./initrd.img mem_bank: map=M, type=RW, addr=0xc0400000, size=0x00c00000 FOR StrongArm SA1100 (1) speciall for linux ------------------------------------------- cpu: sa1100 mach: sa1100 mem_bank: map=I, type=RW, addr=0x80000000, size=0x40000000 mem_bank: map=M, type=RW, addr=0xc0000000, size=0x00800000 mem_bank: map=M, type=RW, addr=0xc0800000, size=0x00400000, file=./initrd.img mem_bank: map=M, type=RW, addr=0xc0c00000, size=0x01400000 mem_bank: map=I, type=RW, addr=0xe0000000, size=0x08000000 #0xe0000000 128M cache flush memory bank 

三.小结
本文讲解了SkyEye硬件模拟平台的硬件配置选项,以及如何安装使用SkyEye。如果读者能够熟练掌握SkyEye的使用,则会提高开发、调试操作系统等的进度,特别是对操作系统、驱动程序如何与嵌入式硬件系统进行交互有更深刻的了解。

关于作者
陈渝, 清华大学,通过 yuchen@tsinghua.edu.cn 可以和他联系。
SkyEye硬件模拟平台, 第一部分: SkyEye 介绍

内容:


一.SkyEye介绍
二.SkyEye模拟硬件介绍
三.SkyEye的设计实现
四.小结
关于作者
对本文的评价

级别: 初级

陈渝yuchen@tsinghua.edu.cn
清华大学
2004 年 9 月

本文主要介绍了SkyEye硬件模拟平台起源,背景和发展状况。SkyEye是一个可以模拟嵌入式硬件开发板的系统软件。通过SkyEye的了解,有助于对嵌入式硬件系统有更深入的认识,特别是对操作系统、驱动程序如何与嵌入式硬件系统进行交互有更深刻的了解。

一.SkyEye介绍

1.SkyEye的起源和发展
2002年11月,一个偶然的机会,一群操作系统的爱好者在网上进行聊天,成立了一个TM-Linux兴趣小组,希望要做一些感兴趣的事情。当时在清华大学计算机系做博士后的陈渝提出做一个用软件实现的嵌入式开发板硬件模拟器,可以在模拟器上运行各种操作系统,这样就可以在没有开发板的情况下学习和研究操作系统。一开始就陈渝一人做,首先他了解了当前国际上的一些类似的项目,发现著名的μCLinux组织实现了一个armulator模拟器软件(在Linux系统上运行),可以模拟Ateml AT91(基于ARM7TDMI CPU)开发板,μCLinux可以在armulator(其网址为http://www.uclinux.org/pub/uCLinux/utilities/armulator/)上运行。于是陈渝以此为基点,借鉴armulator的实现,提出了SkyEye项目,其目标是让SkyEye仿真多种主流的嵌入式开发板和外设,实现一个可扩展的硬件模拟框架,让更多的嵌入式操作系统可以在SkyEye上运行。SkyEye项目于2002年12月1日正式建立后,陈渝完成的第一件工作是把armulator移植到了cygwin/windows环境下,其成果被μCLinux组织接收。接下来清华大学计算机系硕士生李明加入到SkyEye的开发中,8天后,SkyEye的第一个版本推出,再过了4天,μC/OS-II for SkyEye推出。在这期间,SkyEye的网站也建立起来了。紧接着,杨晔、王利明、尹首一等在校学生也加入到SkyEye的开发中,给SkyEye带来了新的活力,SkyEye进入了新的发展阶段,目前通过访问SkyEye的网站(http://www.skyeye.org/)和在linux公社上的SkyEye论坛(http://www.linuxfans.org/)可以了解到SkyEye的最新进展并对有关嵌入式系统开发方面的问题进行交流,还可以到http://gro.clinux.org/projects/skyeye/ 下载最新的SkyEye相关软件和文档。

2.SkyEye的背景
如果你看过电影”黑客帝国”(又称”matrix”),相信电影描述的虚幻世界会深深地吸引你,至少它是我们看过最有想象力的科幻电影之一。也许我们可以把SkyEye看作一个”matrix “,把运行在SkyEye上的各种程序看成是这个”matrix”中的芸芸众生。我们创造SkyEye和编写运行在SkyEye上运行的程序就是为了洞悉计算机的奥秘,尝尝当”造物主”的感觉。当看到各种软件Linux、μCLinux、μC/OS-II…在SkyEye上”愉快”地运行时,那种感觉真是太奇妙了。

对于那些想进行嵌入式系统软件开发和学习,或者想研究嵌入式Linux等操作系统和一些底层系统软件(如TCP/IP等)的研究和开发人员来说,可能存在如下几方面的问题:(1)经常苦于经费不足,缺少足够的硬件开发板和完善的软件开发环境,相关的书籍对一些最新软件的分析还不够全面,无法深入研究和开发嵌入式软件。(2)高层次的软件设计和开发一般不用太考虑底层硬件的实现细节,如果直接处于一个具体的硬件环境下,在开发和研究中可能会陷入硬件的具体细节中不能自拔,而不能把精力放到高层次的软件设计和开发上。(3)如果硬件开发环境不太稳定(这种情况经常见到),且对具体的硬件不是很了解,则可能在排除问题上花费大量的不必要的时间。(4)如果你想自己尝试设计一个操作系统,则先在一个提供源码级调试的软件仿真器上进行开发,可能会大大提高你的开发进度。

对于想了解、学习一般操作系统的实现原理,Linux/μCLinux操作系统或TCP/IP等系统级软件的实现的人员,目前一般采用的方法是看书和读源代码,这是一种静态的学习方法,效率较低,比较枯燥,缺少亲自实践的感觉。要想深入分析和开发软件,就要动手编程,不能只是看看书,读读代码,只有通过亲手实践才能够掌握软件设计的核心内容。上面所指出的问题和需求促使SkyEye项目的诞生。

3.SkyEye的目标和意义
SkyEye是一个开源软件(OpenSource Software)项目,中文名字是”天目”。SkyEye的目标是在通用的Linux和Windows平台上实现一个纯软件集成开发环境,模拟常见的嵌入式计算机系统(这里假定”仿真”和”模拟”的意思基本相同);可在SkyEye上运行μCLinux以及μC/OS-II等多种嵌入式操作系统和各种系统软件(如TCP/IP,图形子系统,文件子系统等),并可对它们进行源码级的分析和测试。

纯软件的模拟器有许多种,如模拟一个芯片时序逻辑的模拟器、只模拟CPU指令的模拟器、模拟整个硬件开发板的模拟器、模拟一个PDA的模拟器等。存在一些纯软件的仿真器或模拟器,如Stanford大学的SimOS模拟器,它仿真的是MIPS系列CPU和相关外设,可以在其上运行SGI公司的Irix操作系统和软件,目前基本上停止了进一步的开发;PSIM是一个仿真PowerPC指令集的模拟器,目前只支持简单的命令行应用程序;xcopilot是一个PDA模拟器,它是由Greg Hewgill出于个人喜好编写的,它仿真的是M68K CPU,通过它可以给基于PalmOS的软件开发者提供一个模拟开发环境。Bochs是一个仿真x86 CPU的开源项目,目前还支持AMD64 CPU,在它上面可以运行Linux操作系统。其它一些商业的仿真软件如vmware和virtualPC可以仿真一个真实的x86计算机,而Virtutech Simics仿真器可以仿真多种CPU和硬件,功能强大,可用于硬件和系统软件的评测。

SkyEye是一个指令级模拟器,可以模拟多种嵌入式开发板,可支持多种CPU指令集,在SkyEye上运行的操作系统意识不到它是在一个虚拟的环境中运行,而且开发人员可以通过SkyEye调试操作系统和系统软件。由于SkyEye的目标不是验证硬件逻辑,而是协助开发、调试和学习系统软件,所以在实现上SkyEye与真实的硬件环境相比还是有一定差别的。SkyEye在时钟节拍的时序上不保证与硬件完全相同,对软件透明的一些硬件仿真进行了一定的简化。这样带来的好处是SkyEye的执行效率更高。SkyEye的推出具有下面三方面的意义:

  • 通过SkyEye仿真集成环境可以很方便地进入到嵌入式系统软件学习和开发的广阔天地中。尤其对于缺少嵌入式硬件开发环境和软件开发环境的用户来说,它将是一个非常有效的学习工具和开发手段,因为SkyEye的整个软件系统都是Open Source的,且基于GPL协议(μCOS-II除外)。因此,如果要学习Linux操作系统或者进行嵌入式系统开发,但苦于没有硬件支持,SkyEye仿真环境软件是一个很好的选择!
  • 如果想研究与具体硬件无关的系统软件(如TCP/IP协议栈等),采用SkyEye可以有效地提高工作效率,因为你可以直接在μCOS-II和μCLinux for SkyEye上进行开发和调试,而与具体硬件打交道的各种driver已经存在,且有源码级调试环境,只需关心高层的逻辑设计和实现就可以了。
  • SkyEye本身作为一个开放式的项目体系,可以划分为多个独立的子项目系统。通过参与SkyEye的各个子项目,与大家共同交流、协作,可以进一步学习、分析、精通Linux内核,掌握ARM嵌入式CPU编程。

在32位嵌入式CPU领域中,ARM系列CPU所占比重很大,而ARM7TDMI是其中最广泛的一种ARM CPU核,因此SkyEye首先选择了ARM7TDMI作为仿真的目标CPU核,当然将来SkyEye会支持更多种类的CPU。目前在SkyEye上可运行并进行源码级调试ARM Linux、μCLinux、μC/OS-II操作系统和LwIP(一个著名的嵌入式TCP/IP实现)、MiniGUI(一个著名的嵌入式GUI系统)等系统软件。SkyEye可用于学习,分析,开发这些系统软件的实现,了解ARM嵌入式CPU编程。而这一切都可在一个纯软件的环境中完成。通过分析SkyEye本身实现,系统软件开发人员对ARM,8019as(NE2000兼容)以太网络芯片等硬件的了解也会更深入。

SkyEye并不能取代开发板等硬件的功能,但通过它可以比较容易进入到嵌入式软件的广阔天地中。由于SkyEye建立在GDB基础之上,使用者可以方便地使用GDB提供的各种调试手段对SkyEye仿真系统上的软件进行源码级的调试,还可以进行各种分析,如执行热点分析、程序执行覆盖度分析等。由于SkyEye提供了源代码和相关文档,有经验的用户完全可以修改和扩充SkyEye来满足自己的需求。

二.SkyEye模拟硬件介绍
目前SkyEye模拟了大量的硬件,包括CPU内核、存储器、存储器管理单元、缓存单元、串口、网络芯片、时钟等。下面做一简单介绍。

1.CPU和开发板系列
目前SkyEye可以模拟的CPU主要是基于ARM内核的CPU,包括ARM7TDMI,ARM720T,ARM9TDMI,ARM9xx,ARM10xx,StrongARM,XScale等。ARM7/9/10TDMI是ARM系列CPU的基本核心部分,它们不支持MMU/CACHE和一些扩展指令,是ARM CPU基本核。ARM720T、ARM920T、ARM10xx、StrongARM、Xscale是建立在以上ARM CPU核上,并扩展了MMU/CACHE和其它功能。各硬件开发公司可以根据它们的需求在上述CPU核上加上特定的扩展,形成基于各种ARM基本核心的特定CPU,如Atmel91X40和 ep7312,分别扩展了ARM7TDMI和ARM720T的内存控制和各种I/O控制器,简化了开发板的逻辑设计,大大增强了开发板的功能。

目前SkyEye模拟的开发板包括基于Atmel 91X40/AT91RM92 CPU的开发板,基于Crirus Logic ep7312的开发板、基于StrongARM CPU的ADSBITSY开发板,基于XScale PXA250 CPU的LUBBOCK开发板、基于SAMSUNG S3C4510B/S3C44B0 CPU的开发板、基于SHARP LH7A400 CPU的开发板、基于Philip LPC22xx CPU的开发板等。主要模拟了对应各个开发板的串口、时钟、RAM、ROM、LCD、网络芯片等硬件外设。

2.存储器管理单元和缓存单元
MMU(Memory Management Unit)即存储器管理单元,是用来管理虚拟内存系统的硬件。MMU的两个主要功能是:将虚地址转换成物理地址;控制存储器的存取权限。MMU关掉时,虚地址直接输出到物理地址总线。MMU本身有少量存储空间存放从虚拟地址到物理地址的匹配表,此表称作TLB(Translation Lookaside Buffers)。TLB表中保存的是虚址及其对应的物理地址,权限,域和映射类型。当CPU对一虚拟地址进行存取时,首先搜索TLB表以查找对应的物理地址等信息,如果没有查到,则进行查找translation table,称为Translation Table Walk(简称TTW)。经过TTW过程后,将查到的信息保存到TLB。然后根据TLB表项的物理地址进行读写。CACHE是缓存单元,主要用于缓存内存中的数据,其读写速度远快于内存的读写速度,所以可以提高CPU的内存数据的访问效率。

write/read buffer硬件单元的作用与CACHE的作用类似。MMU、CACHE、write/read buffer一般是高性能CPU的重要组成部分,且不同类型CPU的MMU、CACHE、write/read buffer的逻辑行为也有一定的差异。为了支持模拟多种类型CPU的MMU/CACHE,SkyEye包含了一个通用的MMU/CACHE模拟实现。通过对一些参数的调整可以支持模拟多种类型的MMU/CACHE物理结构和逻辑行为。

3.网络芯片
目前SkyEye模拟了网络芯片8019AS,其特点是:NE2000兼容,内建 16KRAM缓冲区,10MB传输速率。虽然目前模拟的开发板上不一定有网络芯片8019AS,但我们可以在我们模拟的开发板上加上网络芯片8019AS的模拟。这样再加上在不同操作系统上的8019AS驱动程序,就可以方便地完成各种网络应用的开发和设计。目前已经在在基于Atmel91X40 CPU的开发板上实现了网络芯片8019AS扩展,并增加了μC/OS-II和μClinux的网络驱动程序,已经支持大量的网络应用程序,如LwIP (一个TCP/IP协议栈实现)、nfs server/clinet、http server/client、telnet server/client、ftp server/client等。

三.SkyEye的设计实现
1.SkyEye设计原则

SkyEye软件的核心在目标模拟模块。为了提高模拟效率,且能够模拟更多的CPU、开发板和各种外设,方便开发人员进行开发和学习,SkyEye遵循如下的设计原则:

  • 用C语言编程,采用面向对象的方式定义各种要模拟的硬件;
  • 定义抽象模拟硬件对象,最大化重用代码;
  • 定义硬件配置脚本,使得不用改动SkyEye代码就可以灵活地调整各种硬件配置;

保持与GDB上层接口的一致性,这样可充分利用GDB强大的源代码级调试功能。

2.SkyEye的总体结构
SkyEye基于GDB/ARMulator(目前由David McCullough 维护),并进行了全面的改变和扩展。SkyEye建立在GNU GDB的底层,可以模仿多种完整的嵌入式计算机系统,目前模拟的硬件包括CPU、内存、I/O寄存器、时钟、UART、网络芯片、MMU、CACHE,将来还会模拟 LCD、USB等各种硬件。在SkyEye上运行的操作系统和各种系统软件”意识”不到它们是在一个虚拟的计算机系统上运行。

SkyEye从总体上分为四个层次:

  • 用户接口模块:包括命令行用户界面和图形用户界面,完成处理用户的输入命令,并把相关调试数据输出给用户的任务。这一部分基本上直接利用了GDB的用户接口模块,并在此基础上有一定的扩充。
  • 符号处理模块:主要处理执行文件的头信息,解释执行文件中内嵌的debuger调试信息,对符号表的管理,对源代码表达式的解析,定位源代码中的语句位置和机器码的位置关系等。这一部分也是直接利用了GDB的符号处理模块,也正是有了这个模块的支持,SkyEye可以支持源码级调试。
  • 目标控制模块:主要完成执行控制(如中断程序的执行,设置中断条件等),程序栈结构分析,对具体目标硬件的控制(如本地调试、远程调试和模拟调试的控制)。这一部分完成对SkyEye上运行的软件的控制,提供了多种调试手段。
  • 目标模拟模块:这一部分是SkyEye的核心。它的功能是模仿计算机系统中的主要硬件(包括CPU、内存和各种硬件外设等)的执行,对执行文件的机器指令进行解释,并模拟执行每一条机器指令,产生相应的硬件响应等。

四.小结
本文主要介绍了SkyEye硬件模拟平台的起源、背景和发展状况。如果读者能够熟练掌握SkyEye的使用,则会提高开发、调试操作系统等的进度,特别是对操作系统、驱动程序如何与嵌入式硬件系统进行交互有更深刻的了解。SkyEye还在不断地发展之中,对SkyEye感兴趣的读者需要跟踪最新的SkyEye源码和相关文档,并可在SkyEye论坛上与SkyEye开发人员进行实时交流。

关于作者
陈渝, 清华大学,通过 yuchen@tsinghua.edu.cn 可以和他联系。

我校每年一般集中两次上报研究生就业方案,即春季上报和暑期上报 。春季上报就业方案的时间为2月下旬,暑期上报就业方案的时间为5月下旬,各学院学生工作办公室研究生辅导员负责收取毕业研究生就业协议,统一到研究生院管理办公室盖章,并根据辽教办[2002]94号文件和辽财综[2002]246号文件的有关规定,收取毕业研究生派遣费100 。春季毕业的研究生,最迟应于2004年1月10日前签订就业协议书,落实就业单位,暑期毕业的研究生最迟应在2004年5月10日前签订就业协议书,落实就业单位,并将就业协议统一交到各学院研究生辅导员处,否则不能及时派遣。

按照国家的有关规定,提前毕业的研究生,从正式取得学籍起满两年,方可毕业。凡是提前毕业的研究生,必须在5月10日前落实就业单位,且正式派遣前必须通过毕业论文答辩,取得学位 。

根据教育部规定,毕业研究生落实就业单位,必须签订国家教育部统一制定的就业协议书 。协议书一式四份,毕业研究生签订就业协议时,由研究生本人签字,用人单位签章后,学校方予盖章 。协议一经签订,不得擅自改变 。若毕业生有正当理由需要变动的,需由本人提出申请,并征得用人单位同意后,经学校批准,按违约处理 。根据辽教办[2002]94号文件和辽财综[2002]246号文件的有关规定,结合我校实际情况,凡是违约的毕业研究生,需向学校缴纳违约金2000

 

2004年10月12日

 ctl_io = g_io_channel_unix_new(hcid.sock);
 g_io_add_watch(ctl_io, G_IO_IN, io_stack_event, NULL);

 /* Start event processor */
 g_main_run(event_loop);

 free_device_opts();

 syslog(LOG_INFO, “Exit.”);
 return 0;

g_main_run是hcid deamon的主循环,定义如下:

#define         g_main_run(loop)        g_main_loop_run(loop)

void       g_main_loop_run        (GMainLoop    *loop)
{
 int open_max = sysconf(_SC_OPEN_MAX);
 struct pollfd *ufds = malloc(open_max * sizeof(struct pollfd));

//pollfd结构是我们要注意的,

 while (!loop->bail) {//loop->bail初始化是0,搜遍整个源代码,只有一处

                              //地方赋值;
  int nfds, rc, i;
  struct watch *p, *w;

  nfds = 0;
  for (w = watch_head.next; w != NULL; w = w->next) {

//watch结构有hcid->sock’s fd;
   ufds[nfds].fd = w->channel->fd;
   ufds[nfds].events = w->condition;

//目前condition=POLLIN;监视的socket有io数据进入时,
   ufds[nfds].revents = 0;
   nfds++;
  }
  
  rc = poll(ufds, nfds, -1);

//poll论询ufds数组中的所有文件描述符,判断那个文件描述符有数据

//流入

  if (rc < 0 && (errno == EINTR))
   continue;

  if (rc < 0) {
   perror(“poll”);
   continue;
  }
  
  p = &watch_head;
  w = watch_head.next;
  i = 0;
  while (w) {
   if (ufds[i].revents) {
    gboolean keep = w->func(w->channel, ufds[i].revents, w->user_data);

//g_io_add_watch(ctl_io, G_IO_IN, io_stack_event, NULL)函数注册watch

//结构的func成员函数为io_stack_event;

//io_stack_event函数从有数据的文件描述符中读出数据(read),

//注意这个数据包的格式:数据包类型(1B),事件类型(1B),参

//数长度(1B),参数1,……

gboolean io_stack_event(GIOChannel *chan, GIOCondition cond, gpointer data)
{
 unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr;
 evt_stack_internal *si;
 hci_event_hdr *eh;
 int type;
 size_t len;
 GIOError err;

 ptr = buf;

 if ((err = g_io_channel_read(chan, buf, sizeof(buf), &len))) {

//从文件描述符中读取数据;
  if (err == G_IO_ERROR_AGAIN)
   return TRUE;

  syslog(LOG_ERR, “Read from control socket failed. %s(%d)”,
    strerror(errno), errno);
  g_main_quit(event_loop);
  return FALSE;
 }

//开始解包;

 type = *ptr++;

 if (type != HCI_EVENT_PKT)//判断数据包的第一个字节
  return TRUE;

 eh = (hci_event_hdr *) ptr;
 if (eh->evt != EVT_STACK_INTERNAL)
  return TRUE;

//如果这个数据包不是EVT_STACK_INTERNAL则返回;

//问题:EVT_STACK_INTERNAL事件是干什么用的?

//难道是内部事件,也就是没有实际的蓝牙设备是用于模拟的?

//arm i correct?

 ptr += HCI_EVENT_HDR_SIZE;

 si = (evt_stack_internal *) ptr;
 switch (si->type) {
 case EVT_SI_DEVICE:
  device_event(chan, si);
  break;
 }

 return TRUE;
}

//最重要的一点是:接受的数据没有处理,why,应该在那里处理?

//
    if (!keep) {
      p->next = w->next;
      memset(w, 0, sizeof(*w));
      w = p->next;
      i++;
      continue;
    }
   } 
  
   p = w;
   w = w->next;
   i++;
  }

 } free(ufds);
}

预备知识:

hci,l2cap,bnep模块加载进内核时,要注册对应协议层的
socket操作函数级,proto_ops是socket操作函数级的结构;
注意:不是每层协议都要自己实现socket操作函数集的所有函数;
如没实现,使用sock_no_*空函数替代;
struct proto_ops hci_sock_ops = {
static struct proto_ops bnep_sock_ops = {
struct proto_ops {
  int family;

  int (*release) (struct socket *sock);
  int (*bind)  (struct socket *sock, struct sockaddr *umyaddr,
    int sockaddr_len);
  int (*connect) (struct socket *sock, struct sockaddr *uservaddr,
    int sockaddr_len, int flags);
  int (*socketpair) (struct socket *sock1, struct socket *sock2);
  int (*accept) (struct socket *sock, struct socket *newsock,
    int flags);
  int (*getname) (struct socket *sock, struct sockaddr *uaddr,
    int *usockaddr_len, int peer);
  unsigned int (*poll) (struct file *file, struct socket *sock, struct poll_table_struct *wait);
  int (*ioctl) (struct socket *sock, unsigned int cmd,
    unsigned long arg);
  int (*listen) (struct socket *sock, int len);
  int (*shutdown) (struct socket *sock, int flags);
  int (*setsockopt) (struct socket *sock, int level, int optname,
    char *optval, int optlen);
  int (*getsockopt) (struct socket *sock, int level, int optname,
    char *optval, int *optlen);
  int   (*sendmsg) (struct socket *sock, struct msghdr *m, int total_len, struct scm_cookie *scm);
  int   (*recvmsg) (struct socket *sock, struct msghdr *m, int total_len, int flags, struct scm_cookie *scm);
  int (*mmap)  (struct file *file, struct socket *sock, struct vm_area_struct * vma);
  ssize_t (*sendpage) (struct socket *sock, struct page *page, int offset, size_t size, int flags);
};
bind调用时,hcid.sock是一个int值;而在内核定义时,
bind(hcid.sock, (struct sockaddr *) &addr, sizeof(addr))
struct hcid_opts hcid;
struct hcid_opts {
 ……
 int     sock;
};
而在内核定义时,
static int hci_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
第一个参数是一个socket的结构指针;这是如何处理的?
应该看一下socket是如何产生的!
注意到proto_ops socket操作函数集没有socket函数,
问题
socket函数在那里实现???
linux将bsd socket实现为属于sockfs特殊文件系统的文件。更准确的说,
对于每一个新建的bsd socket,内核都在sockfs特殊文件系统中创建一个
对应的inode.bsd socket的属性存放在一个socket结构中,这个结构是
包含在inode的u.socket_i域中。
socket->sys_socket->sock_create
socket系统调用触发sys_socket,sys_socket返回值是新分配的文件描述符,
sys_socket作三个工作:
为新的bsd socket分配一个描述符
根据指定的网络体系结构,通信模式以及协议初始化这个描述符
分配进程第一个可用的文件描述符,给文件描述符和socket对象联系一个新的文件object;
sys_socket:
1.sock_create
2.sock_map_fd得到一个文件描述符,文件结构指针,目录结构指针,初始化他们。
asmlinkage long sys_socket(int family, int type, int protocol)
{
 int retval;
 struct socket *sock;

 retval = sock_create(family, type, protocol, &sock);
 if (retval < 0)
  goto out;

 retval = sock_map_fd(sock);
 if (retval < 0)
  goto out_release;

out:
 /* It may be already another descriptor 8) Not kernel problem. */
 return retval;

out_release:
 sock_release(sock);
 return retval;
}
sock_create:
1.sock_alloc:分配一个新的inode和socket对象。将他们绑定在一起并且初始化。
返回socket对象。
Allocate a new inode and socket object. The two are bound together
and initialised. The socket is then returned. If we are out of inodes
NULL is returned.
2. net_families[family]->create(sock, protocol))
对于已知的每种网络体系结构,内核都会在net_families数组中存放一个类型为net_proto_family
对象的指针。基本上,这个对象只要定义create方法,内核初始化这种体系结构的socket时会调用
这个方法。针对我们的hci协议,在hci_socket.c文件中:
struct net_proto_family hci_sock_family_ops = {
 family: PF_BLUETOOTH,
 create: hci_sock_create
};//注册net_families对象,提供了create方法(定义在该文件中)
所以这里调用每个协议自己的create方法。
int sock_create(int family, int type, int protocol, struct socket **res)
{
 int i;
 struct socket *sock;
 if (!(sock = sock_alloc()))
 {
  printk(KERN_WARNING “socket: no more sockets\n”);
  i = -ENFILE;  /* Not exactly a match, but its the
        closest posix thing */
  goto out;
 }
 sock->type  = type;
 if ((i = net_families[family]->create(sock, protocol)) < 0)
 {
  sock_release(sock);
  goto out;
 }
 *res = sock;
out:
 net_family_read_unlock();
 return i;
}
net_families[family]->create:
1.sk_alloc分配一个sock结构,请注意socket和sock的区别
2.sock_init_data初始化sock对象,
3.bluez_sock_link(&hci_sk_list, sk)
hci_sk_list是一个全局变量,此函数的目的是将sk加入此链表的头部;
static int hci_sock_create(struct socket *sock, int protocol)
{
 struct sock *sk;

 BT_DBG(“sock %p”, sock);

 if (sock->type != SOCK_RAW)
  return -ESOCKTNOSUPPORT;

 sock->ops = &hci_sock_ops;

 if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1)))
  return -ENOMEM;

 sock->state = SS_UNCONNECTED;
 sock_init_data(sock, sk);

 memset(&sk->protinfo, 0, sizeof(struct hci_pinfo));
 sk->destruct = NULL;
 sk->protocol = protocol;
 sk->state    = BT_OPEN;

 bluez_sock_link(&hci_sk_list, sk);

 MOD_INC_USE_COUNT;
 return 0;
}
总结:socket->sys_socket->net_familiy-create;

其他的socket函数调用类似:

bind->sys_bind->从bind传入的fd文件描述符中得出socket结构,调用socket->ops->bind即可;

其他函数的调用依次类推。

结束。

http://www.ha.xinhuanet.com/fuwu/kejiao/2003-11/20/content_1225142.htm

今年校园招聘凸现五大特点
( 2003-11-20)

——————————————————————–

    新华网河南频道11月20日电  11月20日,一年一度的校园招聘会大幕又将拉开,各个企业纷纷摩拳擦掌,而代理校园招聘业务最出色的中华英才、前程无忧和智联招聘三大网站更是早早开始了前期准备,各个网站也已经各就各位,只等20日枪一响就杀向高校,象牙塔内一年最热闹的时

期即将到来。

    ■校园招聘五大新特点

    特点一:开始早,动手早。虽然规定11月20日后才能进入,但各个企业早就把自己的招聘计划公之于众:TCL集团宣布将在全国招聘2004届高校毕业生共920人,其中研究生165人、本科生746人、大专生9人;专业涉及通讯类、电子类、机械类、计算机类、市场营销、管理类、财会类等,非常详细,而11月22日、23日,TCL将分别进驻北京的清华大学和中国人民大学揽才;摩托罗拉公司今年计划招聘144人,主要分布在研发、技术工程、市场,财务及其他管理部门;11月24日,多位飞利浦全球及中国高层管理者将在清华大学进行演讲,与学生分享飞利浦CEO成功经验的同时,也开始了新一轮的招聘。摩托罗拉中国公司全国招聘经理穆凌云在接受学生提问时也表示,校园招聘是公司非常重要的一个项目,今年希望能够把招聘的数量提高25%至30%左右。

    特点二:国内企业崛起迅速。智联招聘的李建介绍从企业方面看,世界500强跨国企业需求相对减弱,相反今年国内大型企业如联想电脑公司、宁波波导股份有限公司、泰康人寿保险股份有限公司等需求明显。其中联想的招聘范围涉及研发类、工程类、产品类、信息系统类、客服类、生产运作类、市场销售类等。

    特点三:线上递交简历流行。中华英才网专门负责校园招聘的唐桉告诉记者,今年企业在招聘大学生时最大的特点就是采取了线上投递简历的方式,如联想、INTER等公司都采取这一方式,即公司在网上存一份简历模板,学生需在企业规定的时间段内上网填写,进行筛选后再通知面试,唐桉说:“现在的公司越来越希望简历能够简单明了,既可以清楚了解学生还能提高效率,作为学生来讲就要选出最能体现自己特点的内容填写,不要大而全。如果对方要求你填写工作经历,可以考虑填写一两个案例,叙述在发生某事时你是如何做的,由此说明你具有哪方面能力,现在这种简历最为用人单位所青睐。”前程无忧的王建则认为,网上投放简历可以使企业从最基本的网上操作来考察学生,而且很多跨国公司的网站都是英文的,所以又考察了英语水平,而网上对公司的一些介绍,学生则应该很好利用,以便在填写简历时有的放矢。

    特点四:所有职业需求与去年相比有增无减。记者在对三大网站采访中了解到,虽然还没有具体数据,综合今年各个行业的职位需求与去年比较感觉大部分行业的职位有所增加,而且未有明显增幅的行业也未出现用人减少的迹象。在智联代理的企业中,今年的职位与往年一样更集中在技术类的工作,与往年没有特别大的区别。热门的职位有计算机技术和相关职位,财务会计类职位,其他的技术、服务、销售以及研发人员。唐桉介绍在中华英才网方面,航天类的职位在今年成为新宠,有明显增长的趋势,他分析这与载人飞船成功升天不无关系,而电子类、食品类、咨询类、IT类、通讯类、医药类职业依然需求量最大。王建介绍,从全国情况来看现在沿海城市需求最大,一些公司在沿海地区的分公司也成为用人大户,自动化、机械、塑料、通讯和医疗都已成为热门行业。

    特点五:提供实习、征文大赛成为今年企业校园招聘的新招。摩托罗拉日前推出一个叫摩托营的项目,穆凌云介绍说:“这实际上是为公司提供一个人才输送的渠道,我们要在全国的十几家高校当中请大概160名左右的实习生,他们基本上是大三或者研究生最后一年的学生,在我们公司做6个月到12个月实习,然后根据业务部门的评估标准,表现非常优秀的我们就会留下来做我们的正式员工。”而飞利浦则采取了征文大赛的方式作为挑选新员工渠道之一,据其负责人介绍,公司将从中选出60篇优秀的文章,授予作者“PhilipsStar”荣誉称号,除给予相应奖励外,“PhilipsStar”将优先获得在飞利浦实习和优先获得加盟飞利浦的机会。

    ■雇主青睐哪类毕业生

    今年哪类毕业生最为雇主所看中,在采访中各个用人公司普遍认为,刚刚从学校毕业的高校生有活力有热情,具有创造性,会从新的角度去思考问题,跟得上行业的变革趋势。他们全新的思维、狂热的冲力往往会带来意想不到的成果。松下中国公司人力资源部部长陈恺认为,一个员工的创造性和可塑性是最为重要的,也是首先要发现和发掘的,因为没有创造性,只会安于现状是不可能对公司的发展有所贡献,是所谓的“庸才”。但只按个人意愿行事将不能发挥组织、团队作为经营主体的作用,是所谓的“蠢才”。光有个人创造性缺少集体协作能力同样是行不通的,几乎所有的招聘企业都表示,当今社会是协作分工的社会,员工必须要具有协作精神,通过优势互补,打造最佳合力,获得1+1>2的效果。

    而对于现在工作经验越来越受到重视这一现象,前程无忧的副总裁简思怀认为不能简单地从工作经验去判断一个人的能力,她认为工作经验是过去的东西,而能力则是不断向前发展的东西,应更为看重工作热情,能力可以靠成长和磨练不断提高,热情则是工作的“永动力”。西门子(中国)有限公司人事部经理谢克海也表示,“学历只是外在的东西”,人才素质应包括知识、经验、能力三方面,“而这三方面,我最看重的是能力,其次是经验,最后才是知识。”

    另外,毕业生创造力和潜力也是各个公司所看重的,联合利华中国人力资源部招募经理倪铮介绍,才能模型是根据联合利华全球200位最优秀职业经理人表现出的行为和能力综合而成,根据这个资质才能模型,联合利华考察的是候选人的综合素质以及表现出的潜质。她介绍对毕业生来讲,公司更看重其潜力和今后的发展空间。

    摩托罗拉(中国)电子有限公司中国区高校关系经理毛萍介绍,公司会考虑职位需求情况来决定招收什么样的学生,没有倾向性,有特长的学生会更有竞争性,有特长、有创新能力的学生有更多的机会加入摩托罗拉。

作者:李涛 编辑:杨静
来源:北京青年报
2004年10月11日

首先需要明确的一点是,bluez协议栈实现的是从hci层向上的
各层协议,包括hci,l2cap,bnep还有其他协议,但我们只用这三个。
每层协议通过模块加载,注册自己的socket opts操作函数;
也就是说每层协议的具体socket函数你可以在bluez源码中找到。
如hci socket opts–hci_sock.c
l2cap socket opts–l2cap.c
bnep socket opts–bnep/sock.c

gcc的参数 -I (i)nclude 指出头文件的目录
  -L (l)ib 指出库函数的目录
ar工具可以生成静态库, libxxx.a a–archive
静态库和动态库存放在同一个目录下,只是后缀不同

The Host Controller Interface defines an Application Programming Interface(API) between the
host and the Bluetooth hardware. The HCI API is transport independent.
hci api与具体的传输介质无关
bluez应该也实现了bcsp,它的源代码也需要阅读一下,如何查找?
bcsp协议使用串口的三根线,gnd,rx,tx.
To convert the RS232 byte stream into a BCSP packet stream, a version of the SLIP packet
framing is implemented. SLIP is outlined in RFC 1055.
我们在rs232上传送比特流,在bcsp上传送hci messages?
阅读linux设备驱动程序的笔记
libc函数库不属于内核,printf属于libc,printk属于内核开放出来的
函数,应该在system.map中有相应的项。
static struct hci_uart_proto bcsp = {//操作函数,为什么只定义这
//几个,是否参照标准的串口设备
 id:      HCI_UART_BCSP,
 open:    bcsp_open,
 close:   bcsp_close,
 enqueue: bcsp_enqueue,
 dequeue: bcsp_dequeue,
 recv:    bcsp_recv,
 flush:   bcsp_flush
};

struct bcsp_struct {
 struct sk_buff_head unack; /* Unack’ed packets queue */
 struct sk_buff_head rel; /* Reliable packets queue */
 struct sk_buff_head unrel; /* Unreliable packets queue */
//sk_buff建立了三条告诉缓存链表,为什么使用sk_buff这种告诉缓存,主要因为
//上层的协议都实现socket接口
 unsigned long rx_count;
 struct  sk_buff *rx_skb;
 u8      rxseq_txack;  /* rxseq == txack. */
 u8      rxack;   /* Last packet sent by us that the peer ack’ed */
 struct  timer_list tbcsp;
 
 enum {
  BCSP_W4_PKT_DELIMITER,
  BCSP_W4_PKT_START,
  BCSP_W4_BCSP_HDR,
  BCSP_W4_DATA,
  BCSP_W4_CRC
 } rx_state;

 enum {
  BCSP_ESCSTATE_NOESC,
  BCSP_ESCSTATE_ESC
 } rx_esc_state;

 u16     message_crc;
 u8      txack_req;  /* Do we need to send ack’s to the peer? */

 /* Reliable packet sequence number – used to assign seq to each rel pkt. */
 u8      msgq_txseq;
};

static int bcsp_open(struct hci_uart *hu)
{
 struct bcsp_struct *bcsp;

 BT_DBG(“hu %p”, hu);

 bcsp = kmalloc(sizeof(*bcsp), GFP_ATOMIC);
 if (!bcsp)
  return -ENOMEM;
 memset(bcsp, 0, sizeof(*bcsp));
 //struct正常的使用顺序,
 //kmalloc分配空间,
 //初始化为0,
 //填充结构的各个成员。

 hu->priv = bcsp;
 //hu是hci_uart结构,priv的定义是void *priv这种定义方式
 //经常使用,不太了解具体的用途
 
 skb_queue_head_init(&bcsp->unack);
 skb_queue_head_init(&bcsp->rel);
 skb_queue_head_init(&bcsp->unrel);

 init_timer(&bcsp->tbcsp);
 bcsp->tbcsp.function = bcsp_timed_event;
 bcsp->tbcsp.data     = (u_long) hu;

 bcsp->rx_state = BCSP_W4_PKT_DELIMITER;

 return 0;
}

static inline void skb_queue_head_init(struct sk_buff_head *list)
{
 spin_lock_init(&list->lock);
 list->prev = (struct sk_buff *)list;
 //注意这条语句,(struct sk_buff *)list应该是进行了cast处理,
 //是不是他们的头地址对齐,因为他们的空间不一样大,
 //但是sk_buff_head的头四个成员可以嵌入sk_buff中;
 list->next = (struct sk_buff *)list;
 list->qlen = 0;
}

struct sk_buff_head {
 /* These two members must be first. */
 struct sk_buff * next;
 struct sk_buff * prev;

 __u32  qlen;
 spinlock_t lock;
};

struct sk_buff {
 /* These two members must be first. */
 struct sk_buff * next;   /* Next buffer in list     */
 struct sk_buff * prev;   /* Previous buffer in list    */

 struct sk_buff_head * list;  /* List we are on    */
 struct sock *sk;   /* Socket we are owned by    */
 ————————————————-
 struct timeval stamp;   /* Time we arrived    */
 struct net_device *dev;  /* Device we arrived on/are leaving by  */

 /* Transport layer header */
 union
 {
  struct tcphdr *th;
  struct udphdr *uh;
  struct icmphdr *icmph;
  struct igmphdr *igmph;
  struct iphdr *ipiph;
  struct spxhdr *spxh;
  unsigned char *raw;
 } h;

 /* Network layer header */
 union
 {
  struct iphdr *iph;
  struct ipv6hdr *ipv6h;
  struct arphdr *arph;
  struct ipxhdr *ipxh;
  unsigned char *raw;
 } nh;
 
 /* Link layer header */
 union
 { 
    struct ethhdr *ethernet;
    unsigned char  *raw;
 } mac;

 struct  dst_entry *dst;

 /*
  * This is the control buffer. It is free to use for every
  * layer. Please put your private variables there. If you
  * want to keep them across layers you have to do a skb_clone()
  * first. This is owned by whoever has the skb queued ATM.
  */
 char  cb[48]; 

 unsigned int  len;   /* Length of actual data   */
  unsigned int  data_len;
 unsigned int csum;   /* Checksum      */
 unsigned char  __unused,  /* Dead field, may be reused   */
   cloned,   /* head may be cloned (check refcnt to be sure). */
     pkt_type,  /* Packet class     */
     ip_summed;  /* Driver fed us an IP checksum   */
 __u32  priority;  /* Packet queueing priority   */
 atomic_t users;   /* User count – see datagram.c,tcp.c   */
 unsigned short protocol;  /* Packet protocol from driver.   */
 unsigned short security;  /* Security level of packet   */
 unsigned int truesize;  /* Buffer size      */

 unsigned char *head;   /* Head of buffer     */
 unsigned char *data;   /* Data head pointer    */
 unsigned char *tail;   /* Tail pointer     */
 unsigned char  *end;   /* End pointer     */

 void   (*destructor)(struct sk_buff *); /* Destruct function  */
#ifdef CONFIG_NETFILTER
 /* Can be used for communication between hooks. */
        unsigned long nfmark;
 /* Cache info */
 __u32  nfcache;
 /* Associated connection, if any */
 struct nf_ct_info *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
        unsigned int nf_debug;
#endif
#endif /*CONFIG_NETFILTER*/

#if defined(CONFIG_HIPPI)
 union{
  __u32 ifield;
 } private;
#endif

#ifdef CONFIG_NET_SCHED
       __u32           tc_index;               /* traffic control index */
#endif
};

/* Recv data */
static int bcsp_recv(struct hci_uart *hu, void *data, int count)
{//count是数据包data的字节数,data是bcsp包,请对照bcsp speiciality
//
 struct bcsp_struct *bcsp = hu->priv;
 register unsigned char *ptr;

 BT_DBG(“hu %p count %d rx_state %ld rx_count %ld”,
  hu, count, bcsp->rx_state, bcsp->rx_count);

 ptr = data;
 while (count) {
  if (bcsp->rx_count) {
   if (*ptr == 0xc0) {
    BT_ERR(“Short BCSP packet”);
    kfree_skb(bcsp->rx_skb);
    bcsp->rx_state = BCSP_W4_PKT_START;
    bcsp->rx_count = 0;
   } else
    bcsp_unslip_one_byte(bcsp, *ptr);

   ptr++; count–;
   continue;
  }

  switch (bcsp->rx_state) {
  case BCSP_W4_BCSP_HDR:
   if ((0xff & (u8) ~ (bcsp->rx_skb->data[0] + bcsp->rx_skb->data[1] +
     bcsp->rx_skb->data[2])) != bcsp->rx_skb->data[3]) {
    BT_ERR(“Error in BCSP hdr checksum”);
    kfree_skb(bcsp->rx_skb);
    bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
    bcsp->rx_count = 0;
    continue;
   }
   if (bcsp->rx_skb->data[0] & 0×80 /* reliable pkt */
         && (bcsp->rx_skb->data[0] & 0×07) != bcsp->rxseq_txack) {
    BT_ERR (“Out-of-order packet arrived, got %u expected %u”,
     bcsp->rx_skb->data[0] & 0×07, bcsp->rxseq_txack);

    kfree_skb(bcsp->rx_skb);
    bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
    bcsp->rx_count = 0;
    continue;
   }
   bcsp->rx_state = BCSP_W4_DATA;
   bcsp->rx_count = (bcsp->rx_skb->data[1] >> 4) +
     (bcsp->rx_skb->data[2] << 4); /* May be 0 */
   continue;

  case BCSP_W4_DATA:
   if (bcsp->rx_skb->data[0] & 0×40) { /* pkt with crc */
    bcsp->rx_state = BCSP_W4_CRC;
    bcsp->rx_count = 2;
   } else
    bcsp_complete_rx_pkt(hu);
   continue;

  case BCSP_W4_CRC:
   if (bcsp_crc_reverse(bcsp->message_crc) !=
     (bcsp->rx_skb->data[bcsp->rx_skb->len - 2] << 8) +
     bcsp->rx_skb->data[bcsp->rx_skb->len - 1]) {

    BT_ERR (“Checksum failed: computed %04x received %04x”,
     bcsp_crc_reverse(bcsp->message_crc),
          (bcsp->rx_skb-> data[bcsp->rx_skb->len - 2] << 8) +
          bcsp->rx_skb->data[bcsp->rx_skb->len - 1]);

    kfree_skb(bcsp->rx_skb);
    bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
    bcsp->rx_count = 0;
    continue;
   }
   skb_trim(bcsp->rx_skb, bcsp->rx_skb->len – 2);
   bcsp_complete_rx_pkt(hu);
   continue;

  case BCSP_W4_PKT_DELIMITER:
   switch (*ptr) {
   case 0xc0:
    bcsp->rx_state = BCSP_W4_PKT_START;
    break;
   default:
    /*BT_ERR(“Ignoring byte %02x”, *ptr);*/
    break;
   }
   ptr++; count–;
   break;

  case BCSP_W4_PKT_START:
   switch (*ptr) {
   case 0xc0:
    ptr++; count–;
    break;

   default:
    bcsp->rx_state = BCSP_W4_BCSP_HDR;
    bcsp->rx_count = 4;
    bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC;
    BCSP_CRC_INIT(bcsp->message_crc);
    
    /* Do not increment ptr or decrement count
     * Allocate packet. Max len of a BCSP pkt=
     * 0xFFF (payload) +4 (header) +2 (crc) */

    bcsp->rx_skb = bluez_skb_alloc(0×1005, GFP_ATOMIC);
    if (!bcsp->rx_skb) {
     BT_ERR(“Can’t allocate mem for new packet”);
     bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
     bcsp->rx_count = 0;
     return 0;
    }
    bcsp->rx_skb->dev = (void *) &hu->hdev;
    break;
   }
   break;
  }
 }
 return count;
}

/*
 * The DEVICE structure.
 * Actually, this whole structure is a big mistake.  It mixes I/O
 * data with strictly “high-level” data, and it has to know about
 * almost every data structure used in the INET module.
 *
 * FIXME: cleanup struct net_device such that network protocol info
 * moves out.
 */

struct net_device
{

 /*
  * This is the first field of the “visible” part of this structure
  * (i.e. as seen by users in the “Space.c” file).  It is the name
  * the interface.
  */
 char   name[IFNAMSIZ];

 /*
  * I/O specific fields
  * FIXME: Merge these and struct ifmap into one
  */
 unsigned long  rmem_end; /* shmem “recv” end */
 unsigned long  rmem_start; /* shmem “recv” start */
 unsigned long  mem_end; /* shared mem end */
 unsigned long  mem_start; /* shared mem start */
 unsigned long  base_addr; /* device I/O address */
 unsigned int  irq;  /* device IRQ number */

 /*
  * Some hardware also needs these fields, but they are not
  * part of the usual set specified in Space.c.
  */

 unsigned char  if_port; /* Selectable AUI, TP,..*/
 unsigned char  dma;  /* DMA channel  */

 unsigned long  state;

 struct net_device *next;
 
 /* The device initialization function. Called only once. */
 int   (*init)(struct net_device *dev);

 /* ——- Fields preinitialized in Space.c finish here ——- */

 struct net_device *next_sched;

 /* Interface index. Unique device identifier */
 int   ifindex;
 int   iflink;

 struct net_device_stats* (*get_stats)(struct net_device *dev);
 struct iw_statistics* (*get_wireless_stats)(struct net_device *dev);

 /*
  * This marks the end of the “visible” part of the structure. All
  * fields hereafter are internal to the system, and may change at
  * will (read: may be cleaned up at will).
  */

 /* These may be needed for future network-power-down code. */
 unsigned long  trans_start; /* Time (in jiffies) of last Tx */
 unsigned long  last_rx; /* Time of last Rx */

 unsigned short  flags; /* interface flags (a la BSD) */
 unsigned short  gflags;
        unsigned short          priv_flags; /* Like ‘flags’ but invisible to userspace. */
        unsigned short          unused_alignment_fixer; /* Because we need priv_flags,
                                                         * and we want to be 32-bit aligned.
                                                         */

 unsigned  mtu; /* interface MTU value  */
 unsigned short  type; /* interface hardware type */
 unsigned short  hard_header_len; /* hardware hdr length */
 void   *priv; /* pointer to private data */

 struct net_device *master; /* Pointer to master device of a group,
       * which this device is member of.
       */

 /* Interface address info. */
 unsigned char  broadcast[MAX_ADDR_LEN]; /* hw bcast add */
 unsigned char  dev_addr[MAX_ADDR_LEN]; /* hw address */
 unsigned char  addr_len; /* hardware address length */

 struct dev_mc_list *mc_list; /* Multicast mac addresses */
 int   mc_count; /* Number of installed mcasts */
 int   promiscuity;
 int   allmulti;

 int   watchdog_timeo;
 struct timer_list watchdog_timer;

 /* Protocol specific pointers */
 
 void    *atalk_ptr; /* AppleTalk link  */
 void   *ip_ptr; /* IPv4 specific data */ 
 void                    *dn_ptr;        /* DECnet specific data */
 void                    *ip6_ptr;       /* IPv6 specific data */
 void   *ec_ptr; /* Econet specific data */

 struct Qdisc  *qdisc;
 struct Qdisc  *qdisc_sleeping;
 struct Qdisc  *qdisc_list;
 struct Qdisc  *qdisc_ingress;
 unsigned long  tx_queue_len; /* Max frames per queue allowed */

 /* hard_start_xmit synchronizer */
 spinlock_t  xmit_lock;
 /* cpu id of processor entered to hard_start_xmit or -1,
    if nobody entered there.
  */
 int   xmit_lock_owner;
 /* device queue lock */
 spinlock_t  queue_lock;
 /* Number of references to this device */
 atomic_t  refcnt;
 /* The flag marking that device is unregistered, but held by an user */
 int   deadbeaf;

 /* Net device features */
 int   features;
#define NETIF_F_SG  1 /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM  2 /* Can checksum only TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM  4 /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM  8 /* Can checksum all the packets. */
#define NETIF_F_DYNALLOC 16 /* Self-dectructable device. */
#define NETIF_F_HIGHDMA  32 /* Can DMA to high memory. */
#define NETIF_F_FRAGLIST 64 /* Scatter/gather IO. */

 /* Called after device is detached from network. */
 void   (*uninit)(struct net_device *dev);
 /* Called after last user reference disappears. */
 void   (*destructor)(struct net_device *dev);

 /* Pointers to interface service routines. */
 int   (*open)(struct net_device *dev);
 int   (*stop)(struct net_device *dev);
 int   (*hard_start_xmit) (struct sk_buff *skb,
          struct net_device *dev);
 int   (*hard_header) (struct sk_buff *skb,
      struct net_device *dev,
      unsigned short type,
      void *daddr,
      void *saddr,
      unsigned len);
 int   (*rebuild_header)(struct sk_buff *skb);
#define HAVE_MULTICAST   
 void   (*set_multicast_list)(struct net_device *dev);
#define HAVE_SET_MAC_ADDR    
 int   (*set_mac_address)(struct net_device *dev,
         void *addr);
#define HAVE_PRIVATE_IOCTL
 int   (*do_ioctl)(struct net_device *dev,
         struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
 int   (*set_config)(struct net_device *dev,
           struct ifmap *map);
#define HAVE_HEADER_CACHE
 int   (*hard_header_cache)(struct neighbour *neigh,
           struct hh_cache *hh);
 void   (*header_cache_update)(struct hh_cache *hh,
             struct net_device *dev,
             unsigned char *  haddr);
#define HAVE_CHANGE_MTU
 int   (*change_mtu)(struct net_device *dev, int new_mtu);

#define HAVE_TX_TIMEOUT
 void   (*tx_timeout) (struct net_device *dev);

 int   (*hard_header_parse)(struct sk_buff *skb,
           unsigned char *haddr);
 int   (*neigh_setup)(struct net_device *dev, struct neigh_parms *);
 int   (*accept_fastpath)(struct net_device *, struct dst_entry*);

 /* open/release and usage marking */
 struct module *owner;

 /* bridge stuff */
 struct net_bridge_port *br_port;

#ifdef CONFIG_NET_FASTROUTE
#define NETDEV_FASTROUTE_HMASK 0xF
 /* Semi-private data. Keep it at the end of device struct. */
 rwlock_t  fastpath_lock;
 struct dst_entry *fastpath[NETDEV_FASTROUTE_HMASK+1];
#endif
#ifdef CONFIG_NET_DIVERT
 /* this will get initialized at each interface type init routine */
 struct divert_blk *divert;
#endif /* CONFIG_NET_DIVERT */
};

struct hci_dev {
 struct list_head list;
 spinlock_t lock;
 atomic_t  refcnt;

 char  name[8];
 unsigned long flags;
 __u16  id;
 __u8   type;
 bdaddr_t bdaddr;
 __u8  features[8];

 __u16  pkt_type;
 __u16  link_policy;
 __u16  link_mode;

 unsigned long quirks;

 atomic_t  cmd_cnt;
 unsigned int  acl_cnt;
 unsigned int  sco_cnt;

 unsigned int acl_mtu;
 unsigned int  sco_mtu;
 unsigned int acl_pkts;
 unsigned int sco_pkts;

 unsigned long   cmd_last_tx;
 unsigned long   acl_last_tx;
 unsigned long   sco_last_tx;
 
 struct tasklet_struct  cmd_task;
 struct tasklet_struct rx_task;
 struct tasklet_struct  tx_task;

 struct sk_buff_head rx_q;
 struct sk_buff_head  raw_q;
 struct sk_buff_head  cmd_q;

 struct sk_buff      *sent_cmd;

 struct semaphore req_lock;
 wait_queue_head_t req_wait_q;
 __u32   req_status;
 __u32   req_result;

 struct inquiry_cache  inq_cache;
 struct conn_hash  conn_hash;

 struct hci_dev_stats  stat;

 void   *driver_data;
 void   *core_data;

 atomic_t   promisc;

 int (*open)(struct hci_dev *hdev);
 int (*close)(struct hci_dev *hdev);
 int (*flush)(struct hci_dev *hdev);
 int (*send)(struct sk_buff *skb);
 void (*destruct)(struct hci_dev *hdev);
 int (*ioctl)(struct hci_dev *hdev, unsigned int cmd, unsigned long arg);
};

static inline void bcsp_unslip_one_byte(struct bcsp_struct *bcsp, unsigned char byte)
{//此函数,是bcsp的slip协议层的解包程序,slip工作在uart和完整性协议之间;
//负责加入slip协议,或作相反的动作;
//其中rx_esc_state的含义:=BCSP_ESCSTATE_ESC表示这是特殊字符的处理
//   =BCSP_ESCSTATE_NOESC正常字符的处理
//在slip协议中,什么是特殊字符?
/*A byte of value 0xc0 is transmitted. (This denotes the start of the packet.)
for each byte in the packet, reading from its start:
if the byte’s value is 0xc0:
Two bytes are transmitted: values {0xdb, 0xdc}
else if the byte’s value is 0xdb:
Two bytes are transmitted: values {0xdb, 0xdd}
else :
The original byte is transmitted.
A byte of value 0xc0 is transmitted. (This denotes the end of the packet.)*/
//当要发送的数据包中有0xc0是,我们处理为0xdb,0xdc;
//说明0xdb是特殊字符的起始;
 const u8 c0 = 0xc0, db = 0xdb;

 switch (bcsp->rx_esc_state) {
 case BCSP_ESCSTATE_NOESC:
  switch (byte) {
  case 0xdb:
   bcsp->rx_esc_state = BCSP_ESCSTATE_ESC;
   break;
  default:
   memcpy(skb_put(bcsp->rx_skb, 1), &byte, 1);
   if ((bcsp->rx_skb-> data[0] & 0×40) != 0 &&
     bcsp->rx_state != BCSP_W4_CRC)
   //判断bcsp包的包头的CRC present标志,是否置位
    bcsp_crc_update(&bcsp->message_crc, byte);
   bcsp->rx_count–;
  }
  break;

 case BCSP_ESCSTATE_ESC:
  switch (byte) {
  case 0xdc:
   memcpy(skb_put(bcsp->rx_skb, 1), &c0, 1);
   if ((bcsp->rx_skb-> data[0] & 0×40) != 0 &&
     bcsp->rx_state != BCSP_W4_CRC)
    bcsp_crc_update(&bcsp-> message_crc, 0xc0);
   bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC;
   bcsp->rx_count–;
   break;

  case 0xdd:
   memcpy(skb_put(bcsp->rx_skb, 1), &db, 1);
   if ((bcsp->rx_skb-> data[0] & 0×40) != 0 &&
     bcsp->rx_state != BCSP_W4_CRC)
    bcsp_crc_update(&bcsp-> message_crc, 0xdb);
   bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC;
   bcsp->rx_count–;
   break;

  default:
   BT_ERR (“Invalid byte %02x after esc byte”, byte);
   kfree_skb(bcsp->rx_skb);
   bcsp->rx_skb = NULL;
   bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
   bcsp->rx_count = 0;
  }
 }
}

hci_uart和bcsp的调用关系?
bluez实现时:hci_uart.h定义串口的基本属性,与协议无关
不同的串口协议的实现使用不同的头文件,以及实现的c文件。
hci_h4.h,hci_h4.c; uart协议,h4表示蓝牙协议文件的H4部分
hci_bcsp.h,hci_bcsp.c; bcsp协议,
应该阅读一下linux下串口字符设备的实现源代码;