在介绍ATA及相关的代码之前,我们有必要先了解下电脑访问硬盘(包括光盘)里的数据的基本原理 打开台式机的机箱,我们可以看到,硬盘驱动器通过一根数据线连接到主板上,在硬盘驱动器背面的电路板上有一个Disk Controller(磁盘控制器)....

    v1.1.0的项目地址:

    github.com地址:https://github.com/zenglong/zenglOX (只包含git提交上来的源代码)
   
    Dropbox地址:点此进入Dropbox网盘  该版本位于zenglOX_v1.1.0的文件夹,该文件夹里的zenglOX_v1.1.0.zip的压缩包为源代码,ATA-ATAPI-5.pdf,ATA-ATAPI-6.pdf以及INF-8070.PDF这三个PDF是和ATA及ATAPI相关的手册。

    sourceforge地址:https://sourceforge.net/projects/zenglox/files  (该版本位于zenglOX_v1.1.0的文件夹,里面包含了和上面Dropbox里一样的4个文件。)

    有关zenglOX的源码编译及gdb调试的方法,请参考zenglOX v0.0.1里的文章。

    VirtualBox里挂载zenglOX.iso镜像的方法,以及将zenglOX刷入U盘,从U盘启动的方式请参考zenglOX v1.0.0里的文章。

    在介绍ATA及相关的代码之前,我们有必要先了解下电脑访问硬盘(包括光盘)里的数据的基本原理。

    打开台式机的机箱,我们可以看到,硬盘驱动器通过一根数据线连接到主板上,在硬盘驱动器背面的电路板上有一个Disk Controller(磁盘控制器),如下图所示:
 
图1

    Disk Controller(磁盘控制器)负责所有的底层操作,例如:移动磁头,传输数据等。在磁盘控制器里有很多的寄存器,例如:Command/Status register(命令或状态寄存器),Data register(数据寄存器)等,开发驱动程序时,主要就是对磁盘控制器里的这些寄存器进行读写操作,来完成控制磁盘以及进行数据传输的。

    硬盘驱动器(包括光盘驱动器)都是通过数据线连接到主板上的,数据线有比较宽的并行数据线,也有窄的串行数据线:
 
图2 (并行数据线)
 
 
图3 (串行数据线)

    并行数据线连接的磁盘与串行数据线连接的磁盘,以及各种不同类型的磁盘驱动器,在对它们的Disk Controller(磁盘控制器)进行操作时,寄存器在读写方面的规定都不太一样,这样就形成了多套标准(或者叫做interfaces即接口),常见的标准有:用于家用个人电脑的PATA和SATA,用于高端服务器的SCSI、Fibre Channel 或 串行连接的SCSI等。

    在当前zenglOX v1.1.0的版本里,主要是针对PATA的标准来编写的驱动程序。

    当信号从磁盘的Disk Controller(磁盘控制器)里通过并行或串行数据线传到主板上后,就会通过主板上的host bus adapter(主机总线适配器,简称HBA)将信号转为适合在主板总线上传输的格式,然后CPU就可以从主板总线上获取到磁盘Disk Controller发送过来的信号了。host bus adapter(主机总线适配器)还可以作为card(板卡)的形式插到PCI插槽上,如下图所示:
 

图4

    上图是一个PCI ATA/133 IDE host bus adapter(用于接并行ATA设备(即PATA)的主机总线适配器),他的下方有金手指,可以插入PCI插槽里,上方则可以接并行数据线。

    以上就是CPU与磁盘之间的沟通方式的简单介绍。

    我们上面提到了,zenglOX v1.1.0是针对PATA标准来编写驱动程序的,PATA是Parallel ATA(并行ATA)的简写,而ATA又是Advanced Technology Attachment(高级技术附件)的简写,PATA标准的设备,是通过并行数据线连接到主板上的,一般在比较老式的家用台式机上可以看到这种连接宽的并行数据线的硬盘和光盘驱动器。

    对PATA标准的设备编写驱动程序,其实就是对Disk Controller(磁盘控制器)里的寄存器按照ATA的规定来进行相关的读写操作。

    要对寄存器进行读写操作,我们就必须知道这些寄存器对应的I/O端口地址,在 http://en.wikipedia.org/wiki/Input/output_base_address 该链接对应的文章中,将常见的各种I/O端口地址列举在一张表格里,我们本篇所需的PATA相关的I/O端口地址如下:

I/O地址范围 设备
170 - 177 Secondary Parallel ATA Disk Controller 
第二并行ATA磁盘控制器,各主要寄存器的端口地址
1F0 - 1F7 First Parallel ATA Disk Controller 
第一并行ATA磁盘控制器,各寄存器的端口地址
370 - 377 第二并行ATA磁盘控制器里,和Control register控制寄存器等相关的端口地址
3F0 - 3F7 第一并行ATA磁盘控制器里,和Control register控制寄存器等相关的端口地址

    之所以有第一并行和第二并行,是因为PATA有两个通道,在老式的台式机里,可以看到这两个通道:


