; Memory management for USB structures. ; Protocol layer uses the common kernel heap malloc/free. ; Hardware layer has special requirements: ; * memory blocks should be properly aligned ; * memory blocks should not cross page boundary ; Hardware layer allocates fixed-size blocks. ; Thus, the specific allocator is quite easy to write: ; allocate one page, split into blocks, maintain the single-linked ; list of all free blocks in each page. ; Note: size must be a multiple of required alignment. ; Data for one pool: dd pointer to the first page, MUTEX lock. uglobal ; Structures in UHCI and OHCI have equal sizes. ; Thus, functions and data for allocating/freeing can be shared; ; we keep them here rather than in controller-specific files. align 4 ; Data for UHCI and OHCI endpoints pool. usb1_ep_first_page dd ? usb1_ep_mutex MUTEX ; Data for UHCI and OHCI general transfer descriptors pool. usb_gtd_first_page dd ? usb_gtd_mutex MUTEX endg ; sanity check: structures in UHCI and OHCI should be the same for allocation if (sizeof.ohci_pipe=sizeof.uhci_pipe)&(ohci_pipe.SoftwarePart=uhci_pipe.SoftwarePart) ; Allocates one endpoint structure for UHCI/OHCI. ; Returns pointer to software part (usb_pipe) in eax. proc usb1_allocate_endpoint push ebx mov ebx, usb1_ep_mutex stdcall usb_allocate_common, sizeof.ohci_pipe test eax, eax jz @f add eax, ohci_pipe.SoftwarePart @@: pop ebx ret endp ; Free one endpoint structure for UHCI/OHCI. ; Stdcall with one argument, pointer to software part (usb_pipe). proc usb1_free_endpoint sub dword [esp+4], ohci_pipe.SoftwarePart jmp usb_free_common endp else ; sanity check continued .err allocate_endpoint/free_endpoint must be different for OHCI and UHCI end if ; sanity check: structures in UHCI and OHCI should be the same for allocation if (sizeof.ohci_gtd=sizeof.uhci_gtd)&(ohci_gtd.SoftwarePart=uhci_gtd.SoftwarePart) ; Allocates one general transfer descriptor structure for UHCI/OHCI. ; Returns pointer to software part (usb_gtd) in eax. proc usb1_allocate_general_td push ebx mov ebx, usb_gtd_mutex stdcall usb_allocate_common, sizeof.ohci_gtd test eax, eax jz @f add eax, ohci_gtd.SoftwarePart @@: pop ebx ret endp ; Free one general transfer descriptor structure for UHCI/OHCI. ; Stdcall with one argument, pointer to software part (usb_gtd). proc usb1_free_general_td sub dword [esp+4], ohci_gtd.SoftwarePart jmp usb_free_common endp else ; sanity check continued .err allocate_general_td/free_general_td must be different for OHCI and UHCI end if ; Allocator for fixed-size blocks: allocate a block. ; [ebx-4] = pointer to the first page, ebx = pointer to MUTEX structure. proc usb_allocate_common push edi ; save used register to be stdcall virtual at esp dd ? ; saved edi dd ? ; return address .size dd ? end virtual ; 1. Take the lock. mov ecx, ebx call mutex_lock ; 2. Find the first allocated page with a free block, if any. ; 2a. Initialize for the loop. mov edx, ebx .pageloop: ; 2b. Get the next page, keeping the current in eax. mov eax, edx mov edx, [edx-4] ; 2c. If there is no next page, we're out of luck; go to 4. test edx, edx jz .newpage add edx, 0x1000 @@: ; 2d. Get the pointer to the first free block on this page. ; If there is no free block, continue to 2b. mov eax, [edx-8] test eax, eax jz .pageloop ; 2e. Get the pointer to the next free block. mov ecx, [eax] ; 2f. Update the pointer to the first free block from eax to ecx. ; Normally [edx-8] still contains eax, if so, atomically set it to ecx ; and proceed to 3. ; However, the price of simplicity of usb_free_common (in particular, it ; doesn't take the lock) is that [edx-8] could (rarely) be changed while ; we processed steps 2d+2e. If so, return to 2d and retry. lock cmpxchg [edx-8], ecx jnz @b .return: ; 3. Release the lock taken in step 1 and return. push eax mov ecx, ebx call mutex_unlock pop eax pop edi ; restore used register to be stdcall ret 4 .newpage: ; 4. Allocate a new page. push eax stdcall kernel_alloc, 0x1000 pop edx ; If failed, say something to the debug board and return zero. test eax, eax jz .nomemory ; 5. Add the new page to the tail of list of allocated pages. mov [edx-4], eax ; 6. Initialize two service dwords in the end of page: ; first free block is (start of page) + (block size) ; (we will return first block at (start of page), so consider it allocated), ; no next page. mov edx, eax lea edi, [eax+0x1000-8] add edx, [.size] mov [edi], edx and dword [edi+4], 0 ; 7. All blocks starting from edx are free; join them in a single-linked list. @@: mov ecx, edx add edx, [.size] mov [ecx], edx cmp edx, edi jbe @b sub ecx, [.size] and dword [ecx], 0 ; 8. Return (start of page). jmp .return .nomemory: dbgstr 'no memory for USB descriptor' xor eax, eax jmp .return endp ; Allocator for fixed-size blocks: free a block. proc usb_free_common push ecx edx virtual at esp rd 2 ; saved registers dd ? ; return address .block dd ? end virtual ; Insert the given block to the head of free blocks in this page. mov ecx, [.block] mov edx, ecx or edx, 0xFFF @@: mov eax, [edx+1-8] mov [ecx], eax lock cmpxchg [edx+1-8], ecx jnz @b pop edx ecx ret 4 endp ; Helper procedure for OHCI: translate physical address in ecx ; of some transfer descriptor to linear address. proc usb_td_to_virt ; Traverse all pages used for transfer descriptors, looking for the one ; with physical address as in ecx. mov eax, [usb_gtd_first_page] @@: test eax, eax jz .zero push eax call get_pg_addr sub eax, ecx jz .found cmp eax, -0x1000 ja .found pop eax mov eax, [eax+0x1000-4] jmp @b .found: ; When found, combine page address from eax with page offset from ecx. pop eax and ecx, 0xFFF add eax, ecx .zero: ret endp