diff --git a/assets/serialtest.js b/assets/serialtest.js index 58ca288..d7f0d0c 100644 --- a/assets/serialtest.js +++ b/assets/serialtest.js @@ -1,4 +1,8 @@ -var ba = com.sendMessageGetBytes(0, "DEVNAM"+String.fromCharCode(0x17)); +function getStatusMessage(portNo) { + return com.sendMessageGetBytes(portNo, "DEVSTU"+String.fromCharCode(0x17)); +} + +let ba = com.sendMessageGetBytes(0, "DEVNAM"+String.fromCharCode(0x17)); serial.println(ba); ba = com.pullMessage(0) @@ -10,15 +14,23 @@ serial.println("# END OF MSG"); ba = com.sendMessageGetBytes(1, "DEVNAM"+String.fromCharCode(0x17)); serial.println(ba); -ba = com.sendMessageGetBytes(1, "DEVSTU"+String.fromCharCode(0x17)); -serial.println(ba); +serial.println(getStatusMessage(1)); ba = com.sendMessageGetBytes(1, "LIST"); ba = com.pullMessage(1); println(ba); -ba = com.sendMessageGetBytes(1, "DEVSTU"+String.fromCharCode(0x17)); -serial.println(ba); +serial.println(getStatusMessage(1)); +com.sendMessage(1, "OPENR\"basic.js\""); + +println("Status code: "+com.getStatusCode(1)); + +com.sendMessage(1, "READ"); +println("Status code: "+com.getStatusCode(1)); +let source = com.pullMessage(1); +println(source); + +eval(source); serial.println("k bye") \ No newline at end of file diff --git a/serialdev.txt b/serialdev.txt index 22d88c3..7575203 100644 --- a/serialdev.txt +++ b/serialdev.txt @@ -85,9 +85,12 @@ Description: opens the file for writing Description: opens the file for appending (a variant of write) - WRITE + WRITE -Description: puts the device into WRITE mode. Any subsequent bytes will be interpreted as-is for writing +Description: puts the device into WRITE mode and sets internal counter. Any subsequent messages will be interpreted + as-is, get written directly to the file, and will decrement the internal counter accordingly. + The number of bytes are required because of a nature of block-transfer, where every message is always in + the length of the block size. FLUSH diff --git a/src/net/torvald/tsvm/Base64Delegate.kt b/src/net/torvald/tsvm/Base64Delegate.kt index 49f051e..2a6fa5f 100644 --- a/src/net/torvald/tsvm/Base64Delegate.kt +++ b/src/net/torvald/tsvm/Base64Delegate.kt @@ -8,6 +8,10 @@ class Base64Delegate { return Base64Coder.decode(inputstr) } + fun atostr(inputstr: String): String { + return Base64Coder.decode(inputstr).toString(VM.CHARSET) + } + fun btoa(inputbytes: ByteArray): String { val sb = StringBuilder() sb.append(Base64Coder.encode(inputbytes)) diff --git a/src/net/torvald/tsvm/SerialHelper.kt b/src/net/torvald/tsvm/SerialHelper.kt index dab3684..bb4a530 100644 --- a/src/net/torvald/tsvm/SerialHelper.kt +++ b/src/net/torvald/tsvm/SerialHelper.kt @@ -39,6 +39,7 @@ object SerialHelper { } // Returns what's on the RX buffer after sendMessage() + // i.e won't (actually shouldn't) clobber the device's message compose buffer (see TestDiskDrive) fun fetchResponse(vm: VM, portNo: Int): ByteArray { val incomingMsg = ByteArray(BLOCK_SIZE) @@ -89,6 +90,7 @@ object SerialHelper { while (!checkIfDeviceIsReady(vm, portNo)) { Thread.sleep(SLEEP_TIME) } } + fun getStatusCode(vm: VM, portNo: Int) = vm.getIO().mmio_read(4080L + portNo) private fun checkIfDeviceIsThere(vm: VM, portNo: Int) = (vm.getIO().mmio_read(4092L + portNo)!! and 1.toByte()) == 1.toByte() @@ -138,4 +140,5 @@ class SerialHelperDelegate(val vm: VM) { fun fetchResponse(portNo: Int) = SerialHelper.fetchResponse(vm, portNo).toString(VM.CHARSET) fun getDeviceStatus(portNo: Int) = SerialHelper.getDeviceStatus(vm, portNo) fun waitUntilReady(portNo: Int) = SerialHelper.waitUntilReady(vm, portNo) + fun getStatusCode(portNo: Int) = SerialHelper.getStatusCode(vm, portNo) } \ No newline at end of file diff --git a/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt b/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt index 0e0715a..211a6be 100644 --- a/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt +++ b/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt @@ -1,16 +1,16 @@ package net.torvald.tsvm.peripheral -import java.io.IOException - abstract class BlockTransferInterface(val isMaster: Boolean, val isSlave: Boolean) { protected var recipient: BlockTransferInterface? = null - open @Volatile var ready = true - open @Volatile var busy = false + @Volatile var ready = true + @Volatile var busy = false + + @Volatile var statusCode = 0 protected var sendmode = false; private set - open var blockSize = 0 + @Volatile var blockSize = 0 open fun attachDevice(device: BlockTransferInterface?) { recipient = device @@ -49,7 +49,9 @@ abstract class BlockTransferInterface(val isMaster: Boolean, val isSlave: Boolea recipient?.startSend() } - /** A method called by the sender so it can ACTUALLY write its thing onto me. */ + /** A method called by the sender so it can ACTUALLY write its thing onto me. + * + * @param inputData received message, usually 4096 bytes long and null-padded */ abstract fun writeoutImpl(inputData: ByteArray) /** The actual implementation; must be called by a sender class */ fun writeout(inputData: ByteArray) { @@ -64,6 +66,8 @@ abstract class BlockTransferInterface(val isMaster: Boolean, val isSlave: Boolea open fun doYouHaveNext(): Boolean = recipient?.hasNext() ?: false open fun yourBlockSize(): Int = recipient?.blockSize ?: 0 + fun getYourStatusCode() = recipient?.statusCode ?: 0 + /** @param sendmode TRUE for send, FALSE for receive */ open fun setMode(sendmode: Boolean) { this.sendmode = sendmode @@ -82,5 +86,14 @@ abstract class BlockTransferInterface(val isMaster: Boolean, val isSlave: Boolea const val BAD_NEWS = 0x15.toByte() const val UNIT_SEP = 0x1F.toByte() const val END_OF_SEND_BLOCK = 0x17.toByte() + + fun trimNull(ba: ByteArray): ByteArray { + var cnt = BLOCK_SIZE - 1 + while (cnt >= 0) { + if (ba[cnt] != 0.toByte()) break + cnt -= 1 + } + return ba.sliceArray(0..cnt) + } } } \ No newline at end of file diff --git a/src/net/torvald/tsvm/peripheral/IOSpace.kt b/src/net/torvald/tsvm/peripheral/IOSpace.kt index ed38f83..0ad3639 100644 --- a/src/net/torvald/tsvm/peripheral/IOSpace.kt +++ b/src/net/torvald/tsvm/peripheral/IOSpace.kt @@ -92,6 +92,16 @@ class IOSpace(val vm: VM) : PeriBase, InputProcessor { in 72..79 -> systemUptime.ushr((adi - 72) * 8).and(255).toByte() in 80..87 -> rtc.ushr((adi - 80) * 8).and(255).toByte() + 4076L -> blockTransferPorts[0].statusCode.toByte() + 4077L -> blockTransferPorts[1].statusCode.toByte() + 4078L -> blockTransferPorts[2].statusCode.toByte() + 4079L -> blockTransferPorts[3].statusCode.toByte() + + 4080L -> blockTransferPorts[0].getYourStatusCode().toByte() + 4081L -> blockTransferPorts[1].getYourStatusCode().toByte() + 4082L -> blockTransferPorts[2].getYourStatusCode().toByte() + 4083L -> blockTransferPorts[3].getYourStatusCode().toByte() + 4084L -> (blockTransferPorts[0].yourBlockSize().toByte()) 4085L -> (blockTransferPorts[0].doYouHaveNext().toInt().shl(7) or blockTransferPorts[0].yourBlockSize().ushr(8).and(15)).toByte() 4086L -> (blockTransferPorts[1].yourBlockSize().toByte()) @@ -136,6 +146,11 @@ class IOSpace(val vm: VM) : PeriBase, InputProcessor { RTClatched = byte.and(0b10).isNonZero() } + 4076L -> blockTransferPorts[0].statusCode = bi + 4077L -> blockTransferPorts[1].statusCode = bi + 4078L -> blockTransferPorts[2].statusCode = bi + 4079L -> blockTransferPorts[3].statusCode = bi + 4084L -> blockTransferPorts[0].blockSize = blockTransferPorts[0].blockSize.and(0xFF00) or byte.toInt().and(255) 4085L -> { blockTransferPorts[0].hasNext = (byte < 0) diff --git a/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt b/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt index 63f4d94..42a2327 100644 --- a/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt +++ b/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt @@ -13,6 +13,7 @@ class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, t const val STATE_CODE_ILLEGAL_COMMAND = 128 const val STATE_CODE_FILE_NOT_FOUND = 129 const val STATE_CODE_FILE_ALREADY_OPENED = 130 + const val STATE_CODE_OPERATION_NOT_PERMITTED = 131 const val STATE_CODE_SYSTEM_IO_ERROR = 192 @@ -24,6 +25,7 @@ class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, t errorMsgs[STATE_CODE_FILE_NOT_FOUND] = "FILE NOT FOUND" errorMsgs[STATE_CODE_FILE_ALREADY_OPENED] = "FILE ALREADY OPENED" errorMsgs[STATE_CODE_SYSTEM_IO_ERROR] = "IO ERROR ON SIMULATED DRIVE" + errorMsgs[STATE_CODE_OPERATION_NOT_PERMITTED] = "OPERATION NOT PERMITTED" } } @@ -56,7 +58,6 @@ class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, t private var fileOpen = false private var file = File(rootPath.toURI()) //private var readModeLength = -1 // always 4096 - private var stateCode = STATE_CODE_STANDBY private var writeMode = false private var writeModeLength = -1 @@ -66,6 +67,8 @@ class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, t init { + statusCode = STATE_CODE_STANDBY + if (!rootPath.exists()) { rootPath.mkdirs() } @@ -112,78 +115,101 @@ class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, t * Disk drive must create desired side effects in accordance with the input message. */ override fun writeoutImpl(inputData: ByteArray) { - val inputString = inputData.toString(VM.CHARSET) + if (writeMode) { - if (inputString.startsWith("DEVRST\u0017")) { - //readModeLength = -1 - fileOpen = false - file = File(rootPath.toURI()) - blockSendCount = 0 - stateCode = STATE_CODE_STANDBY - writeMode = false - writeModeLength = -1 } - else if (inputString.startsWith("DEVSTU\u0017")) { - if (stateCode < 128) { - recipient?.writeout(composePositiveAns("${stateCode.toChar()}", errorMsgs[stateCode])) + else { + val inputString = trimNull(inputData).toString(VM.CHARSET) + + if (inputString.startsWith("DEVRST\u0017")) { + //readModeLength = -1 + fileOpen = false + file = File(rootPath.toURI()) + blockSendCount = 0 + statusCode = STATE_CODE_STANDBY + writeMode = false + writeModeLength = -1 } - else { - recipient?.writeout(composeNegativeAns("${stateCode.toChar()}", errorMsgs[stateCode])) - } - } - else if (inputString.startsWith("DEVTYP\u0017")) - recipient?.writeout(composePositiveAns("STOR")) - else if (inputString.startsWith("DEVNAM\u0017")) - recipient?.writeout(composePositiveAns("Testtec Virtual Disk Drive")) - else if (inputString.startsWith("OPENR\"") || inputString.startsWith("OPENW\"") || inputString.startsWith("OPENA\"")) { - if (fileOpen) { - stateCode = STATE_CODE_FILE_ALREADY_OPENED - return - } - - val openMode = inputString[4] - - val prop = inputString.subSequence(6, inputString.length).split(",\"") - - if (prop.size == 0 || prop.size > 2) { - stateCode = STATE_CODE_ILLEGAL_COMMAND - return - } - val filePath = sanitisePath(prop[0]) - val driveNum = if (prop.size != 2) 0 else prop[1].toInt() - - // TODO driveNum is for disk drives that may have two or more slots built; for testing purposes we'll ignore it - - file = File(rootPath, filePath) - - if (openMode == 'R' && !file.exists()) { - stateCode = STATE_CODE_FILE_NOT_FOUND - return - } - - fileOpen = true - } - else if (inputString.startsWith("LIST")) { - // temporary behaviour to ignore any arguments - resetBuf() - messageComposeBuffer.write(getReadableLs().toByteArray(VM.CHARSET)) - stateCode = STATE_CODE_STANDBY - } - else if (inputString.startsWith("CLOSE")) { - fileOpen = false - stateCode = STATE_CODE_STANDBY - } - else if (inputString.startsWith("READ")) { - //readModeLength = inputString.substring(4 until inputString.length).toInt() - - resetBuf() - if (file.isFile) { - try { - messageComposeBuffer.write(file.readBytes()) - stateCode = STATE_CODE_STANDBY + else if (inputString.startsWith("DEVSTU\u0017")) { + if (statusCode < 128) { + recipient?.writeout(composePositiveAns("${statusCode.toChar()}", errorMsgs[statusCode])) } - catch (e: IOException) { - stateCode = STATE_CODE_SYSTEM_IO_ERROR + else { + recipient?.writeout(composeNegativeAns("${statusCode.toChar()}", errorMsgs[statusCode])) + } + } + else if (inputString.startsWith("DEVTYP\u0017")) + recipient?.writeout(composePositiveAns("STOR")) + else if (inputString.startsWith("DEVNAM\u0017")) + recipient?.writeout(composePositiveAns("Testtec Virtual Disk Drive")) + else if (inputString.startsWith("OPENR\"") || inputString.startsWith("OPENW\"") || inputString.startsWith("OPENA\"")) { + if (fileOpen) { + statusCode = STATE_CODE_FILE_ALREADY_OPENED + return + } + + println("[TestDiskDrive] msg: $inputString, lastIndex: ${inputString.lastIndex}") + + val openMode = inputString[4] + println("[TestDiskDrive] open mode: $openMode") + // split inputstring into path and optional drive-number + + // get position of latest delimeter (comma) + var commaIndex = inputString.lastIndex + while (commaIndex > 6) { + if (inputString[commaIndex] == ',') break; commaIndex -= 1 + } + // sanity check if path is actually enclosed with double-quote + if (commaIndex != 6 && inputString[commaIndex - 1] != '"') { + statusCode = STATE_CODE_ILLEGAL_COMMAND + return + } + val pathStr = inputString.substring(6, if (commaIndex == 6) inputString.lastIndex else commaIndex - 1) + val driveNum = + if (commaIndex == 6) null else inputString.substring(commaIndex + 1, inputString.length).toInt() + val filePath = sanitisePath(pathStr) + + // TODO driveNum is for disk drives that may have two or more slots built; for testing purposes we'll ignore it + + file = File(rootPath, filePath) + println("[TestDiskDrive] file path: ${file.canonicalPath}, drive num: $driveNum") + + if (openMode == 'R' && !file.exists()) { + println("! file not found") + statusCode = STATE_CODE_FILE_NOT_FOUND + return + } + + statusCode = STATE_CODE_STANDBY + fileOpen = true + blockSendCount = 0 + } + else if (inputString.startsWith("LIST")) { + // temporary behaviour to ignore any arguments + resetBuf() + messageComposeBuffer.write(getReadableLs().toByteArray(VM.CHARSET)) + statusCode = STATE_CODE_STANDBY + } + else if (inputString.startsWith("CLOSE")) { + fileOpen = false + statusCode = STATE_CODE_STANDBY + } + else if (inputString.startsWith("READ")) { + //readModeLength = inputString.substring(4 until inputString.length).toInt() + + resetBuf() + if (file.isFile) { + try { + messageComposeBuffer.write(file.readBytes()) + statusCode = STATE_CODE_STANDBY + } + catch (e: IOException) { + statusCode = STATE_CODE_SYSTEM_IO_ERROR + } + } + else { + statusCode = STATE_CODE_OPERATION_NOT_PERMITTED + return } } } diff --git a/terranmon.txt b/terranmon.txt index 17af7ef..067fda1 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -82,6 +82,9 @@ MMIO 72..79 RO: System uptime in nanoseconds 80..87 RO: RTC in microseconds +4076..4079 RW: 8-bit status code for the port +4080..4083 RO: 8-bit status code for connected device + 4084..4091 RO: Block transfer status 0b nnnnnnnn a000 mmmm