图5

    上面的两个IDE插槽,每个插槽都可以接一根并行数据线,其中一根对应第一并行通道,另一根对应第二并行通道,此外,每根并行数据线可以接主从两个设备:
 

图6

    从上图可以看到,并行数据线的中间插口可以接从盘,离中间插口最远的一端连接到主板的IDE接口上,另一端连接主盘。

    因此,两根并行数据线一共可以接4个设备,第一并行的主从两个设备,以及第二并行的主从两个设备,因此,在当前zenglOX v1.1.0版本的命令行下,通过输入ata -s命令可以看到和PATA相关的4个设备的连接情况:
 

图7

    上面的ata0开头的对应第一并行通道,ata1开头的对应第二并行通道,ata0与ata1后面的master与slave则表示并行通道里连接的主从设备。

    根据上面显示过的I/O地址范围,在zlox_ata.h头文件里,就相应的定义了Disk Controller(磁盘控制器)中不同寄存器的I/O地址:

/* The necessary I/O ports, indexed by "bus". */
#define ZLOX_ATA_DATA(x)         (x)
#define ZLOX_ATA_FEATURES(x)     (x+1)
#define ZLOX_ATA_SECTOR_COUNT(x) (x+2)
#define ZLOX_ATA_ADDRESS1(x)     (x+3)
#define ZLOX_ATA_ADDRESS2(x)     (x+4)
#define ZLOX_ATA_ADDRESS3(x)     (x+5)
#define ZLOX_ATA_DRIVE_SELECT(x) (x+6)
#define ZLOX_ATA_COMMAND(x)      (x+7)
#define ZLOX_ATA_DCR(x)          (x+0x206)   /* device control register */
 
/* valid values for "bus" */
#define ZLOX_ATA_BUS_PRIMARY     0x1F0
#define ZLOX_ATA_BUS_SECONDARY   0x170


    上面的ZLOX_ATA_BUS_PRIMARY宏对应第一并行通道的起始I/O端口地址,ZLOX_ATA_BUS_SECONDARY宏对应第二并行通道的起始I/O端口地址,ZLOX_ATA_COMMAND(x) (x+7)宏表示Command register(命令寄存器)的I/O地址为起始端口地址加上7,也就是说,当访问第一并行通道时,起始端口地址为0x1F0,则该通道里的Command命令寄存器的I/O地址就是0x1F0 + 7即0x1F7 。其他的ZLOX_ATA_DATA(x)之类的宏都是同理。

    看到这,读者可能会问,之前的表格里只给出了一个I/O地址的范围,这些具体的寄存器的I/O地址是依据什么来得出的呢?答案就在Sourceforge里的ATA-ATAPI-5.pdf或ATA-ATAPI-6.pdf文档中,ATA有好几个版本的技术标准,ATA-ATAPI-5.pdf文档里显示的是ATA-5的标准,ATA-ATAPI-6.pdf文档里显示的是ATA-6的标准,ATA-6高度兼容ATA-5,同时又在ATA-5的基础上,做了一些功能上的扩展。

    在ATA-ATAPI-5.pdf的第69页(这里的页数是PDF电子档顶部,分页输入框中的页数)有Command/Status register(命令或状态寄存器)的地址和功能之类的描述:


图8

    上面的图8中,Address部分的地址定义,和我们之前介绍的Intel中的I/O地址的定义,有点不太一样,CS1与CS0对应磁盘控制器芯片的选择引脚,地址中的字符N表示该引脚处于FALSE非激活状态,字符A表示该引脚处于TRUE激活状态,当CS0为A激活状态时,则该寄存器的起始I/O地址为0x1F0或0x170(根据访问的并行通道来确定),当CS1为A激活状态时,则该寄存器的起始I/O地址为0x1F0 + 0x200 = 0x3F0或0x170 + 0x200 = 0x370,起始地址只是确定了其高位的地址值,低4位的地址值则由剩下的DA2,DA1和DA0来确定,其中DA0对应低4位中的最低位,例如,上图的DA0,DA1,DA2都为A,所以,低4位的地址值的二进制值就是111,对应的十六进制值即为0x7,所以Command register的实际I/O地址为0x1F0 + 0x7 = 0x1F7 或者 0x170 + 0x7 = 0x177 ,当向该I/O地址写入数据时,操作的是命令寄存器,当从该地址读取数据时,读取的是状态寄存器的值。

    从ATA-ATAPI-5.pdf的第75页,可以得到Features/Error register(功能或错误寄存器)的地址如下:

CS1 CS0 DA2 DA1 DA0
N A N N A
A = asserted, N = negated

    由CS0为A,说明起始地址为0x1F0或0x170,再由DA0为A,DA1与DA2为N,说明低4位的二进制值为001,对应十六进制值为0x1,所以该寄存器的I/O地址值为0x1F0 + 0x1 = 0x1F1 或 0x170 + 0x1 = 0x171 ,当向0x1F1或0x171写入数据时,写入到的是Features register(功能寄存器),当从该地址读取数据时,读取到的会是Error register(错误寄存器)里的值。

    在ATA-ATAPI-5.pdf的第72页的底部,可以看到Device Control register(设备控制寄存器)的地址如下:

