1, 调试前肩后肩的驱动,那个文件是那个设备的驱动?,lcd刷新频率。
2,MMC sdiosd驱动框架看下;大概了解记忆sdio协议。复读一下wifi驱动的框架,
3,i2c驱动框架。
4,Makefile基本常识基本语句
1,解决过什么问题:收货什么经验,自己的review的总结:
1,解决当wifi没有连接到路由器上时,此时通过ioctl关闭wifi接口,导致死锁的问题:
解决没有连接wifi时,关闭wifi功能,导致死锁的问题:
原因如下:管理wifi的连接用的软件wpa_supplicant,当没有连接上wifi时,wpa_supplicant软件会一直执行搜索热点的代码,这部分代码中会获取了一个锁,而且不释放,直到搜索到热点。
但是此时在应用程序用关闭接口wlan0是通过ioctl的方式。这个ioctl在内核中执行的时候,会尝试获取同一个锁,但是这个锁一直被wpa_supplicant程序的scan部分的代码占用而不释放,导致应用程序死锁。
解决方法:关闭wlan接口不用ioctl和ifconfig的方式,而是用wpa_supplicant软件的命令关闭接口。因为用wpa_supplicant关闭接口时,wpa_supplicant自动回停止搜索热点的任务,释放这个锁,然后再关闭这个接口。
(连接wifi用的软件:system("wpa_supplicant-Dnl80211 -iwlan0 -c/etc/wpa_supplicant.conf -B &");)
死锁的文件:/root/802/trunk/wl18xx/workplace/wl18xx/net/wireless/scan.c
函数为:voidcfg80211_sched_scan_stopped(structwiphy *wiphy)
//rtnl_lock();
//rtnl_unlock();
改正是在应用程序中,关闭wifi不用ifconfigwlan0 down,而是通过wpa_supplicant进程关闭wifi.
2,解决不能识别wifi模块的问题。装上wifi模块后,系统中没有wlan的接口。
因为这个wifi模块时sdio协议的模块,所以我先查看了一下这个sdio设备是不是probe成功了。发现系统中的sd卡probe成功了,但是wifi模块的sdio设备没有probe成功。所以我用示波器量了一下mmc管脚的 CMD 管脚,发现mpu的probe命令确实发出来了,所以我猜测应该wifi模块硬件没有正常工作。但是量了一下wifi模块的晶振和周围的电阻电容都没啥问题。就让硬件工程师重新焊接了这个BGA的wifi芯片。重焊接后还是不行,后来重新购买了别的厂家的wifi模块,就正常工作了。
3, SD 卡在uboot中能正常读写,但是在kernel中不能正常读写。
发现是是mmc管脚中的 DAT_2 管脚没有焊好。
能在uboot中正常工作,是因为在uboot中,读写sd卡的模式没有用到中断,也没有 DAT_2 进行数据传输,只用到了 DAT_0 。所以能正常通信。但是在内核中,为了加快读写速度,用到了中断,而且读写模式中 DAT_0, DAT_1, DAT_2, DAT_3 都会进行数据传输。所以在内核中读写会出问题。
4,
1, u-boot.bin大小:208K
uImage大小:2.2M
2, MPU: Cortex-A8 ARMv7 architecture
DSP: C64x
3,缩短启动时间所做工作:
1,bootDelay设置为1second :#define CONFIG_BOOTDELAY 1
2,设置uboot中的silent_console : #defineCONFIG_SILENT_CONSOLE 1
3,内核启动参数加上启动参数: kernel启动参数添加loglevel=0
kernel启动参数添加loglevel=0:
"nandargs=setenvbootargsconsole=${console} root=${nandroot} vram=32M omapfb.vram=0:32M mem=${mem}mpurate=${mpurate} loglevel=0 \0"
4,所有与视频显示无关的驱动都编译为模块,后续再modprobe加载,减少内核大小,缩短内核加载时间。
4, platform_device驱动框架:
1,当调用函数platform_driver_register()注册设备驱动时,会把driver结构体里的name和所有注册的platform_device的id_table或者name(id_table不存在就用name)做匹配,如果匹配成功,就调用dirver里的probe函数,驱动就注册成功。如果没有匹配成功,那么driver注册失败。
5,在中断处理函数中,经常把具体的操作函数放到workqueue中,然后调用schedule_work(mmc_carddetect_work)函数,让内核线程去执行这个任务,而不是直接在中断函数中去执行这些操作。
6,字符设备和块设备的区别:
1,字符设备:提供连续的数据流,应用程序可以连续读取,通常不支持随即读取。调制解调器是字符设备。
2,块设备:应用程序可以随机访问设备数据,程序可以自行确定读取数据的位置。硬盘是典型的块设备。
7,多个USB设备用VID PID区分;多个SDIO设备用CID (cardidetifier) 区分。
8, ti mmc驱动框架:
1,初始化一个mmc_host的设备和驱动,并在驱动模块init的函数中调用mmc_host驱动的probe函数,在probe函数完成mmc_host驱动的初始化和注册,在probe的最后,执行scan函数,搜索在该mmc总线上是否存在mmc设备。搜索过程是,先发送sdio探测命令如果有回复,则断定是sdio设备,则这是就在系统中创建sdio设备,并初始化;然后发送sd探测命令,如果恢复证明是sd设备,并在系统中创建一个sd设备;然后发送mmc探测命令,如果有回复就断定mmc线上有mmc设备存在,并在系统中创建mmc设备。mmc设备是可以热插拔的,所以当有mmc设备插入时,此时触发管脚中断,中断会执行探测mmc设备的任务,并创建对应的设备。
9,通过c语言的“回调函数”实现C++的多态、虚函数的功能了。还有就是一个用i2c通信的电源管理芯片,有自己对应的驱动程序。但是这个驱动中肯定会创建一个i2c_client的设备,然后通过设这个i2c设备和硬件通信。这就像是这个电源的驱动程序继承了i2c的驱。
10,系统用的 UBI_FS
11, lcd刷新帧率为 60 f/s
#define FB_HFP 200 //8 /* front porch */
#define FB_HSW 0 //3 /* hsync width */
#define FB_HBP 46 //13 /* back porch */
#define FB_VFP 10 //5 /* front porch */
#define FB_VSW 12 //1 /* vsync width */
#define FB_VBP 23 //7 /* back porch */
#define FB_HRES 800 /* horizon pixel x resolition */
#define FB_VRES 480 /* line cnt y resolution */
#define FB_VFRAME_FREQ 60 /* frame rate freq */
#define FB_PIXEL_CLOCK (FB_VFRAME_FREQ * (FB_HFP + FB_HSW +FB_HBP + FB_HRES) * (FB_VFP + FB_VSW + FB_VBP + FB_VRES))
12, 摄像头用的media框架。其中包括很多entity实体,entity通过media link来设置数据的流向。entity包括ccdc,resizer,preview,adv7280等等。
视频采集过程:adv7280把cvbs信号转为bt656,ccdc采集bt656数据流,并保存为yuv数据。yuv数据传给isp模块,isp模块通过video2 layer处理,最终和grahpic layer根据color key叠加,最终输出为rgb888的并行数据,通过lcd的时序传给oled设备。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1, GNU C 增加了关键字typeof,gcc编译器能处理这个关键字。这个关键字能根据变量推导出表达式的类型(如定义int型指针变量:typeof(&var) pvar = &var; ),这个typeof关键字实现了C++泛型编程中的模板的功能。
如:#define max(x, y) ({ \
typeof(x)_tmp_x = x; \
typeof(y)_tmp_y = y; \
_tmp_x>_tmp_y ? _tmp_x : _tmp_y})
这个typeof在内核的list中有重要应用。通过这个typeof实现了通过list_node找到对应的数据的功能。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
宏定义:__init,用于告诉编译器相关函数或变量仅适用于初始化。编译器将标__init的所有代码存放在特殊的内存段中,初始化结束后释放这段内存。
如:
static void __initinit_mount_tree(void)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////字符设备相关:///////////////////////////////////////////// /////////////
文件char_dev.c
管理所有字符设备cdev的结构体:kobj_map,在文件中char_dev.c中。内核中所有的字符设备cdev都在这个哈希链表中。
static structkobj_map *cdev_map;
//调用函数cdev_add(structcdev*p, dev_tdev, unsigned count)把字符设备cdev添加到哈希链表cdev_map后,就能在内核中操作该设备了。
structkobj_map {
struct probe {
struct probe*next; //主设备号相同的设备的链表
dev_tdev;
unsigned longrange;
struct module*owner;
kobj_probe_t*get;
int(*lock)(dev_t, void *);
void *data;
} *probes[255]; //主设备号作为索引
structmutex *lock;
};
/*******************************************************************************************************************************
probe[255] => probe[0] probe[1] probe[2] probe[3] probe[4] probe[5] probe[6] ... probe[254]
lock | |
|next |next .
\/ \/
probe space probe space .
| |
|next |next .
\/ \/
probe space probe space .
. .
. .
. .
*******************************************************************************************************************************/
该文件对外提供的接口:
//设备cdev调用此函数添加到哈希链表cdev_map后,就能在内核中操作该设备了
intcdev_add(structcdev *p, dev_tdev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map,dev, count, NULL, exact_match, exact_lock, p);//把设备添加到了哈希链表cdev_map中
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
structcdev{
structkobjectkobj; //包含一个kobject结构体,相当于继承了基类kobject
struct module*owner;
conststructfile_operations*ops;
structlist_headlist; //链接很多inode的链表头,删除该cdev时,要先释放该链表上所有的inode空间
dev_tdev;
unsigned int count;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////块设备相关:///////////////////////////////////////////// //
块设备的注册和删除不在block_dev.c文件中,而在genhd.c文件中。设备的注册和删除与字符设备相似,也是用一个内部的哈希链表static structkobj_map *bdev_map;来维护。
该文件对外提供的接口:
1,添加块设备的接口:voidadd_disk(structgendisk *disk)
/**
*add_disk - add partitioning information to kernel list
*@disk: per-device partitioning information
*
*This function registers the partitioning information in @disk
*with the kernel.
*
*FIXME: error handling
*/
3,extern structkobject*block_depr;主要用于sysfs文件系统
看一个系统的模块,先看这个模块对外(其他模块)的接口(extern函数和extern的全局变量),然后看这个模块内定义的所有数据结构struct,就基本上能抓住这个模块的功能和框架。
系统调用syscall:
应用程序通过调用库函数(libc),在库函数里通过一个软中断指令swi(int 0x80),让系统转入内核态,然后通过传入参数,判断是哪个系统调用,找到对应的处理例程。处理完成后,返回到应用层。
就是通过让cpu执行一条软中断(swi)指令,让cpu从用户态(usr)模式陷入内核态(svc)模式。这样cpu就能访问所有资源。
内核同步与并发:
主要有中断屏蔽、原子操作、自旋锁、信号量,用的最多的是自旋锁和信号量。
阻塞读取与非阻塞读取:
阻塞读取时,当执行驱动程序的write read函数时,如果设备没有数据,write()和read()函数内部会把当前进程挂起,__set_current_state(TASK_INTERRUPTIBLE);,并执行调度程序schedule()。当有数据时,再唤醒当前进程。
中断处理:
在中断处理函数中,中底半部机制包括:tasklet、工作队列和软中断。(面试题)
1,在需要调度tasklet的时候引用一个tasklet_schedule()函数就能是系统在适当的时候进行调度运行。
Irqreturn_txxx_interrupt(intirq, void *dev_id)
{
….
Tasklet_schedule(&xxx_tasklet);
….
}
2,工作队列的使用方法和tasklet非常相似:
Irqreturn_txxx_interrupt(intirq, void*dev_id, structpt_regs *regs)
{
Schedule_work(&xxx_wq);
Return IRQ_HANDLED;
}
总线、设备与驱动:
在linux2.6的设备驱动模型中,关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配总有总线完成。
Platform
一个现实的linux设备和驱动通常都需要挂接在一种总线,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,soc系统中集成的独立的外设控制器、挂接在soc内存空间的外设等却不依附于此类总线。基于这一背景,linux发明了一种虚拟总线,称为platform总线,相应的设备为paltform_device,而驱动称为platform_driver。
驱动的写法:
首先判断这个设备是否可以随机读写,如果可以随机读写就是块设备,不可以就是字符设备。因为linux的设备驱动模型是描述bus、device、driver。写驱动前,先判断这个设备是挂在什么总线(bus)上。如果是i2c的设备,那么可以确定挂在i2c 总线(bus)上,那么用系统提供的i2c接口函数,创建一个i2c的设备和i2c的驱动,然后注册到系统中就行。如果是设备是一个soc芯片里的外设(外围设备),那么可以确定这个总线是platform总线(虚拟总线),首先要创建一个platform设备,然后创建对应的platform驱动,然后加到系统中。
Bus device driver驱动模型的好处,将数据和操作分开,降低程序耦合,低耦合,易扩展。移植程序的时候,只需要更改device的数据就行。Device内是设备的数据,driver是设备的操作(函数)。
设备对字符设备cdev和块设备block_dev这两种设备的继承,实现了程序的复用。自己的设备只要继承这两个基类,就能在内核的驱动模型中正常工作。
在linux驱动中,包含有i2c、spi、mmc、power、rtc、input、platform等驱动接口,这些接口都是和具体的硬件平台无关的,三星的芯片和ti的soc芯片上都会有i2c的外设,都会有input型的设备,在这些平台上创建i2c设备和input设备只需要用linux提供的对应的接口就行,实现了代码的复用。这些驱动代码向下提供操作具体平台硬件(寄存器操作/填充相关数据,比如配置mmc host寄存器)的接口,向上提供具体设备的接口(比如自定义的旋钮开关的input设备)。
Linux的bus、device、driver模型能把硬件平台相关的host和具体的外设(比如i2c的eeprom,i2c的rtc;mmc的tf卡和mmc的wifi模块)通过bus来隔开,求耦合。
//自定义bus(总线)驱动的开发流程:
1, 首先通过/drivers/base/bus.c文件提供的接口bus_register(structbus_type *bus),注册自己的总线类型(bus_type)。
这个接口注册这个bus_type对象后,会把该bus_type添加到一个kset中:bus_kset。这个bus_kset串联了所有bus_type对象。这个bus_kset在sysfs中对应的目录是:/sys/bus。
2,然后通过文件/drivers/base/bus.c提供的接口bus_add_device(structdevice *dev)把device添加到内核(添加到device所在的bus上的klist_devices这个klist链表上)。
这个接口把device添加到对应的(在device内部保存它所在的bus)bus上的klist_devices链表中。只是添加,不会probe对应的驱动driver。
(但是文件/drivers/base/bus.c还提供了bus_probe_device(structdevice *dev)接口,供上层文件/drivers/base/core.c添加设备device_add()时:先调用bus.c文件的bus_add_device()添加设备,然后调用bus文件的bus_probe_device()探测设备。所以,其实内核添加设备时候,也会自动探测初始化设备devcie。)
3,然后通过/drivers/base/bus.c文件提供的接口bus_add_driver(structdevice_driver *drv)把device_driver添加到内核(添加到device_driver所在的bus上的klist_drivers这个klist链表上)。
这个接口把device_driver添加到所在bus的klist_drivers这个链表中。添加完后,和该bus上所有的device进行match匹配(这个match方法是bus的方法)。【匹配过程:如果比较dev_ID table或者比较name字符串相同,则继续调用bus上的probe方法,如果bus上的probe方法不存在,则调用devcie_driver上probe方法初始化device。】
注:一般情况下,设备和驱动都要在struct device和structdevice_driver的基础上添加自己的内容,继承它们。都要把struct device和structdevice_driver嵌套在自己的设备和驱动中。比如i2c bus的设备和驱动:struct i2c_client和struct i2c_driver,分别嵌套了struct device和structdevice_driver结构体。
所以如果要让设备和驱动互相能找到要符合的条件:
1,首先是两者挂在的bus相同,即device的structbus_type *bus成员和devcie_driver的structbus_type*bus成员。
2,然后两者的id_table或name相同,这样bus在match的时候,当符合“两者的id_table或name相同”这个条件后,才会继续调用bus或device_driver的probe初始化设备device。
TiDM3730 SOC芯片的驱动中,除了一般通用的i2c、platform(虚拟)、mmc、usb等总线外,还添加了两个虚拟总线:omapdss和media两条虚拟总线。其中omapdss是实现了ti平台的显示模块的驱动,media是实现了多个视频相关的entity之间的互联的功能。(这部分需要继续看!!!)
//字符/块设备是驱动模型driver-model和文件系统的交界概念。
视频信号编解码:
入口:通过解码芯片把cvbs信号解码成数字信号ITU-R BT.656,
出口1:通过编码芯片把rgb888编成模拟信号。
出口2:通过oled屏显示rgb888
ADV7280是Decoder芯片
Adv7280把模拟信号解码成数字信号
CH7025/CH7026 TV/VGA Encoder芯片
Ch7026是encoder芯片:把数字信号编码成模拟信号
Driver-model中的bus的match函数:
Platform总线和i2c总线和spi总线的匹配函数都是比较(device_driver中的id_table中的name字符串)和devcie中name字符串项。
Sdio总线的match函数:比较: class、vendor_id、device_id
structsdio_device_id {
__u8 class; /*Standard interface or SDIO_ANY_ID */
__u16 vendor; /*Vendor or SDIO_ANY_ID */
__u16 device; /*Device ID or SDIO_ANY_ID */
kernel_ulong_tdriver_data /* Data private to the driver */
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
内核中每一个kobject都对应sysfs文件系统中的一个目录。kobject的每个属性attribute都对应着该目录下的一个文件,这些attribute(文件)保存的内容一般都是ascii字符串,用户可读的。(有时候也是二进制,比如上传设备固件)
什么时候用spin_lock()spin_lock_irq()spin_lock_irqsave():
1, 如果是内核中不同进程共享资源,则用spin_lock()就行。
2, 如果进程和中断处理函数中都用到了共享资源,要用spin_lock_irq()。因为当进程A获得了spin_lock后,此时产生中断,该进程A被设置为TASK_INTERRUPT不会被执行,所以不会释放锁,中断程序会一直尝试获取锁spin_lock而忙等busy_wait,所以产生了死锁。
3, 如果不能确定获取锁之前的中断使能状态,就要用spin_lock_irqsave(),获取锁之前,先保存当前中断使能状态,当使用完锁后,再恢复原来的中断使能状态。(因为spin_unlock_irq()执行后,默认会使能系统中断)
日志:
如果在终端上看不到printk输出,可以通过查看 /var/log/messages文件,或者直接运行dmesg命令查看,或者查看 /proc/kmsg。
/var/log/messages : 几乎所有的开机系统发生的错误都会在此记录。
dmesg命令:kernel会将开机信息存储在ring buffer中,dmesg用来显示内核缓冲区(kernel ring buffer)内容,内核将各种信息存放在这里。内核将与硬件和模块初始化相关的信息填到这个缓冲区中。
printk打印的时间单位是秒,“.”之前的是s, ”.”之后的是us
如:
[ 27.576202] usb usb1:Manufacturer: Linux 2.6.37 ehci_hcd
27秒576202微妙的时候
我做的内容:
在oled_panel驱动中,给i2c_client包含的device内的kobject (i2c bus 上的device) 添加了亮度、对比度等属性attribute。这样在用户空间可以读写oled_panel的属性。(看看lcd的驱动中是不是包含这个i2c配置寄存器的驱动代码)
1,在gpio_key旋钮开关的驱动里,在timer定时器对应的处理函数中,当访问4个gpio对应的4bit的数值code时,要加上一个自旋锁spin_lock_irq()避免并发存取的问题(或者使用原子操作)。因为和管脚触发的中断程序要写入往这个code变量里写入数据。
Wifi相关:
1,上电后系统启动,通过系统模块接口subsys_initcall(mmc_init);在这个模块初始化函数中,注册了两个总线类型bus_type,分别是mmc总线和sdio总线。Wifi模块用的是sdio总线。这样,driver-model的bus就注册到系统中了。
2,系统加载mmc_host这个外设的驱动的模块时,在module_init(omap_hsmmc_init)这个模块初始化函数中,初始化了mmc_host这个外设的寄存器,申请了一个管脚中断,并注册中断处理函数,这个管脚中断判断卡的插入和拔出。 在这个初始化函数的最后,(mmc_rescan函数)搜索mmc总线上是否存在设备,host先发命令探测是否存在sdio卡,再探测是否存在sd卡,最后探测mmc卡。
当host收到sdio卡的回复response后,就读取sdio卡的vendor id(供应商id)和device id(设备id),然后创建sdio_func设备并注册到sdio bus上。
3, wifi模块的驱动device_driver是注册到sdio总线上的驱动。sdio总线通过match匹配sdio_func设备的vendor id和device id将wifi设备和驱动进行绑定。
系统上电后,初始化系统子模块的时候,通过初始化函数sbusystem_init()函数里,注册了mmc_bus和sdio总线。然后系统加载platform设备的mmc_host这个外设的platform总线的设备的驱动。mmc_host模块module初始化过程中,初始化外设的寄存器,申请检测sdio插入拔出的管脚的中断。注册中断处理函数。最后在mmc总线上搜索mmc_rescan总线上的设备。先探测sdio卡,然后是sd卡,然后是mmc卡。如果收到sdio卡的response,说明存在sdio卡,然后读取sdio卡的vendor id供应商id和device id设备id,并且创建sdio_func这个设备对象,并注册到sdio总线上。这个vendor id和deviceid就是sdio总线就行驱动和设备进行match的比较的东西。这个wlt1833模块有两个function,一个是wifi,一个sdio_uart,在本系统中没有用到串口。这是sdio的device(sdio_func)已经注册到sdio 总线上了。然后就是wlcore的驱动模块的加载,当加载这个wlcore驱动时,就是把这个sdio总线上上的驱动注册到这个sdio总线上,通过他们的vendor id 和device id 把wifi驱动和设备进行了绑定。然后就是驱动中probe函数对设备进行初始化。
1, 感觉块设备block_device和字符设备cdev的区别,就是块设备多了一个请求队列request_queue_t,让访问硬件的效率和系统效率变高了。
网络接口的event:
内核中网络接口的驱动模块和网络协议模块是分开的(没有耦合)。网络协议模块对网络接口的访问就是通过net_device对象,它代表着一个网络接口硬件。 当硬件net_device状态有什么变化时,就会通过事件event通知内核的网络模块(事件evetn定义在notifier.h文件中)。
启动过程中的net event相关的log:
########################
=== Loading App...===
Root filesystem already rw,not remounting
logger: mount: mount point /dev/shmdoes not exist
Configuring networkinterfaces... [ 8.248657]<---file:net/ipv4/devinet.c func:inetdev_event line:1041 -->
[ 8.255706] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1061 event_value:0xd -->
[ 8.264221] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1041 -->
[ 8.271209] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1061 event_value:0x1 -->
[ 8.325012] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1041 -->
[ 8.332061] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1061 event_value:0xd -->
[ 8.340637] net eth0: SMSC911x/921xidentified at 0xd60d4000, IRQ: 217
[ 8.347686] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1041 -->
[ 8.354705] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1061 event_value:0x1 -->
done.
Setting up IP spoofingprotection: rp_filter.
INIT: Entering runlevel: 5
#############################File:/etc/init.d/rc ... ##############################################
########################
therunlevel is :
5
the previous runlevel is:
N
########################
########################
Find the rc* directory,andthe run-level is:
5
########################
Starting system message bus:dbus.
#################################File: /etc/init.d/rc.local #####################################
Starting telnet daemon.
Starting syslogd/klogd: done
http://www.bd-corp.cnbd-corp ttyO0
BD 2015.10 bd-corp ttyO0
bd-corp login: [ 10.196105] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1041 -->
[ 10.203155] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1061 event_value:0x4 -->
[ 11.195495] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1041 -->
[ 11.202484] <---file:net/ipv4/devinet.cfunc:inetdev_event line:1061 event_value:0x4 -->
经常提到的rtnl和文件rtnetlink.c文件相关,文件路径是:/net/core/rtnetlink.c
Rtnl可能是 Routing netlink的缩写。
* Routingnetlink socket interface: protocol independent part.
用户空间user space访问字符设备、块设备是通过/dev/ 目录下的设备节点来访问;而网络设备是通过 socket接口来让用户空间访问。
用的是bsd socket:
* INET An implementation of the TCP/IPprotocol suite for the LINUX
* operatingsystem. INET is implemented usingthe BSD Socket
* interfaceas the means of communication with the user level.
ifconfig源码中:
ifconfig源码中,程序先创建了一个socket,然后通过ioctl来对接口进行操作:创建socket的语句: int s =socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0))或者s =socket(AF_LOCAL, SOCK_DGRAM, 0))
执行ifconfig eth1 192.168.2.18 命令时,也是通过ioctl(s,SIOCAIFADDR, param)来设置的接口的ip。
执行ifconfig eth1 up 时,也是通过 ioctl(s, SIOCSIFFLAGS, (caddr_t)&my_ifr)
Ethernet芯片smsc LAN9220驱动相关:
SmscLAN9220的驱动挂platform总线上。因为smscLAN9220芯片(sram-likeinterface)挂在系统总线上。
在这个platform设备的寄存器等初始化完成后,创建一个net_device设备,并(调用register_netdev()函数把net_device)注册到到网络子系统中,这样,系统就能用这个网络接口interface了。
每一个net_device里面都有一个供发送用的队列queue,如果队列不满的时候,网络子系统还能继续往队列里放数据,从而从接口往外发送;如果满了,就不能继续往队列发了。当接口往外发了数据后,queue的数据不满了,就通知网络子系统,这个queue又能收数据了。
SMSC LAN9220里的PHY物理收发模块这个设备是挂在mdio总线上的。PHY是用一个状态机(相关函数phy_state_machine())来管理的。当PHY外部状态(cable 、carrie、speed等)有所改变时,产生phy中断,此时就调用phy_state_machine()来处理状态的改变。
硬件设备的驱动一般都是用中断的方式,除非设备的数据量太大,导致对cpu的中断过于频繁,反而影响了cpu的效率。这是就采用轮询poll的模式。所以网络设备一般都有一个napi的功能接口。(之前之所以说poll的方式效率低,是因为数据量小,如果每次轮询都能获取数据的话,那就不存在效率低的问题)
内核的网络模块(/net/core/…)和网络硬件驱动模块(/driver/net/… ) 是通过net_device这个结构体连接起来的。
FIFO(First In First Out)全称是先进先出的存储器: FIFO只允许两端一个写,一个读,因此FIFO是一种半共享式存储器。在双机系统中,只允许一个CPU往FIFO写数据,另一个CPU从FIFO读数据。FIFO的仲裁控制简单,但其容量不如双口RAM。由于先进先出的特点,特别适合数据缓冲和突发传送数据。某些芯片的内部就集成小容量FIFO,例如,DSP的同步串口就集成两个FIFO,用于接收和发送数据缓冲。FIFO只给外部提供一个读和一个写信号,因此CPU用一个I/O地址便可读或写FIFO,使硬件趋于简单,给编程也带来一些方便,但CPU不能对FIFO内部的存储器进行寻址。
往SMSC LAN9220里写数据的时候,都是写到同一个地址(在芯片挂在系统总线的基地址的基础上加上TX_DATA_FIFO所在的偏移地址0x20)写),这个地址就是芯片SMSC LAN9220内部的硬件FIFO的地址。
Uboot中往LAN9220里写数据的代码为:
while(tmplen--)
pkt_data_push(dev, TX_DATA_FIFO, *data++);
其中pkt_data_push()函数就是语句:*(volatile u32*)(dev->iobase + offset) = val;
从内存中往smsc Lan9220的硬件TX_DATA_FIFO传输的数据,是完整的以太帧(mac frame),包括硬件mac目地地址、硬件mac源地址等mac层信息,(就是说mac目的源地址信息不是mac硬件自动添加上的,那mac内保存的自己的mac地址,可能就只是用来filter收到的帧)
与此类似,cpu从RX_DATA_FIFO读出的数据,也是一个完整的以太帧(mac frame),也是包含硬件mac源地址、目的地址等mac层信息。
看当前在uboot中,smsc Lan9220没有使用中断,都是往FIFO写入一帧数据后,一直查看LAN9220的状态位。
驱动调试工具:
1,查看当前存在的死锁的工具:
1, 要做到事:!!!
解决那个lan920,一开始不插网线启动板子,之后再插入网线,依然不能网络通信的问题。
什么是架构:
片面上讲,我们可以将架构理解为内核所使用的指令集。例如:用于高端的(手机等)Cortex-A8,Cortex-A9等内核用的是ARMv7-A架构,或者说用的是ARMv7-A指令集架构,我们常用到的STM32的Cortex-M3内核用到的是ARMv7-M架构
Cortex是ARM公司设计的内核系列名称,如Cortex-A8、Cortex-A9等,ARMv7是一种内核的架构。
一种内核架构会被厂商用于多种内核中,如ARM Cortex-A5、ARM-Cortex-A7、Cortex-A8、Cortex-A9……都采用ARMv7-A架构。
The ARMCortex-A8 is a 32-bit processor corelicensed by ARM Holdingsimplementing the ARMv7-A architecture.
The 32-bit ARMarchitecture, such as ARMv7-A,is the most widely used architecture in mobile devices.[32]
大小端格式:
Little-Endian:高字节在高位(arm cpu内部处理的指令就是little-endian。Instructions are always treated aslittle-endian)
Big-Endian:高字节在低位(一般通信用大端格式big-endian)
举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
可见,大端模式和字符串的存储模式类似。
内核的文件系统file_system_type有很多种,比如ext2、ext3、jff2、ramfs、rootfs、bdev、chardev等,内核启动的时候,先注册了rootfs文件系统,然后调用该文件系统的rootfs_mount函数,rootfs_mount调用power.c中的mount_nodev(),在这个函数中分配了一个跟file_system_type rootfs类型对应的超级块super_block,然后调用ramfs_fill_super填充了这个超级块的一些基本参数(比如s_magic等等),并用ramfs_get_inode()分配了一个节点inode,然后分配了一个名称为"\" 目录dentry,并把这个dentry和inode对应起来。同时把这个dentry赋值给这个super_block中的s_root,到此这个根目录"\"就挂载完成了。每个文件系统类型file_system_type可以对应着多个超级块super_block对象(比如两个硬盘设备对应两个super_block)。每个超级块中保存着该(外存中)文件系统的相关信息。同时super_block中还保存着很多链表头,比如已经分配所有的inode的链表头s_inodes,已经分配的file的链表头s_files。所以通过这个super_block对象保存着与该文件系统相关的所有信息。而file_system_type是通过自己的成员fs_supers(是一个super_block的链表头)来找到已经分配的所有的super_block。
环境变量
1,系统中环境变量的作用:
Environment variables are aset of dynamic named values that can affect the way running processes willbehave on a computer.
They are part of theenvironment in which a process runs. For example, a running process can querythe value of the TEMP environment variable to discover a suitable location tostore temporary files, or the HOME or USERPROFILE variable to find thedirectory structure owned by the user running the process.
1,PATH环境变量的作用:指定命令的搜索路径
linux 下设置环境变量:
1, 永久设置:修改/etc/profile文件或者修改/etc/profile.d目录下的文件。
如:$vim /etc/profile
$source /etc/profile (或者 $./profile )
2, 临时设置:用export命令,在当前终端下声明环境变量,关闭shell终端失效。
如:export PATH=$PATH:/usr/local/new/bin
常用命令:
查看某个环境变量:
$echo $PATH
设置一个新的环境变量:
$export HELLO=hello
查看所有环境变量:
$env