izapple2/cardSmartport.go

302 lines
9.0 KiB
Go
Raw Permalink Normal View History

2020-10-03 21:38:26 +00:00
package izapple2
2019-10-02 21:39:39 +00:00
2020-10-13 22:26:47 +00:00
import (
"fmt"
"strconv"
)
2019-10-05 13:30:13 +00:00
2019-10-02 21:39:39 +00:00
/*
To implement a hard drive we just have to support boot from #PR7 and the PRODOS expectations.
2019-10-27 18:18:51 +00:00
See:
Beneath Prodos, section 6-6, 7-13 and 5-8. (http://www.apple-iigs.info/doc/fichiers/beneathprodos.pdf)
Apple IIc Technical Reference, 2nd Edition. Chapter 8. https://ia800207.us.archive.org/19/items/AppleIIcTechnicalReference2ndEd/Apple%20IIc%20Technical%20Reference%202nd%20ed.pdf
2019-10-27 18:18:51 +00:00
https://prodos8.com/docs/technote/21/
2020-08-12 20:50:40 +00:00
https://prodos8.com/docs/technote/20/
2019-10-02 21:39:39 +00:00
*/
2022-10-24 21:09:06 +00:00
// CardSmartPort represents a SmartPort card
type CardSmartPort struct {
2019-10-02 21:39:39 +00:00
cardBase
devices []smartPortDevice
hardDiskBlocks uint32
2020-10-13 22:26:47 +00:00
2020-01-12 16:14:20 +00:00
mliParams uint16
trace bool
2019-10-02 21:39:39 +00:00
}
2024-01-06 20:48:23 +00:00
func newCardSmartPortStorageBuilder() *cardBuilder {
return &cardBuilder{
name: "SmartPort",
description: "SmartPort interface card",
defaultParams: &[]paramSpec{
{"image1", "Disk image for unit 1", ""},
{"image2", "Disk image for unit 2", ""},
{"image3", "Disk image for unit 3", ""},
{"image4", "Disk image for unit 4", ""},
{"image5", "Disk image for unit 5", ""},
{"image6", "Disk image for unit 6", ""},
{"image7", "Disk image for unit 7", ""},
{"image8", "Disk image for unit 8", ""},
{"tracesp", "Trace SmartPort calls", "false"},
{"tracehd", "Trace image accesses", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardSmartPort
c.trace = paramsGetBool(params, "tracesp")
traceHD := paramsGetBool(params, "tracehd")
for i := 1; i <= 8; i++ {
image := paramsGetPath(params, "image"+strconv.Itoa(i))
if image != "" {
err := c.LoadImage(image, traceHD)
if err != nil {
return nil, err
}
}
}
return &c, nil
},
}
}
func newCardSmartPortFujinetBuilder() *cardBuilder {
return &cardBuilder{
name: "Fujinet",
description: "SmartPort interface card hosting the Fujinet",
defaultParams: &[]paramSpec{
{"tracesp", "Trace SmartPort calls", "false"},
{"tracenet", "Trace on the network device", "false"},
{"traceclock", "Trace on the clock device", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardSmartPort
c.trace = paramsGetBool(params, "tracesp")
net := NewSmartPortFujinetNetwork(&c)
net.trace = paramsGetBool(params, "tracenet")
c.AddDevice(net)
clock := NewSmartPortFujinetClock(&c)
clock.trace = paramsGetBool(params, "traceclock")
c.AddDevice(clock)
return &c, nil
},
}
2020-10-13 22:26:47 +00:00
}
2019-10-02 21:39:39 +00:00
2022-10-24 21:09:06 +00:00
// GetInfo returns smartPort info
func (c *CardSmartPort) GetInfo() map[string]string {
2020-10-13 22:26:47 +00:00
info := make(map[string]string)
info["trace"] = strconv.FormatBool(c.trace)
return info
}
2020-10-13 22:26:47 +00:00
// LoadImage loads a disk image
2022-10-24 21:09:06 +00:00
func (c *CardSmartPort) LoadImage(filename string, trace bool) error {
device, err := NewSmartPortHardDisk(c, filename)
if err == nil {
2022-10-24 21:09:06 +00:00
device.trace = trace
c.devices = append(c.devices, device)
c.hardDiskBlocks = device.disk.GetSizeInBlocks() // Needed for the PRODOS status
2020-08-12 20:50:40 +00:00
}
return err
2019-10-02 21:39:39 +00:00
}
2022-10-24 21:09:06 +00:00
// LoadImage loads a disk image
2022-11-01 15:57:45 +00:00
func (c *CardSmartPort) AddDevice(device smartPortDevice) {
c.devices = append(c.devices, device)
2022-10-24 21:09:06 +00:00
c.hardDiskBlocks = 0 // Needed for the PRODOS status
}
func (c *CardSmartPort) assign(a *Apple2, slot int) {
2024-03-24 19:15:16 +00:00
c.loadRom(buildHardDiskRom(slot), cardRomSimple)
2020-10-13 22:26:47 +00:00
2022-08-05 17:43:17 +00:00
c.addCardSoftSwitchR(0, func() uint8 {
2019-10-02 21:39:39 +00:00
// Prodos entry point
command := a.mmu.Peek(0x42)
unit := a.mmu.Peek(0x43) & 0x0f
// Generate Smarport compatible params
2022-10-24 21:09:06 +00:00
var call *smartPortCall
2022-10-31 21:57:18 +00:00
if command == smartPortCommandStatus {
2022-10-24 21:09:06 +00:00
call = newSmartPortCallSynthetic(c, command, []uint8{
3, // 3 args
unit,
a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address
0,
2022-10-24 21:09:06 +00:00
})
2022-10-31 21:57:18 +00:00
} else if command == smartPortCommandReadBlock || command == smartPortCommandWriteBlock {
2022-10-24 21:09:06 +00:00
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
2022-10-24 21:09:06 +00:00
})
} else {
2022-10-31 21:57:18 +00:00
return smartPortBadCommand
2019-10-05 13:30:13 +00:00
}
2022-10-24 21:09:06 +00:00
return c.exec(call)
}, "SMARTPORTPRODOSCOMMAND")
2019-10-02 21:39:39 +00:00
2022-08-05 17:43:17 +00:00
c.addCardSoftSwitchR(1, func() uint8 {
2019-10-02 21:39:39 +00:00
// Blocks available, low byte
return uint8(c.hardDiskBlocks)
2019-10-20 22:00:42 +00:00
}, "HDBLOCKSLO")
2022-08-05 17:43:17 +00:00
c.addCardSoftSwitchR(2, func() uint8 {
2019-10-02 21:39:39 +00:00
// Blocks available, high byte
2024-01-06 20:48:23 +00:00
return uint8(c.hardDiskBlocks >> 8)
2019-10-20 22:00:42 +00:00
}, "HDBLOCKHI")
2019-10-02 21:39:39 +00:00
2022-08-05 17:43:17 +00:00
c.addCardSoftSwitchR(3, func() uint8 {
2020-01-12 16:14:20 +00:00
// Smart port entry point
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
2022-10-24 21:09:06 +00:00
call := newSmartPortCall(c, command, paramsAddress)
return c.exec(call)
}, "SMARTPORTEXEC")
2022-08-05 17:43:17 +00:00
c.addCardSoftSwitchW(4, func(value uint8) {
2020-01-12 16:14:20 +00:00
c.mliParams = (c.mliParams & 0xff00) + uint16(value)
}, "HDSMARTPORTLO")
2022-08-05 17:43:17 +00:00
c.addCardSoftSwitchW(5, func(value uint8) {
2020-01-12 16:14:20 +00:00
c.mliParams = (c.mliParams & 0x00ff) + (uint16(value) << 8)
}, "HDSMARTPORTHI")
2019-10-02 21:39:39 +00:00
c.cardBase.assign(a, slot)
}
2022-10-24 21:09:06 +00:00
func (c *CardSmartPort) exec(call *smartPortCall) uint8 {
var result uint8
unit := int(call.unit())
2022-10-24 21:09:06 +00:00
2022-10-31 21:57:18 +00:00
if call.command == smartPortCommandStatus &&
2022-10-24 21:09:06 +00:00
// Call to the host
2022-10-31 21:57:18 +00:00
call.statusCode() == smartPortStatusCodeDevice {
2022-10-24 21:09:06 +00:00
result = c.hostStatus(call)
} else if unit > len(c.devices) {
2022-10-31 21:57:18 +00:00
result = smartPortErrorNoDevice
2022-10-24 21:09:06 +00:00
} else {
if unit == 0 {
unit = 1 // For unit 0(host) use the first device
}
if unit > len(c.devices) {
result = smartPortErrorNoDevice
} else {
result = c.devices[unit-1].exec(call)
}
2022-10-24 21:09:06 +00:00
}
if c.trace {
2022-11-01 15:57:45 +00:00
fmt.Printf("[CardSmartPort] Command %v on slot %v => result %s.\n",
call, c.slot, smartPortErrorMessage(result))
2022-10-24 21:09:06 +00:00
}
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, uint8(len(c.devices)))
2022-10-24 21:09:06 +00:00
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
2022-10-31 21:57:18 +00:00
return smartPortNoError
2022-10-24 21:09:06 +00:00
}
2020-10-13 22:26:47 +00:00
func buildHardDiskRom(slot int) []uint8 {
data := make([]uint8, 256)
ssBase := 0x80 + uint8(slot<<4)
copy(data, []uint8{
// Preamble bytes to comply with the expectation in $Cn01, 3, 5 and 7
0xa9, 0x20, // LDA #$20
0xa9, 0x00, // LDA #$00
0xa9, 0x03, // LDA #$03
0xa9, 0x00, // LDA #$00
2022-10-24 21:09:06 +00:00
0xd0, 0x36, // BNE bootcode, there is no space for a jmp
2020-10-13 22:26:47 +00:00
})
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
}
2022-10-24 21:09:06 +00:00
copy(data[0x0a:], []uint8{
// Entrypoints and SmartPort body it has to be in $Cx0a
2020-10-13 22:26:47 +00:00
0x4c, 0x80, 0xc0 + uint8(slot), // JMP $cs80 ; Prodos Entrypoint
2022-10-24 21:09:06 +00:00
// 3 bytes later, smartPort entrypoint. Uses the ProDos MLI calling convention
2020-10-13 22:26:47 +00:00
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
})
2019-11-01 17:48:39 +00:00
2022-10-24 21:09:06 +00:00
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.
0xa9, 0x01, // LDA·#$01
0x85, 0x42, // STA $42 ; Command READ(1)
0xa9, 0x00, // LDA·#$00
0x85, 0x43, // STA $43 ; Unit 0
0x85, 0x44, // STA $44 ; Dest LO($0800)
0x85, 0x46, // STA $46 ; Block LO(0)
0x85, 0x47, // STA $47 ; Block HI(0)
0xa9, 0x08, // LDA·#$08
0x85, 0x45, // STA $45 ; Dest HI($0800)
0xad, ssBase, 0xc0, // LDA $C0n1 ;Call to softswitch 0.
0xa2, uint8(slot << 4), // LDX $s7 ; Slot on hign nibble of X
0x4c, 0x01, 0x08, // JMP $801 ; Jump to loaded boot sector
})
2020-10-13 22:26:47 +00:00
// Prodos entrypoint body
copy(data[0x80:], []uint8{
0xad, ssBase + 0, 0xc0, // LDA $C0n0 ; Softswitch 0, execute command. Error code in reg A.
0x48, // PHA
0xae, ssBase + 1, 0xc0, // LDX $C0n1 ; Softswitch 1, LO(Blocks), STATUS needs that in reg X.
0xac, ssBase + 2, 0xc0, // LDY $C0n2 ; Softswitch 2, HI(Blocks). STATUS needs that in reg Y.
0x18, // CLC ; Clear carry for no errors.
0x68, // PLA ; Sets Z if no error
0xF0, 0x01, // BEQ $01 ; Skips the SEC if reg A is zero
0x38, // SEC ; Set carry on errors
0x60, // RTS
})
data[0xfc] = 0
data[0xfd] = 0
data[0xfe] = 3 // Status and Read. No write, no format. Single volume
2022-10-24 21:09:06 +00:00
data[0xff] = 0x0a // Driver entry point // Must be $0a
2020-10-13 22:26:47 +00:00
return data
2019-11-01 17:48:39 +00:00
}