CS1 CS0 DA2 DA1 DA0
A N A A N
A = asserted, N = negated

    由CS1为1,说明起始地址为0x3F0或0x370,再由DA0为N,DA1与DA2为A,说明低4位的二进制值为110,对应十六进制值为0x6,所以控制寄存器的I/O地址为0x3F0 + 0x6 = 0x3F6 或 0x370 + 0x6 = 0x376 。

    这样,以此类推,就可以得到上面zlox_ata.h头文件里各个寄存器的I/O地址的宏定义了,在这些寄存器里,有一个比较特别的寄存器,就是ZLOX_ATA_DRIVE_SELECT宏定义的寄存器,可以在ATA-ATAPI-5.pdf的第73页,看到该寄存器的相关介绍,该寄存器的I/O地址为0x1F6或0x176(可以用上面介绍的方法来计算出),这是一个设备选择寄存器,通过向该寄存器写入值,就可以设置需要操作的主从设备,之前我们提到过,一根PATA的并行数据线上可以接主从的两个设备,处理器向该数据线上发送的命令会同时发给主设备和从设备,通过向设备选择寄存器写入数据,就可以设置到底是访问主设备还是从设备。

    设备选择寄存器中需要写入的值的格式如下(该图位于ATA-ATAPI-5.pdf的第74页):


图9

    通过将位4(即上图的DEV位)设置为0则表示选择主设备,将该位设置为1表示选择从设备,其他的位可以都设置为0,zenglOX里将位5和位7这两个废弃的位设置为了1。

    在zlox_ata.c文件中,有一个zlox_init_ata函数,内核启动时,会调用该函数来初始化ATA设备,在该函数里,会对ATA的两个并行通道,以及这些通道上接的主从设备进行检测,判断当前系统里是否存在ATA相关的设备,并将ATA设备相关的信息写入到ide_devices数组中(该数组有4个成员,每个成员对应一个可能存在的PATA设备):

/*zlox_ata.c Define some functions relate to ata*/

.....................................
.....................................

ZLOX_IDE_DEVICE ide_devices[4];

.....................................
.....................................

static ZLOX_VOID zlox_ata_callback(/*ZLOX_ISR_REGISTERS * regs*/)
{
	if(ata_wait_task != ZLOX_NULL)
		ata_wait_task->status = ZLOX_TS_RUNNING;
}

