diff --git a/serialdev.txt b/serialdev.txt index fe23e67..133d715 100644 --- a/serialdev.txt +++ b/serialdev.txt @@ -51,6 +51,14 @@ Note: non-standard device types must have LONGER THAN 4 characters of DEVTYP Description: resets the device Returns: none + DEVSTU + +Description: reads status of the device, if applicable +Returns: + 0x06 <0x1F> 0x17 + 0x15 <0x1F> 0x17 +(see section 1.0) + 2. Device-specific commands 2.0 Command formats diff --git a/src/net/torvald/tsvm/FilesystemDelegate.kt b/src/net/torvald/tsvm/FilesystemDelegate.kt new file mode 100644 index 0000000..dd6d546 --- /dev/null +++ b/src/net/torvald/tsvm/FilesystemDelegate.kt @@ -0,0 +1,63 @@ +package net.torvald.tsvm + +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint +import java.io.InputStream + +class VmFilesystemDelegate(val vm: VM, val portNo: Int) { + + fun getFileInputStream(path: String) = DiskDriveFileInputStream(vm, portNo, path) + +} + +class DiskDriveFileInputStream(vm: VM, portNo: Int, path: String) : InputStream() { + + private val contents = SerialHelper.sendMessageGetBytes(vm, portNo, "OPENR\"$path\"".toByteArray(VM.CHARSET)) + private var readCursor = 0 + + init { + contents.toString() // meaningless statement to NOT lazy eval the property + } + + override fun markSupported() = true + + override fun read(): Int { + if (readCursor >= contents.size) return -1 + val ret = contents[readCursor].toUint() + readCursor += 1 + return ret + } + + override fun skip(n: Long): Long { + val newReadCursor = minOf(contents.size.toLong(), readCursor + n) + val diff = newReadCursor - readCursor + readCursor = newReadCursor.toInt() + return diff + } + + override fun reset() { + readCursor = 0 + } + + override fun mark(i: Int) { + TODO() + } + + override fun read(p0: ByteArray): Int { + var readBytes = 0 + for (k in p0.indices) { + val r = read() + p0[k] = r.toByte() + if (r >= 0) readBytes += 1 + } + + return readBytes + } + + override fun read(p0: ByteArray, p1: Int, p2: Int): Int { + TODO() + } + + override fun close() { + TODO() + } +} \ No newline at end of file diff --git a/src/net/torvald/tsvm/SerialHelper.kt b/src/net/torvald/tsvm/SerialHelper.kt new file mode 100644 index 0000000..33adb4f --- /dev/null +++ b/src/net/torvald/tsvm/SerialHelper.kt @@ -0,0 +1,89 @@ +package net.torvald.tsvm + +import net.torvald.UnsafeHelper +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint +import net.torvald.tsvm.peripheral.BlockTransferInterface.Companion.BLOCK_SIZE +import java.io.ByteArrayOutputStream +import kotlin.experimental.and +import kotlin.experimental.or + +object SerialHelper { + + private const val SLEEP_TIME = 4L + + fun sendMessageGetBytes(vm: VM, portNo: Int, message: ByteArray): ByteArray { + sendMessage(vm, portNo, message) + + // wait until it is ready + while (!checkIfDeviceIsReady(vm, portNo)) { Thread.sleep(SLEEP_TIME) } + + return getMessage(vm, portNo) + } + + fun sendMessage(vm: VM, portNo: Int, message: ByteArray) { + if (!checkIfDeviceIsThere(vm, portNo)) throw IllegalStateException("Device not connected") + if (message.size > BLOCK_SIZE) throw NotImplementedError("sending message greater than 4096 is a future work :p") + + UnsafeHelper.memcpyRaw( + message, UnsafeHelper.getArrayOffset(message), + null, vm.getIO().blockTransferTx[portNo].ptr, + minOf(BLOCK_SIZE, message.size).toLong() + ) + initiateWriting(vm, portNo) + } + + fun getMessage(vm: VM, portNo: Int): ByteArray { + val msgBuffer = ByteArrayOutputStream(BLOCK_SIZE) + + // pull all the blocks of messages + do { + initiateReading(vm, portNo) + while (!checkIfDeviceIsReady(vm, portNo)) { Thread.sleep(SLEEP_TIME) } + + val transStat = getBlockTransferStatus(vm, portNo) + val incomingMsg = ByteArray(transStat.first) + + UnsafeHelper.memcpyRaw( + null, vm.getIO().blockTransferRx[portNo].ptr, + incomingMsg, UnsafeHelper.getArrayOffset(incomingMsg), + transStat.first.toLong() + ) + + msgBuffer.write(incomingMsg) + } while (getBlockTransferStatus(vm, portNo).second) + + return msgBuffer.toByteArray() + } + + + private fun checkIfDeviceIsThere(vm: VM, portNo: Int) = + (vm.getIO().mmio_read(4092L + portNo)!! and 1.toByte()) == 1.toByte() + + private fun checkIfDeviceIsReady(vm: VM, portNo: Int) = + (vm.getIO().mmio_read(4092L + portNo)!! and 0b110.toByte()) == 0b011.toByte() + + private fun initiateWriting(vm: VM, portNo: Int) { + vm.getIO().mmio_write(4092L + portNo, 0b1110) + } + + private fun initiateReading(vm: VM, portNo: Int) { + vm.getIO().mmio_write(4092L + portNo, 0b0110) + } + + private fun setBlockTransferStatus(vm: VM, portNo: Int, blockSize: Int, moreToSend: Boolean = false) { + vm.getIO().mmio_write(4084L + (portNo * 2), (blockSize and 255).toByte()) + vm.getIO().mmio_write(4085L + (portNo * 2), + ((blockSize ushr 8).and(15) or (moreToSend.toInt() shl 7)).toByte() + ) + } + + private fun getBlockTransferStatus(vm: VM, portNo: Int): Pair { + val bits = vm.getIO().mmio_read(4084L + (portNo * 2))!!.toUint() or + (vm.getIO().mmio_read(4085L + (portNo * 2))!!.toUint() shl 8) + val rawcnt = bits.and(BLOCK_SIZE - 1) + return (if (rawcnt == 0) BLOCK_SIZE else rawcnt) to (bits < 0) + } + + + private fun Boolean.toInt() = if (this) 1 else 0 +} \ No newline at end of file diff --git a/src/net/torvald/tsvm/UnsafePtr.kt b/src/net/torvald/tsvm/UnsafePtr.kt index 1562fe5..f898f8f 100644 --- a/src/net/torvald/tsvm/UnsafePtr.kt +++ b/src/net/torvald/tsvm/UnsafePtr.kt @@ -44,7 +44,7 @@ internal object UnsafeHelper { * * @return offset from the array's base memory address (aka pointer) that the actual data begins. */ - fun getArrayOffset(obj: Any) = unsafe.arrayBaseOffset(obj.javaClass) + fun getArrayOffset(obj: Any) = unsafe.arrayBaseOffset(obj.javaClass).toLong() } /** diff --git a/src/net/torvald/tsvm/VM.kt b/src/net/torvald/tsvm/VM.kt index cff3e06..99b843b 100644 --- a/src/net/torvald/tsvm/VM.kt +++ b/src/net/torvald/tsvm/VM.kt @@ -121,6 +121,8 @@ class VM( companion object { + val CHARSET = Charsets.ISO_8859_1 + val MMIO_SIZE = 128.kB() val HW_RESERVE_SIZE = 1024.kB() val USER_SPACE_SIZE = 8192.kB() diff --git a/src/net/torvald/tsvm/VMJSR223Delegate.kt b/src/net/torvald/tsvm/VMJSR223Delegate.kt index 5e0d271..9148ef2 100644 --- a/src/net/torvald/tsvm/VMJSR223Delegate.kt +++ b/src/net/torvald/tsvm/VMJSR223Delegate.kt @@ -8,8 +8,6 @@ import java.nio.charset.Charset */ class VMJSR223Delegate(val vm: VM) { - private val SYSTEM_CHARSET = Charsets.ISO_8859_1 - fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte()) fun peek(addr: Int) = vm.peek(addr.toLong())!!.toInt().and(255) fun nanoTime() = System.nanoTime() @@ -35,11 +33,11 @@ class VMJSR223Delegate(val vm: VM) { fun print(s: String) { //System.out.print("[Nashorn] $s") - vm.getPrintStream().write(s.toByteArray(SYSTEM_CHARSET)) + vm.getPrintStream().write(s.toByteArray(VM.CHARSET)) } fun println(s: String) { System.out.println("[Nashorn] $s") - vm.getPrintStream().write((s + '\n').toByteArray(SYSTEM_CHARSET)) + vm.getPrintStream().write((s + '\n').toByteArray(VM.CHARSET)) } fun println() = print('\n') diff --git a/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt b/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt index 36104d1..2ddc77c 100644 --- a/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt +++ b/src/net/torvald/tsvm/peripheral/BlockTransferInterface.kt @@ -4,8 +4,8 @@ abstract class BlockTransferInterface(val isMaster: Boolean, val isSlave: Boolea protected var recipient: BlockTransferInterface? = null - open var ready = true - open var busy = false + open @Volatile var ready = true + open @Volatile var busy = false protected var sendmode = false; private set open var blockSize = 0 @@ -68,5 +68,9 @@ abstract class BlockTransferInterface(val isMaster: Boolean, val isSlave: Boolea companion object { const val BLOCK_SIZE = 4096 + const val GOOD_NEWS = 0x06.toByte() + const val BAD_NEWS = 0x15.toByte() + const val UNIT_SEP = 0x1F.toByte() + const val END_OF_SEND_BLOCK = 0x17.toByte() } } \ No newline at end of file diff --git a/src/net/torvald/tsvm/peripheral/BlockTransferPort.kt b/src/net/torvald/tsvm/peripheral/BlockTransferPort.kt index 2531bdb..8f6af80 100644 --- a/src/net/torvald/tsvm/peripheral/BlockTransferPort.kt +++ b/src/net/torvald/tsvm/peripheral/BlockTransferPort.kt @@ -6,7 +6,7 @@ import net.torvald.tsvm.VM /** * Implementation of single COM port */ -internal class BlockTransferPort(val vm: VM, val portno: Int) : BlockTransferInterface(true, false) { +class BlockTransferPort(val vm: VM, val portno: Int) : BlockTransferInterface(true, false) { internal var hasNext = false @@ -22,7 +22,7 @@ internal class BlockTransferPort(val vm: VM, val portno: Int) : BlockTransferInt override fun writeout(inputData: ByteArray) { writeout(inputData) { val copySize = minOf(BLOCK_SIZE, inputData.size).toLong() - val arrayOffset = UnsafeHelper.getArrayOffset(inputData).toLong() + val arrayOffset = UnsafeHelper.getArrayOffset(inputData) UnsafeHelper.memcpyRaw(inputData, arrayOffset, null, vm.getIO().blockTransferRx[portno].ptr, copySize) } } diff --git a/src/net/torvald/tsvm/peripheral/IOSpace.kt b/src/net/torvald/tsvm/peripheral/IOSpace.kt index fdf285c..a51555c 100644 --- a/src/net/torvald/tsvm/peripheral/IOSpace.kt +++ b/src/net/torvald/tsvm/peripheral/IOSpace.kt @@ -112,7 +112,7 @@ class IOSpace(val vm: VM) : PeriBase, InputProcessor { in 786432..917503 -> vm.peripheralTable[6].peripheral?.mmio_read(addr - 786432) in 917504..1048575 -> vm.peripheralTable[7].peripheral?.mmio_read(addr - 917504) - else -> -1 + else -> null } } diff --git a/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt b/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt index 83a7a0c..c9083c6 100644 --- a/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt +++ b/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt @@ -1,50 +1,83 @@ package net.torvald.tsvm.peripheral +import net.torvald.tsvm.VM +import java.io.ByteArrayOutputStream import java.io.File +import java.io.IOException import java.util.* class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, true) { + companion object { + const val STATE_CODE_STANDBY = 0 + const val STATE_CODE_ILLEGAL_COMMAND = 128 + const val STATE_CODE_FILE_NOT_FOUND = 129 + const val STATE_CODE_SYSTEM_IO_ERROR = 192 + + + val errorMsgs = Array(256) { "" } + + init { + errorMsgs[STATE_CODE_STANDBY] = "READY" + errorMsgs[STATE_CODE_ILLEGAL_COMMAND] = "SYNTAX ERROR" + errorMsgs[STATE_CODE_FILE_NOT_FOUND] = "FILE NOT FOUND" + errorMsgs[STATE_CODE_SYSTEM_IO_ERROR] = "IO ERROR ON SIMULATED DRIVE" + } + } + + fun composePositiveAns(vararg msg: String): ByteArray { + val sb = ArrayList() + sb.add(GOOD_NEWS) + sb.addAll(msg[0].toByteArray().toTypedArray()) + for (k in 1 until msg.lastIndex) { + sb.add(UNIT_SEP) + sb.addAll(msg[k].toByteArray().toTypedArray()) + } + sb.add(END_OF_SEND_BLOCK) + return sb.toByteArray() + } + + fun composeNegativeAns(vararg msg: String): ByteArray { + val sb = ArrayList() + sb.add(BAD_NEWS) + sb.addAll(msg[0].toByteArray().toTypedArray()) + for (k in 1 until msg.lastIndex) { + sb.add(UNIT_SEP) + sb.addAll(msg[k].toByteArray().toTypedArray()) + } + sb.add(END_OF_SEND_BLOCK) + return sb.toByteArray() + } + private var fileOpen = false - private var readModeLength = -1 + private var file: File? = null + //private var readModeLength = -1 // always 4096 + private var stateCode = STATE_CODE_STANDBY + + private val messageComposeBuffer = ByteArrayOutputStream(BLOCK_SIZE) // always use this and don't alter blockSendBuffer please + private var blockSendBuffer = ByteArray(1) + private var blockSendCount = 0 private val rootPath = File("test_assets/test_drive_$driveNum") + init { if (!rootPath.exists()) { rootPath.mkdirs() } } - fun composePositiveAns(vararg msg: String): ByteArray { - val sb = ArrayList() - sb.add(0x06) - sb.addAll(msg[0].toByteArray().toTypedArray()) - for (k in 1 until msg.lastIndex) { - sb.add(0x1F) - sb.addAll(msg[k].toByteArray().toTypedArray()) - } - sb.add(0x17) - return sb.toByteArray() + + private fun resetBuf() { + blockSendCount = 0 + messageComposeBuffer.reset() } - fun composeNegativeAns(vararg msg: String): ByteArray { - val sb = ArrayList() - sb.add(0x15) - sb.addAll(msg[0].toByteArray().toTypedArray()) - for (k in 1 until msg.lastIndex) { - sb.add(0x1F) - sb.addAll(msg[k].toByteArray().toTypedArray()) - } - sb.add(0x17) - return sb.toByteArray() - } override fun hasNext(): Boolean { - if (!fileOpen) return false - return false + return (blockSendCount * BLOCK_SIZE <= blockSendBuffer.size) } /** Computer's attempt to startRead() will result in calling this very function. @@ -52,7 +85,23 @@ class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, t * Disk drive must send prepared message (or file transfer packet) to the computer. */ override fun startSend() { - TODO("Not yet implemented") + recipient?.let { recipient -> + if (blockSendCount == 0) { + blockSendBuffer = messageComposeBuffer.toByteArray() + } + + recipient.writeout(ByteArray(BLOCK_SIZE) { + val i = (blockSendCount + 1) * BLOCK_SIZE + if (i + it >= blockSendBuffer.size) { + 0.toByte() + } + else { + blockSendBuffer[i + it] + } + }) + + blockSendCount += 1 + } } /** Computer's attempt to startSend() will result in calling this very function. @@ -61,40 +110,117 @@ class TestDiskDrive(private val driveNum: Int) : BlockTransferInterface(false, t * Disk drive must create desired side effects in accordance with the input message. */ override fun writeout(inputData: ByteArray) { + ready = false + busy = true + + val inputString = inputData.toString() - if (inputString.startsWith("DEVRST\u0017")) { - readModeLength = -1 + if (inputString.startsWith("DEVRST$END_OF_SEND_BLOCK")) { + //readModeLength = -1 fileOpen = false + file = null + blockSendCount = 0 + stateCode = STATE_CODE_STANDBY } - else if (inputString.startsWith("DEVTYP\u0017")) - startSend { it.writeout(composePositiveAns("STOR")) } - else if (inputString.startsWith("DEVNAM\u0017")) - startSend { it.writeout(composePositiveAns("Testtec Virtual Disk Drive")) } - else if (inputString.startsWith("OPENR\"")) { + else if (inputString.startsWith("DEVSTU$END_OF_SEND_BLOCK")) { + if (stateCode < 128) { + recipient?.writeout(composePositiveAns("${stateCode.toChar()}", errorMsgs[stateCode])) + //startSend { it.writeout(composePositiveAns("${stateCode.toChar()}", errorMsgs[stateCode])) } + } + else { + startSend { it.writeout(composeNegativeAns("${stateCode.toChar()}", errorMsgs[stateCode])) } + } + } + else if (inputString.startsWith("DEVTYP$END_OF_SEND_BLOCK")) + //startSend { it.writeout(composePositiveAns("STOR")) } + recipient?.writeout(composePositiveAns("STOR")) + else if (inputString.startsWith("DEVNAM$END_OF_SEND_BLOCK")) + //startSend { it.writeout(composePositiveAns("Testtec Virtual Disk Drive")) } + recipient?.writeout(composePositiveAns("Testtec Virtual Disk Drive")) + else if (inputString.startsWith("OPENR\"") || inputString.startsWith("OPENW\"") || inputString.startsWith("OPENA\"")) { + 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)) + } else if (inputString.startsWith("CLOSE")) { - - - + file = null fileOpen = false } else if (inputString.startsWith("READ")) { + //readModeLength = inputString.substring(4 until inputString.length).toInt() - - - readModeLength = inputString.substring(4 until inputString.length).toInt() - } - else if (inputString.startsWith("LIST")) { - startSend { it.writeout("\"LOREM.TXT\" TXT\nTotal 1 files on the disk".toByteArray()) } + resetBuf() + if (file?.isFile == true) { + try { + messageComposeBuffer.write(file!!.readBytes()) + stateCode = STATE_CODE_STANDBY + } + catch (e: IOException) { + stateCode = STATE_CODE_SYSTEM_IO_ERROR + } + } } + + + ready = true + busy = false } val diskID: UUID = UUID(0, 0) + private fun getReadableLs(): String { + if (file == null) throw IllegalStateException("No file is opened") + + val sb = StringBuilder() + + if (file!!.isFile) sb.append(file!!.name) + else { + sb.append(".\n") + if (file!!.absolutePath != rootPath.absolutePath) sb.append("..\n") + // actual entries + file!!.listFiles()!!.forEach { + var filenameLen = it.name.length + sb.append(it.name) + if (it.isDirectory) { + sb.append("/") + filenameLen += 1 + } + sb.append(" ".repeat(40 - filenameLen)) + if (it.isFile) { + sb.append("${it.length()} B") + } + } + sb.append('\n') + } + + return sb.toString() + } + + private fun sanitisePath(s: String) = s.replace('\\','/').replace(Regex("""\?<>:\*\|"""),"-") } \ No newline at end of file