mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
filesystem wip wip
This commit is contained in:
@@ -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 <status code> <0x1F> <message string> 0x17
|
||||
0x15 <status code> <0x1F> <message string> 0x17
|
||||
(see section 1.0)
|
||||
|
||||
2. Device-specific commands
|
||||
|
||||
2.0 Command formats
|
||||
|
||||
63
src/net/torvald/tsvm/FilesystemDelegate.kt
Normal file
63
src/net/torvald/tsvm/FilesystemDelegate.kt
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
89
src/net/torvald/tsvm/SerialHelper.kt
Normal file
89
src/net/torvald/tsvm/SerialHelper.kt
Normal file
@@ -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<Int, Boolean> {
|
||||
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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Byte>()
|
||||
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<Byte>()
|
||||
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<Byte>()
|
||||
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<Byte>()
|
||||
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("""\?<>:\*\|"""),"-")
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user