ZLOX_VOID zlox_init_ata()
{
	ZLOX_SINT32 i, j, k, count = 0;
	ZLOX_UINT16 bus,drive;
	ZLOX_UINT8 * ide_buf = (ZLOX_UINT8 *)zlox_kmalloc(256);
	for (i = 0; i < 2; i++)
	{
		if(i == 0)
			bus = ZLOX_ATA_BUS_PRIMARY;
		else
			bus = ZLOX_ATA_BUS_SECONDARY;
 		for (j = 0; j < 2; j++)
		{
			if(j == 0)
				drive = ZLOX_ATA_DRIVE_MASTER;
			else
				drive = ZLOX_ATA_DRIVE_SLAVE;
			ZLOX_UINT8 err = 0, type = ZLOX_IDE_ATA, status;
			ide_devices[count].Reserved = 0; // Assuming that no drive here.
			// (I) Select Drive:
			zlox_outb (ZLOX_ATA_DRIVE_SELECT (bus),(drive & (1 << 4)));
			ZLOX_ATA_SELECT_DELAY (bus);       /* 400ns delay */
			zlox_outb (ZLOX_ATA_COMMAND (bus), 0xEC);      /* ATA IDENTIFY command */
			ZLOX_ATA_SELECT_DELAY (bus);       /* 400ns delay */
			if(zlox_inb (ZLOX_ATA_COMMAND (bus)) == 0)
				goto inner_end; // If Status = 0, No Device.
			
			while(ZLOX_TRUE)
			{
				status = zlox_inb (ZLOX_ATA_COMMAND (bus));
				if((status & 0x1))
				{
					err = 1; // If Err, Device is not ATA.
					break;
				}
				if(!(status & 0x80) && (status & 0x8))
					break; // Everything is right.
			}

			// (IV) Probe for ATAPI Devices:
			if (err != 0) {
				ZLOX_UINT8 cl = zlox_inb (ZLOX_ATA_ADDRESS2 (bus));
				ZLOX_UINT8 ch = zlox_inb (ZLOX_ATA_ADDRESS3 (bus));
				
				if (cl == 0x14 && ch ==0xEB)
					type = ZLOX_IDE_ATAPI;
				else if (cl == 0x69 && ch == 0x96)
					type = ZLOX_IDE_ATAPI;
				else
					goto inner_end; // Unknown Type (may not be a device).
				
				zlox_outb (ZLOX_ATA_COMMAND (bus), 0xA1); /* ATA IDENTIFY PACKET command */
				ZLOX_ATA_SELECT_DELAY (bus); /* 400ns delay */
			}
			
			// (V) Read Identification Space of the Device:
			zlox_insw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *)ide_buf, 256 / 2);
			
			// (VI) Read Device Parameters:
			ide_devices[count].Reserved = 1;
			ide_devices[count].Type = type;
			ide_devices[count].Bus = bus;
			ide_devices[count].Drive = drive;
			ide_devices[count].Signature = *((ZLOX_UINT16 *)(ide_buf + ZLOX_ATA_IDENT_DEVICETYPE));
			ide_devices[count].Capabilities = *((ZLOX_UINT16 *)(ide_buf + ZLOX_ATA_IDENT_CAPABILITIES));
			ide_devices[count].CommandSets = *((ZLOX_UINT32 *)(ide_buf + ZLOX_ATA_IDENT_COMMANDSETS));

			// (VII) Get Size:
			if (ide_devices[count].CommandSets & (1 << 26))
				// Device uses 48-Bit Addressing:
				ide_devices[count].Size   = *((ZLOX_UINT32 *)(ide_buf + ZLOX_ATA_IDENT_MAX_LBA_EXT));
			else
				// Device uses CHS or 28-bit Addressing:
				ide_devices[count].Size   = *((ZLOX_UINT32 *)(ide_buf + ZLOX_ATA_IDENT_MAX_LBA));

			// (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...):
			for(k = 0; k < 40; k += 2) 
			{
				ide_devices[count].Model[k] = ide_buf[ZLOX_ATA_IDENT_MODEL + k + 1];
				ide_devices[count].Model[k + 1] = ide_buf[ZLOX_ATA_IDENT_MODEL + k];
			}
			ide_devices[count].Model[40] = 0; // Terminate String.
inner_end:
			count++;
		} // for (j = 0; j < 2; j++)
	} // for (i = 0; i < 2; i++)
	
	zlox_kfree(ide_buf);

	// 4- Print Summary:
	for (i = 0; i < 4; i++)
	{
		if(ide_devices[i].Reserved == 1)
		{
			if(ide_devices[i].Type == ZLOX_IDE_ATA)
				zlox_monitor_write("Found ATA Drive ");
			else
				zlox_monitor_write("Found ATAPI Drive ");
			if(ide_devices[i].Type == ZLOX_IDE_ATA && !(ide_devices[i].CommandSets & (1 << 26)))
			{
				zlox_monitor_write_dec(ide_devices[i].Size * ZLOX_ATA_SECTOR_SIZE / 1024 / 1024);
				zlox_monitor_write("MB - ");
			}
			else
				zlox_monitor_write(" - ");
			zlox_monitor_write((ZLOX_CHAR *)ide_devices[i].Model);
			zlox_monitor_put('\n');
		}
	}
	
	zlox_register_interrupt_callback(ZLOX_IRQ14,&zlox_ata_callback);
	zlox_register_interrupt_callback(ZLOX_IRQ15,&zlox_ata_callback);
}


    上面的zlox_init_ata初始化函数的大概过程是:

    1,先选择需要操作的主从设备(通过向上面提到过的设备选择寄存器中写入值来进行设置),相关代码为:

// (I) Select Drive:
zlox_outb (ZLOX_ATA_DRIVE_SELECT (bus),(drive & (1 << 4)));


    2,选择设备后,需要等待400ns,让其完成选择操作,可以通过读取4次控制寄存器来实现400ns的等待。

    3,向命令寄存器发送0xEC即ATA IDENTIFY DEVICE command(ATA的设备识别命令)来获取设备的相关信息,该命令位于ATA-ATAPI-5.pdf文档的第101页。

    4,同样等待400ns,让其执行完上面的设备识别命令。

    5,读取命令寄存器,当对命令寄存器的I/O地址进行读取操作时,读取的会是状态寄存器的值,从状态寄存器中的值,可以得知该设备是否存在,如果状态寄存器的值为0,则说明设备不存在。从状态寄存器中,读取的值的格式如下:

7 6 5 4 3 2 1 0
BSY DRDY # # DRQ Obsolete Obsolete ERR

    上表位于ATA-ATAPI-5.pdf文档的第77页。

    位0为ERR错误标志,当执行某个命令发生错误时,就会设置该标志,通过检测该标志就可以判断之前的命令是否执行成功,位1和位2是过时的,废弃的标志位,位3(即DRQ位)表示需要传输的数据是否准备好了,位7(即BSY位)表示设备是否正忙,设备正在执行某个命令,或正在准备相关数据时,就会设置该位。

    所以在执行完设备识别命令后,就有一个循环读取状态寄存器的操作:

if(zlox_inb (ZLOX_ATA_COMMAND (bus)) == 0)
	goto inner_end; // If Status = 0, No Device.

