From dcb4e3c9239cf3dc8b620ca2723ab53d2618efc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Izaguirre?= Date: Sat, 13 Sep 2025 21:34:48 +0200 Subject: [PATCH] Playing with the Apple II Profile Interface Card --- README.md | 1 - cardBase.go | 16 +++- cardBuilder.go | 2 + cardInOut.go | 1 + cardProfile.go | 87 ++++++++++++++++++ cardThunderClockPlus.go | 2 +- configuration.go | 4 +- doc/usage.txt | 1 - ...ce 341-0271-A ROM 2716 for 5MB Profile.bin | Bin 0 -> 2048 bytes ...e 341-0299-B ROM 2716 for 10MB Profile.bin | Bin 0 -> 2048 bytes setup.go | 7 +- 11 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 cardProfile.go create mode 100644 resources/Apple II Interface 341-0271-A ROM 2716 for 5MB Profile.bin create mode 100644 resources/Apple II Interface 341-0299-B ROM 2716 for 10MB Profile.bin diff --git a/README.md b/README.md index 74ca20b3..a522176e 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,6 @@ The available cards are: diskiiseq: Disk II interface card emulating the Woz state machine fastchip: Accelerator card for Apple IIe (limited support) fujinet: SmartPort interface card hosting the Fujinet - inout: Card to test I/O language: Language card with 16 extra KB for the Apple ][ and ][+ memexp: Memory expansion card mouse: Mouse card implementation, does not emulate a real card, only the firmware behaviour diff --git a/cardBase.go b/cardBase.go index baab0abb..50d8469b 100644 --- a/cardBase.go +++ b/cardBase.go @@ -54,8 +54,9 @@ type cardRomLayout int const ( cardRomSimple cardRomLayout = iota // The ROM is on the slot area, there can be more than one page - cardRomUpper // The ROM is on the full C800 area. The slot area copies C8xx + cardRomUpperStart // The ROM is on the full C800 area. The slot area copies C8xx cardRomUpperHalfEnd // The ROM is on half of the C800 areas. The slot area copies CBxx + cardRomUpperEnd // The ROM is on the full C800 area. The slot area copies CFxx cardRomFull // The ROM is on the full Cxxx area, with pages for each slot position ) @@ -88,14 +89,14 @@ func (c *cardBase) loadRom(data []uint8, layout cardRomLayout) error { } else { return fmt.Errorf("invalid ROM size for simple layout") } - case cardRomUpper: + case cardRomUpperStart: if len(data) == 0x800 { // The file has C800 to CFFF // The 256 bytes in Cx00 are copied from the first page in C800 c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM") c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") } else { - return fmt.Errorf("invalid ROM size for upper layout") + return fmt.Errorf("invalid ROM size for upper start layout") } case cardRomUpperHalfEnd: if len(data) == 0x400 { @@ -107,6 +108,15 @@ func (c *cardBase) loadRom(data []uint8, layout cardRomLayout) error { } else { return fmt.Errorf("invalid ROM size for upper half end layout") } + case cardRomUpperEnd: + if len(data) == 0x800 { + // The file has C800 to CFFF for ROM + // The 256 bytes in Cx00 are copied from the last page in C800-CFFF + c.romCsxx = newMemoryRangeROM(0, data[0x700:], "Slot ROM") + c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") + } else { + return fmt.Errorf("invalid ROM size for upper end layout") + } case cardRomFull: if len(data) == 0x1000 { // The file covers the full Cxxx range. Only showing the page diff --git a/cardBuilder.go b/cardBuilder.go index 611da413..7bfb1a6f 100644 --- a/cardBuilder.go +++ b/cardBuilder.go @@ -20,6 +20,7 @@ type cardBuilder struct { description string defaultParams *[]paramSpec requiresIIe bool + hide bool buildFunc func(params map[string]string) (Card, error) } @@ -57,6 +58,7 @@ func getCardFactory() map[string]*cardBuilder { cardFactory["prodosromdrive"] = newCardProDOSRomDriveBuilder() cardFactory["prodosromcard3"] = newCardProDOSRomCard3Builder() // cardFactory["prodosnvramdrive"] = newCardProDOSNVRAMDriveBuilder() + cardFactory["profile"] = newCardProfileBuilder() cardFactory["saturn"] = newCardSaturnBuilder() cardFactory["smartport"] = newCardSmartPortStorageBuilder() cardFactory["swyftcard"] = newCardSwyftBuilder() diff --git a/cardInOut.go b/cardInOut.go index 122b3688..e103e596 100644 --- a/cardInOut.go +++ b/cardInOut.go @@ -28,6 +28,7 @@ func newCardInOutBuilder() *cardBuilder { return &cardBuilder{ name: "InOut test card", description: "Card to test I/O", + hide: true, buildFunc: func(params map[string]string) (Card, error) { return &CardInOut{}, nil }, diff --git a/cardProfile.go b/cardProfile.go new file mode 100644 index 00000000..dc45750d --- /dev/null +++ b/cardProfile.go @@ -0,0 +1,87 @@ +package izapple2 + +import ( + "fmt" +) + +/* +Profile card for Apple II Interface. + +The firmware source code for 5MB is available at: https://bitsavers.org/pdf/apple/disk/profile/appleII_interface/AII_Profile_Boot_Prom_198402.pdf +See also: + https://bitsavers.org/pdf/apple/disk/profile/072-0116_Profile_Level_II_Service_Manual_Oct84.pdf + https://bitsavers.org/pdf/apple/disk/profile/Profile_Communication_Protocol.pdf + https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Hard%20Disk%20Drive%20Controllers/Apple%20II%20ProFile%20Interface%20Card/ + +Softswitches: + W0: WR_PORT, write to Z8 RAM (byte at a time) + R1: RD_PORT, read from Z8 RAM (byte at a time) + R2: BUSY, Z8 not ready + bit 0 off is drive present and connected to card + bit 6 is parity error + bit 7 on is ready and online + W2: RSTAT? + W3: CLR PARITY, clear any previous parity error + +*/ + +// CardProfile represents the Apple II Profile interface card +type CardProfile struct { + cardBase +} + +func newCardProfileBuilder() *cardBuilder { + return &cardBuilder{ + name: "Apple II Profile interface card", + description: "Profile interface card - not implemented", + hide: true, + defaultParams: &[]paramSpec{ + {"rom", "ROM file to load", "/Apple II Interface 341-0271-A ROM 2716 for 5MB Profile.bin"}, + //{"rom", "ROM file to load", "/Apple II Interface 341-0299-B ROM 2716 for 10MB Profile.bin"}, + }, + buildFunc: func(params map[string]string) (Card, error) { + var c CardProfile + romFile := paramsGetPath(params, "rom") + err := c.loadRomFromResource(romFile, cardRomUpperEnd) + if err != nil { + return nil, err + } + return &c, nil + }, + } +} + +func (c *CardProfile) assign(a *Apple2, slot int) { + + // WR_PORT, write to Z8 RAM (byte at a time) + c.addCardSoftSwitchW(0, func(value uint8) { + fmt.Printf("[cardProfile] WR_PORT: Write to Z8 RAM, value 0x%02x\n", value) + }, "PROFILE_WR_PORT") + + // RD_PORT, read from Z8 RAM (byte at a time) + c.addCardSoftSwitchR(1, func() uint8 { + fmt.Printf("[cardProfile] RD_PORT: Read from Z8 RAM\n") + return 0x00 // Return dummy data for now + }, "PROFILE_RD_PORT") + + // BUSY, Z8 not ready + c.addCardSoftSwitchR(2, func() uint8 { + fmt.Printf("[cardProfile] BUSY: Z8 status check\n") + // Return 0x80 (bit 7 on) to indicate ready and online + // bit 0 off indicates drive present + return 0x80 + }, "PROFILE_BUSY") + + // RSTAT? + c.addCardSoftSwitchW(2, func(value uint8) { + fmt.Printf("[cardProfile] RSTAT: Write status, value 0x%02x\n", value) + }, "PROFILE_RSTAT") + + // CLR PARITY, clear any previous parity error + c.addCardSoftSwitchW(3, func(value uint8) { + fmt.Printf("[cardProfile] CLR_PARITY: Clear parity error, value 0x%02x\n", value) + }, "PROFILE_CLR_PARITY") + + // Call base assignment to setup ROM and other card functionality + c.cardBase.assign(a, slot) +} diff --git a/cardThunderClockPlus.go b/cardThunderClockPlus.go index 2368e3fd..5220eb30 100644 --- a/cardThunderClockPlus.go +++ b/cardThunderClockPlus.go @@ -33,7 +33,7 @@ func newCardThunderClockPlusBuilder() *cardBuilder { description: "Clock card", buildFunc: func(params map[string]string) (Card, error) { var c CardThunderClockPlus - err := c.loadRomFromResource("/ThunderclockPlusROM.bin", cardRomUpper) + err := c.loadRomFromResource("/ThunderclockPlusROM.bin", cardRomUpperStart) if err != nil { return nil, err } diff --git a/configuration.go b/configuration.go index 3c8d7b70..3c1db1b4 100644 --- a/configuration.go +++ b/configuration.go @@ -271,7 +271,9 @@ func setupFlags(models *configurationModels, configuration *configuration) error fmt.Fprintf(out, "\nThe available cards are:\n") for _, card := range availableCards() { builder := getCardFactory()[card] - fmt.Fprintf(out, " %s: %s\n", card, builder.description) + if !builder.hide { + fmt.Fprintf(out, " %s: %s\n", card, builder.description) + } } fmt.Fprintf(out, "\nThe available tracers are:\n") diff --git a/doc/usage.txt b/doc/usage.txt index 13e120a5..e00281c0 100644 --- a/doc/usage.txt +++ b/doc/usage.txt @@ -72,7 +72,6 @@ The available cards are: diskiiseq: Disk II interface card emulating the Woz state machine fastchip: Accelerator card for Apple IIe (limited support) fujinet: SmartPort interface card hosting the Fujinet - inout: Card to test I/O language: Language card with 16 extra KB for the Apple ][ and ][+ memexp: Memory expansion card mouse: Mouse card implementation, does not emulate a real card, only the firmware behaviour diff --git a/resources/Apple II Interface 341-0271-A ROM 2716 for 5MB Profile.bin b/resources/Apple II Interface 341-0271-A ROM 2716 for 5MB Profile.bin new file mode 100644 index 0000000000000000000000000000000000000000..caf48034a01b281bf7369d54e28e77e0d8022882 GIT binary patch literal 2048 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/dos33.dsk", - "prodos": "/ProDOS_2_4_3.po", - "cpm": "/cpm_2.20B_56K.po", + "dos33": "/dos33.dsk", + "prodos": "/ProDOS_2_4_3.po", + "cpm": "/cpm_2.20B_56K.po", + "cardcat": "/Card Cat 1.7.dsk", } func applyDiskAliases(filename string) string {