HSDPA and driver implementation

This commit is contained in:
minjaesong
2025-08-17 01:09:42 +09:00
parent e6fc6ed070
commit 1d3d3bd246
8 changed files with 886 additions and 24 deletions

View File

@@ -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/")

View File

@@ -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...`);

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String>, baudRate: java.lang.Long) : super(vm, baudRate.toLong()) {
initializeHostFiles(hostFilePaths.toList())
}
// Secondary constructor for Kotlin usage
constructor(vm: VM, hostFilePaths: List<String> = emptyList(), baudRate: Long = 133_333_333L) : super(vm, baudRate) {
initializeHostFiles(hostFilePaths)
}
// Host files for each disk slot
private val hostFiles = Array<RandomAccessFile?>(MAX_DISKS) { null }
private val hostFilePaths = Array<String?>(MAX_DISKS) { null }
private fun initializeHostFiles(hostFilePathsList: List<String>) {
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<String> {
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"
}
}
}
}

View File

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