while(ZLOX_TRUE)
{
	status = zlox_inb (ZLOX_ATA_COMMAND (bus));
	if((status & 0x1))
	{
		err = 1; // If Err, Device is not ATA.
		break;
	}
	if(!(status & 0x80) && (status & 0x8))
		break; // Everything is right.
}


    上面的代码里,当状态寄存器的值为0时,说明当前检测的ATA设备不存在,则直接跳到下一个设备去进行检测,如果设备存在,则再通过循环来判断之前的命令是否执行完了,相关数据是否准备好了,当状态寄存器的BSY位被清零,同时DRQ位被设置为1时,说明数据准备好了,之前执行的ATA设备识别命令,会将设备的相关信息存储在磁盘控制器的缓存里,驱动程序可以通过读取数据寄存器来获取这些信息,一共有256个字节的设备信息,这些字节对应的设备信息的具体含义,可以参考ATA-ATAPI-5.pdf文档的第103页,还可以参考ATA-ATAPI-6.pdf文档的第129页,ATA-6标准中对ATA-5标准的设备信息进行了一些扩展。

    6,在读取数据寄存器里的设备信息之前,还需要从状态寄存器的值中判断,是否有错误,当有错误发生时,则说明当前检测的设备不是ATA硬盘驱动器,有可能是一个支持ATAPI标准的光盘驱动器,光盘驱动器和硬盘驱动器不太一样,使用的是ATAPI(AT Attachment Packet Interface)标准,它是从ATA标准的基础上,衍生出来的一种用于光盘驱动器的标准,它有自己单独的检测设备信息的命令:

// (IV) Probe for ATAPI Devices:
if (err != 0) {
	ZLOX_UINT8 cl = zlox_inb (ZLOX_ATA_ADDRESS2 (bus));
	ZLOX_UINT8 ch = zlox_inb (ZLOX_ATA_ADDRESS3 (bus));
	
	if (cl == 0x14 && ch ==0xEB)
		type = ZLOX_IDE_ATAPI;
	else if (cl == 0x69 && ch == 0x96)
		type = ZLOX_IDE_ATAPI;
	else
		goto inner_end; // Unknown Type (may not be a device).
	
	zlox_outb (ZLOX_ATA_COMMAND (bus), 0xA1); /* ATA IDENTIFY PACKET command */
	ZLOX_ATA_SELECT_DELAY (bus); /* 400ns delay */
}


    从上面的代码片段可以看出,在发送ATAPI的设备识别命令之前,需要先判断当前设备是否确实是一个ATAPI设备,如果不是,则跳过该设备的检测,要判断一个设备是否是ATAPI类型的,可以参考ATA-ATAPI-5.pdf文档的第294页,在执行完之前的ATA IDENTIFY DEVICE command(ATA的设备识别命令)后,如果是有效的ATAPI设备,那么磁盘控制器里的Cylinder Low register与Cylinder High register这两个寄存器的值会被分别设置为0x14与0xEB,有的情况下会被分别设置为0x69与0x96,因此,通过读取这两个寄存器的值,就可以判断是否是有效的ATAPI设备了,上面代码片段中的ZLOX_ATA_ADDRESS2宏对应的就是Cylinder Low register,ZLOX_ATA_ADDRESS3宏对应的就是Cylinder High register 。

    通过向命令寄存器里写入0xA1,就可以发送IDENTIFY PACKET DEVICE Command(ATAPI的设备识别命令),该命令的详情可以参考ATA-ATAPI-5.pdf文档的第119页的底部。

    如果发送了ATAPI的设备识别命令,也需要等待400ns 。

    7,当执行完设备识别命令后,就可以将256字节的设备信息通过数据寄存器读取出来,并将有用的信息设置到ide_devices数组中:

// (V) Read Identification Space of the Device:
zlox_insw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *)ide_buf, 256 / 2);

// (VI) Read Device Parameters:
ide_devices[count].Reserved = 1;
ide_devices[count].Type = type;
ide_devices[count].Bus = bus;
ide_devices[count].Drive = drive;
ide_devices[count].Signature = *((ZLOX_UINT16 *)(ide_buf + ZLOX_ATA_IDENT_DEVICETYPE));
ide_devices[count].Capabilities = *((ZLOX_UINT16 *)(ide_buf + ZLOX_ATA_IDENT_CAPABILITIES));
ide_devices[count].CommandSets = *((ZLOX_UINT32 *)(ide_buf + ZLOX_ATA_IDENT_COMMANDSETS));

// (VII) Get Size:
if (ide_devices[count].CommandSets & (1 << 26))
	// Device uses 48-Bit Addressing:
	ide_devices[count].Size   = *((ZLOX_UINT32 *)(ide_buf + ZLOX_ATA_IDENT_MAX_LBA_EXT));
else
	// Device uses CHS or 28-bit Addressing:
	ide_devices[count].Size   = *((ZLOX_UINT32 *)(ide_buf + ZLOX_ATA_IDENT_MAX_LBA));

