diff --git a/assets/disk0/tvdos/HSDPADRV.SYS b/assets/disk0/tvdos/HSDPADRV.SYS new file mode 100644 index 0000000..f0e16ca --- /dev/null +++ b/assets/disk0/tvdos/HSDPADRV.SYS @@ -0,0 +1,271 @@ +/////////////////////////////////////////////////////////////////////////////// +// High Speed Disk Peripheral Adapter (HSDPA) Driver for TVDOS +// This driver treats each disk from HSDPA as a single large file +// Created by Claude on 2025-08-16 +/////////////////////////////////////////////////////////////////////////////// + +// Add TAPE device names to reserved names +files.reservedNames.push("TAPE0", "TAPE1", "TAPE2", "TAPE3") + +// HSDPA peripheral addresses for slot 4 +// MMIO area (for registers): -524289 to -655360 (backwards!) +// Memory Space area (for buffer): -4194305 to -5242880 (backwards!) +const HSDPA_SLOT4_MMIO_START = -524289 // MMIO area start +const HSDPA_REG_DISK1_STATUS = HSDPA_SLOT4_MMIO_START - 0 +const HSDPA_REG_DISK2_STATUS = HSDPA_SLOT4_MMIO_START - 3 +const HSDPA_REG_DISK3_STATUS = HSDPA_SLOT4_MMIO_START - 6 +const HSDPA_REG_DISK4_STATUS = HSDPA_SLOT4_MMIO_START - 9 +const HSDPA_REG_DISK_CONTROL = HSDPA_SLOT4_MMIO_START - 12 +const HSDPA_REG_ACTIVE_DISK = HSDPA_SLOT4_MMIO_START - 20 +const HSDPA_REG_SEQ_IO_CONTROL = HSDPA_SLOT4_MMIO_START - 256 +const HSDPA_REG_SEQ_IO_OPCODE = HSDPA_SLOT4_MMIO_START - 258 +const HSDPA_REG_SEQ_IO_ARG1 = HSDPA_SLOT4_MMIO_START - 259 +const HSDPA_REG_SEQ_IO_ARG2 = HSDPA_SLOT4_MMIO_START - 262 + +// Sequential I/O opcodes +const HSDPA_OPCODE_NOP = 0x00 +const HSDPA_OPCODE_SKIP = 0x01 +const HSDPA_OPCODE_READ = 0x02 +const HSDPA_OPCODE_WRITE = 0x03 +const HSDPA_OPCODE_REWIND = 0xF0 +const HSDPA_OPCODE_TERMINATE = 0xFF + +// Helper functions for HSDPA MMIO access +const hsdpa = {} + +hsdpa.setActiveDisk = (diskNumber) => { + sys.poke(HSDPA_REG_ACTIVE_DISK, diskNumber) // 1-based +} + +hsdpa.getActiveDisk = () => { + return sys.peek(HSDPA_REG_ACTIVE_DISK) +} + +hsdpa.enableSequentialIO = () => { + sys.poke(HSDPA_REG_SEQ_IO_CONTROL, 0x01) +} + +hsdpa.disableSequentialIO = () => { + sys.poke(HSDPA_REG_SEQ_IO_CONTROL, 0x00) +} + +hsdpa.rewind = () => { + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_REWIND) +} + +hsdpa.skip = (bytes) => { + // Write arg1 (3 bytes, little endian) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2 + // Execute skip operation + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_SKIP) +} + +hsdpa.readToMemory = (bytes, vmMemoryPointer) => { + serial.println(`HSDPA: readToMemory(${bytes}, ${vmMemoryPointer})`) + // Write arg1 (bytes to read) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2 + // Write arg2 (VM memory pointer) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG2, vmMemoryPointer & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG2 - 1, (vmMemoryPointer >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG2 - 2, (vmMemoryPointer >> 16) & 0xFF) // MSB2 + // Execute read operation + serial.println(`HSDPA: Executing read opcode...`) + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_READ) + serial.println(`HSDPA: Read opcode executed`) +} + +hsdpa.writeFromMemory = (bytes, vmMemoryPointer) => { + // Write arg1 (bytes to write) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2 + // Write arg2 (VM memory pointer) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG2, vmMemoryPointer & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG2 - 1, (vmMemoryPointer >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG2 - 2, (vmMemoryPointer >> 16) & 0xFF) // MSB2 + // Execute write operation + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_WRITE) +} + +hsdpa.terminate = () => { + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_TERMINATE) +} + +// Helper to get disk number from device name (TAPE0 -> 1, TAPE1 -> 2, etc.) +hsdpa.getHsdpaDiskNumber = (deviceName) => { + if (deviceName.startsWith("TAPE")) { + let diskIndex = parseInt(deviceName.substring(4)) + return diskIndex + 1 // HSDPA uses 1-based disk numbers + } + return 0 +} + +// State tracking for open files +hsdpa.openFiles = {} + +// Create TAPE device drivers +for (let tapeIndex = 0; tapeIndex < 4; tapeIndex++) { + let deviceName = `DEVTAPE${tapeIndex}` + + _TVDOS.DRV.FS[deviceName] = {} + + let driver = _TVDOS.DRV.FS[deviceName] + + // Get file size - for HSDPA tapes, we don't know the size ahead of time + // So we return a very large number to indicate it's available + driver.getFileLen = (fd) => { + return 0x7FFFFFFF // Return max positive 32-bit integer + } + + // Sequential read from tape + driver.pread = (fd, ptr, count, offset) => { + let diskNumber = hsdpa.getHsdpaDiskNumber(fd._driverID.substring(3)) // Remove "DEV" prefix + + // Set active disk + hsdpa.setActiveDisk(diskNumber) + hsdpa.enableSequentialIO() + + try { + // If offset specified, rewind and skip to position + if (offset && offset > 0) { + hsdpa.rewind() + hsdpa.skip(offset) + } + + // Read data to VM memory + hsdpa.readToMemory(count, ptr) + } catch (e) { + console.error("HSDPA read error:", e) + // Fill with zeros on error + for (let i = 0; i < count; i++) { + sys.poke(ptr + i, 0) + } + } + } + + // Binary read - reads entire file into byte array + driver.bread = (fd) => { + let diskNumber = hsdpa.getHsdpaDiskNumber(fd._driverID.substring(3)) + + // For simplicity, read a reasonable chunk size + let chunkSize = 65536 // 64KB + let tempPtr = sys.malloc(chunkSize) + + try { + hsdpa.setActiveDisk(diskNumber) + hsdpa.enableSequentialIO() + hsdpa.rewind() + hsdpa.readToMemory(chunkSize, tempPtr) + + // Copy to JavaScript array + let result = new Int8Array(chunkSize) + for (let i = 0; i < chunkSize; i++) { + result[i] = sys.peek(tempPtr + i) + } + + return result + } finally { + sys.free(tempPtr) + } + } + + // String read - reads file as string + driver.sread = (fd) => { + let bytes = driver.bread(fd) + let result = "" + for (let i = 0; i < bytes.length && bytes[i] !== 0; i++) { + result += String.fromCharCode(bytes[i] & 0xFF) + } + return result + } + + // Write operations (not supported for tape devices) + driver.pwrite = (fd, ptr, count, offset) => { + throw Error("Write operations not supported on HSDPA tape devices") + } + + driver.bwrite = (fd, bytes) => { + throw Error("Write operations not supported on HSDPA tape devices") + } + + driver.swrite = (fd, string) => { + throw Error("Write operations not supported on HSDPA tape devices") + } + + driver.pappend = (fd, ptr, count) => { + throw Error("Append operations not supported on HSDPA tape devices") + } + + driver.bappend = (fd, bytes) => { + throw Error("Append operations not supported on HSDPA tape devices") + } + + driver.sappend = (fd, string) => { + throw Error("Append operations not supported on HSDPA tape devices") + } + + // File operations + driver.flush = (fd) => { + // No-op for read-only tape + } + + driver.close = (fd) => { + let diskNumber = hsdpa.getHsdpaDiskNumber(fd._driverID.substring(3)) + hsdpa.setActiveDisk(diskNumber) + hsdpa.terminate() + hsdpa.disableSequentialIO() + + // Remove from open files tracking + delete hsdpa.openFiles[fd._driverID] + } + + // Directory operations (tapes are single files, not directories) + driver.isDirectory = (fd) => { + return false + } + + driver.listFiles = (fd) => { + return undefined // Not a directory + } + + // File management operations (not supported) + driver.touch = (fd) => { + throw Error("Touch operation not supported on HSDPA tape devices") + } + + driver.mkDir = (fd) => { + throw Error("Directory creation not supported on HSDPA tape devices") + } + + driver.mkFile = (fd) => { + throw Error("File creation not supported on HSDPA tape devices") + } + + driver.remove = (fd) => { + throw Error("File removal not supported on HSDPA tape devices") + } + + // Check if tape exists (always true if disk is attached) + driver.exists = (fd) => { + let diskNumber = hsdpa.getHsdpaDiskNumber(fd._driverID.substring(3)) + + // Try to set active disk and check if it responds + try { + hsdpa.setActiveDisk(diskNumber) + // If we can read the active disk register back, the disk exists + return hsdpa.getActiveDisk() === diskNumber + } catch (e) { + return false + } + } + + // Freeze the driver object + Object.freeze(driver) +} + +// Print initialization message +serial.println("HSDPA Driver loaded: TAPE0-TAPE3 devices available at $:/TAPE0/, $:/TAPE1/, $:/TAPE2/, $:/TAPE3/") \ No newline at end of file diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index e050cd1..2297c99 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -1388,7 +1388,7 @@ Object.freeze(unicode); /////////////////////////////////////////////////////////////////////////////// // patch con to use VTs -con.move = function(y, x) { +/*con.move = function(y, x) { let activeVT = _TVDOS.ACTIVE_VT || 0 // 0 is physical terminal let vt = _TVDOS.VT_CONTEXTS[activeVT] @@ -1400,7 +1400,7 @@ con.getyx = function() { let vt = _TVDOS.VT_CONTEXTS[activeVT] return vt.getCursorYX() -}; +};*/ /////////////////////////////////////////////////////////////////////////////// @@ -1516,6 +1516,16 @@ var execApp = (cmdsrc, args, appname) => { /////////////////////////////////////////////////////////////////////////////// +// Load HSDPA driver +try { + let hsdpadrvFile = files.open("A:/tvdos/HSDPADRV.SYS") + if (hsdpadrvFile.exists) { + eval(hsdpadrvFile.sread()) + } +} catch (e) { + serial.println("Warning: Could not load HSDPA driver: " + e.message) +} + // Boot script serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`); diff --git a/assets/disk0/tvdos/bin/playmov.js b/assets/disk0/tvdos/bin/playmov.js index 7557248..3f7470f 100644 --- a/assets/disk0/tvdos/bin/playmov.js +++ b/assets/disk0/tvdos/bin/playmov.js @@ -17,24 +17,40 @@ graphics.clearPixels(255) graphics.clearPixels2(240) -let seqread = require("seqread") -seqread.prepare(fullFilePath.full) +let seqreadserial = require("seqread") +let seqreadtape = require("seqreadtape") +let seqread = undefined +let fullFilePathStr = fullFilePath.full + +// select seqread driver to use +if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) { + seqread = seqreadtape + seqread.seek(0) +} +else { + seqread = seqreadserial +} + +seqread.prepare(fullFilePathStr) let magic = seqread.readBytes(8) let magicMatching = true +let actualMagic = [] + // check if magic number matches MAGIC.forEach((b,i) => { let testb = sys.peek(magic + i) & 255 // for some reason this must be located here + actualMagic.push(testb) if (testb != b) { magicMatching = false } }) sys.free(magic) if (!magicMatching) { - println("Not a movie file (MAGIC mismatch)") + println("Not a movie file (MAGIC mismatch) -- got " + actualMagic.join()) return 1 } diff --git a/assets/disk0/tvdos/include/seqreadtape.mjs b/assets/disk0/tvdos/include/seqreadtape.mjs new file mode 100644 index 0000000..7f48d84 --- /dev/null +++ b/assets/disk0/tvdos/include/seqreadtape.mjs @@ -0,0 +1,263 @@ +// Sequential reader for HSDPA TAPE devices +// Unlike seqread.mjs which is limited to 4096 bytes per read due to serial communication, +// this module can read larger chunks efficiently from HSDPA devices. + +let readCount = 0 +let currentTapeDevice = undefined +let fileDescriptor = undefined +let fileHeader = new Uint8Array(65536) // Much larger header buffer than serial version + +// HSDPA peripheral addresses for slot 4 +// MMIO area (for registers): -524289 to -655360 (backwards!) +const HSDPA_SLOT4_MMIO_START = -524289 // MMIO area start +const HSDPA_REG_ACTIVE_DISK = HSDPA_SLOT4_MMIO_START - 20 +const HSDPA_REG_SEQ_IO_CONTROL = HSDPA_SLOT4_MMIO_START - 256 +const HSDPA_REG_SEQ_IO_OPCODE = HSDPA_SLOT4_MMIO_START - 258 +const HSDPA_REG_SEQ_IO_ARG1 = HSDPA_SLOT4_MMIO_START - 259 +const HSDPA_REG_SEQ_IO_ARG2 = HSDPA_SLOT4_MMIO_START - 262 + +// Sequential I/O opcodes +const HSDPA_OPCODE_NOP = 0x00 +const HSDPA_OPCODE_SKIP = 0x01 +const HSDPA_OPCODE_READ = 0x02 +const HSDPA_OPCODE_WRITE = 0x03 +const HSDPA_OPCODE_REWIND = 0xF0 +const HSDPA_OPCODE_TERMINATE = 0xFF + +// Helper functions for HSDPA MMIO access +function hsdpaSetActiveDisk(diskNumber) { + sys.poke(HSDPA_REG_ACTIVE_DISK, diskNumber) // 1-based +} + +function hsdpaEnableSequentialIO() { + sys.poke(HSDPA_REG_SEQ_IO_CONTROL, 0x01) +} + +function hsdpaDisableSequentialIO() { + sys.poke(HSDPA_REG_SEQ_IO_CONTROL, 0x00) +} + +function hsdpaRewind() { + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_REWIND) +} + +function hsdpaSkip(bytes) { + // Write arg1 (3 bytes, little endian) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2 + // Execute skip operation + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_SKIP) +} + +function hsdpaReadToMemory(bytes, vmMemoryPointer) { + // Write arg1 (bytes to read) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2 + // Write arg2 (VM memory pointer) - using backwards addressing + sys.poke(HSDPA_REG_SEQ_IO_ARG2, vmMemoryPointer & 0xFF) // LSB + sys.poke(HSDPA_REG_SEQ_IO_ARG2 - 1, (vmMemoryPointer >> 8) & 0xFF) // MSB + sys.poke(HSDPA_REG_SEQ_IO_ARG2 - 2, (vmMemoryPointer >> 16) & 0xFF) // MSB2 + // Execute read operation + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_READ) +} + +function hsdpaTerminate() { + sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_TERMINATE) +} + +function prepare(fullPath) { + // Parse tape device path like "$:/TAPE0/" + if (!fullPath.startsWith("$:/TAPE") && !fullPath.startsWith("$:\\TAPE")) { + throw Error("seqreadtape: Expected TAPE device path like $:\\TAPE0, got '" + fullPath + "'") + } + + readCount = 0 + + // Extract tape number from path (TAPE0 -> 0, TAPE1 -> 1, etc.) + let tapeMatch = fullPath.match(/\$:[/\\]TAPE([0-9]+)/) + if (!tapeMatch) { + throw Error("seqreadtape: Invalid TAPE device path format: " + fullPath) + } + + let tapeNumber = parseInt(tapeMatch[1]) + if (tapeNumber < 0 || tapeNumber > 3) { + throw Error("seqreadtape: TAPE device number must be 0-3, got " + tapeNumber) + } + + currentTapeDevice = tapeNumber + + try { + // Open the tape device using TVDOS file system + fileDescriptor = files.open(fullPath) + if (!fileDescriptor.exists) { + throw Error(`seqreadtape: TAPE device ${tapeNumber} not available or no file attached`) + } + + // Initialize HSDPA for sequential reading + hsdpaSetActiveDisk(tapeNumber + 1) // HSDPA uses 1-based disk numbers + hsdpaEnableSequentialIO() + hsdpaRewind() + + // Read file header (much larger than serial version) + let headerPtr = sys.malloc(fileHeader.length) + try { + hsdpaReadToMemory(fileHeader.length, headerPtr) + + // Copy header to our buffer + for (let i = 0; i < fileHeader.length; i++) { + fileHeader[i] = sys.peek(headerPtr + i) & 0xFF + } + } finally { + sys.free(headerPtr) + } + + // Reset position for actual reading + hsdpaRewind() + readCount = 0 + + return 0 + + } catch (e) { + throw Error("seqreadtape: Failed to prepare TAPE device: " + e.message) + } +} + +function readBytes(length, ptrToDecode) { + if (length <= 0) return + + let ptr = (ptrToDecode === undefined) ? sys.malloc(length) : ptrToDecode + + try { + // Skip to current read position if needed + if (readCount > 0) { + hsdpaRewind() + if (readCount > 0) { + hsdpaSkip(readCount) + } + } + + // Read directly using HSDPA - no 4096 byte limitation! + hsdpaReadToMemory(length, ptr) + + readCount += length + + return ptr + + } catch (e) { + if (ptrToDecode === undefined) { + sys.free(ptr) + } + throw Error("seqreadtape: Failed to read bytes: " + e.message) + } +} + +function readInt() { + let b = readBytes(4) + let i = (sys.peek(b) & 0xFF) | + ((sys.peek(b+1) & 0xFF) << 8) | + ((sys.peek(b+2) & 0xFF) << 16) | + ((sys.peek(b+3) & 0xFF) << 24) + sys.free(b) + return i +} + +function readShort() { + let b = readBytes(2) + let i = (sys.peek(b) & 0xFF) | ((sys.peek(b+1) & 0xFF) << 8) + sys.free(b) + return i +} + +function readOneByte() { + let b = readBytes(1) + let i = sys.peek(b) & 0xFF + sys.free(b) + return i +} + +function readFourCC() { + let b = readBytes(4) + let a = [sys.peek(b) & 0xFF, sys.peek(b+1) & 0xFF, sys.peek(b+2) & 0xFF, sys.peek(b+3) & 0xFF] + sys.free(b) + let s = String.fromCharCode.apply(null, a) + if (s.length != 4) { + throw Error(`seqreadtape: FourCC is not 4 characters long (${s}; ${a.map(it=>"0x"+it.toString(16).padStart(2,'0')).join()})`) + } + return s +} + +function readString(length) { + let b = readBytes(length) + let s = "" + for (let k = 0; k < length; k++) { + s += String.fromCharCode(sys.peek(b + k) & 0xFF) + } + sys.free(b) + return s +} + +function skip(n) { + if (n <= 0) return + + // For HSDPA, we can skip efficiently without reading + hsdpaSkip(n) + readCount += n +} + +function getReadCount() { + return readCount +} + +function close() { + if (fileDescriptor) { + try { + hsdpaTerminate() + hsdpaDisableSequentialIO() + fileDescriptor.close() + } catch (e) { + // Ignore close errors + } + fileDescriptor = undefined + currentTapeDevice = undefined + readCount = 0 + } +} + +function getCurrentTapeDevice() { + return currentTapeDevice +} + +function isReady() { + return fileDescriptor !== undefined && currentTapeDevice !== undefined +} + +function seek(position) { + // Seek to absolute position + hsdpaRewind() + if (position > 0) { + hsdpaSkip(position) + } + readCount = position +} + +function rewind() { seek(0) } + +exports = { + fileHeader, + prepare, + readBytes, + readInt, + readShort, + readFourCC, + readOneByte, + readString, + skip, + getReadCount, + close, + getCurrentTapeDevice, + isReady, + // Enhanced functions + seek +} \ No newline at end of file diff --git a/terranmon.txt b/terranmon.txt index a445252..b42e257 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -842,6 +842,8 @@ MMIO High Speed Disk Peripheral Adapter (HSDPA) +An interface card to read and write to a single large disk sequentially which has no filesystem on it. + Endianness: Little MMIO @@ -903,4 +905,7 @@ NOTE: Sequential I/O will clobber the peripheral memory space. Memory Space 0..1048575 RW: Buffer for the block transfer lane - note: length of a command cannot exceed 4096 bytes + IMPLEMENTATION RECOMMENDATION: split the memory space into two 512K blocks, and when the sequential + reading reaches the second space, prepare the next bytes in the first memory space, so that the read + cursor reaches 1048576, it wraps into 0 and continue reading the content of the disk as if nothing happend. + diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt index c2f8621..0d2fb18 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/HSDPA.kt @@ -9,13 +9,13 @@ import net.torvald.tsvm.VM * * Created by minjaesong on 2025-05-06. */ -class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { +open class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { companion object { const val BUFFER_SIZE = 1048576 // 1MB buffer size const val MAX_DISKS = 4 - // MMIO register offsets + // MMIO register offsets (relative to peripheral base) const val REG_DISK1_STATUS = 0 const val REG_DISK2_STATUS = 3 const val REG_DISK3_STATUS = 6 @@ -37,6 +37,7 @@ class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { const val OPCODE_SKIP = 0x01 const val OPCODE_READ = 0x02 const val OPCODE_WRITE = 0x03 + const val OPCODE_REWIND = 0xF0 const val OPCODE_TERMINATE = 0xFF } @@ -52,16 +53,25 @@ class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { // Currently active disk (0-based index, -1 means no disk selected) private var activeDisk = -1 + + // Sequential I/O state + protected var sequentialIOActive = false + protected var sequentialIOPosition = 0L override fun peek(addr: Long): Byte? { - return if (addr in 0L until BUFFER_SIZE) + // Memory Space area - for buffer access + return if (addr in 0L until BUFFER_SIZE) { buffer[addr.toInt()] - else null + } else { + null + } } override fun poke(addr: Long, byte: Byte) { - if (addr in 0L until BUFFER_SIZE) + // Memory Space area - for buffer access + if (addr in 0L until BUFFER_SIZE) { buffer[addr.toInt()] = byte + } } override fun dispose() { @@ -96,7 +106,14 @@ class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { (activeDisk + 1).toByte() // 1-based in register } REG_SEQ_IO_CONTROL -> { - TODO() + // Return sequential I/O control flags + var flags = 0 + if (sequentialIOActive) flags = flags or 0x01 + flags.toByte() + } + REG_SEQ_IO_CONTROL + 1 -> { + // Second byte of control flags (currently unused) + 0 } REG_SEQ_IO_OPCODE -> { opcodeBuf.toByte() @@ -118,6 +135,7 @@ class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { */ override fun mmio_write(addr: Long, value: Byte) { val address = addr.toInt() + println("HSDPA: mmio_write(addr=$addr, value=0x${(value.toInt() and 0xFF).toString(16)})") when (address) { in REG_DISK1_STATUS..REG_DISK4_STATUS+2 -> { val diskIndex = (address - REG_DISK1_STATUS) / 3 @@ -136,17 +154,34 @@ class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { setActiveDisk(value.toInt() - 1) // 1-based in register } REG_SEQ_IO_CONTROL -> { - + // Set sequential I/O control flags + sequentialIOActive = (value.toInt() and 0x01) != 0 + } + REG_SEQ_IO_CONTROL + 1 -> { + // Second byte of control flags (currently unused) } REG_SEQ_IO_OPCODE -> { opcodeBuf = value.toUint() + println("HSDPA: Writing opcode 0x${value.toUint().toString(16)} to register") handleSequentialIOOpcode(value.toUint()) } in REG_SEQ_IO_ARG1..REG_SEQ_IO_ARG1+2 -> { - arg1 = arg1 or (value.toUint() shl (address - REG_SEQ_IO_ARG1) * 8) + val byteOffset = (address - REG_SEQ_IO_ARG1) + if (byteOffset == 0) { + // Reset arg1 when writing to LSB + arg1 = value.toUint() + } else { + arg1 = arg1 or (value.toUint() shl (byteOffset * 8)) + } } in REG_SEQ_IO_ARG2..REG_SEQ_IO_ARG2+2 -> { - arg2 = arg2 or (value.toUint() shl (address - REG_SEQ_IO_ARG2) * 8) + val byteOffset = (address - REG_SEQ_IO_ARG2) + if (byteOffset == 0) { + // Reset arg2 when writing to LSB + arg2 = value.toUint() + } else { + arg2 = arg2 or (value.toUint() shl (byteOffset * 8)) + } } else -> null } @@ -317,29 +352,73 @@ class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") { * Handles sequential I/O opcodes * @param opcode Opcode to handle */ - private fun handleSequentialIOOpcode(opcode: Int) { + protected open fun handleSequentialIOOpcode(opcode: Int) { + println("HSDPA: handleSequentialIOOpcode(0x${opcode.toString(16)})") when (opcode) { OPCODE_NOP -> { // No operation + println("HSDPA: NOP") } OPCODE_SKIP -> { - // Skip bytes - // Implementation depends on VM memory access + // Skip arg1 bytes in the active disk + println("HSDPA: SKIP $arg1 bytes, activeDisk=$activeDisk") + if (activeDisk in 0 until MAX_DISKS) { + sequentialIOSkip(arg1) + } } OPCODE_READ -> { - // Read bytes and store to core memory - // Implementation depends on VM memory access + // Read arg1 bytes and store to core memory at pointer arg2 + println("HSDPA: READ $arg1 bytes to pointer $arg2, activeDisk=$activeDisk") + println("HSDPA: arg1 = 0x${arg1.toString(16)}, arg2 = 0x${arg2.toString(16)}") + if (activeDisk in 0 until MAX_DISKS) { + sequentialIORead(arg1, arg2) + } } OPCODE_WRITE -> { - // Write bytes from core memory - // Implementation depends on VM memory access + // Write arg1 bytes from core memory at pointer arg2 + if (activeDisk in 0 until MAX_DISKS) { + sequentialIOWrite(arg1, arg2) + } + } + OPCODE_REWIND -> { + // Rewind to starting point + println("HSDPA: REWIND to position 0") + sequentialIOPosition = 0L } OPCODE_TERMINATE -> { // Terminate sequential I/O session - // Clear buffer or reset state as needed + sequentialIOActive = false + sequentialIOPosition = 0L + // Clear the buffer + buffer.fill(0) } } } + + /** + * Skip bytes in sequential I/O mode + */ + protected open fun sequentialIOSkip(bytes: Int) { + sequentialIOPosition += bytes + } + + /** + * Read bytes from disk to VM memory in sequential I/O mode + */ + protected open fun sequentialIORead(bytes: Int, vmMemoryPointer: Int) { + // Default implementation - subclasses should override + // For now, just advance the position + sequentialIOPosition += bytes + } + + /** + * Write bytes from VM memory to disk in sequential I/O mode + */ + protected open fun sequentialIOWrite(bytes: Int, vmMemoryPointer: Int) { + // Default implementation - subclasses should override + // For now, just advance the position + sequentialIOPosition += bytes + } /** * Attaches a disk to a specific port diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt new file mode 100644 index 0000000..19068c7 --- /dev/null +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt @@ -0,0 +1,217 @@ +package net.torvald.tsvm.peripheral + +import net.torvald.tsvm.VM +import java.io.File +import java.io.RandomAccessFile + +/** + * Host File High Speed Disk Peripheral Adapter (HostFileHSDPA) + * + * A testing version of HSDPA that uses actual files on the host computer as disk sources. + * Each disk corresponds to a single file on the host filesystem. + * + * Created by Claude on 2025-08-16. + */ +class HostFileHSDPA : HSDPA { + + // Primary constructor for Java reflection compatibility + constructor(vm: VM, hostFilePaths: Array, baudRate: java.lang.Long) : super(vm, baudRate.toLong()) { + initializeHostFiles(hostFilePaths.toList()) + } + + // Secondary constructor for Kotlin usage + constructor(vm: VM, hostFilePaths: List = emptyList(), baudRate: Long = 133_333_333L) : super(vm, baudRate) { + initializeHostFiles(hostFilePaths) + } + + // Host files for each disk slot + private val hostFiles = Array(MAX_DISKS) { null } + private val hostFilePaths = Array(MAX_DISKS) { null } + + private fun initializeHostFiles(hostFilePathsList: List) { + if (hostFilePathsList.isNotEmpty()) { + for (i in 0 until minOf(hostFilePathsList.size, MAX_DISKS)) { + val file = File(hostFilePathsList[i]) + if (file.exists() && file.isFile) { + this.hostFiles[i] = RandomAccessFile(file, "r") + this.hostFilePaths[i] = hostFilePathsList[i] + println("HostFileHSDPA: Attached file '${hostFilePathsList[i]}' to disk $i") + } else { + println("HostFileHSDPA: Warning - file '${hostFilePathsList[i]}' does not exist or is not a file") + } + } + } + } + + /** + * Attaches a host file to a disk slot + * @param diskIndex Disk slot index (0-3) + * @param filePath Path to the host file + */ + fun attachHostFile(diskIndex: Int, filePath: String) { + if (diskIndex < 0 || diskIndex >= MAX_DISKS) return + + try { + // Close existing file if any + hostFiles[diskIndex]?.close() + + // Open new file + val file = File(filePath) + if (file.exists() && file.isFile) { + hostFiles[diskIndex] = RandomAccessFile(file, "r") + hostFilePaths[diskIndex] = filePath + println("HSDPA: Attached file '$filePath' to disk $diskIndex") + } else { + println("HSDPA: Warning - file '$filePath' does not exist or is not a file") + } + } catch (e: Exception) { + println("HSDPA: Error attaching file '$filePath' to disk $diskIndex: ${e.message}") + } + } + + /** + * Detaches a host file from a disk slot + * @param diskIndex Disk slot index (0-3) + */ + fun detachHostFile(diskIndex: Int) { + if (diskIndex < 0 || diskIndex >= MAX_DISKS) return + + try { + hostFiles[diskIndex]?.close() + hostFiles[diskIndex] = null + hostFilePaths[diskIndex] = null + println("HSDPA: Detached file from disk $diskIndex") + } catch (e: Exception) { + println("HSDPA: Error detaching file from disk $diskIndex: ${e.message}") + } + } + + /** + * Gets the size of the file attached to a disk slot + * @param diskIndex Disk slot index (0-3) + * @return File size in bytes, or 0 if no file attached + */ + fun getAttachedFileSize(diskIndex: Int): Long { + if (diskIndex < 0 || diskIndex >= MAX_DISKS) return 0L + + return try { + hostFiles[diskIndex]?.length() ?: 0L + } catch (e: Exception) { + 0L + } + } + + override fun sequentialIOSkip(bytes: Int) { + sequentialIOPosition += bytes + // Clamp position to file bounds if needed + val activeDiskIndex = getActiveDiskIndex() + if (activeDiskIndex >= 0) { + val fileSize = getAttachedFileSize(activeDiskIndex) + if (sequentialIOPosition > fileSize) { + sequentialIOPosition = fileSize + } + } + } + + override fun sequentialIORead(bytes: Int, vmMemoryPointer: Int) { + println("HostFileHSDPA: sequentialIORead($bytes, $vmMemoryPointer)") + val activeDiskIndex = getActiveDiskIndex() + println("HostFileHSDPA: activeDiskIndex = $activeDiskIndex") + if (activeDiskIndex < 0 || hostFiles[activeDiskIndex] == null) { + // No file attached, just advance position + println("HostFileHSDPA: No file attached, advancing position") + sequentialIOPosition += bytes + return + } + + try { + val file = hostFiles[activeDiskIndex]!! + println("HostFileHSDPA: Seeking to position $sequentialIOPosition") + file.seek(sequentialIOPosition) + + // Read data into a temporary buffer + val readBuffer = ByteArray(bytes) + val bytesRead = file.read(readBuffer) + println("HostFileHSDPA: Read $bytesRead bytes from file") + + if (bytesRead > 0) { + // Log first few bytes for debugging + val firstBytes = readBuffer.take(8).map { (it.toInt() and 0xFF).toString(16).padStart(2, '0') }.joinToString(" ") + println("HostFileHSDPA: First bytes: $firstBytes") + + // Copy data to VM memory + for (i in 0 until bytesRead) { + vm.poke(vmMemoryPointer + i.toLong(), readBuffer[i]) + } + sequentialIOPosition += bytesRead + println("HostFileHSDPA: Copied $bytesRead bytes to VM memory at $vmMemoryPointer") + } + + // Fill remaining bytes with zeros if we read less than requested + if (bytesRead < bytes) { + for (i in bytesRead until bytes) { + vm.poke(vmMemoryPointer + i.toLong(), 0) + } + sequentialIOPosition += (bytes - bytesRead) + } + + } catch (e: Exception) { + println("HSDPA: Error reading from file: ${e.message}") + // Just advance position on error + sequentialIOPosition += bytes + } + } + + override fun sequentialIOWrite(bytes: Int, vmMemoryPointer: Int) { + val activeDiskIndex = getActiveDiskIndex() + if (activeDiskIndex < 0 || hostFiles[activeDiskIndex] == null) { + // No file attached, just advance position + sequentialIOPosition += bytes + return + } + + // For now, we only support read-only access to host files + // In a full implementation, we would write to the file here + println("HSDPA: Write operation not supported in read-only mode") + sequentialIOPosition += bytes + } + + /** + * Gets the currently active disk index + * @return Active disk index (0-3), or -1 if no disk is active + */ + private fun getActiveDiskIndex(): Int { + // Read the active disk register + val activeReg = mmio_read(REG_ACTIVE_DISK.toLong())?.toInt() ?: 0 + return if (activeReg > 0) activeReg - 1 else -1 + } + + override fun dispose() { + super.dispose() + + // Close all open files + for (i in 0 until MAX_DISKS) { + try { + hostFiles[i]?.close() + } catch (e: Exception) { + // Ignore errors during cleanup + } + } + } + + /** + * Gets information about attached files + * @return Array of file info strings + */ + fun getAttachedFilesInfo(): Array { + return Array(MAX_DISKS) { i -> + val path = hostFilePaths[i] + if (path != null) { + val size = getAttachedFileSize(i) + "Disk $i: $path (${size} bytes)" + } else { + "Disk $i: No file attached" + } + } + } +} \ No newline at end of file diff --git a/tsvm_executable/src/net/torvald/tsvm/AppLoader.java b/tsvm_executable/src/net/torvald/tsvm/AppLoader.java index a24f5b8..4713090 100644 --- a/tsvm_executable/src/net/torvald/tsvm/AppLoader.java +++ b/tsvm_executable/src/net/torvald/tsvm/AppLoader.java @@ -55,10 +55,11 @@ public class AppLoader { ArrayList defaultPeripherals = new ArrayList(); defaultPeripherals.add(new Pair(3, new PeripheralEntry2("net.torvald.tsvm.peripheral.AudioAdapter", vm))); + defaultPeripherals.add(new Pair(4, new PeripheralEntry2("net.torvald.tsvm.peripheral.HostFileHSDPA", vm, new String[]{"assets/diskMediabin/lg.mov"}, 133_333_333L))); EmulInstance reference = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", diskPath, 560, 448, defaultPeripherals); - EmulInstance referenceRemote = new EmulInstance(vm, "net.torvald.tsvm.peripheral.RemoteGraphicsAdapter", diskPath, 560, 448, defaultPeripherals); + EmulInstance referenceRemote = new EmulInstance(vm, "net.trorvald.tsvm.peripheral.RemoteGraphicsAdapter", diskPath, 560, 448, defaultPeripherals); EmulInstance reference2 = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceLikeLCD", diskPath, 560, 448, defaultPeripherals); EmulInstance term = new EmulInstance(vm, "net.torvald.tsvm.peripheral.Term", diskPath, 720, 480); EmulInstance portable = new EmulInstance(vm, "net.torvald.tsvm.peripheral.CLCDDisplay", diskPath, 1080, 436);