因为显卡bios中的指令是x86的,因此龙芯执行显卡bios中的程序初始化显卡采用的集成一个x86模拟器,解释运行的方法。
有两种模拟,一种是模拟int10(从freex86移植过来的),一种是直接模拟.
当模拟int10时
INCLUDES= -I. -I${S}/include -I./machine -I${S} /
-I${S}/x86emu/int10/x86emu/include -I${S}/x86emu/int10/x86emu/src/x86emu/x86emu/ /
-I${S}/sys/arch/${MACHINE}/include -I${S}/sys /
-I${TARGET} -I${COMPILEDIR} -nostdinc
否则
INCLUDES= -I. -I${S}/include -I./machine -I${S} /
-I${S}/x86emu/src/x86emu -I${S}/x86emu/src /
-I${S}/sys/arch/${MACHINE}/include -I${S}/sys /
-I${TARGET} -I${COMPILEDIR} -nostdinc
X86模拟器包括几个部分:
内存
系统中断向量,0地址模拟
Sys bios模拟
显存地址0xa0000模拟
显卡bios地址0xc0000模拟
寄存器
Cpu 寄存器的模拟
指令模拟
vga_bios_init的流程
1)分配xf86Int10InfoPtr
xf86Int10InfoPtr用来存储模拟环境
typedef struct {
int entityIndex;
int scrnIndex;
pointer cpuRegs;
CARD16 BIOSseg;
CARD16 inb40time;
pointer private;
struct _int10Mem* mem;
int num;
int ax;
int bx;
int cx;
int dx;
int si;
int di;
int es;
int bp;
int flags;
int stackseg;
struct pci_device *pdev;
} xf86Int10InfoRec, *xf86Int10InfoPtr;
2)xf86Int10ExecSetup函数设置
X86EMU_setupMemFuncs
X86EMU_setupPioFuncs
X86EMU_setupIntrFuncs
pInt->cpuRegs = &M;
M.mem_base = 0;
M.mem_size = 1024*1024 + 1024;
for (i=0;i<256;i++)
intFuncs[i] = x86emu_do_int;
X86EMU_setupIntrFuncs(intFuncs);
void X86EMU_setupIntrFuncs(
X86EMU_intrFuncs funcs[])
{
int i;
for (i=0; i < 256; i++)
_X86EMU_intrTab[i] = NULL;
if (funcs) {
for (i = 0; i < 256; i++)
_X86EMU_intrTab[i] = funcs[i];
}
}
void x86emuOp_int_IMM(u8 X86EMU_UNUSED(op1))
{
u8 intnum;
START_OF_INSTR();
DECODE_PRINTF("INT/t");
intnum = fetch_byte_imm();
DECODE_PRINTF2("%x/n", intnum);
TRACE_AND_STEP();
if (_X86EMU_intrTab[intnum]) {
(*_X86EMU_intrTab[intnum])(intnum);
} else {
push_word((u16)M.x86.R_FLG);
CLEAR_FLAG(F_IF);
CLEAR_FLAG(F_TF);
push_word(M.x86.R_CS);
M.x86.R_CS = mem_access_word(intnum * 4 + 2);
push_word(M.x86.R_IP);
M.x86.R_IP = mem_access_word(intnum * 4);
}
DECODE_CLEAR_SEGOVR();
END_OF_INSTR();
}
3)几个关键的内存变量的分配
typedef struct {
int shift;
int entries;
void* base;
void* vRam;
void* sysMem;
char* alloc;
} genericInt10Priv;
这几个变量就是pInt-> private->base,vRam,sysMem。
#define INTPriv(x) ((genericInt10Priv*)x->private)
# define vgaram_base 0xb00a0000
pInt->mem = &genericMem;
pInt->private = (pointer)calloc(1,sizeof(genericInt10Priv));
pInt->scrnIndex = 0; /* screen */
base = INTPriv(pInt)->base = 0x80000000+memorysize-0x100000;
/*
* we need to map video RAM MMIO as some chipsets map mmio
* registers into this range.
*/
INTPriv(pInt)->vRam=vgaram_base;
if (!sysMem) {
sysMem = malloc(BIOS_SIZE);
setup_system_bios(sysMem);
}
INTPriv(pInt)->sysMem = sysMem;
vbiosMem = (char *)base + V_BIOS;
这三个变量的作用
INTPriv(pInt)->base设置在内存的顶端,0xC0000以上区域用来存放显卡的rom程序。
vgaram_base是显卡的Framebuffer的地址。
Bios在扫描pci设备的时候建立了设备链,每个桥上的pci设备用单向链表parent->bridge.child链接起来,而设备上的内存被连接到&pd->parent->bridge.memspace,io被链接到&pd->parent->bridge.memspace。
在后面调用_pci_setup_windows分配pci设备内存的时候,沿dev->bridge.memspace和dev->bridge.iospace分配内存、io并脱链。也就是说dev->bridge.memspace和dev->bridge.iospace记录的是期待分配内存的pci内存窗口,当分配内存后就从dev->bridge.memspace和dev->bridge.iospace中脱链。
我们重点关心的是跟显卡相关的内存分配。
for (pm = dev->bridge.memspace; pm != NULL; pm = next) {
pd = pm->device;
if (PCI_ISCLASS(pd->pa.pa_class, PCI_CLASS_DISPLAY, PCI_SUBCLASS_DISPLAY_VGA))
vga_dev = pd;
#if 0
/*
* If this is the first VGA card we find, set the BIOS rom
* at address c0000 if PCI base address is 0x00000000.
*/
if (pm->reg == PCI_MAPREG_ROM && !have_vga &&
dev->bridge.secbus->minpcimemaddr == 0 &&
(PCI_ISCLASS(pd->pa.pa_class,
PCI_CLASS_PREHISTORIC, PCI_SUBCLASS_PREHISTORIC_VGA) ||
PCI_ISCLASS(pd->pa.pa_class,
PCI_CLASS_DISPLAY, PCI_SUBCLASS_DISPLAY_VGA))) {
have_vga = pd->pa.pa_tag;
pm->address = 0x000c0000; /* XXX PCI MEM @ 0x000!!! */
}
#endif
if (pm->reg == PCI_MAPREG_ROM) {
/* expansion rom */
if (_pciverbose >= 2)
_pci_tagprintf (pd->pa.pa_tag, "exp @%p, %d bytes/n",
pm->address, pm->size);
_pci_conf_write(pd->pa.pa_tag, pm->reg, pm->address | PCI_MAPREG_TYPE_ROM);
}
next = pm->next;
dev->bridge.memspace = next;
pfree(pm);
}
可见只是设置了全局变量vga_dev,并没有其他的特别操作。ROM在x86分配的地址为0xc0000,在mips中没有特别的要求。
显卡模拟下面的代码将显卡程序rom中的内容拷贝到vbiosMem中,以后解析运行的程序都从vbiosMem中读取。
#define V_RAM 0xA0000
#define VRAM_SIZE 0x20000
#define SYS_SIZE 0x100000
#define SYS_BIOS 0xF0000
#define VRAM(addr) ((addr >= V_RAM) && (addr < (V_RAM + VRAM_SIZE)))
#define V_ADDR_WW(addr,val) /
if(VRAM(addr)) /
MMIO_OUT16((CARD16*)VRAM_BASE,VRAM_ADDR(addr),val); /
else /
stw_u(val,(pointer)(V_ADDR(addr)));
#define SYS(addr) ((addr) >= SYS_BIOS)
#define V_ADDR(addr) /
(SYS(addr) ? ((char*)INTPriv(pInt)->sysMem) + (addr - SYS_BIOS) /
: ((char*)(INTPriv(pInt)->base) + addr))
从上面的定义我们可以看出几块内存的影射关系。
4)初始化工作
vga_bios_init是整个初始化的开始,在tgt_devconfig处被调用。
模拟过程中定义了好多的变量和函数。
在windows中采用debug命令
Debug
-u f000:f065
F000:F065 CF IRET
在bios的模拟中不可避免的要采用中断模拟,这是因为bios采用bios软中断来调用bios的系统调用。
要模拟和显卡相关的bios的系统调用。
Xf86int10.c 因为bios video的中断号为0x10,因此这个文件就是用来模拟显卡的bios的。
Vga_bios_init>setup_int_vect>:
void
setup_int_vect(xf86Int10InfoPtr pInt)
{
int i;
printf("in setup_int_vect!");
/* let the int vects point to the SYS_BIOS seg */
for (i = 0; i < 0x80; i++) {
MEM_WW(pInt, i << 2, 0);
MEM_WW(pInt, (i << 2) + 2, SYS_BIOS >> 4);
}
reset_int_vect(pInt);
setup_int_vect
设置中断向量 8086实模式下的中断向量的地址在0地址处,每个中断占四个字节。
setup_int_vect将所有中断向量的地址指向0xF0000:0,即0xF0000。
0x1000
设置中断向量0x1d,0x10,0x42,0x6d。
0x10指向VideoParms
其他指向0xff065
/*
* This table is normally located at 0xF000:0xF0A4. However, int 0x42,
* function 0 (Mode Set) expects it (or a copy) somewhere in the bottom
* 64kB. Note that because this data doesn't survive POST, int 0x42 should
* only be used during EGA/VGA BIOS initialisation.
*/
Debug
-d F000:F0A4
在windows中采用debug命令
Debug
-u f000:f065
F000:F065 CF IRET
Vga_bios_init>setup_int_vect>:
/* font tables default location (int 1F) */
MEM_WW(pInt,0x1f<<2,0xfa6e);
/* int 11 default location (Get Equipment Configuration) */
MEM_WW(pInt, 0x11 << 2, 0xf84d);
/* int 12 default location (Get Conventional Memory Size) */
MEM_WW(pInt, 0x12 << 2, 0xf841);
/* int 15 default location (I/O System Extensions) */
MEM_WW(pInt, 0x15 << 2, 0xf859);
/* int 1A default location (RTC, PCI and others) */
MEM_WW(pInt, 0x1a << 2, 0xff6e);
/* int 05 default location (Bound Exceeded) */
MEM_WW(pInt, 0x05 << 2, 0xff54);
/* int 08 default location (Double Fault) */
MEM_WW(pInt, 0x08 << 2, 0xfea5);
/* int 13 default location (Disk) */
MEM_WW(pInt, 0x13 << 2, 0xec59);
/* int 0E default location (Page Fault) */
MEM_WW(pInt, 0x0e << 2, 0xef57);
/* int 17 default location (Parallel Port) */
MEM_WW(pInt, 0x17 << 2, 0xefd2);
/* fdd table default location (int 1e) */
MEM_WW(pInt, 0x1e << 2, 0xefc7);
MEM_WB(pInt, 0x0410, 0x67);
MEM_WB(pInt, 0x0411, 0x42);
printf("done!");
/* XXX Perhaps setup more of the BDA here. See also int42(0x00). */
}
40:10处的含义:
10h WORD Installed hardware:
bits 15-14: number of parallel devices
bit 13: [Conv] Internal modem
bit 12: reserved
bits 11- 9: number of serial devices
bit 8: reserved
bits 7- 6: number of diskette drives minus one
bits 5- 4: Initial video mode:
00b = EGA,VGA,PGA
01b = 40 x 25 color
10b = 80 x 25 color
11b = 80 x 25 mono
bit 3: reserved
bit 2: [PS] =1 if pointing device
[non-PS] reserved
bit 1: =1 if math co-processor
bit 0: =1 if diskette available for boot
vga_bios_init>:
printf("set return trap/n");
set_return_trap(pInt);
vga_bios_init> set_return_trap>:
void
set_return_trap(xf86Int10InfoPtr pInt)
{
/*
* Here we set the exit condition: We return when we encounter
* 'hlt' (=0xf4), which we locate at address 0x600 in x86 memory.
*/
MEM_WB(pInt, 0x0600, 0xf4);
/*
* Allocate a segment for the stack
*/
pInt->stackseg=0x1000;
}
define vgaram_base 0xb00a0000
INTPriv(pInt)->vRam=vgaram_base;
int
setup_system_bios(void *base_addr)
{
char *base = (char *) base_addr;
/*
* we trap the "industry standard entry points" to the BIOS
* and all other locations by filling them with "hlt"
* TODO: implement hlt-handler for these
*/
memset(base, 0xf4, 0x10000);
/* int11 handler: push ds; push 0040; pop ds; mov ax,[0010]; pop ds; iret16
*/
*(base + 0xf84d + 0) = 0x1e;
*(base + 0xf84d + 1) = 0x68;
*(base + 0xf84d + 2) = 0x40;
*(base + 0xf84d + 3) = 0x00;
*(base + 0xf84d + 4) = 0x1f;
*(base + 0xf84d + 5) = 0xa1;
*(base + 0xf84d + 6) = 0x10;
*(base + 0xf84d + 7) = 0x00;
*(base + 0xf84d + 8) = 0x1f;
*(base + 0xf84d + 9) = 0xcf;
/* set bios date */
strcpy(base + 0x0FFF5, "06/11/99");
/* set up eisa ident string */
strcpy(base + 0x0FFD9, "PCI_ISA");
/* write system model id for IBM-AT */
*((unsigned char *)(base + 0x0FFFE)) = 0xfc;
return 1;
}
base = INTPriv(pInt)->base = 0x80000000+memorysize-0x100000;
#define V_BIOS 0xC0000
vbiosMem = (char *)base + V_BIOS;
// zhb if (PCI_VENDOR(pdev->pa.pa_id) == 0x1002 && pdev->pa.pa_device == 0x4750)
if (PCI_VENDOR(pdev->pa.pa_id) == 0x1002 && PCI_PRODUCT(pdev->pa.pa_id) == 0x4750) {
printf("yes/n");
MEM_WW(pInt,0xc015e,0x4750);
}
#define V_RAM 0xA0000
#define VRAM_SIZE 0x20000
#define SYS_SIZE 0x100000
#define SYS_BIOS 0xF0000
#define VRAM(addr) ((addr >= V_RAM) && (addr < (V_RAM + VRAM_SIZE)))
#define V_ADDR_WW(addr,val) /
if(VRAM(addr)) /
MMIO_OUT16((CARD16*)VRAM_BASE,VRAM_ADDR(addr),val); /
else /
stw_u(val,(pointer)(V_ADDR(addr)));
#define SYS(addr) ((addr) >= SYS_BIOS)
#define V_ADDR(addr) /
(SYS(addr) ? ((char*)INTPriv(pInt)->sysMem) + (addr - SYS_BIOS) /
: ((char*)(INTPriv(pInt)->base) + addr))
base = INTPriv(pInt)->base = 0x80000000+memorysize-0x100000;
/*
* we need to map video RAM MMIO as some chipsets map mmio
* registers into this range.
*/
INTPriv(pInt)->vRam=vgaram_base;
if (!sysMem) {
sysMem = malloc(BIOS_SIZE);
setup_system_bios(sysMem);
}
INTPriv(pInt)->sysMem = sysMem;
printf("memorysize=%lx,base=%lx,sysMem=%lx,vram=%lx/n",memorysize,INTPriv(pInt)->base,sysMem,INTPriv(pInt)->vRam);
printf("set up int/n");
setup_int_vect(pInt);
printf("set return trap/n");
set_return_trap(pInt);
vbiosMem = (char *)base + V_BIOS;
….
pInt->BIOSseg = V_BIOS >> 4;
pInt->num = 0xe6;
printf("lock vga/n");
LockLegacyVGA(screen, &vga);
printf("starting bios emu.../n");
//X86EMU_trace_on();
printf("ax=%lx,bx=%lx,cx=%lx,dx=%lx/n",pInt->ax,pInt->bx,pInt->cx,pInt->dx);
xf86ExecX86int10(pInt);
printf("bios emu done/n");
void
xf86ExecX86int10(xf86Int10InfoPtr pInt)
{
int sig = setup_int(pInt);
if (sig < 0)
return;
if (int_handler(pInt)) {
X86EMU_exec();
}
finish_int(pInt, sig);
}
int
setup_int(xf86Int10InfoPtr pInt)
{
if (pInt != Int10Current) {
if (!MapCurrentInt10(pInt))
return -1;
Int10Current = pInt;
}
X86_EAX = (CARD32) pInt->ax;
X86_EBX = (CARD32) pInt->bx;
X86_ECX = (CARD32) pInt->cx;
X86_EDX = (CARD32) pInt->dx;
X86_ESI = (CARD32) pInt->si;
X86_EDI = (CARD32) pInt->di;
X86_EBP = (CARD32) pInt->bp;
X86_ESP = 0x1000; X86_SS = pInt->stackseg >> 4;
X86_EIP = 0x0600; X86_CS = 0x0; /* address of 'hlt' */
X86_DS = 0x40; /* standard pc ds */
X86_ES = pInt->es;
X86_FS = 0;
X86_GS = 0;
X86_EFLAGS = X86_IF_MASK | X86_IOPL_MASK;
return 0;
}
int
int_handler(xf86Int10InfoPtr pInt)
{
int num = pInt->num;
int ret = 0;
printf("int_handler called,int=%x/n",num);
switch (num) {
#ifndef _PC
case 0x10:
case 0x42:
case 0x6D:
if (getIntVect(pInt, num) == I_S_DEFAULT_INT_VECT) {
printf("default int10 called,intno=%x/n",num);
ret = int42_handler(pInt);
}
break;
case 0x15:
ret = int15_handler(pInt);
break;
#endif
case 0x1A:
ret = int1A_handler(pInt);
break;
case 0xe6:
ret = intE6_handler(pInt);
break;
default:
break;
}
if (!ret) {
ret = run_bios_int(num, pInt);
//if(num==0x10) X86EMU_trace_on();
printf("run_bios_int,intno=%x,ret=%x/n",num,ret);
}
if (!ret) {
printf( "Halting on int 0x%2.2x!/n", num);
dump_registers(pInt);
stack_trace(pInt);
}
return ret;
}
/*
* handle initialization
*/
static int
intE6_handler(xf86Int10InfoPtr pInt)
{
struct pci_device *pdev;
if ((pdev = pInt->pdev))
X86_AX = (pdev->pa.pa_bus << 8) | (pdev->pa.pa_device << 3) | (pdev->pa.pa_function & 0x7);
pushw(pInt, X86_CS);
pushw(pInt, X86_IP);
X86_CS = pInt->BIOSseg;
X86_EIP = 0x0003;
X86_ES = 0; /* standard pc es */
return 1;
}
可以看出当handler返回1的时候,表示需要模拟器来模拟。
pushw(pInt, X86_CS);
pushw(pInt, X86_IP);
这个操作来模仿call的执行。
最先执行的代码是0xC000:3
C000:0这个地址应该影射到显卡的bios的rom地址。
PMON终端显示
pmon的输入输出分析:
//pmon/fs/termio.c
static FileSystem termfs =
{
"tty", FS_TTY,
term_open,
term_read,
term_write,
NULL,
term_close,
term_ioctl
};
static void init_fs __P((void)) __attribute__ ((constructor));
static void
init_fs()
{
//SBD_DISPLAY ("DEVI", CHKPNT_DEVI);
SBD_DISPLAY ("TTYI", CHKPNT_DEVI);
devinit ();
/*
* Install terminal based file system.
*/
filefs_init(&termfs);
/*
* Create the standard i/o files the proper way
*/
_file[0].valid = 1;
_file[0].fs = &termfs;
_file[1].valid = 1;
_file[1].fs = &termfs;
_file[2].valid = 1;
_file[2].fs = &termfs;
_file[3].valid = 1;
_file[3].fs = &termfs;
_file[4].valid = 1;
_file[4].fs = &termfs;
term_open(0, "/dev/tty0", 0, 0); /* stdin */
term_open(1, "/dev/tty0", 0, 0); /* stdout */
term_open(2, "/dev/tty0", 0, 0); /* stderr */
term_open(3, "/dev/tty1", 0, 0); /* kbdin */
term_open(4, "/dev/tty1", 0, 0); /* vgaout */
}
Bonito/Bonito/tgt_machdep.c
ConfigEntry ConfigTable[] =
{
{ (char *)COM3_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ },
/* { (char *)COM1_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ },
{ (char *)COM2_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ }, */
#if NMOD_VGACON >0
{ (char *)1, 0, vgaterm, 256, CONS_BAUD, NS16550HZ },
#endif
{ 0 }
};
init_net>tgt_devconfig>kbd_initialize
kbd_available=1
pmon选择是键盘还是串口输入是依靠宏来实现的:
#define stdin (kbd_available?(&_iob[3]):(&_iob[0]))
#define stdout (vga_available?(&_iob[4]):(&_iob[1]))
#define stderr (vga_available?(&_iob[4]):(&_iob[2]))
#define kbdin (&_iob[3])
#define vgaout (&_iob[4])
//----------------------------------------------------------------------------
串口Bonito/include/bonito.h
#define COM1_BASE_ADDR 0xbfd003f8
#define COM2_BASE_ADDR 0xbfd002f8
#define COM3_BASE_ADDR 0xbff003f8
//#define NS16550HZ 1843200
#define NS16550HZ 3686400
用那个串口在汇编中取决于
LEAF(initserial)
# la v0, COM1_BASE_ADDR
la v0, COM3_BASE_ADD
...
END(initserial)
LEAF(tgt_putchar)
# la v0, COM1_BASE_ADDR
la v0, COM3_BASE_ADDR
...
END(tgt_putchar)
Bonito/Bonito/tgt_machdep.c
ConfigEntry ConfigTable[] =
{
{ (char *)COM3_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ },
/* { (char *)COM1_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ },
{ (char *)COM2_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ }, */
#if NMOD_VGACON >0
{ (char *)1, 0, vgaterm, 256, CONS_BAUD, NS16550HZ },
#endif
{ 0 }
};
int
vgaterm (int op, struct DevEntry *dev, unsigned long param, int data)
{
unsigned char code;
switch (op) {
case OP_INIT:
case OP_XBAUD:
case OP_BAUD:
return 0;
case OP_TXRDY:
return 1;
case OP_TX:
if(vga_available)
write_at_cursor(data&0xff);
break;
case OP_RXRDY:
if(kbd_available)
return kbd_code?1:0;
else
return 0;
case OP_RX:
if(kbd_available){
code = kbd_code;
kbd_code = 0;
return code;
}else{
return 0;
}
case OP_RXSTOP:
break;
}
return 0;
}
int write_at_cursor(char val)
{
// int pos_row,pos_col;
unsigned char hi,lo;
unsigned short cursor;
if(val == '/n') {
vga_set_enter();
return 0;
}
if(val == '/b') {
backspace_cursor();
return 0;
}
if(val == '/r') {
return 0;
}
hi=get_crt_reg(index_cursor_pos_hi);
lo=get_crt_reg(index_cursor_pos_lo);
cursor=(hi<<8)|lo;
// printf("cursor hi =0x%x,lo =0x%x,cursor=0x%x /n",hi,lo,cursor);
*(unsigned char *)(vgabh+2*cursor)=val;
*(unsigned char *)(vgabh+2*cursor+1)=0x07;
if (cursor==rowcount*colcount-1) {
vga_roll();
cursor-=colcount;
}
cursor=cursor+1;
set_crt_reg(index_cursor_pos_lo,(cursor&0xff));
set_crt_reg(index_cursor_pos_hi,(cursor>>8));
// printf("write value=0x%x done! cursor=0x%x /n",val,cursor);
return 0;
}
芯片应该只管指令集中每个指令的实现
不应管操作系统如何运行。
1、龙新至少32位,现在流行64位了。因为汉字站双字节:32位,便于处理汉字。
2、扩充启动程序,采取小字库方案,实现纯中文电脑。这个应该是主板的任务
3、加快速度,设计并行方案与并行指令,非时间戳方式。真正意义上的并行