// (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...):
for(k = 0; k < 40; k += 2) 
{
	ide_devices[count].Model[k] = ide_buf[ZLOX_ATA_IDENT_MODEL + k + 1];
	ide_devices[count].Model[k + 1] = ide_buf[ZLOX_ATA_IDENT_MODEL + k];
}
ide_devices[count].Model[40] = 0; // Terminate String.


    这些信息中包含了设备的类型,设备支持的功能,设备的容量,设备的生产供应商等信息。ATA硬盘设备的信息在前面已经给出了参考页面,ATAPI光盘设备的信息可以参考ATA-ATAPI-5.pdf文档的第122页,或者参考ATA-ATAPI-6.pdf文档的第151页,ATAPI-6标准对ATAPI-5标准做了一些扩展。

    8,在得到相关设备信息后,就可以循环将ide_devices数组中的设备信息显示出来:

// 4- Print Summary:
for (i = 0; i < 4; i++)
{
	if(ide_devices[i].Reserved == 1)
	{
		if(ide_devices[i].Type == ZLOX_IDE_ATA)
			zlox_monitor_write("Found ATA Drive ");
		else
			zlox_monitor_write("Found ATAPI Drive ");
		if(ide_devices[i].Type == ZLOX_IDE_ATA && !(ide_devices[i].CommandSets & (1 << 26)))
		{
			zlox_monitor_write_dec(ide_devices[i].Size * ZLOX_ATA_SECTOR_SIZE / 1024 / 1024);
			zlox_monitor_write("MB - ");
		}
		else
			zlox_monitor_write(" - ");
		zlox_monitor_write((ZLOX_CHAR *)ide_devices[i].Model);
		zlox_monitor_put('\n');
	}
}


    另外,这里需要注意的是,硬盘的扇区大小一般为512字节,光盘的扇区大小一般为2048字节,因此在zlox_ata.h里就有这两种扇区大小的宏定义:

/* The default and seemingly universal sector size for CD-ROMs. */
#define ZLOX_ATAPI_SECTOR_SIZE 2048

/* The default and seemingly universal sector size for hard disk. */
#define ZLOX_ATA_SECTOR_SIZE 512


    以上就是ATA的初始化过程。

    下面看下,如何通过ATAPI标准来读取光盘里的数据,这里所介绍的硬盘设备和光盘设备都是连接并行数据线的设备,前面提到过,ATAPI是在ATA标准的基础上发展出来的用于处理光盘设备的标准,ATAPI和ATA标准使用的是相同的寄存器,只不过写入和读取的数据有所不同。

    在zlox_ata.c文件中,有一个zlox_atapi_drive_read_sector函数,该函数可以将光盘指定逻辑扇区里的数据读取到目标缓冲里:

/* Use the ATAPI protocol to read a single sector from the given
 * ide_index into the buffer using logical block address lba. */
ZLOX_SINT32 zlox_atapi_drive_read_sector (ZLOX_UINT32 ide_index, ZLOX_UINT32 lba, ZLOX_UINT8 *buffer)
{
	/* 0xA8 is READ SECTORS command byte. */
	ZLOX_UINT8 read_cmd[12] = { 0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	ZLOX_UINT8 status;
	ZLOX_SINT32 size;
	ZLOX_UINT32 bus, drive;
	if(ide_index > 3)
		return -1;
	if((ide_devices[ide_index].Reserved == 0) || (ide_devices[ide_index].Type == ZLOX_IDE_ATA))
		return -1;

	bus = ide_devices[ide_index].Bus;
	drive = ide_devices[ide_index].Drive;

	/* Select drive (only the slave-bit is set) */
	zlox_outb (ZLOX_ATA_DRIVE_SELECT (bus),(drive & (1 << 4)));
	ZLOX_ATA_SELECT_DELAY (bus);       /* 400ns delay */
	zlox_outb (ZLOX_ATA_FEATURES (bus),0x0);       /* PIO mode */
	zlox_outb (ZLOX_ATA_ADDRESS2 (bus), (ZLOX_ATAPI_SECTOR_SIZE & 0xFF));
	zlox_outb (ZLOX_ATA_ADDRESS3 (bus), (ZLOX_ATAPI_SECTOR_SIZE >> 8));
	zlox_outb (ZLOX_ATA_COMMAND (bus), 0xA0);       /* ATA PACKET command */
	while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x80)     /* BUSY */
		asm volatile ("pause");
	while (!((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x8) && !(status & 0x1))
		asm volatile ("pause");
	/* DRQ or ERROR set */
	if (status & 0x1) {
		size = -1;
		goto end;
	}
	read_cmd[9] = 1;              	/* 1 sector */
	read_cmd[2] = (lba >> 0x18) & 0xFF;   /* most sig. byte of LBA */
	read_cmd[3] = (lba >> 0x10) & 0xFF;
	read_cmd[4] = (lba >> 0x08) & 0xFF;
	read_cmd[5] = (lba >> 0x00) & 0xFF;   /* least sig. byte of LBA */
	/* Send ATAPI/SCSI command */
	zlox_outsw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *) read_cmd, 6);
	/* Tell the scheduler that this process is using the ATA subsystem. */
	current_task->status = ZLOX_TS_ATA_WAIT;
	ata_wait_task = current_task;
	/* Wait for IRQ that says the data is ready. */
	zlox_switch_task();
	/* Read actual size */
	size = (((ZLOX_SINT32) zlox_inb (ZLOX_ATA_ADDRESS3 (bus))) << 8) | 
		(ZLOX_SINT32) (zlox_inb (ZLOX_ATA_ADDRESS2 (bus)));
	/* This example code only supports the case where the data transfer
	* of one sector is done in one step. */
	ZLOX_ASSERT (size == ZLOX_ATAPI_SECTOR_SIZE);
	/* Read data. */
	zlox_insw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *)buffer, size / 2);
	/* Wait for BSY and DRQ to clear, indicating Command Finished */
	while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x88)
		asm volatile ("pause");
