一 Fasync... 2 二 同步互斥阻塞... 2 三 poll... 3 四 input子系统... 3 五 LCD驱动程序... 7 六 触摸屏驱动程序... 9 七 USB驱动程序... 10 八 块设备驱动程序... 14 九 NAND FLASH驱动程序... 16 十 NOR FLASH驱动程序... 20 十一 网卡驱动程序... 23 十二 I2C驱动程序... 24 十三 RTC驱动程序... 25 十四 声卡驱动程序...27 十五 uevnt... 30 十六 裸板调试 ... 33 十七 驱动调试 ... 33 十八 应用调试 ... 38 十九 自己写bootloader ... 43 二十 移植最新的u-boot ... 43 二十一 最新版本3移植.4.2内核... 49 二十二 tslib编译使用方法... 52 二十三 移植所有驱动到3.4.2内核去... 54 二十四 3.4.2内核下的I2C驱动... 54 二十五 如何看原理图?... 57 二十六 初始接触开发板的基本操作... 58 一、Fasync 驱动程序涉及以下三项工作,使设备支持异步通知机制: 1. 支持F_SETOWN该控制命令可以设置filp->f_owner为对应进程ID。 但这项工作已经由核心完成,不需要处理设备驱动。 2. 支持F_SETFL每当处理命令FASYNC当标志改变时,驱动程序中的标志fasync()函数将得以执行。 应实现驱动fasync()函数。 3. 可获得设备资源时,调用kill_fasync()函数激发相应的信号 应用程序: fcntl(fd, F_SETOWN, getpid()); // 告诉核心,发给谁 Oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, Oflags | FASYNC); // 改变fasync标记最终将调用到驱动程序中faync > fasync_helper:初始化/释放fasync_struct 二、同步互斥阻塞 1. 原子操作 原子操作是指在执行过程中不会被其他代码路径中断的操作。 常用的原子操作函数例: atomic_t v = ATOMIC_INIT(0); ///定义原子变量v并初始化为0 atomic_read(atomic_t *v); ///返回原子变量的值 void atomic_inc(atomic_t *v); ///原子变量增加1 void atomic_dec(atomic_t *v); ///原子变量减少1 int atomic_dec_and_test(atomic_t *v); ////自减操作后测试是否为0,0返回true,否则返回false。 2. 信号量 信号量(semaphore)它是一种常用的保护临界区的方法,只有在获得信号量的过程中才能执行临界区代码。 当获取不到信号量时,进程进入休眠等待状态。 定义信号量 struct semaphore sem; 初始化信号量 void sema_init (struct semaphore *sem, int val); void init_MUTEX(struct semaphore *sem);///初始化为0 static DECLARE_MUTEX(button_lock); //定义互斥锁 获得信号量 void down(struct semaphore * sem); int down_interruptible(struct semaphore * sem); int down_trylock(struct semaphore * sem); 释放信号量 void up(struct semaphore * sem); 3. 阻塞 是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。 被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。 非阻塞操作 进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。 fd = open("...", O_RDWR | O_NONBLOCK); 三、Poll 1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。 2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数 它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的; 它还判断一下设备是否就绪。 3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间 4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。 5.如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。 四、input子系统 drivers/input/input.c: input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops); static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, }; 问:怎么读按键? input_open_file struct input_handler *handler = input_table[iminor(inode) >> 5]; new_fops = fops_get(handler->fops) // =>&evdev_fops file->f_op = new_fops; err = new_fops->open(inode, file); app: read > ... > file->f_op->read input_table数组由谁构造? input_register_handler 注册input_handler: input_register_handler // 放入数组 input_table[handler->minor >> 5] = handler; // 放入链表 list_add_tail(&handler->node, &input_handler_list); // 对于每个input_dev,调用input_attach_handler list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev 注册输入设备: input_register_device // 放入链表 list_add_tail(&dev->node, &input_dev_list); // 对于每一个input_handler,都调用input_attach_handler list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev input_attach_handler id = input_match_device(handler->id_table, dev); error = handler->connect(handler, dev, id); 注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler, 根据input_handler的id_table判断这个input_handler能否支持这个input_dev, 如果能支持,则调用input_handler的connect函数建立"连接" 怎么建立连接? 1. 分配一个input_handle结构体 2. input_handle.dev = input_dev; // 指向左边的input_dev input_handle.handler = input_handler; // 指向右边的input_handler 3. 注册: input_handler->h_list = &input_handle; inpu_dev->h_list = &input_handle; evdev_connect evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle // 设置 evdev->handle.dev = dev; // 指向左边的input_dev evdev->handle.name = evdev->name; evdev->handle.handler = handler; // 指向右边的input_handler evdev->handle.private = evdev; // 注册 error = input_register_handle(&evdev->handle); 怎么读按键? app: read -------------------------- ....... evdev_read // 无数据并且是非阻塞方式打开,则立刻返回 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) return -EAGAIN; // 否则休眠 retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); 谁来唤醒? evdev_event wake_up_interruptible(&evdev->wait); evdev_event被谁调用? 猜:应该是硬件相关的代码,input_dev那层调用的 在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数 gpio_keys_isr // 上报事件 input_event(input, type, button->code, !!state); input_sync(input); input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) struct input_handle *handle; list_for_each_entry(handle, &dev->h_list, d_node) if (handle->open) handle->handler->event(handle, type, code, value); 怎么写符合输入子系统框架的驱动程序? 1. 分配一个input_dev结构体 2. 设置 3. 注册 4. 硬件相关的代码,比如在中断服务程序里上报事件 struct input_dev { void *private; const char *name; const char *phys; const char *uniq; struct input_id id; unsigned long evbit[NBITS(EV_MAX)]; // 表示能产生哪类事件 unsigned long keybit[NBITS(KEY_MAX)]; // 表示能产生哪些按键 unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件, x,y,滚轮 unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,y unsigned long mscbit[NBITS(MSC_MAX)]; unsigned long ledbit[NBITS(LED_MAX)]; unsigned long sndbit[NBITS(SND_MAX)]; unsigned long ffbit[NBITS(FF_MAX)]; unsigned long swbit[NBITS(SW_MAX)]; 测试: 1. hexdump /dev/event1 (open(/dev/event1), read(), ) 秒 微秒 类 code value 0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000 0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000 0000020 0bb2 0000 5815 000e 0001 0026 0000 0000 0000030 0bb2 0000 581f 000e 0000 0000 0000 0000 2. 如果没有启动QT: cat /dev/tty1 按:s2,s3,s4 就可以得到ls 或者: exec 0</dev/tty1 然后可以使用按键来输入 3. 如果已经启动了QT: 可以点开记事本 然后按:s2,s3,s4 五、LCD驱动程序 假设 app: open("/dev/fb0", ...) 主设备号: 29, 次设备号: 0 -------------------------------------------------------------- kernel: fb_open int fbidx = iminor(inode); struct fb_info *info = = registered_fb[0]; app: read() --------------------------------------------------------------- kernel: fb_read int fbidx = iminor(inode); struct fb_info *info = registered_fb[fbidx]; if (info->fbops->fb_read) return info->fbops->fb_read(info, buf, count, ppos); src = (u32 __iomem *) (info->screen_base + p); dst = buffer; *dst++ = fb_readl(src++); copy_to_user(buf, buffer, c) 问1. registered_fb在哪里被设置? 答1. register_framebuffer 怎么写LCD驱动程序? 1. 分配一个fb_info结构体: framebuffer_alloc 2. 设置 3. 注册: register_framebuffer 4. 硬件相关的操作 测试: 1. make menuconfig去掉原来的驱动程序 -> Device Drivers -> Graphics support <M> S3C2410 LCD framebuffer support 2. make uImage make modules 3. 使用新的uImage启动开发板: 4. insmod cfbcopyarea.ko insmod cfbfillrect.ko insmod cfbimgblt.ko insmod lcd.ko echo hello > /dev/tty1 // 可以在LCD上看见hello cat lcd.ko > /dev/fb0 // 花屏 5. 修改 /etc/inittab tty1::askfirst:-/bin/sh 用新内核重启开发板 insmod cfbcopyarea.ko insmod cfbfillrect.ko insmod cfbimgblt.ko insmod lcd.ko insmod buttons.ko 六、触摸屏驱动程序 测试2th~7th: 1. make menuconfig 去掉原来的触摸屏驱动程序 -> Device Drivers -> Input device support -> Generic input layer -> Touchscreens <> S3C2410/S3C2440 touchscreens make uImage 使用新内核启动 2. insmod s3c_ts.ko 按下/松开触摸笔 测试2th~7th: 1. ls /dev/event* 2. insmod s3c_ts.ko 3. ls /dev/event* 4. hexdump /dev/event0 秒 微秒 type code value 0000000 29a4 0000 8625 0008 0003 0000 0172 0000 0000010 29a4 0000 8631 0008 0003 0001 027c 0000 0000020 29a4 0000 8634 0008 0003 0018 0001 0000 0000030 29a4 0000 8638 0008 0001 014a 0001 0000 0000040 29a4 0000 863c 0008 0000 0000 0000 0000 0000050 29a4 0000 c85e 0008 0003 0000 0171 0000 0000060 29a4 0000 c874 0008 0003 0001 027d 0000 0000070 29a4 0000 c87b 0008 0000 0000 0000 0000 0000080 29a4 0000 ed37 0008 0003 0018 0000 0000 0000090 29a4 0000 ed48 0008 0001 014a 0000 0000 00000a0 29a4 0000 ed4a 0008 0000 0000 0000 0000 使用: 七、USB设备驱动程序 现象:把USB设备接到PC 1. 右下角弹出"发现android phone" 2. 跳出一个对话框,提示你安装驱动程序 问1. 既然还没有"驱动程序",为何能知道是"android phone" 答1. windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone" 提示你安装的是"设备驱动程序" USB总线驱动程序负责:识别USB设备, 给USB设备找到对应的驱动程序 问2. USB设备种类非常多,为什么一接入电脑,就能识别出来? 答2. PC和USB设备都得遵守一些规范。 比如:USB设备接入电脑后,PC机会发出"你是什么"? USB设备就必须回答"我是xxx", 并且回答的语言必须是中文 USB总线驱动程序会发出某些命令想获取设备信息(描述符), USB设备必须返回"描述符"给PC 问3. PC机上接有非常多的USB设备,怎么分辨它们? USB接口只有4条线: 5V,GND,D-,D+ 答3. 每一个USB设备接入PC时,USB总线驱动程序都会给它分配一个编号 接在USB总线上的每一个USB设备都有自己的编号(地址) PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址) 问4. USB设备刚接入PC时,还没有编号;那么PC怎么把"分配的编号"告诉它? 答4. 新接入的USB设备的默认编号是0,在未分配新编号前,PC使用0编号和它通信。 问5. 为什么一接入USB设备,PC机就能发现它? 答5. PC的USB口内部,D-和D+接有15K的下拉电阻,未接USB设备时为低电平 USB设备的USB口内部,D-或D+接有1.5K的上拉电阻;它一接入PC,就会把PC USB口的D-或D+拉高,从硬件的角度通知PC有新设备接入 其他概念: 1. USB是主从结构的 所有的USB传输,都是从USB主机这方发起;USB设备没有"主动"通知USB主机的能力。 例子:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动地等得PC机来读。 2. USB的传输类型: a. 控制传输:可靠,时间有保证,比如:USB设备的识别过程 b. 批量传输: 可靠, 时间没有保证, 比如:U盘 c. 中断传输:可靠,实时,比如:USB鼠标 d. 实时传输:不可靠,实时,比如:USB摄像头 3. USB传输的对象:端点(endpoint) 我们说"读U盘"、"写U盘",可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据 除了端点0外,每一个端点只支持一个方向的数据传输 端点0用于控制传输,既能输出也能输入 4. 每一个端点都有传输类型,传输方向 5. 术语里、程序里说的输入(IN)、输出(OUT) "都是" 基于USB主机的立场说的。 比如鼠标的数据是从鼠标传到PC机, 对应的端点称为"输入端点" 6. USB总线驱动程序的作用 a. 识别USB设备 b. 查找并安装对应的设备驱动程序 c. 提供USB读写函数 USB驱动程序框架: app: ------------------------------------------- USB设备驱动程序 // 知道数据含义 内核 -------------------------------------- USB总线驱动程序 // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义) ------------------------------------------- USB主机控制器 UHCI OHCI EHCI 硬件 ----------- USB设备 UHCI: intel, 低速(1.5Mbps)/全速(12Mbps) OHCI: microsoft 低速/全速 EHCI: 高速(480Mbps) USB总线驱动程序的作用 1. 识别USB设备 1.1 分配地址 1.2 并告诉USB设备(set address) 1.3 发出命令获取描述符 描述符的信息可以在include\linux\usb\Ch9.h看到 2. 查找并安装对应的设备驱动程序 3. 提供USB读写函数 把USB设备接到开发板上,看输出信息: usb 1-1: new full speed USB device using s3c2410-ohci and address 2 usb 1-1: configuration #1 chosen from 1 choice scsi0 : SCSI emulation for USB Mass Storage devices scsi 0:0:0:0: Direct-Access HTC Android Phone 0100 PQ: 0 ANSI: 2 sd 0:0:0:0: [sda] Attached SCSI removable disk 拔掉 usb 1-1: USB disconnect, address 2 再接上: usb 1-1: new full speed USB device using s3c2410-ohci and address 3 usb 1-1: configuration #1 chosen from 1 choice scsi1 : SCSI emulation for USB Mass Storage devices scsi 1:0:0:0: Direct-Access HTC Android Phone 0100 PQ: 0 ANSI: 2 sd 1:0:0:0: [sda] Attached SCSI removable disk 在内核目录下搜: grep "USB device using" * -nR drivers/usb/core/hub.c:2186: "%s %s speed %sUSB device using %s and address %d\n", hub_irq kick_khubd hub_thread hub_events hub_port_connect_change udev = usb_alloc_dev(hdev, hdev->bus, port1); dev->dev.bus = &usb_bus_type; choose_address(udev); // 给新设备分配编号(地址) hub_port_init // usb 1-1: new full speed USB device using s3c2410-ohci and address 3 hub_set_address // 把编号(地址)告诉USB设备 usb_get_device_descriptor(udev, 8); // 获取设备描述符 retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); usb_new_device(udev) err = usb_get_configuration(udev); // 把所有的描述符都读出来,并解析 usb_parse_configuration device_add // 把device放入usb_bus_type的dev链表, // 从usb_bus_type的driver链表里取出usb_driver, // 把usb_interface和usb_driver的id_table比较 // 如果能匹配,调用usb_driver的probe <LINUX内核源代码情景分析> 怎么写USB设备驱动程序? 1. 分配/设置usb_driver结构体 .id_table .probe .disconnect 2. 注册 测试1th/2th: 1. make menuconfig去掉原来的USB鼠标驱动 -> Device Drivers -> HID Devices <> USB Human Interface Device (full HID) support 2. make uImage 并使用新的内核启动 3. insmod usbmouse_as_key.ko 4. 在开发板上接入、拔出USB鼠标 测试3th: 1. insmod usbmouse_as_key.ko 2. ls /dev/event* 3. 接上USB鼠标 4. ls /dev/event* 5. 操作鼠标观察数据 测试4th: 1. insmod usbmouse_as_key.ko 2. ls /dev/event* 3. 接上USB鼠标 4. ls /dev/event* 5. cat /dev/tty1 然后按鼠标键 6. hexdump /dev/event0 八、块设备驱动程序 框架: app: open,read,write "1.txt" --------------------------------------------- 文件的读写 文件系统: vfat, ext2, ext3, yaffs2, jffs2 (把文件的读写转换为扇区的读写) -----------------ll_rw_block----------------- 扇区的读写 1. 把"读写"放入队列 2. 调用队列的处理函数(优化/调顺序/合并) 块设备驱动程序 --------------------------------------------- 硬件: 硬盘,flash <LINUX内核源代码情景分析> 分析ll_rw_block for (i = 0; i < nr; i++) { struct buffer_head *bh = bhs[i]; submit_bh(rw, bh); struct bio *bio; // 使用bh来构造bio (block input/output) submit_bio(rw, bio); // 通用的构造请求: 使用bio来构造请求(request) generic_make_request(bio); __generic_make_request(bio); request_queue_t *q = bdev_get_queue(bio->bi_bdev); // 找到队列 // 调用队列的"构造请求函数" ret = q->make_request_fn(q, bio); // 默认的函数是__make_request __make_request // 先尝试合并 elv_merge(q, &req, bio); // 如果合并不成,使用bio构造请求 init_request_from_bio(req, bio); // 把请求放入队列 add_request(q, req); // 执行队列 __generic_unplug_device(q); // 调用队列的"处理函数" q->request_fn(q); 怎么写块设备驱动程序呢? 1. 分配gendisk: alloc_disk 2. 设置 2.1 分配/设置队列: request_queue_t // 它提供读写能力 blk_init_queue 2.2 设置gendisk其他信息 // 它提供属性: 比如容量 3. 注册: add_disk 参考: drivers\block\xd.c drivers\block\z2ram.c 测试3th,4th: 在开发板上: 1. insmod ramblock.ko 2. 格式化: mkdosfs /dev/ramblock 3. 挂接: mount /dev/ramblock /tmp/ 4. 读写文件: cd /tmp, 在里面vi文件 5. cd /; umount /tmp/ 6. cat /dev/ramblock > /mnt/ramblock.bin 7. 在PC上查看ramblock.bin sudo mount -o loop ramblock.bin /mnt 测试5th: 1. insmod ramblock.ko 2. ls /dev/ramblock* 3. fdisk /dev/ramblock 九、NAND FLASH驱动程序 NAND FLASH是一个存储芯片 那么: 这样的操作很合理"读地址A的数据,把数据B写到地址A" 问1. 原理图上NAND FLASH和S3C2440之间只有数据线, 怎么传输地址? 答1.在DATA0~DATA7上既传输数据,又传输地址 当ALE为高电平时传输的是地址, 问2. 从NAND FLASH芯片手册可知,要操作NAND FLASH需要先发出命令 怎么传入命令? 答2.在DATA0~DATA7上既传输数据,又传输地址,也传输命令 当ALE为高电平时传输的是地址, 当CLE为高电平时传输的是命令 当ALE和CLE都为低电平时传输的是数据 问3. 数据线既接到NAND FLASH,也接到NOR FLASH,还接到SDRAM、DM9000等等 那么怎么避免干扰? 答3. 这些设备,要访问之必须"选中", 没有选中的芯片不会工作,相当于没接一样 问4. 假设烧写NAND FLASH,把命令、地址、数据发给它之后, NAND FLASH肯定不可能瞬间完成烧写的, 怎么判断烧写完成? 答4. 通过状态引脚RnB来判断:它为高电平表示就绪,它为低电平表示正忙 问5. 怎么操作NAND FLASH呢? 答5. 根据NAND FLASH的芯片手册,一般的过程是: 发出命令 发出地址 发出数据/读数据 NAND FLASH S3C2440 发命令 选中芯片 CLE设为高电平 NFCMMD=命令值 在DATA0~DATA7上输出命令值 发出一个写脉冲 发地址 选中芯片 NFADDR=地址值 ALE设为高电平 在DATA0~DATA7上输出地址值 发出一个写脉冲 发数据 选中芯片 NFDATA=数据值 ALE,CLE设为低电平 在DATA0~DATA7上输出数据值 发出一个写脉冲 读数据 选中芯片 val=NFDATA 发出读脉冲 读DATA0~DATA7的数据 用UBOOT来体验NAND FLASH的操作: 1. 读ID S3C2440 u-boot 选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 0x4E000004 1 发出命令0x90 NFCMMD=0x90 mw.b 0x4E000008 0x90 发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00 读数据得到0xEC val=NFDATA md.b 0x4E000010 1 读数据得到device code val=NFDATA md.b 0x4E000010 1 0xda 退出读ID的状态 NFCMMD=0xff mw.b 0x4E000008 0xff 2. 读内容: 读0地址的数据 使用UBOOT命令: nand dump 0 Page 00000000 dump: 17 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 S3C2440 u-boot 选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 0x4E000004 1 发出命令0x00 NFCMMD=0x00 mw.b 0x4E000008 0x00 发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00 发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00 发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00 发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00 发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00 发出命令0x30 NFCMMD=0x30 mw.b 0x4E000008 0x30 读数据得到0x17 val=NFDATA md.b 0x4E000010 1 读数据得到0x00 val=NFDATA md.b 0x4E000010 1 读数据得到0x00 val=NFDATA md.b 0x4E000010 1 读数据得到0xea val=NFDATA md.b 0x4E000010 1 退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff NAND FLASH驱动程序层次 看内核启动信息 S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2440-nand s3c2440-nand: Tacls=3, 30ns Twrph0=7 70ns, Twrph1=3 30ns NAND device: Manufacturer ID: 0xec, Chip ID: 0xda (Samsung NAND 256MiB 3,3V 8-bit) Scanning device for bad blocks Bad eraseblock 256 at 0x02000000 Bad eraseblock 257 at 0x02020000 Bad eraseblock 319 at 0x027e0000 Bad eraseblock 606 at 0x04bc0000 Bad eraseblock 608 at 0x04c00000 Creating 4 MTD partitions on "NAND 256MiB 3,3V 8-bit": 0x00000000-0x00040000 : "bootloader" 0x00040000-0x00060000 : "params" 0x00060000-0x00260000 : "kernel" 0x00260000-0x10000000 : "root" 搜"S3C24XX NAND Driver" S3c2410.c (drivers\mtd\nand) s3c2410_nand_inithw s3c2410_nand_init_chip nand_scan // drivers/mtd/nand/nand_base.c 根据nand_chip的底层操作函数识别NAND FLASH,构造mtd_info nand_scan_ident nand_set_defaults if (!chip->select_chip) chip->select_chip = nand_select_chip; // 默认值不适用 if (chip->cmdfunc == NULL) chip->cmdfunc = nand_command; chip->cmd_ctrl(mtd, command, ctrl); if (!chip->read_byte) chip->read_byte = nand_read_byte; readb(chip->IO_ADDR_R); if (chip->waitfunc == NULL) chip->waitfunc = nand_wait; chip->dev_ready nand_get_flash_type chip->select_chip(mtd, 0); chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); *maf_id = chip->read_byte(mtd); dev_id = chip->read_byte(mtd); nand_scan_tail mtd->erase = nand_erase; mtd->read = nand_read; mtd->write = nand_write; s3c2410_nand_add_partition add_mtd_partitions add_mtd_device list_for_each(this, &mtd_notifiers) { // 问. mtd_notifiers在哪设置 // 答. drivers/mtd/mtdchar.c,mtd_blkdev.c调用register_mtd_user struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); not->add(mtd); // mtd_notify_add 和 blktrans_notify_add 先看字符设备的mtd_notify_add class_device_create class_device_create 再看块设备的blktrans_notify_add list_for_each(this, &blktrans_majors) { // 问. blktrans_majors在哪设置 // 答. drivers\mtd\mdblock.c或mtdblock_ro.c register_mtd_blktrans struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list); tr->add_mtd(tr, mtd); mtdblock_add_mtd (drivers\mtd\mdblock.c) add_mtd_blktrans_dev alloc_disk gd->queue = tr->blkcore_priv->rq; // tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock); add_disk 测试4th: 1. make menuconfig去掉内核自带的NAND FLASH驱动 -> Device Drivers -> Memory Technology Device (MTD) support -> NAND Device Support < > NAND Flash support for S3C2410/S3C2440 SoC 2. make uImage 使用新内核启动, 并且使用NFS作为根文件系统 3. insmod s3c_nand.ko 4. 格式化 (参考下面编译工具) flash_eraseall /dev/mtd3 // yaffs 5. 挂接 mount -t yaffs /dev/mtdblock3 /mnt 6. 在/mnt目录下建文件 编译工具: 1. tar xjf mtd-utils-05.07.23.tar.bz2 2. cd mtd-utils-05.07.23/util 修改Makefile: #CROSS=arm-linux- 改为 CROSS=arm-linux- 3. make 4. cp flash_erase flash_eraseall /work/nfs_root/first_fs/bin/ 十、NOR FLASH驱动程序 使用UBOOT体验NOR FLASH的操作(开发板设为NOR启动,进入UBOOT) 先使用OpenJTAG烧写UBOOT到NOR FLASH 1. 读数据 md.b 0 2. 读ID NOR手册上: 往地址555H写AAH 往地址2AAH写55H 往地址555H写90H 读0地址得到厂家ID: C2H 读1地址得到设备ID: 22DAH或225BH 退出读ID状态: 给任意地址写F0H 2440的A1接到NOR的A0,所以2440发出(555h<<1), NOR才能收到555h这个地址 UBOOT怎么操作? 往地址AAAH写AAH mw.w aaa aa 往地址554写55H mw.w 554 55 往地址AAAH写90H mw.w aaa 90 读0地址得到厂家ID: C2H md.w 0 1 读2地址得到设备ID: 22DAH或225BH md.w 2 1 退出读ID状态: mw.w 0 f0 3. NOR有两种规范, jedec, cfi(common flash interface) 读取CFI信息 NOR手册: 进入CFI模式 往55H写入98H 读数据: 读10H得到0051 读11H得到0052 读12H得到0059 读27H得到容量 2440的A1接到NOR的A0,所以2440发出(555h<<1), NOR才能收到555h这个地址 UBOOT怎么操作? 进入CFI模式 往AAH写入98H mw.w aa 98 读数据: 读20H得到0051 md.w 20 1 读22H得到0052 md.w 22 1 读24H得到0059 md.w 24 1 读4EH得到容量 md.w 4e 1 退出CFI模式 mw.w 0 f0 4. 写数据: 在地址0x100000写入0x1234 md.w 100000 1 // 得到ffff mw.w 100000 1234 md.w 100000 1 // 还是ffff NOR手册: 往地址555H写AAH 往地址2AAH写55H 往地址555H写A0H 往地址PA写PD 2440的A1接到NOR的A0,所以2440发出(555h<<1), NOR才能收到555h这个地址 UBOOT怎么操作? 往地址AAAH写AAH mw.w aaa aa 往地址554H写55H mw.w 554 55 往地址AAAH写A0H mw.w aaa a0 往地址0x100000写1234h mw.w 100000 1234 NOR FLASH驱动程序框架 测试1:通过配置内核支持NOR FLASH 1. make menuconfig -> Device Drivers -> Memory Technology Device (MTD) support -> Mapping drivers for chip access <M> CFI Flash device in physical memory map (0x0) Physical start address of flash mapping // 物理基地址 (0x1000000) Physical length of flash mapping // 长度 (2) Bank width in octets (NEW) // 位宽 2. make modules cp drivers/mtd/maps/physmap.ko /work/nfs_root/first_fs 3. 启动开发板 ls /dev/mtd* insmod physmap.ko ls /dev/mtd* cat /proc/mtd 测试2: 使用自己写的驱动程序: 1. ls /dev/mtd* 2. insmod s3c_nor.ko 3. ls /dev/mtd* 4. 格式化: flash_eraseall -j /dev/mtd1 5. mount -t jffs2 /dev/mtdblock1 /mnt 在/mnt目录下操作文件 NOR FLASH识别过程: do_map_probe("cfi_probe", s3c_nor_map); drv = get_mtd_chip_driver(name) ret = drv->probe(map); // cfi_probe.c cfi_probe mtd_do_chip_probe(map, &cfi_chip_probe); cfi = genprobe_ident_chips(map, cp); genprobe_new_chip(map, cp, &cfi) cp->probe_chip(map, 0, NULL, cfi) cfi_probe_chip // 进入CFI模式 cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); // 看是否能读出"QRY" qry_present(map,base,cfi) ..... do_map_probe("jedec_probe", s3c_nor_map); drv = get_mtd_chip_driver(name) ret = drv->probe(map); // jedec_probe jedec_probe mtd_do_chip_probe(map, &jedec_chip_probe); genprobe_ident_chips(map, cp); genprobe_new_chip(map, cp, &cfi) cp->probe_chip(map, 0, NULL, cfi) jedec_probe_chip // 解锁 cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL); // 读ID命令 cfi_send_gen_cmd(0x90, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); // 得到厂家ID,设备ID cfi->mfr = jedec_read_mfr(map, base, cfi); cfi->id = jedec_read_id(map, base, cfi); // 和数组比较 jedec_table 十一、网卡驱动程序 网卡驱动程序框架: app: socket -------------------------------------------------- --------------- --------------- 若干层网络协议--纯软件 --------------- --------------- hard_start_xmit|| /\ \/ || netif_rx sk_buff --------------- 硬件相关的驱动程序(要提供hard_start_xmit, 有数据时要用netif_rx上报) -------------------------------------------------- 硬件 怎么写网卡驱动程序? 1. 分配一个net_device结构体 2. 设置: 2.1 发包函数: hard_start_xmit 2.2 收到数据时(在中断处理函数里)用netif_rx上报数据 2.3 其他设置 3. 注册: register_netdevice 测试1th/2th: 1. insmod virt_net.ko 2. ifconfig vnet0 3.3.3.3 ifconfig // 查看 3. ping 3.3.3.3 // 成功 ping 3.3.3.4 // 死机 测试DM9000C驱动程序: 1. 把dm9dev9000c.c放到内核的drivers/net目录下 2. 修改drivers/net/Makefile 把 obj-$(CONFIG_DM9000) += dm9000.o 改为 obj-$(CONFIG_DM9000) += dm9dev9000c.o 3. make uImage 使用新内核启动 4. 使用NFS启动 或 ifconfig eth0 192.168.1.17 ping 192.168.1.1 十二、I2C驱动程序 i2c_add_driver i2c_register_driver driver->driver.bus = &i2c_bus_type; driver_register(&driver->driver); list_for_each_entry(adapter, &adapters, list) { driver->attach_adapter(adapter); i2c_probe(adapter, &addr_data, eeprom_detect); i2c_probe_address // 发出S信号,发出设备地址(来自addr_data) i2c_smbus_xfer i2c_smbus_xfer_emulated i2c_transfer adap->algo->master_xfer // s3c24xx_i2c_xfer 怎么写I2C设备驱动程序? 1. 分配一个i2c_driver结构体 2. 设置 attach_adapter // 它直接调用 i2c_probe(adap, 设备地址, 发现这个设备后要调用的函数); detach_client // 卸载这个驱动后,如果之前发现能够支持的设备,则调用它来清理 3. 注册:i2c_add_driver 测试1th: 1. insmod at24cxx.ko 观察输出信息 2. 修改normal_addr里的0x50为0x60 编译加载,观察输出信息 十三、RTC驱动程序 drivers\rtc\rtc-s3c.c s3c_rtc_init platform_driver_register s3c_rtc_probe rtc_device_register("s3c", &pdev->dev, &s3c_rtcops, THIS_MODULE) rtc_dev_prepare cdev_init(&rtc->char_dev, &rtc_dev_fops); rtc_dev_add_device cdev_add app: open("/dev/rtc0"); ------------------------------------------- kernel: sys_open rtc_dev_fops.open rtc_dev_open // 根据次设备号找到以前用"rtc_device_register"注册的rtc_device struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev); const struct rtc_class_ops *ops = rtc->ops; err = ops->open ? ops->open(rtc->dev.parent) : 0; s3c_rtc_open app: ioctl(fd, RTC_RD_TIME,...) ------------------------------------------- kernel: sys_ioctl rtc_dev_fops.ioctl rtc_dev_ioctl struct rtc_device *rtc = file->private_data; rtc_read_time(rtc, &tm); err = rtc->ops->read_time(rtc->dev.parent, tm); s3c_rtc_gettime 测试RTC: 1. 修改arch\arm\plat-s3c24xx\common-smdk.c static struct platform_device __initdata *smdk_devs[] = { &s3c_device_nand, &smdk_led4, &smdk_led5, &smdk_led6, &smdk_led7, 改为(在数组smdk_devs里加上s3c_device_rtc): static struct platform_device __initdata *smdk_devs[] = { &s3c_device_nand, &smdk_led4, &smdk_led5, &smdk_led6, &smdk_led7, &s3c_device_rtc, 2. make uImage, 使用新内核启动 3. ls /dev/rtc* -l date /* 显示系统时间 */ date 123015402011.30 /* 设置系统时间 date [MMDDhhmm[[CC]YY][.ss]] */ hwclock -w /* 把系统时间写入RTC */ 短电,重启,执行date 十四、声卡驱动程序 sound\soc\s3c24xx\s3c2410-uda1341.c s3c2410_uda1341_init driver_register(&s3c2410iis_driver); ..... s3c2410iis_probe /* 使能时钟 */ /* 配置GPIO */ 标签: 1341三极管