;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; This file is part of 'M', a MIPS system emulator. ;; ;; ;; ;; (C) 2019 Stanislav Datskovskiy ( www.loper-os.org ) ;; ;; http://wot.deedbot.org/17215D118B7239507FAFED98B98228A001ABFFC7.html ;; ;; ;; ;; You do not have, nor can you ever acquire the right to use, copy or ;; ;; distribute this software ; Should you use this software for any purpose, ;; ;; or copy and distribute it to anyone or in any manner, you are breaking ;; ;; the laws of whatever soi-disant jurisdiction, and you promise to ;; ;; continue doing so for the indefinite future. In any case, please ;; ;; always : read and understand any software ; verify any PGP signatures ;; ;; that you use - for any purpose. ;; ;; ;; ;; See also http://trilema.com/2015/a-new-software-licensing-paradigm . ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;; ;; UART Console Device ;; ;;;;;;;;;;;;;;;;;;;;;;;;; ;----------------------------------------------------------------------------- ; Console UART MMIO: DECLARE_BUS_DEVICE UART, 0x3F8, 0x40C ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ; Console UART IRQ: ;----------------------------------------------------------------------------- %define UART0_IRQ 2 ; UART0 Receiver Slave IRQ ;----------------------------------------------------------------------------- section .text ;----------------------------------------------------------------------------- _PD_Read_Word_UART: ; Word reads from UART: always 0 xor eax, eax ret ; Fin. ;----------------------------------------------------------------------------- _PD_Write_Word_UART: ; Word writes to POWER do nothing! ret ;----------------------------------------------------------------------------- _PD_Read_Byte_UART: ; Read Byte from UART call _UART_Read_Reg ; Read this byte from UART mov eax, edx ; Return the resulting byte. ret ; Fin. ;----------------------------------------------------------------------------- _PD_Write_Byte_UART: ; Write Byte to UART call _UART_Write_Reg ; write this byte to UART ret ; Fin. ;----------------------------------------------------------------------------- _Device_Init_UART: call _Cure_TTY ; Cure the tty: call _UART_Reset ; Reset the UART mov rdi, _UART_Slave ; UART Slave call _Create_Thread ; Start the UART Slave Thread ret ;----------------------------------------------------------------------------- _Device_Shutdown_UART: ; Nothing needed mov dword [UART_Die], 0x01 ; Ask (unblocked) slave to die call _Uncure_TTY ; bring back the old tty settings, for clean shell ret ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;; UART State ;----------------------------------------------------------------------------- section .bss UART0 resb UART_State_size ; UART-0 State UART_Die resd 1 ; Shutdown Trigger ;----------------------------------------------------------------------------- section .text ;----------------------------------------------------------------------------- ; Slave Thread which connects UART0 to linux console ;----------------------------------------------------------------------------- _UART_Slave: call _Read_Char_Blocking mov dl, byte [IOBUF] call _UART_Receive_Byte ; put DL in the UART receiver queue SetSlaveIRQ UART0_IRQ ; trigger the slave IRQ cmp dword [UART_Die], 0x01 ; time to die? jne _UART_Slave ; if not, keep going. jmp _exit_thread ; terminate thread ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ; UARTism ;----------------------------------------------------------------------------- ;; UART State struc UART_State .LCR resb 1 ; Line Control .LSR resb 1 ; Line Status .MSR resb 1 ; Modem Status .IIR resb 1 ; Interrupt ID .IER resb 1 ; Interrupt Enable .DLL resb 1 ; .DLH resb 1 ; .FCR resb 1 ; FIFO Control .MCR resb 1 ; Modem Control .SCR resb 1 ; Modem Control ;; FIFO: .FIFO resb 32 ; The FIFO itself .FIFO_First resb 1 ; index of 1st byte in the FIFO .FIFO_Last resb 1 ; index of last byte in the FIFO .FIFO_Count resb 1 ; number of bytes in the FIFO endstruc ;----------------------------------------------------------------------------- %define UART_LSR_DATA_READY 0x1 %define UART_LSR_FIFO_EMPTY 0x20 %define UART_LSR_TRANSMITTER_EMPTY 0x40 ; Enable Transmitter holding register int. %define UART_IER_THRI 0x02 ; Enable receiver data interrupt %define UART_IER_RDI 0x01 ; Modem status interrupt (Low priority) %define UART_IIR_MSI 0x00 %define UART_IIR_NO_INT 0x01 ; Transmitter holding register empty %define UART_IIR_THRI 0x02 ; Receiver data interrupt %define UART_IIR_RDI 0x04 ; Receiver line status interrupt (High p.) %define UART_IIR_RLSI 0x06 ; Character timeout %define UART_IIR_CTI 0x0c ; Divisor latch access bit %define UART_LCR_DLAB 0x80 ; R/W: Divisor Latch Low, DLAB=1 %define UART_DLL 0 ; R/W: Divisor Latch High, DLAB=1 %define UART_DLH 1 ; R/W: Interrupt Enable Register %define UART_IER 1 ; R: Interrupt ID Register %define UART_IIR 2 ; W: FIFO Control Register %define UART_FCR 2 ; R/W: Line Control Register %define UART_LCR 3 ; W: Modem Control Register %define UART_MCR 4 ; R: Line Status Register %define UART_LSR 5 ; R: Modem Status Register %define UART_MSR 6 ; R/W: %define UART_SCR 7 ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;; Access to UART regs: %define U0r(R) byte [UART0 + UART_State. %+ R] ;----------------------------------------------------------------------------- ;; Clear FIFO ;----------------------------------------------------------------------------- _UART_FIFO_Clear: mov U0r(FIFO_Last), 0 mov U0r(FIFO_First), 0 mov U0r(FIFO_Count), 0 ret ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;; Take a byte from the UART FIFO. Byte will be in DL. ;----------------------------------------------------------------------------- _UART_FIFO_Get: xor edx, edx ; edx := 0 cmp U0r(FIFO_Count), 0 ; is FIFO nonempty? je _UART_FIFO_Get_Nope ; ... if empty, skip and return 0 ;; if FIFO is nonempty: xor eax, eax ; eax := 0 mov al, U0r(FIFO_First) ; al := fifo_first mov dl, U0r(FIFO + eax) ; dl := FIFO[al] inc eax ; eax := eax + 1 and eax, 0x1F ; eax := eax & 0x1f mov U0r(FIFO_First), al ; fifo_first := al mov al, U0r(FIFO_Count) ; al := fifo_count test eax, eax ; is al 0 ? jz _UART_FIFO_Get_Nope ; ... if 0, skip and do not update count dec eax ; eax := eax - 1 mov U0r(FIFO_Count), al ; fifo_count := al _UART_FIFO_Get_Nope: ret ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;; Reset UART-0 ;----------------------------------------------------------------------------- _UART_Reset: mov U0r(LCR), 3 mov U0r(LSR), UART_LSR_TRANSMITTER_EMPTY | UART_LSR_FIFO_EMPTY mov U0r(MSR), 0 mov U0r(IIR), UART_IIR_NO_INT mov U0r(IER), 0 mov U0r(DLL), 0 mov U0r(DLH), 0 mov U0r(FCR), 0 mov U0r(MCR), 0 mov U0r(SCR), 0 call _UART_FIFO_Clear ; zap the FIFO ret ;----------------------------------------------------------------------------- ;; Update UART IRQ ;----------------------------------------------------------------------------- _UART_IRQ: test U0r(LSR), UART_LSR_DATA_READY jz _UART_IRQ_Not_RDI test U0r(IER), UART_IER_RDI jz _UART_IRQ_Not_RDI _UART_IRQ_RDI: mov U0r(IIR), UART_IIR_RDI ; IIR=UART_IIR_RDI jmp _UART_IRQ_Maybe_Pending ; See if interrupt pending _UART_IRQ_Not_RDI: test U0r(LSR), UART_LSR_FIFO_EMPTY jz _UART_IRQ_Not_THRI test U0r(IER), UART_IER_THRI jz _UART_IRQ_Not_THRI _UART_IRQ_THRI: mov U0r(IIR), UART_IIR_THRI ; IIR=UART_IIR_THRI jmp _UART_IRQ_Maybe_Pending ; See if interrupt pending _UART_IRQ_Not_THRI: mov U0r(IIR), UART_IIR_NO_INT ; IIR=UART_IIR_NO_INT _UART_IRQ_Maybe_Pending: ; See if interrupt pending ;; see if interrupt pending cmp U0r(IIR), UART_IIR_NO_INT ; IIR==UART_IIR_NO_INT ? jne _UART_IRQ_Pending ; if !=, interrupt is pending _UART_IRQ_Not_Pending: ; interrupt is not pending: ClrIRQ UART0_IRQ ret _UART_IRQ_Pending: ; interrupt is pending: SetIRQ UART0_IRQ ret ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;; Place a byte (DL) into the UART's receiver buffer. ; TODO: mutex? ;----------------------------------------------------------------------------- _UART_Receive_Byte: xor eax, eax ; eax := 0 mov al, U0r(FIFO_Last) ; al := fifo_last mov U0r(FIFO + eax), dl ; FIFO[al] := dl inc eax ; eax := eax + 1 and eax, 0x1F ; eax := eax & 0x1f mov U0r(FIFO_Last), al ; fifo_last := al mov al, U0r(FIFO_Count) ; eax := fifo_count inc eax ; eax := eax + 1 mov edx, 32 ; edx := 32 cmp eax, edx ; is eax > 32 ? cmovg eax, edx ; ... if yes, eax := 32 mov U0r(FIFO_Count), al ; fifo_count := al or U0r(LSR), UART_LSR_DATA_READY ; set the 'ready' flag call _UART_IRQ ; update IRQ ret ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;; Read UART Register. Offset in EAX, result in DL. ;----------------------------------------------------------------------------- _UART_Read_Reg: xor edx, edx ; result := 0 sub eax, UART_BASE ; Adjust for base of UART and eax, 0x7 ; Adjusted offset is a 4-bit quantity test U0r(LCR), UART_LCR_DLAB jz _UART_Read_Reg_Not_DLAB _UART_Read_Reg_DLAB: cmp eax, UART_DLL ; offset=DLL? je _UART_Read_Reg_DLAB_DLL cmp eax, UART_DLH ; offset=DLH? je _UART_Read_Reg_DLAB_DLH jmp _UART_Read_Reg_Not_DLAB ; neither of these _UART_Read_Reg_DLAB_DLL: mov dl, U0r(DLL) ; return DLL ret _UART_Read_Reg_DLAB_DLH: mov dl, U0r(DLH) ; return DLH ret _UART_Read_Reg_Not_DLAB: cmp eax, 0 ; offset=0 ? je _UART_Read_Reg_0 cmp eax, UART_IER ; offset=IER ? je _UART_Read_Reg_IER cmp eax, UART_MSR ; offset=MSR ? je _UART_Read_Reg_MSR cmp eax, UART_MCR ; offset=MCR ? je _UART_Read_Reg_MCR cmp eax, UART_IIR ; offset=IIR ? je _UART_Read_Reg_IIR cmp eax, UART_LCR ; offset=LCR ? je _UART_Read_Reg_LCR cmp eax, UART_LSR ; offset=LSR ? je _UART_Read_Reg_LSR cmp eax, UART_SCR ; offset=SCR ? je _UART_Read_Reg_SCR ACHTUNG "Bad UART Read Command." ; TODO: print detail ret _UART_Read_Reg_0: cmp U0r(FIFO_Count), 0 ; is FIFO nonempty? je _UART_Read_Reg_0_Empty ; ... if empty, skip ;; FIFO is not empty: call _UART_FIFO_Get ; get byte from FIFO and U0r(LSR), ~UART_LSR_DATA_READY ; lower 'ready' flag cmp U0r(FIFO_Count), 0 ; is FIFO still nonempty? je _UART_Read_Reg_0_Empty ; ... if empty, leave 'ready' flag down ;; FIFO still has data: or U0r(LSR), UART_LSR_DATA_READY ; else, raise the 'ready' flag. _UART_Read_Reg_0_Empty: call _UART_IRQ ; update IRQ ret ; return DL _UART_Read_Reg_IER: mov dl, U0r(IER) and edx, 0xF ret ; return IER & 0xF _UART_Read_Reg_MSR: mov dl, U0r(MSR) ret ; return MSR _UART_Read_Reg_MCR: mov dl, U0r(MCR) ret ; return MCR _UART_Read_Reg_IIR: mov dl, U0r(IIR) ret ; return IIR _UART_Read_Reg_LCR: mov dl, U0r(LCR) ret ; return LCR _UART_Read_Reg_LSR: cmp U0r(FIFO_Count), 0 ; is FIFO nonempty? je _UART_Read_Reg_LSR_Empty ; if empty, go to 'empty' ;; FIFO is not empty: or U0r(LSR), UART_LSR_DATA_READY ; raise the 'ready' flag. jmp _UART_Read_Reg_LSR_Done ; return _UART_Read_Reg_LSR_Empty: and U0r(LSR), ~UART_LSR_DATA_READY ; lower the 'ready' flag _UART_Read_Reg_LSR_Done: mov dl, U0r(LSR) ret ; return LSR _UART_Read_Reg_SCR: mov dl, U0r(SCR) ret ; return SCR ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- ;; Write UART Register. Offset in EAX, payload in DL. ;----------------------------------------------------------------------------- _UART_Write_Reg: sub eax, UART_BASE ; Adjust for base of UART and eax, 0x7 ; Adjusted offset is a 4-bit quantity test U0r(LCR), UART_LCR_DLAB jz _UART_Write_Reg_Not_DLAB _UART_Write_Reg_DLAB: cmp eax, UART_DLL ; offset=DLL? je _UART_Write_Reg_DLAB_DLL cmp eax, UART_DLH ; offset=DLH? je _UART_Write_Reg_DLAB_DLH jmp _UART_Write_Reg_Not_DLAB ; neither of these _UART_Write_Reg_DLAB_DLL: mov U0r(DLL), dl ; write DLL ret _UART_Write_Reg_DLAB_DLH: mov U0r(DLH), dl ; write DLH ret _UART_Write_Reg_Not_DLAB: cmp eax, 0 ; offset=0 ? je _UART_Write_Reg_0 cmp eax, UART_IER ; offset=IER? je _UART_Write_Reg_IER cmp eax, UART_FCR ; offset=FCR? je _UART_Write_Reg_FCR cmp eax, UART_LCR ; offset=LCR? je _UART_Write_Reg_LCR cmp eax, UART_MCR ; offset=MCR? je _UART_Write_Reg_MCR cmp eax, UART_SCR ; offset=SCR? je _UART_Write_Reg_SCR ACHTUNG "Bad UART Write Command." ; TODO: print detail ret _UART_Write_Reg_0: and U0r(LSR), ~UART_LSR_FIFO_EMPTY test U0r(MCR), (1 << 4) jz _UART_Write_Reg_0_Not_Loopback _UART_Write_Reg_0_Loopback: call _UART_Receive_Byte ; put DL in the receiver FIFO jmp _UART_Write_Reg_0_Fin ; wrap up _UART_Write_Reg_0_Not_Loopback: ; if NOT loopback : call _Write_Char ; ... then write char in DL to console _UART_Write_Reg_0_Fin: or U0r(LSR), UART_LSR_FIFO_EMPTY ; mark the send buffer as empty call _UART_IRQ ; refresh IRQ ret ; fin _UART_Write_Reg_IER: and edx, 0xF ; 4-bit qty mov U0r(IER), dl ; IER := dl & 0xF call _UART_IRQ ; refresh IRQ ret ; fin _UART_Write_Reg_FCR: mov U0r(FCR), dl ; FCR := dl test dl, 0x2 ; if FCR & 2: jz _UART_Write_Reg_FCR_No_Clear call _UART_FIFO_Clear ; ... then zap the FIFO. _UART_Write_Reg_FCR_No_Clear: ret _UART_Write_Reg_LCR: mov U0r(LCR), dl ; LCR := dl ret ; fin _UART_Write_Reg_MCR: mov U0r(MCR), dl ; MCR := dl ret ; fin _UART_Write_Reg_SCR: mov U0r(SCR), dl ; SCR := dl ret ; fin ;-----------------------------------------------------------------------------