end:
	ata_wait_task = ZLOX_NULL;
	return size;
}


    同样的,我们来看下zlox_atapi_drive_read_sector函数的大致过程:

    1,先选择需要操作的主从设备(通过向上面提到过的设备选择寄存器中写入值来进行设置)。

    2,选择设备后,需要等待400ns,让其完成选择操作,可以通过读取4次控制寄存器来实现400ns的等待。

    3,通过Features register(功能寄存器)设置PIO模式,数据传输有两种模式,一种是DMA即Direct Memory Access(存储器直接访问)模式,DMA模式无需处理器干涉,会直接在目标内存与硬盘或光盘之类的存储设备之间传输数据,PIO即Programming Input/Output Model(可编程的输入输出模式),该模式下,当光盘之类的设备准备好数据后,会通过中断告诉CPU数据准备好了,然后,处理器就可以通过读取数据端口来将数据给读取出来。

    Features register(功能寄存器)是以参数的形式,为下面的PACKET包命令准备参数的,所以该寄存器的具体设置方式需要参考下面的第5步。

    4,通过向Cylinder Low register与Cylinder High register这两个寄存器写入数据,来为下面的PACKET包命令准备参数,表示需要读取的数据的字节值,具体设置方式参考下一步。

    5, 向Command register(命令寄存器)发送PACKET包命令,命令代码为0xA0 ,可以参考ATA-ATAPI-5.pdf文档的第144页:


图10

    上图显示,在发送0xA0的命令之前,需要先在Device/Head即设备选择寄存器里选择需要操作的主从设备,这个在第1步和第2步里已经完成了,接着,需要将Features register(功能寄存器)的位0(即上图的DMA位)设置为所需的操作模式,当DMA位为1时,则表示使用DMA传输模式,当DMA位为0时,则表示使用PIO模式,另外,位1(即上图中的OVL位)表示该PACKET包命令是否是overlapped(异步的,并发的),这里设置为0,表示非并发的。

    还需要设置Byte count low(Cylinder Low)和Byte count high(Cylinder high)两个寄存器的值为PACKET包命令完整的执行完后(包括后面包命令的字节数组也执行完时),需要从数据寄存器里读取出来的数据的总字节数,Cylinder Low寄存器里存储字节数的低8位,Cylinder high则存储高8位,由于目前我们一次只读取一个扇区的数据,所以,字节数就被设置为了光盘的一个扇区的大小(即2048字节):

/* Select drive (only the slave-bit is set) */
zlox_outb (ZLOX_ATA_DRIVE_SELECT (bus),(drive & (1 << 4)));
ZLOX_ATA_SELECT_DELAY (bus);       /* 400ns delay */
zlox_outb (ZLOX_ATA_FEATURES (bus),0x0);       /* PIO mode */
zlox_outb (ZLOX_ATA_ADDRESS2 (bus), (ZLOX_ATAPI_SECTOR_SIZE & 0xFF));
zlox_outb (ZLOX_ATA_ADDRESS3 (bus), (ZLOX_ATAPI_SECTOR_SIZE >> 8));
zlox_outb (ZLOX_ATA_COMMAND (bus), 0xA0);       /* ATA PACKET command */


    上面代码片段中,ZLOX_ATA_ADDRESS2宏对应Cylinder Low寄存器,ZLOX_ATA_ADDRESS3宏对应Cylinder high寄存器。

    6,发送PACKET包命令后,我们就需要通过循环读取状态寄存器,来判断包命令是否被正常执行:

while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x80)     /* BUSY */
	asm volatile ("pause");
