1.设备驱动综述
前面我们已经介绍了8259A,I/O APIC,Local APIC这类中断控制器的交互和配置方法。本质上这些中断控制器也是设备。对它们的设置也属于设备驱动的范畴。
可见设备驱动关键是了解设备内哪些内容可以交互,如何交互,交互后的效果如何。
这里我们更近一步,论述中断源设备的驱动方法。
2.键盘驱动
2.1.论述
我们先了解键盘外设的特点。
键盘外设内部有键盘控制器,按键中断信号通过电路,先是汇集到键盘控制器,再通过键盘控制器发送出去。
键盘控制器不仅用于接收键盘按键的中断信号,也负责接收鼠标设备的中断信号。
为了实现键盘和鼠标的设备驱动,我们需要了解处理器与键盘和鼠标设备交互的方法,交互的效果这些信息。
处理器与键盘控制器交互方法:
1.读取控制器状态
从0x64端口读取1字节。按比特位展开后含义如下:
bit 7:与键盘通信是否发生奇偶错误
bit 6:接收键盘数据是否超时
bit 5:鼠标输出缓冲是否满
bit 4:为0,禁止键盘
bit 3:0,上次操作端口是0x60。1,上次操作端口是0x64。
bit 2:控制器是否完成自检
bit 1:键盘输入缓冲区是否满
bit 0:键盘输出缓冲区是否满
2.向键盘控制器发送命令--不带参数
向0x64写入1字节命令。
以无参数命令0x20为例。
写入0x20后, 通过返回值返回键盘配置值。返回值可以通过读取0x60得到。
3.向键盘控制器发送命令-带参数
向0x64写入1字节命令。
以带参数命令0x60为例。
向0x64写入0x60后,
继续向0x60写入1字节的命令参数。
对命令0x60的1字节参数,按比特展开后解释如下:
bit 7:
bit 6:是否在扫描码存入输入缓存前,转换为第一套扫描码
bit 5:使能鼠标
bit 4:使能键盘
bit 3:
bit 2:通知系统已完成热启动测试及初始化
bit 1:使能鼠标中断IRQ12
bit 0:使能键盘中断IRQ1
4.读取键盘扫描码
执行的时机一般是键盘中断处理函数。
从0x60端口读取1字节。这1字节就是键盘扫描码/控制器命令执行返回值。
2.2.实践
// 动态分配一个结构,用于内核层键盘输入的环形缓冲区
struct keyboard_inputbuffer * p_kb = NULL;
// 阻塞等待键盘键盘输入缓冲区不满
#define wait_KB_write() while(io_in8(0x64) & 0x02)
void keyboard_init()
{
// 动态分配一个结构对象,用于内核层键盘输入的环形缓冲区
p_kb = (struct keyboard_inputbuffer *)kmalloc(sizeof(struct keyboard_inputbuffer),0);
p_kb->p_head = p_kb->buf;
p_kb->p_tail = p_kb->buf;
p_kb->count = 0;
memset(p_kb->buf,0,KB_BUF_SIZE);
// 构造一个IO_APIC_RET_entry对象备用
struct IO_APIC_RET_entry entry;
entry.vector = 0x21;
entry.deliver_mode = APIC_ICR_IOAPIC_Fixed ;
entry.dest_mode = ICR_IOAPIC_DELV_PHYSICAL;
entry.deliver_status = APIC_ICR_IOAPIC_Idle;
entry.polarity = APIC_IOAPIC_POLARITY_HIGH;
entry.irr = APIC_IOAPIC_IRR_RESET;
entry.trigger = APIC_ICR_IOAPIC_Edge;
entry.mask = APIC_ICR_IOAPIC_Masked;
entry.reserved = 0;
entry.destination.physical.reserved1 = 0;
entry.destination.physical.phy_dest = 0;
entry.destination.physical.reserved2 = 0;
// 阻塞等待键盘输入缓冲区不满。因为不满,所以我们可以向控制器发送命令
wait_KB_write();
io_out8(0x64,0x60);
// 阻塞等待键盘输入缓冲区不满
wait_KB_write();
// 完成键盘,鼠标使能
// 完成鼠标中断IRQ12使能
// 完成键盘中断IRQ使能
// 保证键盘扫描码采用第一套存入输入缓存区
io_out8(0x60,0x47);
// 消耗时间,以便上述控制命令实际起作用。
unsigned long i,j;
for(i = 0;i<1000;i++)
for(j = 0;j<1000;j++)
nop();
// 结构化设备中断注册
register_irq(0x21, &entry , &keyboard_handler, (unsigned long)p_kb, &keyboard_int_controller, "ps/2 keyboard");
}
上述代码中。
我们首先动态构造一个实例对象用于实现内核层面键盘按键的环形缓存区。
然后,先构造了一个 IO_APIC_RET_entry实例对象,以备后续使用。
接下来,我们向的cpu向键盘控制器发送控制命令。控制的效果是告诉键盘控制器我们使能的键盘设备,鼠标设备,使能了鼠标中断IRQ12,使能了键盘中断IRQ1。
然后,我们执行一段无意义指令,以便耗费一些时间使得上述控制命令实际被键盘控制器接收并生效。
最后,我们通过register_irq完成结构化设备中断注册。
int register_irq(
unsigned long irq,
void * arg,
void (*handler)(unsigned long nr, unsigned long parameter, struct pt_regs * regs),
unsigned long parameter,
hw_int_controller * controller,
char * irq_name)
{
irq_desc_T * p = &interrupt_desc[irq - 32];
p->controller = controller;
p->irq_name = irq_name;
p->parameter = parameter;
p->flags = 0;
p->handler = handler;
p->controller->install(irq, arg);
p->controller->enable(irq);
return 1;
}
我们为每个向量号的外部中断准备了一个irq_desc_T类型的实例对象。
这个实例对象的类型如下:
typedef struct {
// 设备控制
hw_int_controller * controller;
// 中断名称
char * irq_name;
// 参数
unsigned long parameter;
// 中断处理函数
void (*handler)(unsigned long nr, unsigned long parameter, struct pt_regs * regs);
// 标志位
unsigned long flags;
}irq_desc_T;
这里的parameter由我们结构化注册中断时提供,且这个提供的数值会在中断处理函数被调用时,作为参数进行传递。这里我们的parameter是我们动态分配的 struct keyboard_inputbuffer类型实例对象指针。这个指针指向对象将用于保存键盘中断处理结果。
typedef struct hw_int_type
{
// 中断使能
void (*enable)(unsigned long irq);
// 中断禁止
void (*disable)(unsigned long irq);
// 中断按照
unsigned long (*install)(unsigned long irq,void * arg);
// 中断卸载
void (*uninstall)(unsigned long irq);
// 中断处理的确认
void (*ack)(unsigned long irq);
}hw_int_controller;
可以认为每个中断向量号注册背后代表的是一类硬件设备的注册。所以,我们需要为这类设备提供控制。设备中断控制包括,使能,禁止。安装,卸载,确认。
// 具体到我们的键盘设备
// 为键盘设备提供一个hw_int_controller类型实例对象
// 为实例对象各个字段赋值,以便其成员符合键盘设备特性
hw_int_controller keyboard_int_controller =
{
.enable = IOAPIC_enable,
.disable = IOAPIC_disable,
.install = IOAPIC_install,
.uninstall = IOAPIC_uninstall,
.ack = IOAPIC_edge_ack,
};
在结构化注册函数实现中 ,我们执行了
p->controller->install(irq, arg);
p->controller->enable(irq);
具体到我们的键盘设备,上述将分别执行
unsigned long IOAPIC_install(unsigned long irq,void * arg)
{
struct IO_APIC_RET_entry *entry = (struct IO_APIC_RET_entry *)arg;
ioapic_rte_write((irq - 32) * 2 + 0x10,*(unsigned long *)entry);
return 1;
}
void IOAPIC_enable(unsigned long irq)
{
unsigned long value = 0;
value = ioapic_rte_read((irq - 32) * 2 + 0x10);
value = value & (~0x10000UL);
ioapic_rte_write((irq - 32) * 2 + 0x10,value);
}
对于键盘设备,执行IOAPIC_install时,参数1为0x21,参数2为构造的IO_APIC_RET_entry实例对象的指针。
因为I/O APIC前面设备初始化中,我们将其引脚0的中断号设置为0x20,后续每个引脚的中断号对应+1,所以,根据向量号我们很容易找到该向量号对应的I/O APIC的引脚。
这里向量号和引脚的对应,向量号的选取。依据的是I/O APIC引脚外接设备的部署。向我们这里的键盘设备,根据引脚部署,我们知道I/O APIC的引脚1接收来自键盘设备的中断信号。所以,键盘设备的中断向量号是0x21。对应的是I/O APIC的引脚1。
对于I/O APIC我们通过R-T-x寄存器来控制收到来自每个引脚的信号后的后续处理方式。
所以,IOAPIC_install函数干的事情就是根据中断向量号找到其I/O APIC的引脚。然后设置引脚对应的R-T-x寄存器的内容为传入的从arg开始的64个比特位。
这里就是我们前面构造的IO_APIC_RET_entry实例对象。
struct IO_APIC_RET_entry entry;
entry.vector = 0x21;
entry.deliver_mode = APIC_ICR_IOAPIC_Fixed;
entry.dest_mode = ICR_IOAPIC_DELV_PHYSICAL;
entry.deliver_status = APIC_ICR_IOAPIC_Idle;
entry.polarity = APIC_IOAPIC_POLARITY_HIGH;
entry.irr = APIC_IOAPIC_IRR_RESET;
entry.trigger = APIC_ICR_IOAPIC_Edge;
entry.mask = APIC_ICR_IOAPIC_Masked;
entry.reserved = 0;
entry.destination.physical.reserved1 = 0;
entry.destination.physical.phy_dest = 0;
entry.destination.physical.reserved2 = 0;
这个是我们所构造的实例对象,按R-T-x进行解释就是:
中断向量号为:0x21,即对应引脚中断信号的向量号是0x21。
投递模式为:0x0,即Fixed模式。Fixed模式会将中断信号按0x21向量号投递。其他模式要么采用专线,要么穿透8259A。
目标模式:0,此时采用[56, 59]区域来保存APIC ID号。
投递状态:0,表示此时这个引脚没有处于投递中的信号。
电平触发极性:0,意思是引脚收到高电平触发信号通知(推测)。
远程IRR标志位:0(具体作用不明)
触发模式:0(边沿触发模式)
结合极性,推测这里是每个上升沿设置一次中断输入。
// 关于触发模式参考这里
https://blog.csdn.net/as480133937/article/details/97396383
屏蔽位:1。即目前R-T-X对应输入引脚是被屏蔽掉的。
[56, 59]区域:0x0。结合目标模式。这里是意思是此R-T-X关联引脚的中断信号将投递给APIC ID为0x0的Local APIC设备。
这样就完成了设备驱动的安装。
下面进行设备驱动的使能,所谓使能,就是找到向量号对应I/O APIC的引脚,再找到引脚对应的R-T-X,修改R-T-X的Mask标志为0。这样就意味着来自引脚的中断输入将不再被屏蔽。
一切准备就绪后,我们只需要等待键盘控制器产生中断信号。中断信号产生时,将进入我们为其设置的处理过程进行中断处理。
void do_IRQ(struct pt_regs * regs,unsigned long nr)
{
switch(nr & 0x80)
{
case 0x00:
{
irq_desc_T * irq = &interrupt_desc[nr - 32];
if(irq->handler != NULL)
irq->handler(nr,irq->parameter,regs);
if(irq->controller != NULL && irq->controller->ack != NULL)
irq->controller->ack(nr);
}
break;
case 0x80:
// color_printk(RED,BLACK,"SMP IPI:%d,CPU:%d\n",nr,SMP_cpu_id());
Local_APIC_edge_level_ack(nr);
{
irq_desc_T * irq = &SMP_IPI_desc[nr - 200];
if(irq->handler != NULL)
irq->handler(nr,irq->parameter,regs);
}
break;
default:
color_printk(RED,BLACK,"do_IRQ receive:%d\n",nr);
break;
}
}
键盘中断信号产生时,信号到达I/O APIC引脚1,I/O APIC引脚1收到信号,将其投递给APIC ID为0x0的Local APIC。Local APIC再将中断信号传递给cpu。cpu收到中断信号触发中断处理过程。也即会进入do_IRQ进行具体的中断处理。
对于0x21,先是触发注册时提供的中断处理函数。并传递参数,参数1为向量号,参数2为注册时提供的参数,参数3为处理器中断进入到中断处理环境保存阶段所保存的现场环境信息。
void keyboard_handler(unsigned long nr, unsigned long parameter, struct pt_regs * regs)
{
struct keyboard_inputbuffer * lpkb = (struct keyboard_inputbuffer*)parameter;
unsigned char x;
x = io_in8(0x60);
color_printk(WHITE,BLACK,"(K:%02x)",x);
if(lpkb->p_head == lpkb->buf + KB_BUF_SIZE)
lpkb->p_head = lpkb->buf;
*lpkb->p_head = x;
lpkb->count++;
lpkb->p_head ++;
}
上述是键盘设备的中断处理函数 。这里面做的事情是读取键盘扫描码,存入内核为其准备的环形缓冲区。
然后我们在设备控制提供了ack下,需要执行ack。
void IOAPIC_edge_ack(unsigned long irq)
{
__asm__ __volatile__( "movq $0x00, %%rdx \n\t"
"movq $0x00, %%rax \n\t"
"movq $0x80b, %%rcx \n\t"
"wrmsr \n\t"
:::"memory");
}
上述函数做的事情是向cpu关联的Local APIC设备的EOI写入0x00。对于Local APIC采用Fixed模式投递的中断来说,向EOI写入0x00就是通知Local APIC,处理器已经处理完毕了当前中断。可以继续向处理器投递后续中断(如果有的话。)
这样我们就完成了键盘设备外部中断的安装,使能。
键盘外部中断产生时的中断处理,确认。
3.鼠标驱动
3.1.论述
为了实现键盘和鼠标的设备驱动,我们需要了解处理器与键盘和鼠标设备交互的方法,交互的效果这些信息。
处理器与鼠标控制器交互方法:
向0x64写入0xA8来开启鼠标端口
向0x64写入0xd4意思是向鼠标设备发送数据,
向鼠标设备发送的数据将作为0xd4主命令的参数。
通过向0x60写入1字节实现,如写入0xf4,那么这个0xf4就是传递给鼠标控制器的控制命令。
0xf4对于鼠标控制器是允许鼠标设备发送数据包。
鼠标设备上报的数据:
鼠标设备产生中断时,通过向端口0x60读取1字节,读到的是键盘扫描码或键盘控制器发来的数据。鼠标控制器将数据发送到键盘控制器。通过0x60读到的将是鼠标数据。
鼠标数据包对一般鼠标设备是3B。
首个字节8个比特位可以展开为:
bit 7:Y溢出
bit 6:X溢出
bit 5:Y符号位
bit 4:X符号位
bit 3:1
bit 2:鼠标中键
bit 1:鼠标右键
bit 0:鼠标左键
次个字节:
X移动值
再次个字节:
Y移动值
3.2.实践
// 动态分配一个环形缓存区
struct keyboard_inputbuffer * p_mouse = NULL;
// 阻塞等待键盘键盘输入缓冲区不满
// 每次向控制器写入时,执行这个处理,可以保证在控制器可写时写入
#define wait_KB_write() while(io_in8(0x64) & 0x02)
void mouse_init()
{
// 动态分配一个环形缓存区,以便容纳鼠标输入
p_mouse = (struct keyboard_inputbuffer *)kmalloc(sizeof(struct keyboard_inputbuffer),0);
p_mouse->p_head = p_mouse->buf;
p_mouse->p_tail = p_mouse->buf;
p_mouse->count = 0;
memset(p_mouse->buf, 0, KB_BUF_SIZE);
// 构造一个IO_APIC_RET_entry对象备用
struct IO_APIC_RET_entry entry;
entry.vector = 0x2c;
entry.deliver_mode = APIC_ICR_IOAPIC_Fixed;
entry.dest_mode = ICR_IOAPIC_DELV_PHYSICAL;
entry.deliver_status = APIC_ICR_IOAPIC_Idle;
entry.polarity = APIC_IOAPIC_POLARITY_HIGH;
entry.irr = APIC_IOAPIC_IRR_RESET;
entry.trigger = APIC_ICR_IOAPIC_Edge;
entry.mask = APIC_ICR_IOAPIC_Masked;
entry.reserved = 0;
entry.destination.physical.reserved1 = 0;
entry.destination.physical.phy_dest = 0;
entry.destination.physical.reserved2 = 0;
mouse_count = 0;
// 阻塞等待键盘输入缓存不满
wait_KB_write();
// 开启鼠标端口
io_out8(PORT_KB_CMD,KBCMD_EN_MOUSE_INTFACE);
// 循环执行无意义指令,以便等待上述控制器命令生效
unsigned long i,j;
for(i = 0;i<1000;i++)
for(j = 0;j<1000;j++)
nop();
// 每次向控制器写入控制命令均需要等待控制器缓存可写
wait_KB_write();
// 向控制器写入命令--向鼠标设备发送数据
io_out8(PORT_KB_CMD,KBCMD_SENDTO_MOUSE);
// 每次向控制器写入控制命令均需要等待控制器缓存可写
wait_KB_write();
// 向控制器写入命令--允许鼠标设备发送数据包
io_out8(PORT_KB_DATA,MOUSE_ENABLE);
// 循环执行无意义指令,以便等待上述控制器命令生效
for(i = 0;i<1000;i++)
for(j = 0;j<1000;j++)
nop();
// 每次向控制器写入控制命令均需等待控制器缓存可写
wait_KB_write();
// 向键盘发送配置命令
io_out8(PORT_KB_CMD,KBCMD_WRITE_CMD);
// 每次向控制器写入控制命令均需等待控制器缓存可写
wait_KB_write();
// 提供配置命令参数-0x47
// 使能鼠标设备,使能键盘设备
// 使能IRQ12,使能IRQ1
io_out8(PORT_KB_DATA,KB_INIT_MODE);
// 结构化设备中断注册
register_irq(0x2c, &entry , &mouse_handler, (unsigned long)p_mouse, &mouse_int_controller, "ps/2 mouse");
}
上述代码中。
我们首先动态构造一个实例对象用于实现内核层面鼠标的环形缓存区。
然后,先构造了一个 IO_APIC_RET_entry实例对象,以备后续使用。
接下来,我们向的cpu向键盘控制器发送控制命令。控制的效果是告诉键盘控制器我们使能的键盘设备,鼠标设备,使能了鼠标中断IRQ12,使能了键盘中断IRQ1。
为了使能鼠标设备,需要开启鼠标端口,允许鼠标设备发送数据包。
然后,我们执行一段无意义指令,以便耗费一些时间使得上述控制命令实际被键盘控制器接收并生效。
最后,我们通过register_irq完成结构化设备中断注册。
省略掉和键盘部分相同的内容。
对于鼠标,I/O APIC的引脚12用于外接鼠标中断信号。所以,鼠标设备中断的向量号为0x2c。
struct IO_APIC_RET_entry entry;
entry.vector = 0x2c;
entry.deliver_mode = APIC_ICR_IOAPIC_Fixed;
entry.dest_mode = ICR_IOAPIC_DELV_PHYSICAL;
entry.deliver_status = APIC_ICR_IOAPIC_Idle;
entry.polarity = APIC_IOAPIC_POLARITY_HIGH;
entry.irr = APIC_IOAPIC_IRR_RESET;
entry.trigger = APIC_ICR_IOAPIC_Edge;
entry.mask = APIC_ICR_IOAPIC_Masked;
entry.reserved = 0;
entry.destination.physical.reserved1 = 0;
entry.destination.physical.phy_dest = 0;
entry.destination.physical.reserved2 = 0;
上述部分的解释可以参考键盘部分。
void mouse_handler(unsigned long nr, unsigned long parameter, struct pt_regs * regs)
{
struct keyboard_inputbuffer * lp_mouse = (struct keyboard_inputbuffer *)parameter;
unsigned char x;
// 读取键盘扫描码或控制器命令返回值
x = io_in8(PORT_KB_DATA);
color_printk(GREEN,WHITE,"(M:%02x)",x);
if(lp_mouse->p_head == lp_mouse->buf + KB_BUF_SIZE)
lp_mouse->p_head = lp_mouse->buf;
*lp_mouse->p_head = x;
lp_mouse->count++;
lp_mouse->p_head++;
}
鼠标设备中断处理类似键盘中断。
4.磁盘驱动
4.1.论述
通过I/O端口与硬盘控制器交互,实现硬盘驱动。
0x376:
可以用于读取状态寄存器。状态寄存器比特位含义:
bit 7,为1表示控制器忙
bit 6,为1表示驱动器准备就绪
bit 3,为1表示数据请求
bit 0,为1表示命令执行错误
可以用于写入控制寄存器。控制寄存器比特位含义:
bit 2,为1表示重启控制器
bit 1,为0时使能中断请求。使能IRQ15。
0x3F6:类似0x376。0x376用于与从控制器交互。0x3F6用于与主控制器交互。
主控制器使能中断请求时,是IRQ14。
0x177:
可以用于读取状态寄存器。状态寄存器比特位含义:
同0x376。
可以用于写入控制器命令。含义:
0xEC 硬件设备识别信息
0x20 读扇区(28位LBA寻址)
0x24 扩展读扇区(48位LBA寻址)
0x30 写扇区(28位LBA寻址)
0x34 扩展写扇区(48位LBA寻址)
0x1F7:类似0x177。0x177用于与从控制器交互。0x1F7用于与主控制器交互。
0x170:
可以用于数据读取,数据写入。
0x1F0:类似0x170。0x170用于与从控制器交互。0x1F0用于与主控制器交互。
0x176:
可以用于写入设备控制寄存器。含义:
bit 7:需为1
bit 6:寻址模式,0为CHS,1为LBA
bit 5:需为1
bit 4:硬盘驱动器,0为主硬盘,1为从硬盘
bit 0~3:CHS下为磁头号,LBA下为LBA(24~27)
0x1F6:类似0x176,0x176用于与从控制器交互。0x1F6用于与主控制器交互。
写扇区(48位LBA寻址)流程:
1.通过0x176写入0x40表明采用LBA的48位寻址模式
2.提供起始扇区LBA编号,扇区数
2.1.0x171/0x1F1写入0
2.2.0x172/0x1F2写入扇区数高8位
2.3.0x173/0x1F3写入LBA(24~31)
2.4.0x174/0x1F4写入LBA(32~39)
2.5.0x175/0x1F5写入LBA(40~47)
2.6.0x171/0x1F1写入0
2.7.0x172/0x1F2写入扇区数低8位
2.8.0x173/0x1F3写入LBA(0~7)
2.9.0x174/0x1F4写入LBA(8~15)
2.10.0x175/0x1F5写入LBA(16~23)
4.2.实践
struct IO_APIC_RET_entry entry;
entry.vector = 0x2f;
entry.deliver_mode = APIC_ICR_IOAPIC_Fixed ;
entry.dest_mode = ICR_IOAPIC_DELV_PHYSICAL;
entry.deliver_status = APIC_ICR_IOAPIC_Idle;
entry.polarity = APIC_IOAPIC_POLARITY_HIGH;
entry.irr = APIC_IOAPIC_IRR_RESET;
entry.trigger = APIC_ICR_IOAPIC_Edge;
entry.mask = APIC_ICR_IOAPIC_Masked;
entry.reserved = 0;
entry.destination.physical.reserved1 = 0;
entry.destination.physical.phy_dest = 0;
entry.destination.physical.reserved2 = 0;
register_irq(0x2f, &entry , &disk_handler, (unsigned long)&disk_request, &disk_int_controller, "disk1");
采用IOAPIC下为向量号0x2f进行注册
hw_int_controller disk_int_controller =
{
.enable = IOAPIC_enable,
.disable = IOAPIC_disable,
.install = IOAPIC_install,
.uninstall = IOAPIC_uninstall,
.ack = IOAPIC_edge_ack,
};
上述0x2f对应IoAPIC的引脚15这个引脚用于接收从SATA设备发来的中断信号
// 0x376
io_out8(PORT_DISK1_ALT_STA_CTL,0);
向0x376写入0x00。以便使能IRQ15。
struct request_queue
{
wait_queue_T wait_queue_list;
struct block_buffer_node *in_using;
long block_request_count;
};
typedef struct
{
struct List wait_list;
struct task_struct *tsk;
} wait_queue_T;
// disk.c
struct request_queue disk_request;
void wait_queue_init(wait_queue_T * wait_queue,struct task_struct *tsk)
{
list_init(&wait_queue->wait_list);
wait_queue->tsk = tsk;
}
wait_queue_init(&disk_request.wait_queue_list,NULL);
disk_request.in_using = NULL;
disk_request.block_request_count = 0;
为了管理磁盘请求,我们引入struct request_queue实例成员disk_request。上述对disk_request执行初始化。
4.2.1.磁盘请求
long IDE_ioctl(long cmd, long arg)
{
struct block_buffer_node * node = NULL;
if(cmd == GET_IDENTIFY_DISK_CMD)
{
node = make_request(cmd, 0, 0, (unsigned char *)arg);
submit(node);
wait_for_finish();
return 1;
}
return 0;
}
先构造请求
// 构造磁盘请求
struct block_buffer_node * make_request(long cmd,unsigned long blocks,long count,unsigned char * buffer)
{
// 为请求分配节点
struct block_buffer_node * node = (struct block_buffer_node *)kmalloc(sizeof(struct block_buffer_node),0);
// 每个请求节点包含wait_queue
wait_queue_init(&node->wait_queue,current);
// 根据命令设置end_handler
switch(cmd)
{
case ATA_READ_CMD:
node->end_handler = read_handler;
node->cmd = ATA_READ_CMD;
break;
case ATA_WRITE_CMD:
node->end_handler = write_handler;
node->cmd = ATA_WRITE_CMD;
break;
default:
node->end_handler = other_handler;
node->cmd = cmd;
break;
}
// 设置起始扇区编号
node->LBA = blocks;
// 设置扇区数
node->count = count;
// 设置内存缓存区位置
node->buffer = buffer;
return node;
}
将构造的请求提交
// 请求提交
void submit(struct block_buffer_node * node)
{
add_request(node);
if(disk_request.in_using == NULL)
cmd_out();
}
void add_request(struct block_buffer_node * node)
{
list_add_to_before(&disk_request.wait_queue_list.wait_list,&node->wait_queue.wait_list);
disk_request.block_request_count++;
}
将node中wait_queue加入双向链表结构。双向链表哨兵节点为wait_queue_list的wait_list。
更新disk_request中阻塞请求数。
如果当前没有磁盘请求正在处理中,则从队列中取出一个实际执行。
// 从硬盘:从控制器的主硬盘
// 请求执行
long cmd_out()
{
// 从哨兵节点取得下一节点
wait_queue_T *wait_queue_tmp = container_of(list_next(&disk_request.wait_queue_list.wait_list),wait_queue_T,wait_list);
// 不断从结构成员指针得到成员隶属的实例对象的指针
// 得到隶属的block_buffer_node实例对象指针
struct block_buffer_node * node = disk_request.in_using = container_of(wait_queue_tmp,struct block_buffer_node,wait_queue);
disk_request.in_using = node;
// 从双向链表删除此node的成员wait_queue的wait_list
list_del(&disk_request.in_using->wait_queue.wait_list);
// 阻塞等待请求数更新
disk_request.block_request_count--;
// 0x177读取控制器状态。
// 阻塞等待控制器不忙
while(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_BUSY)
nop();
switch(node->cmd)
{
// 写扇区(48位LBA寻址)
case ATA_WRITE_CMD:
// 告知采用LBA 48位寻址模式
io_out8(PORT_DISK1_DEVICE, 0xe0);
io_out8(PORT_DISK1_ERR_FEATURE, 0);
// 扇区数高8位
io_out8(PORT_DISK1_SECTOR_CNT, (node->count >> 8) & 0xff);
// 24~31
io_out8(PORT_DISK1_SECTOR_LOW, (node->LBA >> 24) & 0xff);
// 32~39
io_out8(PORT_DISK1_SECTOR_MID, (node->LBA >> 32) & 0xff);
// 40~47
io_out8(PORT_DISK1_SECTOR_HIGH, (node->LBA >> 40) & 0xff);
io_out8(PORT_DISK1_ERR_FEATURE, 0);
// 扇区数低8位
io_out8(PORT_DISK1_SECTOR_CNT, node->count & 0xff);
// 0~7
io_out8(PORT_DISK1_SECTOR_LOW, node->LBA & 0xff);
// 8~15
io_out8(PORT_DISK1_SECTOR_MID, (node->LBA >> 8) & 0xff);
// 16~23
io_out8(PORT_DISK1_SECTOR_HIGH,(node->LBA >> 16) & 0xff);
// 阻塞等待,直到驱动器准备就绪
while(!(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_READY))
nop();
// 向驱动器发送0x34命令
io_out8(PORT_DISK1_STATUS_CMD,node->cmd);
// 阻塞等待,直到数据请求被置位
while(!(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_REQ))
nop();
// 写入一个扇区
port_outsw(PORT_DISK1_DATA, node->buffer, 256);
break;
case ATA_READ_CMD:
// 告知采用LBA 48位寻址模式
io_out8(PORT_DISK1_DEVICE, 0xe0);
io_out8(PORT_DISK1_ERR_FEATURE, 0);
// 扇区数高8位
io_out8(PORT_DISK1_SECTOR_CNT, (node->count >> 8) & 0xff);
io_out8(PORT_DISK1_SECTOR_LOW, (node->LBA >> 24) & 0xff);
io_out8(PORT_DISK1_SECTOR_MID, (node->LBA >> 32) & 0xff);
io_out8(PORT_DISK1_SECTOR_HIGH, (node->LBA >> 40) & 0xff);
io_out8(PORT_DISK1_ERR_FEATURE, 0);
io_out8(PORT_DISK1_SECTOR_CNT, node->count & 0xff);
io_out8(PORT_DISK1_SECTOR_LOW, node->LBA & 0xff);
io_out8(PORT_DISK1_SECTOR_MID, (node->LBA >> 8) & 0xff);
io_out8(PORT_DISK1_SECTOR_HIGH, (node->LBA >> 16) & 0xff);
// 阻塞等待驱动器准备就绪
while(!(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_READY))
nop();
// 向驱动器发送读取命令
io_out8(PORT_DISK1_STATUS_CMD, node->cmd);
break;
case GET_IDENTIFY_DISK_CMD:
io_out8(PORT_DISK1_DEVICE, 0xe0);
io_out8(PORT_DISK1_ERR_FEATURE, 0);
io_out8(PORT_DISK1_SECTOR_CNT, node->count & 0xff);
io_out8(PORT_DISK1_SECTOR_LOW, node->LBA & 0xff);
io_out8(PORT_DISK1_SECTOR_MID, (node->LBA >> 8) & 0xff);
io_out8(PORT_DISK1_SECTOR_HIGH, (node->LBA >> 16) & 0xff);
// 阻塞等待驱动器就绪
while(!(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_READY))
nop();
// 通过控制器发送0xEc命令
io_out8(PORT_DISK1_STATUS_CMD, node->cmd);
default:
color_printk(BLACK,WHITE,"ATA CMD Error\n");
break;
}
return 1;
}
向硬盘读取数据的命令发出后,硬盘准备好数据后(将数据放入硬盘缓存区)会向逻辑处理器发送中断信号。
向硬盘写入数据 ,数据全部写入目标扇区后,硬盘会向逻辑处理器发送中断信号。
// 来自磁盘中断信号的处理函数
void disk_handler(unsigned long nr, unsigned long parameter, struct pt_regs * regs)
{
// 从parameter得到in_using
// 通过in_using的end_handler进行具体处理
struct block_buffer_node * node = ((struct request_queue *)parameter)->in_using;
color_printk(BLACK,WHITE,"disk_handler\n");
node->end_handler(nr,parameter);
}
// 读取命令中断处理
void read_handler(unsigned long nr, unsigned long parameter)
{
struct block_buffer_node * node = ((struct request_queue *)parameter)->in_using;
// 即使读取多个扇区,硬盘也是每次读完一个扇区发一次中断信号
if(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_ERROR)
color_printk(RED,BLACK,"read_handler:%#010x\n",io_in8(PORT_DISK1_ERR_FEATURE));
else
port_insw(PORT_DISK1_DATA,node->buffer,256);
node->count--;
// 说明,还有扇区待读取。继续等待通知。
if(node->count)
{
node->buffer += 512;
return;
}
end_request(node);
}
// 表明node代表的请求已经处理完毕
void end_request(struct block_buffer_node * node)
{
if(node == NULL)
color_printk(RED,BLACK,"end_request error\n");
// 每个磁盘请求都是一个任务提出的
// 任务提出请求后,放弃逻辑处理器,等待请求完成
// 请求完成时,任务变为可调度的
node->wait_queue.tsk->state = TASK_RUNNING;
// 加入逻辑处理器的调度队列
insert_task_queue(node->wait_queue.tsk);
// 设置当前进程调度标志,以便尽快恢复阻塞进程的运行。---抢占
current->flags |= NEED_SCHEDULE;
//node->wait_queue.tsk->flags |= NEED_SCHEDULE;
// 释放node
kfree((unsigned long *)disk_request.in_using);
// 当前正在处理请求置空
disk_request.in_using = NULL;
// 如果还有其他阻塞的请求,继续选出一个执行。
if(disk_request.block_request_count)
cmd_out();
}
上述是一个读取磁盘的中断处理。每次中断处理实际从硬盘读取一个扇区内容。若完成当前磁盘请求,执行end_request。
end_request中先是将请求关联的任务设置为可调度,加入逻辑处理器的调度队列。然后设置当前进程的调度标志,释放前面动态分配的node。
在有磁盘请求排队时,选择一个进行处理后,再结束。
void write_handler(unsigned long nr, unsigned long parameter)
{
struct block_buffer_node * node = ((struct request_queue *)parameter)->in_using;
// 检查命令执行是否出错
if(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_ERROR)
color_printk(RED,BLACK,"write_handler:%#010x\n",io_in8(PORT_DISK1_ERR_FEATURE));
// 每次写完一个扇区触发一次中断信号通知
node->count--;
if(node->count)
{
node->buffer += 512;
// 阻塞等待,直到数据请求被置位
while(!(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_REQ))
nop();
// 向硬盘写入一个扇区
port_outsw(PORT_DISK1_DATA,node->buffer,256);
return;
}
// 请求结束处理
end_request(node);
}
void other_handler(unsigned long nr, unsigned long parameter)
{
struct block_buffer_node * node = ((struct request_queue *)parameter)->in_using;
if(io_in8(PORT_DISK1_STATUS_CMD) & DISK_STATUS_ERROR)
color_printk(RED,BLACK,"other_handler:%#010x\n",io_in8(PORT_DISK1_ERR_FEATURE));
else
port_insw(PORT_DISK1_DATA,node->buffer,256);// 读取1个扇区
// 请求处理结束
end_request(node);
}
提交磁盘请求后同步等待
// 同步等待请求完成
void wait_for_finish()
{
//current->state = TASK_UNINTERRUPTIBLE;
// STORE_ALL
//schedule();
// RESTORE_ALL
while(disk_request.in_using != NULL)
{
nop();
}
}
上述是投递请求后,循环等待当前请求处理完毕。这里想的比较简单,认为当前请求就是希望等待完成的请求。等待的过程也未让出逻辑处理。
更好的等待方式是:
磁盘IO等待处理:
1.将自己标记为不可调度
2.设置自身调度标志
3.自身循环检测等待请求完成
这样后续产生中断,中断返回时,将执行进程切换:
1.换入新进程,当前进程不再占用逻辑处理器。当前进程执行现场通过中断进入得以保存。
2.新进程执行中,磁盘请求完成,产生中断
3.磁盘中断处理中,在磁盘请求已经完成下
3.1.设置请求关联进程状态为就绪
3.2.将关联进程放入调度队列
3.3.设置当前进程调度标志
这样,中断返回时,将引发进程切换。
如果切换选择了等待的进程,则等待的进程将继续其原来的执行流程--循环检测。
这时进行检测时,循环检测将通过,从而使得其可以继续执行后续逻辑。