Fujinet network device

This commit is contained in:
Ivan Izaguirre 2022-10-24 23:09:06 +02:00
parent 5d857dda4b
commit 718f0b60b3
11 changed files with 431 additions and 148 deletions

View File

@ -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) - Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only)
- SwyftCard (Apple //e only) - SwyftCard (Apple //e only)
- Useful cards not emulating a real card - 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 - 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 - FASTChip, limited to what Total Replay needs to set and clear fast mode
- Mouse Card, emulates the entry points, not the softswitches. - 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 - Softswitch reads and writes
- ProDOS MLI calls - ProDOS MLI calls
- Apple Pascal BIOS calls - Apple Pascal BIOS calls
- Smartport commands - SmartPort commands
- BBC MOS calls when using [Applecorn](https://github.com/bobbimanners/) - BBC MOS calls when using [Applecorn](https://github.com/bobbimanners/)
- Other features: - Other features:
- Sound - Sound
@ -245,7 +245,7 @@ Only valid on SDL mode
-traceCpu -traceCpu
dump to the console the CPU execution. Use F11 to toggle. dump to the console the CPU execution. Use F11 to toggle.
-traceHD -traceHD
dump to the console the hd/smartport commands dump to the console the hd/smartPort commands
-traceMLI -traceMLI
dump to the console the calls to ProDOS machine language interface calls to $BF00 dump to the console the calls to ProDOS machine language interface calls to $BF00
-tracePascal -tracePascal

View File

@ -67,7 +67,7 @@ func (a *Apple2) Start(paused bool) {
for i := 0; i < cpuSpinLoops; i++ { for i := 0; i < cpuSpinLoops; i++ {
// Conditional tracing // Conditional tracing
//pc, _ := a.cpu.GetPCAndSP() //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 // Execution
a.cpu.ExecuteInstruction() a.cpu.ExecuteInstruction()

View File

@ -138,13 +138,24 @@ func (a *Apple2) AddDisk2Sequencer(slot int, diskImage, diskBImage string, track
} }
// AddSmartPortDisk adds a smart port card and image // AddSmartPortDisk adds a smart port card and image
func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, trace bool) error { func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, traceHD bool, traceSP bool) error {
c := NewCardSmartport() 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 c.trace = trace
a.insertCard(c, slot) a.insertCard(c, slot)
err := c.LoadImage(hdImage) d := NewSmartPortFujinet(c)
return err d.trace = trace
c.AddDevice(0, d)
} }
// AddVidHD adds a card with the signature of VidHD // AddVidHD adds a card with the signature of VidHD

View File

@ -32,6 +32,10 @@ func MainApple() *Apple2 {
"hdSlot", "hdSlot",
-1, -1,
"slot for the hard drive if present. -1 for none.") "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( smartPortImage := flag.String(
"disk35", "disk35",
"", "",
@ -127,7 +131,11 @@ func MainApple() *Apple2 {
traceHD := flag.Bool( traceHD := flag.Bool(
"traceHD", "traceHD",
false, 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 := flag.String(
"model", "model",
"2enh", "2enh",
@ -260,12 +268,16 @@ func MainApple() *Apple2 {
} }
if *smartPortImage != "" { if *smartPortImage != "" {
err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD) err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD, *traceSP)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
if *fujinetSlot >= 0 {
a.AddFujinet(*fujinetSlot, *traceSP)
}
if *fastChipCardSlot >= 0 { if *fastChipCardSlot >= 0 {
a.AddFastChip(*fastChipCardSlot) a.AddFastChip(*fastChipCardSlot)
} }
@ -288,7 +300,7 @@ func MainApple() *Apple2 {
// If there is a hard disk image, but no slot assigned, use slot 7. // If there is a hard disk image, but no slot assigned, use slot 7.
*hardDiskSlot = 7 *hardDiskSlot = 7
} }
err := a.AddSmartPortDisk(*hardDiskSlot, hardDiskImageFinal, *traceHD) err := a.AddSmartPortDisk(*hardDiskSlot, hardDiskImageFinal, *traceHD, *traceSP)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -52,7 +52,7 @@ func NewCardDisk2(trackTracer trackTracer) *CardDisk2 {
return &c return &c
} }
// GetInfo returns smartport info // GetInfo returns smartPort info
func (c *CardDisk2) GetInfo() map[string]string { func (c *CardDisk2) GetInfo() map[string]string {
info := make(map[string]string) info := make(map[string]string)
info["rom"] = "16 sector" info["rom"] = "16 sector"

View File

@ -5,8 +5,8 @@ import "fmt"
/* /*
Apple II Memory Expansion Card Apple II Memory Expansion Card
See: See:
http://www.apple-iigs.info/doc/fichiers/a2me.pdf http://www.apple-iigs.info/doc/fichiers/a2me.pdf
http://ae.applearchives.com/files/RamFactor_Manual_1.5.pdf http://ae.applearchives.com/files/RamFactor_Manual_1.5.pdf
http://www.1000bit.it/support/manuali/apple/technotes/memx/tn.memx.1.html 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. There is a self test in ROM, address Cs0A.
From the RamFactor docs: From the RamFactor docs:
The RamFactor card has five addressable registers, which are addressed The RamFactor card has five addressable registers, which are addressed
according to the slot number the card is in: according to the slot number the card is in:
$C080+slot * 16low byte of RAM address $C080+slot * 16low byte of RAM address
$C081+slot * 16middle byte of RAM address $C081+slot * 16middle byte of RAM address
$C082+slot * 16high byte of RAM address $C082+slot * 16high byte of RAM address
$C083+slot * 16data at addressed location $C083+slot * 16data at addressed location
$C08F+slot * 16Firmware Bank Select $C08F+slot * 16Firmware Bank Select
After power up or Control-Reset, the registers on the card are all in a 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 disabled state. They will be enabled by addressing any address in the firmware
page $Cs00-CsFF. page $Cs00-CsFF.
The three address bytes can be both written into and read from. If the card 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 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, 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 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. Megabyte of RAM, the top nybble will be a meaningful part of the address.
*/ */
const ( const (
memoryExpansionSize256 = 256 * 1024 memoryExpansionSize256 = 256 * 1024
@ -55,7 +60,7 @@ func NewCardMemoryExpansion() *CardMemoryExpansion {
return &c return &c
} }
// GetInfo returns smartport info // GetInfo returns smartPort info
func (c *CardMemoryExpansion) GetInfo() map[string]string { func (c *CardMemoryExpansion) GetInfo() map[string]string {
info := make(map[string]string) info := make(map[string]string)
info["size"] = fmt.Sprintf("%vKB", len(c.ram)/1024) info["size"] = fmt.Sprintf("%vKB", len(c.ram)/1024)

View File

@ -17,42 +17,48 @@ See:
*/ */
// CardSmartport represents a SmartPort card // CardSmartPort represents a SmartPort card
type CardSmartport struct { type CardSmartPort struct {
cardBase cardBase
hardDiskDevice smartPortDevice device smartPortDevice
hardDiskBlocks uint32 hardDiskBlocks uint32
mliParams uint16 mliParams uint16
trace bool trace bool
} }
// NewCardSmartport creates a new SmartPort card // NewCardSmartPort creates a new SmartPort card
func NewCardSmartport() *CardSmartport { func NewCardSmartPort() *CardSmartPort {
var c CardSmartport var c CardSmartPort
c.name = "Smartport Card" c.name = "SmartPort Card"
return &c return &c
} }
// GetInfo returns smartport info // GetInfo returns smartPort info
func (c *CardSmartport) GetInfo() map[string]string { func (c *CardSmartPort) GetInfo() map[string]string {
info := make(map[string]string) info := make(map[string]string)
info["trace"] = strconv.FormatBool(c.trace) info["trace"] = strconv.FormatBool(c.trace)
return info return info
} }
// LoadImage loads a disk image // 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) device, err := NewSmartPortHardDisk(c, filename)
if err == nil { if err == nil {
device.trace = c.trace device.trace = trace
c.hardDiskDevice = device c.device = device
c.hardDiskBlocks = device.disk.GetSizeInBlocks() // Needed for the PRODOS status c.hardDiskBlocks = device.disk.GetSizeInBlocks() // Needed for the PRODOS status
} }
return err 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.loadRom(buildHardDiskRom(slot))
c.addCardSoftSwitchR(0, func() uint8 { c.addCardSoftSwitchR(0, func() uint8 {
@ -61,25 +67,26 @@ func (c *CardSmartport) assign(a *Apple2, slot int) {
unit := a.mmu.Peek(0x43) & 0x0f unit := a.mmu.Peek(0x43) & 0x0f
// Generate Smarport compatible params // Generate Smarport compatible params
var params []uint8 var call *smartPortCall
if command == proDosDeviceCommandStatus { if command == proDosDeviceCommandStatus {
params = []uint8{ call = newSmartPortCallSynthetic(c, command, []uint8{
5, unit, 3, // 3 args
unit,
a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address
0, 0,
} })
} else { } else if command == proDosDeviceCommandRead || command == proDosDeviceCommandWrite {
params = []uint8{ call = newSmartPortCallSynthetic(c, command, []uint8{
7, unit, 3, // 3args
unit,
a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address
a.mmu.Peek(0x46), a.mmu.Peek(0x47), 0, // block number a.mmu.Peek(0x46), a.mmu.Peek(0x47), 0, // block number
} })
} else {
return proDosDeviceBadCommand
} }
result := c.hardDiskDevice.exec(command, params)
if c.trace { return c.exec(call)
fmt.Printf("[CardSmartport] PRODOS command $%x on slot %v, unit $%x, result $%02x.\n", command, slot, unit, result)
}
return result
}, "SMARTPORTPRODOSCOMMAND") }, "SMARTPORTPRODOSCOMMAND")
c.addCardSoftSwitchR(1, func() uint8 { 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) 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 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)) call := newSmartPortCall(c, command, paramsAddress)
params := make([]uint8, paramsSize) return c.exec(call)
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
}, "SMARTPORTEXEC") }, "SMARTPORTEXEC")
c.addCardSoftSwitchW(4, func(value uint8) { c.addCardSoftSwitchW(4, func(value uint8) {
c.mliParams = (c.mliParams & 0xff00) + uint16(value) c.mliParams = (c.mliParams & 0xff00) + uint16(value)
if c.trace {
fmt.Printf("[CardSmartport] Smart port LO: 0x%x.\n", c.mliParams)
}
}, "HDSMARTPORTLO") }, "HDSMARTPORTLO")
c.addCardSoftSwitchW(5, func(value uint8) { c.addCardSoftSwitchW(5, func(value uint8) {
c.mliParams = (c.mliParams & 0x00ff) + (uint16(value) << 8) c.mliParams = (c.mliParams & 0x00ff) + (uint16(value) << 8)
if c.trace {
fmt.Printf("[CardSmartport] Smart port HI: 0x%x.\n", c.mliParams)
}
}, "HDSMARTPORTHI") }, "HDSMARTPORTHI")
c.cardBase.assign(a, slot) 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 { func buildHardDiskRom(slot int) []uint8 {
data := make([]uint8, 256) data := make([]uint8, 256)
ssBase := 0x80 + uint8(slot<<4) ssBase := 0x80 + uint8(slot<<4)
@ -136,7 +168,38 @@ func buildHardDiskRom(slot int) []uint8 {
0xa9, 0x00, // LDA #$00 0xa9, 0x00, // LDA #$00
0xa9, 0x03, // LDA #$03 0xa9, 0x03, // LDA #$03
0xa9, 0x00, // LDA #$00 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. // 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 // Note: after execution the first block expects $42 to $47 to have
// valid values to read block 0. At least Total Replay expects that. // 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 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 // Prodos entrypoint body
copy(data[0x80:], []uint8{ copy(data[0x80:], []uint8{
0xad, ssBase + 0, 0xc0, // LDA $C0n0 ; Softswitch 0, execute command. Error code in reg A. 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[0xfc] = 0
data[0xfd] = 0 data[0xfd] = 0
data[0xfe] = 3 // Status and Read. No write, no format. Single volume 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 return data
} }

143
smartPortCall.go Normal file
View File

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

View File

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

121
smartPortFujinet.go Normal file
View File

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

View File

@ -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: See:
Beneath Prodos, section 6-6, 7-13 and 5-8. (http://www.apple-iigs.info/doc/fichiers/beneathprodos.pdf) 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 { type SmartPortHardDisk struct {
host *CardSmartport // For DMA host *CardSmartPort // For DMA
filename string filename string
trace bool trace bool
disk *storage.BlockDisk disk *storage.BlockDisk
} }
// NewSmartPortHardDisk creates a new hard disk with the smartport interface // NewSmartPortHardDisk creates a new hard disk with the smartPort interface
func NewSmartPortHardDisk(host *CardSmartport, filename string) (*SmartPortHardDisk, error) { func NewSmartPortHardDisk(host *CardSmartPort, filename string) (*SmartPortHardDisk, error) {
var d SmartPortHardDisk var d SmartPortHardDisk
d.host = host d.host = host
d.filename = filename d.filename = filename
@ -38,22 +38,22 @@ func NewSmartPortHardDisk(host *CardSmartport, filename string) (*SmartPortHardD
return &d, nil return &d, nil
} }
func (d *SmartPortHardDisk) exec(command uint8, params []uint8) uint8 { func (d *SmartPortHardDisk) exec(call *smartPortCall) uint8 {
var result uint8 var result uint8
switch command { switch call.command {
case proDosDeviceCommandStatus: case proDosDeviceCommandStatus:
address := smartPortParam16(params, 2) address := call.param16(2)
result = d.status(address) result = d.status(address)
case proDosDeviceCommandRead: case proDosDeviceCommandRead:
address := smartPortParam16(params, 2) address := call.param16(2)
block := smartPortParam24(params, 4) block := call.param24(4)
result = d.readBlock(block, address) result = d.readBlock(block, address)
case proDosDeviceCommandWrite: case proDosDeviceCommandWrite:
address := smartPortParam16(params, 2) address := call.param16(2)
block := smartPortParam24(params, 4) block := call.param24(4)
result = d.writeBlock(block, address) result = d.writeBlock(block, address)
default: default:
@ -62,7 +62,8 @@ func (d *SmartPortHardDisk) exec(command uint8, params []uint8) uint8 {
} }
if d.trace { 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 return result
@ -77,6 +78,7 @@ func (d *SmartPortHardDisk) readBlock(block uint32, dest uint16) uint8 {
if err != nil { if err != nil {
return proDosDeviceErrorIO return proDosDeviceErrorIO
} }
// Byte by byte transfer to memory using the full Poke code path // Byte by byte transfer to memory using the full Poke code path
for i := uint16(0); i < uint16(len(data)); i++ { for i := uint16(0); i < uint16(len(data)); i++ {
d.host.a.mmu.Poke(dest+i, 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 // 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+1, 0xff) // No interrupt
d.host.a.mmu.Poke(dest+2, 0x00) d.host.a.mmu.Poke(dest+2, 0x00)
d.host.a.mmu.Poke(dest+3, 0x00) // Unknown manufacturer d.host.a.mmu.Poke(dest+3, 0x00) // Unknown manufacturer