diff --git a/.gitignore b/.gitignore index a263031..a8a9d6b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ a2sdl.exe frontend/a2sdl/a2sdl frontend/a2sdl/*.woz frontend/a2sdl/*.dsk +frontend/a2sdl/*.po +frontend/a2sdl/*.2mg frontend/a2fyne/a2fyne frontend/headless/headless frontend/*/snapshot.gif diff --git a/apple2Setup.go b/apple2Setup.go index 91400a5..d59d998 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -139,14 +139,12 @@ 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 := NewCardHardDisk() + c := NewCardSmartport() c.trace = trace - err := c.LoadImage(hdImage) - if err != nil { - return err - } a.insertCard(c, slot) - return nil + + err := c.LoadImage(hdImage) + return err } // AddVidHD adds a card with the signature of VidHD diff --git a/cardHardDisk.go b/cardSmartport.go similarity index 52% rename from cardHardDisk.go rename to cardSmartport.go index b9b29ff..01750a6 100644 --- a/cardHardDisk.go +++ b/cardSmartport.go @@ -3,12 +3,10 @@ package izapple2 import ( "fmt" "strconv" - - "github.com/ivanizag/izapple2/storage" ) /* -To implement a hard drive we just have to support boot from #PR7 and the PRODOS expextations. +To implement a hard drive we just have to support boot from #PR7 and the PRODOS expectations. See: Beneath Prodos, section 6-6, 7-13 and 5-8. (http://www.apple-iigs.info/doc/fichiers/beneathprodos.pdf) @@ -19,188 +17,115 @@ See: */ -// CardHardDisk represents a SmartPort card -type CardHardDisk struct { +// CardSmartport represents a SmartPort card +type CardSmartport struct { cardBase + hardDiskDevice smartPortDevice + hardDiskBlocks uint32 - filename string - trace bool - - disk *storage.BlockDisk mliParams uint16 + trace bool } -// NewCardHardDisk creates a new SmartPort card -func NewCardHardDisk() *CardHardDisk { - var c CardHardDisk +// NewCardSmartport creates a new SmartPort card +func NewCardSmartport() *CardSmartport { + var c CardSmartport c.name = "Smartport Card" return &c } // GetInfo returns smartport info -func (c *CardHardDisk) GetInfo() map[string]string { +func (c *CardSmartport) GetInfo() map[string]string { info := make(map[string]string) - info["filename"] = c.filename info["trace"] = strconv.FormatBool(c.trace) return info } // LoadImage loads a disk image -func (c *CardHardDisk) LoadImage(filename string) error { - hd, err := storage.OpenBlockDisk(filename) - if err != nil { - return err +func (c *CardSmartport) LoadImage(filename string) error { + device, err := NewSmartPortHardDisk(c, filename) + if err == nil { + device.trace = c.trace + c.hardDiskDevice = device + c.hardDiskBlocks = device.disk.GetSizeInBlocks() // Needed for the PRODOS status } - c.disk = hd - c.filename = filename - return nil + return err } -const ( - proDosDeviceCommandStatus = 0 - proDosDeviceCommandRead = 1 - proDosDeviceCommandWrite = 2 - proDosDeviceCommandFormat = 3 -) - -const ( - proDosDeviceNoError = uint8(0) - proDosDeviceErrorIO = uint8(0x27) - proDosDeviceErrorNoDevice = uint8(0x28) - proDosDeviceErrorWriteProtected = uint8(0x2b) -) - -func (c *CardHardDisk) assign(a *Apple2, slot int) { +func (c *CardSmartport) assign(a *Apple2, slot int) { c.loadRom(buildHardDiskRom(slot)) c.addCardSoftSwitchR(0, func() uint8 { // Prodos entry point command := a.mmu.Peek(0x42) - unit := a.mmu.Peek(0x43) - address := uint16(a.mmu.Peek(0x44)) + uint16(a.mmu.Peek(0x45))<<8 - block := uint16(a.mmu.Peek(0x46)) + uint16(a.mmu.Peek(0x47))<<8 - if c.trace { - fmt.Printf("[CardHardDisk] Prodos command %v on slot %v, unit $%x, block %v to $%x.\n", command, slot, unit, block, address) - } + unit := a.mmu.Peek(0x43) & 0x0f - switch command { - case proDosDeviceCommandStatus: - return c.status(unit, address) - case proDosDeviceCommandRead: - return c.readBlock(block, address) - case proDosDeviceCommandWrite: - return c.writeBlock(block, address) - default: - // Prodos device command not supported - return proDosDeviceErrorIO + // Generate Smarport compatible params + var params []uint8 + if command == proDosDeviceCommandStatus { + params = []uint8{ + 5, unit, + a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address + 0, + } + } else { + params = []uint8{ + 7, unit, + a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address + a.mmu.Peek(0x46), a.mmu.Peek(0x47), 0, // block number + } } - }, "HDCOMMAND") + 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 + }, "SMARTPORTPRODOSCOMMAND") + c.addCardSoftSwitchR(1, func() uint8 { // Blocks available, low byte - return uint8(c.disk.GetSizeInBlocks()) + return uint8(c.hardDiskBlocks) }, "HDBLOCKSLO") c.addCardSoftSwitchR(2, func() uint8 { // Blocks available, high byte - return uint8(c.disk.GetSizeInBlocks() >> 8) + return uint8(c.hardDiskBlocks) }, "HDBLOCKHI") c.addCardSoftSwitchR(3, func() uint8 { // 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 - unit := a.mmu.Peek(paramsAddress + 1) - address := uint16(a.mmu.Peek(paramsAddress+2)) + uint16(a.mmu.Peek(paramsAddress+3))<<8 - block := uint16(a.mmu.Peek(paramsAddress+4)) + uint16(a.mmu.Peek(paramsAddress+5))<<8 - if c.trace { - fmt.Printf("[CardHardDisk] Smart port command %v on slot %v, unit $%x, block %v to $%x.\n", command, slot, unit, block, address) - } - switch command { - case proDosDeviceCommandStatus: - return c.status(unit, address) - case proDosDeviceCommandRead: - return c.readBlock(block, address) - case proDosDeviceCommandWrite: - return c.writeBlock(block, address) - default: - // Smartport device command not supported - return proDosDeviceErrorIO + 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)) } - }, "HDSMARTPORT") + 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") + c.addCardSoftSwitchW(4, func(value uint8) { c.mliParams = (c.mliParams & 0xff00) + uint16(value) if c.trace { - fmt.Printf("[CardHardDisk] Smart port LO: 0x%x.\n", c.mliParams) + 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("[CardHardDisk] Smart port HI: 0x%x.\n", c.mliParams) + fmt.Printf("[CardSmartport] Smart port HI: 0x%x.\n", c.mliParams) } }, "HDSMARTPORTHI") c.cardBase.assign(a, slot) } -func (c *CardHardDisk) readBlock(block uint16, dest uint16) uint8 { - if c.trace { - fmt.Printf("[CardHardDisk] Read block %v into $%x.\n", block, dest) - } - - data, err := c.disk.Read(uint32(block)) - 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++ { - c.a.mmu.Poke(dest+i, data[i]) - } - - return proDosDeviceNoError -} - -func (c *CardHardDisk) writeBlock(block uint16, source uint16) uint8 { - if c.trace { - fmt.Printf("[CardHardDisk] Write block %v from $%x.\n", block, source) - } - - if c.disk.IsReadOnly() { - return proDosDeviceErrorWriteProtected - } - - // Byte by byte transfer from memory using the full Peek code path - buf := make([]uint8, storage.ProDosBlockSize) - for i := uint16(0); i < uint16(len(buf)); i++ { - buf[i] = c.a.mmu.Peek(source + i) - } - - err := c.disk.Write(uint32(block), buf) - if err != nil { - return proDosDeviceErrorIO - } - - return proDosDeviceNoError -} - -func (c *CardHardDisk) status(unit uint8, dest uint16) uint8 { - if c.trace { - fmt.Printf("[CardHardDisk] Status for %v into $%x.\n", unit, dest) - } - - // See http://www.1000bit.it/support/manuali/apple/technotes/smpt/tn.smpt.2.html - c.a.mmu.Poke(dest+0, 0x02) // 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) diff --git a/smartPortDevice.go b/smartPortDevice.go new file mode 100644 index 0000000..4f2af7c --- /dev/null +++ b/smartPortDevice.go @@ -0,0 +1,46 @@ +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/smartPortHardDisk.go b/smartPortHardDisk.go new file mode 100644 index 0000000..89a2824 --- /dev/null +++ b/smartPortHardDisk.go @@ -0,0 +1,127 @@ +package izapple2 + +import ( + "fmt" + + "github.com/ivanizag/izapple2/storage" +) + +/* +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) + Apple IIc Technical Reference, 2nd Edition. Chapter 8. https://ia800207.us.archive.org/19/items/AppleIIcTechnicalReference2ndEd/Apple%20IIc%20Technical%20Reference%202nd%20ed.pdf + +*/ + +// SmartPortHardDisk represents a SmartPort card +type SmartPortHardDisk struct { + 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) { + var d SmartPortHardDisk + d.host = host + d.filename = filename + + hd, err := storage.OpenBlockDisk(filename) + if err != nil { + return nil, err + } + d.disk = hd + + return &d, nil +} + +func (d *SmartPortHardDisk) exec(command uint8, params []uint8) uint8 { + var result uint8 + + switch command { + case proDosDeviceCommandStatus: + address := smartPortParam16(params, 2) + result = d.status(address) + + case proDosDeviceCommandRead: + address := smartPortParam16(params, 2) + block := smartPortParam24(params, 4) + result = d.readBlock(block, address) + + case proDosDeviceCommandWrite: + address := smartPortParam16(params, 2) + block := smartPortParam24(params, 4) + result = d.writeBlock(block, address) + + default: + // Prodos device command not supported + result = proDosDeviceErrorIO + } + + if d.trace { + fmt.Printf("[SmartPortHardDisk] Command $%x, return $%02x \n", command, result) + } + + return result +} + +func (d *SmartPortHardDisk) readBlock(block uint32, dest uint16) uint8 { + if d.trace { + fmt.Printf("[SmartPortHardDisk] Read block %v into $%x.\n", block, dest) + } + + data, err := d.disk.Read(block) + 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]) + } + + return proDosDeviceNoError +} + +func (d *SmartPortHardDisk) writeBlock(block uint32, source uint16) uint8 { + if d.trace { + fmt.Printf("[SmartPortHardDisk] Write block %v from $%x.\n", block, source) + } + + if d.disk.IsReadOnly() { + return proDosDeviceErrorWriteProtected + } + + // Byte by byte transfer from memory using the full Peek code path + buf := make([]uint8, storage.ProDosBlockSize) + for i := uint16(0); i < uint16(len(buf)); i++ { + buf[i] = d.host.a.mmu.Peek(source + i) + } + + err := d.disk.Write(block, buf) + if err != nil { + return proDosDeviceErrorIO + } + + return proDosDeviceNoError +} + +func (d *SmartPortHardDisk) status(dest uint16) uint8 { + if d.trace { + fmt.Printf("[SmartPortHardDisk] Status into $%x.\n", dest) + } + + // 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+1, 0xff) // No interrupt + d.host.a.mmu.Poke(dest+2, 0x00) + d.host.a.mmu.Poke(dest+3, 0x00) // Unknown manufacturer + d.host.a.mmu.Poke(dest+4, 0x01) + d.host.a.mmu.Poke(dest+5, 0x00) // Version 1.0 final + d.host.a.mmu.Poke(dest+6, 0x00) + d.host.a.mmu.Poke(dest+7, 0x00) // Reserved + + return proDosDeviceNoError +}