/*********************************************************************** Copyright 2002 Ben Rudiak-Gould. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA, or visit . ***********************************************************************/ #include "scsi-miniport.h" #include "../dvdproxy95/dvdproxy95.h" #include "../include/dvdsynth-device.h" #include "ddk.h" #include "internal.h" // Returns 0 on success, MS-DOS error code on error. int __stdcall OpenFileRO(unsigned* phandle, const char* pathname) { const unsigned flags = RING0_OPENCREATE_FLAG_MM|RING0_OPENCREATE_FLAG_ALLOW_EXISTING|RING0_OPENCREATE_FLAG_NO_INT_24|RING0_OPENCREATE_FLAG_EXTENDED_SIZE|RING0_OPENCREATE_FLAG_DENYNONE|RING0_OPENCREATE_FLAG_READONLY; return Ring0_OpenCreate(flags, pathname, phandle); } //80013040 // Returns 0 on success, MS-DOS error code on error. int __stdcall CreateOrOpenFileRW(unsigned* phandle, const char* pathname, int creation_disposition) { const unsigned flags = (RING0_OPENCREATE_FLAG_MM|RING0_OPENCREATE_FLAG_NO_INT_24|RING0_OPENCREATE_FLAG_EXTENDED_SIZE|RING0_OPENCREATE_FLAG_DENYWRITE|RING0_OPENCREATE_FLAG_READWRITE) + RING0_OPENCREATE_FLAG_ALLOW_EXISTING*(creation_disposition<=1) + RING0_OPENCREATE_FLAG_ALLOW_CREATE*(creation_disposition>=1); return Ring0_OpenCreate(flags, pathname, phandle); } // Returns bytes read on success, negative of MS-DOS error code on error int ReadFile(dvs_file_handle handle, unsigned offset, unsigned offset_high, unsigned count, void* buf) { if (offset_high != 0) return 0; return Ring0_ReadWrite(R0_READFILE, (unsigned)handle, offset, count, buf); } // Returns bytes written on success, negative of MS-DOS error code on error int WriteFile(dvs_file_handle handle, unsigned offset, unsigned offset_high, unsigned count, void* buf) { if (offset_high != 0) return 0; return Ring0_ReadWrite(R0_WRITEFILE, (unsigned)handle, offset, count, buf); } // Returns file size on success, 0 on error unsigned GetFileSize(dvs_file_handle handle, unsigned* size_high) { if (size_high) *size_high = 0; return Ring0_SizeClose(R0_GETFILESIZE, (unsigned)handle); } void CloseFile(dvs_file_handle handle) { Ring0_SizeClose(R0_CLOSEFILE, (unsigned)handle); } __declspec(naked) void* __stdcall MakeThunkCall(void* func, void* args, unsigned arg_bytes) { __asm { push ebp mov ebp,esp mov edx,[ebp+12] /* args */ mov ecx,[ebp+16] /* arg_bytes */ shr ecx,2 jz make_call copy_loop: dec ecx push [edx+ecx*4] jnz copy_loop make_call: call [ebp+8] /* func */ mov esp,ebp pop ebp ret 12 } } /*******************************************************************\ \*******************************************************************/ struct AsyncCallRecord { void (__stdcall*f)(void*, int); void* p; int i; }; struct AsyncCallQueue { unsigned head,tail; enum { queue_size = 16 }; AsyncCallRecord q[queue_size]; unsigned records_lost; void Reset() { tail = head; } void Put(void (__stdcall*f)(void*, int), void* p, int i) { unsigned h = head; // this is safe because VxDs are cooperatively multitasked if (h > tail && q[(h-1u)%queue_size].f == f && q[(h-1u)%queue_size].p == p && q[(h-1u)%queue_size].i == i) { return; } if (h - tail < queue_size) { q[h%queue_size].f = f; q[h%queue_size].p = p; q[h%queue_size].i = i; head = h+1; } else { ++records_lost; } } }; AsyncCallQueue async_call_queue; ULONG notification_hwnd; #define WM_APP_APC 0xA5FC __declspec(naked) void AsyncPostMessage() { __asm { pushad mov esi,offset callback VXD_CALL(1,14) // Schedule_VM_Event popad retn callback: mov eax,[notification_hwnd] test eax,eax jz exit push 0 push 0 push offset async_call_queue push 0 push WM_APP_APC push eax VXD_CALL(23,6) // _SHELL_PostMessage add esp,24 exit: retn } } void __stdcall AsyncUserModeCall(void (__stdcall*f)(void*, int), void* p, int i) { async_call_queue.Put(f,p,i); AsyncPostMessage(); } void __cdecl SetAsyncNotificationWindow(ULONG hwnd) { notification_hwnd = hwnd; async_call_queue.Reset(); } __declspec(naked) void AsyncSetEvent(unsigned event) { __asm { pushad VXD_CALL(1,3) // Get_Sys_VM_Handle (in ebx) mov esi,offset callback mov edx,[esp+36] VXD_CALL(1,17) // Call_VM_Event popad retn callback: mov eax,edx VXD_JUMP(42,14) // _VWIN32_SetWin32Event } } __declspec(naked) void __stdcall CloseEvent(unsigned event) { __asm { pushad mov eax,[esp+36] VXD_CALL(42,20) // _VWIN32_CloseVxDHandle popad retn 4 } } /*******************************************************************\ \*******************************************************************/ void __cdecl MemCpy(void* dst, const void* src, unsigned long count) { ScsiPortMoveMemory(dst, src, count); } /*******************************************************************\ \*******************************************************************/ void CallDriverInitFunc(dvs_driver_handle hmodule) { static DvsDockingBayKernelGlobal callbacks = { MemSet, MemCpy, DebugPrintf, 0, //AsyncUserModeCall, ReadFile, WriteFile, AsyncSetEvent, }; void* function = _PELDR_GetProcAddress(hmodule, "DvdsynthDriverInit"); if (function) { ((void(__cdecl*)(DvsDockingBayKernelGlobal*))function)(&callbacks); } } //__declspec(naked) scsi_result_t ScsiDispatch(DvsDeviceKernel* device, const unsigned char* cdb, int cdblen, unsigned char* buffer, unsigned long* pbuflen, int inout, SenseData* sense) { return device->ScsiCommand(device, cdb, cdblen, buffer, pbuflen, inout, sense); } void __cdecl Init(ULONG notification_hwnd) { // SetAsyncNotificationWindow(notification_hwnd); // IPSA::IPSAInit(); } /*******************************************************************\ \*******************************************************************/ DVDProxy* g_dvdproxy; void __cdecl SetHandler(int scsi_id, DvsDeviceKernel* handler) { // DebugPrintf("SetHandler(%d,%X)\n", scsi_id, handler); // if (handler==(DvsDeviceKernel*)-1) { // extern DvsDeviceKernel dummy_device; // handler = &dummy_device; // } if (scsi_id > 0 && scsi_id < 32) { g_dvdproxy->handlers[scsi_id] = handler; } } /*******************************************************************\ \*******************************************************************/ // This must match the mpdfunc_* enum in miniport.h! void* mpdfunc_pointers[] = { Init, _PELDR_LoadModule, _PELDR_FreeModule, CallDriverInitFunc, SetHandler, ScsiDispatch, _PageReserve, _PageCommit, _PageFree, CONFIGMG_Locate_DevNode, CONFIGMG_Get_Child, CONFIGMG_Get_Sibling, CONFIGMG_Query_Remove_SubTree, CONFIGMG_Remove_SubTree, CONFIGMG_Reenumerate_DevNode, OpenFileRO, CreateOrOpenFileRW, ReadFile, WriteFile, GetFileSize, CloseFile, CloseEvent, }; static const void* miniport_handle = (void*)~0; /*******************************************************************\ \*******************************************************************/ scsi_result_t __cdecl HandleDummyDevice(DvsDeviceKernel*, const unsigned char* cdb, int cdblen, unsigned char* buffer, unsigned long* pbuflen, int inout, SenseData* sense) { if (cdb[0] == DVDPROXY_SCSIOP) { if (cdb[1] != DVDPROXY_VERSION) { return MAKE_SCSIRESULT_ERROR(DVDPROXY_ERROR_WRONG_VERSION); } else if (cdb[2] != DVDPROXY_CMD_THUNK_CALL) { return MAKE_SCSIRESULT_ERROR(DVDPROXY_ERROR_INVALID_COMMAND); } else { CDB_Thunk_Call* call_cdb = (CDB_Thunk_Call*)cdb; void* function = 0; unsigned index = (unsigned)call_cdb->export_name; if (call_cdb->hmodule == hmodule_miniport) { DebugPrintf("looking up miniport function #%d\n", index); if (index < sizeof(mpdfunc_pointers)/sizeof(mpdfunc_pointers[0])) { function = mpdfunc_pointers[index]; } } else { if (index<65536) { DebugPrintf("looking up export #%d in module %X\n", call_cdb->export_name, call_cdb->hmodule); } else { DebugPrintf("looking up export %X in module %X\n", call_cdb->export_name, call_cdb->hmodule); DebugPrintf(" (%s)\n", call_cdb->export_name); } function = _PELDR_GetProcAddress(call_cdb->hmodule, call_cdb->export_name); } if (function == 0) { return MAKE_SCSIRESULT_ERROR(DVDPROXY_ERROR_EXPORT_NOT_FOUND); } else { *(void**)sense->command_specific = MakeThunkCall(function, buffer, *pbuflen); return MAKE_SCSIRESULT_ERROR(DVDPROXY_ERROR_THUNK_CALL_SUCCEEDED); } } } else { return MAKE_SCSIRESULT_ERROR(0x52000); // INVALID COMMAND OPERATION CODE } } DvsDeviceKernel dummy_device = { HandleDummyDevice }; /*******************************************************************\ \*******************************************************************/ scsi_result_t HandleSRB(SCSI_REQUEST_BLOCK* Srb, DvsDeviceKernel* handler, SenseData* sense) { if (handler == 0) { return MAKE_SCSIRESULT(SRB_STATUS_INVALID_TARGET_ID, 0, 0, 0, 0); } const unsigned char* cdb = Srb->Cdb; int cdblen = Srb->CdbLength; // check CDB length static const BYTE expected_cdb_lengths[] = { 6, 10, 10, 0, 0, 12, 0, 0 }; BYTE expected_cdb_length = expected_cdb_lengths[cdb[0]>>5]; if (expected_cdb_length ? (expected_cdb_length != cdblen) : (cdblen < 6 || cdblen > 16)) { return MAKE_SCSIRESULT_ERROR(0x51A00); // PARAMETER LIST LENGTH ERROR } // convert READ(6) and READ(12) commands into READ(10) unsigned char cdbbuf[10]; if (cdb[0] == SCSIOP_READ6) { cdbbuf[0] = SCSIOP_READ; cdbbuf[1] = cdb[1] & 0xE0; cdbbuf[2] = 0; cdbbuf[3] = cdb[1] & 0x1F; cdbbuf[4] = cdb[2]; cdbbuf[5] = cdb[3]; // logical block address cdbbuf[6] = 0; cdbbuf[7] = !cdb[4]; cdbbuf[8] = cdb[4]; // transfer length cdbbuf[9] = cdb[5]; // control cdb = cdbbuf; cdblen = 10; } else if (cdb[0] == SCSIOP_READ12) { if (cdb[6] == 0 && cdb[7] == 0) { cdbbuf[0] = SCSIOP_READ; cdbbuf[1] = cdb[1]; cdbbuf[2] = cdb[2]; cdbbuf[3] = cdb[3]; cdbbuf[4] = cdb[4]; cdbbuf[5] = cdb[5]; // logical block address cdbbuf[6] = 0; cdbbuf[7] = cdb[8]; cdbbuf[8] = cdb[9]; // transfer length cdbbuf[9] = cdb[11]; // control cdb = cdbbuf; cdblen = 10; } else { // The caller wants to transfer 65536 or // more *blocks* in one call -- unlikely! return SCSIRESULT_INVALID_CDB; } } // dispatch DBGPRINT((DBGLEVEL "Dispatching (handler %X)\n", handler)); return handler->ScsiCommand(handler, cdb, cdblen, (unsigned char*)Srb->DataBuffer, &Srb->DataTransferLength, (Srb->SrbFlags & 0xC0) >> 6, sense); } /*******************************************************************\ \*******************************************************************/ void SetScsiSense(SCSI_REQUEST_BLOCK* completed_srb, SenseData* sense, scsi_result_t result) { completed_srb->SrbStatus = SCSIRESULT_SRBSTAT(result); completed_srb->ScsiStatus = SCSIRESULT_TARGSTAT(result); // Windows doesn't seem to like it if you set // SRB_STATUS_AUTOSENSE_VALID on success. if (result != SCSIRESULT_SUCCESS && (SCSIRESULT_SRBSTAT(result) == SRB_STATUS_SUCCESS || SCSIRESULT_SRBSTAT(result) == SRB_STATUS_ERROR)) { unsigned sense_len = min(18U, completed_srb->SenseInfoBufferLength); if (sense_len > 0) { sense->sense_key = (sense->sense_key & 0xF0) | SCSIRESULT_SENSEKEY(result); sense->asc = SCSIRESULT_ASC(result); sense->ascq = SCSIRESULT_ASCQ(result); ScsiPortMoveMemory(completed_srb->SenseInfoBuffer, sense, sense_len); completed_srb->SenseInfoBufferLength = sense_len; completed_srb->SrbStatus |= SRB_STATUS_AUTOSENSE_VALID; } } } /*******************************************************************\ \*******************************************************************/ static const SenseData default_sense = { 0x70, 0, 0, {0,0,0,0}, 10, {0,0,0,0}, 0, 0, 0, {0,0,0} }; #ifdef WORKER_THREAD void __cdecl WorkerThreadProc(void* _self) { DBGPRINT((DBGLEVEL "WorkerThreadProc start")); DVDProxy* self = (DVDProxy*)_self; g_dvdproxy = self; for (;;) { Wait_Semaphore(self->semaphore_handle, BLOCK_SVC_INTS|BLOCK_THREAD_IDLE); for (int n = 0; n < DVDProxy::max_outstanding_srbs; ++n) { if (self->srbs[n] != 0 && !self->srb_complete[n]) { SCSI_REQUEST_BLOCK* srb = self->srbs[n]; DBGPRINT((DBGLEVEL "Worker thread: got SRB %X\n", srb)); SenseData sense = default_sense; scsi_result_t result = HandleSRB(srb, self->handlers[srb->TargetId], &sense); DBGPRINT((DBGLEVEL "Worker thread: finished %X with result %X\n", srb, result)); SetScsiSense(srb, &sense, result); self->srb_complete[n] = true; //WakeMiniportThread(); } } } } #else void __cdecl WorkProc(DVDProxy* self, SCSI_REQUEST_BLOCK* srb) { g_dvdproxy = self; SenseData sense = default_sense; scsi_result_t result = HandleSRB(srb, self->handlers[srb->TargetId], &sense); SetScsiSense(srb, &sense, result); } #endif