九、 直接读取SATA硬盘之(二)、PCI设备枚举、32位端口的访问、找到AHCI控制器的内存空间地址
(1)PCI设备枚举
SATA AHCI控制器是一个挂在PCI总线上的设备,通过对PCI设备枚举,可以找到这个设备,然后得到它的配置空间信息。
方法是直接对IO端口号CF8H、CFCH进行访问。原理是“总线号、设备号、功能号”这三者确定了一个“PCI功能设备”,其实是读这个设备的00H号寄存器,字节偏移地址是00处,这里有Device ID和Vendor ID,通过判断这个ID是否正常,来判断是否存在一个PCI设备。
很多文章有介绍,比如:《dos下枚举 pci设备》(http://blog.csdn.net/zyl910/article/details/858349)
问难的难点是:CF8H、CFCH端口是32位的,即访问它们的数据应是32位(4字节)的,而DOS下的汇编、TC都只有8位、16位端口访问。
解决方法有2个:
一个是在程序中插入汇编,采用32位指令前缀66H,这样就能执行32位指令。可参考上面的文章,以及《DEBUG下使用32位指令的技术》。
另一个是采用PCI BIOS中断,中断号1AH。可参见上面的文章。这里采用这种方法,用WORD方式来访问PCI配置空间。
INT 1A - PCI BIOS v2.0c+ - READ CONFIGURATION WORD
AX = B109h
BH = bus number
BL = device/function number (bits 7-3 device, bits 2-0 function)
DI = register number (0000h-00FFh, must be multiple of 2) (see #00878) ――偏移量(以字节计)
Return:
CF clear if successful
CX = word read ―― 一次读出2个字节,即1个WORD
CF set on error
AH = status (00h,87h) (see #00729)
EAX, EBX, ECX, and EDX may be modified
all other flags (except IF) may be modified
(2)程序
从上面的文章中的程序改的,findpci.c:
/*
File: epcib.c
Name: 使用PCI BIOS来枚举PCI设备
Author: zyl910
Blog: http://blog.csdn.net/zyl910/
Version: V1.0
Updata: 2006-6-30
*/
#include <stdio.h>
#include <conio.h>
#include <dos.h>
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;
/* PCI设备索引。bus/dev/func 共16位,为了方便处理可放在一个WORD中 */
#define PDI_BUS_SHIFT 8
#define PDI_BUS_SIZE 8
#define PDI_BUS_MAX 0xFF
#define PDI_BUS_MASK 0xFF00
#define PDI_DEVICE_SHIFT 3
#define PDI_DEVICE_SIZE 5
#define PDI_DEVICE_MAX 0x1F
#define PDI_DEVICE_MASK 0x00F8
#define PDI_FUNCTION_SHIFT 0
#define PDI_FUNCTION_SIZE 3
#define PDI_FUNCTION_MAX 0x7
#define PDI_FUNCTION_MASK 0x0007
#define MK_PDI(bus,dev,func) (WORD)((bus&PDI_BUS_MAX)<<PDI_BUS_SHIFT | (dev&PDI_DEVICE_MAX)<<PDI_DEVICE_SHIFT |(func&PDI_FUNCTION_MAX) )
int main(void)
{
int bus, dev, func;
int i;
union REGS regs;
WORD wAddr;
FILE *hF;
char szFile[0x10];
printf("\n");
printf("Bus#\tDevice#\tFunc#\tVendor\tDevice\tClass\tIRQ\tIntPin\n");
/* 枚举PCI设备 */
for(bus = 0; bus <= PDI_BUS_MAX; ++bus) // BUS编号00~FFH,256个
{
for(dev = 0; dev <= PDI_DEVICE_MAX; ++dev) // 设备编号00~1FH,32个
{
for(func = 0; func <= PDI_FUNCTION_MAX; ++func) //功能编号00~07H,8个
{
/* 计算地址 */
wAddr = MK_PDI(bus, dev, func); //转换成16BIT地址
/* 获取厂商ID */
regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
regs.x.bx = wAddr; // BH = bus number
// BL = device/function number (bits 7-3 device, bits 2-0 function)
regs.x.di = 0; // DI=register number=00H,即取出“PCI Configuration Data”的00H处的值,00H处的值是 Vendor ID
regs.x.cx = 0xFFFF; // 非法的Vendor ID ,也可以不写这句
int86(0x1A, ®s, ®s); //返回 CX = word read,如果是非法Vendor ID,则会返回CX=0xFFFF
/* 判断设备是否存在。FFFFh是非法厂商ID */
if (regs.x.cx != 0xFFFF) //如果Vendor ID合法
{
/* bus/dev/func */
printf("%2.2XH\t%2.2XH\t%1XH\t", bus, dev, func);
/* Vendor */
printf("%4.4XH\t", regs.x.cx);
/* Device */
regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
regs.x.bx = wAddr;
regs.x.di = 2; // Device ID 。DI=register number=02H,即取出“PCI Configuration Data”的02H处的值,02H处的值是 Device ID
int86(0x1A, ®s, ®s);
printf("%4.4XH\t", regs.x.cx);
/* Class Code */
regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
regs.x.bx = wAddr;
regs.x.di = 0xA; // 取出Class/SubClass 值
int86(0x1A, ®s, ®s);
printf("%4.4XH\t", regs.x.cx);
/* IRQ/intPin */
regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
regs.x.bx = wAddr;
regs.x.di = 0x3C; // 取出IRQ/IntPin 值
int86(0x1A, ®s, ®s);
printf("%d\t", (BYTE)regs.x.cx);
printf("%d", (BYTE)(regs.x.cx>>8));
printf("\n");
/* 写文件 */
sprintf(szFile, "PCI%2.2X%2.2X%X.bin", bus, dev, func);
hF = fopen(szFile, "wb"); //打开文件
if (hF != NULL)
{
/* 256字节的PCI配置空间 */
for (i = 0; i < 0x100; i += 2)
{
/* Read */
regs.x.ax = 0xB109; // PCI BIOS v2.0c+ - READ CONFIGURATION WORD
regs.x.bx = wAddr;
regs.x.di = i;
int86(0x1A, ®s, ®s);
/* Write */
fwrite(®s.x.cx, 2, 1, hF);
}
fclose(hF);
}
}
}
}
}
return 0;
}
(3)DOS下运行得到的结果:
Bus# Device# Func# Vendor Device Class IRQ IntPin
00H 1FH 2H 8086H 1E03H 0106H 10 2 即INTEL7 SATA AHCI CONTROLLER
得到的“设备配置空间”的内容:
8680031E0700B0020401060100000000
B1F00000A1F0000091F0000081F00000
61F00000006090F7000000004310AC10
0000000080000000000000000A020000
00800080000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
01A80340080000000000000000000000
05700000000000000000000000000000
603A05858301003A08425C0100000000
E00000003900000012B0100048000000
13000603000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
0000000000000000870F040800000000
(其余为00)
分析如下:
“设备配置空间”数据的0x10偏移处:
B1 F0 00 00 第0BIT的“1”表示映射到I/O空间,即这是IO地址,0xF0B0
A1 F0 00 00
91 F0 00 00
81 F0 00 00
61 F0 00 00
00 60 90 F7 第0BIT的“0”则表示映射到内存空间,即这是内存地址F7906000H,这个就是AHCI控制器的内存空间地址!!
在win7的设备管理器上,看SATA AHCI控制器资源是:
IO范围
F0B0-F0B7, ―― 这几个是端口地址,如果在BIOS中设置成“IDE兼容”模式,应该就可以像端口1F0H~1F7H那样用,没试过。
F0A0-F0A3, ―― 这几个是端口地址,如果在BIOS中设置成“IDE兼容”模式,应该就可以像端口3F6H~3F7H那样用,没试过。
F090-F097,
F080-F083,
F060-F07F
内存 00000000F7906000 - 00000000F79067FF ――内存空间地址也是F7906000H
IRQ 0x13
访问这个内存空间F7906000H,就是对AHCI控制器进行控制。
难题来了,这个内存地址是高于1MB的,DOS下如何访问???
文章评论(0条评论)
登录后参与讨论