Page 1 of 1

APM Protected Mode Interface

Posted: Sun Apr 27, 2008 9:41 am
by sevobal
Hi,
reading the the APM 1.2 specification I found out that there ist a function to initialisize a protected mode interface for APM.
4.6.4 APM Protected Mode 32-bit Interface Connect (03H)
This call initializes the 32-bit protected mode interface between the APM Driver
(caller) and the APM BIOS. This interface allows a protected mode APM Driver to
invoke the APM BIOS functions without the need to first switch into real or virtual-86
mode. This function must be invoked in real mode.
Call With

AH = 53H APM
AL = 03H Protected mode 32-bit interface connect
BX = Power device ID
0000H APM BIOS

All other values are reserved

Returns
If function successful:
Carry = 0
AX = PM 32-bit code segment (real mode segment base address)
EBX = Offset of the entry point into the APM BIOS
CX = APM 16-bit code segment (real mode segment base address)
DX = APM data segment (real mode segment base address)
ESI = APM BIOS 32-bit code segment length (low word of ESI)
APM BIOS 16-bit code
segment length (high word of ESI)
DI = APM BIOS data segment length

If function unsuccessful:
Carry = 1
AH = Error code
02H Real mode interface connection already established
05H 16-bit protected mode interface connection already
established
07H 32-bit protected mode interface connection already
established
08H 32-bit protected mode interface not supported
09H Unrecognized device ID
86H APM not present
So I called this function in the real mode part of my kernel and stored the returns of this function in some variables:

Code: Select all

    mov ah, 0x53
    mov al, 0x03
    mov bx, 0x00
    int 15h
    jne next_twelve
    
    mov si, kernelE_msg
    call print
    mov byte [apm_pm], 0x01
    mov word [apm_32_code_segment], ax
    mov dword [apm_offset_entry_point], ebx
    mov word [apm_16_code_segment], cx
    mov word [apm_data_segment], dx
    mov word [apm_32_code_length], si
    mov word [apm_16_code_length], esi
    mov word [apm_data_length], di
And here a the three GDT entries I'll fillup later with values:

Code: Select all

APM_32_CODE_SEL	equ	$-gdt
gdt6:
  dw 0xFFFF ; Limit wird zur Laufzeit eingetragen
	dw 0			; (base gets set above)
	db 0
	db 0x92		; present, ring 0, data, expand-up, writable
  db 0      ; page-granular, 16-bit
	db 0
	
APM_16_CODE_SEL	equ	$-gdt
gdt7:
  dw 0xFFFF
	dw 0			; (base gets set above)
	db 0
	db 0x92		; present, ring 0, data, expand-up, writable
	db 0			; byte-granular, 16-bit
	db 0
	
APM_DATA_SEL	equ	$-gdt
gdt8:
  dw 0xFFFF ; Limit wird zu Laufzeit eingetragen
	dw 0			; (base gets set above)
	db 0
	db 0x92		; present, ring 0, data, expand-up, writable
  db 0      ; page-granular, 16-bit
	db 0
Now, let's fill them:

Code: Select all

    ; Write segment length
    mov word dx, [apm_32_code_length]
    sub dx, 0x01
    mov word [gdt6], dx
    mov word ax, [apm_16_code_length]
    sub dx, 0x01
    mov word [gdt7], dx
    mov word ax, [apm_data_length]
    sub dx, 0x01
    mov word [gdt8], dx
    
    ;Write segment base address
    mov word dx, [apm_32_code_segment]
    mov word [gdt6 + 2], dx
    mov word dx, [apm_16_code_segment]
    mov word [gdt7 + 2], dx
    mov word dx, [apm_data_segment]
    mov word [gdt8 + 2], dx
Now the documentation tell me how to use the APM protected mode interface:
The APM BIOS 32-bit protected mode interface requires 3 consecutive
selector/segment descriptors for use as 32-bit code, 16-bit code, and data segments,
respectively. Both 32-bit and 16-bit code segment descriptors are necessary so the
APM BIOS 32-bit interface can call other BIOS routines in a 16-bit code segment if
necessary. The caller must initialize these descriptors using the segment base and
length information returned from this call to the APM BIOS. These selectors may
either be in the GDT or LDT, but must be valid when the APM BIOS is called in
protected mode. The code segment descriptors must specify protection level 0, and
the APM BIOS routines must be invoked with CPL = 0 so they can execute privileged
instructions such as port inputs and outputs.
The three segment lengths returned in DI and the high/low words of ESI must contain
the lengths of their respective segments in bytes. The protected mode segment
descriptors created for these segments will have a limit that is 1 less than the length.
For example, a segment with length 0800H will be given a segment limit of 07FFH,
allowing access to offsets 0 to 07FFH within the segment.
The APM caller invokes the APM BIOS routines using the 32-bit interface by making
a far call to the 32-bit code segment selector it initializes, with the offset returned in
EBX from this call. The caller must supply a stack large enough for use by the APM
BIOS and potential interrupt handlers. The caller's stack will be active if or when
interrupts are enabled in the APM BIOS routines; the BIOS will not switch stacks
when interrupts are enabled, including NMI interrupts. The APM BIOS 32-bit
protected mode interface must be called with a 32-bit stack.
When the APM BIOS routines are called in protected mode, the current I/O
permission bit map must allow access to the I/O ports the BIOS may need to access in
the process of performing the selected function.
I've tried this one directly out of my kernel:

Code: Select all

                        mov ax, 0x28
		mov ds, ax
		
		mov ax,530eh
	            xor bx,bx
	            mov cx,0102h
		call [ds:edi]
		
		mov ax,530fh
		mov bx,0001h
		mov cx,0001h
		call [ds:edi]

		mov ax,5308h
		mov bx,0001h
		mov cx,0001h
		call [ds:edi]

		mov ax,5307h
		mov bx,0001h
		mov cx,0003h
		call [ds:edi]
It doesn't work, but I don't know why. As my assembler I'm using NASM.
Thank for every help I can get.

Posted: Sun Apr 27, 2008 2:22 pm
by bewing
Well, you aren't testing the carry flag in your posted code ... do you know for a fact that the function is really supported on your system? And why ARE you testing the zero flag? Other than that, your code looks OK on first glance.

Posted: Mon Apr 28, 2008 2:15 am
by sevobal
okay, now I've checked the Carry Flag and found out, that my PC supports this function. But I'm still ending up in a protection fault when calling my shutdown function and I don't know what I'm doing wrong...

Posted: Mon Apr 28, 2008 9:54 am
by bewing
And edi is being set to [apm_offset_entry_point] when you make your APM calls -- and you've verified that the value matches? And you've checked that [apm_offset_entry_point] is less than [apm_32_code_length]?

And are you sure that GDT6 is accessed by segment 0x28? I don't see how it could be. I'd guess 0x30 would be the right value.