From 718f0b60b37eef425e1a3e4853f8ec192240993e Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Mon, 24 Oct 2022 23:09:06 +0200 Subject: [PATCH] Fujinet network device --- README.md | 6 +- apple2.go | 2 +- apple2Setup.go | 19 ++++- apple2main.go | 18 +++- cardDisk2.go | 2 +- cardMemoryExpansion.go | 11 ++- cardSmartport.go | 181 ++++++++++++++++++++++++----------------- smartPortCall.go | 143 ++++++++++++++++++++++++++++++++ smartPortDevice.go | 46 ----------- smartPortFujinet.go | 121 +++++++++++++++++++++++++++ smartPortHardDisk.go | 30 +++---- 11 files changed, 431 insertions(+), 148 deletions(-) create mode 100644 smartPortCall.go delete mode 100644 smartPortDevice.go create mode 100644 smartPortFujinet.go diff --git a/README.md b/README.md index 4933531..f9cd4c4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Portable emulator of an Apple II+ or //e. Written in Go. - Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only) - SwyftCard (Apple //e only) - Useful cards not emulating a real card - - Bootable Smartport / ProDOS card + - Bootable SmartPort / ProDOS card - VidHd, limited to the ROM signature and SHR as used by Total Replay, only for //e models with 128Kb - FASTChip, limited to what Total Replay needs to set and clear fast mode - Mouse Card, emulates the entry points, not the softswitches. @@ -62,7 +62,7 @@ Portable emulator of an Apple II+ or //e. Written in Go. - Softswitch reads and writes - ProDOS MLI calls - Apple Pascal BIOS calls - - Smartport commands + - SmartPort commands - BBC MOS calls when using [Applecorn](https://github.com/bobbimanners/) - Other features: - Sound @@ -245,7 +245,7 @@ Only valid on SDL mode -traceCpu dump to the console the CPU execution. Use F11 to toggle. -traceHD - dump to the console the hd/smartport commands + dump to the console the hd/smartPort commands -traceMLI dump to the console the calls to ProDOS machine language interface calls to $BF00 -tracePascal diff --git a/apple2.go b/apple2.go index a6ce70f..0582be5 100644 --- a/apple2.go +++ b/apple2.go @@ -67,7 +67,7 @@ func (a *Apple2) Start(paused bool) { for i := 0; i < cpuSpinLoops; i++ { // Conditional tracing //pc, _ := a.cpu.GetPCAndSP() - //a.cpu.SetTrace((pc >= 0xc300 && pc <= 0xc400) || (pc >= 0xc800 && pc <= 0xce00)) + //a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800)) // Execution a.cpu.ExecuteInstruction() diff --git a/apple2Setup.go b/apple2Setup.go index d59d998..7ce320e 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -138,13 +138,24 @@ func (a *Apple2) AddDisk2Sequencer(slot int, diskImage, diskBImage string, track } // AddSmartPortDisk adds a smart port card and image -func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, trace bool) error { - c := NewCardSmartport() +func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, traceHD bool, traceSP bool) error { + c := NewCardSmartPort() + c.trace = traceSP + a.insertCard(c, slot) + + err := c.LoadImage(hdImage, traceHD) + return err +} + +// AddSmartPortDisk adds a smart port card and image +func (a *Apple2) AddFujinet(slot int, trace bool) { + c := NewCardSmartPort() c.trace = trace a.insertCard(c, slot) - err := c.LoadImage(hdImage) - return err + d := NewSmartPortFujinet(c) + d.trace = trace + c.AddDevice(0, d) } // AddVidHD adds a card with the signature of VidHD diff --git a/apple2main.go b/apple2main.go index e4364c0..da6d2f0 100644 --- a/apple2main.go +++ b/apple2main.go @@ -32,6 +32,10 @@ func MainApple() *Apple2 { "hdSlot", -1, "slot for the hard drive if present. -1 for none.") + fujinetSlot := flag.Int( + "fujinet", + -1, + "slot for the smatport card hosting the Fujinet. -1 for none.") smartPortImage := flag.String( "disk35", "", @@ -127,7 +131,11 @@ func MainApple() *Apple2 { traceHD := flag.Bool( "traceHD", false, - "dump to the console the hd/smarport commands") + "dump to the console the hd accesses") + traceSP := flag.Bool( + "traceSP", + false, + "dump to the console the smarport commands") model := flag.String( "model", "2enh", @@ -260,12 +268,16 @@ func MainApple() *Apple2 { } if *smartPortImage != "" { - err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD) + err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD, *traceSP) if err != nil { panic(err) } } + if *fujinetSlot >= 0 { + a.AddFujinet(*fujinetSlot, *traceSP) + } + if *fastChipCardSlot >= 0 { a.AddFastChip(*fastChipCardSlot) } @@ -288,7 +300,7 @@ func MainApple() *Apple2 { // If there is a hard disk image, but no slot assigned, use slot 7. *hardDiskSlot = 7 } - err := a.AddSmartPortDisk(*hardDiskSlot, hardDiskImageFinal, *traceHD) + err := a.AddSmartPortDisk(*hardDiskSlot, hardDiskImageFinal, *traceHD, *traceSP) if err != nil { panic(err) } diff --git a/cardDisk2.go b/cardDisk2.go index d3ee954..b6f7489 100644 --- a/cardDisk2.go +++ b/cardDisk2.go @@ -52,7 +52,7 @@ func NewCardDisk2(trackTracer trackTracer) *CardDisk2 { return &c } -// GetInfo returns smartport info +// GetInfo returns smartPort info func (c *CardDisk2) GetInfo() map[string]string { info := make(map[string]string) info["rom"] = "16 sector" diff --git a/cardMemoryExpansion.go b/cardMemoryExpansion.go index 14030a6..3a06dac 100644 --- a/cardMemoryExpansion.go +++ b/cardMemoryExpansion.go @@ -5,8 +5,8 @@ import "fmt" /* Apple II Memory Expansion Card - See: + http://www.apple-iigs.info/doc/fichiers/a2me.pdf http://ae.applearchives.com/files/RamFactor_Manual_1.5.pdf http://www.1000bit.it/support/manuali/apple/technotes/memx/tn.memx.1.html @@ -14,22 +14,27 @@ See: There is a self test in ROM, address Cs0A. From the RamFactor docs: + The RamFactor card has five addressable registers, which are addressed + according to the slot number the card is in: + $C080+slot * 16low byte of RAM address $C081+slot * 16middle byte of RAM address $C082+slot * 16high byte of RAM address $C083+slot * 16data at addressed location $C08F+slot * 16Firmware Bank Select After power up or Control-Reset, the registers on the card are all in a + disabled state. They will be enabled by addressing any address in the firmware page $Cs00-CsFF. + The three address bytes can be both written into and read from. If the card + has one Megabyte or less, reading the high address byte will always return a value in the range $F0-FF. The top nybble can be any value when you write it, but it will always be “F” when you read it. If the card has more than one Megabyte of RAM, the top nybble will be a meaningful part of the address. - */ const ( memoryExpansionSize256 = 256 * 1024 @@ -55,7 +60,7 @@ func NewCardMemoryExpansion() *CardMemoryExpansion { return &c } -// GetInfo returns smartport info +// GetInfo returns smartPort info func (c *CardMemoryExpansion) GetInfo() map[string]string { info := make(map[string]string) info["size"] = fmt.Sprintf("%vKB", len(c.ram)/1024) diff --git a/cardSmartport.go b/cardSmartport.go index 01750a6..8b3af65 100644 --- a/cardSmartport.go +++ b/cardSmartport.go @@ -17,42 +17,48 @@ See: */ -// CardSmartport represents a SmartPort card -type CardSmartport struct { +// CardSmartPort represents a SmartPort card +type CardSmartPort struct { cardBase - hardDiskDevice smartPortDevice + device smartPortDevice hardDiskBlocks uint32 mliParams uint16 trace bool } -// NewCardSmartport creates a new SmartPort card -func NewCardSmartport() *CardSmartport { - var c CardSmartport - c.name = "Smartport Card" +// NewCardSmartPort creates a new SmartPort card +func NewCardSmartPort() *CardSmartPort { + var c CardSmartPort + c.name = "SmartPort Card" return &c } -// GetInfo returns smartport info -func (c *CardSmartport) GetInfo() map[string]string { +// GetInfo returns smartPort info +func (c *CardSmartPort) GetInfo() map[string]string { info := make(map[string]string) info["trace"] = strconv.FormatBool(c.trace) return info } // LoadImage loads a disk image -func (c *CardSmartport) LoadImage(filename string) error { +func (c *CardSmartPort) LoadImage(filename string, trace bool) error { device, err := NewSmartPortHardDisk(c, filename) if err == nil { - device.trace = c.trace - c.hardDiskDevice = device + device.trace = trace + c.device = device c.hardDiskBlocks = device.disk.GetSizeInBlocks() // Needed for the PRODOS status } return err } -func (c *CardSmartport) assign(a *Apple2, slot int) { +// LoadImage loads a disk image +func (c *CardSmartPort) AddDevice(unt uint8, device smartPortDevice) { + c.device = device + c.hardDiskBlocks = 0 // Needed for the PRODOS status +} + +func (c *CardSmartPort) assign(a *Apple2, slot int) { c.loadRom(buildHardDiskRom(slot)) c.addCardSoftSwitchR(0, func() uint8 { @@ -61,25 +67,26 @@ func (c *CardSmartport) assign(a *Apple2, slot int) { unit := a.mmu.Peek(0x43) & 0x0f // Generate Smarport compatible params - var params []uint8 + var call *smartPortCall if command == proDosDeviceCommandStatus { - params = []uint8{ - 5, unit, + call = newSmartPortCallSynthetic(c, command, []uint8{ + 3, // 3 args + unit, a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address 0, - } - } else { - params = []uint8{ - 7, unit, + }) + } else if command == proDosDeviceCommandRead || command == proDosDeviceCommandWrite { + call = newSmartPortCallSynthetic(c, command, []uint8{ + 3, // 3args + unit, a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address a.mmu.Peek(0x46), a.mmu.Peek(0x47), 0, // block number - } + }) + } else { + return proDosDeviceBadCommand } - result := c.hardDiskDevice.exec(command, params) - if c.trace { - fmt.Printf("[CardSmartport] PRODOS command $%x on slot %v, unit $%x, result $%02x.\n", command, slot, unit, result) - } - return result + + return c.exec(call) }, "SMARTPORTPRODOSCOMMAND") c.addCardSoftSwitchR(1, func() uint8 { @@ -96,36 +103,61 @@ func (c *CardSmartport) assign(a *Apple2, slot int) { command := c.a.mmu.Peek(c.mliParams + 1) paramsAddress := uint16(c.a.mmu.Peek(c.mliParams+2)) + uint16(c.a.mmu.Peek(c.mliParams+3))<<8 - paramsSize := int(a.mmu.Peek(paramsAddress + 0)) - params := make([]uint8, paramsSize) - for i := 0; i < paramsSize; i++ { - params[i] = a.mmu.Peek(paramsAddress + uint16(i)) - } - unit := params[1] - - result := c.hardDiskDevice.exec(command, params) - if c.trace { - fmt.Printf("[CardSmartport] Smart port command $%x on slot %v, unit $%x, result $%02x.\n", command, slot, unit, result) - } - return result + call := newSmartPortCall(c, command, paramsAddress) + return c.exec(call) }, "SMARTPORTEXEC") c.addCardSoftSwitchW(4, func(value uint8) { c.mliParams = (c.mliParams & 0xff00) + uint16(value) - if c.trace { - fmt.Printf("[CardSmartport] Smart port LO: 0x%x.\n", c.mliParams) - } }, "HDSMARTPORTLO") c.addCardSoftSwitchW(5, func(value uint8) { c.mliParams = (c.mliParams & 0x00ff) + (uint16(value) << 8) - if c.trace { - fmt.Printf("[CardSmartport] Smart port HI: 0x%x.\n", c.mliParams) - } }, "HDSMARTPORTHI") c.cardBase.assign(a, slot) } +func (c *CardSmartPort) exec(call *smartPortCall) uint8 { + var result uint8 + + if call.command == proDosDeviceCommandStatus && + // Call to the host + call.statusCode() == prodosDeviceStatusCodeDevice { + + result = c.hostStatus(call) + } else if call.unit() > 1 { + result = proDosDeviceErrorNoDevice + } else { + // TODO: select the device, 0(host) is sent to 1 + result = c.device.exec(call) + } + + if c.trace { + fmt.Printf("[CardSmartPort] Command %v on slot %v => result %s.\n", + call, c.slot, smartPortErrorMessage(result)) + } + return result +} + +func (c *CardSmartPort) hostStatus(call *smartPortCall) uint8 { + dest := call.param16(2) + if c.trace { + fmt.Printf("[CardSmartPort] Host status into $%x.\n", dest) + } + + // See http://www.1000bit.it/support/manuali/apple/technotes/smpt/tn.smpt.2.html + c.a.mmu.Poke(dest+0, 0x01) // One device + c.a.mmu.Poke(dest+1, 0xff) // No interrupt + c.a.mmu.Poke(dest+2, 0x00) + c.a.mmu.Poke(dest+3, 0x00) // Unknown manufacturer + c.a.mmu.Poke(dest+4, 0x01) + c.a.mmu.Poke(dest+5, 0x00) // Version 1.0 final + c.a.mmu.Poke(dest+6, 0x00) + c.a.mmu.Poke(dest+7, 0x00) // Reserved + + return proDosDeviceNoError +} + func buildHardDiskRom(slot int) []uint8 { data := make([]uint8, 256) ssBase := 0x80 + uint8(slot<<4) @@ -136,7 +168,38 @@ func buildHardDiskRom(slot int) []uint8 { 0xa9, 0x00, // LDA #$00 0xa9, 0x03, // LDA #$03 0xa9, 0x00, // LDA #$00 + 0xd0, 0x36, // BNE bootcode, there is no space for a jmp + }) + if slot == 7 { + // It should be 0 for SmartPort, but with 0 it's not bootable with the II+ ROM + // See http://www.1000bit.it/support/manuali/apple/technotes/udsk/tn.udsk.2.html + data[0x07] = 0x3c + } + + copy(data[0x0a:], []uint8{ + // Entrypoints and SmartPort body it has to be in $Cx0a + 0x4c, 0x80, 0xc0 + uint8(slot), // JMP $cs80 ; Prodos Entrypoint + + // 3 bytes later, smartPort entrypoint. Uses the ProDos MLI calling convention + 0x68, // PLA + 0x8d, ssBase + 4, 0xc0, // STA $c0n4 ; Softswitch 4, store LO(cmdBlock) + 0xa8, // TAY ; We will need it later + 0x68, // PLA + 0x8d, ssBase + 5, 0xc0, // STA $c0n5 ; Softswitch 5, store HI(cmdBlock) + 0x48, // PHA + 0x98, // TYA + 0x18, // CLC + 0x69, 0x03, // ADC #$03 ; Fix return address past the cmdblock + 0x48, // PHA + 0xad, ssBase + 3, 0xc0, // LDA $C0n3 ; Softswitch 3, execute command. Error code in reg A. + 0x18, // CLC ; Clear carry for no errors. + 0xF0, 0x01, // BEQ $01 ; Skips the SEC if reg A is zero + 0x38, // SEC ; Set carry on errors + 0x60, // RTS + }) + + copy(data[0x40:], []uint8{ // Boot code: SS will load block 0 in address $0800. The jump there. // Note: after execution the first block expects $42 to $47 to have // valid values to read block 0. At least Total Replay expects that. @@ -155,34 +218,6 @@ func buildHardDiskRom(slot int) []uint8 { 0x4c, 0x01, 0x08, // JMP $801 ; Jump to loaded boot sector }) - if slot == 7 { - // It should be 0 for SmartPort, but with 0 it's not bootable with the II+ ROM - // See http://www.1000bit.it/support/manuali/apple/technotes/udsk/tn.udsk.2.html - data[0x07] = 0x3c - } - - // Entrypoints and Smartport body - copy(data[0x40:], []uint8{ - 0x4c, 0x80, 0xc0 + uint8(slot), // JMP $cs80 ; Prodos Entrypoint - - // 3 bytes later, smartport entrypoint. Uses the ProDos MLI calling convention - 0x68, // PLA - 0x8d, ssBase + 4, 0xc0, // STA $c0n4 ; Softswitch 4, store LO(cmdBlock) - 0xa8, // TAY ; We will need it later - 0x68, // PLA - 0x8d, ssBase + 5, 0xc0, // STA $c0n5 ; Softswitch 5, store HI(cmdBlock) - 0x48, // PHA - 0x98, // TYA - 0x18, // CLC - 0x69, 0x03, // ADC #$03 ; Fix return address past the cmdblock - 0x48, // PHA - 0xad, ssBase + 3, 0xc0, // LDA $C0n3 ; Softswitch 3, execute command. Error code in reg A. - 0x18, // CLC ; Clear carry for no errors. - 0xF0, 0x01, // BEQ $01 ; Skips the SEC if reg A is zero - 0x38, // SEC ; Set carry on errors - 0x60, // RTS - }) - // Prodos entrypoint body copy(data[0x80:], []uint8{ 0xad, ssBase + 0, 0xc0, // LDA $C0n0 ; Softswitch 0, execute command. Error code in reg A. @@ -199,7 +234,7 @@ func buildHardDiskRom(slot int) []uint8 { data[0xfc] = 0 data[0xfd] = 0 data[0xfe] = 3 // Status and Read. No write, no format. Single volume - data[0xff] = 0x40 // Driver entry point + data[0xff] = 0x0a // Driver entry point // Must be $0a return data } diff --git a/smartPortCall.go b/smartPortCall.go new file mode 100644 index 0000000..0843821 --- /dev/null +++ b/smartPortCall.go @@ -0,0 +1,143 @@ +package izapple2 + +import "fmt" + +/* + A smartPort device +*/ + +type smartPortDevice interface { + exec(call *smartPortCall) uint8 +} + +const ( + proDosDeviceCommandStatus = 0 + proDosDeviceCommandRead = 1 + proDosDeviceCommandWrite = 2 + proDosDeviceCommandFormat = 3 +) + +const ( + prodosDeviceStatusCodeDevice = 0 + prodosDeviceStatusCodeDeviceControlBlock = 1 + prodosDeviceStatusCodeNewline = 2 + prodosDeviceStatusCodeDeviceInfo = 3 +) + +const ( + prodosDeviceStatusCodeTypeBlock = uint8(1) << 7 + prodosDeviceStatusCodeTypeWrite = uint8(1) << 6 + prodosDeviceStatusCodeTypeRead = uint8(1) << 5 + prodosDeviceStatusCodeTypeOnline = uint8(1) << 4 + prodosDeviceStatusCodeTypeFormat = uint8(1) << 3 + prodosDeviceStatusCodeTypeProtected = uint8(1) << 2 + prodosDeviceStatusCodeTypeInterruping = uint8(1) << 1 + prodosDeviceStatusCodeTypeOpen = uint8(1) << 0 +) + +const ( + proDosDeviceNoError = uint8(0) + proDosDeviceBadCommand = uint8(1) + proDosDeviceErrorIO = uint8(0x27) + proDosDeviceErrorNoDevice = uint8(0x28) + proDosDeviceErrorWriteProtected = uint8(0x2b) +) + +type smartPortCall struct { + host *CardSmartPort + command uint8 + + address uint16 // When the params are on the Apple memory + params []uint8 // When the params are built externally as on a ProDOS to SP translation +} + +func newSmartPortCall(host *CardSmartPort, command uint8, address uint16) *smartPortCall { + var spc smartPortCall + spc.host = host + spc.command = command + spc.address = address + spc.params = nil + return &spc +} + +func newSmartPortCallSynthetic(host *CardSmartPort, command uint8, params []uint8) *smartPortCall { + var spc smartPortCall + spc.host = host + spc.command = command + spc.address = 0xffff + spc.params = params + return &spc +} + +func (spc *smartPortCall) unit() uint8 { + return spc.param8(1) +} + +func (spc *smartPortCall) statusCode() uint8 { + if spc.command != proDosDeviceCommandStatus { + panic("Status code paremeter requeted for a non status smartport call") + } + return spc.param8(4) +} + +func (spc *smartPortCall) param8(offset uint8) uint8 { + if spc.params == nil { + return spc.host.a.mmu.Peek(spc.address + uint16(offset)) + } + + if int(offset) >= len(spc.params) { + panic("Synthetised smartpot call out of range") + } + + return spc.params[offset] +} + +func (spc *smartPortCall) param16(offset uint8) uint16 { + return uint16(spc.param8(offset)) + + uint16(spc.param8(offset+1))<<8 +} + +func (spc *smartPortCall) param24(offset uint8) uint32 { + return uint32(spc.param8(offset)) + + uint32(spc.param8(offset+1))<<8 + + uint32(spc.param8(offset+2))<<16 +} + +func (spc *smartPortCall) String() string { + switch spc.command { + case proDosDeviceCommandStatus: + return fmt.Sprintf("STATUS(%v, unit=%v, code=%v)", + spc.command, spc.unit(), + spc.statusCode()) + case proDosDeviceCommandRead: + return fmt.Sprintf("READ(%v, unit=%v, block=%v)", + spc.command, spc.unit(), + spc.param24(4)) + case proDosDeviceCommandWrite: + return fmt.Sprintf("WRITE(%v, unit=%v, block=%v)", + spc.command, spc.unit(), + spc.param24(4)) + + default: + return fmt.Sprintf("UNKNOWN(%v, unit=%v)", + spc.command, spc.unit()) + } +} + +func smartPortErrorMessage(code uint8) string { + switch code { + case proDosDeviceNoError: + return "SUCCESS" + case proDosDeviceBadCommand: + return "BAD_COMMAND" + case proDosDeviceErrorIO: + return "ERROR_IO" + case proDosDeviceErrorNoDevice: + return "NO_DEVICE" + case proDosDeviceErrorWriteProtected: + return "WRITE_PROTECT_ERROR" + default: + return string(code) + + } +} diff --git a/smartPortDevice.go b/smartPortDevice.go deleted file mode 100644 index 4f2af7c..0000000 --- a/smartPortDevice.go +++ /dev/null @@ -1,46 +0,0 @@ -package izapple2 - -/* - A smartport device -*/ - -type smartPortDevice interface { - exec(command uint8, params []uint8) uint8 -} - -const ( - proDosDeviceCommandStatus = 0 - proDosDeviceCommandRead = 1 - proDosDeviceCommandWrite = 2 - proDosDeviceCommandFormat = 3 -) - -const ( - proDosDeviceNoError = uint8(0) - proDosDeviceErrorIO = uint8(0x27) - proDosDeviceErrorNoDevice = uint8(0x28) - proDosDeviceErrorWriteProtected = uint8(0x2b) -) - -/* -func smartPortParam8(params []uint8, offset uint8) uint8 { - if int(offset) >= len(params) { - return 0 - } - return params[offset] -} -*/ - -func smartPortParam16(params []uint8, offset uint8) uint16 { - if int(offset+1) >= len(params) { - return 0 - } - return uint16(params[offset]) + uint16(params[offset+1])<<8 -} - -func smartPortParam24(params []uint8, offset uint8) uint32 { - if int(offset+2) >= len(params) { - return 0 - } - return uint32(params[offset]) + uint32(params[offset+1])<<8 + uint32(params[offset+2])<<16 -} diff --git a/smartPortFujinet.go b/smartPortFujinet.go new file mode 100644 index 0000000..59ed523 --- /dev/null +++ b/smartPortFujinet.go @@ -0,0 +1,121 @@ +package izapple2 + +import ( + "fmt" +) + +/* + +The network device as implemented by Fujinet: + +See: + https://github.com/FujiNetWIFI/fujinet-platformio/blob/master/lib/device/iwm/network.cpp + +*/ + +// SmartPortFujinet represents a Fujinet device +type SmartPortFujinet struct { + host *CardSmartPort // For DMA + trace bool +} + +// NewSmartPortFujinet creates a new fujinet device +func NewSmartPortFujinet(host *CardSmartPort) *SmartPortFujinet { + var d SmartPortFujinet + d.host = host + + return &d +} + +func (d *SmartPortFujinet) exec(call *smartPortCall) uint8 { + var result uint8 + + switch call.command { + case proDosDeviceCommandStatus: + address := call.param16(2) + result = d.status(call.statusCode(), address) + + case proDosDeviceCommandRead: + address := call.param16(2) + block := call.param24(4) + result = d.readBlock(block, address) + + case proDosDeviceCommandWrite: + address := call.param16(2) + block := call.param24(4) + result = d.writeBlock(block, address) + + default: + // Prodos device command not supported + result = proDosDeviceErrorIO + } + + if d.trace { + fmt.Printf("[SmartPortFujinet] Command %v, return %s \n", + call, smartPortErrorMessage(result)) + } + + return result +} + +func (d *SmartPortFujinet) readBlock(block uint32, dest uint16) uint8 { + if d.trace { + fmt.Printf("[SmartPortFujinet] Read block %v into $%x.\n", block, dest) + } + + // TODO + + return proDosDeviceNoError +} + +func (d *SmartPortFujinet) writeBlock(block uint32, source uint16) uint8 { + if d.trace { + fmt.Printf("[SmartPortFujinet] Write block %v from $%x.\n", block, source) + } + + // TODO + + return proDosDeviceNoError +} + +func (d *SmartPortFujinet) status(code uint8, dest uint16) uint8 { + + switch code { + case prodosDeviceStatusCodeDevice: + // See iwmNetwork::encode_status_reply_packet() + d.host.a.mmu.Poke(dest, prodosDeviceStatusCodeTypeRead&prodosDeviceStatusCodeTypeOnline) + d.host.a.mmu.Poke(dest+1, 0x00) + d.host.a.mmu.Poke(dest+2, 0x00) + d.host.a.mmu.Poke(dest+3, 0x00) // Block size is 0 + + case prodosDeviceStatusCodeDeviceInfo: + // See iwmNetwork::encode_status_reply_packet() + d.host.a.mmu.Poke(dest, prodosDeviceStatusCodeTypeRead&prodosDeviceStatusCodeTypeOnline) + d.host.a.mmu.Poke(dest+1, 0x00) + d.host.a.mmu.Poke(dest+2, 0x00) + d.host.a.mmu.Poke(dest+3, 0x00) // Block size is 0 + d.host.a.mmu.Poke(dest+4, 0x07) // Name length + d.host.a.mmu.Poke(dest+5, 'N') + d.host.a.mmu.Poke(dest+6, 'E') + d.host.a.mmu.Poke(dest+7, 'T') + d.host.a.mmu.Poke(dest+8, 'W') + d.host.a.mmu.Poke(dest+9, 'O') + d.host.a.mmu.Poke(dest+10, 'R') + d.host.a.mmu.Poke(dest+11, 'K') + d.host.a.mmu.Poke(dest+12, ' ') + d.host.a.mmu.Poke(dest+13, ' ') + d.host.a.mmu.Poke(dest+14, ' ') + d.host.a.mmu.Poke(dest+15, ' ') + d.host.a.mmu.Poke(dest+16, ' ') + d.host.a.mmu.Poke(dest+17, ' ') + d.host.a.mmu.Poke(dest+18, ' ') + d.host.a.mmu.Poke(dest+19, ' ') + d.host.a.mmu.Poke(dest+20, ' ') + d.host.a.mmu.Poke(dest+20, 0x02) // Type hard disk + d.host.a.mmu.Poke(dest+20, 0x00) // Subtype network (comment in network.cpp has 0x0a) + d.host.a.mmu.Poke(dest+23, 0x00) + d.host.a.mmu.Poke(dest+24, 0x01) // Firmware + } + + return proDosDeviceNoError // The return code is always success +} diff --git a/smartPortHardDisk.go b/smartPortHardDisk.go index 89a2824..5759c36 100644 --- a/smartPortHardDisk.go +++ b/smartPortHardDisk.go @@ -7,7 +7,7 @@ import ( ) /* -To implement a smartport hard drive we have to support the smartport commands. +To implement a smartPort hard drive we have to support the smartPort commands. See: Beneath Prodos, section 6-6, 7-13 and 5-8. (http://www.apple-iigs.info/doc/fichiers/beneathprodos.pdf) @@ -15,16 +15,16 @@ See: */ -// SmartPortHardDisk represents a SmartPort card +// SmartPortHardDisk represents a hard disk type SmartPortHardDisk struct { - host *CardSmartport // For DMA + host *CardSmartPort // For DMA filename string trace bool disk *storage.BlockDisk } -// NewSmartPortHardDisk creates a new hard disk with the smartport interface -func NewSmartPortHardDisk(host *CardSmartport, filename string) (*SmartPortHardDisk, error) { +// NewSmartPortHardDisk creates a new hard disk with the smartPort interface +func NewSmartPortHardDisk(host *CardSmartPort, filename string) (*SmartPortHardDisk, error) { var d SmartPortHardDisk d.host = host d.filename = filename @@ -38,22 +38,22 @@ func NewSmartPortHardDisk(host *CardSmartport, filename string) (*SmartPortHardD return &d, nil } -func (d *SmartPortHardDisk) exec(command uint8, params []uint8) uint8 { +func (d *SmartPortHardDisk) exec(call *smartPortCall) uint8 { var result uint8 - switch command { + switch call.command { case proDosDeviceCommandStatus: - address := smartPortParam16(params, 2) + address := call.param16(2) result = d.status(address) case proDosDeviceCommandRead: - address := smartPortParam16(params, 2) - block := smartPortParam24(params, 4) + address := call.param16(2) + block := call.param24(4) result = d.readBlock(block, address) case proDosDeviceCommandWrite: - address := smartPortParam16(params, 2) - block := smartPortParam24(params, 4) + address := call.param16(2) + block := call.param24(4) result = d.writeBlock(block, address) default: @@ -62,7 +62,8 @@ func (d *SmartPortHardDisk) exec(command uint8, params []uint8) uint8 { } if d.trace { - fmt.Printf("[SmartPortHardDisk] Command $%x, return $%02x \n", command, result) + fmt.Printf("[SmartPortFujinet] Command %v, return %s \n", + call, smartPortErrorMessage(result)) } return result @@ -77,6 +78,7 @@ func (d *SmartPortHardDisk) readBlock(block uint32, dest uint16) uint8 { if err != nil { return proDosDeviceErrorIO } + // Byte by byte transfer to memory using the full Poke code path for i := uint16(0); i < uint16(len(data)); i++ { d.host.a.mmu.Poke(dest+i, data[i]) @@ -114,7 +116,7 @@ func (d *SmartPortHardDisk) status(dest uint16) uint8 { } // See http://www.1000bit.it/support/manuali/apple/technotes/smpt/tn.smpt.2.html - d.host.a.mmu.Poke(dest+0, 0x02) // One device + d.host.a.mmu.Poke(dest+0, 0x01) // One device d.host.a.mmu.Poke(dest+1, 0xff) // No interrupt d.host.a.mmu.Poke(dest+2, 0x00) d.host.a.mmu.Poke(dest+3, 0x00) // Unknown manufacturer