while (!((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x8) && !(status & 0x1))
	asm volatile ("pause");
/* DRQ or ERROR set */
if (status & 0x1) {
	size = -1;
	goto end;
}


    第一个while循环用于判断状态寄存器的BSY位是否清零(上面我们介绍过状态寄存器里各标志位的含义),当BSY位清零时,则表示命令执行完毕。在循环检测的过程中,使用了一个pause的汇编指令,该指令可以延时后面指令的执行,从而产生了一个短暂的等待效果。

    当BSY位被清零后,第二个while循环用于判断状态寄存器的DRQ位是否被设置为1,当DRQ位被设置为1时,表示可以进行下一步的数据寄存器的写入或读取操作了。

    7,写入包命令的字节数组,包命令的特点是,具体的读写指令和读写指令相关的参数等,都是以字节数组的形式写入到数据寄存器里的:

/* Use the ATAPI protocol to read a single sector from the given
 * ide_index into the buffer using logical block address lba. */
ZLOX_SINT32 zlox_atapi_drive_read_sector (ZLOX_UINT32 ide_index, ZLOX_UINT32 lba, ZLOX_UINT8 *buffer)
{
	/* 0xA8 is READ SECTORS command byte. */
	ZLOX_UINT8 read_cmd[12] = { 0xA8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	..........................................
	..........................................
	read_cmd[9] = 1;              	/* 1 sector */
	read_cmd[2] = (lba >> 0x18) & 0xFF;   /* most sig. byte of LBA */
	read_cmd[3] = (lba >> 0x10) & 0xFF;
	read_cmd[4] = (lba >> 0x08) & 0xFF;
	read_cmd[5] = (lba >> 0x00) & 0xFF;   /* least sig. byte of LBA */
	/* Send ATAPI/SCSI command */
	zlox_outsw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *) read_cmd, 6);
	..........................................
	..........................................
}


    上面的read_cmd是与READ SECTORS读扇区相关的包字节数组,该字节数组中的第一个字节即0xA8是READ SECTORS读扇区的命令代码,剩下的是读扇区相关的参数,ATAPI包字节数组可用的命令代码如下:


图11

    可以看到,我们需要的0xA8对应为READ(12),即包字节数组的大小为12个字节,上图位于sourceforge的INF-8070.PDF文档的第23页。

    READ(12)包字节数组中各个字节的含义如下图所示:
 

图12

    上图位于INF-8070.PDF文档的第44页,字节6到字节9为需要传输的扇区个数,字节6存储个数的最高位部分,而字节9则存储个数的最低位部分。

    字节2到字节5存储需要访问的逻辑块地址,逻辑地址0表示第一块扇区,逻辑地址1表示第二块扇区,以此类推,同样的,字节2对应LBA(逻辑块地址)的最高位部分,字节5对应LBA的最低位部分。

    在准备好包的字节数组后,就可以将这12个字节以每次写入一个字(两个字节)的方式,分6次写入数据寄存器中。

    8,在发送包字节数组后,设备就会去准备数据,当准备好时,会以中断的形式通知处理器,所以,接下来,就需要将当前任务切换到ATA_WAIT的等待状态:

/* Tell the scheduler that this process is using the ATA subsystem. */
current_task->status = ZLOX_TS_ATA_WAIT;
ata_wait_task = current_task;
/* Wait for IRQ that says the data is ready. */
zlox_switch_task();


    9,在被中断唤醒后,我们就可以从数据寄存器里将指定的逻辑扇区中的数据给读取出来:

/* Read actual size */
size = (((ZLOX_SINT32) zlox_inb (ZLOX_ATA_ADDRESS3 (bus))) << 8) | 
	(ZLOX_SINT32) (zlox_inb (ZLOX_ATA_ADDRESS2 (bus)));
/* This example code only supports the case where the data transfer
* of one sector is done in one step. */
ZLOX_ASSERT (size == ZLOX_ATAPI_SECTOR_SIZE);
/* Read data. */
zlox_insw (ZLOX_ATA_DATA (bus), (ZLOX_UINT16 *)buffer, size / 2);
/* Wait for BSY and DRQ to clear, indicating Command Finished */
while ((status = zlox_inb (ZLOX_ATA_COMMAND (bus))) & 0x88)
	asm volatile ("pause");
end:
ata_wait_task = ZLOX_NULL;
return size;


    以上就是ATAPI读取扇区数据的过程,另外,在zlox_ata.c文件中,还有一个zlox_atapi_drive_read_capacity函数,该函数其实是发送命令代码为0x25的包字节数组,该包字节数组的完整结构可以参考INF-8070.PDF的第45页。

    在zenglOX v1.1.0的命令行下,可以通过ata -r命令来读取出光盘里的数据:


图13

    这和我用vim编辑器查看到的zenglOX.iso在0x800偏移处的值是一致的:
 

图14

    最后,v1.1.0的版本还添加了idle_cpu的系统调用,通过hlt指令让CPU空闲下来,在bochs上面可以达到三四百兆的IPS,之前的版本只有几M的IPS ,提高了虚拟机中的响应速度。

    当然,ata工具还只能用在老式的PATA即并行数据线的磁盘设备上,如果是SATA,使用ata工具,会全部显示为no drive。

    ATA相关的代码参考自以下链接:
    限于篇幅,本篇就到这里,休息,休息一下 o(∩_∩)o~~
 
上下篇

下一篇: zenglOX v1.2.0 ISO 9660文件系统

上一篇: zenglOX v1.0.0 shell(命令行程式及各种小工具)

相关文章

zenglOX v1.2.0 ISO 9660文件系统

zenglOX v3.0.0与v3.0.1 GUI窗口界面

zenglOX v0.0.6 使用Heap(堆)动态分配和释放内存

zenglOX v2.3.0 移植zengl嵌入式脚本语言

zenglOX v0.0.2 VGA输出显示字符串

zenglOX v0.0.4 IRQ(中断请求)与PIT(可编程间隔定时器)