;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2007-2008. All rights reserved. ;; ;; Distributed under terms of the GNU General Public License ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; $Revision$ ; Virtual-8086 mode manager ; diamond, 2007, 2008 DEBUG_SHOW_IO = 0 struc V86_machine { ; page directory .pagedir dd ? ; translation table: V86 address -> flat linear address .pages dd ? ; mutex to protect all data from writing by multiple threads at one time .mutex dd ? ; i/o permission map .iopm dd ? .size = $ } virtual at 0 V86_machine V86_machine end virtual ; Create V86 machine ; in: nothing ; out: eax = handle (pointer to struc V86_machine) ; eax = NULL => failure ; destroys: ebx, ecx, edx (due to malloc) v86_create: ; allocate V86_machine structure mov eax, V86_machine.size call malloc test eax, eax jz .fail ; initialize mutex and dword [eax+V86_machine.mutex], 0 ; allocate tables mov ebx, eax ; We allocate 4 pages. ; First is main page directory for V86 mode. ; Second page: ; first half (0x800 bytes) is page table for addresses 0 - 0x100000, ; second half is for V86-to-linear translation. ; Third and fourth are for I/O permission map. push 8000h ; blocks less than 8 pages are discontinuous call kernel_alloc test eax, eax jz .fail2 mov [ebx+V86_machine.pagedir], eax push edi eax mov edi, eax add eax, 1800h mov [ebx+V86_machine.pages], eax ; initialize tables mov ecx, 2000h/4 xor eax, eax rep stosd mov [ebx+V86_machine.iopm], edi dec eax mov ecx, 2000h/4 rep stosd pop eax ; page directory: first entry is page table... mov edi, eax add eax, 1000h push eax call get_pg_addr or al, PG_UW stosd ; ...and also copy system page tables ; thx to Serge, system is located at high addresses add edi, (OS_BASE shr 20) - 4 push esi mov esi, (OS_BASE shr 20) + sys_pgdir mov ecx, 0x80000000 shr 22 rep movsd mov eax, [ebx+V86_machine.pagedir] ;root dir also is call get_pg_addr ;used as page table or al, PG_SW mov [edi-4096+(page_tabs shr 20)], eax pop esi ; now V86 specific: initialize known addresses in first Mb pop eax edi ; first page - BIOS data (shared between all machines!) ; physical address = 0x2f0000 ; linear address = BOOT_VAR = OS_BASE + 0x2f0000 mov dword [eax], (BOOT_VAR - OS_BASE) or 111b mov dword [eax+800h], BOOT_VAR ; page before 0xA0000 - Extended BIOS Data Area (shared between all machines!) ; physical address = 0x9F000 ; linear address = 0x8009F000 mov dword [eax+0x9E*4], 0x9E000 or 111b mov dword [eax+0x9E*4+800h], 0x9E000 + OS_BASE mov dword [eax+0x9F*4], 0x9F000 or 111b mov dword [eax+0x9F*4+800h], 0x9F000 + OS_BASE ; addresses 0xC0000 - 0xFFFFF - BIOS code (shared between all machines!) ; physical address = 0xC0000 ; linear address = 0x800C0000 mov ecx, 0xC0 @@: mov edx, ecx shl edx, 12 push edx or edx, 101b mov [eax+ecx*4], edx pop edx add edx, OS_BASE mov [eax+ecx*4+0x800], edx inc cl jnz @b mov eax, ebx ret .fail2: mov eax, ebx call free .fail: xor eax, eax ret ; Destroy V86 machine ; in: eax = handle ; out: nothing ; destroys: eax, ebx, ecx, edx (due to free) v86_destroy: push eax stdcall kernel_free, [eax+V86_machine.pagedir] pop eax jmp free ; Translate V86-address to linear address ; in: eax=V86 address ; esi=handle ; out: eax=linear address ; destroys: nothing v86_get_lin_addr: push ecx edx mov ecx, eax mov edx, [esi+V86_machine.pages] shr ecx, 12 and eax, 0xFFF add eax, [edx+ecx*4] ; atomic operation, no mutex needed pop edx ecx ret ; Sets linear address for V86-page ; in: eax=linear address (must be page-aligned) ; ecx=V86 page (NOT address!) ; esi=handle ; out: nothing ; destroys: nothing v86_set_page: push eax ebx mov ebx, [esi+V86_machine.pagedir] mov [ebx+ecx*4+0x1800], eax call get_pg_addr or al, 111b mov [ebx+ecx*4+0x1000], eax pop ebx eax ret ; Allocate memory in V86 machine ; in: eax=size (in bytes) ; esi=handle ; out: eax=V86 address, para-aligned (0x10 multiple) ; destroys: nothing ; ������ᠭ�!!! ;v86_alloc: ; push ebx ecx edx edi ; lea ebx, [esi+V86_machine.mutex] ; call wait_mutex ; add eax, 0x1F ; shr eax, 4 ; mov ebx, 0x1000 ; start with address 0x1000 (second page) ; mov edi, [esi+V86_machine.tables] ;.l: ; mov ecx, ebx ; shr ecx, 12 ; mov edx, [edi+0x1000+ecx*4] ; get linear address ; test edx, edx ; page allocated? ; jz .unalloc ; mov ecx, ebx ; and ecx, 0xFFF ; add edx, ecx ; cmp dword [edx], 0 ; free block? ; jnz .n ; cmp dword [edx+4], ; and [esi+V86_machine.mutex], 0 ; pop edi edx ecx ebx ; ret uglobal sys_v86_machine dd ? endg ; Called from kernel.asm at first stages of loading ; Initialize system V86 machine (used to simulate BIOS int 13h) init_sys_v86: call v86_create mov [sys_v86_machine], eax test eax, eax jz .ret mov byte [BOOT_VAR + 0x500], 0xCD mov byte [BOOT_VAR + 0x501], 0x13 mov byte [BOOT_VAR + 0x502], 0xF4 mov byte [BOOT_VAR + 0x503], 0xCD mov byte [BOOT_VAR + 0x504], 0x10 mov byte [BOOT_VAR + 0x505], 0xF4 mov esi, eax mov ebx, [eax+V86_machine.pagedir] mov dword [ebx+0x9B*4+0x1000], 0x9B000 or 111b mov dword [ebx+0x9B*4+0x1800], OS_BASE + 0x9B000 mov dword [ebx+0x9C*4+0x1000], 0x9C000 or 111b mov dword [ebx+0x9C*4+0x1800], OS_BASE + 0x9C000 mov dword [ebx+0x9D*4+0x1000], 0x9D000 or 111b mov dword [ebx+0x9D*4+0x1800], OS_BASE + 0x9D000 if ~DEBUG_SHOW_IO ; allow access to all ports mov ecx, [esi+V86_machine.iopm] xor eax, eax mov edi, ecx mov ecx, 10000h/8/4 rep stosd end if .ret: ret struc v86_regs { ; don't change the order, it is important .edi dd ? .esi dd ? .ebp dd ? dd ? ; ignored .ebx dd ? .edx dd ? .ecx dd ? .eax dd ? .eip dd ? .cs dd ? .eflags dd ? ; VM flag must be set! .esp dd ? .ss dd ? .es dd ? .ds dd ? .fs dd ? .gs dd ? .size = $ } virtual at 0 v86_regs v86_regs end virtual ; Run V86 machine ; in: ebx -> registers for V86 (two structures: in and out) ; esi = handle ; ecx = expected end address (CS:IP) ; edx = IRQ to hook or -1 if not required ; out: structure pointed to by ebx is filled with new values ; eax = 1 - exception has occured, cl contains code ; eax = 2 - access to disabled i/o port, ecx contains port address ; eax = 3 - IRQ is already hooked by another VM ; destroys: nothing v86_start: pushad cli mov ecx, [CURRENT_TASK] shl ecx, 8 add ecx, SLOT_BASE mov eax, [esi+V86_machine.iopm] call get_pg_addr inc eax push dword [ecx+APPDATA.io_map] push dword [ecx+APPDATA.io_map+4] mov dword [ecx+APPDATA.io_map], eax mov dword [page_tabs + (tss._io_map_0 shr 10)], eax add eax, 0x1000 mov dword [ecx+APPDATA.io_map+4], eax mov dword [page_tabs + (tss._io_map_1 shr 10)], eax push [ecx+APPDATA.dir_table] push [ecx+APPDATA.saved_esp0] mov [ecx+APPDATA.saved_esp0], esp mov [tss._esp0], esp mov eax, [esi+V86_machine.pagedir] call get_pg_addr mov [ecx+APPDATA.dir_table], eax mov cr3, eax ; mov [irq_tab+5*4], my05 ; We do not enable interrupts, because V86 IRQ redirector assumes that ; machine is running ; They will be enabled by IRET. ; sti mov eax, esi sub esp, v86_regs.size mov esi, ebx mov edi, esp mov ecx, v86_regs.size/4 rep movsd cmp edx, -1 jz .noirqhook uglobal v86_irqhooks rd 16*2 endg cmp [v86_irqhooks+edx*8], 0 jz @f cmp [v86_irqhooks+edx*8], eax jz @f mov esi, v86_irqerr call sys_msg_board_str inc [v86_irqhooks+edx*8+4] mov eax, 3 jmp v86_exc_c.exit @@: mov [v86_irqhooks+edx*8], eax inc [v86_irqhooks+edx*8+4] .noirqhook: popad iretd ; It is only possible to leave virtual-8086 mode by faulting to ; a protected-mode interrupt handler (typically the general-protection ; exception handler, which in turn calls the virtual 8086-mode monitor). v86_debug_exc: pushad xor eax, eax mov dr6, eax mov bl, 1 jmp v86_exc_c v86_page_fault: add esp, 4 pushad mov bl, 14 jmp v86_exc_c v86_except_16: pushad mov bl, 16 jmp v86_exc_c v86_except_19: pushad mov bl, 19 iglobal v86_exc_str1 db 'V86 : unexpected exception ',0 v86_exc_str2 db ' at ',0 v86_exc_str3 db ':',0 v86_exc_str4 db 13,10,'V86 : faulted code:',0 v86_exc_str5 db ' (unavailable)',0 v86_newline db 13,10,0 v86_io_str1 db 'V86 : access to disabled i/o port ',0 v86_io_byte db ' (byte)',13,10,0 v86_io_word db ' (word)',13,10,0 v86_io_dword db ' (dword)',13,10,0 v86_irqerr db 'V86 : IRQ already hooked',13,10,0 endg v86_exc_c: mov ax, app_data mov ds, ax mov es, ax ; Did we all that we have wanted to do? mov eax, [esp+v86_regs.size+10h+18h] cmp word [esp+v86_regs.eip], ax jnz @f shr eax, 16 cmp word [esp+v86_regs.cs], ax jz .done @@: ; Various system events, which must be handled, result in #GP cmp bl, 13 jnz .nogp ; If faulted EIP exceeds 0xFFFF, we have #GP and it is an error cmp word [esp+v86_regs.eip+2], 0 jnz .nogp ; Otherwise we can safely access byte at CS:IP ; (because it is #GP, not #PF handler) ; � �� �� ����� �嫮����� �᪫�祭�� ⮫쪮 ��-�� �⥭�� ���⮢ ����, ; �� �� ��� 㦥 �嫮��⠫� � �� �뫮 �� �� #GP movzx esi, word [esp+v86_regs.cs] shl esi, 4 add esi, [esp+v86_regs.eip] lodsb cmp al, 0xCD ; int xx command = CD xx jz .handle_int cmp al, 0xCF jz .handle_iret cmp al, 0xF3 jz .handle_rep cmp al, 0xEC jz .handle_in cmp al, 0xED jz .handle_in_word cmp al, 0xEE jz .handle_out cmp al, 0xEF jz .handle_out_word cmp al, 0xE4 jz .handle_in_imm cmp al, 0xE6 jz .handle_out_imm cmp al, 0x9C jz .handle_pushf cmp al, 0x9D jz .handle_popf cmp al, 0xFA jz .handle_cli cmp al, 0xFB jz .handle_sti cmp al, 0x66 jz .handle_66 jmp .nogp .handle_int: cmp word [esp+v86_regs.eip], 0xFFFF jae .nogp xor eax, eax lodsb ; call sys_msg_board_byte ; simulate INT command ; N.B. It is possible that some checks need to be corrected, ; but at least in case of normal execution the code works. .simulate_int: cmp word [esp+v86_regs.esp], 6 jae @f mov bl, 12 ; #SS exception jmp .nogp @@: movzx edx, word [esp+v86_regs.ss] shl edx, 4 push eax movzx eax, word [esp+4+v86_regs.esp] sub eax, 6 add edx, eax mov eax, edx mov esi, [esp+4+v86_regs.size+10h+4] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 ; #PF exception jmp .nogp @@: lea eax, [edx+5] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 ; #PF exception jmp .nogp @@: sub word [esp+4+v86_regs.esp], 6 mov eax, [esp+4+v86_regs.eip] inc eax inc eax mov word [edx], ax mov eax, [esp+4+v86_regs.cs] mov word [edx+2], ax mov eax, [esp+4+v86_regs.eflags] mov word [edx+4], ax pop eax mov cx, [eax*4] mov word [esp+v86_regs.eip], cx mov cx, [eax*4+2] mov word [esp+v86_regs.cs], cx ; note that interrupts will be disabled globally at IRET and byte [esp+v86_regs.eflags+1], not 3 ; clear IF and TF flags ; continue V86 execution popad iretd .handle_iret: cmp word [esp+v86_regs.esp], 0x10000 - 6 jbe @f mov bl, 12 jmp .nogp @@: movzx edx, word [esp+v86_regs.ss] shl edx, 4 movzx eax, word [esp+v86_regs.esp] add edx, eax mov eax, edx mov esi, [esp+v86_regs.size+10h+4] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 jmp .nogp @@: lea eax, [edx+5] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 jmp .nogp @@: mov ax, [edx] mov word [esp+v86_regs.eip], ax mov ax, [edx+2] mov word [esp+v86_regs.cs], ax mov ax, [edx+4] mov word [esp+v86_regs.eflags], ax add word [esp+v86_regs.esp], 6 popad iretd .handle_pushf: cmp word [esp+v86_regs.esp], 1 jnz @f mov bl, 12 jmp .nogp @@: movzx edx, word [esp+v86_regs.ss] shl edx, 4 mov eax, [esp+v86_regs.esp] sub eax, 2 movzx eax, ax add edx, eax mov eax, edx mov esi, [esp+v86_regs.size+10h+4] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 ; #PF exception jmp .nogp @@: lea eax, [edx+1] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 jmp .nogp @@: sub word [esp+v86_regs.esp], 2 mov eax, [esp+v86_regs.eflags] mov [edx], ax inc word [esp+v86_regs.eip] popad iretd .handle_pushfd: cmp word [esp+v86_regs.esp], 4 jae @f mov bl, 12 ; #SS exception jmp .nogp @@: movzx edx, word [esp+v86_regs.ss] shl edx, 4 movzx eax, word [esp+v86_regs.esp] sub eax, 4 add edx, eax mov eax, edx mov esi, [esp+v86_regs.size+10h+4] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 ; #PF exception jmp .nogp @@: lea eax, [edx+3] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 ; #PF exception jmp .nogp @@: sub word [esp+v86_regs.esp], 4 movzx eax, word [esp+v86_regs.eflags] mov [edx], eax add word [esp+v86_regs.eip], 2 popad iretd .handle_popf: cmp word [esp+v86_regs.esp], 0xFFFF jnz @f mov bl, 12 jmp .nogp @@: movzx edx, word [esp+v86_regs.ss] shl edx, 4 movzx eax, word [esp+v86_regs.esp] add edx, eax mov eax, edx mov esi, [esp+v86_regs.size+10h+4] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 ; #PF exception jmp .nogp @@: lea eax, [edx+1] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 jmp .nogp @@: mov ax, [edx] mov word [esp+v86_regs.eflags], ax add word [esp+v86_regs.esp], 2 inc word [esp+v86_regs.eip] popad iretd .handle_popfd: cmp word [esp+v86_regs.esp], 0x10000 - 4 jbe @f mov bl, 12 jmp .nogp @@: movzx edx, word [esp+v86_regs.ss] shl edx, 4 movzx eax, word [esp+v86_regs.esp] add edx, eax mov eax, edx mov esi, [esp+v86_regs.size+10h+4] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 jmp .nogp @@: lea eax, [edx+3] call v86_get_lin_addr cmp eax, 0x1000 jae @f mov bl, 14 jmp .nogp @@: mov eax, [edx] mov word [esp+v86_regs.eflags], ax add word [esp+v86_regs.esp], 4 add word [esp+v86_regs.eip], 2 popad iretd .handle_cli: and byte [esp+v86_regs.eflags+1], not 2 inc word [esp+v86_regs.eip] popad iretd .handle_sti: or byte [esp+v86_regs.eflags+1], 2 inc word [esp+v86_regs.eip] popad iretd .handle_rep: cmp word [esp+v86_regs.eip], 0xFFFF jae .nogp lodsb cmp al, 6Eh jz .handle_rep_outsb jmp .nogp .handle_rep_outsb: .handle_in: .handle_out: .invalid_io_byte: movzx ebx, word [esp+v86_regs.edx] mov ecx, 1 jmp .invalid_io .handle_in_imm: .handle_out_imm: cmp word [esp+v86_regs.eip], 0xFFFF jae .nogp lodsb movzx ebx, al mov ecx, 1 jmp .invalid_io .handle_66: cmp word [esp+v86_regs.eip], 0xFFFF jae .nogp lodsb cmp al, 0x9C jz .handle_pushfd cmp al, 0x9D jz .handle_popfd cmp al, 0xEF jz .handle_out_dword cmp al, 0xED jz .handle_in_dword jmp .nogp .handle_in_word: .handle_out_word: movzx ebx, word [esp+v86_regs.edx] mov ecx, 2 jmp .invalid_io .handle_in_dword: .handle_out_dword: .invalid_io_dword: movzx ebx, word [esp+v86_regs.edx] mov ecx, 4 .invalid_io: mov esi, v86_io_str1 call sys_msg_board_str mov eax, ebx call sys_msg_board_dword mov esi, v86_io_byte cmp ecx, 1 jz @f mov esi, v86_io_word cmp ecx, 2 jz @f mov esi, v86_io_dword @@: call sys_msg_board_str if DEBUG_SHOW_IO mov edx, ebx mov ebx, 200 call delay_hs mov esi, [esp+v86_regs.size+10h+4] mov eax, [esi+V86_machine.iopm] @@: btr [eax], edx inc edx loop @b popad iretd else mov eax, 2 jmp .exit end if .nogp: mov esi, v86_exc_str1 call sys_msg_board_str mov al, bl call sys_msg_board_byte mov esi, v86_exc_str2 call sys_msg_board_str mov ax, [esp+32+4] call sys_msg_board_word mov esi, v86_exc_str3 call sys_msg_board_str mov ax, [esp+32] call sys_msg_board_word mov esi, v86_exc_str4 call sys_msg_board_str mov ecx, 8 movzx edx, word [esp+32+4] shl edx, 4 add edx, [esp+32] @@: mov esi, [esp+v86_regs.size+10h+4] mov eax, edx call v86_get_lin_addr cmp eax, 0x1000 jb .nopage mov esi, v86_exc_str3-2 call sys_msg_board_str mov al, [edx] call sys_msg_board_byte inc edx loop @b jmp @f .nopage: mov esi, v86_exc_str5 call sys_msg_board_str @@: mov esi, v86_newline call sys_msg_board_str mov eax, 1 jmp .exit .done: xor eax, eax .exit: mov [esp+v86_regs.size+10h+1Ch], eax mov [esp+v86_regs.size+10h+18h], ebx mov edx, [esp+v86_regs.size+10h+14h] cmp edx, -1 jz @f dec [v86_irqhooks+edx*8+4] jnz @f and [v86_irqhooks+edx*8], 0 @@: mov esi, esp mov edi, [esi+v86_regs.size+10h+10h] add edi, v86_regs.size mov ecx, v86_regs.size/4 rep movsd mov esp, esi cli mov ecx, [CURRENT_TASK] shl ecx, 8 pop eax mov [SLOT_BASE+ecx+APPDATA.saved_esp0], eax mov [tss._esp0], eax pop eax mov [SLOT_BASE+ecx+APPDATA.dir_table], eax pop ebx mov dword [SLOT_BASE+ecx+APPDATA.io_map+4], ebx mov dword [page_tabs + (tss._io_map_1 shr 10)], ebx pop ebx mov dword [SLOT_BASE+ecx+APPDATA.io_map], ebx mov dword [page_tabs + (tss._io_map_0 shr 10)], ebx mov cr3, eax ; mov [irq_tab+5*4], 0 sti popad ret ;my05: ; mov dx, 30C2h ; mov cx, 4 ;.0: ; in al, dx ; cmp al, 0FFh ; jz @f ; test al, 4 ; jnz .1 ;@@: ; add dx, 8 ; in al, dx ; cmp al, 0FFh ; jz @f ; test al, 4 ; jnz .1 ;@@: ; loop .0 ; ret ;.1: ; or al, 84h ; out dx, al ;.2: ; mov dx, 30F7h ; in al, dx ; mov byte [BOOT_VAR + 48Eh], 0FFh ; ret v86_irq: ; push eax/pushad/jmp v86_irq ; eax = irq mov ebx, eax lea esi, [esp+18h] lea edi, [esi+4] mov ecx, 7 std rep movsd cld mov edi, ebx v86_irq2: ; pushad/call v86_irq2 ; edi = irq pop eax mov esi, [v86_irqhooks+edi*8] ; get VM handle mov eax, [esi+V86_machine.pagedir] call get_pg_addr mov ecx, [CURRENT_TASK] shl ecx, 8 cmp [SLOT_BASE+ecx+APPDATA.dir_table], eax jnz .notcurrent lea eax, [edi+8] cmp al, 10h jb @f add al, 60h @@: jmp v86_exc_c.simulate_int .notcurrent: mov ebx, SLOT_BASE + 0x100 mov ecx, [TASK_COUNT] .scan: cmp [ebx+APPDATA.dir_table], eax jnz .cont push ecx mov ecx, [ebx+APPDATA.saved_esp0] cmp word [ecx-v86_regs.size+v86_regs.esp], 6 jb .cont2 movzx edx, word [ecx-v86_regs.size+v86_regs.ss] shl edx, 4 push eax movzx eax, word [ecx-v86_regs.size+v86_regs.esp] sub eax, 6 add edx, eax mov eax, edx call v86_get_lin_addr cmp eax, 0x1000 jb .cont3 lea eax, [edx+5] call v86_get_lin_addr cmp eax, 0x1000 jb .cont3 pop eax pop ecx jmp .found .cont3: pop eax .cont2: pop ecx .cont: loop .scan mov al, 20h out 20h, al cmp edi, 8 jb @f out 0A0h, al @@: popad iretd .found: mov cr3, eax sub word [esi-v86_regs.size+v86_regs.esp], 6 mov ecx, [esi-v86_regs.size+v86_regs.eip] mov word [edx], cx mov ecx, [esi-v86_regs.size+v86_regs.cs] mov word [edx+2], cx mov ecx, [esi-v86_regs.size+v86_regs.eflags] mov word [edx+4], cx lea eax, [edi+8] cmp al, 10h jb @f add al, 60h @@: mov cx, [eax*4] mov word [esi-v86_regs.size+v86_regs.eip], cx mov cx, [eax*4+2] mov word [esi-v86_regs.size+v86_regs.cs], cx and byte [esi-v86_regs.size+v86_regs.eflags+1], not 3 push ebx call update_counters pop ebx sub ebx, SLOT_BASE shr ebx, 8 mov esi, [CURRENT_TASK] call do_change_task popad iretd