filesystem wip wip

This commit is contained in:
minjaesong
2020-10-21 17:56:12 +09:00
parent b00ecf7847
commit 8d5df113ab
10 changed files with 341 additions and 51 deletions

View File

@@ -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

View 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()
}
}

View 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
}

View File

@@ -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()
}
/**

View File

@@ -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()

View File

@@ -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')

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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()
resetBuf()
if (file?.isFile == true) {
try {
messageComposeBuffer.write(file!!.readBytes())
stateCode = STATE_CODE_STANDBY
}
else if (inputString.startsWith("LIST")) {
startSend { it.writeout("\"LOREM.TXT\" TXT\nTotal 1 files on the disk".toByteArray()) }
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("""\?<>:\*\|"""),"-")
}