mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-23 00:30:21 +00:00
Fujinet network device
This commit is contained in:
parent
5d857dda4b
commit
718f0b60b3
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
181
cardSmartport.go
181
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
|
||||
}
|
||||
|
143
smartPortCall.go
Normal file
143
smartPortCall.go
Normal 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)
|
||||
|
||||
}
|
||||
}
|
@ -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
121
smartPortFujinet.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user