Makefile cleaup
[thunix.git] / doc / Chapter6-Driver
blob8598955029a4c663f577e3c9e1533c5801a89f0c
1 Chapter 6 基本驱动的实现
3 在这章节中, 将先后介绍键盘--典型的输入系统, 软盘--存储设备及典型的输入输出系统, 时钟三个驱动的实现。
5 6.1 键盘驱动的实现
6 键盘驱动的实现相对比较简单, 其过程也很清晰: 捕捉键盘控制器的数据, 做出处理, 输出结果到屏幕上。 因此对于键盘驱动的实现, 我就主要以这三个步骤来写。
8 6.1.1 捕捉键盘的数据
9 这个小标题把事情说的复杂了些, 但其实问题很简单: 假如我在键盘上按一个字母 a , 键盘控制器就会把其相应的扫描码[Scancode set](这是一种键盘控制器内部识别的编码系统, 有点类似于ASCII, 给每个字符编上相对应的码, 之后便可用这个码来对应于其字符, 但不是ASCII, 具体的字母所对应的编码可查询相关的表)到特定的端口(0x60), 所以, 我们只要读取该端口,就能得知我们刚按下键的扫描码, 代码如下:
10         scancode = inb(0x60);
12 6.1.2 处理
13 就如上面所示, 要得到一个键所对应的扫描码一条语句就可以解决。 但在对其处理之前, 有必要讲下什么是扫描码, 以及按键与松键时所对应的扫描码又是如何。 扫描码分两种, make以及break。 当一个键按下时, 就会产生一个make码, 当一个键松开时便会得到一个break码。 通常来说, 一个扫描码用前七位来表示, 第八位刚表示其状态, 1表示break, 0表示make。 所以, 一个简单的处理程序是这样的:
15         if (scancode & 0x80){
16                 / It's a break code; it's useless for us, so just skip it. */
17                 return;
18         }else {
19                 /* It's make code */
20                 printf("%c", scancode_table[scancode]);
21         }
23 其中, scancode_table是一张表(或者说是个字符数组), index就是扫描码的值, 而scancode_table[index]就是其所对应的字符了(注, 此时是ASCII)。 如一个简单的scancode_table可以是这样:
26 #defien NO 0x0
28 static unsigned char scancode_table[] =
30         NO,   0x1B, '1',  '2',  '3',  '4',  '5',  '6',  // 0x00
31         '7',  '8',  '9',  '0',  '-',  '=',  '\b', '\t',
32         'q',  'w',  'e',  'r',  't',  'y',  'u',  'i',  // 0x10
33         'o',  'p',  '[',  ']',  '\n', NO,   'a',  's',
34         'd',  'f',  'g',  'h',  'j',  'k',  'l',  ';',  // 0x20
35         '\'', '`',  NO,   '\\', 'z',  'x',  'c',  'v',
36         'b',  'n',  'm',  ',',  '.',  '/',  NO,   '*',  // 0x30
37         ....
38
39 从这里可以看出, 数字1所对应的扫描码是2, 也就是说当我们按下1时, scancode的值便为2, 而scancode_table[2]所对应的字符就是1了, 这便是一个键盘驱动处理的大概过程。 
41 这是一个很简单的处理程序, 但可以处理绝大多数的按键, 最起码那些可打印字符之类的都可以正确处理。
42 注意, 我们这里并没有对break码做出任何处理, 这很正常, 因为我们通常不期望松开某键会发生什么事情。
44 6. 1. 3 输出
45 其实上面已经包含了一部分的输出, 如查询扫描码所对应的字符, 并用printf打印其值。 但是, 现实比上面的更复杂些, 如上面没有处理好大写字母, 多个键按下(组合键)也没处理。 有的单个键甚至会产生多个make码, 以及多个break码。 再者, 有些甚至是非打印字符(如shift, fn键), 又该如何处理。 有没发现, 其实我们现在所讲的还是处理, 因为输出 , 其实一条语句就可以解决。 
47 先说第一问题, 大写字母。 这个问题可以用两张表来解决, 一张是normal_map, 另一张是shift_map, 分别对应普通情况下与大写情况下所对应的扫描码表。
48 static unsigned char normal_map[256] =
50         NO,   0x1B, '1',  '2',  '3',  '4',  '5',  '6',  // 0x00
51         '7',  '8',  '9',  '0',  '-',  '=',  '\b', '\t',
52         'q',  'w',  'e',  'r',  't',  'y',  'u',  'i',  // 0x10
53         'o',  'p',  '[',  ']',  '\n', NO,   'a',  's',
54         'd',  'f',  'g',  'h',  'j',  'k',  'l',  ';',  // 0x20
55         '\'', '`',  NO,   '\\', 'z',  'x',  'c',  'v',
56         'b',  'n',  'm',  ',',  '.',  '/',  NO,   '*',  // 0x30
57         ....
58         ....
61 static unsigned char shift_map[256] = {
62         NO,   033,  '!',  '@',  '#',  '$',  '%',  '^',  // 0x00
63         '&',  '*',  '(',  ')',  '_',  '+',  '\b', '\t',
64         'Q',  'W',  'E',  'R',  'T',  'Y',  'U',  'I',  // 0x10
65         'O',  'P',  '{',  '}',  '\n', NO,   'A',  'S',
66         'D',  'F',  'G',  'H',  'J',  'K',  'L',  ':',  // 0x20
67         '"',  '~',  NO,   '|',  'Z',  'X',  'C',  'V',
68         'B',  'N',  'M',  '<',  '>',  '?',  NO,   '*',  // 0x30
69         NO,   ' ',  NO,   NO,   NO,   NO,   NO,   NO,
70         ...
71         ...
75 也就是说, 普通情况下, 我们用第一张表的映射, 若是Caps Lock开着或是Shift按下时, 我们就用第二张表。 那如何知道Caps是开着还是关着呢? 因为Caps Lock也是个按键, 所以必有一个扫描码与其对应,不过和之前所讲的不同的是, Caps Lock按下去之后, 它就将会保持一种状态(都大写或都小写), 除非再次按下Caps Lock, 它的状态又将改变, 所以, 我们得用一个全局变量保持这种状态。因此, 简要代码可以是这样:
76         int caps_lock_status = 0;
78         switch () {
79         case CAPS_LOCK:
80                 caps_lock_status ^= 1; /* 置返 */
81                 break;
82         case ....
83                 /* Handle other special scancodes here */
84                 break;
85         default:
86                 printf("%c", get_right_map()[scancode]);
87                 break;
88         }
89 此处中的get_right_map会根据caps lock是否开着而返回一个正确的map。 代码简写如下:
91 static inline unsigned char *get_right_map()
93         if (caps_lock_status)
94                 return shift_map
95         else
96                 return normal_map
99 好的, 大小写的问题也能处理了。 至于组合键与shift, 及多个扫描码的处理, 因稍较复杂, 这里就不详究。 
101 6.2 软盘驱动
102 软盘的驱动实现相对比键盘驱动复杂多了, 因为这会涉及到更多的端口读与写命令(而键盘我们比较关注的只有一个)。 编程端口[FDC spec]如下:
103                 软盘控制器端口
104 -----------------------------------------------------
105  端口     读写性     寄存器名称
106 -----------------------------------------------------
107 0x3f2   只写      数字输出寄存器(DOR)(数字控制寄存器)
108 -----------------------------------------------------
109 0x3f4   只读      FDC 主状态寄存器(STATUS)
110 -----------------------------------------------------
111 0x3f5   读/写     FDC 数据寄存器(DATA)
112 -----------------------------------------------------
113         只读      数字输入寄存器(DIR)
114 0x3f7
115         只写      磁盘控制寄存器(DCR)(传输率控制)
116 -----------------------------------------------------
118 首先讲解几个helper函数, send_byte 与 get_byte。 
121  *   send a byte to FD_DATA register 
122  */
123 static void send_byte (unsigned char byte)
125         volatile int msr;
126         int counter;
127         
128         for (counter = 0; counter < 1000; counter++) {
129                 msr = inb_p(FD_STATUS) & (STATUS_READY | STATUS_DIR);
130                 /*
131                  * msr 为 读取状态寄存器之后值, 我们仅当STATUS_READY成立之后,
132                  * 并且是DOR(数据从cpu到控制器), 才发送数据。
133                  */
134                 if (msr == STATUS_READY) {
135                         outb(byte,FD_DATA);
136                         return ;
137                 }
138         }
139         printk("Unable to send byte to FDC\n");
142  * get *ONE* byte of results from FD_DATA register then return what 
143  * it get, or retrun -1 if faile.
144  */
145 static int get_byte()
147         volatile int msr;
148         int counter;
150         for (counter = 0; counter < 1000; counter ++) {
151                 sleep(1); /* delay 10ms */
152                 msr = inb_p(FD_STATUS) & (STATUS_DIR|STATUS_READY|STATUS_BUSY);
153                 if (msr == (STATUS_DIR|STATUS_READY|STATUS_BUSY))
154                         return inb_p(FD_DATA);
155         }
156         printk("get_byte: get status times out!\n");
157         return -1;
161 6.2.1 中断的处理
162 cpu发送命令至软盘控制器, 控制器便开始执行命令(如读写数据, seek, 等), 若这一操作完成了, 便会产生一个中断, 告诉CPU, 你要我做的事情已经做好了。 接着CPU便会做下善后的工作, 如检查其状态, 置位一些flag变量等等。 对于软盘中断的处理,很简单, 只是置位一全局变量, 告知, 中断已完成。 简要代码如下:
163 static void floppy_interrupt(void)
165         done = 1;
166         outb(0x20, 0x20); /* End of Interrupt, 这是一部必要的操作, 告知中断已完成 */
171 6.2.2 seek 操作
172 对于一个磁盘设备来说, 自然少不了磁头的seek操作。软盘的seek操作可以通过如下命令操作:
174 #define FD_SEEK         0x0F            /* seek track */
176         send_byte(FD_SEEK);  /* 发送 seek 命令 */
177         send_byte(0);        /* 发送参数:磁头号+当前软驱号 */
178         send_byte(track);    /* 发送参数:磁道号 */
180         sleep_a_while();    /* 等待中断完成 */
181         if (!done)
182                 return -1; /* seek error */
183         
185 6.2.3 数据的读写
186  数据读或写操作需要分几步来完成。首先驱动器马达需要开启,然后把磁头定位到正确的磁道上, 最后发送数据读或写命令。 开始马达可用如下命令:
187         outb(0x1c, FD_DOR);
188 而关于seek操作可以用上面的代码。 在读写之前, 必需把LBA形式的扇区数改为CHS计数方式 , 如下:
190 static void block_to_hts(struct floppy *floppy, int sector_number, int *head, int *track, int *sector)
192         *sector = sector_number % floppy->sector;
193         sector_number /= floppy->sector;
194         *head = sector_number % floppy->head;
195         *track = sector_number / floppy->head;
200 6.3 时钟中断
201 时钟驱动总的来说很简单,主要是对8253/54芯片[PIT spec]传送一些初始化命令字就可[PIT]。 代码如下:
203 void timer_init(int hz)
205         /* hz 指向的是频率, 假如hz为100, 表示1秒内会有一百个时钟中断发生 */
206         unsigned int divisor = 1193180 / hz;   
207         outb(0x36, 0x43);         /* 发送命令字到0x43端口 */
208         outb(divisor&0xff, 0x40); /* 写入低8字节 */
209         outb(divisor>>8, 0x40);   /* 写入高8字节 */
211         set_idt_entry(0x20, timer_interrupt);  /* 最后别忘了设好IDT项 */
212         outb(inb(0x21)&0xfe, 0x21);            /* 以及开中断 */
215 正如你所想, timer_interrupt将会是很简单:
217 unsigned long long timer_ticks = 0;  /* 全局变量 , 记录着开机以来的滴答数 */
219 static void timer_interrupt(void)
221         timer_ticks++;
224 好的, 时钟中断有了, 因此我们就可以实现诸如sleep, sleep_a_while之类的了。 简单的sleep实现如下:
225 void sleep(unsigned long sleep_value)
227         unsigned long long now_ticks = timer_ticks;
229         do{
230                 ;
231         }while ( timer_ticks < now_ticks + sleep_value);
232