mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-12 14:11:50 +09:00
HSDPA and driver implementation
This commit is contained in:
271
assets/disk0/tvdos/HSDPADRV.SYS
Normal file
271
assets/disk0/tvdos/HSDPADRV.SYS
Normal 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/")
|
||||||
@@ -1388,7 +1388,7 @@ Object.freeze(unicode);
|
|||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// patch con to use VTs
|
// 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 activeVT = _TVDOS.ACTIVE_VT || 0 // 0 is physical terminal
|
||||||
let vt = _TVDOS.VT_CONTEXTS[activeVT]
|
let vt = _TVDOS.VT_CONTEXTS[activeVT]
|
||||||
|
|
||||||
@@ -1400,7 +1400,7 @@ con.getyx = function() {
|
|||||||
let vt = _TVDOS.VT_CONTEXTS[activeVT]
|
let vt = _TVDOS.VT_CONTEXTS[activeVT]
|
||||||
|
|
||||||
return vt.getCursorYX()
|
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
|
// Boot script
|
||||||
serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`);
|
serial.println(`TVDOS.SYS initialised on VM ${sys.getVmId()}, running boot script...`);
|
||||||
|
|
||||||
|
|||||||
@@ -17,24 +17,40 @@ graphics.clearPixels(255)
|
|||||||
graphics.clearPixels2(240)
|
graphics.clearPixels2(240)
|
||||||
|
|
||||||
|
|
||||||
let seqread = require("seqread")
|
let seqreadserial = require("seqread")
|
||||||
seqread.prepare(fullFilePath.full)
|
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 magic = seqread.readBytes(8)
|
||||||
let magicMatching = true
|
let magicMatching = true
|
||||||
|
|
||||||
|
let actualMagic = []
|
||||||
|
|
||||||
// check if magic number matches
|
// check if magic number matches
|
||||||
MAGIC.forEach((b,i) => {
|
MAGIC.forEach((b,i) => {
|
||||||
let testb = sys.peek(magic + i) & 255 // for some reason this must be located here
|
let testb = sys.peek(magic + i) & 255 // for some reason this must be located here
|
||||||
|
actualMagic.push(testb)
|
||||||
if (testb != b) {
|
if (testb != b) {
|
||||||
magicMatching = false
|
magicMatching = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
sys.free(magic)
|
sys.free(magic)
|
||||||
if (!magicMatching) {
|
if (!magicMatching) {
|
||||||
println("Not a movie file (MAGIC mismatch)")
|
println("Not a movie file (MAGIC mismatch) -- got " + actualMagic.join())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
263
assets/disk0/tvdos/include/seqreadtape.mjs
Normal file
263
assets/disk0/tvdos/include/seqreadtape.mjs
Normal 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
|
||||||
|
}
|
||||||
@@ -842,6 +842,8 @@ MMIO
|
|||||||
|
|
||||||
High Speed Disk Peripheral Adapter (HSDPA)
|
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
|
Endianness: Little
|
||||||
|
|
||||||
MMIO
|
MMIO
|
||||||
@@ -903,4 +905,7 @@ NOTE: Sequential I/O will clobber the peripheral memory space.
|
|||||||
Memory Space
|
Memory Space
|
||||||
|
|
||||||
0..1048575 RW: Buffer for the block transfer lane
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import net.torvald.tsvm.VM
|
|||||||
*
|
*
|
||||||
* Created by minjaesong on 2025-05-06.
|
* 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 {
|
companion object {
|
||||||
const val BUFFER_SIZE = 1048576 // 1MB buffer size
|
const val BUFFER_SIZE = 1048576 // 1MB buffer size
|
||||||
const val MAX_DISKS = 4
|
const val MAX_DISKS = 4
|
||||||
|
|
||||||
// MMIO register offsets
|
// MMIO register offsets (relative to peripheral base)
|
||||||
const val REG_DISK1_STATUS = 0
|
const val REG_DISK1_STATUS = 0
|
||||||
const val REG_DISK2_STATUS = 3
|
const val REG_DISK2_STATUS = 3
|
||||||
const val REG_DISK3_STATUS = 6
|
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_SKIP = 0x01
|
||||||
const val OPCODE_READ = 0x02
|
const val OPCODE_READ = 0x02
|
||||||
const val OPCODE_WRITE = 0x03
|
const val OPCODE_WRITE = 0x03
|
||||||
|
const val OPCODE_REWIND = 0xF0
|
||||||
const val OPCODE_TERMINATE = 0xFF
|
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)
|
// Currently active disk (0-based index, -1 means no disk selected)
|
||||||
private var activeDisk = -1
|
private var activeDisk = -1
|
||||||
|
|
||||||
|
// Sequential I/O state
|
||||||
|
protected var sequentialIOActive = false
|
||||||
|
protected var sequentialIOPosition = 0L
|
||||||
|
|
||||||
override fun peek(addr: Long): Byte? {
|
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()]
|
buffer[addr.toInt()]
|
||||||
else null
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun poke(addr: Long, byte: Byte) {
|
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
|
buffer[addr.toInt()] = byte
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
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
|
(activeDisk + 1).toByte() // 1-based in register
|
||||||
}
|
}
|
||||||
REG_SEQ_IO_CONTROL -> {
|
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 -> {
|
REG_SEQ_IO_OPCODE -> {
|
||||||
opcodeBuf.toByte()
|
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) {
|
override fun mmio_write(addr: Long, value: Byte) {
|
||||||
val address = addr.toInt()
|
val address = addr.toInt()
|
||||||
|
println("HSDPA: mmio_write(addr=$addr, value=0x${(value.toInt() and 0xFF).toString(16)})")
|
||||||
when (address) {
|
when (address) {
|
||||||
in REG_DISK1_STATUS..REG_DISK4_STATUS+2 -> {
|
in REG_DISK1_STATUS..REG_DISK4_STATUS+2 -> {
|
||||||
val diskIndex = (address - REG_DISK1_STATUS) / 3
|
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
|
setActiveDisk(value.toInt() - 1) // 1-based in register
|
||||||
}
|
}
|
||||||
REG_SEQ_IO_CONTROL -> {
|
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 -> {
|
REG_SEQ_IO_OPCODE -> {
|
||||||
opcodeBuf = value.toUint()
|
opcodeBuf = value.toUint()
|
||||||
|
println("HSDPA: Writing opcode 0x${value.toUint().toString(16)} to register")
|
||||||
handleSequentialIOOpcode(value.toUint())
|
handleSequentialIOOpcode(value.toUint())
|
||||||
}
|
}
|
||||||
in REG_SEQ_IO_ARG1..REG_SEQ_IO_ARG1+2 -> {
|
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 -> {
|
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
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -317,29 +352,73 @@ class HSDPA(val vm: VM, val baudRate: Long = 133_333_333L): PeriBase("hsdpa") {
|
|||||||
* Handles sequential I/O opcodes
|
* Handles sequential I/O opcodes
|
||||||
* @param opcode Opcode to handle
|
* @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) {
|
when (opcode) {
|
||||||
OPCODE_NOP -> {
|
OPCODE_NOP -> {
|
||||||
// No operation
|
// No operation
|
||||||
|
println("HSDPA: NOP")
|
||||||
}
|
}
|
||||||
OPCODE_SKIP -> {
|
OPCODE_SKIP -> {
|
||||||
// Skip bytes
|
// Skip arg1 bytes in the active disk
|
||||||
// Implementation depends on VM memory access
|
println("HSDPA: SKIP $arg1 bytes, activeDisk=$activeDisk")
|
||||||
|
if (activeDisk in 0 until MAX_DISKS) {
|
||||||
|
sequentialIOSkip(arg1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OPCODE_READ -> {
|
OPCODE_READ -> {
|
||||||
// Read bytes and store to core memory
|
// Read arg1 bytes and store to core memory at pointer arg2
|
||||||
// Implementation depends on VM memory access
|
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 -> {
|
OPCODE_WRITE -> {
|
||||||
// Write bytes from core memory
|
// Write arg1 bytes from core memory at pointer arg2
|
||||||
// Implementation depends on VM memory access
|
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 -> {
|
OPCODE_TERMINATE -> {
|
||||||
// Terminate sequential I/O session
|
// 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
|
* Attaches a disk to a specific port
|
||||||
|
|||||||
217
tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt
Normal file
217
tsvm_core/src/net/torvald/tsvm/peripheral/HostFileHSDPA.kt
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,10 +55,11 @@ public class AppLoader {
|
|||||||
|
|
||||||
ArrayList defaultPeripherals = new ArrayList();
|
ArrayList defaultPeripherals = new ArrayList();
|
||||||
defaultPeripherals.add(new Pair(3, new PeripheralEntry2("net.torvald.tsvm.peripheral.AudioAdapter", vm)));
|
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 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 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 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);
|
EmulInstance portable = new EmulInstance(vm, "net.torvald.tsvm.peripheral.CLCDDisplay", diskPath, 1080, 436);
|
||||||
|
|||||||
Reference in New Issue
Block a user