| enable_disable_a20: |
| |
| ################################################################### |
| # Thanks to `A20 - a pain from the past' from url: |
| # http://www.win.tue.nl/~aeb/linux/kbd/A20.html |
| ################################################################### |
| |
| ################################################################### |
| # Thanks to Chris Giese <geezer@execpc.com> for this code: |
| # http://my.execpc.com/CE/AC/geezer/osd/boot/a20.asm |
| ################################################################### |
| |
| ################################################################### |
| # input: DL=0 disable a20 |
| # DL=non-zero enable a20 |
| # DH=0 don't reset keyboard controller |
| # DH=non-zero reset keyboard controller |
| # CX=loops to try when failure |
| # |
| # output: ZF=0 failed |
| # ZF=1 completed ok. If ZF=CF=1, then |
| # the A20 status needn't change and |
| # was not touched. |
| # EAX modified |
| # CX modified |
| ################################################################### |
| #if 1 |
| pushfw /* save old IF */ |
| |
| /* oh, in case we are in real mode, disable interrupt since we will |
| * touch the int0 vector. |
| */ |
| |
| cli |
| |
| /* disable CPU cache for the test to work reliably. */ |
| movl %cr0, %eax |
| pushl %eax /* save old cr0 */ |
| // andl $0x00000011, %eax |
| orl $0x60000000, %eax /* set CD and NW */ |
| movl %eax, %cr0 |
| movl %cr0, %eax |
| testl $0x60000000, %eax /* check if we can use wbinvd. */ |
| jz 1f /* CPU has no wbinvd instruction. */ |
| wbinvd |
| andl $0xDFFFFFFF, %eax /* clear NW */ |
| movl %eax, %cr0 |
| 1: |
| #endif |
| 3: |
| # First, see if the A20 status is already what we desired. |
| |
| call a20_test_match |
| /* ZF=1(means equal) for desired and we needn't do anything. */ |
| stc /* ZF=CF=1 indicates nothing needs to be done. */ |
| jz 3f |
| //jnz 3f |
| |
| # Try ELAN before the BIOS call |
| |
| #if defined(CONFIG_X86_ELAN) || 1 |
| |
| #if 0 // bad! this would hurt some PCs like the DELL machines |
| testb %dl, %dl |
| setnz %al |
| shlb $1, %al |
| //movb $0x02, %al # alternate A20 gate |
| outb %al, $0x92 # this works on SC410/SC520 |
| |
| call a20_test_loop |
| jz 3f |
| |
| #else // hopefully this wouldn't conflict with other PCs |
| |
| # Try another ELAN method: Use "inb 0xee" to control A20. |
| # H. Peter Anvin and Christer Weinigel talked about this at(googled): |
| # http://www.ussg.iu.edu/hypermail/linux/kernel/0201.0/0128.html |
| |
| # Alternate Gate A20 Control Register (Port 00EEh) A special 8-bit |
| # read/write control register provides a fast and reliable way to |
| # control the CPU A20 signal. A dummy read of this register returns |
| # a value of FFh and forces the CPU A20 to propagate to the core |
| # logic, while a dummy write to this register will cause the CPU A20 |
| # signal to be forced Low as long as no other A20 gate control |
| # sources are forcing the CPU A20 signal to propagate. |
| #if 1 |
| testb %dl, %dl |
| jz 1f |
| inb $0xEE, %al |
| jmp 2f |
| 1: |
| xorb %al, %al |
| outb %al, $0xEE |
| 2: |
| call a20_test_loop |
| jz 3f |
| //jnz 3f |
| |
| #endif |
| #endif |
| #endif |
| |
| # Try the BIOS call(INT 0x15, AX=0x240x) |
| |
| testb %dl, %dl |
| setnz %al |
| movb $0x24, %ah |
| pushfl # Be paranoid about flags |
| pushal |
| int $0x15 |
| popal |
| popfl |
| |
| call a20_test_match |
| jz 3f |
| //jnz 3f |
| |
| # Try the keyboard controller |
| |
| call empty_8042 # clear output buffer and wait until |
| # input buffer is empty |
| |
| call a20_test_match # Just in case the BIOS worked |
| jz 3f # but had a delayed reaction. |
| |
| pushfw |
| |
| cli |
| |
| # first, get the keyboard controller output status |
| |
| movb $0xD0, %al # 8042 command byte to read output port |
| outb %al, $0x64 # write command to port 64h |
| call delay |
| |
| pushw %cx |
| |
| xorw %cx, %cx # try 65536 times |
| 1: |
| inb $0x64, %al # get 8042 status |
| testb $1, %al # output buffer (data _from_ keyboard) full? |
| loopz 1b # no, loop |
| |
| popw %cx |
| |
| # wait at least 7 microseconds for MCA type 1 controller |
| |
| call delay |
| call delay |
| call delay |
| call delay |
| call delay |
| call delay |
| call delay |
| |
| # safe to read data |
| |
| inb $0x60, %al # read output port |
| andb $0xFD, %al # clear bit 1 |
| testb %dl, %dl |
| setne %ah |
| shlb $1, %ah |
| orb %al, %ah |
| |
| call empty_8042 |
| |
| movb $0xD1, %al # 8042 command byte to write output port |
| outb %al, $0x64 # write command to port 64h |
| call delay |
| call empty_8042 |
| |
| movb %ah, %al # the value to write: 0xDF for a20 on, |
| # and 0xDD for a20 off. |
| outb %al, $0x60 |
| |
| popfw |
| |
| #if 0 |
| call empty_8042 |
| |
| /* output a dummy command (USB keyboard hack) */ |
| movb $0xff, %al |
| outb %al, $0x64 |
| call delay |
| #endif |
| |
| # Wait until a20 really *is* enabled; it can take a fair amount of |
| # time on certain systems; Toshiba Tecras are known to have this |
| # problem. |
| |
| call empty_8042 |
| |
| call a20_test_loop |
| jz 3f |
| |
| # Try the shortcut HP Vectra method |
| |
| pushfw |
| |
| cli |
| |
| # 0xDD is for disable and 0xDF is for enable |
| |
| testb %dl, %dl |
| setne %al |
| shlb $1, %al |
| orb $0xDD, %al |
| outb %al, $0x64 |
| |
| popfw |
| |
| call empty_8042 |
| |
| call a20_test_loop |
| jz 3f |
| |
| # Final attempt: use "configuration port A" |
| |
| inb $0x92, %al # Configuration Port A |
| testb %dl, %dl |
| jz 2f # we want to disable a20 |
| testb $0x02, %al |
| jnz 1f # chipset bug: do nothing if already set |
| orb $0x02, %al # "fast A20" version |
| andb $0xFE, %al # don't accidentally reset the cpu |
| jmp 0f |
| 2: |
| testb $0x02, %al |
| jz 1f # chipset bug: do nothing if already cleared |
| andb $0xFC, %al # don't accidentally reset the cpu |
| 0: |
| outb %al, $0x92 |
| 1: |
| # Wait for configuration port A to take effect |
| |
| call a20_test_loop |
| jz 3f |
| |
| # A20 is still not responding. Try frobbing it again. |
| |
| decw %cx |
| jnz 3b |
| |
| # right now CX=0, ZF=1 |
| |
| decw %cx # just let ZF=0 |
| |
| # failure, ZF=0 at this point |
| |
| 3: |
| |
| #if 1 |
| pushfw |
| |
| cli |
| |
| call empty_8042 |
| |
| #if 0 |
| testb %dh, %dh |
| jz 1f |
| |
| # try to activate usb keyboard by disable and enable keyboard |
| |
| /* 8042 self test */ |
| movb $0xAA, %al |
| outb %al, $0x64 |
| call delay |
| |
| call empty_8042 |
| |
| /* disable keyboard */ |
| movb $0xAD, %al |
| outb %al, $0x64 |
| call delay |
| |
| call empty_8042 |
| |
| /* enable keyboard */ |
| movb $0xAE, %al |
| outb %al, $0x64 |
| call delay |
| |
| call empty_8042 |
| |
| /* write command byte */ |
| movb $0x60, %al |
| outb %al, $0x64 |
| call delay |
| |
| call empty_8042 |
| |
| movb $0x45, %al # enable IRQ1, turn on keyboard |
| outb %al, $0x60 |
| |
| call empty_8042 |
| |
| movb $0xF4, %al # enable keyboard |
| outb %al, $0x60 |
| |
| call empty_8042 |
| |
| movb $0xFF, %al # restart keyboard |
| outb %al, $0x60 |
| |
| call empty_8042 |
| |
| 1: |
| #endif |
| |
| /* output a dummy command (USB keyboard hack) */ |
| /* it may also be a keyboard-reset command according to |
| * http://www.phys.uu.nl/~0307467/docs/keyboa~2.txt |
| * where it says: |
| * 0FFh Reset the keyboard and start internal diagnostics |
| */ |
| movb $0xff, %al |
| outb %al, $0x64 |
| call delay |
| |
| call empty_8042 |
| //1: |
| popfw |
| #endif |
| |
| #if 1 |
| popl %eax /* restore cr0 */ |
| movl %eax, %cr0 |
| |
| lahf /* Load Flags into AH Register. */ |
| /* AH = SF:ZF:xx:AF:xx:PF:xx:CF */ |
| |
| popfw /* restore IF */ |
| sahf /* update ZF */ |
| #endif |
| ret |
| |
| a20_test_loop: |
| pushw %cx |
| xorw %cx, %cx |
| 1: |
| call a20_test_match |
| jz 2f |
| loop 1b |
| 2: |
| /* ZF=1(means equal) for match */ |
| popw %cx |
| ret |
| |
| |
| a20_test_match: |
| call a20_test |
| sete %al /* save ZF to AL */ |
| testb %dl, %dl |
| sete %ah /* save ZF to AH */ |
| cmpb %al, %ah |
| /* ZF=1(means equal) for match */ |
| ret |
| |
| |
| # This routine tests whether or not A20 is enabled. If so, it |
| # exits with zf = 0. |
| # |
| |
| a20_test: |
| pushl %eax |
| pushw %ds |
| pushw %es |
| xorw %ax, %ax |
| movw %ax, %ds /* DS=0 */ |
| decw %ax |
| movw %ax, %es /* ES=0xFFFF */ |
| |
| movl 0, %eax |
| pushl %eax /* save old int0 vector */ |
| |
| cmpl %es:0x10, %eax |
| jne 1f /* A20 is on */ |
| |
| notl 0 |
| movl 0, %eax |
| cmpl %es:0x10, %eax |
| notl 0 /* logical `NOT' won't touch flags */ |
| 1: |
| /* ZF=0(means not equal) for A20 on, ZF=1(means equal) for A20 off. */ |
| |
| popl %eax /* restore int0 vector */ |
| movl %eax, 0 |
| popw %es |
| popw %ds |
| |
| popl %eax |
| ret |
| |
| # This routine checks that the keyboard command queue is empty |
| # (after emptying the output buffers) |
| # |
| # Some machines have delusions that the keyboard buffer is always full |
| # with no keyboard attached... |
| # |
| # If there is no keyboard controller, we will usually get 0xff |
| # to all the reads. With each IO taking a microsecond and |
| # a timeout of 100,000 iterations, this can take about half a |
| # second ("delay" == outb to port 0x80). That should be ok, |
| # and should also be plenty of time for a real keyboard controller |
| # to empty. |
| # |
| |
| empty_8042: |
| pushl %ecx |
| //xorw %cx, %cx |
| movl $100000, %ecx # use 1000 instead of 100000 iterations |
| |
| 3: |
| //call delay |
| |
| inb $0x64, %al # read 8042 status from port 64h |
| testb $1, %al # is output buffer(data _from_ keyboard) full? |
| jnz 1f # yes, read it and discard |
| testb $2, %al # is input buffer(data _to_ keyboard) empty? |
| jnz 2f # no, wait until time out |
| jmp 3f # both input buffer and output buffer are empty |
| # success and return |
| 1: |
| //call delay |
| inb $0x60, %al # read output buffer and discard data/status from 8042 |
| 2: |
| ADDR32 loop 3b |
| |
| # timed out and failure, return |
| 3: |
| popl %ecx |
| ret |
| |
| # Delay is needed after doing I/O |
| delay: |
| // outb %al, $0x80 |
| pushw %cx |
| movw $0x0800, %cx #; 0x2000 |
| 1: |
| loop 1b |
| popw %cx |
| ret |
| |