Compare commits

...

725 Commits

Author SHA1 Message Date
dingusdev f92cb92353 Initial work on Rage 128 2024-04-19 09:29:35 -07:00
Maxim Poliakovski 9a70c3bdb0
Remove bogus atirage128.cpp 2024-04-15 17:17:04 +02:00
dingusdev 103ef6169c Continued expanding zdocs 2024-04-15 07:52:09 -07:00
dingusdev 1a165ff64c Update manual.md 2024-04-15 07:28:30 -07:00
dingusdev 8cbbb2ed50 Initial preparation for a release 2024-04-15 07:01:51 -07:00
Maxim Poliakovski 781d9b46da athens: allow setting custom XTAL frequency. 2024-04-15 14:38:46 +02:00
Maxim Poliakovski ffa221192d athens: use component's name in logging messages. 2024-04-15 14:21:08 +02:00
Maxim Poliakovski 751f964139 athens: clean up initialization. 2024-04-15 14:21:08 +02:00
joevt 14f75d834a macio: Add list of OHare/Heathrow bits. 2024-04-12 19:54:34 -07:00
Maxim Poliakovski 96efc99a00 control: clean up register bit definitions. 2024-04-11 17:50:50 +02:00
Maxim Poliakovski 45d2c8854d Revert "macio: Add fatman bits register."
This reverts commit 8aaf211c5b.
2024-04-11 17:18:20 +02:00
Maxim Poliakovski c22843f238 appleramdac: vendor ID for AT&T fabbed RAMDACs. 2024-04-11 17:10:44 +02:00
joevt 1c5009fcb0 amic: Add more registers. 2024-04-10 20:59:34 -07:00
joevt bd63d1dcda platinum: Validity check before enable display. 2024-04-10 20:56:51 -07:00
joevt 8aaf211c5b macio: Add fatman bits register.
This register is used in the control ndrv to detect presence of connected S-Video or composite video display.
2024-04-10 20:48:29 -07:00
joevt 8cc5838efe control: More register bits. 2024-04-10 20:48:12 -07:00
joevt 4b965c623b machinetnt: Add more Power Macs. 2024-04-10 20:36:59 -07:00
joevt be27ceed00 machinetnt: Remove sixty6 and mesh properties.
Use the presence of the devices in the MachineDescription to determine these.
2024-04-10 20:19:43 -07:00
joevt 1d75730d44 dbdma: Do interrupt on main thread.
These may be triggered by other threads (such as for audio) so use the timer manager to handle them in the main thread.
2024-04-10 19:17:49 -07:00
joevt 95d74a6940 scsihd: Check Lun for INQUIRY.
INQUIRY now returns 0x7f for device type if LUN doesn't match. INQUIRY can get LUN from CDB or from IDENTIFY message.
2024-04-10 19:02:01 -07:00
joevt c6ea3a374e control: Fix PAL/NTSC interlaced display. 2024-04-10 07:32:35 -07:00
joevt e8ce805f2a poweropcodes: Remove power_setsoov.
None of the POWER opcodes uses it now, plus it is a duplicate of ppc_setsoov (though ppc_setsoov is inline so it would have to be moved to be able to use it in poweropcodes.cpp?
2024-04-10 07:30:06 -07:00
joevt 3d898ebdf3 poweropcodes: Cleanup power_rot_mask.
Use U instead of UL. U will use the smallest size that can fit all the unsigned bytes. Since 0xFFFFFFFF fits in 32 bits, the 0xFFFFFFFFU is a uint32_t.
2024-04-10 07:29:56 -07:00
joevt f45b7c47c8 poweropcodes: Fix srq.
Test bit 26 of rB instead of testing for >= 0x20 to determine which operation to perform.
2024-04-10 07:29:46 -07:00
joevt 916cb47b9d poweropcodes: Fix srlq.
Test bit 26 of rB instead of testing for >= 0x20 to determine which operation to perform.
2024-04-10 07:29:37 -07:00
joevt bce816139b poweropcodes: Fix sreq.
Including bits of rot_sh in the rA and MQ calculations is nonsensical since it is a rotation count and not a source of bits to be extracted or rotated.
The mask is not complicated, so we don't need to use power_rot_mask.
2024-04-10 07:29:28 -07:00
joevt 24bce16c4d poweropcodes: Fix srea.
Fix carry flag calculation. Anding with the rotation count (n = rB) is nonsensical.
(r & ~mask) is the rotated word ANDed with the complement of the generated mask of n zeros followed by 32 - n ones.
The manual says this 32-bit result is ORed together. This means all the bits are ORed together which is equivalent to saying 0 if all zeros and 1 if any ones. In other words: (r & ~mask) != 0.
This boolean is ANDed with bit 0 of rS to produce the carry. int32_t(rS) < 0 will test bit 0. The && operator will treat each side as a boolean so you can exclude "!= 0" tests.
2024-04-10 07:29:20 -07:00
joevt a928c67913 poweropcodes: Fix sraq.
If bit 26 of rB is set then the mask should be all ones.
If bit 26 of rB is set then rA should be all ones or all zeros (depending on the sign bit of rA).
2024-04-10 07:29:11 -07:00
joevt 2b8f510603 poweropcodes: Fix slq.
Test bit 26 of rB instead of using >= 0x20 to determine which operation to perform.
The two operations need to be switched such that rA is cleared when bit 26 is set.
Don't forget to store the result in rA.
2024-04-10 07:28:58 -07:00
joevt e8273ecc61 poweropcodes: Fix sllq.
Test bit 26 of rB instead of using >= 0x20 to determine which operation to perform.
Since the mask is not complicated, we don't need to use power_rot_mask.
2024-04-10 07:28:46 -07:00
joevt e1f31a2da3 poweropcodes: Fix rrib.
It is redundant to test bit 0 of rS and then use bit 0 of rS in the case when bit 0 of rS is set.
In the case when bit 0 of rS is not set, using bit 0 or rS is incorrect since it results in no change of rA.
2024-04-10 07:26:52 -07:00
joevt d897acfd3c poweropcodes: Fix nabs.
Calculate overflow first before calculating condition codes because the overflow condition is copied from XER.
2024-04-10 07:26:32 -07:00
joevt 1e57ac408a poweropcodes: Fix mul.
Operands are supposed to be twos complement numbers.
Calculate overflow first before calculating condition codes because the overflow condition is copied from XER.
Fix OV calculation. Previously, it was using power_setsoov which I think is only for add and subtract operations.
Fix CR calcalation. It's supposed to depend on the low order 32 bits that are placed into MQ.
2024-04-10 07:26:24 -07:00
joevt ef8522e101 poweropcodes: Fix maskg.
The condition code register depends on rA, not rD since rA contains the result.
2024-04-10 07:26:16 -07:00
joevt c71d856a08 poweropcodes: Fix lscbx.
- Fix CR calculation. It depends on whether a match occurred and only the EQ flag is affected.
- Remove bytes_copied. We can subtract bytes_remaining from bytes_to_load to calculate that.
- Initialize ppc_result_d to zero so that bitmask is not needed to add new bytes to it. This is ok since the manual says that bytes that are not loaded are undefined.
2024-04-10 07:26:07 -07:00
joevt df7ff76404 poweropcodes: Fix doz.
Calculate overflow first before calculating condition codes because the overflow condition is copied from XER.
Fix OV calculation. Previously, it was using power_setsoov which I think is only for add and subtract operations. doz does a subtract but only if the result is supposed to be positive, therefore a negative result indicates an overflow.
2024-04-10 07:22:50 -07:00
joevt 0d1ce68d19 poweropcodes: Fix divs.
dividend and divisor are supposed to be a twos compliment numbers.
Fix OV calculation. Previously, it was using power_setsoov which I think is only for add and subtract operations.
Fix CR calculation. It depends on the remainder, not the quotient.
2024-04-10 07:22:41 -07:00
joevt 88aa249ce1 poweropcodes: Fix div.
dividend is supposed to be a twos compliment number.
Fix test for dividend = -0x80000000 and divisor = -1. Previously, the test was assuming dividend was a 32-bit value from rA.
Fix OV calculation. Previously, it was using power_setsoov which I think is only for add and subtract operations.
Fix CR calculation. It depends on the remainder, not the quotient.
2024-04-10 07:22:34 -07:00
joevt ff626ae0b5 poweropcodes: Fix clcs.
For MPC601 CPUs, all values of rA return 64 though the manual says undefined values of rA produce undefined results.
For non-MPC601 CPUs, if this instruction is included (such as for risu DPPC) then return results that are obtained from a G4 running Mac OS 9.2.2.
2024-04-10 07:22:26 -07:00
joevt 529f23d836 poweropcodes: Fix abs.
Making a negative value positive requires unary negate operator rather than binary and operator since negative numbers are stored using twos compliment.
If ov is set then clear overflow when overflow doesn't happen.
2024-04-10 07:22:18 -07:00
joevt cb88bab67d ppcopcodes: Fixes for SPRs.
- Rename DEC to DEC_S and add DEC_U.
- MQ, RTCL_U, RTCU_U, and DEC_U should cause an illegal instruction program exception for non-MPC601 CPUs. The exception handler of classic Mac OS uses this to emulate the instruction.
- For mtspr, the SPRs RTCL_U, RTCU_U, and DEC_U are treated as no-op on MPC601.
- For debugging, use the supervisor instead of the user SPR number as the index for storing the values for RTC, TB, and DEC.
- For debugging, RTC, TB, and DEC should be updated after each access. Previously, mfspr and mtspr would only update the half of RTC and TB that was being accessed instead of both halves.
2024-04-10 07:21:54 -07:00
joevt 67a5c39b1c ppcopcodes: Add Privileged exception for SPRs.
Accessing an SPR with bit 4 set (> 15) requires supervisor privilege and should cause a supervisor-level instruction exception (privileged instruction type program exception).
2024-04-10 07:21:23 -07:00
joevt 0273867c49 ppcopcodes: Cleanup ppc_changecrf0.
- Use one assignment to set ppc_state.cr.
- Use enums for CR and XER bits.
- Use < to check sign bit.
2024-04-10 06:47:10 -07:00
joevt 1e50d88183 ppcopcodes: Use macro to grab instruction fields. 2024-04-10 06:46:46 -07:00
joevt 29a832c68d ppcopcodes: Use < 0 instead of & 0x8000000. 2024-04-10 06:45:31 -07:00
joevt cb05bd05eb cpu: Add ppc_grab_regssash macro.
This macro is like ppc_grab_regssa but includes rot_sh = (ppc_cur_instruction >> 11) & 0x1F;
2024-04-10 06:43:34 -07:00
joevt 4f45d7de35 cpu: Add cpu options to ppc_cpu_init.
The first option is a flag that enables MPC601 (POWER) instructions for CPUs that are not MPC601.
This can be useful for the following reasons:
1) To produce results similar to classic Mac OS which emulates MPC601 instructions on CPUs that don't implement MPC601 instructions. This option is used to compare the risu traces produced in Mac OS 9 on a G3 or G4 with DPPC.
2) May increase performance in apps that use POWER instructions on emulated machines with CPUs that are not MPC601. It is not known if any such apps exist but there could be since Apple included MPC601 emulation in classic Mac OS.
2024-04-10 06:43:18 -07:00
joevt a6ba9a0554 memaccess: Add addr type cast. 2024-04-09 21:19:04 -07:00
dingusdev 9c95bc17fe Implement VX and FEX updates for mtfsfi 2024-04-09 21:11:09 -07:00
dingusdev 2c94cfee03 Removing currently unneeded functions 2024-04-09 18:34:36 -07:00
joevt 3c16870f86 ppcmmu: Replace defines.
They may interfere with system headers.
2024-04-09 07:57:48 -07:00
dingusdev 22f45902ca
Merge pull request #89 from sdkmap/master
Enhanced GitHub Actions Workflow for DingusPPC Build Automation
2024-04-09 06:58:36 -07:00
sdkmap 28b1eb8524
CI
one commit.
2024-04-09 12:56:06 +03:00
Maxim Poliakovski 1c8702d67a Add missing credits. 2024-04-09 01:51:51 +02:00
Mihai Parparita cf4913deb0 bandit: Fix regression in non-Aspen PCI bridge
Refactor from e7da98b6bd accidentally
made the non-Aspen PCI code path for CONFIG_ADDR writes by a no-op.
2024-04-08 15:41:19 -05:00
Maxim Poliakovski bdd441b1b6 Add machinepippin.cpp to /machines. 2024-04-08 00:56:00 +02:00
Maxim Poliakovski 4c9fe06229 Implement Aspen memory controller. 2024-04-08 00:44:24 +02:00
Maxim Poliakovski e7da98b6bd bandit: implement Aspen style PCI bridge. 2024-04-08 00:44:24 +02:00
Maxim Poliakovski dcdfaabedf dbdma: skip transfer commands with reqCount=0. 2024-04-07 20:39:24 +02:00
Maxim Poliakovski 524daa45a5 ppcexec.cpp: fix compilation with Apple Clang 10. 2024-04-07 20:39:24 +02:00
Maxim Poliakovski 073b8fd981 atimach64defs: add ATI_DP_CHAIN_MSK register definition. 2024-04-07 18:48:52 +02:00
Maxim Poliakovski d7749e0a2c awacs: small cosmetic improvements. 2024-04-07 18:48:52 +02:00
Maxim Poliakovski 7972a0f2a8 hammerhead: use instance name in logging messages. 2024-04-07 18:48:52 +02:00
Maxim Poliakovski 19dcb43658 pcibase.h: remove superfluous spaces. 2024-04-07 18:48:52 +02:00
joevt 9ed1a118e6 ppcmmu: Check sizeof(T) explicitly.
I don't know if the compiler is smart enough to figure out that ((guest_va & 0xFFF) + sizeof(T)) > 0x1000) is always false when sizeof(T) == 1 so we'll add a check for sizeof(T) > 1.
2024-04-07 08:59:05 -07:00
dingusdev a5a5410515 Continued fixing floating-point ops 2024-04-07 08:58:38 -07:00
dingusdev 40a4ca31b9 More minor floating-point clean-up 2024-04-07 07:23:30 -07:00
dingusdev 7f44ab2262 Minor fixes to floating point 2024-04-06 17:31:03 -07:00
dingusdev 123c927b1a Another refactor for floating points
FCMPO and FCMPU passes the tests now*
2024-04-06 11:02:03 -07:00
joevt 74274f164d G3 CPU upgrade property. 2024-04-04 19:05:59 -07:00
joevt 5c2bd0b3bb machines: Don't override existing properties. 2024-04-04 19:04:33 -07:00
joevt d0a5a1e7be Add ability to override built-in GPU. 2024-04-04 19:04:06 -07:00
joevt 077e6ebae5 machineid: Don't decode board register address.
All addresses return the same value. Converted for Board Register 1 and Board Register 66.
2024-04-03 19:53:03 -07:00
joevt abe0c14301 scsibus: push_data of zero bytes is ok.
It just means the data hasn't been put on the fifo yet.
2024-04-03 19:51:50 -07:00
joevt 61576d4032 sc53c94: Check drq_cb. 2024-04-03 19:35:58 -07:00
joevt 782a8d2c3c sc53c94: CMD_COMPLETE_STEPS includes INTSTAT_SO.
Last step of CMD_COMPLETE_STEPS includes INTSTAT_SO to indicate to CurioSCSIController::fsmCompleting that it returned both a status byte and a message byte in the FIFO.
2024-04-03 19:35:27 -07:00
joevt e619dd2493 sc53c94: CMD_COMPLETE and XFER_END goes to IDLE.
CMD_COMPLETE and XFER_END goes to the IDLE sequence state so that reentry doesn't cause a second interrupt.
2024-04-03 19:35:13 -07:00
joevt 8a1055ed1b sc53c94: Add DBDMA support.
- For pdm/amic, real_dma_xfer is called when SCSI_DMA_Ctrl has the run bit set.
- For tnt/grandcentral, dma_wait is called when the DBDMA is started (run bit is set). It will call real_dma_xfer when the phase and sequence are DATA_IN/RCV_DATA or DATA_OUT/SEND_DATA.
- dma_wait and real_dma_xfer uses a one shot timer instead of a loop to continue doing DMA while also giving time to the CPU. This and the above changes handles the case where the DBDMA is started before setting up the transfer phase and sequence.
- dma_stop will stop the one shot timer when the DBDMA channel is stopped.
2024-04-03 19:19:42 -07:00
joevt ff766b10eb sc53c94: Add seq_id to sequence descriptor.
The seq_id can be used for logging the current command and step number.
2024-04-03 18:43:46 -07:00
joevt ceb2276098 sc53c94: Move chip_id to xfer_count.
Method for reading chip_id needs verification.
2024-04-03 18:43:40 -07:00
joevt 82f4d05f4b sc53c94: Set timer_id to zero.
If a timer is not in progress then set its timer_id to zero. Also, don't start a newer timer if an existing one is still in progress.
2024-04-03 18:36:53 -07:00
joevt 8a81cb4f9c sc53c94: Use enums. 2024-04-03 18:12:00 -07:00
joevt 1504bd2227 sc53c94: Move DATA_FIFO_MAX from header.
Headers shouldn't make defines that are not meant to be used outside the source file.
2024-04-03 18:11:07 -07:00
dingusdev 475f894582 Comment out log message 2024-04-02 19:21:56 -07:00
joevt 7007e002e6 macio: Make interrupt flags atomic.
So they can be modified by other threads.
2024-04-02 19:05:57 -07:00
joevt 9af1b1a720 control: Narrow the scope of local variable. 2024-04-01 22:12:20 -07:00
joevt ca9657baf1 control: Update little-endian error message. 2024-04-01 22:12:05 -07:00
joevt 3e347746f9 control: Don't abort when setting little endian.
If this gets set accidentally, (such as while playing in Open Firmware), allow the user to change it back.
2024-04-01 22:11:35 -07:00
joevt b5987afaa6 control: Implement BAR update. 2024-04-01 22:11:30 -07:00
joevt ea46d08835 appleramdac: Allow read from CLUT part 2. 2024-04-01 21:47:59 -07:00
joevt 789114cc7d control: Save CNT_TST and MON_SENSE.
These are writable registers that should return what was written to them.
2024-04-01 21:34:26 -07:00
joevt cf292fafcb control: vram changes.
Allow VRAM presence only in optional bank.

control: Implement banks.

The BAR is 64MB.
The little-endian/big-endian ranges repeat every 16MB.
An endian range can be addressed in d128 (wide) mode or d64 mode.
In d128 mode, there is a 4MB range followed by an undefined 4MB range. bytes 0..7 belong to the standard bank and bytes 8..15 belong to the optional bank.
In d64 mode:
- 2MB mirror ranges at 0MB and 2MB (both have the same read/write behavor: write to both banks or read from the standard bank.
- 2MB standard bank rage at 4MB.
- 2MB optional bank range at 6MB.
2024-04-01 21:17:17 -07:00
joevt 08fca7de69 control: Recalc framebuffer after each change. 2024-04-01 20:58:46 -07:00
joevt b42437c458 control: Mask unmodifiable bits. 2024-04-01 19:01:03 -07:00
joevt 98e1787f93 machines: Remove duplicate gfxmem_size properties. 2024-04-01 18:28:13 -07:00
joevt 7a0ec0ecd3 README: Grammar. 2024-04-01 18:23:39 -07:00
dingusdev 55b9f8bbe5 bzero -> memset 2024-04-01 08:03:12 -07:00
joevt 2968645f2e scsihd: Allow INQUIRY allocation length != 36.
Truncate for < 36 and zero fill for > 36.
2024-04-01 08:02:17 -07:00
joevt 92dea0e404 scsihd: Include invalid field info.
For CHECK_CONDITION status from READ_CAPACITY_10.
2024-04-01 08:01:09 -07:00
joevt bfd3077bd0 scsicdrom: Check Lun for INQUIRY.
INQUIRY now returns 0x7f for device type if LUN doesn't match. INQUIRY can get LUN from CDB or from IDENTIFY message.
2024-04-01 08:00:41 -07:00
joevt 2d1616894d appleramdac: Allow read from CLUT. 2024-04-01 07:39:20 -07:00
joevt 7c203b40c8 appleramdac: Reset comp_index on address change. 2024-04-01 07:37:16 -07:00
joevt 2f63a2fa17 appleramdac: Add other vendor id. 2024-04-01 07:10:55 -07:00
joevt e3e065a6d7 appleramdac: Register comments. 2024-04-01 06:47:25 -07:00
dingusdev afd8ba8cf2 Slight adjustment for the readme 2024-03-31 15:09:57 -07:00
dingusdev 43d87b4791 Temp revert for icnt_factor
We should, at minimum, make icnt_factor adjustable. That said, powermax is suggesting we develop a more sophisticated scheduler.
2024-03-31 14:13:45 -07:00
joevt 6267685920 ppcexec: Make EXEF_TIMER separate variable. 2024-03-31 12:15:48 -07:00
joevt 48882f3fec ppcexec: Adjust icnt_factor.
So that 1000 ms takes ≈ 1 second in Open Firmware on 4 GHz Intel CPU.
2024-03-31 11:51:04 -07:00
joevt 0ac54ea1ea ppcexec: Add host time option. 2024-03-31 11:50:55 -07:00
joevt 4395ce01d7 ohare: Update name of Mesh device. 2024-03-30 17:56:19 -07:00
joevt 2c097da12d grandcentral: Add escc DMA. 2024-03-30 14:55:24 -07:00
joevt 5f316dc7a4 grandcentral: Adjust audio in DMA logging. 2024-03-30 12:49:33 -07:00
joevt 02a475e113 sc53c94: Remove duplicate bus_obj.
sc53c94 is a ScsiDevice which has its own bus_obj which is set by register_device.
2024-03-30 11:56:59 -07:00
joevt 338bbe27a8 scsihd: Implement REQ_SENSE.
Returns error information stored in class fields: sense, asc, ascq, sksv, and field.
2024-03-30 11:56:37 -07:00
joevt 44564065f6 scsihd: Changes for MODE_SENSE_6.
Return CHECK_CONDITION for unsupported page_ctrl or page or subpage.
Handle the all pages code 0x3F.
2024-03-30 11:56:02 -07:00
dingusdev c54ec7be2d Use memset instead of bzero 2024-03-30 11:19:32 -07:00
joevt 274e380b34 scsicdrom: Allow INQUIRY allocation length != 36.
Truncate for < 36 and zero fill for > 36.
2024-03-30 11:16:41 -07:00
joevt e872f08273 scsicdrom: Include invalid field info.
For CHECK_CONDITION status from READ_CAPACITY_10 and READ_TOC.
2024-03-30 11:00:01 -07:00
joevt a6fda3b787 scsicdrom: Handle MODE_SENSE_6 unsupported page.
Return CHECK_CONDITION status instead of abort. To Do: apply change to scshhd.
2024-03-30 10:59:43 -07:00
joevt a48851888f scsihd: Setup data_ptr for STATUS and MESSAGE_IN.
bytes_out is used instead of the removed cur_buf_cnt.
data_ptr is setup by prepare_data instead of process_command.
Not sure why scsicdrom doesn't set bytes_out in prepare_data.
2024-03-30 08:37:10 -07:00
joevt f1abb66f9a atahd: Add STANDBY_IMMEDIATE. 2024-03-30 08:30:37 -07:00
joevt c999c51d77 scsicdrom, scsihd: Check lun.
READ_CAPACITY_10, READ_6, and READ_10 now checks LUN that is included in CDB and returns a CHECK_CONDITION if it doesn't match.
2024-03-29 19:12:06 -07:00
joevt 6bb5227ee1 scsihd: Abort if RelAdr bit of READ_10.
Like scsicdrom does.
2024-03-29 19:01:42 -07:00
joevt cefe8698da scsihd: Add PREVENT_ALLOW_MEDIUM_REMOVAL.
Like scsicdrom has.
2024-03-29 19:01:32 -07:00
joevt 4be6bad526 scsicdrom: Fix compiler warning. 2024-03-29 19:00:38 -07:00
joevt d3c913e384 scsicdrom: Cleanup.
- Use macros for reading 2 byte or 4 byte big endian data. block_size is assumed to be < 64K so WRITE_DWORD_BE_A will write two leading zeros.
- Scalar pass by value parameters don't need to be const.
- Remove some unnecessary locals.
2024-03-29 18:53:13 -07:00
joevt a868f4eee4 scsihd: Cleanup.
Use this qualifier.
Use macros for reading 2 byte or 4 byte big endian data.
2024-03-29 18:53:04 -07:00
joevt 44da89979f scsihd: Use device name in abort message. 2024-03-29 18:52:52 -07:00
joevt a79f07e4dc scsicdrom: Move mode_select_6.
Make it a separate method like scsihd. It also checks the incoming param size. If it's zero then the phase is not switched. Is that wrong? Still probably unfinished.
2024-03-29 18:52:30 -07:00
Maxim Poliakovski e17a96f5ec atirage: break long lines. 2024-03-29 21:21:58 +01:00
joevt 5062508940 scsicdrom: Rename mode_sense_6.
To match scsihd.
2024-03-29 07:56:48 -07:00
joevt 9cf91328c1 scsicdrom: Move inquiry info to class fields.
Like scsihd.
2024-03-29 07:56:44 -07:00
joevt 6a30ef7017 scsicdrom: Fields inited by parent class.
They don't need to be reinitialized.
2024-03-29 07:56:31 -07:00
Maxim Poliakovski 71dabf5334 atahd: break long lines. 2024-03-29 12:46:57 +01:00
joevt 155b8cdad9 atahd: Support inexact CHS.
The total size needs to have 3 factors cylinders, heads, & sectors. Imagine a disk having a total size with 3 prime factors 3 x 5 x 11. 15 cannot be assigned to heads because that would only leave 11 for sectors and cylinders. Therefore, test all heads and sectors combinations. If the third factor for cylinders is not found, then choose 16 heads, then the minimum number of sectors, and finally the maximum number of cylinders. The loop could be changed to skip values of heads that are not a factor, but it doesn't take any time to try them all.
2024-03-28 21:07:12 -07:00
joevt 96dc02b249 atahd: Make sure disk is not too big. 2024-03-28 21:07:08 -07:00
Maxim Poliakovski eaddcab0ba atahd: don't register device for empty images.
Workaround for the case when no hard disk image
was specified. The device instance is still there
but it won't be visible by the guest OS.
2024-03-29 03:54:09 +01:00
joevt d9b02ecd8d ppcmmu: Check 8 byte alignment spanning pages. 2024-03-28 07:53:03 -07:00
joevt b9c12e44a4 ppcopcodes: Cleanup 3. 2024-03-28 07:36:40 -07:00
joevt 58ed5bb56e ppcexec: Opcode initialization to one function.
Move all opcode initialization to initialize_ppc_opcode_tables.
Some opcodes are illegal for some processors.
2024-03-28 07:35:57 -07:00
joevt 094f44e92c ppcopcodes: Make MQ read only on non-601 CPUs. 2024-03-28 07:29:50 -07:00
joevt 566706dd62 ppctests: Fix compiler warnings. 2024-03-28 07:17:38 -07:00
joevt 60a76e9348 ppcexec: Fix branch check in ppc_exec_single. 2024-03-28 07:17:13 -07:00
joevt 1d9b0f7fa5 macio: Add MIO_AUX_CTRL enum. 2024-03-28 07:10:05 -07:00
joevt f55ad323b4 ppcdisasm: Fix order of operands.
For cntlzw, extsh, extsb.
2024-03-28 07:09:50 -07:00
joevt 78558e4c52 debugger: Ensure space between opcode and operand.
Instructions that are 8 characters or longer (such as mtdbat3l) did not have a space between opcode and operand. Now there is always a space. The width of the opcode column is unchanged except for those opcodes that have 8 or more characters.
2024-03-28 06:54:23 -07:00
joevt c9d4cc3321 ppcmmu: Remove old and slow code. 2024-03-27 20:13:45 -07:00
joevt 0f8a464157 ppcmmu: Use MSR enums for calculating mmu_mode. 2024-03-27 18:44:59 -07:00
joevt e4a675babb ppcmmu: Remove line feed from log messages. 2024-03-27 18:44:42 -07:00
joevt 5b4ed01bec ppcexec: Make separate enum for shift instructions. 2024-03-27 18:43:46 -07:00
joevt 64df253053 ppcexec: Rename bool function enums.
Use "logical" since the functions deal with multiple bits instead of a single boolean value and because the 601 manual calls them Logical Instructions.
Use "ppc" for the enums because logical_and is defined elsewhere and because the original DPPC code used these names for those functions.
2024-03-27 18:43:35 -07:00
joevt d8129bd643 ppcexec: Add comments for macros. 2024-03-27 18:43:15 -07:00
joevt 60a4738694 Add stub for pci_unregister_device. 2024-03-27 18:23:27 -07:00
joevt 9ade14e076 memctrlbase: fix possible memory leak. 2024-03-27 14:08:20 +01:00
Maxim Poliakovski 6aa54b8dda ppcexec: break long lines, improve indentation. 2024-03-27 13:55:05 +01:00
Maxim Poliakovski 0ff911cc26 poweropcodes: cosmetic improvements. 2024-03-27 03:45:22 +01:00
Maxim Poliakovski b5b14b2f9d ppcopcodes: cosmetic improvements. 2024-03-27 03:36:17 +01:00
Maxim Poliakovski 2b6f41e0d0 poweropcodes: use XER constants instead of magic numbers. 2024-03-27 03:36:17 +01:00
Maxim Poliakovski 9b429cc751 ppcopcodes: replace magic numbers with XER constants. 2024-03-27 03:36:17 +01:00
dingusdev ec56dffd19 Adding missing includes 2024-03-26 19:25:05 -07:00
dingusdev a09f2093b5 Optimize register initialization
Courtesy of joevt, adapted to fit the C++ standard
2024-03-26 18:52:56 -07:00
dingusdev b15d3be88a Moving is_601 up, so the opcodes get initialized correctly 2024-03-26 18:41:16 -07:00
joevt a26628ed50 bandit: Add delayed aack register. 2024-03-26 07:34:08 -07:00
joevt 0b5a798343 atimach64gx: Fix ATI_CRTC_GEN_CNTL.
Add call to crtc_update.
Maybe consider AK and EN bits (placeholder for now).
2024-03-26 07:31:49 -07:00
joevt 224ae50e91 ppcexec: Make more instructions illegal for 601. 2024-03-26 06:50:33 -07:00
joevt 03d7728d46 ppcexec: Use macros to assign subopcode functions. 2024-03-26 06:50:17 -07:00
joevt 19ba15f2f1 ppc: Separate enums for separate fields. 2024-03-26 06:44:26 -07:00
joevt 9da9967b83 ppcopcodes: Cleanup 2. 2024-03-26 06:37:45 -07:00
dingusdev 1510c45ecb Fixed 601 flags 2024-03-26 06:36:32 -07:00
dingusdev 9b76c9fe3e Fix for mffs in opcode table 2024-03-25 20:04:13 -07:00
Maxim Poliakovski ea4564c827 machinepdm: implement extended RAM. 2024-03-26 00:39:33 +01:00
Maxim Poliakovski 0a97e4e038 hmc: implement extended memory for PDM. 2024-03-26 00:39:33 +01:00
Maxim Poliakovski 8e19164977 memctrlbase: introduce add_mem_mirror_common(). 2024-03-26 00:39:33 +01:00
Maxim Poliakovski ab60bb8d0b memctrlbase: cosmetic improvements. 2024-03-26 00:39:33 +01:00
dingusdev c7ca4d9b97
Merge pull request #80 from dingusdev/cpu-refactor2
Use templating for interpreter
2024-03-25 08:01:51 -07:00
dingusdev 3c3d0b46db
Merge branch 'master' into cpu-refactor2 2024-03-25 07:45:21 -07:00
joevt f08d9ba81e ppcexec: Fix templated lhzux. 2024-03-25 07:43:34 -07:00
joevt 15a9ffd340 ppcopcodes: Indent. 2024-03-25 07:42:17 -07:00
joevt b9aae48517 ppcopcodes: Fix templated st. 2024-03-25 07:37:54 -07:00
joevt e2864ab08c ppcopcodes: Fix templated add. 2024-03-25 07:37:52 -07:00
joevt effe0198ce ppcexec: Fix bcctr templated parameters. 2024-03-25 07:36:46 -07:00
joevt c5ac862cef debugger: Add list of input and output registers.
So the debugger can show them during stepping.
The fmt_* functions now take a PPCDisasmContext instead of just the ctx->instr_str so that they can alter the context.
Some fmt_* functions have an alternate (e.g. fmt_twoop_in for fmt_twoop) to indicate a difference in input/output registers.
The mtsrin and mfsrin instructions use a register to indicate which sr register to use.
The string instructions may affect multiple registers but only the first is included in the list.
Removed some extra blank lines.

Fixes:
lscbx: Add r0 check.
mftb: Do simplified if the spr is illegal. Maybe should do illegal opcode instead?
2024-03-24 19:34:29 -07:00
joevt cd77e361ab ppcexceptions: Use MSR enums. 2024-03-24 18:53:05 -07:00
joevt bc5fd44172 ppcmmu: Don't log mmu_mode 1. 2024-03-24 18:52:49 -07:00
dingusdev c781820bf6 Continued table fixes 2024-03-24 17:43:14 -07:00
dingusdev 30802affd4 Continued fixes for tables 2024-03-24 17:24:36 -07:00
dingusdev eab021a5cb Regression fixes 2024-03-24 16:34:42 -07:00
dingusdev 505b5e6468 Slight tweak to PPC Macros 2024-03-24 15:35:11 -07:00
dingusdev 5631485465 Cleaning up templating 2024-03-24 14:06:07 -07:00
joevt fafbd9a04f mmiodevice: Move SIZE_ARG macro from pcibase. 2024-03-24 13:03:31 -07:00
joevt bc582e64cc dbdma: Clear cmd_in_progress before callback.
Because the callback might start DMA commands.
2024-03-24 13:01:12 -07:00
joevt df0044a110 dbdma: Make sure interrupt controller is set. 2024-03-24 13:00:58 -07:00
joevt 503556196a dbdma: Add missing flags, fields, comments. 2024-03-24 13:00:33 -07:00
joevt fd961f9ff9 Fix Analyzer warnings.
In Xcode, type Command-Shift-B to analyze every source file or Command-Shift-Control-B to analyze the current source file.

For pseudo_dma_read report FIFO underrun and init data_word in that case.
2024-03-24 12:56:11 -07:00
joevt e3411670cb videoctrl: Add cursor_dirty flag.
If the flag is set when it comes time to draw the cursor again, then call setup_hw_cursor to update the cursor before drawing the cursor.
2024-03-24 12:45:52 -07:00
joevt d134107aba atirage: Draw frame buffer only when it changes. 2024-03-24 12:45:30 -07:00
joevt 5de1c23aba adbbus: Don't abort. 2024-03-24 12:35:11 -07:00
joevt 72b257e5d1 atirage: Improve draw_hw_cursor loops.
- Read 8 bytes at a time instead of just 1.
- Remove multiply operations from loop. We just need increments or additions.
- Change compares with int to compares with zero.
2024-03-24 12:27:44 -07:00
joevt 9d0bae2d03 atirage: Add offset to cursor X position.
CUR_HORZ_OFF becomes non-zero when the cursor needs to be drawn to the left of the left edge of the frame buffer.

CUR_VERT_OFF is handled differently. When CUR_VERT_OFF is non-zero, CUR_OFFSET is changed to point to the first line of the cursor that will be drawn, so CUR_VERT_OFF is the number of lines to remove from the total height of the cursor.
Alternatively, we could handle CUR_VERT_OFF the same way as CUR_HORZ_OFF by leaving the cursor height constant, drawing the cursor starting from the CUR_VERT_OFF line, and adjusting cursor Y position by negative CUR_VERT_OFF.
2024-03-24 12:26:10 -07:00
joevt ad6d5e9ec9 atimach64gx: Improve draw_hw_cursor loops.
- Read 8 bytes at a time instead of just 1.
- Remove multiply operations from loop. We just need increments or additions.
- Change compares with int to compares with zero.
2024-03-24 12:25:55 -07:00
joevt 6462ceef24 atimach64gx: Add offset to cursor X position.
CUR_HORZ_OFF becomes non-zero when the cursor needs to be drawn to the left of the left edge of the frame buffer.

CUR_VERT_OFF is handled differently. When CUR_VERT_OFF is non-zero, CUR_OFFSET is changed to point to the first line of the cursor that will be drawn, so CUR_VERT_OFF is the number of lines to remove from the total height of the cursor.
Alternatively, we could handle CUR_VERT_OFF the same way as CUR_HORZ_OFF by leaving the cursor height constant, drawing the cursor starting from the CUR_VERT_OFF line, and adjusting cursor Y position by negative CUR_VERT_OFF.
2024-03-24 12:25:35 -07:00
dingusdev c281b27220 Attempted templating for interpreter 2024-03-24 12:21:19 -07:00
joevt 1d5502dc3c ppcemu: Make flags atomic.
For flags that might be accessed by other threads.
2024-03-22 19:04:51 -07:00
dingusdev 4ef3c792de Refactoring interpreter, pt. 1
Reduce the number of global variables used by interpreter
2024-03-22 08:01:29 -07:00
joevt a5aac5754c machinetnt: Add Bandit2 option. 2024-03-20 07:38:56 -07:00
joevt 23903a969d Add get_comp_by_name_optional. 2024-03-20 07:38:48 -07:00
joevt 78020c4794 Add Bandit2 and properties for Chaos. 2024-03-20 07:38:39 -07:00
Maxim Poliakovski 682f1900ae zdocs: add PDM RAM documentation. 2024-03-20 12:16:10 +01:00
Maxim Poliakovski a9cb0cfb2a atahd: report correct CHS parameters & capacity.
This is required for Open Firmware 2.x to boot Mac OS X
and OpenDarwin.
2024-03-20 12:16:10 +01:00
Mihai Parparita fc6a4872d6 Ensure that NVRAM is persisted when exiting the debugger after an abort
The SIGABRT handler is not invoked by the abort() call in the loguru
fatal handler, but either way it's not necessary (it does its own abort).

Switch to explicitly cleaning up the machine object, which as a side
effect in the destructor chain will persist the NVRAM.
2024-03-20 12:16:10 +01:00
dingusdev 284f58dec4 Continued build fixing 2024-03-16 19:24:40 -07:00
joevt e51bc0cea5 scsihd: Reorder switch statement.
Order by case value to match scsicdrom.
2024-03-16 17:44:09 -07:00
dingusdev da9770c99b Attempted build fix 2024-03-16 13:45:28 -07:00
joevt 266d45e13a timermanager: Make immediate timer more immediate. 2024-03-16 11:46:40 -07:00
joevt 703662cb5b timermanager: Add some thread safety. 2024-03-16 11:46:30 -07:00
joevt 833f74dce6 timemanager: Remove unnecessary parameter. 2024-03-16 11:36:41 -07:00
joevt 014aa90462 timemanager: Remove timer minimum timeout. 2024-03-16 11:36:25 -07:00
joevt f5dcaebbf8 timemanager: 0 is also < a positive integer. 2024-03-16 11:36:10 -07:00
dingusdev 02a9e8d886 Partial fix for writing qwords 2024-03-15 22:00:54 -07:00
joevt 4fe8cf76bb control: Fix framebuffer start for OF and macOS. 2024-03-15 10:27:48 -07:00
joevt d4ee43179c control: 15bpp mode is big endian. 2024-03-15 10:27:33 -07:00
joevt 5afe1f1a25 control: MISC_ENABLES is a 12 bit register. 2024-03-15 10:27:24 -07:00
joevt 7eb9a66837 atimach64gx: Calculate vert_blank.
So it's not always zero.
2024-03-15 10:16:44 -07:00
joevt aa33a1644c control: Support unaligned read and size != 4. 2024-03-15 09:29:36 -07:00
joevt c42e1f28d6 atimach64gx: Fix fb_pitch calculation.
Also, move the calculation to crtc_update where we calculate everything else (including bits per pixel which is needed for the fb_pitch calculation.
2024-03-15 09:20:06 -07:00
joevt fe21108f08 atimach64gx: Implement hardware cursor. 2024-03-15 08:50:34 -07:00
joevt 81f3b95914 atimach64gx: Add write CRTC_INT_CNTL.
Required for interrupt handling.
2024-03-15 07:57:47 -07:00
joevt 2daad2d223 atimach64gx: Add write ATI_CRTC_VLINE_CRNT_VLINE.
Maybe for interrupts.
2024-03-15 07:03:57 -07:00
joevt 09becbfb04 atimach64gx: Add write ATI_CRTC_H_TOTAL_DISP.
For debugging.
2024-03-15 07:03:46 -07:00
joevt 1f9f2d2cf1 sixty6: Add support for sixty6 video output. 2024-03-14 20:06:55 -07:00
joevt 939d6d42bb machineproperties: More binary property values. 2024-03-14 19:16:54 -07:00
joevt 45a9d45e3f Add SCSI devices.
scsibus has a new method attach_scsi_devices which is used by all machines to populate a SCSI bus with one or more hard drives or CD-ROM drives.

HDDs are specified by the hdd_img property.
CDs are specified by the cdr_img property.
Multiple images are delimited by a colon :

attach_scsi_devices is called by the scsi controller after the scsi controller has attached itself to the scsi bus.
The bus suffix is applied to the property name.
Curio has no suffix so it will use hdd_img and cdr_img properties.
Mesh is expected to have a suffix of 2 so it will use hdd_img2 and cdr_img2 properties.

HDDs will skip SCSI ID 3 unless 7 HDDs are added, in which case, the seventh HDD will use ID 3.
CDs will start at SCSI ID 3, go to 7, then down to 0.
SCSI IDs are skipped if a device is already using that SCSI ID.

ScsiCdrom and ScsiHD no longer use REGISTER_DEVICE or DeviceDescription or PropMap which is normal for devices that can have multiple instances.
2024-03-14 19:12:11 -07:00
Mihai Parparita cfca42e577 Add basic support for multiple hard disks in the 6100
We treat the hdd_img parameter as a colon-separated list of disk
images and create additional SCSI HD devices as needed.
2024-03-14 19:11:54 -07:00
joevt 4c9b125cc8 amic: Add modem port transmit DMA. 2024-03-14 08:05:42 -07:00
joevt e1e00c951b sc53c94: Split real_dma_xfer.
Create real_dma_xfer_out and real_dma_xfer_in methods.
2024-03-14 08:01:07 -07:00
joevt bc5153dd4a ppcmmu: Make sure dummy page is 8 byte aligned. 2024-03-13 21:45:56 -07:00
joevt aed74479fd ppcmmu: Handle undefined mmu mode. 2024-03-13 21:38:26 -07:00
joevt c14974d167 sc53c94: Init class fields. 2024-03-13 21:37:08 -07:00
joevt f07de5401d sc53c94: Add registers and comments. 2024-03-13 21:25:28 -07:00
dingusdev faf066f2b9 Fixed building 2024-03-13 21:17:04 -07:00
joevt 06640844e9 scsihd: Allow read and write > 4 GB. 2024-03-13 21:09:55 -07:00
joevt bfc703a556 scsihd: Add sector_size.
Replace HDD_SECTOR_SIZE with class field sector_size.
2024-03-13 21:06:18 -07:00
joevt ec01993d84 scsihd: Make seek and rewind illegal. 2024-03-13 18:42:25 -07:00
joevt 5a54b6a761 scsicdrom: Rename read_capacity_10.
Like scsihd.
2024-03-13 07:50:32 -07:00
joevt a605c435b6 scsicdrom: Move test_unit_ready.
Make it a separate method like scsihd.
2024-03-13 07:50:20 -07:00
joevt 05da1708eb scsihd: Remove unused cur_buf_cnt. 2024-03-13 07:46:12 -07:00
joevt 3f826b8971 scsi: Add illegal_command method.
- scsicdrom now logs unsupported commands instead of abort.
2024-03-13 07:45:50 -07:00
joevt de8388f9a7 scsihd: Rename img_buffer to data_buf.
To match scsicdrom.
2024-03-13 07:44:52 -07:00
joevt 569f782a60 scsihd: Rename hdd_img to disk_img. 2024-03-13 07:44:43 -07:00
dingusdev 96b9b6a375 Fixed building 2024-03-12 21:30:38 -07:00
dingusdev 410502fa7e Merge branch 'master' of https://github.com/dingusdev/dingusppc 2024-03-12 21:15:49 -07:00
Maxim Poliakovski a8cd73cc69 hammerhead: remove MACH_TYPE_CATALYST definition.
Catalyst uses another memory controller (Platinum)
that significantly differs from Hammerhead.
Low-level board constants don't match too.
2024-03-12 17:41:12 +01:00
Maxim Poliakovski 54ce23d0a8 platinum: cleanup non-DWORD register reads. 2024-03-12 17:29:19 +01:00
Maxim Poliakovski 2d68b72dbd platinum: use meaningful name for 'register _4B'. 2024-03-12 17:05:47 +01:00
joevt 091cf4337c scsidevice: Remember SELECT_WITH_ATN message.
It might be an IDENTIFY message which contains a LUN number.
2024-03-12 08:01:52 -07:00
joevt ff9b8a59e2 scsibus: Change control lines for MESSAGE_IN. 2024-03-12 07:49:43 -07:00
joevt 6e4544450e platinum: Add register _4B. 2024-03-12 07:07:43 -07:00
joevt d4922beefe platinum: Don't ignore read/write of size != 4.
For reading, we'll return values such that dumping bytes or words or longs in Open Firmware will produce the same info in all cases.
2024-03-12 07:07:36 -07:00
joevt 6f37ff9ea3 platinum: Convert register offset to index.
Same as control.
2024-03-12 07:06:05 -07:00
joevt 1dfa671405 hammerhead: Add Motherboard ID Burst ROM flag. 2024-03-12 07:05:09 -07:00
joevt eb1d5d0a6d atimach64gx: Make sure refresh rate is reasonable. 2024-03-11 20:27:00 -07:00
joevt a190d5cbd9 atimach64gx: Handle all pixel formats. 2024-03-11 20:26:07 -07:00
joevt ab647ec0eb atimach64gx: Move pixel clock calculation. 2024-03-11 20:25:32 -07:00
joevt 1fb9e37ec5 scsidevice: Add check_lun.
This will create a CHECK_CONDITON if the LUN doesn't match.
2024-03-11 20:24:24 -07:00
joevt bcd057d45b atimach64gx: Don't recalculate if nothing changed. 2024-03-11 19:25:42 -07:00
joevt 0a63e2946d atimach64gx: Init some fields. 2024-03-11 19:25:18 -07:00
dingusdev b63e42ecf2 Temporarily removing SOFT RESET ATA command
Not implemented properly. Will re-implement soon.
2024-03-11 19:05:13 -07:00
joevt ee2ec7fe54 atimach64gx: Update VGA unsupported mode message. 2024-03-11 08:00:48 -07:00
joevt 1f63342c96 atimach64gx: Handle disabled display. 2024-03-11 08:00:38 -07:00
joevt 6e094a8edb atirage: Remove crtc_enable. 2024-03-11 07:55:15 -07:00
joevt 2ece059c5e atapi: Add ATAPI_SOFT_RESET command.
I don't know if this is correct.
2024-03-11 07:19:11 -07:00
joevt 7866675a55 debugger: Auto increment after disassemble.
Pressing return after disassembling some instructions should cause the next instruction to disassemble.
2024-03-11 07:13:40 -07:00
joevt 619579bee3 debugger: Add instruction bytes to disassembly.
8 characters for address, 8 characters for instruction hex, followed by instruction.
2024-03-11 07:13:07 -07:00
joevt b5dbaff748 debugger: Don't echo auto-repeated commands. 2024-03-11 07:12:57 -07:00
joevt a0ce1efabe display_sdl: Add Mouse Grabbed to window title.
Necessary when the window is created after a restart while mouse is grabbed.
2024-03-11 07:10:26 -07:00
dingusdev 3fd45abad8
Merge pull request #79 from leap0x7b/patch-1
Fix build on Fedora
2024-03-11 06:52:06 -07:00
leap123 c1b557fbb9
Fix build on Fedora 2024-03-11 13:01:43 +07:00
joevt babefd09f3 atimach64gx: Rename enable_crtc_internal. 2024-03-10 17:02:38 -07:00
joevt 578e5dc063 atimach64gx: Remove disable_crtc_internal. 2024-03-10 17:02:29 -07:00
joevt 2a290ff9c1 atimach64gx: Remove crtc_enable. 2024-03-10 17:02:18 -07:00
joevt bfd60155b6 atimach64gx: Fix DAC_CNTL for non-byte access.
If offset is 2, a size of 2 will also overwrite byte 3.
2024-03-10 17:00:47 -07:00
joevt df09a1e3bf atimach64gx: Register cleanup.
- Use register number instead of offset.
- Have one exit path from the read_reg and write_reg methods.
2024-03-10 16:59:11 -07:00
joevt 3cced5e29b atimach64gx: Use register field names. 2024-03-10 16:58:36 -07:00
joevt bd5ecf8cbb atimach64gx: Init CRTC_DISPLAY_DIS. 2024-03-10 16:54:48 -07:00
joevt 3e6f7ef541 atimach64gx: Init FIFO_CNT. 2024-03-10 16:54:31 -07:00
joevt ac5b434641 atimach64gx: Add verbose_pixel_format.
It is used to log the pixel format/depth.
2024-03-09 17:57:27 -07:00
joevt 00f917f52e atimach64gx: Add io_access_allowed.
This method is used by both pci_io_read and pci_io_write to determine if ISA type I/O access is allowed.
The SPARSE_IO_BASE I/O address is defined. This I/O range is not defined by an I/O BAR.
2024-03-09 17:54:05 -07:00
joevt a11770961e Add pci_find_device for pci type 0 requests. 2024-03-09 17:49:51 -07:00
joevt b0d33a5385 Add pmg3twr machine description. 2024-03-09 17:48:04 -07:00
joevt 2d8f2422b3 dbmda: Fix dma output res_count update.
Use this->res_count to track amount of data transferred.
2024-03-09 06:47:48 -07:00
joevt 0166059d1b dbdma: Add set_stat method.
A hardware device may have status flags connected to 8 status bits of the DBDMA engine.
2024-03-09 06:45:06 -07:00
joevt 50fcb45b88 awacs: Add dma input stub. 2024-03-09 06:20:24 -07:00
joevt 29d13aef09 dma: Add is_active method for input channels.
Since DBDMA contains an input and output channel, rename the existing is_active method to is_out_active.
2024-03-09 06:20:13 -07:00
joevt 31036b8dee grandcentral: Add sound in DMA. 2024-03-09 06:03:33 -07:00
joevt 6f231f3367 grandcentral: IOBus changes.
- Add IOBusDevice (nvram_addr_hi_dev) for NVRAM addr hi.
- Add IOBusDevice (nvram_dev) for NVRAM data.
- Make all IOBusDevices use the same code.
- Log error if 4 least significant bits of offset are not zero.
- Correctly byte swap the value before passing it to the IOBusDevice.
- When reading, duplicate the bytes in a word or dword like a real Power Mac does.
2024-03-09 06:02:23 -07:00
joevt 97f08f21b7 appleramdac: Add get_clut_entry_cb. 2024-03-08 21:02:41 -07:00
joevt 9a26016ed4 memctrlbase: Clear RAM to zero. 2024-03-08 20:58:41 -07:00
joevt f541613c6b mpc106: Fix RAM allocation.
Fix bank_end calculation (left shift 28 instead of 30).
Handle unorderred and discontinous ranges.
2024-03-08 20:58:04 -07:00
joevt 6931b2944b mpc106: Allow multiple of the same PCI device.
Grackle allows attaching different PCI devices. This change allows attach multiple of the same PCI device. To make the name unique in the machine map, the name of the PCI slot is appended to the device name.
2024-03-08 20:57:47 -07:00
dingusdev 3b3634bf5f Continued cleanup for bcl 2024-03-08 19:28:51 -07:00
joevt 0af9d0c972 chario: Cleanup. 2024-03-08 19:27:42 -07:00
dingusdev daeecbe99e Clean-up bc and bcl
Using templating to make the code a touch more readable
2024-03-08 19:22:25 -07:00
joevt 5d9194d03d atimach64gx: Add config read/write stub. 2024-03-08 08:00:21 -07:00
Maxim Poliakovski 3c7ce3de8b
Merge pull request #62 from mihaip/upstream-cuda-restart
Add support for the CUDA_RESTART_SYSTEM command
2024-03-08 15:25:02 +01:00
dingusdev e56d4e63f4
Merge pull request #76 from mihaip/upstream-mouse-grab2
Add a basic mouse grab mode
2024-03-08 07:10:10 -07:00
Mihai Parparita 57e6e90002 Add support for the CUDA_RESTART_SYSTEM command
There are cases where when it's necessary (e.g. given uninitialized NVRAM,
the Beige G3 with the 10.2 install CD inserted will update the boot
device and restart to boot from it).

Restart support was done by wrapping the ppc_exec function in a loop and
checking for a restart power off reason. We also need to disconnect all
event listeners, since they will be recreated when the machine is
re-initialized.
2024-03-07 23:32:23 -08:00
Mihai Parparita c7d2eb87ac Initialize MMU recently used regions in ppc_mmu_init
More encapsulated and allows re-initialization.
2024-03-07 23:31:56 -08:00
Mihai Parparita 7226fe5303 Ensure that video controller refresh task timer is stopped when the object is destroyed. 2024-03-07 23:31:56 -08:00
Mihai Parparita 10af336cd1 Ensure that VIA timers are stopped when the object is destroyed. 2024-03-07 23:31:56 -08:00
Mihai Parparita f218a38294 Ensure that AMIC pseudo-VBL timer is stopped when the object is destroyed. 2024-03-07 23:31:44 -08:00
dingusdev 2f326a8199 Compile fix for CharIO in VS2022
For some reason, it wouldn't define the symbol when trying to compile with Clang.
2024-03-07 20:57:40 -07:00
dingusdev eb07a3c2f1 Templating bclr to match with bcctr 2024-03-07 20:44:36 -07:00
joevt 04526012f9 grandcentral: Add Ethernet ROM. 2024-03-07 07:59:13 -07:00
joevt b5bb214920 ohare: Interrupt fixes.
Based on work done for grandcentral.
2024-03-07 07:58:54 -07:00
joevt 1e587b0848 chario: Move static variable to class. 2024-03-07 07:48:11 -07:00
joevt 97727e0d1e escc: Return NULL if nothing to read. 2024-03-07 07:47:41 -07:00
joevt 723eab59d6 escc: Include port name in log messages. 2024-03-07 07:40:30 -07:00
joevt 1421ccc81e escc: Add dma stub. 2024-03-07 07:30:06 -07:00
joevt e1d43b8eb2 ppcopcodes: Cleanup branch instructions. 2024-03-07 06:56:37 -07:00
joevt 67bd47f11f ppcopcodes: Fixes for bcctr(l)?.
Add MPC601 variants. Variants that decrement and test the ctr are invalid bon't don't appear to trigger an exception. The manual says MPC601 can decrement the counter. Other CPUs do not decrement the counter but will branch based on the value.
2024-03-07 06:55:54 -07:00
joevt e44676e491 ppcfpopcodes: Template mffs variants. 2024-03-07 06:45:46 -07:00
joevt 7b4d513e22 videoctrl: Add change resolution support.
PDM defaults to 640x480.
If you set --mon_id to MacRGB12in then it would draw 512x384 inside a 640x480 window.
If you set --mon_id to Multiscan20in then it would try to draw 832x624 inside a 640x480 window and crash.
If you set the Monitors control panel to switch multiscan display from 640x480 to 832x624 and  restart then it would crash.
Now it will correctly change the window size every time the mode changes.
2024-03-06 21:44:10 -07:00
joevt 5b51cd06c0 atimach64gx: Add register names.
For logging.
2024-03-06 21:20:23 -07:00
joevt 052a47734f macio: Add DMA interrupts. 2024-03-06 21:19:04 -07:00
joevt 54767bf97d More interrupts.
- Add all the interrupts including DMA.
- Modify the Interrupt to IRQ_ID translation so the interrupts belonging to the first set of 32 interrupts don't need to be shifted.
2024-03-06 19:17:16 -07:00
joevt e5bace03f7 Abort if register_dma_int.
Make register_dma_int cause Abort for heathrow and ohare like it does for amic.
2024-03-06 19:17:03 -07:00
joevt 691fcfb657 atimach64gx: PCI BAR changes.
Support changing BAR address.
2024-03-06 18:58:15 -07:00
joevt 49f7da4402 atimach64gx: Remove second column.
It incorrectly implies a relationship between items on the same row.
2024-03-06 18:57:52 -07:00
joevt 6c0ca42fff atimach64defs: Add more registers and bit fields. 2024-03-06 18:57:30 -07:00
Maxim Poliakovski 30c6cbefbd ppcexec: fix indentation, break long lines. 2024-03-06 23:28:40 +01:00
joevt 5a049642ea atirage: Add VBL callback. 2024-03-06 07:50:03 -07:00
joevt b168459007 atirage: Calculate vert_blank. 2024-03-06 07:42:51 -07:00
joevt d5c7b5f537 atirage: Add write CRTC_INT_CNTL. 2024-03-06 07:41:12 -07:00
joevt 506ed000a0 atirage: Add write CONFIG_STAT0. 2024-03-06 07:04:59 -07:00
joevt b92e9216f4 atirage: Add write CRTC_VLINE_CRNT_VLINE.
For VLINE interrupt (but Mac OS X doesn't use enable VLINE interrupt).
2024-03-06 07:04:40 -07:00
joevt 6ff5079df8 atirage: Init CRTC_DISPLAY_DIS. 2024-03-06 07:04:25 -07:00
joevt d686fc04f4 atirage: Fix crtc_update.
Add pixel format and pixel clock to the list of fields that will initiate a recalculation.
If frame rate is less than 24 or greater than 120 then assume 60Hz.
2024-03-05 08:02:50 -07:00
joevt 9aef78be4f atirage: Modify write CLOCK_CNTL.
Consider write-only bits: ATI_CLOCK_STROBE can't be read so clear it.
8 bits at Offset 2 is PLL_DATA. If we don't modify PLL_DATA, then insert the current value of PLL_DATA into the value that will be read from ATI_CLOCK_CNTL.
2024-03-05 08:02:20 -07:00
joevt f6b1c080ad atirage: Init FIFO_CNT of GUI_STAT. 2024-03-05 07:54:19 -07:00
joevt d4fa85688d atirage: Check both offset and size.
When checking if a particular byte of a register is accessed, check both the starting position (offset) and ending position (offset + size) of the bytes being access.
2024-03-05 07:52:12 -07:00
joevt 20b4a33c00 ppcexec: Remove EXHAUSTIVE_DEBUG. 2024-03-05 07:05:03 -07:00
joevt f61055ebc0 ppcexec: Convert if to switch for ppc_opcode19. 2024-03-05 07:03:16 -07:00
joevt 777a02cbe9 pci: Use SIZE_ARG for logging arg size.
The SIZE_ARG macro defined in pcibase can be used outside pcibase with a small modification.
2024-03-04 21:16:46 -07:00
joevt 61b1940397 pci: Add command register mask.
The mask represents the list of bits that are allowed to change in the command register of PCI config space.
2024-03-04 21:16:38 -07:00
joevt eef6d267c3 atirage: PCI BAR changes.
- Add BAR 2 decode. This BAR isn't actually used by Mac OS X, but decode it anyway just in case.
- Support updating of BARs (using change_one_bar method).
2024-03-04 21:13:07 -07:00
joevt 9c48c296c8 atirage: Register cleanup.
- Use register number instead of offset.
- Have one exit path from the read_reg and write_reg methods.
2024-03-04 21:11:56 -07:00
joevt 214c61669a videoctrl: Add pixel_format.
pixel_format is different than pixel_depth.
pixel_format depends on the GPU. A GPU might have multiple formats for the same depth.
We store this in videoctrl so that we can detect changes in pixel_format like we do for pixel_depth and active_width and active_height.
2024-03-04 21:11:42 -07:00
joevt fb0396923f Fix dma STORE_QUAD and LOAD_QUAD.
When fetching DMA command, make sure to convert little endian fields to host endianness (i.e. don't use memcpy).
When fetching DMA command, return the host address of the DMA command for LOAD_QUAD. Maybe this address should be cached whenever this->cmdptr changes?
When fetching DMA command, return whether the DMA command is writable.
For LOAD_QUAD, pass the address of the DMA command to xfer_quad.
Always log unexpected DMA command values.
Write full 32-bit value for LOAD_QUAD.
Write reqCount to resCount.
2024-03-04 20:38:07 -07:00
joevt 6503a300cc Add PCI interrupt method.
A PCI device passes an interrupt to its host. The host will determine from the PCI device which interrupt to trigger.
2024-03-04 07:47:20 -07:00
joevt be80595834 Remove obsolete pci config type 1 methods.
pci_find_device is the method used to pass pci config type 1 methods to child PCI devices.
2024-03-04 07:47:08 -07:00
joevt 54bda0ea95 pci: Change pci_conv_rd_data unaligned.
pci_conv_rd_data can be used to handle unaligned or 64-bit accesses in mmio regions if it's modified to include the next 32-bit value.
For pci config accesses, grackle repeats the 32-bit value. bandit uses a seemingly random number for the next 32-bit value, but we'll make it work like grackle.
2024-03-03 20:06:13 -07:00
joevt 6d23e18c11 pci: Add PCI CardBus bridge.
PCCard is used by PowerBook G3 Wallstreet in Open Firmware 2.0.1.
CardBus is probed in New World Macs starting from at least Open Firmware 4.1.9f1 sometime after Open Firmware 3.1.1.

- Create PCIBase from common stuff in PCIDevice.
- Add PCIBridgeBase. These have a primary bus number, secondary bus number, and subordinate bus number which are used to determine if PCI type 1 config cycle should be passed.
- Change PCIBridge to use PCIBridgeBase instead of PCIDevice.
- Add PCICardBusBridge which uses PCIBridgeBase.
2024-03-03 16:00:55 -07:00
joevt 5f8e7fcb73 pci: Log invalid BAR values.
For example, Old World Macs have versions of Open Firmware that don't support 512 MB BARs correctly. They may attempt to set such a BAR to 0x90000000 (a 256 MB boundary) instead of 0xA0000000 (the next available 512 MB boundary).
2024-03-03 15:49:59 -07:00
joevt 214b52a96a machinefactory: Fix spelling. 2024-03-03 15:13:29 -07:00
joevt d426d0faeb Add settings when adding pci device. 2024-03-03 15:01:39 -07:00
joevt ebb51addd7 debugger: Allow interrupt of disassembly.
Part of the "debugger: Fix interrupt signal." commit.
2024-03-03 14:36:24 -07:00
joevt c64fab6ecb heathrow: Align read/write messages. 2024-03-03 12:03:06 -07:00
joevt 696bd6f316 mpc106: Remove pci_read and pci_write.
Because they are only called once and are small enough to include in read and write methods like they are in BanditHost.
2024-03-03 11:56:37 -07:00
joevt 7a3a661e2a platinum: Allow reading swatch registers. 2024-03-03 11:47:03 -07:00
joevt f0949d296d platinum: Don't abort. 2024-03-03 11:46:40 -07:00
joevt d2ebcb24b9 platinum: Use calculated fb_ptr for HW cursor. 2024-03-03 10:43:57 -07:00
joevt 644087b592 platinum: Fix fb_ptr calculation. 2024-03-03 10:43:40 -07:00
joevt be2f5273d1 platinum: Init bank_base.
Because the constructor doesn't do it.
2024-03-03 10:43:25 -07:00
joevt 10053a8a1b atirage: Rename variable.
To match other occurrences in the same file.
2024-03-02 20:49:25 -07:00
joevt 9cefaec49c atirage: Spelling. 2024-03-02 20:44:39 -07:00
joevt 55b79c1518 atirage: Use register bit field names. 2024-03-02 20:44:08 -07:00
joevt c2ab86d4ba atirage: Do something for hw cursor invert pixels.
Try a 50% alpha blend with black.
2024-03-02 20:43:54 -07:00
joevt 0e5fcde1e9 atirage: Add CUR_HORZ_VERT_OFF name. 2024-03-02 17:27:45 -07:00
joevt 002cce886c atirage: Indent. 2024-03-02 17:15:39 -07:00
joevt 151ea2ece4 atimach64defs: Add register bit fields. 2024-03-02 17:15:25 -07:00
joevt 568882a2ea atimach64defs: Sort addresses in descending order. 2024-03-02 17:14:59 -07:00
joevt f38d6d73f4 atimach64defs: Add more device IDs. 2024-03-02 17:08:31 -07:00
joevt 569893861d heathrow: Don't set lat_timer.
It will be set by firmware.
2024-03-02 16:42:49 -07:00
joevt e81ac6f61e Add PERCH slot.
So you can connect a USB controller or whatever to it.
2024-03-02 16:02:19 -07:00
joevt 177098c957 debugger: Fix interrupt signal.
Typing Control-C in Terminal app causes an interrupt signal that should enter the DPPC debugger but this only worked once since the signal handler never returned. Even if the signal handler reenabled the signal somehow, it calls enter_debugger recursively which is strange since the earlier calls to enter_debugger would never return.

Now the signal handler just sets a flag (power_on) which can be used to exit any loop (emulator loops, stepping loops, disassembly loops, dumping loops).

Main always calls enter_debugger now which calls the ppc_exec loop. The power_on flag will exit the ppc_exec loop to return to the debugger. Recursion of enter_debugger is eliminated except for calls to loguru's ABORT_F.

An enum power_off_reason is used to indicate why the power_on flag is set to false and to determine what happens next.
2024-03-02 12:57:02 -07:00
joevt 1e78512c95 Rename Curio and Mesh. 2024-03-02 11:12:45 -07:00
joevt 1b147151f0 videoctrl: Rename get_palette_color.
So it matches set_palette_color.
2024-03-02 08:59:02 -07:00
joevt ad8a26616f scsidevice: Add LUN field.
This may make it possible for multiple LUNs to be added to the same target ID.
For now just use LUN #0.
2024-03-02 08:52:09 -07:00
joevt 8f28823217 scsi: Initialize cur_phase. 2024-03-02 08:51:45 -07:00
joevt b509df78df Don't allow vert_blank to be 0. 2024-03-02 08:37:44 -07:00
joevt 5876cc7e17 bandit: Fix indent. 2024-03-02 08:09:42 -07:00
joevt de73a36399 ppcmmu: Move defines to the top. 2024-03-02 07:58:25 -07:00
dingusdev c9aed600b6 More opcode clean-up 2024-03-02 07:57:15 -07:00
joevt 318e035344 ppcmmu: Shorten tlb_flush_entries. 2024-03-02 07:56:26 -07:00
joevt cd097232cb ppcmmu: Shorten tlb_flush_entry.
Don't need a weird short loop.
2024-03-02 07:56:17 -07:00
joevt b7b783b6be ppcmmu: Shorten ppc_mmu_init.
Also, initialize all the fields.
2024-03-02 07:50:02 -07:00
joevt 968f503d80 debugger: Improve my_sprintf for short strings.
Don't need to repeat snprintf with memory allocation if the string is short.
2024-03-02 07:24:17 -07:00
joevt 7fc92e236b Fix extract_bits and insert_bits.
(1 << len) is actually 1 when len is (sizeof(T)*8) so we need to special case that. len is usually constant so the compare won't be compiled.
2024-03-02 06:54:58 -07:00
joevt 3062a29b78 Add clear_bit and set_bit. 2024-03-02 06:54:45 -07:00
joevt 3bea3ec3d8 ppcmmu: Fix compiler warnings. 2024-03-01 20:12:42 -07:00
joevt 0f66d454c1 ppcmmu: Cleanup the cleanup. 2024-03-01 20:05:10 -07:00
joevt 6738d7472e ppcmmu: Add a function to get phys address.
Since the function is for the debugger during stepping or disassembly, don't do extra logging.
2024-03-01 19:48:52 -07:00
joevt 500f38a496 ppcmmu: Add phys address to mmu_translate_imem.
For debugging.
2024-03-01 19:45:09 -07:00
joevt 3a5a70b56d ppcmmu: Allow convert virtual to physical address.
The TLBEntries allow converting virtual guest address to virtual host address but there's no easy way to get a guest physical address for debugging purposes.
Add a phys_tag field to fix that.
2024-03-01 19:42:25 -07:00
joevt 15e132c824 cmake: Add header files in CMakeLists.txt.
So they will appear in Xcode project.

mkdir -p dingusppc/build-xcode
cd       dingusppc/build-xcode
cmake -G Xcode ..
xcodebuild -configuration Release
2024-03-01 19:41:43 -07:00
dingusdev 2b3cf58b8a Continued clean-up 2024-03-01 19:40:46 -07:00
joevt 2998796c2c pci: Fix log message for PCI rom exceptions.
It should log device name, not just "PCIDevice".
2024-03-01 08:02:01 -07:00
joevt 3978d0754d CD-ROM: Add max blocks check.
The code does not support more than 2^32 - 2 blocks because of this expression: static_cast<uint32_t>(this->size_blocks + 1)
2024-03-01 08:01:31 -07:00
joevt 655b9a17e1 psx: Set name of Psx memory controller.
For the logs.
2024-03-01 07:59:33 -07:00
joevt 84a694d4c2 nvram: Output nvram file name in error message.
So that you don't see identical messages "Could not restore NVRAM content from the given file."
Instead, one will mention nvram.bin and the other will mention pram.bin.
2024-03-01 07:59:19 -07:00
dingusdev ebac8b92ba Clean-up for loading instructions
Expanding the scope of the clean-up from lscbx to other loading/storing instructions.
2024-03-01 07:57:46 -07:00
joevt 6a4326af39 poweropcodes: lscbx cleanup. 2024-03-01 07:46:04 -07:00
joevt 6a51e8a1c9 debugger: regs command needs to reset setfill.
So printenv will fill with spaces after regs.
2024-03-01 07:28:58 -07:00
joevt 006a90f681 debugger: Some commands should not repeat.
Entering a blank command causes the last command to repeat. This doesn't make sense for the following commands:
help, quit, regs, context, printenv, setenv, nvedit, amicint, viaint.
2024-03-01 07:26:34 -07:00
joevt 17983e7fad debugger: Fix setenv command to allow spaces.
To allow setting boot-command to "0 bootr -v debug=0x144" for example.
2024-03-01 07:13:41 -07:00
joevt a15b1805fb debugger: Fix handling of empty commands.
An empty command should not be reported as an unknown command.
An empty command should not repeat an unknown command.
2024-03-01 07:08:12 -07:00
joevt 11e0bd79b0 debugger: Work around Xcode Terminal.app issue.
If debugging with Xcode using Terminal.app for the console, Terminal.app may send empty lines to the dingusppc debugger while resizing the Terminal.app window and it will do this forever.
As a workaround, use std::cin.clear() to clear the input buffer before getline and loop until the terminal window size is not changing or the input line is not empty.
2024-03-01 06:56:20 -07:00
Mihai Parparita bfbf4cb453 Add a basic mouse grab mode
Take 2 of 587eb48f61. We now implement
this at the SDL level, which works cross-platform and allows us to
ensure that the guest never gets the Control-G event.
2024-02-29 22:58:50 -08:00
dingusdev 229509a067 Add help for mregs 2024-02-29 21:48:04 -07:00
joevt ff5c43e6cb debugger: Add mregs command.
To dump more registers.
2024-02-29 21:46:44 -07:00
joevt 7cd3aae753 debugger: Fix repeated until.
The loop needs to be do while instead of while do.
2024-02-29 18:57:11 -07:00
joevt fe05b1de12 Fix compiler warnings.
Xcode build has compiler warnings involving loss of precision. Remove them by adding type casts. Check results in some cases for overflow.
2024-02-29 18:49:14 -07:00
joevt 1903c8b557 debugger: Fix reg_op for floating point registers. 2024-02-29 18:48:59 -07:00
joevt 456a96042f debugger: Shorten try catch in reg_op.
Because SPGR0 matches SPR but stoul causes an exception.
2024-02-29 07:55:09 -07:00
joevt 888df0ac53 debugger: Add more register names for reg_op.
So the debugger can output their values.
2024-02-29 07:46:16 -07:00
joevt 18afe91a82 atirage: Use convert frame big-endian methods. 2024-02-26 14:46:31 +01:00
Maxim Poliakovski b8d0ed39d9 atirage: fix FB pitch calculation. 2024-02-26 14:21:53 +01:00
Maxim Poliakovski 0c3f399de3 poweropcodes: fix compiler warning. 2024-02-24 22:51:05 +01:00
Maxim Poliakovski 45ccabb11d poweropcodes: improve lscbx emulation. 2024-02-24 22:46:54 +01:00
dingusdev d71a213c4b Continued clean-up, part 3 2024-02-22 19:46:34 -07:00
dingusdev 8e9123bdce Slightly less clumsy check for compiler 2024-02-21 07:14:21 -07:00
dingusdev 2e3e65f3e7 Moving vid_enable_seq
Visual C complains about this being in the wrong place
2024-02-21 07:07:18 -07:00
dingusdev 9db53a4e3f Updating CMakeLists file 2024-02-21 06:32:24 -07:00
joevt c2e098e535 zdocs: Add Open Firmware $find bug description. 2024-02-20 18:28:59 -07:00
joevt a6de2c2b44 zdocs: Fix Open Firmware startvec notes.
Fields 488 to 48c are not part of the startvec struct. They are for the fcode file struct used by byte-load-file.
2024-02-20 18:28:51 -07:00
dingusdev 9dad9ea38b Revert Memory exceptions use mmu handler. 2024-02-20 18:22:55 -07:00
dingusdev 1d938c93b6 Mask fixes for sr(*) 2024-02-20 18:15:08 -07:00
joevt 35bc1bcb44 poweropcodes: Fix sriq. 2024-02-20 18:07:38 -07:00
joevt 1438ebc12a poweropcodes: Fix compiler warnings. 2024-02-20 18:07:26 -07:00
joevt 61b29f6fab ppcexceptions: Memory exceptions use mmu handler. 2024-02-20 18:07:04 -07:00
dingusdev fb9b6886fa Add stdio for Windows serial 2024-02-20 18:05:11 -07:00
joevt 0e3eaf724b ppcfpopcodes: Fix stfs*, attempt #2. 2024-02-20 02:04:02 +01:00
joevt 2a05ccbee1 ppcfpopcodes: Fix fres.
Don't convert to float until the end.
2024-02-20 02:03:41 +01:00
joevt 59bee01c0a ppcfpopcodes: Fix fmsubs inf nan check.
There's probably still an issue with the inf_nan check
using reg_a for the first value instead of reg_a * reg_c.
This will probably need rewriting anyway.
2024-02-20 02:02:58 +01:00
joevt 4e4c8d71be ppcfpopcodes: Fix fadds inf nan check. 2024-02-20 02:01:55 +01:00
joevt c7ae31dfce ppcfpopcodes: Spaces. 2024-02-20 02:01:37 +01:00
joevt 2ea80b0aab ppcopcodes: Cleanup. 2024-02-20 01:59:54 +01:00
joevt 5bbf5ee3af ppcopcodes: Fix divw overflow result. 2024-02-20 01:59:40 +01:00
joevt 748e9c5d86 ppcopcodes: Write protect XER zero bits. 2024-02-20 01:59:01 +01:00
joevt 8764beba39 ppcopcodes: Fix lswx.
- Remove invalid form check unless you know for sure it's supposed to cause an exception.
- Add register skip for 601 CPU. This needs testing.
2024-02-20 01:57:00 +01:00
joevt 1fc551fae0 Fix yosemite machine description name. 2024-02-20 01:54:03 +01:00
joevt 8baf722343 Spelling. 2024-02-20 01:53:32 +01:00
joevt cf4ce01ddd ppcopcodes: set DSISR for alignment exception. 2024-02-20 01:22:55 +01:00
joevt 57d919e424 appleramdac: HW cursor fixes.
- Add mask so that hardware cursor cannot be drawn beyond the right edge of the frame buffer.
- Add invert pixels. Invert pixels are used in the I-beam cursor and the Watch cursor.
2024-02-20 01:00:50 +01:00
joevt b0dc893a05 dma: Add name to dma classes.
For logging purposes, each DMA channel should have a name.
2024-02-19 15:30:20 +01:00
Maxim Poliakovski f5bb484226 sc53c94: fix interrupt reporting. 2024-02-19 15:30:20 +01:00
dingusdev 29f3ffd474 Continued clean-up, part 2 2024-02-18 07:06:27 -07:00
dingusdev b160e38f8f Continued code clean-up 2024-02-16 06:55:13 -07:00
Maxim Poliakovski bc2714ab2a platinum: handle non-DWORD register accesses. 2024-02-15 15:35:17 +01:00
dingusdev 38d94e509f Further code cleanup 2024-02-14 22:19:08 -07:00
dingusdev ec23a532f6 Minor clean-up for branching instructions 2024-02-12 20:59:04 -07:00
joevt 8a800062dd grandcentral: Add DMA channel enum. 2024-02-12 14:06:19 +01:00
Maxim Poliakovski cf14144d5b machinetnt: add Power Macintosh 7300. 2024-02-12 02:45:29 +01:00
Maxim Poliakovski 28e7a806b4 grandcentral: use MeshStub on machines without MESH. 2024-02-12 02:38:38 +01:00
Maxim Poliakovski a0e56aa4cf grandcentral: connect external SCSI HW. 2024-02-12 02:17:09 +01:00
Maxim Poliakovski b3e3b73159 grandcentral: connect MESH HW. 2024-02-12 02:17:09 +01:00
Maxim Poliakovski 046452fc56 mesh: various improvements. 2024-02-12 02:17:09 +01:00
Maxim Poliakovski e77b8785ff grandcentral: wire SWIM3 DMA interrupt. 2024-02-12 02:01:01 +01:00
Maxim Poliakovski 061fc5a24d hwinterrupt: remove deprecated SCSI0 & SCSI1. 2024-02-12 01:46:21 +01:00
Maxim Poliakovski cb8c2cb450 Wire CONTROL interrupt. 2024-02-12 01:46:21 +01:00
Maxim Poliakovski 98d661eda1 Wire PLATINUM interrupt. 2024-02-12 01:46:21 +01:00
Maxim Poliakovski 8ddbc9c427 Wire SCSI_MESH interrupt. 2024-02-12 01:46:21 +01:00
Maxim Poliakovski 5902cd5c28 Wire SCSI_CURIO interrupt. 2024-02-12 01:46:21 +01:00
Maxim Poliakovski ce2f6ddadd grandcentral: cleanup interrupt acknowledgement. 2024-02-12 01:46:21 +01:00
Maxim Poliakovski cdc5589bcf hwinterrupt: new interrupt definitions (joevt's work). 2024-02-12 01:42:59 +01:00
joevt 833534bdaa machinecatalyst: Cleanup board register 1. 2024-02-11 23:11:10 +01:00
Maxim Poliakovski 8d30fea63b platinum: implement video emulation. 2024-02-11 22:59:43 +01:00
Maxim Poliakovski 7d06c5b37a control: use RaDACal emulation from appleramdac. 2024-02-11 22:59:43 +01:00
Maxim Poliakovski dd95468d74 New source for TNT RAMDAC emulation (DACula & RaDACal). 2024-02-11 22:59:43 +01:00
Maxim Poliakovski 478bd31dc7 dbdma.h: add missing include. 2024-02-11 22:59:43 +01:00
Maxim Poliakovski 44b1d34cc7 control: support interlaced modes. 2024-02-11 22:22:27 +01:00
joevt d0b0b8070c ppcopcodes: Fix l*ux? invalid form check.
Invalid form is (reg_a == reg_d) || reg_a == 0.
Therefore, valid form is (reg_a != reg_d) && reg_a != 0
!(a || b) == !a && !b
2024-02-11 07:41:13 -07:00
joevt 996857b10d endianswap: Add parenthesis around macros. 2024-02-11 07:40:42 -07:00
joevt fd81d7b040 ppcfpopcodes: Fix load float.
Type casting an int to a float assigns the value of the int to the float which is not what is needed here.
2024-02-11 07:40:17 -07:00
joevt 267a9448ea ppctest: Fix floating-point tests.
genppctests.py
- Fix incorrect bits for some floating-point instruction opcodes or fields.
- Use separate register for FP results like DolphinPPCTests does.
- Remove extra FMULS.
- Use a regular expression for parsing ppcfloattest.txt. Don't parse the values, just put them in the output ppcloattests.csv file.

ppcfloattest.txt
- Clear crf0 and crf7 because we only care about crf1.
- Use values from DolphinPPCTests (0.0, 0.5, 1.0, 3.5, DBL_MAX, FLT_MAX, 2.4679999352, 4.9359998704, etc.). Some of the values were rounded. This will un-round them. Specify snan or qnan instead of nan.
- One of the FCMPO and FCMPU tests had qnan instead of snan input values.

ppcfloattest.csv
- Regenerate this file using the updated genppctests.py which uses the updated ppcfloattest.txt.

ppctests
- Update double_from_string to be able to parse the new values (snan, qnan, FLT_MAX, DBL_MAX).
2024-02-10 14:56:21 -07:00
joevt 01e45d656e ppcfpopcodes: Update header date. 2024-02-10 14:47:46 -07:00
joevt 9199b1e520 ppcfpopcodes: Fix multiply add opcodes.
Use std::fma for all of them for max accuracy.
For single precision opcodes, convert only the result, not the operands.
2024-02-10 14:19:09 -07:00
joevt 3be22dac99 ppcfpopcodes: No float cast for operand check. 2024-02-10 14:18:49 -07:00
joevt ff895aa8a4 ppcfpopcodes: Remove some globals.
ppc_result64_d and ppc_dblresult64_d don't need to be globals. The rest are unused.
2024-02-10 13:56:07 -07:00
joevt c9c4280e6e ppcfpopcodes: No float cast for operand check. 2024-02-10 13:02:49 -07:00
joevt dac9c1e52c ppcfpopcodes: Fix fctiw* round to nearest.
0.3 should not round up to 1.
2024-02-10 12:58:58 -07:00
joevt a7e6ab33a1 ppcfpopcodes: Make fctiw* results QNaN. 2024-02-10 12:58:29 -07:00
joevt 6c49b87a06 ppcopcodes: Fix rlwnm when shift > 31. 2024-02-10 12:54:41 -07:00
joevt 29e5bbdcc0 ppcopcodes: Fix divw. 2024-02-10 12:54:12 -07:00
joevt 4fcb357e2f ppcfpopcodes: add 601 variant of mffs. 2024-02-10 12:51:48 -07:00
joevt ddb5259464 ppcexec: Make illegal operations per CPU model. 2024-02-10 12:51:00 -07:00
dingusdev 52dfc0cf93 Slightly faster typecasting 2024-01-31 08:06:33 -07:00
Maxim Poliakovski ad58d102df machinecatalyst: implement board register 1. 2024-01-30 00:34:24 +01:00
Maxim Poliakovski 9847f5ba6c machinecatalyst: remove video properties.
They will be implemented in the Platinum source.
2024-01-30 00:34:24 +01:00
Maxim Poliakovski 5f06be6226 control: implement HW cursor rendering. 2024-01-19 23:48:22 +01:00
Maxim Poliakovski a68afbf79a videoctrl: add one more cursor rendering callback. 2024-01-19 23:48:22 +01:00
joevt bf425884fb ppcopcodes: Add ppc_grab_dab.
For instructions that don't use the general purpose registers.
2024-01-19 12:09:24 +01:00
joevt 4430fd89a9 ppcopcodes: Fix subfic. 2024-01-19 12:00:55 +01:00
Maxim Poliakovski 7432369162 display_sdl.cpp: improve formatting and add license header. 2024-01-19 09:48:54 +01:00
Maxim Poliakovski 43dc9ed88a control: remove unused defines and variables. 2024-01-19 09:48:54 +01:00
Maxim Poliakovski d413e4a278 control: incorporate recent HW knowledge. 2024-01-19 09:48:54 +01:00
joevt 5c460c9f3b videoctrl: Add VBL callback.
Allows overriding the method used to post interrupts.
2024-01-13 00:41:08 +01:00
joevt cf9237f7d6 control: Fixes.
- Fix video vram endianness. It should behave like RAM.
- Add read for registers ENABLE, INT_STATUS, INT_ENABLE.
- Add write for registers CNTTST, INT_ENABLE.
- Add support for 16bpp and 32bpp.
- Add vbl interrupt.
2024-01-13 00:41:08 +01:00
dingusdev a0b1d6394a Another revert
This affects Virtus VR - With the lmw checks, the opening sign doesn't display and the intro crashes sooner
2024-01-07 17:45:05 -07:00
dingusdev c6af1e31fe Partial revert of previous commit 2024-01-07 17:21:11 -07:00
dingusdev a5ce6a806f CPU clean-up 2024-01-07 17:04:51 -07:00
dingusdev a59475af1c Further lha(*) fixes 2024-01-05 19:10:05 -07:00
dingusdev 1cc1ac2e68 Fixing lha(*) opcodes 2024-01-05 17:19:03 -07:00
dingusdev 924b80574a Further fix from last commit 2024-01-05 15:53:56 -07:00
dingusdev f3a759c80d CPU code clean-up 2024-01-05 15:11:37 -07:00
dingusdev 79ee8543f5
Merge pull request #77 from mihaip/upstream-machineid
machinepdm: fix machine ID
2024-01-03 07:44:38 -07:00
Maxim Poliakovski 9b30dfb474 ppcfpopcodes: refactor fctiw/fctiwz emulation. 2024-01-03 01:27:21 +01:00
joevt 0100e67ebf ppcfpopcodes: Fix fctiw/fctiwz. 2024-01-03 01:07:53 +01:00
joevt bd419912b5 ppcfpopcodes: Fix stfs*.
It should try to convert its operand to a single precision
floating point number at least.
2024-01-02 21:53:03 +01:00
Maxim Poliakovski cb85d358d1 Remove unused globals. 2024-01-02 17:51:12 +01:00
Maxim Poliakovski 5b114c2412 ppcopcodes: refactor mtcrf emulation. 2024-01-02 17:44:35 +01:00
Maxim Poliakovski c25b027de4 ppcfpopcodes: fix mtfsf emulation. 2024-01-02 17:21:08 +01:00
Maxim Poliakovski 8595dd7d99 ppcfpopcodes: fix mtfsfi emulation. 2024-01-02 17:21:08 +01:00
joevt 61a90e2cfb ppcfpopcodes: Fix mcrfs. 2024-01-02 15:52:30 +01:00
Maxim Poliakovski 593508df22 Refactor subfze. 2024-01-02 13:44:56 +01:00
joevt 1f3505f371 ppcopcodes: Fix subfze. 2024-01-02 13:44:13 +01:00
Maxim Poliakovski 679e80a7c3 atahd: basic commands for disk I/O. 2023-12-30 16:23:00 +01:00
Maxim Poliakovski ebdefb5acd atabasedevice: transfer data in chunks. 2023-12-30 16:23:00 +01:00
Mihai Parparita e36e1cf282 machinepdm: fix machine ID
Handle 4 byte reads from the machine ID MMIO region. Also change the 6100
machine ID to match the one used by MAME.
2023-12-29 23:31:16 +00:00
Maxim Poliakovski fef5bde0c7 Refactor recent subfme fix. 2023-12-24 02:56:47 +01:00
joevt dc00879419 ppcopcodes: Fix subfme. 2023-12-24 02:36:34 +01:00
joevt bae488fd97 ppcfpopcodes: Fix lfs* opcodes. 2023-12-22 13:11:13 +01:00
joevt 0a8c1df968 ppcopcodes: Fix sraw. 2023-12-19 16:30:02 +01:00
joevt 4c49558120 ppcopcodes: Fix subfe. 2023-12-19 14:57:41 +01:00
Maxim Poliakovski 750f91e339 ppcemu.h: add enum for XER bits. 2023-12-19 14:57:41 +01:00
Maxim Poliakovski d24b5d21b8 CRx_bit enum stores masks for now. 2023-12-19 14:27:57 +01:00
Maxim Poliakovski 9dbfde1a4c Cleanup previous commit. 2023-12-19 13:15:10 +01:00
joevt 7f229b0fe8 ppcfpopcodes: Fix fcmpo/fcmpu.
It was always changing CR1 (starting at CR bit 4) instead of the CR selected by crfD.
Also, it was clearing all but the FL,FG,FE,FU bits of FPRF of FPSCR.
2023-12-19 13:15:10 +01:00
Maxim Poliakovski 920c2024be Revert "Add a basic mouse grab mode when running on macOS"
This reverts commit 587eb48f61.
2023-12-19 13:15:10 +01:00
dingusdev 2a0f391113 Compilation fix for machineproperties.cpp 2023-12-17 20:04:10 -07:00
dingusdev c3b770ef17
Merge pull request #74 from mihaip/upstream-mouse-grab
Add a basic mouse grab mode when running on macOS
2023-12-15 18:03:45 -07:00
dingusdev a14fbe865f
Merge pull request #75 from mihaip/upstream-extended-adb
Add basic support for the extended ADB mouse protocol.
2023-12-15 18:02:56 -07:00
Mihai Parparita ae0bb838bf Add basic support for the extended ADB mouse protocol.
We now respond to the switch to device handler ID 4 and the register 1
read (as described by https://developer.apple.com/library/archive/technotes/hw/hw_01.html#Extended).

We don't yet use the extra precision bits in the register 0.
2023-12-15 16:04:27 -08:00
Mihai Parparita 587eb48f61 Add a basic mouse grab mode when running on macOS
Adds a "Grab Mouse" command to the "Window" menu which toggles
SDL_SetRelativeMouseMode. That "traps" the mouse to the current window
(and hides it) which makes it less annoying to deal with the separate
acceleration curves of the host and guest OS.

This needs to be done via AppKit code since SDL does not have a
cross-platform way to add menu commands.

I also looked into the extended ADB mouse protocol but it does not
appear to do that we want. If we use it to report a device class of
classAbsolute (0) the acceleration curve changes to a flat response
(we end up using 'accl' 0 from 9dd3c4bef8/base/SuperMarioProj.1994-02-09/Resources/MiscROMRsrcs.r (L64-L76))
but the coordinates are still interpreted as being deltas. If I use a
device type 4 (Absolute pointing devices) then the mouse position is
never queried. Per https://68kmla.org/bb/index.php?threads/anyone-have-an-adb-graphics-tablet-or-joystick.39128/
all tablet devices end up using custom drivers.
2023-12-15 15:38:32 -08:00
dingusdev 0e0de638d4 Update loguru from the master branch 2023-12-11 07:45:51 -07:00
Maxim Poliakovski fd92d86954 mesh: add MESH TNT variant. 2023-12-11 08:05:39 +01:00
dingusdev c41f5355fd
Merge pull request #73 from mihaip/upstream-scsi-name
Fix uninitialized value read in the ScsiHardDisk constructor
2023-12-10 10:58:25 -07:00
Mihai Parparita ea9de4feaf Fix uninitialized value read in the ScsiHardDisk constructor
The call to the ScsiDevice superclass was using the name field (which
was not initialized yet) instead of the name constructor argument
2023-12-10 08:58:12 -08:00
Maxim Poliakovski 114737db41 scsicdrom: use CdromDrive as base class. 2023-12-10 00:19:44 +01:00
Maxim Poliakovski bf278af950 scsidevice: add get_more_data() method.
It is required for supporting large data transfers split
into multiple chunks.
2023-12-10 00:19:44 +01:00
Maxim Poliakovski e1b231882e cdromdrive: declare some methods virtual. 2023-12-10 00:19:44 +01:00
Maxim Poliakovski fa04cde25d machinetnt: more control of machine configuration. 2023-12-10 00:19:44 +01:00
Maxim Poliakovski 705dd390e9 grandcentral: respect size when reading from IOBus devices. 2023-12-10 00:19:44 +01:00
Maxim Poliakovski 078aa79270 grandcentral: remove board register 1 stub. 2023-12-10 00:19:44 +01:00
Maxim Poliakovski a1ad0a3e07 machineid: implement BoardRegister class. 2023-12-10 00:19:44 +01:00
Maxim Poliakovski 858f699750 hammerhead: add configuration accessors. 2023-12-10 00:19:44 +01:00
dingusdev 7cf3d9cd94
Merge pull request #72 from mihaip/upstream-ub
Avoid some undefined behavior
2023-12-08 06:45:44 -07:00
joevt c28e1fa0be ppcmmu: Fix write accesses to read-only memory. 2023-12-08 11:15:04 +01:00
Mihai Parparita e9bc8926ab Avoid some undefined behavior
The `SubOpcode31Grabber[1024] = { ppc_illegalop }` initializer only
populates the first entry with ppc_illegalop (at least on some compilers),
switch to explicitly initializing the entire array with std::fill_n.

Also fix a couple of sign and overflow issues flagged by the Xcode
undefined behavior sanitizer.
2023-12-07 23:59:49 -08:00
dingusdev 4d7204debc
Merge pull request #71 from mihaip/upstream-ati-rage
Add support for more ATI Rage video modes
2023-12-07 18:03:51 -07:00
Mihai Parparita 30ded5e803 Add support for more ATI Rage video modes
Cherrypicks a small piece of joevt/dingusppc@117ca1e449
so that booting from the 10.2 CD gets past it trying to change the video
mode to 15bpp.

Co-authored-by: joevt <joevt@shaw.ca>
2023-12-07 00:16:48 -08:00
Maxim Poliakovski 65a343ce5c Clean up names for SCSI devices. 2023-12-04 22:41:01 +01:00
Maxim Poliakovski 8841c3e7f9 scsihd: more commands. 2023-12-04 21:41:55 +01:00
Maxim Poliakovski 1e4579a076 Improve SCSI state machine. 2023-12-04 21:41:55 +01:00
Maxim Poliakovski be2721cd67 scsihd: cleanup, fixes and more commands. 2023-12-04 21:41:55 +01:00
Maxim Poliakovski 58281520d3 Implement writes to SCSI Pseudo-DMA register. 2023-12-04 21:41:55 +01:00
Maxim Poliakovski 58dacfa263 amic: improve VIA2_IFR and VIA2_IER emulation. 2023-12-04 21:41:55 +01:00
Maxim Poliakovski ae903082d8 amic: implement SCSI DRQ callback. 2023-12-04 21:41:55 +01:00
joevt 9e54fb88f8 memaccess: Fix unaligned 8 byte read. 2023-12-04 15:31:19 +01:00
dingusdev 5859b4cd66 Delete _config.yml
RIP Page that no one really cared about
2023-12-04 06:57:48 -07:00
Maxim Poliakovski 8a987afefd
Merge pull request #70 from mihaip/upstream-adb-cleanup
Remove obsolete ADB_Bus class
2023-12-04 11:10:11 +01:00
Mihai Parparita 1a859669eb Remove obsolete ADB_Bus class
Superseded by e9d91175c4 and subsequent
changes.
2023-12-03 23:15:25 -08:00
dingusdev 56f2480caa Update cubeb 2023-12-03 14:34:49 -07:00
joevt 0096d063dd pdmonboard: Add 2, 4, 15, 24 bpp support. 2023-12-03 20:38:01 +01:00
joevt 7c3bb41728 videoctrl: Add more convert frame methods.
Also rename 1bpp and 8bpp methods to 1bpp_indexed and 8bpp_indexed.
2023-12-03 20:38:01 +01:00
Maxim Poliakovski 99ae0c3d31 ppcemu.h: add PVR definition for MPC604e. 2023-12-03 20:05:19 +01:00
Mihai Parparita 6582536591 Inline ppc_set_cur_instruction
It's used in the main emulator loop (ppc_exec_inner), and the function
call overhead adds up.

By inlining it, time to boot to the Finder using a 7.1.2 install CD
and a 6100 ROM goes from ~6700ms to ~6400ms (with clang 14 on a
M2 Max)
2023-12-02 15:12:02 -08:00
dingusdev 628d7249b1 Update gitignore for CodeLite project files 2023-12-01 16:11:32 -07:00
dingusdev 7eb2fd23c3 Fixing typos 2023-12-01 14:04:26 -07:00
Maxim Poliakovski 46961711e4 amic: stub for Ch-B serial xmit DMA. 2023-12-01 22:01:48 +01:00
Maxim Poliakovski 819d475181 poweropcodes: fix div emulation.
Clean up power_doz and power_maskir as well.
2023-12-01 20:41:22 +01:00
dingusdev 07030378c8 Non-US keyboard support started
This is quite unfinished, but should get some of the major targets started.
2023-12-01 10:23:41 -07:00
dingusdev 277be165b6
Merge pull request #67 from mihaip/upstream-power
Fix emulation of doz, dozi, and nabs POWER instructions
2023-12-01 07:48:38 -07:00
Mihai Parparita 1b4de3b64e Fix emulation of doz, dozi, and nabs POWER instructions
doz and dozi were storing the result into the wrong register.

nabs was not taking into account two's complement storage of numbers
and was just setting the signed bit.

These two instructions are used in the implementation of text
measurement in native QuickDraw on 7.1.2/the PDM ROM, and the incorrect
values were resulting in nothing being rendered. With the fix text
appears when booting from the 7.1.2 CD.
2023-12-01 01:34:12 -08:00
Maxim Poliakovski a1d8f8aa4e ppctests: fix test cases with SNaN/QNaN operands. 2023-11-30 17:44:46 +01:00
Maxim Poliakovski 8c3dfe94c7 ppcfpopcodes: infinities should set FPCC_FUNAN. 2023-11-30 12:53:10 +01:00
Maxim Poliakovski 0a9107b602 ppcfloattests.csv: remove unrelated CR7 changes. 2023-11-30 12:28:32 +01:00
Maxim Poliakovski 680cab52f3 ppcfpopcodes: fix ppc_fadds. 2023-11-30 12:06:44 +01:00
Maxim Poliakovski 6abb07e61b Add rounding control for the host FPU. 2023-11-30 12:06:44 +01:00
Maxim Poliakovski b59c2be12d ppcfpopcodes: fix fpresult_update(). 2023-11-30 12:06:44 +01:00
Maxim Poliakovski d49d03846f ppcemu: fix and beatify FPSCR enum. 2023-11-30 12:06:44 +01:00
Maxim Poliakovski b51670cb25 ppcfpopcodes: improve mffs, mtfsb0 and mtfsb1. 2023-11-30 12:06:44 +01:00
Maxim Poliakovski 487c6c2c7c ppcfpopcodes: remove dead code. 2023-11-30 12:06:44 +01:00
dingusdev 87b8a8e0a0 Correcting multiply tests 2023-11-28 19:02:48 -07:00
Maxim Poliakovski 47e0c23e64 Fix CR1 updates for floating-point instructions. 2023-11-28 16:31:51 +01:00
dingusdev dd454689e0 Fixes for condition reg move instructions 2023-11-28 07:06:04 -07:00
Maxim Poliakovski 94872b3ebb Store SCSI bus object pointer during registration. 2023-11-24 19:48:07 +01:00
Maxim Poliakovski 9ae863d7c4 sc53c94: add is_dma_cmd member variable. 2023-11-24 19:48:07 +01:00
Maxim Poliakovski 457accf329 scsi: define READ_BUFFER and WRITE_BUFFER commands. 2023-11-24 19:48:07 +01:00
Maxim Poliakovski b9c1ecf65f
Merge pull request #66 from mihaip/upstream-pending-events
Fix keyboard events occasionally being dropped
2023-11-24 19:17:51 +01:00
Mihai Parparita d08b486db0 Fix keyboard events occasionally being dropped
AdbKeyboard would copy the event into its own fields and set the
changed field, so that we could return the event when register was 0.
However, if a subsequent event was received before ADB polling, the
previous event would be overwritten and lost.

Fix this by maintaining a queue of events, so that we can return
everything since the last poll.
2023-11-24 10:08:16 -08:00
Maxim Poliakovski d37d83c5b6
Merge pull request #64 from mihaip/upstream-a-key
Fix "a" key always being as as a keyup
2023-11-24 11:35:18 +01:00
Mihai Parparita d45bba924d Fix "a" key always being as as a keyup
We were using an empty value on the second byte of the ADB keyboard
register 0, but that maps to the "a" key. This manifested itself
as the Key Caps DA never showing the "a" key as being down.

Switch to a non-existent key for the second byte.
2023-11-23 22:39:06 -08:00
dingusdev 69f7fbe703
Update README.md
More accurately reflects DPPC's current status
2023-11-23 20:59:49 -07:00
dingusdev 4753ba5361 Continued clean-up 2023-11-23 16:56:58 -07:00
Maxim Poliakovski ada68ffc71 machinegossamer: add primary IDE hard disk drive. 2023-11-22 17:35:14 +01:00
Maxim Poliakovski 446b1b8d99 atahd: various improvements implementing basic commands. 2023-11-22 17:35:14 +01:00
Maxim Poliakovski 27ff05607c atadefs: bit definitions for the device/head register. 2023-11-22 17:35:14 +01:00
Maxim Poliakovski f2558cd379 atabasedevice: data transfers to host. 2023-11-22 17:35:14 +01:00
Maxim Poliakovski 4f76a4ead2 Move signal_data_ready() to atabasedevice. 2023-11-22 17:35:14 +01:00
dingusdev 7835aec034 Further CPU cleanup 2023-11-21 08:06:50 -07:00
dingusdev f4f035682c Fixed cfloat include 2023-11-19 20:34:40 -07:00
dingusdev d1f9b5631a Added missing include for cfloat 2023-11-19 20:07:00 -07:00
dingusdev d92ae6136a CPU code clean-up in progress
Happened to fix one case in the process.
2023-11-19 17:56:30 -07:00
Maxim Poliakovski be633d3872 soundserver_cubeb: remove soundio code. 2023-11-15 19:01:54 +01:00
Maxim Poliakovski 453930ff75 awacs: pausing sound output DMA channel. 2023-11-15 19:01:54 +01:00
Maxim Poliakovski b7341d0ab8 amic: implement sound out DMA IRQ. 2023-11-15 19:01:54 +01:00
Maxim Poliakovski f814822ca3 timermanager: support for timers that expire immediately. 2023-11-15 19:01:54 +01:00
Maxim Poliakovski c3f2c9e84c awac-pdm: improve read_stat(). 2023-11-15 19:01:54 +01:00
dingusdev 074a760b6a FP compare fixes
This is the start of several fixes for the floating point emulation.
2023-11-13 07:30:31 -07:00
dingusdev 931060db7c
Merge pull request #61 from mihaip/upstream-imgfile
Move disk image reading to be behind an ImgFile class
2023-11-10 06:29:33 -07:00
Maxim Poliakovski 417f988ca0
Merge pull request #57 from mihaip/upstreaming2
Remap Cocoa/macOS menu item key modifiers
2023-11-10 08:16:54 +01:00
Mihai Parparita d4c9db7fcf Move disk image reading to be behind an ImgFile class
Allows different implementations for different platforms (the JS
build relies on browser APIs to stream disk images over the network).

Setting aside the JS build, this also reduces some code duplication.
2023-11-09 21:49:28 -08:00
Maxim Poliakovski 04956c19d5
Fix tiny tipo in adbdevice.h 2023-11-09 11:51:33 +01:00
Maxim Poliakovski b350beafa8
Merge pull request #56 from mihaip/upstreaming
Implement the ADB keyboard
2023-11-09 11:47:50 +01:00
Maxim Poliakovski 37cca00e13
Merge pull request #60 from mihaip/upstream-postinit
machinebase: fully initialize devices registered by other devices
2023-11-08 13:55:09 +01:00
Mihai Parparita e5c50640e3 machinebase: fully initialize devices registered by other devices
postinit_devices() may cause additional devices to be registered
(e.g. PCI hosts will register their cards). We were not calling
device_postinit on those devices, because the iterator over the
device map was set up at the start of the loop.

Keep looping until we've actually initialized all devices in the map.
2023-11-08 00:11:41 -08:00
Maxim Poliakovski 351ac78e4b
Merge pull request #59 from mihaip/upstream-amic-dma
amic: don't reset cur_buf_pos if we've drained the DMA buffer
2023-11-08 08:20:50 +01:00
Mihai Parparita e011d86742 amic: don't reset cur_buf_pos if we've drained the DMA buffer
Otherwise if pull_data is called again, it will think that it still
has data available in the buffer (rem_len will be non-zero) and
random data at the buffer location will be returned.

This manifested itself as noise being played back in the JS
implementation of the SoundServer. The cubeb implementation was not
affected because it stops polling once it's told it has no more
data in the buffer. Both approaches are valid (the JS version pads
data with silence), and the DMA buffer should support both.
2023-11-06 22:33:00 -08:00
dingusdev f019902f41
Merge pull request #58 from mihaip/iwyu
Clean up #includes
2023-11-03 06:12:00 -07:00
Mihai Parparita 35c86ad6bf Clean up #includes
Result of running IWYU (https://include-what-you-use.org/) and
applying most of the suggestions about unncessary includes and
forward declarations.

Was motivated by observing that <thread> was being included in
ppcopcodes.cpp even though it was unused (found while researching
the use of threads), but seems generally good to help with build
times and correctness.
2023-11-03 00:33:47 -07:00
dingusdev 6ffc2b2f10 Optimize string word instructions
Partially unrolled the loop. Boots 7.1.2 Disk Tools slightly faster.
2023-10-29 17:23:31 -07:00
Mihai Parparita ec155bf7ba Remap Cocoa/macOS menu item key modifiers
As part of adding ADB keyboard support (#56), we're now running into
conflicts between the guest and host OS keyboard shortcuts when running
on macOS hosts.

SDL2 unconditionally adds some menu items to the "Window" menu, and
there are built-in ones too. As a workaround, we now iterate over all
menu items are swap out command for control, since the the latter is
generally unused in classic Mac OS.
2023-10-29 12:16:01 -07:00
dingusdev 8cad7ee509
Merge pull request #55 from mihaip/upstreaming
Add Emscripten build target
2023-10-27 12:24:56 -07:00
Mihai Parparita 1f7edfdb3b Make Emscripten build not depend on SDL2 or cubeb
While Emscripten has an SDL compabtility layer, it assumes that the
code is executing in the main browser process (and thus has access to
them DOM). The Infinite Mac project runs emulators in a worker thread
(for better performance) and has a custom API for the display, sound,
input, etc. Similarly, it does not need the cross-platform sound support
from cubeb, there there is a sound API as well.

This commit makes SDL (*_sdl.cpp) and cubeb-based (*_cubeb.cpp) code be
skipped when targeting Emscripten, and instead *_js.cpp files are used
instead (this is the cross-platform convention used by Chromium[^1], and
could be extended for other targets).

For hostevents.cpp and soundserver.cpp the entire file was replaced,
whereas for videoctrl.cpp there was enough shared logic that it was
kept, and the platform-specific bits were moved behind a Display class
that can have per-platform implementations. For cases where we need
additional private fields in the platform-specific classes, we use
a PIMPL pattern.

The *_js.cpp files with implementations are not included in this
commit, since they are closely tied to the Infinite Mac project, and
will live in its fork of DingusPPC.

[^1]: https://www.chromium.org/developers/design-documents/conventions-and-patterns-for-multi-platform-development/
2023-10-25 22:25:53 -07:00
dingusdev 55a354406a
Merge pull request #54 from mihaip/upstreaming
Upstream small changes from the Infinite Mac fork
2023-10-22 06:55:20 -07:00
Mihai Parparita a9fd2453a4 Make assertion messages more visible when we drop into the debugger.
Log output may be sent to a file, ensure that the reason why we're
aborting execution is visible.
2023-10-19 07:50:58 -07:00
Mihai Parparita ea0eae467d Add assertion for valid SCSI IDs when updating control lines
Would have flagged the out-of-bounds write fixed by
dingusdev/dingusppc@36cb84eaaa sooner.
2023-10-19 07:49:23 -07:00
Mihai Parparita 5a5ae9fd16 Exit with a non-0 exit code if there are errors
More correct behavior (and makes the Infinite Mac wrapper not close
the emulated Mac, which it does when it thinks there was a graceful
shutdown).
2023-10-18 23:14:15 -07:00
Mihai Parparita 838ccdd7b4 Fix for building on with GCC on ARM
The x86intrin.h intrinsics header is not available in that case.
2023-10-18 23:13:05 -07:00
Maxim Poliakovski cfb1999caf soundserver: fix double freeing. 2023-10-18 16:18:54 +02:00
Maxim Poliakovski 36cb84eaaa mesh: fix write-out-of-bounds to BusStatus1. 2023-10-18 11:21:38 +02:00
Mihai Parparita 73272b28dd Implement the ADB keyboard
Besides generating KeyboardEvents in the SDL event handler and
returning the key state in the register 0 reads of the AdbKeyboard
device, we also needed to generalize the ADB bus polling a bit. We now
check all devices that have the service request bit set, instead of
hardcoding the mouse.

The SDL key event -> ADB raw key code mapping is based on BasiliskII/
SheepShaver's, but cleaned up a bit.
2023-10-11 23:43:20 -07:00
Maxim Poliakovski aa5ef742f6 atirage: return meaningful value for GUI_STAT:GUI_FIFO. 2023-10-10 02:31:09 +02:00
Maxim Poliakovski 47d2e235a3 atirage: prevent big-endian accesses outside VRAM.
The simplest solution is to cut the aperture size by the amount
of video RAM installed. This way, accesses to the big-endian
aperture located above the installed VRAM will be catched and
reported by the MMU.
2023-10-03 14:18:12 +02:00
Maxim Poliakovski 576912dd55 displayid: prevent reading past EDID data. 2023-10-03 00:42:03 +02:00
joevt 5b366e592c Fix spelling. 2023-10-02 15:06:51 +02:00
joevt 170a9d78e7 Fix comment. 2023-10-02 15:06:06 +02:00
Maxim Poliakovski 63b10175bf mesh: stubs for BusFree and EnaReselect commands. 2023-10-02 15:01:27 +02:00
Maxim Poliakovski a5fb124e69 pdmonboard: switch to mmu_map_dma_mem. 2023-10-02 15:00:12 +02:00
Maxim Poliakovski 72d45fb0de amic: switch DMA code to mmu_map_dma_mem. 2023-10-02 14:48:25 +02:00
Maxim Poliakovski f754f63f8f dbdma: fix LOAD_QUAD and STORE_QUAD to work with MMIO. 2023-10-02 14:46:52 +02:00
Maxim Poliakovski 8cf290c034 ppcmmu: add mmu_map_dma_mem method. 2023-10-02 02:20:42 +02:00
Maxim Poliakovski b408a02ef9 dbdma: implement LOAD_QUAD, STORE_QUAD and NOP. 2023-09-30 17:09:45 +02:00
joevt 67146028bf ppcmmu: Add 64-bit accesses to I/O.
Also add an exception for unaligned 64 bit. 64 bit accesses require dword alignment.
2023-09-30 00:29:01 +02:00
joevt acdb14a10a Recalculate execution block after RFI.
While booting Mac OS X 10.2 installer CD, a return from RFI didn't change the instruction address virtual memory page but did change the physical memory page so we must always recalculate the physical address after RFI.
Perhaps there are other cases where this may be required?
2023-09-26 00:13:11 +02:00
joevt dcd4384d46 Fix eb_end calculation.
- Subtract one so that it can't overflow to zero.
- Use page_start as the base so mask operation is not required.
- Recalculate it only when the page changes.
2023-09-26 00:04:07 +02:00
joevt 8348370142 Add separate flags for instruction and data TLBs.
The same flag was being used for flushing both instruction and data TLBs so sometimes a flush for one TLB list would not occur if the flag was cleared when flushing the other TLB list.
2023-09-25 23:42:32 +02:00
joevt 6b3cdad877 ppcmmu: Fix BAT update.
Need to schedule flush of both BAT and PAT type TLBs because BAT takes precedence over PAT which means updating a BAT can invalidate a PAT.
2023-09-25 23:27:00 +02:00
joevt 6b40caf63a ppcmmu: fix setting of LRU bits. 2023-09-25 23:17:57 +02:00
Maxim Poliakovski 99f596ea19 cdromdrive: clean-up and extend READ_TOC command. 2023-09-25 23:17:57 +02:00
joevt 637844269f atapicdrom: Implement sector areas for Read CD.
The disk cache is unchanged. data_ptr continues to be only used for the user data sector area for each block. The other sector areas (synch, header, etc.) are filled in while reading.

has_data and get_data exist as a way to bypass data_ptr for parts of the transfer outside the user data sector area of each block. The default behaviour is defined in atabasedevice and is overridden by atapicdrom for the Read CD command. atapicdrom has a flag doing_sector_areas to control the behavior of the get_data method. When the flag is true, the sector_areas, current_block, and current_block_byte are used for selecting the correct data from one of the sector areas. The Read CD command initializes those variables. xfer_cnt remains the total number of bytes to be transferred and is now not necessarily the same as the number of disk image blocks read into the disk cache.

lba_to_msf is used to fill in the header. The values was not verified using a real CD.

Mac OS X just cares about the Mode in the header. For now, only the synch and header and user data areas are filled in. The other areas read as all zeros.
2023-09-25 12:22:17 +02:00
joevt ec5bf8e985 atapicdrom: Implement READ(6) and READ(10). 2023-09-25 12:22:17 +02:00
joevt 4bbc5ab0af CD-ROM: Fix interpretation of session number.
In Read TOC format 2, Mac OS X passes zero for Session Number. I believe Read TOC is supposed to return the first session starting from that number so it should return info for Session 1 as it would if Mac OS X passed 1 for the Session Number.
2023-09-25 12:22:17 +02:00
Maxim Poliakovski 6cfde29f00 heathrow: implement native interrupt mode. 2023-09-25 12:22:17 +02:00
Maxim Poliakovski c115a887d8 heathrow: fix processing of emulated interrupts. 2023-09-25 02:14:29 +02:00
Maxim Poliakovski 4cdb81e822 mesh: fix Arbitrate command for OS X. 2023-09-24 22:36:44 +02:00
Maxim Poliakovski f4f7edcc28 heathrow: add Ethernet DBDMA channels. 2023-09-22 00:11:19 +02:00
Maxim Poliakovski ae97d7bcc7 Improve zdoc/bmac documentation. 2023-09-22 00:10:19 +02:00
Maxim Poliakovski 5f48a3ab5b bigmac: support more registers. 2023-09-22 00:09:17 +02:00
Maxim Poliakovski 9db3076a48 dbdma: support INT_SELECT & WAIT_SELECT registers. 2023-09-20 00:45:39 +02:00
Maxim Poliakovski 6eb6a5892d bigmac: fix/improve PHY interface. 2023-09-20 00:44:08 +02:00
Maxim Poliakovski 0ebcd15a3d heathrow: connect DMA channel for MESH. 2023-09-19 14:19:15 +02:00
Maxim Poliakovski 04acf120d6 dbdma: support optinal CommandPtrHi register.
It's written with zero in OS X. This dummy implementation
is meant to reduce log warnings.
2023-09-19 14:16:13 +02:00
Maxim Poliakovski 278799795c Disable decrementer exceptions for MPC601. 2023-09-18 21:20:59 +02:00
Maxim Poliakovski c47cbb354d Add is_601 flag for selecting MPC601 specific behavior. 2023-09-18 21:20:59 +02:00
Maxim Poliakovski 8ff2125312 Revert "Minor checks for Data Cache opcodes and LMW"
This reverts commit fd6327ab62.
2023-09-18 21:20:59 +02:00
Maxim Poliakovski a69763c6de dbdma: noop incomplete LOAD_QUAD & STORE_QUAD. 2023-09-18 20:20:25 +02:00
dingusdev 5e32b599d6 Merge branch 'master' of https://github.com/dingusdev/dingusppc 2023-09-04 07:22:27 -07:00
dingusdev fd6327ab62 Minor checks for Data Cache opcodes and LMW 2023-09-04 07:21:00 -07:00
Mihai Parparita 732977db27 Minimal changes to get an Emscripten build
Disable stack traces in loguru (excinfo.h is not available in Emscripten)
and set Emscripten linker flags.
2023-08-30 23:12:43 -07:00
Maxim Poliakovski 45528bfc6d ppcmmu: fix flushing of the secondary ITLB. 2023-08-22 23:36:48 +02:00
Maxim Poliakovski c690049246
Merge pull request #47 from joevt/ofnvram-branch
ofnvram changes
2023-08-22 08:24:57 +02:00
Maxim Poliakovski 932f2bbceb ppcopcodes: fix stwcx. emulation. 2023-08-21 04:50:02 +02:00
dingusdev a7ef177164 Preliminary DBDMA expansion 2023-08-13 16:38:15 -07:00
joevt 300965ab10 Decrementer exception changes. 2023-08-10 00:46:04 +02:00
Maxim Poliakovski d2e7c9a5df ppcexceptions: fix next address for decrementer exceptions. 2023-08-09 12:53:48 +02:00
Maxim Poliakovski ee9c692115 mesh: implement more register reads. 2023-08-09 01:40:09 +02:00
Maxim Poliakovski de1f0c8a9b ppc_mmu: rename reg_desc to rgn_desc to improve readability. 2023-08-07 13:56:49 +02:00
Maxim Poliakovski 52a64168d7 Clean up previous merge. 2023-08-07 13:45:26 +02:00
Maxim Poliakovski b571ff8412 Revert "ppcmmu: Add 64-bit accesses to I/O"
This reverts commit 16123dea45.
2023-08-07 13:06:11 +02:00
Maxim Poliakovski 762319055c
Merge pull request #50 from joevt/mmu-changes-branch
mmu changes
2023-08-07 12:58:46 +02:00
Maxim Poliakovski cd9ccb66ed adbkeyboard: fix compiler warning. 2023-08-07 12:51:09 +02:00
joevt ac64f9e30d ppcmmu: Fix mmio read/write offset calculation.
For TLBs referencing an mmio region, calculate an offset that will translate a guest virtual address to an offset in the mmio region.
2023-08-04 20:16:13 -07:00
joevt 16123dea45 ppcmmu: Add 64-bit accesses to I/O
Also add an exception for unaligned 64 bit. 64 bit accesses require dword alignment.
2023-08-04 20:16:13 -07:00
joevt 814260f0b6 ppcmmu: Reduce unmapped physical memory logging.
Don't log consecutive accesses to unmapped physical memory addresses. This saves a couple hundred thousand lines in the log in some cases.
This is only a partial fix. Any access that isn't logged should be queued and output if a log message is output that is not this log message or after a time period.
2023-08-04 20:16:13 -07:00
joevt 439509b408 Fix New World NVRAM limits calculations.
It was possible to corrupt New World nvram using dingusppc setenv command.

- setenv must call get_config_vars to set data_length so that it can calculate free space.
- data_length represents the number of bytes taken by nvram variables including the terminating null for the value (name, '=', value, '\0'). Previously, it did not include the terminating null.
- The list of variables and values ends at a '\0' or at pos == 4096 bytes. Previously, data_length wouldn't get set if pos >= 4096.
- Allow setenv to create new nvram variables.
- Since data_length now represents the total number of used bytes, free_space now represents the actual free space, so use > free_space (instead of >= free_space) to determine insufficient space.
- While parsing nvram variable name, do not read beyond 4096 bytes.
- Use a different error message for each problem that can occur while parsing nvram variable name.
2023-08-04 20:11:59 -07:00
joevt 07f57a1e9b Remove extra semi-colons. 2023-08-04 20:11:59 -07:00
joevt 0c9ddaccf7 Fix dppc debugger printenv of multiline variables.
If a nvram variable has CRLF or CR, replace them with LF so each line appears on a new line in the console output.
Also, add indent to each line so that each line appears only in the value column and not in the name column.
2023-08-04 20:11:59 -07:00
dingusdev b23bb04dac Start fix for SCSI Hard Drive 2023-08-02 07:53:19 -07:00
dingusdev a7601c36bd Started work on keyboard input 2023-08-01 22:43:11 -07:00
Maxim Poliakovski 1a883ba73e viacuda: implement basic autopolling. 2023-08-02 00:07:17 +02:00
Maxim Poliakovski 2e50b364c4 adbmouse: emulate single button mouse. 2023-08-01 23:58:29 +02:00
Maxim Poliakovski b4b41a47b2 hostevents: dispatch mouse button events. 2023-08-01 23:57:16 +02:00
Maxim Poliakovski 5b90a3e21d AMIC: rework and improve interrupts. 2023-08-01 17:42:52 +02:00
Maxim Poliakovski ca83f7e8ef pdmonboard: switch to new video controller API. 2023-08-01 17:41:02 +02:00
Maxim Poliakovski 337a9d6dd0 videoctrl: generate VBL interrupts. 2023-08-01 17:37:50 +02:00
Maxim Poliakovski ddf139a659 Extend TimerManager API a bit. 2023-08-01 17:36:29 +02:00
Maxim Poliakovski 6b8fe50f50 hostevents: add prost-processing signal. 2023-08-01 17:21:17 +02:00
Maxim Poliakovski 233ab778b6 adbmouse: support for movement data in R0. 2023-08-01 17:18:46 +02:00
Maxim Poliakovski 4872af1053 control: use new refresh task control. 2023-07-31 03:53:13 +02:00
Maxim Poliakovski 0ca1ebf724 atimach64gx: use new refresh task control. 2023-07-31 03:52:17 +02:00
Maxim Poliakovski 632479b1ba atirage: use new refresh task control. 2023-07-31 03:46:16 +02:00
Maxim Poliakovski 9b81891467 pdmonboard: use new refresh task control. 2023-07-31 03:36:17 +02:00
Maxim Poliakovski 7b2e1d90e6 Move refresh task control to VideoCtrlBase. 2023-07-31 03:34:02 +02:00
Maxim Poliakovski 6fa6b4d4dc Rework the EventManager to use CoreSignal. 2023-07-27 02:40:32 +02:00
Maxim Poliakovski 6efe6f13a9 Add new CoreSignal class. 2023-07-27 02:38:21 +02:00
Maxim Poliakovski c254749493 viacuda: connect to new AdbBus device. 2023-07-26 04:50:05 +02:00
Maxim Poliakovski fd9f8c90a5 Basic ADB mouse device emulation.
For now, it solely handles the basic ADB commands.
2023-07-26 04:40:20 +02:00
Maxim Poliakovski 0a0761c7e0 AdbDevice - the base class for ADB devices. 2023-07-26 04:35:07 +02:00
Maxim Poliakovski e9d91175c4 Add AdbBus class and device. 2023-07-26 04:29:08 +02:00
Maxim Poliakovski 0c0166b565 viacuda: fix packet response protocol.
Cuda needs to negate TREQ before sending the last byte of a
fixed-length response to avoid transfering an extraneous byte.
2023-07-26 04:22:08 +02:00
Maxim Poliakovski 7bb7ff9f0f heathrow: human-readable DBDMA channel names. 2023-07-24 15:20:52 +02:00
Maxim Poliakovski c2cd076662 machinebase: fix log statement warning. 2023-07-23 16:50:14 +02:00
Maxim Poliakovski a9f73e7384 hwcomponent: fix size of the component type enum. 2023-07-23 16:32:00 +02:00
Maxim Poliakovski 14c7d18bdb Revert "Prevents crashing for Big Mac"
This reverts commit 5787d49e9b.
2023-07-23 16:28:31 +02:00
dingusdev 5787d49e9b Prevents crashing for Big Mac
For some reason, on Windows, whether it be VS2022 or Clang, it will crash when trying to write to a Big Mac register if you don't specify the exact name of the component within Heathrow.

This commit fixes that.
2023-07-22 15:15:33 -07:00
Maxim Poliakovski 25150268cd ppcexceptions: fix ISI exception target address. 2023-07-10 14:06:20 +02:00
Maxim Poliakovski f7a1412ec7 atapibasedevice: fix task file initialization on reset. 2023-07-09 02:10:48 +02:00
Maxim Poliakovski a424d48447 bigmac: implement software reset registers. 2023-07-08 23:30:44 +02:00
Maxim Poliakovski e76e3afa87 Improve zdoc/bmac documentation. 2023-07-08 21:33:10 +02:00
Maxim Poliakovski 482fe3eb80 bigmac: MAC serial EEPROM emulation. 2023-07-08 21:32:26 +02:00
Maxim Poliakovski 4de2afc0c5 bigmac: fix Gossamer PHY stuff. 2023-07-08 14:35:23 +02:00
Maxim Poliakovski 742003b6f3 Basic BigMac Ethernet controller emulation.
Emulates MII and some PHY configuration registers.
2023-07-08 01:27:01 +02:00
Maxim Poliakovski 10b8366219 hwcomponent: add Ethernet MAC type. 2023-07-08 01:23:18 +02:00
206 changed files with 23600 additions and 12743 deletions

View File

@ -1,43 +1,265 @@
name: CMake
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: install_dependencies
run: |
sudo apt-get update -y -qq
sudo apt-get install libsdl2-dev
- name: init_submodules
run: git submodule update --init --recursive
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDPPC_68K_DEBUGGER=ON
- name: Build
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: DingusPPC-LINUX
path: ${{github.workspace}}/build/bin
- name: Test
working-directory: ${{github.workspace}}/build
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}
build-windows:
runs-on: windows-latest
strategy:
matrix:
cache-name: [msys64-cache]
env:
MSYSTEM: MINGW64
FF_SCRIPT_SECTIONS: '0'
CONFIGURE_ARGS: '--target-list=x86_64-softmmu --without-default-devices -Ddebug=false -Doptimization=0'
TEST_ARGS: '--no-suite qtest'
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up MSYS2
run: |
Write-Output "Acquiring msys2.exe installer at $(Get-Date -Format u)"
If ( !(Test-Path -Path msys64\var\cache ) ) {
mkdir msys64\var\cache
}
Invoke-WebRequest "https://repo.msys2.org/distrib/msys2-x86_64-latest.sfx.exe.sig" -outfile "msys2.exe.sig"
if ( Test-Path -Path msys64\var\cache\msys2.exe.sig ) {
Write-Output "Cached installer sig" ;
if ( ((Get-FileHash msys2.exe.sig).Hash -ne (Get-FileHash msys64\var\cache\msys2.exe.sig).Hash) ) {
Write-Output "Mis-matched installer sig, new installer download required" ;
Remove-Item -Path msys64\var\cache\msys2.exe.sig ;
if ( Test-Path -Path msys64\var\cache\msys2.exe ) {
Remove-Item -Path msys64\var\cache\msys2.exe
}
} else {
Write-Output "Matched installer sig, cached installer still valid"
}
} else {
Write-Output "No cached installer sig, new installer download required" ;
if ( Test-Path -Path msys64\var\cache\msys2.exe ) {
Remove-Item -Path msys64\var\cache\msys2.exe
}
}
if ( !(Test-Path -Path msys64\var\cache\msys2.exe ) ) {
Write-Output "Fetching latest installer" ;
Invoke-WebRequest "https://repo.msys2.org/distrib/msys2-x86_64-latest.sfx.exe" -outfile "msys64\var\cache\msys2.exe" ;
Copy-Item -Path msys2.exe.sig -Destination msys64\var\cache\msys2.exe.sig
} else {
Write-Output "Using cached installer"
}
Write-Output "Invoking msys2.exe installer at $(Get-Date -Format u)"
msys64\var\cache\msys2.exe -y
((Get-Content -path .\msys64\etc\post-install\07-pacman-key.post -Raw) -replace '--refresh-keys', '--version') | Set-Content -Path .\msys64\etc\post-install\07-pacman-key.post
.\msys64\usr\bin\bash -lc "sed -i 's/^CheckSpace/#CheckSpace/g' /etc/pacman.conf"
.\msys64\usr\bin\bash -lc 'pacman --noconfirm -Syuu' # Core update
.\msys64\usr\bin\bash -lc 'pacman --noconfirm -Syuu' # Normal update
taskkill /F /FI "MODULES eq msys-2.0.dll"
- name: Install dependencies
run: |
Write-Output "Installing mingw packages at $(Get-Date -Format u)"
.\msys64\usr\bin\bash -lc 'pacman -Sy --noconfirm --needed \
bison \
diffutils \
flex \
tar \
doxygen \
cmake \
wget \
git \
grep \
make \
rsync \
ninja \
glib2-devel \
patch \
sed \
mingw-w64-x86_64-cmake \
mingw-w64-x86_64-binutils \
mingw-w64-x86_64-doxygen \
mingw-w64-x86_64-capstone \
mingw-w64-x86_64-ccache \
mingw-w64-x86_64-curl \
mingw-w64-x86_64-cyrus-sasl \
mingw-w64-x86_64-dtc \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-glib2 \
mingw-w64-x86_64-gnutls \
mingw-w64-x86_64-gtk3 \
mingw-w64-x86_64-libgcrypt \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-libnfs \
mingw-w64-x86_64-libpng \
mingw-w64-x86_64-libssh \
mingw-w64-x86_64-libtasn1 \
mingw-w64-x86_64-libusb \
mingw-w64-x86_64-lzo2 \
mingw-w64-x86_64-libslirp \
mingw-w64-x86_64-nettle \
mingw-w64-x86_64-clang \
mingw-w64-x86_64-ninja \
mingw-w64-x86_64-pixman \
mingw-w64-x86_64-pkgconf \
mingw-w64-x86_64-python \
mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-SDL2_image \
mingw-w64-x86_64-snappy \
mingw-w64-x86_64-spice \
mingw-w64-x86_64-usbredir \
mingw-w64-x86_64-zstd \
mingw-w64-x86_64-make'
- name: init_submodules
run: git submodule update --init --recursive
- name: Build
run: |
Write-Output "Running build at $(Get-Date -Format u)"
$env:CHERE_INVOKING = 'yes' # Preserve the current working directory
$env:MSYS = 'winsymlinks:native' # Enable native Windows symlink
$env:CCACHE_BASEDIR = "$env:CI_PROJECT_DIR"
$env:CCACHE_DIR = "$env:CCACHE_BASEDIR/ccache"
$env:CCACHE_MAXSIZE = "500M"
$env:CCACHE_DEPEND = 1 # cache misses are too expensive with preprocessor mode
$env:CC = "ccache gcc"
mkdir build
cd build
D:\a\dingusppc\dingusppc\msys64\usr\bin\bash -lc "ccache --zero-stats"
D:\a\dingusppc\dingusppc\msys64\usr\bin\bash -lc "cmake -DCMAKE_BUILD_TYPE=Release -DPPC_BUILD_PPC_TESTS=True .. && ninja"
D:\a\dingusppc\dingusppc\msys64\usr\bin\bash -lc "ccache --show-stats"
Write-Output "Finished build at $(Get-Date -Format u)"
vsbuild-CLANG:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Setup msbuild
if: inputs.configuration != 'CMake'
uses: microsoft/setup-msbuild@v1
- name: Setup VCPKG
run: |
vcpkg integrate install
vcpkg install pthread:x64-windows
vcpkg install pthreads:x64-windows
vcpkg install pthread-stubs:x64-windows
vcpkg install pthreadpool:x64-windows
vcpkg install
- name: init_submodules
run: git submodule update --init --recursive
- name: Build
shell: cmd
run: |
mkdir build
cd build
call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DPPC_BUILD_PPC_TESTS=True -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows ..
nmake
- name: Upload artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: DingusPPC-CLANG
path: ${{github.workspace}}/build/bin
vsbuild-MSVC:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Setup msbuild
if: inputs.configuration != 'CMake'
uses: microsoft/setup-msbuild@v1
- name: Setup VCPKG
run: |
vcpkg integrate install
vcpkg install pthread:x64-windows
vcpkg install pthreads:x64-windows
vcpkg install pthread-stubs:x64-windows
vcpkg install pthreadpool:x64-windows
vcpkg install
- name: init_submodules
run: git submodule update --init --recursive
- name: Build
shell: cmd
run: |
mkdir build
cd build
call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DPPC_BUILD_PPC_TESTS=True -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows ..
nmake
- name: Upload artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: DingusPPC-MSVC
path: ${{github.workspace}}/build/bin
vsbuild-MSVC-MSBUILD:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Setup msbuild
if: inputs.configuration != 'CMake'
uses: microsoft/setup-msbuild@v1
- name: Setup VCPKG
run: |
vcpkg integrate install
vcpkg install pthread:x64-windows
vcpkg install pthreads:x64-windows
vcpkg install pthread-stubs:x64-windows
vcpkg install pthreadpool:x64-windows
vcpkg install
- name: init_submodules
run: git submodule update --init --recursive
- name: Build
shell: cmd
run: |
mkdir build
cd build
call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cmake -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=Release -DPPC_BUILD_PPC_TESTS=True -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows ..
msbuild dingusppc.sln
- name: Upload artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: DingusPPC-MSBUILD
path: ${{github.workspace}}/build/bin

7
.gitignore vendored
View File

@ -16,6 +16,13 @@ CMakeSettings.json
*.vcxproj
*.vcxproj.filters
# Ignore CodeLite configuration files
*.workspace
*.workspace.*
compile_commands.json
*.session
*.tags
# Ignore generated executables
dingusppc
dingusppc.exe

View File

@ -1,20 +1,23 @@
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.14)
project(dingusppc)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(PlatformGlob)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib)
if (NOT WIN32)
if (NOT WIN32 AND NOT EMSCRIPTEN)
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
if (UNIX AND NOT APPLE)
find_package (Threads)
endif()
else() # Windows build relies on vcpkg
elseif (WIN32) # Windows build relies on vcpkg
# pick up system wide vcpkg if exists
if (DEFINED ENV{VCPKG_ROOT} AND EXISTS $ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
message(STATUS "Using system vcpkg at $ENV{VCPKG_ROOT}")
@ -40,6 +43,12 @@ else() # Windows build relies on vcpkg
add_compile_definitions(SDL_MAIN_HANDLED)
endif()
if (EMSCRIPTEN)
message(STATUS "Targeting Emscripten")
# loguru tries to include excinfo.h, which is not available under Emscripten.
add_compile_definitions(LOGURU_STACKTRACES=0)
endif()
option(DPPC_BUILD_PPC_TESTS "Build PowerPC tests" OFF)
option(DPPC_BUILD_BENCHMARKS "Build benchmarking programs" OFF)
@ -83,11 +92,12 @@ add_subdirectory("${PROJECT_SOURCE_DIR}/machines/")
add_subdirectory("${PROJECT_SOURCE_DIR}/utils/")
add_subdirectory("${PROJECT_SOURCE_DIR}/thirdparty/loguru/")
set(BUILD_TESTS OFF CACHE BOOL "Build Cubeb tests")
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries")
set(BUILD_TOOLS OFF CACHE BOOL "Build Cubeb tools")
add_subdirectory(thirdparty/cubeb EXCLUDE_FROM_ALL)
if (NOT EMSCRIPTEN)
set(BUILD_TESTS OFF CACHE BOOL "Build Cubeb tests")
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries")
set(BUILD_TOOLS OFF CACHE BOOL "Build Cubeb tools")
add_subdirectory(thirdparty/cubeb EXCLUDE_FROM_ALL)
endif()
set(CLI11_ROOT ${PROJECT_SOURCE_DIR}/thirdparty/CLI11)
@ -101,10 +111,15 @@ include_directories("${PROJECT_SOURCE_DIR}"
"${PROJECT_SOURCE_DIR}/thirdparty/CLI11/"
"${PROJECT_SOURCE_DIR}/thirdparty/cubeb/include")
file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/*.cpp"
"${PROJECT_SOURCE_DIR}/*.c"
"${PROJECT_SOURCE_DIR}/*.hpp"
"${PROJECT_SOURCE_DIR}/*.h")
platform_glob(SOURCES "${PROJECT_SOURCE_DIR}/*.cpp"
"${PROJECT_SOURCE_DIR}/*.c"
"${PROJECT_SOURCE_DIR}/*.hpp"
"${PROJECT_SOURCE_DIR}/*.h")
if (APPLE)
platform_glob(APPLE_SOURCES "${PROJECT_SOURCE_DIR}/*.m")
list(APPEND SOURCES ${APPLE_SOURCES})
endif()
file(GLOB TEST_SOURCES "${PROJECT_SOURCE_DIR}/cpu/ppc/test/*.cpp")
@ -118,11 +133,27 @@ add_executable(dingusppc ${SOURCES} $<TARGET_OBJECTS:core>
if (WIN32)
target_link_libraries(dingusppc PRIVATE SDL2::SDL2 SDL2::SDL2main cubeb)
elseif (EMSCRIPTEN)
target_link_libraries(dingusppc PRIVATE
${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}
"-gsource-map"
# 256 MB max for emulated Mac RAM, plus 32 MB of emulator overhead
"-s INITIAL_MEMORY=301989888"
"-s MODULARIZE"
"-s EXPORT_ES6"
"-s EXPORT_NAME=emulator"
"-s 'EXTRA_EXPORTED_RUNTIME_METHODS=[\"FS\"]'")
else()
target_link_libraries(dingusppc PRIVATE SDL2::SDL2 SDL2::SDL2main cubeb
${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
endif()
if (APPLE)
find_library(COCOA_LIBRARY Cocoa)
target_link_libraries(dingusppc PRIVATE ${COCOA_LIBRARY})
endif()
if (DPPC_68K_DEBUGGER)
target_link_libraries(dingusppc PRIVATE capstone)
endif()

32
CREDITS.md Normal file
View File

@ -0,0 +1,32 @@
# DingusPPC
## Developers
- divingkatae
- maximumspatium
- joevt
- mihaip
## Building
- Waqar144
- webspacecreations
- leap0x7b
- sdkmap
## Testing
- LagLifeYT
## Thanks
- 68kmla
- AppleFritter
- Archive.org
- Bitsavers
- Emaculation
- GitHub
- PenguinPPC
- The developers of other PowerPC Mac emulators, past and present
- All those preserving the software of 68k and PowerPC Macs

View File

@ -6,9 +6,9 @@ Be warned the program is highly unfinished and could use a lot of testing. Any f
## Philosophy of Use
While many other PowerPC emus exist (PearPC, Sheepshaver), none of them currently attempt emulation of PPC Macs accurately (except for QEMU).
While many other PowerPC emus exist (PearPC, Sheepshaver), none of them currently attempt emulation of PowerPC Macs accurately (except for QEMU).
This program aims to not only improve upon what Sheepshaver, PearPC, and other PowerPC mac emulators have done, but also to provide a better debugging environment. This currently is designed to work best with PowerPC Old World ROMs, including those of the PowerMac G3 Beige.
This program aims to not only improve upon what Sheepshaver, PearPC, and other PowerPC Mac emulators have done, but also to provide a better debugging environment. This currently is designed to work best with PowerPC Old World ROMs, including those of the Power Mac 6100, 7200, and G3 Beige.
## Implemented Features
@ -44,7 +44,7 @@ Specifies the Boot ROM path (optional; looks for bootrom.bin by default)
Specify machine ID (optional; will attempt to determine machine ID from the boot rom otherwise)
As of now, only the Power Macintosh G3 Beige is implemented.
As of now, the most complete machines are the Power Mac 6100 (SCSI emulation in progress) and the Power Mac G3 Beige (SCSI + ATA emulation in progress, No ATI Rage acceleration).
## How to Compile
@ -57,7 +57,15 @@ You will also have to recursive clone or run
git submodule update --init --recursive
```
This is because the CubeB module is not included by default. All other components are already included in the thirdparty folder and compiled along with the rest of DingusPPC.
This is because the CubeB, Capstone, and SDL2 modules are not included by default.
For SDL2, Linux users may also have to run:
```
sudo apt install libsdl2-dev
```
CLI11 and loguru are already included in the thirdparty folder and compiled along with the rest of DingusPPC.
For example, to build the project in a Unix-like environment, you will need to run
the following commands in the OS terminal:
@ -69,8 +77,6 @@ make dingusppc
```
You may specify another build type using the variable CMAKE_BUILD_TYPE.
Future versions may drop SDL 2 as a requirement.
For Raspbian, you may also need the following command:
```
sudo apt install doxygen graphviz
@ -83,7 +89,7 @@ emulation. To build the tests, use the following terminal commands:
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake -DCMAKE_BUILD_TYPE=Release -DPPC_BUILD_PPC_TESTS=True ..
make testppc
```
@ -97,5 +103,4 @@ make testppc
## Compiler Requirements
- GCC 4.7 or newer (i.e. CodeBlocks 13.12 or newer)
- Visual Studio 2013 or newer
- Any C++20 compatible compiler

View File

@ -1 +0,0 @@
theme: jekyll-theme-minimal

21
cmake/PlatformGlob.cmake Normal file
View File

@ -0,0 +1,21 @@
# Detect platform suffix
if (EMSCRIPTEN)
set(PLATFORM_SUFFIX "_js$")
else()
set(PLATFORM_SUFFIX "_sdl|_cubeb$")
endif()
# Function to perform a platform-specific glob
function(platform_glob RESULT_VAR)
set(PLATFORM_SOURCES)
foreach(GLOB_PATTERN ${ARGN})
file(GLOB GLOB_RESULT ${GLOB_PATTERN})
foreach(FILE_PATH ${GLOB_RESULT})
get_filename_component(BASE_NAME ${FILE_PATH} NAME_WE)
if("${BASE_NAME}" MATCHES ${PLATFORM_SUFFIX} OR NOT "${BASE_NAME}" MATCHES "_js$|_sdl|_cubeb$")
list(APPEND PLATFORM_SOURCES ${FILE_PATH})
endif()
endforeach()
endforeach()
set(${RESULT_VAR} ${PLATFORM_SOURCES} PARENT_SCOPE)
endfunction()

View File

@ -2,7 +2,11 @@ include_directories("${PROJECT_SOURCE_DIR}"
"${PROJECT_SOURCE_DIR}/thirdparty/loguru/"
)
file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
platform_glob(SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_library(core OBJECT ${SOURCES})
target_link_libraries(core PRIVATE SDL2::SDL2)
if (EMSCRIPTEN)
target_link_libraries(core PRIVATE)
else()
target_link_libraries(core PRIVATE SDL2::SDL2)
endif()

View File

@ -26,7 +26,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cinttypes>
#if defined(__GNUG__) && !defined(__clang__) // GCC, mybe ICC but not Clang
#if defined(__GNUG__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) // GCC, mybe ICC but not Clang
# include <x86intrin.h>
@ -60,12 +60,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
template <class T>
inline T extract_bits(T val, int pos, int len) {
return (val >> pos) & (((T)1 << len) - 1);
return (val >> pos) & ((len == sizeof(T) * 8) ? (T)-1 : ((T)1 << len) - 1);
}
template <class T>
inline void insert_bits(T &old_val, T new_val, int pos, int len) {
T mask = (((T)1 << len) - 1) << pos;
T mask = ((len == sizeof(T) * 8) ? (T)-1 : ((T)1 << len) - 1) << pos;
old_val = (old_val & ~mask) | ((new_val << pos) & mask);
}
@ -78,4 +78,19 @@ static inline bool bit_set(const uint64_t val, const int bit_num) {
return !!(val & (1ULL << bit_num));
}
template <class T>
inline void clear_bit(T &val, const int bit_num) {
val &= ~((T)1 << bit_num);
}
template <class T>
inline void set_bit(T &val, const int bit_num) {
val |= ((T)1 << bit_num);
}
static inline uint32_t extract_with_wrap_around(uint32_t val, int pos, int size) {
return (uint32_t)((((uint64_t)val << 32) | val) >> ((8 - (pos & 3) - size) << 3)) &
((1LL << (size << 3)) - 1);
}
#endif // BIT_OPS_H

74
core/coresignal.h Normal file
View File

@ -0,0 +1,74 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** Poor man's signals & slots mechanism implementation. */
// Inspired by http://schneegans.github.io/tutorials/2015/09/20/signal-slot
#ifndef CORE_SIGNAL_H
#define CORE_SIGNAL_H
#include <functional>
#include <map>
template <typename... Args>
class CoreSignal {
public:
CoreSignal() = default;
~CoreSignal() = default;
// Connects a std::function type slot to the signal.
// The return value can be used to disconnect the slot.
int connect_func(std::function<void(Args...)> const& slot) const {
_slots.insert(std::make_pair(++_current_id, slot));
return _current_id;
}
// Connects a class method type slot to the signal.
// The return value can be used to disconnect the slot.
template <typename T>
int connect_method(T *inst, void (T::*func)(Args...)) {
return connect_func([=](Args... args) {
(inst->*func)(args...);
});
}
// Calls all connected slots.
void emit(Args... args) {
for (auto const& it : _slots) {
it.second(args...);
}
}
void disconnect(int id) {
_slots.erase(id);
}
void disconnect_all() {
_slots.clear();
}
private:
mutable std::map<int, std::function<void(Args...)>> _slots;
mutable unsigned int _current_id { 0 };
};
#endif // CORE_SIGNAL_H

View File

@ -22,33 +22,47 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef EVENT_MANAGER_H
#define EVENT_MANAGER_H
#include <core/coresignal.h>
#include <cinttypes>
#include <functional>
#include <list>
#include <map>
#include <utility>
enum EventType : uint16_t {
EVENT_UNKNOWN = 0,
EVENT_WINDOW = 100,
EVENT_KEYBOARD = 200,
EVENT_MOUSE = 300,
};
class BaseEvent {
class WindowEvent {
public:
BaseEvent(EventType type) { this->type = type; };
EventType type;
};
class WindowEvent : public BaseEvent {
public:
WindowEvent() : BaseEvent(EventType::EVENT_WINDOW) {};
WindowEvent() = default;
~WindowEvent() = default;
uint16_t sub_type;
uint32_t window_id;
};
enum : uint32_t {
MOUSE_EVENT_MOTION = 1 << 0,
MOUSE_EVENT_BUTTON = 1 << 1,
KEYBOARD_EVENT_DOWN = 1 << 0,
KEYBOARD_EVENT_UP = 1 << 1,
};
class MouseEvent {
public:
MouseEvent() = default;
~MouseEvent() = default;
uint32_t flags;
uint32_t xrel;
uint32_t yrel;
uint8_t buttons_state;
};
class KeyboardEvent {
public:
KeyboardEvent() = default;
~KeyboardEvent() = default;
uint32_t flags;
uint32_t key;
uint16_t keys_state;
};
class EventManager {
public:
static EventManager* get_instance() {
@ -58,31 +72,43 @@ public:
return event_manager;
};
using EventHandler = std::function<void(const BaseEvent&)>;
void poll_events();
void register_handler(const EventType event_type, EventHandler&& eh) {
this->_handlers[event_type].emplace_back(std::move(eh));
};
template <typename T>
void add_window_handler(T *inst, void (T::*func)(const WindowEvent&)) {
_window_signal.connect_method(inst, func);
}
void post_event(const BaseEvent& event) {
auto type = event.type;
template <typename T>
void add_mouse_handler(T *inst, void (T::*func)(const MouseEvent&)) {
_mouse_signal.connect_method(inst, func);
}
if( _handlers.find(type) == _handlers.end() )
return;
template <typename T>
void add_keyboard_handler(T* inst, void (T::*func)(const KeyboardEvent&)) {
_keyboard_signal.connect_method(inst, func);
}
auto&& handlers = _handlers.at(type);
template <typename T>
void add_post_handler(T *inst, void (T::*func)()) {
_post_signal.connect_method(inst, func);
}
for(auto&& handler : handlers)
handler(event);
void disconnect_handlers() {
_window_signal.disconnect_all();
_mouse_signal.disconnect_all();
_keyboard_signal.disconnect_all();
_post_signal.disconnect_all();
}
private:
static EventManager* event_manager;
EventManager() {}; // private constructor to implement a singleton
std::map<EventType, std::list<EventHandler>> _handlers;
CoreSignal<const WindowEvent&> _window_signal;
CoreSignal<const MouseEvent&> _mouse_signal;
CoreSignal<const KeyboardEvent&> _keyboard_signal;
CoreSignal<> _post_signal;
uint64_t events_captured = 0;
uint64_t unhandled_events = 0;

311
core/hostevents_sdl.cpp Normal file
View File

@ -0,0 +1,311 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <core/hostevents.h>
#include <core/coresignal.h>
#include <cpu/ppc/ppcemu.h>
#include <devices/common/adb/adbkeyboard.h>
#include <loguru.hpp>
#include <SDL.h>
EventManager* EventManager::event_manager;
static int get_sdl_event_key_code(const SDL_KeyboardEvent &event);
static void toggle_mouse_grab(const SDL_KeyboardEvent &event);
void EventManager::poll_events()
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
events_captured++;
switch (event.type) {
case SDL_QUIT:
power_on = false;
power_off_reason = po_shut_down;
break;
case SDL_WINDOWEVENT: {
WindowEvent we;
we.sub_type = event.window.event;
we.window_id = event.window.windowID;
this->_window_signal.emit(we);
}
break;
case SDL_KEYDOWN:
case SDL_KEYUP: {
// Internal shortcuts to trigger mouse grab, intentionally not
// sent to the host.
if (event.key.keysym.sym == SDLK_g && SDL_GetModState() == KMOD_LCTRL) {
if (event.type == SDL_KEYUP) {
toggle_mouse_grab(event.key);
}
return;
}
int key_code = get_sdl_event_key_code(event.key);
if (key_code != -1) {
KeyboardEvent ke;
ke.key = key_code;
if (event.type == SDL_KEYDOWN) {
ke.flags = KEYBOARD_EVENT_DOWN;
key_downs++;
} else {
ke.flags = KEYBOARD_EVENT_UP;
key_ups++;
}
// Caps Lock is a special case, since it's a toggle key
if (ke.key == AdbKey_CapsLock) {
ke.flags = SDL_GetModState() & KMOD_CAPS ?
KEYBOARD_EVENT_DOWN : KEYBOARD_EVENT_UP;
}
this->_keyboard_signal.emit(ke);
} else {
LOG_F(WARNING, "Unknown key %x pressed", event.key.keysym.sym);
}
}
break;
case SDL_MOUSEMOTION: {
MouseEvent me;
me.xrel = event.motion.xrel;
me.yrel = event.motion.yrel;
me.flags = MOUSE_EVENT_MOTION;
this->_mouse_signal.emit(me);
}
break;
case SDL_MOUSEBUTTONDOWN: {
MouseEvent me;
me.buttons_state = 1;
me.flags = MOUSE_EVENT_BUTTON;
this->_mouse_signal.emit(me);
}
break;
case SDL_MOUSEBUTTONUP: {
MouseEvent me;
me.buttons_state = 0;
me.flags = MOUSE_EVENT_BUTTON;
this->_mouse_signal.emit(me);
}
break;
default:
unhandled_events++;
}
}
// perform post-processing
this->_post_signal.emit();
}
static int get_sdl_event_key_code(const SDL_KeyboardEvent &event)
{
switch (event.keysym.sym) {
case SDLK_a: return AdbKey_A;
case SDLK_b: return AdbKey_B;
case SDLK_c: return AdbKey_C;
case SDLK_d: return AdbKey_D;
case SDLK_e: return AdbKey_E;
case SDLK_f: return AdbKey_F;
case SDLK_g: return AdbKey_G;
case SDLK_h: return AdbKey_H;
case SDLK_i: return AdbKey_I;
case SDLK_j: return AdbKey_J;
case SDLK_k: return AdbKey_K;
case SDLK_l: return AdbKey_L;
case SDLK_m: return AdbKey_M;
case SDLK_n: return AdbKey_N;
case SDLK_o: return AdbKey_O;
case SDLK_p: return AdbKey_P;
case SDLK_q: return AdbKey_Q;
case SDLK_r: return AdbKey_R;
case SDLK_s: return AdbKey_S;
case SDLK_t: return AdbKey_T;
case SDLK_u: return AdbKey_U;
case SDLK_v: return AdbKey_V;
case SDLK_w: return AdbKey_W;
case SDLK_x: return AdbKey_X;
case SDLK_y: return AdbKey_Y;
case SDLK_z: return AdbKey_Z;
case SDLK_1: return AdbKey_1;
case SDLK_2: return AdbKey_2;
case SDLK_3: return AdbKey_3;
case SDLK_4: return AdbKey_4;
case SDLK_5: return AdbKey_5;
case SDLK_6: return AdbKey_6;
case SDLK_7: return AdbKey_7;
case SDLK_8: return AdbKey_8;
case SDLK_9: return AdbKey_9;
case SDLK_0: return AdbKey_0;
case SDLK_ESCAPE: return AdbKey_Escape;
case SDLK_BACKQUOTE: return AdbKey_Grave;
case SDLK_MINUS: return AdbKey_Minus;
case SDLK_EQUALS: return AdbKey_Equal;
case SDLK_LEFTBRACKET: return AdbKey_LeftBracket;
case SDLK_RIGHTBRACKET: return AdbKey_RightBracket;
case SDLK_BACKSLASH: return AdbKey_Backslash;
case SDLK_SEMICOLON: return AdbKey_Semicolon;
case SDLK_QUOTE: return AdbKey_Quote;
case SDLK_COMMA: return AdbKey_Comma;
case SDLK_PERIOD: return AdbKey_Period;
case SDLK_SLASH: return AdbKey_Slash;
// Convert shifted variants to unshifted
case SDLK_EXCLAIM: return AdbKey_1;
case SDLK_AT: return AdbKey_2;
case SDLK_HASH: return AdbKey_3;
case SDLK_DOLLAR: return AdbKey_4;
case SDLK_UNDERSCORE: return AdbKey_Minus;
case SDLK_PLUS: return AdbKey_Equal;
case SDLK_COLON: return AdbKey_Semicolon;
case SDLK_QUOTEDBL: return AdbKey_Quote;
case SDLK_LESS: return AdbKey_Comma;
case SDLK_GREATER: return AdbKey_Period;
case SDLK_QUESTION: return AdbKey_Slash;
case SDLK_TAB: return AdbKey_Tab;
case SDLK_RETURN: return AdbKey_Return;
case SDLK_SPACE: return AdbKey_Space;
case SDLK_BACKSPACE: return AdbKey_Delete;
case SDLK_DELETE: return AdbKey_ForwardDelete;
case SDLK_INSERT: return AdbKey_Help;
case SDLK_HOME: return AdbKey_Home;
case SDLK_HELP: return AdbKey_Home;
case SDLK_END: return AdbKey_End;
case SDLK_PAGEUP: return AdbKey_PageUp;
case SDLK_PAGEDOWN: return AdbKey_PageDown;
case SDLK_LCTRL: return AdbKey_Control;
case SDLK_RCTRL: return AdbKey_Control;
case SDLK_LSHIFT: return AdbKey_Shift;
case SDLK_RSHIFT: return AdbKey_Shift;
case SDLK_LALT: return AdbKey_Option;
case SDLK_RALT: return AdbKey_Option;
case SDLK_LGUI: return AdbKey_Command;
case SDLK_RGUI: return AdbKey_Command;
case SDLK_MENU: return AdbKey_Grave;
case SDLK_CAPSLOCK: return AdbKey_CapsLock;
case SDLK_UP: return AdbKey_ArrowUp;
case SDLK_DOWN: return AdbKey_ArrowDown;
case SDLK_LEFT: return AdbKey_ArrowLeft;
case SDLK_RIGHT: return AdbKey_ArrowRight;
;
case SDLK_KP_0: return AdbKey_Keypad0;
case SDLK_KP_1: return AdbKey_Keypad1;
case SDLK_KP_2: return AdbKey_Keypad2;
case SDLK_KP_3: return AdbKey_Keypad3;
case SDLK_KP_4: return AdbKey_Keypad4;
case SDLK_KP_5: return AdbKey_Keypad5;
case SDLK_KP_6: return AdbKey_Keypad6;
case SDLK_KP_7: return AdbKey_Keypad7;
case SDLK_KP_9: return AdbKey_Keypad9;
case SDLK_KP_8: return AdbKey_Keypad8;
case SDLK_KP_PERIOD: return AdbKey_KeypadDecimal;
case SDLK_KP_PLUS: return AdbKey_KeypadPlus;
case SDLK_KP_MINUS: return AdbKey_KeypadMinus;
case SDLK_KP_MULTIPLY: return AdbKey_KeypadMultiply;
case SDLK_KP_DIVIDE: return AdbKey_KeypadDivide;
case SDLK_KP_ENTER: return AdbKey_KeypadEnter;
case SDLK_KP_EQUALS: return AdbKey_KeypadEquals;
case SDLK_NUMLOCKCLEAR: return AdbKey_KeypadClear;
;
case SDLK_F1: return AdbKey_F1;
case SDLK_F2: return AdbKey_F2;
case SDLK_F3: return AdbKey_F3;
case SDLK_F4: return AdbKey_F4;
case SDLK_F5: return AdbKey_F5;
case SDLK_F6: return AdbKey_F6;
case SDLK_F7: return AdbKey_F7;
case SDLK_F8: return AdbKey_F8;
case SDLK_F9: return AdbKey_F9;
case SDLK_F10: return AdbKey_F10;
case SDLK_F11: return AdbKey_F11;
case SDLK_F12: return AdbKey_F12;
case SDLK_PRINTSCREEN: return AdbKey_F13;
case SDLK_SCROLLLOCK: return AdbKey_F14;
case SDLK_PAUSE: return AdbKey_F15;
//International keyboard support
//Japanese keyboard
case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_INTERNATIONAL3):
return AdbKey_JIS_Yen;
case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_INTERNATIONAL1):
return AdbKey_JIS_Underscore;
case 0XBC:
return AdbKey_JIS_KP_Comma;
case 0X89:
return AdbKey_JIS_Eisu;
case SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_INTERNATIONAL2):
return AdbKey_JIS_Kana;
//German keyboard
case 0XB4: return AdbKey_Slash;
case 0X5E: return AdbKey_ISO1;
case 0XDF: return AdbKey_Minus; //Eszett
case 0XE4: return AdbKey_LeftBracket; //A-umlaut
case 0XF6: return AdbKey_Semicolon; //O-umlaut
case 0XFC: return AdbKey_LeftBracket; //U-umlaut
// French keyboard
case 0X29: return AdbKey_Minus; // Right parenthesis
case 0X43: return AdbKey_KeypadMultiply; // Star/Mu
//0XB2 is superscript 2. Which Mac key should this one map to?
case 0XF9: return AdbKey_Quote; // U-grave
// Italian keyboard
case 0XE0: return AdbKey_9; // A-grave
case 0XE8: return AdbKey_6; // E-grave
case 0XEC: return AdbKey_LeftBracket; // I-grave
case 0XF2: return AdbKey_KeypadMultiply; // O-grave
}
return -1;
}
static void toggle_mouse_grab(const SDL_KeyboardEvent &event) {
SDL_Window *window = SDL_GetWindowFromID(event.windowID);
if (SDL_GetRelativeMouseMode()) {
SDL_SetRelativeMouseMode(SDL_FALSE);
SDL_SetWindowTitle(window, "DingusPPC Display");
} else {
// If the mouse is initially outside the window, move it to the middle,
// so that clicks are handled by the window (instead making it lose
// focus, at least with macOS hosts).
int mouse_x, mouse_y, window_x, window_y, window_width, window_height;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
SDL_GetWindowPosition(window, &window_x, &window_y);
SDL_GetWindowSize(window, &window_width, &window_height);
if (mouse_x < window_x || mouse_x >= window_x + window_width ||
mouse_y < window_y || mouse_y >= window_y + window_height) {
SDL_WarpMouseInWindow(window, window_width / 2, window_height / 2);
}
SDL_SetRelativeMouseMode(SDL_TRUE);
SDL_SetWindowTitle(window, "DingusPPC Display (Mouse Grabbed)");
}
}

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -26,15 +26,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cinttypes>
#include <memory>
#include <mutex>
TimerManager* TimerManager::timer_manager;
uint32_t TimerManager::add_oneshot_timer(uint64_t timeout, timer_cb cb)
{
if (!timeout || timeout <= MIN_TIMEOUT_NS) {
LOG_F(WARNING, "One-shot timer too short, timeout=%llu ns", (long long unsigned)timeout);
}
TimerInfo* ti = new TimerInfo;
ti->id = ++this->id;
@ -55,16 +52,33 @@ uint32_t TimerManager::add_oneshot_timer(uint64_t timeout, timer_cb cb)
return ti->id;
}
uint32_t TimerManager::add_cyclic_timer(uint64_t interval, timer_cb cb)
{
if (!interval || interval <= MIN_TIMEOUT_NS) {
LOG_F(WARNING, "Cyclic timer interval too short, timeout=%llu ns", (long long unsigned)interval);
}
uint32_t TimerManager::add_immediate_timer(timer_cb cb) {
TimerInfo* ti = new TimerInfo;
ti->id = ++this->id;
ti->timeout_ns = this->get_time_now() + interval;
ti->timeout_ns = this->get_time_now();
ti->interval_ns = 0;
ti->cb = cb;
std::shared_ptr<TimerInfo> timer_desc(ti);
// add new timer to the timer queue
this->timer_queue.push(timer_desc);
// notify listeners about changes in the timer queue
if (!this->cb_active) {
this->notify_timer_changes();
}
return ti->id;
}
uint32_t TimerManager::add_cyclic_timer(uint64_t interval, uint64_t delay, timer_cb cb)
{
TimerInfo* ti = new TimerInfo;
ti->id = ++this->id;
ti->timeout_ns = this->get_time_now() + delay;
ti->interval_ns = interval;
ti->cb = cb;
@ -81,39 +95,41 @@ uint32_t TimerManager::add_cyclic_timer(uint64_t interval, timer_cb cb)
return ti->id;
}
uint32_t TimerManager::add_cyclic_timer(uint64_t interval, timer_cb cb) {
return this->add_cyclic_timer(interval, interval, cb);
}
void TimerManager::cancel_timer(uint32_t id)
{
TimerInfo* cur_timer = this->timer_queue.top().get();
if (cur_timer->id == id) {
this->timer_queue.pop();
} else {
this->timer_queue.remove_by_id(id);
}
this->timer_queue.remove_by_id(id);
if (!this->cb_active) {
this->notify_timer_changes();
}
}
uint64_t TimerManager::process_timers(uint64_t time_now)
uint64_t TimerManager::process_timers()
{
TimerInfo* cur_timer;
std::shared_ptr<TimerInfo> cur_timer;
uint64_t time_now = get_time_now();
{ // mtx scope
std::lock_guard<std::recursive_mutex> lk(this->timer_queue.get_mtx());
if (this->timer_queue.empty()) {
return 0ULL;
}
// scan for expired timers
cur_timer = this->timer_queue.top().get();
while (cur_timer->timeout_ns <= time_now ||
cur_timer->timeout_ns <= (time_now + MIN_TIMEOUT_NS)) {
cur_timer = this->timer_queue.top();
} // ] mtx scope
while (cur_timer->timeout_ns <= time_now) {
timer_cb cb = cur_timer->cb;
// re-arm cyclic timers
if (cur_timer->interval_ns) {
auto new_timer = this->timer_queue.top();
new_timer->timeout_ns = time_now + cur_timer->interval_ns;
this->timer_queue.pop();
this->timer_queue.push(new_timer);
std::lock_guard<std::recursive_mutex> lk(this->timer_queue.get_mtx());
cur_timer->timeout_ns = time_now + cur_timer->interval_ns;
this->timer_queue.remove_by_id(cur_timer->id);
this->timer_queue.push(cur_timer);
} else {
// remove one-shot timers from queue
this->timer_queue.pop();
@ -127,11 +143,14 @@ uint64_t TimerManager::process_timers(uint64_t time_now)
this->cb_active = false;
// process next timer
{ // [ mtx scope
std::lock_guard<std::recursive_mutex> lk(this->timer_queue.get_mtx());
if (this->timer_queue.empty()) {
return 0ULL;
}
cur_timer = this->timer_queue.top().get();
cur_timer = this->timer_queue.top();
} // ] mtx scope
}
// return time slice in nanoseconds until next timer's expiry

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -22,17 +22,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef TIMER_MANAGER_H
#define TIMER_MANAGER_H
#include <atomic>
#include <algorithm>
#include <cinttypes>
#include <functional>
#include <memory>
#include <queue>
#include <vector>
#include <mutex>
using namespace std;
#define MIN_TIMEOUT_NS 200
#define NS_PER_SEC 1E9
#define USEC_PER_SEC 1E6
#define NS_PER_USEC 1000UL
@ -52,16 +52,43 @@ template <typename T, class Container = std::vector<T>, class Compare = std::les
class my_priority_queue : public std::priority_queue<T, Container, Compare> {
public:
bool remove_by_id(const uint32_t id){
std::lock_guard<std::recursive_mutex> lk(mtx);
auto el = this->top();
if (el->id == id) {
std::priority_queue<T, Container, Compare>::pop();
return true;
}
auto it = std::find_if(
this->c.begin(), this->c.end(), [id](const T& el) { return el->id == id; });
if (it != this->c.end()) {
this->c.erase(it);
std::make_heap(this->c.begin(), this->c.end(), this->comp);
return true;
} else {
return false;
}
return false;
};
void push(T val)
{
std::lock_guard<std::recursive_mutex> lk(mtx);
std::priority_queue<T, Container, Compare>::push(val);
};
T pop()
{
std::lock_guard<std::recursive_mutex> lk(mtx);
T val = std::priority_queue<T, Container, Compare>::top();
std::priority_queue<T, Container, Compare>::pop();
return val;
};
std::recursive_mutex& get_mtx()
{
return mtx;
}
private:
std::recursive_mutex mtx;
};
typedef struct TimerInfo {
@ -103,10 +130,12 @@ public:
// creating and cancelling timers
uint32_t add_oneshot_timer(uint64_t timeout, timer_cb cb);
uint32_t add_immediate_timer(timer_cb cb);
uint32_t add_cyclic_timer(uint64_t interval, timer_cb cb);
uint32_t add_cyclic_timer(uint64_t interval, uint64_t delay, timer_cb cb);
void cancel_timer(uint32_t id);
uint64_t process_timers(uint64_t time_now);
uint64_t process_timers();
private:
static TimerManager* timer_manager;
@ -118,8 +147,8 @@ private:
function<uint64_t()> get_time_now;
function<void()> notify_timer_changes;
uint32_t id = 0;
bool cb_active = false; // true if a timer callback is executing
std::atomic<uint32_t> id{0};
bool cb_active = false; // true if a timer callback is executing // FIXME: Do we need this? It gets written in main thread and read in audio thread.
};
#endif // TIMER_MANAGER_H

View File

@ -1,6 +1,8 @@
include_directories("${PROJECT_SOURCE_DIR}"
"${PROJECT_SOURCE_DIR}/thirdparty/loguru/")
file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/*.h"
)
add_library(cpu_ppc OBJECT ${SOURCES})

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-21 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -23,166 +23,239 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
// Any shared opcodes are in ppcopcodes.cpp
#include "ppcemu.h"
#include "ppcmacros.h"
#include "ppcmmu.h"
#include <array>
#include <cmath>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <stdio.h>
#include <loguru.hpp>
// Affects the XER register's SO and OV Bits
inline void power_setsoov(uint32_t a, uint32_t b, uint32_t d) {
if ((a ^ b) & (a ^ d) & 0x80000000UL) {
ppc_state.spr[SPR::XER] |= 0xC0000000UL;
} else {
ppc_state.spr[SPR::XER] &= 0xBFFFFFFFUL;
}
}
#include <stdint.h>
/** mask generator for rotate and shift instructions (§ 4.2.1.4 PowerpC PEM) */
static inline uint32_t power_rot_mask(unsigned rot_mb, unsigned rot_me) {
uint32_t m1 = 0xFFFFFFFFUL >> rot_mb;
uint32_t m2 = (uint32_t)(0xFFFFFFFFUL << (31 - rot_me));
uint32_t m1 = 0xFFFFFFFFU >> rot_mb;
uint32_t m2 = 0xFFFFFFFFU << (31 - rot_me);
return ((rot_mb <= rot_me) ? m2 & m1 : m1 | m2);
}
template <field_rc rec, field_ov ov>
void dppc_interpreter::power_abs() {
ppc_grab_regsda();
uint32_t ppc_result_d;
ppc_grab_regsda(ppc_cur_instruction);
if (ppc_result_a == 0x80000000) {
ppc_result_d = ppc_result_a;
if (oe_flag)
ppc_state.spr[SPR::XER] |= 0xC0000000;
if (ov)
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else {
ppc_result_d = ppc_result_a & 0x7FFFFFFF;
ppc_result_d = (int32_t(ppc_result_a) < 0) ? -ppc_result_a : ppc_result_a;
if (ov)
ppc_state.spr[SPR::XER] &= ~XER::OV;
}
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_d);
ppc_store_result_regd();
ppc_store_iresult_reg(reg_d, ppc_result_d);
}
template void dppc_interpreter::power_abs<RC0, OV0>();
template void dppc_interpreter::power_abs<RC0, OV1>();
template void dppc_interpreter::power_abs<RC1, OV0>();
template void dppc_interpreter::power_abs<RC1, OV1>();
void dppc_interpreter::power_clcs() {
ppc_grab_regsda();
uint32_t ppc_result_d;
ppc_grab_regsda(ppc_cur_instruction);
switch (reg_a) {
case 12: //instruction cache line size
case 13: //data cache line size
case 14: //minimum line size
case 15: //maximum line size
ppc_result_d = 64;
break;
default:
ppc_result_d = 0;
default: ppc_result_d = is_601 ? 64 : 32; break;
case 7:
case 23: ppc_result_d = is_601 ? 64 : 0; break;
case 8:
case 9:
case 24:
case 25: ppc_result_d = is_601 ? 64 : 4; break;
case 10:
case 11:
case 26:
case 27: ppc_result_d = is_601 ? 64 : 0x4000; break;
}
ppc_store_result_regd();
ppc_store_iresult_reg(reg_d, ppc_result_d);
}
template <field_rc rec, field_ov ov>
void dppc_interpreter::power_div() {
ppc_grab_regsdab();
ppc_result_d = (ppc_result_a | ppc_state.spr[SPR::MQ]) / ppc_result_b;
ppc_state.spr[SPR::MQ] = (ppc_result_a | ppc_state.spr[SPR::MQ]) % ppc_result_b;
uint32_t ppc_result_d;
ppc_grab_regsdab(ppc_cur_instruction);
if (oe_flag)
power_setsoov(ppc_result_b, ppc_result_a, ppc_result_d);
if (rc_flag)
ppc_changecrf0(ppc_result_d);
int64_t dividend = (uint64_t(ppc_result_a) << 32) | ppc_state.spr[SPR::MQ];
int32_t divisor = ppc_result_b;
int64_t quotient;
int32_t remainder;
ppc_store_result_regd();
}
void dppc_interpreter::power_divs() {
ppc_grab_regsdab();
ppc_result_d = ppc_result_a / ppc_result_b;
ppc_state.spr[SPR::MQ] = (ppc_result_a % ppc_result_b);
if (oe_flag)
power_setsoov(ppc_result_b, ppc_result_a, ppc_result_d);
if (rc_flag)
ppc_changecrf0(ppc_result_d);
ppc_store_result_regd();
}
void dppc_interpreter::power_doz() {
ppc_grab_regsdab();
if (((int32_t)ppc_result_a) > ((int32_t)ppc_result_b)) {
ppc_result_d = 0;
if (dividend == -0x80000000 && divisor == -1) {
remainder = 0;
ppc_result_d = 0x80000000U; // -2^31 aka INT32_MIN
if (ov)
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else if (!divisor) {
remainder = 0;
ppc_result_d = 0x80000000U; // -2^31 aka INT32_MIN
if (ov)
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else {
ppc_result_d = ppc_result_b - ppc_result_a;
quotient = dividend / divisor;
remainder = dividend % divisor;
ppc_result_d = uint32_t(quotient);
if (ov) {
if (((quotient >> 31) + 1) & ~1) {
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else {
ppc_state.spr[SPR::XER] &= ~XER::OV;
}
}
}
if (rc_flag)
ppc_changecrf0(ppc_result_d);
if (oe_flag)
power_setsoov(ppc_result_a, ppc_result_b, ppc_result_d);
if (rec)
ppc_changecrf0(remainder);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_d, ppc_result_d);
ppc_state.spr[SPR::MQ] = remainder;
}
template void dppc_interpreter::power_div<RC0, OV0>();
template void dppc_interpreter::power_div<RC0, OV1>();
template void dppc_interpreter::power_div<RC1, OV0>();
template void dppc_interpreter::power_div<RC1, OV1>();
template <field_rc rec, field_ov ov>
void dppc_interpreter::power_divs() {
uint32_t ppc_result_d;
int32_t remainder;
ppc_grab_regsdab(ppc_cur_instruction);
if (!ppc_result_b) { // handle the "anything / 0" case
ppc_result_d = -1;
remainder = ppc_result_a;
if (ov)
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else if (ppc_result_a == 0x80000000U && ppc_result_b == 0xFFFFFFFFU) {
ppc_result_d = 0x80000000U;
remainder = 0;
if (ov)
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else { // normal signed devision
ppc_result_d = int32_t(ppc_result_a) / int32_t(ppc_result_b);
remainder = (int32_t(ppc_result_a) % int32_t(ppc_result_b));
if (ov)
ppc_state.spr[SPR::XER] &= ~XER::OV;
}
if (rec)
ppc_changecrf0(remainder);
ppc_store_iresult_reg(reg_d, ppc_result_d);
ppc_state.spr[SPR::MQ] = remainder;
}
template void dppc_interpreter::power_divs<RC0, OV0>();
template void dppc_interpreter::power_divs<RC0, OV1>();
template void dppc_interpreter::power_divs<RC1, OV0>();
template void dppc_interpreter::power_divs<RC1, OV1>();
template <field_rc rec, field_ov ov>
void dppc_interpreter::power_doz() {
ppc_grab_regsdab(ppc_cur_instruction);
uint32_t ppc_result_d = (int32_t(ppc_result_a) < int32_t(ppc_result_b)) ?
ppc_result_b - ppc_result_a : 0;
if (ov) {
if (int32_t(ppc_result_d) < 0) {
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else {
ppc_state.spr[SPR::XER] &= ~XER::OV;
}
}
if (rec)
ppc_changecrf0(ppc_result_d);
ppc_store_iresult_reg(reg_d, ppc_result_d);
}
template void dppc_interpreter::power_doz<RC0, OV0>();
template void dppc_interpreter::power_doz<RC0, OV1>();
template void dppc_interpreter::power_doz<RC1, OV0>();
template void dppc_interpreter::power_doz<RC1, OV1>();
void dppc_interpreter::power_dozi() {
ppc_grab_regsdasimm();
uint32_t ppc_result_d;
ppc_grab_regsdasimm(ppc_cur_instruction);
if (((int32_t)ppc_result_a) > simm) {
ppc_result_d = 0;
} else {
ppc_result_d = simm - ppc_result_a;
}
ppc_store_result_rega();
ppc_store_iresult_reg(reg_d, ppc_result_d);
}
template <field_rc rec>
void dppc_interpreter::power_lscbx() {
ppc_grab_regsdab();
ppc_effective_address = (reg_a == 0) ? ppc_result_b : ppc_result_a + ppc_result_b;
//ppc_result_d = 0xFFFFFFFF;
ppc_grab_regsdab(ppc_cur_instruction);
ppc_effective_address = ppc_result_b + (reg_a ? ppc_result_a : 0);
uint8_t return_value = 0;
uint32_t bytes_to_load = (ppc_state.spr[SPR::XER] & 0x7f);
uint32_t bytes_copied = 0;
uint8_t matching_byte = (uint8_t)((ppc_state.spr[SPR::XER] & 0xFF00) >> 8);
uint32_t bytes_to_load = (ppc_state.spr[SPR::XER] & 0x7F);
uint32_t bytes_remaining = bytes_to_load;
uint8_t matching_byte = (uint8_t)(ppc_state.spr[SPR::XER] >> 8);
uint32_t ppc_result_d = 0;
bool is_match = false;
// for storing each byte
uint32_t bitmask = 0xFF000000;
uint8_t shift_amount = 24;
uint8_t shift_amount = 24;
while (bytes_to_load > 0) {
return_value = mmu_read_vmem<uint8_t>(ppc_effective_address);
// return_value = mem_grab_byte(ppc_effective_address);
ppc_result_d = (ppc_result_d & ~(bitmask)) | (return_value << shift_amount);
ppc_store_result_regd();
if (bitmask == 0x000000FF) {
reg_d = (reg_d + 1) & 31;
//ppc_result_d = 0xFFFFFFFF;
bitmask = 0xFF000000;
while (bytes_remaining > 0) {
uint8_t return_value = mmu_read_vmem<uint8_t>(ppc_effective_address);
ppc_result_d |= return_value << shift_amount;
if (!shift_amount) {
if (reg_d != reg_a && reg_d != reg_b)
ppc_store_iresult_reg(reg_d, ppc_result_d);
reg_d = (reg_d + 1) & 0x1F;
ppc_result_d = 0;
shift_amount = 24;
}
else {
bitmask >>= 8;
} else {
shift_amount -= 8;
}
ppc_effective_address++;
bytes_copied++;
bytes_to_load--;
bytes_remaining--;
if (return_value == matching_byte)
if (return_value == matching_byte) {
is_match = true;
break;
}
}
ppc_state.spr[SPR::XER] = (ppc_state.spr[SPR::XER] & 0xFFFFFF80) | bytes_copied;
// store partially loaded register if any
if (shift_amount != 24 && reg_d != reg_a && reg_d != reg_b)
ppc_store_iresult_reg(reg_d, ppc_result_d);
ppc_state.spr[SPR::XER] = (ppc_state.spr[SPR::XER] & ~0x7F) | (bytes_to_load - bytes_remaining);
if (rc_flag)
ppc_changecrf0(ppc_result_d);
if (rec) {
ppc_state.cr =
(ppc_state.cr & 0x0FFFFFFFUL) |
(is_match ? CRx_bit::CR_EQ : 0) |
((ppc_state.spr[SPR::XER] & XER::SO) >> 3);
}
}
template void dppc_interpreter::power_lscbx<RC0>();
template void dppc_interpreter::power_lscbx<RC1>();
template <field_rc rec>
void dppc_interpreter::power_maskg() {
ppc_grab_regssab();
uint32_t mask_start = ppc_result_d & 31;
uint32_t mask_end = ppc_result_b & 31;
ppc_grab_regssab(ppc_cur_instruction);
uint32_t mask_start = ppc_result_d & 0x1F;
uint32_t mask_end = ppc_result_b & 0x1F;
uint32_t insert_mask = 0;
if (mask_start < (mask_end + 1)) {
@ -197,318 +270,401 @@ void dppc_interpreter::power_maskg() {
ppc_result_a = insert_mask;
if (rc_flag)
ppc_changecrf0(ppc_result_d);
ppc_store_result_rega();
}
void dppc_interpreter::power_maskir() {
ppc_grab_regssab();
ppc_result_a = (ppc_result_d & ppc_result_b) | (~(ppc_result_b) & ppc_result_a);
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_maskg<RC0>();
template void dppc_interpreter::power_maskg<RC1>();
template <field_rc rec>
void dppc_interpreter::power_maskir() {
ppc_grab_regssab(ppc_cur_instruction);
ppc_result_a = (ppc_result_a & ~ppc_result_b) | (ppc_result_d & ppc_result_b);
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_maskir<RC0>();
template void dppc_interpreter::power_maskir<RC1>();
template <field_rc rec, field_ov ov>
void dppc_interpreter::power_mul() {
ppc_grab_regsdab();
uint64_t product;
ppc_grab_regsdab(ppc_cur_instruction);
int64_t product = int64_t(int32_t(ppc_result_a)) * int32_t(ppc_result_b);
uint32_t ppc_result_d = uint32_t(uint64_t(product) >> 32);
ppc_state.spr[SPR::MQ] = uint32_t(product);
product = ((uint64_t)ppc_result_a) * ((uint64_t)ppc_result_b);
ppc_result_d = ((uint32_t)(product >> 32));
ppc_state.spr[SPR::MQ] = ((uint32_t)(product));
if (rc_flag)
ppc_changecrf0(ppc_result_d);
ppc_store_result_regd();
if (ov) {
if (uint64_t(product >> 31) + 1 & ~1) {
ppc_state.spr[SPR::XER] |= XER::SO | XER::OV;
} else {
ppc_state.spr[SPR::XER] &= ~XER::OV;
}
}
if (rec)
ppc_changecrf0(uint32_t(product));
ppc_store_iresult_reg(reg_d, ppc_result_d);
}
template void dppc_interpreter::power_mul<RC0, OV0>();
template void dppc_interpreter::power_mul<RC0, OV1>();
template void dppc_interpreter::power_mul<RC1, OV0>();
template void dppc_interpreter::power_mul<RC1, OV1>();
template <field_rc rec, field_ov ov>
void dppc_interpreter::power_nabs() {
ppc_grab_regsda();
ppc_result_d = (0x80000000 | ppc_result_a);
ppc_grab_regsda(ppc_cur_instruction);
uint32_t ppc_result_d = (int32_t(ppc_result_a) < 0) ? ppc_result_a : -ppc_result_a;
if (rc_flag)
if (ov)
ppc_state.spr[SPR::XER] &= ~XER::OV;
if (rec)
ppc_changecrf0(ppc_result_d);
ppc_store_result_regd();
ppc_store_iresult_reg(reg_d, ppc_result_d);
}
template void dppc_interpreter::power_nabs<RC0, OV0>();
template void dppc_interpreter::power_nabs<RC0, OV1>();
template void dppc_interpreter::power_nabs<RC1, OV0>();
template void dppc_interpreter::power_nabs<RC1, OV1>();
void dppc_interpreter::power_rlmi() {
ppc_grab_regssab();
unsigned rot_mb = (ppc_cur_instruction >> 6) & 31;
unsigned rot_me = (ppc_cur_instruction >> 1) & 31;
unsigned rot_sh = ppc_result_b & 31;
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_mb = (ppc_cur_instruction >> 6) & 0x1F;
unsigned rot_me = (ppc_cur_instruction >> 1) & 0x1F;
unsigned rot_sh = ppc_result_b & 0x1F;
uint32_t r = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
uint32_t mask = power_rot_mask(rot_mb, rot_me);
ppc_result_a = ((r & mask) | (ppc_result_a & ~mask));
if (rc_flag)
if ((ppc_cur_instruction & 0x01) == 1)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template <field_rc rec>
void dppc_interpreter::power_rrib() {
ppc_grab_regssab();
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
if (ppc_result_d & 0x80000000) {
ppc_result_a |= ((ppc_result_d & 0x80000000) >> ppc_result_b);
if (int32_t(ppc_result_d) < 0) {
ppc_result_a |= (0x80000000U >> rot_sh);
} else {
ppc_result_a &= ~((ppc_result_d & 0x80000000) >> ppc_result_b);
ppc_result_a &= ~(0x80000000U >> rot_sh);
}
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_rrib<RC0>();
template void dppc_interpreter::power_rrib<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sle() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
ppc_result_a = ppc_result_d << rot_sh;
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sle<RC0>();
template void dppc_interpreter::power_sle<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sleq() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
uint32_t r = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
uint32_t mask = power_rot_mask(0, 31 - rot_sh);
ppc_result_a = ((r & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
ppc_state.spr[SPR::MQ] = r;
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sleq<RC0>();
template void dppc_interpreter::power_sleq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sliq() {
ppc_grab_regssa();
unsigned rot_sh = (ppc_cur_instruction >> 11) & 31;
ppc_grab_regssash(ppc_cur_instruction);
ppc_result_a = ppc_result_d << rot_sh;
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sliq<RC0>();
template void dppc_interpreter::power_sliq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_slliq() {
ppc_grab_regssa();
unsigned rot_sh = (ppc_cur_instruction >> 11) & 31;
uint32_t r = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
uint32_t mask = power_rot_mask(0, 31 - rot_sh);
ppc_result_a = ((r & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
ppc_state.spr[SPR::MQ] = r;
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_sllq() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
uint32_t r = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
uint32_t mask = power_rot_mask(0, 31 - rot_sh);
if (ppc_result_b >= 0x20) {
ppc_result_a = (ppc_state.spr[SPR::MQ] & mask);
}
else {
ppc_result_a = ((r & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
}
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_slq() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
if (ppc_result_b >= 0x20) {
ppc_result_a = ppc_result_d << rot_sh;
} else {
ppc_result_a = 0;
}
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
}
void dppc_interpreter::power_sraiq() {
ppc_grab_regssa();
unsigned rot_sh = (ppc_cur_instruction >> 11) & 0x1F;
uint32_t mask = (1 << rot_sh) - 1;
ppc_result_a = (int32_t)ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
if ((ppc_result_d & 0x80000000UL) && (ppc_result_d & mask)) {
ppc_state.spr[SPR::XER] |= 0x20000000UL;
} else {
ppc_state.spr[SPR::XER] &= 0xDFFFFFFFUL;
}
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_sraq() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 0x1F;
uint32_t mask = (1 << rot_sh) - 1;
ppc_result_a = (int32_t)ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
if ((ppc_result_d & 0x80000000UL) && (ppc_result_d & mask)) {
ppc_state.spr[SPR::XER] |= 0x20000000UL;
} else {
ppc_state.spr[SPR::XER] &= 0xDFFFFFFFUL;
}
ppc_state.spr[SPR::MQ] = ((ppc_result_d << ppc_result_b) | (ppc_result_d >> (ppc_result_b)));
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_sre() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
ppc_result_a = ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_srea() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 0x1F;
ppc_result_a = (int32_t)ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
if ((ppc_result_d & 0x80000000UL) && (ppc_result_d & rot_sh)) {
ppc_state.spr[SPR::XER] |= 0x20000000UL;
} else {
ppc_state.spr[SPR::XER] &= 0xDFFFFFFFUL;
}
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_sreq() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
unsigned mask = power_rot_mask(rot_sh, 31);
ppc_result_a = ((rot_sh & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
ppc_state.spr[SPR::MQ] = rot_sh;
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_sriq() {
ppc_grab_regssa();
unsigned rot_sh = (ppc_cur_instruction >> 11) & 31;
ppc_result_a = ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (rot_sh)));
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_srliq() {
ppc_grab_regssa();
unsigned rot_sh = (ppc_cur_instruction >> 11) & 31;
uint32_t r = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
unsigned mask = power_rot_mask(rot_sh, 31);
ppc_result_a = ((r & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
ppc_state.spr[SPR::MQ] = r;
if (rc_flag)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
}
void dppc_interpreter::power_srlq() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
ppc_grab_regssash(ppc_cur_instruction);
uint32_t r = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
uint32_t mask = power_rot_mask(0, 31 - rot_sh);
ppc_result_a = ((r & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
ppc_state.spr[SPR::MQ] = r;
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_slliq<RC0>();
template void dppc_interpreter::power_slliq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sllq() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
if (ppc_result_b & 0x20) {
ppc_result_a = ppc_state.spr[SPR::MQ] & (-1U << rot_sh);
} else {
ppc_result_a = ((ppc_result_d << rot_sh) | (ppc_state.spr[SPR::MQ] & ((1 << rot_sh) - 1)));
}
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sllq<RC0>();
template void dppc_interpreter::power_sllq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_slq() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
if (ppc_result_b & 0x20) {
ppc_result_a = 0;
} else {
ppc_result_a = ppc_result_d << rot_sh;
}
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_slq<RC0>();
template void dppc_interpreter::power_slq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sraiq() {
ppc_grab_regssash(ppc_cur_instruction);
uint32_t mask = (1 << rot_sh) - 1;
ppc_result_a = (int32_t)ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
if ((int32_t(ppc_result_d) < 0) && (ppc_result_d & mask)) {
ppc_state.spr[SPR::XER] |= XER::CA;
} else {
ppc_state.spr[SPR::XER] &= ~XER::CA;
}
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sraiq<RC0>();
template void dppc_interpreter::power_sraiq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sraq() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
uint32_t mask = (ppc_result_b & 0x20) ? -1 : (1 << rot_sh) - 1;
ppc_result_a = (int32_t)ppc_result_d >> ((ppc_result_b & 0x20) ? 31 : rot_sh);
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
if ((int32_t(ppc_result_d) < 0) && (ppc_result_d & mask)) {
ppc_state.spr[SPR::XER] |= XER::CA;
} else {
ppc_state.spr[SPR::XER] &= ~XER::CA;
}
ppc_state.spr[SPR::MQ] = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sraq<RC0>();
template void dppc_interpreter::power_sraq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sre() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
ppc_result_a = ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sre<RC0>();
template void dppc_interpreter::power_sre<RC1>();
template <field_rc rec>
void dppc_interpreter::power_srea() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
ppc_result_a = (int32_t)ppc_result_d >> rot_sh;
uint32_t r = ((ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh)));
uint32_t mask = -1U >> rot_sh;
if ((int32_t(ppc_result_d) < 0) && (r & ~mask)) {
ppc_state.spr[SPR::XER] |= XER::CA;
} else {
ppc_state.spr[SPR::XER] &= ~XER::CA;
}
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
ppc_state.spr[SPR::MQ] = r;
}
template void dppc_interpreter::power_srea<RC0>();
template void dppc_interpreter::power_srea<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sreq() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
uint32_t mask = -1U >> rot_sh;
ppc_result_a = (ppc_result_d >> rot_sh) | (ppc_state.spr[SPR::MQ] & ~mask);
ppc_state.spr[SPR::MQ] = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sreq<RC0>();
template void dppc_interpreter::power_sreq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_sriq() {
ppc_grab_regssash(ppc_cur_instruction);
ppc_result_a = ppc_result_d >> rot_sh;
ppc_state.spr[SPR::MQ] = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_sriq<RC0>();
template void dppc_interpreter::power_sriq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_srliq() {
ppc_grab_regssash(ppc_cur_instruction);
uint32_t r = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
unsigned mask = power_rot_mask(rot_sh, 31);
if (ppc_result_b >= 0x20) {
ppc_result_a = ((r & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
ppc_state.spr[SPR::MQ] = r;
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_srliq<RC0>();
template void dppc_interpreter::power_srliq<RC1>();
template <field_rc rec>
void dppc_interpreter::power_srlq() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
uint32_t r = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
unsigned mask = power_rot_mask(rot_sh, 31);
if (ppc_result_b & 0x20) {
ppc_result_a = (ppc_state.spr[SPR::MQ] & mask);
}
else {
} else {
ppc_result_a = ((r & mask) | (ppc_state.spr[SPR::MQ] & ~mask));
}
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
void dppc_interpreter::power_srq() {
ppc_grab_regssab();
unsigned rot_sh = ppc_result_b & 31;
template void dppc_interpreter::power_srlq<RC0>();
template void dppc_interpreter::power_srlq<RC1>();
if (ppc_result_b >= 0x20) {
template <field_rc rec>
void dppc_interpreter::power_srq() {
ppc_grab_regssab(ppc_cur_instruction);
unsigned rot_sh = ppc_result_b & 0x1F;
if (ppc_result_b & 0x20) {
ppc_result_a = 0;
} else {
ppc_result_a = ppc_result_d >> rot_sh;
}
ppc_state.spr[SPR::MQ] = ((ppc_result_d << rot_sh) | (ppc_result_d >> (32 - rot_sh)));
ppc_state.spr[SPR::MQ] = (ppc_result_d >> rot_sh) | (ppc_result_d << (32 - rot_sh));
if (rc_flag)
if (rec)
ppc_changecrf0(ppc_result_a);
ppc_store_result_rega();
ppc_store_iresult_reg(reg_a, ppc_result_a);
}
template void dppc_interpreter::power_srq<RC0>();
template void dppc_interpreter::power_srq<RC1>();

File diff suppressed because it is too large Load Diff

View File

@ -24,12 +24,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cinttypes>
#include <string>
#include <vector>
typedef struct PPCDisasmContext {
uint32_t instr_addr;
uint32_t instr_code;
std::string instr_str;
bool simplified; /* true if we should output simplified mnemonics */
std::vector<std::string> regs_in;
std::vector<std::string> regs_out;
} PPCDisasmContext;
std::string disassemble_single(PPCDisasmContext* ctx);

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-21 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -24,24 +24,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/memctrl/memctrlbase.h>
#include <endianswap.h>
#include <memaccess.h>
#include <atomic>
#include <cinttypes>
#include <functional>
#include <setjmp.h>
#include <string>
// Uncomment this to help debug the emulator further
//#define EXHAUSTIVE_DEBUG 1
// Uncomment this to have a more graceful approach to illegal opcodes
//#define ILLEGAL_OP_SAFE 1
// Uncomment this to use GCC built-in functions.
#define USE_GCC_BUILTINS 1
// Uncomment this to use Visual Studio built-in functions.
//#define USE_VS_BUILTINS 1
//#define CPU_PROFILING // enable CPU profiling
/** type of compiler used during execution */
@ -92,25 +85,36 @@ extern SetPRS ppc_state;
/** symbolic names for frequently used SPRs */
enum SPR : int {
MQ = 0,
MQ = 0, // MQ (601)
XER = 1,
RTCU_U = 4, // user RTCU
RTCL_U = 5, // user RTCL
RTCU_U = 4, // user mode RTCU (601)
RTCL_U = 5, // user mode RTCL (601)
DEC_U = 6, // user mode decrementer (601)
LR = 8,
CTR = 9,
DSISR = 18,
DAR = 19,
RTCU_S = 20, // supervisor RTCU
RTCL_S = 21, // supervisor RTCL
DEC = 22, // decrementer
RTCU_S = 20, // supervisor RTCU (601)
RTCL_S = 21, // supervisor RTCL (601)
DEC_S = 22, // supervisor decrementer
SDR1 = 25,
SRR0 = 26,
SRR1 = 27,
TBL_U = 268, // user mode TBL
TBU_U = 269, // user mode TBU
SPRG0 = 272,
SPRG1 = 273,
SPRG2 = 274,
SPRG3 = 275,
TBL_S = 284, // supervisor TBL
TBU_S = 285, // supervisor TBU
PVR = 287,
MMCR0 = 952,
PMC1 = 953,
PMC2 = 954,
SIA = 955,
MMCR1 = 956,
SDA = 959,
HID0 = 1008,
HID1 = 1009,
};
@ -122,7 +126,9 @@ enum PPC_VER : uint32_t {
MPC604 = 0x00040001,
MPC603E = 0x00060101,
MPC603EV = 0x00070101,
MPC750 = 0x00080200
MPC750 = 0x00080200,
MPC604E = 0x00090202,
MPC970MP = 0x00440100,
};
/**
@ -164,8 +170,6 @@ SUPERVISOR MODEL
536 - 543 are the Data BAT registers
**/
extern uint32_t opcode_value; // used for interpreting opcodes
extern uint64_t timebase_counter;
extern uint64_t tbr_wr_timestamp;
extern uint64_t dec_wr_timestamp;
@ -173,55 +177,14 @@ extern uint64_t rtc_timestamp;
extern uint64_t tbr_wr_value;
extern uint32_t dec_wr_value;
extern uint32_t tbr_freq_ghz;
extern uint64_t tbr_period_ns;
extern uint32_t rtc_lo, rtc_hi;
// Additional steps to prevent overflow?
extern int32_t add_result;
extern int32_t simult_result;
extern uint32_t uiadd_result;
extern uint32_t uimult_result;
extern uint32_t crf_d;
extern uint32_t crf_s;
extern uint32_t reg_s;
extern uint32_t reg_d;
extern uint32_t reg_a;
extern uint32_t reg_b;
extern uint32_t reg_c;
extern uint32_t xercon;
extern uint32_t cmp_c;
extern uint32_t crm;
extern uint32_t br_bo;
extern uint32_t br_bi;
extern uint32_t rot_sh;
extern uint32_t rot_mb;
extern uint32_t rot_me;
extern uint32_t uimm;
extern uint32_t grab_sr;
extern uint32_t grab_inb; // This is for grabbing the number of immediate bytes for loading and storing
extern uint32_t ppc_to;
extern int32_t simm;
extern int32_t adr_li;
extern int32_t br_bd;
// Used for GP calcs
extern uint32_t ppc_result_a;
extern uint32_t ppc_result_b;
extern uint32_t ppc_result_c;
extern uint32_t ppc_result_d;
// Used for FP calcs
extern uint64_t ppc_result64_a;
extern uint64_t ppc_result64_b;
extern uint64_t ppc_result64_c;
extern uint64_t ppc_result64_d;
/* Flags for controlling interpreter execution. */
enum {
EXEF_BRANCH = 1 << 0,
EXEF_EXCEPTION = 1 << 1,
EXEF_RFI = 1 << 2,
EXEF_TIMER = 1 << 7
};
enum CR_select : int32_t {
@ -229,11 +192,14 @@ enum CR_select : int32_t {
CR1_field = (0xF << 24),
};
// Define bit masks for CR0.
// To use them in other CR fields, just right shift it by 4*CR_num bits.
enum CRx_bit : uint32_t {
CR_SO = 28,
CR_EQ = 29,
CR_GT = 30,
CR_LT = 31 };
CR_SO = 1UL << 28,
CR_EQ = 1UL << 29,
CR_GT = 1UL << 30,
CR_LT = 1UL << 31
};
enum CR1_bit : uint32_t {
CR1_OX = 24,
@ -243,49 +209,71 @@ enum CR1_bit : uint32_t {
};
enum FPSCR : uint32_t {
RN = 0x3,
NI = 0x4,
XE = 0x8,
ZE = 0x10,
UE = 0x20,
OE = 0x40,
VE = 0x80,
VXCVI = 0x100,
VXSQRT = 0x200,
VXSOFT = 0x400,
FPRF = 0x1F000,
FPCC_FUNAN = 0x10000,
FPCC_NEG = 0x8000,
FPCC_POS = 0x4000,
FPCC_ZERO = 0x2000,
FPCC_FPRCD = 0x1000,
FI = 0x20000,
FR = 0x40000,
VXVC = 0x80000,
VXIMZ = 0x100000,
VXZDZ = 0x200000,
VXIDI = 0x400000,
VXISI = 0x800000,
VXSNAN = 0x1000000,
XX = 0x2000000,
ZX = 0x4000000,
UX = 0x8000000,
OX = 0x10000000,
VX = 0x20000000,
FEX = 0x40000000,
FX = 0x80000000
RN_MASK = 0x3,
NI = 1UL << 2,
XE = 1UL << 3,
ZE = 1UL << 4,
UE = 1UL << 5,
OE = 1UL << 6,
VE = 1UL << 7,
VXCVI = 1UL << 8,
VXSQRT = 1UL << 9,
VXSOFT = 1UL << 10,
FPCC_FUNAN = 1UL << 12,
FPCC_ZERO = 1UL << 13,
FPCC_POS = 1UL << 14,
FPCC_NEG = 1UL << 15,
FPCC_MASK = FPCC_NEG | FPCC_POS | FPCC_ZERO | FPCC_FUNAN,
FPRCD = 1UL << 16,
FPRF_MASK = FPRCD | FPCC_MASK,
FI = 1UL << 17,
FR = 1UL << 18,
VXVC = 1UL << 19,
VXIMZ = 1UL << 20,
VXZDZ = 1UL << 21,
VXIDI = 1UL << 22,
VXISI = 1UL << 23,
VXSNAN = 1UL << 24,
XX = 1UL << 25,
ZX = 1UL << 26,
UX = 1UL << 27,
OX = 1UL << 28,
VX = 1UL << 29,
FEX = 1UL << 30,
FX = 1UL << 31
};
enum MSR : int {
LE = 0x1, //little endian mode
RI = 0x2,
DR = 0x10,
IR = 0x20,
IP = 0x40,
FE1 = 0x100,
BE = 0x200,
SE = 0x400,
FE0 = 0x800,
ME = 0x1000,
FP = 0x2000,
PR = 0x4000,
EE = 0x8000, //external interrupt
ILE = 0x10000,
POW = 0x40000
};
enum XER : uint32_t {
CA = 1UL << 29,
OV = 1UL << 30,
SO = 1UL << 31
};
//for inf and nan checks
enum FPOP : int {
DIV = 0x12,
SUB = 0x14,
ADD = 0x15,
MUL = 0x19,
FMSUB = 0x1C,
FMADD = 0x1D,
FNMSUB = 0x1E,
FNMADD = 0x1F,
DIV = 0x12,
SUB = 0x14,
ADD = 0x15,
SQRT = 0x16,
MUL = 0x19
};
/** PowerPC exception types. */
@ -303,7 +291,7 @@ enum class Except_Type {
EXC_TRACE = 13
};
/** Programm Exception subclasses. */
/** Program Exception subclasses. */
enum Exc_Cause : uint32_t {
FPU_OFF = 1 << (31 - 11),
ILLEGAL_OP = 1 << (31 - 12),
@ -317,21 +305,38 @@ extern jmp_buf exc_env;
extern bool grab_return;
enum Po_Cause : int {
po_none,
po_starting_up,
po_shut_down,
po_shutting_down,
po_restarting,
po_restart,
po_disassemble_on,
po_disassemble_off,
po_enter_debugger,
po_entered_debugger,
po_signal_interrupt,
};
extern bool power_on;
extern Po_Cause power_off_reason;
extern bool int_pin;
extern bool dec_exception_pending;
extern bool is_601; // For PowerPC 601 Emulation
extern bool is_altivec; // For Altivec Emulation
extern bool is_64bit; // For PowerPC G5 Emulation
extern bool rc_flag; // Record flag
extern bool oe_flag; // Overflow flag
// Important Addressing Integers
extern uint32_t ppc_cur_instruction;
extern uint32_t ppc_effective_address;
extern uint32_t ppc_next_instruction_address;
inline void ppc_set_cur_instruction(const uint8_t* ptr) {
ppc_cur_instruction = READ_DWORD_BE_A(ptr);
}
// Profiling Stats
#ifdef CPU_PROFILING
extern uint64_t num_executed_instrs;
@ -341,9 +346,61 @@ extern uint64_t num_int_stores;
extern uint64_t exceptions_processed;
#endif
// instruction enums
typedef enum {
ppc_and = 1,
ppc_andc = 2,
ppc_eqv = 3,
ppc_nand = 4,
ppc_nor = 5,
ppc_or = 6,
ppc_orc = 7,
ppc_xor = 8,
} logical_fun;
typedef enum {
LK0,
LK1,
} field_lk;
typedef enum {
AA0,
AA1,
} field_aa;
typedef enum {
SHFT0,
SHFT1,
} field_shift;
typedef enum {
RIGHT0,
LEFT1,
} field_direction;
typedef enum {
RC0,
RC1,
} field_rc;
typedef enum {
OV0,
OV1,
} field_ov;
typedef enum {
CARRY0,
CARRY1,
} field_carry;
typedef enum {
NOT601,
IS601,
} field_601;
// Function prototypes
extern void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version, uint64_t tb_freq);
extern void ppc_mmu_init(uint32_t cpu_version);
extern void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version, bool include_601, uint64_t tb_freq);
extern void ppc_mmu_init();
void ppc_illegalop();
void ppc_fpu_off();
@ -353,39 +410,25 @@ void ppc_release_int();
//void ppc_opcode4();
void ppc_opcode16();
void ppc_opcode18();
void ppc_opcode19();
template <field_601 for601> void ppc_opcode19();
void ppc_opcode31();
void ppc_opcode59();
void ppc_opcode63();
void initialize_ppc_opcode_tables();
void initialize_ppc_opcode_tables(bool include_601);
extern double fp_return_double(uint32_t reg);
extern uint64_t fp_return_uint64(uint32_t reg);
extern void ppc_grab_regsda();
extern void ppc_grab_regsdb();
extern void ppc_grab_regssa();
extern void ppc_grab_regssb();
extern void ppc_grab_regsdab();
extern void ppc_grab_regssab();
extern void ppc_grab_regsdasimm();
extern void ppc_grab_regsdauimm();
extern void ppc_grab_regsasimm();
extern void ppc_grab_regssauimm();
extern void ppc_store_result_regd();
extern void ppc_store_result_rega();
void ppc_changecrf0(uint32_t set_result);
void ppc_fp_changecrf1();
void set_host_rounding_mode(uint8_t mode);
void update_fpscr(uint32_t new_fpscr);
/* Exception handlers. */
void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits);
[[noreturn]] void dbg_exception_handler(Except_Type exception_type, uint32_t srr1_bits);
void ppc_floating_point_exception();
void ppc_alignment_exception(uint32_t ea);
// MEMORY DECLARATIONS
extern MemCtrlBase* mem_ctrl_instance;
@ -394,11 +437,10 @@ extern void add_ctx_sync_action(const std::function<void()> &);
extern void do_ctx_sync(void);
// The functions used by the PowerPC processor
namespace dppc_interpreter {
extern void ppc_bcctr();
extern void ppc_bcctrl();
extern void ppc_bclr();
extern void ppc_bclrl();
template <field_lk l, field_601 for601> extern void ppc_bcctr();
template <field_lk l> extern void ppc_bclr();
extern void ppc_crand();
extern void ppc_crandc();
extern void ppc_creqv();
@ -409,72 +451,55 @@ extern void ppc_crorc();
extern void ppc_crxor();
extern void ppc_isync();
extern void ppc_add();
extern void ppc_addc();
extern void ppc_adde();
extern void ppc_addme();
extern void ppc_addze();
extern void ppc_and();
extern void ppc_andc();
template <logical_fun logical_op, field_rc rec> extern void ppc_logical();
template <field_carry carry, field_rc rec, field_ov ov> extern void ppc_add();
template <field_rc rec, field_ov ov> extern void ppc_adde();
template <field_rc rec, field_ov ov> extern void ppc_addme();
template <field_rc rec, field_ov ov> extern void ppc_addze();
extern void ppc_cmp();
extern void ppc_cmpl();
extern void ppc_cntlzw();
template <field_rc rec> extern void ppc_cntlzw();
extern void ppc_dcbf();
extern void ppc_dcbi();
extern void ppc_dcbst();
extern void ppc_dcbt();
extern void ppc_dcbtst();
extern void ppc_dcbz();
extern void ppc_divw();
extern void ppc_divwu();
template <field_rc rec, field_ov ov> extern void ppc_divw();
template <field_rc rec, field_ov ov> extern void ppc_divwu();
extern void ppc_eciwx();
extern void ppc_ecowx();
extern void ppc_eieio();
extern void ppc_eqv();
extern void ppc_extsb();
extern void ppc_extsh();
template <class T, field_rc rec>extern void ppc_exts();
extern void ppc_icbi();
extern void ppc_mftb();
extern void ppc_lhzux();
extern void ppc_lhzx();
extern void ppc_lhaux();
extern void ppc_lhax();
extern void ppc_lhbrx();
extern void ppc_lwarx();
extern void ppc_lbzux();
extern void ppc_lbzx();
extern void ppc_lwbrx();
extern void ppc_lwzux();
extern void ppc_lwzx();
template <class T> extern void ppc_lzx();
template <class T> extern void ppc_lzux();
extern void ppc_mcrxr();
extern void ppc_mfcr();
extern void ppc_mulhwu();
extern void ppc_mulhw();
extern void ppc_mullw();
extern void ppc_nand();
extern void ppc_neg();
extern void ppc_nor();
extern void ppc_or();
extern void ppc_orc();
extern void ppc_slw();
extern void ppc_srw();
extern void ppc_sraw();
extern void ppc_srawi();
extern void ppc_stbx();
extern void ppc_stbux();
template <field_rc rec> extern void ppc_mulhwu();
template <field_rc rec> extern void ppc_mulhw();
template <field_rc rec, field_ov ov> extern void ppc_mullw();
template <field_rc rec, field_ov ov> extern void ppc_neg();
template <field_direction shift, field_rc rec> extern void ppc_shift();
template <field_rc rec> extern void ppc_sraw();
template <field_rc rec> extern void ppc_srawi();
template <class T> extern void ppc_stx();
template <class T> extern void ppc_stux();
extern void ppc_stfiwx();
extern void ppc_sthx();
extern void ppc_sthux();
extern void ppc_sthbrx();
extern void ppc_stwx();
extern void ppc_stwcx();
extern void ppc_stwux();
extern void ppc_stwbrx();
extern void ppc_subf();
extern void ppc_subfc();
extern void ppc_subfe();
extern void ppc_subfme();
extern void ppc_subfze();
template <field_carry carry, field_rc rec, field_ov ov> extern void ppc_subf();
template <field_rc rec, field_ov ov> extern void ppc_subfe();
template <field_rc rec, field_ov ov> extern void ppc_subfme();
template <field_rc rec, field_ov ov> extern void ppc_subfze();
extern void ppc_sync();
extern void ppc_tlbia();
extern void ppc_tlbie();
@ -482,7 +507,6 @@ extern void ppc_tlbli();
extern void ppc_tlbld();
extern void ppc_tlbsync();
extern void ppc_tw();
extern void ppc_xor();
extern void ppc_lswi();
extern void ppc_lswx();
@ -501,58 +525,39 @@ extern void ppc_mfspr();
extern void ppc_mtmsr();
extern void ppc_mtspr();
extern void ppc_mtfsb0();
extern void ppc_mtfsb1();
template <field_rc rec> extern void ppc_mtfsb0();
template <field_rc rec> extern void ppc_mtfsb1();
extern void ppc_mcrfs();
extern void ppc_fmr();
extern void ppc_mffs();
extern void ppc_mtfsf();
extern void ppc_mtfsfi();
template <field_rc rec> extern void ppc_fmr();
template <field_601 for601, field_rc rec> extern void ppc_mffs();
template <field_rc rec> extern void ppc_mtfsf();
template <field_rc rec> extern void ppc_mtfsfi();
extern void ppc_addi();
extern void ppc_addic();
extern void ppc_addicdot();
extern void ppc_addis();
extern void ppc_andidot();
extern void ppc_andisdot();
extern void ppc_b();
extern void ppc_ba();
extern void ppc_bl();
extern void ppc_bla();
extern void ppc_bc();
extern void ppc_bca();
extern void ppc_bcl();
extern void ppc_bcla();
template <field_shift shift> extern void ppc_addi();
template <field_rc rec> extern void ppc_addic();
template <field_shift shift> extern void ppc_andirc();
template <field_lk l, field_aa a> extern void ppc_b();
template <field_lk l, field_aa a> extern void ppc_bc();
extern void ppc_cmpi();
extern void ppc_cmpli();
extern void ppc_lbz();
extern void ppc_lbzu();
template <class T> extern void ppc_lz();
template <class T> extern void ppc_lzu();
extern void ppc_lha();
extern void ppc_lhau();
extern void ppc_lhz();
extern void ppc_lhzu();
extern void ppc_lwz();
extern void ppc_lwzu();
extern void ppc_lmw();
extern void ppc_mulli();
extern void ppc_ori();
extern void ppc_oris();
template <field_shift shift> extern void ppc_ori();
extern void ppc_rfi();
extern void ppc_rlwimi();
extern void ppc_rlwinm();
extern void ppc_rlwnm();
extern void ppc_sc();
extern void ppc_stb();
extern void ppc_stbu();
extern void ppc_sth();
extern void ppc_sthu();
extern void ppc_stw();
extern void ppc_stwu();
template <class T> extern void ppc_st();
template <class T> extern void ppc_stu();
extern void ppc_stmw();
extern void ppc_subfic();
extern void ppc_twi();
extern void ppc_xori();
extern void ppc_xoris();
template <field_shift shift> extern void ppc_xori();
extern void ppc_lfs();
extern void ppc_lfsu();
@ -571,66 +576,66 @@ extern void ppc_stfdu();
extern void ppc_stfdx();
extern void ppc_stfdux();
extern void ppc_fadd();
extern void ppc_fsub();
extern void ppc_fmul();
extern void ppc_fdiv();
extern void ppc_fadds();
extern void ppc_fsubs();
extern void ppc_fmuls();
extern void ppc_fdivs();
extern void ppc_fmadd();
extern void ppc_fmsub();
extern void ppc_fnmadd();
extern void ppc_fnmsub();
extern void ppc_fmadds();
extern void ppc_fmsubs();
extern void ppc_fnmadds();
extern void ppc_fnmsubs();
extern void ppc_fabs();
extern void ppc_fnabs();
extern void ppc_fneg();
extern void ppc_fsel();
extern void ppc_fres();
extern void ppc_fsqrts();
extern void ppc_fsqrt();
extern void ppc_frsqrte();
extern void ppc_frsp();
extern void ppc_fctiw();
extern void ppc_fctiwz();
template <field_rc rec> extern void ppc_fadd();
template <field_rc rec> extern void ppc_fsub();
template <field_rc rec> extern void ppc_fmul();
template <field_rc rec> extern void ppc_fdiv();
template <field_rc rec> extern void ppc_fadds();
template <field_rc rec> extern void ppc_fsubs();
template <field_rc rec> extern void ppc_fmuls();
template <field_rc rec> extern void ppc_fdivs();
template <field_rc rec> extern void ppc_fmadd();
template <field_rc rec> extern void ppc_fmsub();
template <field_rc rec> extern void ppc_fnmadd();
template <field_rc rec> extern void ppc_fnmsub();
template <field_rc rec> extern void ppc_fmadds();
template <field_rc rec> extern void ppc_fmsubs();
template <field_rc rec> extern void ppc_fnmadds();
template <field_rc rec> extern void ppc_fnmsubs();
template <field_rc rec> extern void ppc_fabs();
template <field_rc rec> extern void ppc_fnabs();
template <field_rc rec> extern void ppc_fneg();
template <field_rc rec> extern void ppc_fsel();
template <field_rc rec> extern void ppc_fres();
template <field_rc rec> extern void ppc_fsqrts();
template <field_rc rec> extern void ppc_fsqrt();
template <field_rc rec> extern void ppc_frsqrte();
template <field_rc rec> extern void ppc_frsp();
template <field_rc rec> extern void ppc_fctiw();
template <field_rc rec> extern void ppc_fctiwz();
extern void ppc_fcmpo();
extern void ppc_fcmpu();
// Power-specific instructions
extern void power_abs();
template <field_rc rec, field_ov ov> extern void power_abs();
extern void power_clcs();
extern void power_div();
extern void power_divs();
extern void power_doz();
template <field_rc rec, field_ov ov> extern void power_div();
template <field_rc rec, field_ov ov> extern void power_divs();
template <field_rc rec, field_ov ov> extern void power_doz();
extern void power_dozi();
extern void power_lscbx();
extern void power_maskg();
extern void power_maskir();
extern void power_mul();
extern void power_nabs();
template <field_rc rec> extern void power_lscbx();
template <field_rc rec> extern void power_maskg();
template <field_rc rec> extern void power_maskir();
template <field_rc rec, field_ov ov> extern void power_mul();
template <field_rc rec, field_ov ov> extern void power_nabs();
extern void power_rlmi();
extern void power_rrib();
extern void power_sle();
extern void power_sleq();
extern void power_sliq();
extern void power_slliq();
extern void power_sllq();
extern void power_slq();
extern void power_sraiq();
extern void power_sraq();
extern void power_sre();
extern void power_srea();
extern void power_sreq();
extern void power_sriq();
extern void power_srliq();
extern void power_srlq();
extern void power_srq();
template <field_rc rec> extern void power_rrib();
template <field_rc rec> extern void power_sle();
template <field_rc rec> extern void power_sleq();
template <field_rc rec> extern void power_sliq();
template <field_rc rec> extern void power_slliq();
template <field_rc rec> extern void power_sllq();
template <field_rc rec> extern void power_slq();
template <field_rc rec> extern void power_sraiq();
template <field_rc rec> extern void power_sraq();
template <field_rc rec> extern void power_sre();
template <field_rc rec> extern void power_srea();
template <field_rc rec> extern void power_sreq();
template <field_rc rec> extern void power_sriq();
template <field_rc rec> extern void power_srliq();
template <field_rc rec> extern void power_srlq();
template <field_rc rec> extern void power_srq();
} // namespace dppc_interpreter
// AltiVec instructions

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-21 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -43,7 +43,7 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) {
break;
case Except_Type::EXC_MACHINE_CHECK:
if (!(ppc_state.msr & 0x1000)) {
if (!(ppc_state.msr & MSR::ME)) {
/* TODO: handle internal checkstop */
}
ppc_state.spr[SPR::SRR0] = ppc_state.pc & 0xFFFFFFFC;
@ -56,12 +56,16 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) {
break;
case Except_Type::EXC_ISI:
ppc_state.spr[SPR::SRR0] = ppc_next_instruction_address;
if (exec_flags) {
ppc_state.spr[SPR::SRR0] = ppc_next_instruction_address;
} else {
ppc_state.spr[SPR::SRR0] = ppc_state.pc & 0xFFFFFFFCUL;
}
ppc_next_instruction_address = 0x0400;
break;
case Except_Type::EXC_EXT_INT:
if (exec_flags & ~EXEF_TIMER) {
if (exec_flags) {
ppc_state.spr[SPR::SRR0] = ppc_next_instruction_address;
} else {
ppc_state.spr[SPR::SRR0] = (ppc_state.pc & 0xFFFFFFFCUL) + 4;
@ -85,7 +89,11 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) {
break;
case Except_Type::EXC_DECR:
ppc_state.spr[SPR::SRR0] = (ppc_state.pc & 0xFFFFFFFC) + 4;
if (exec_flags) {
ppc_state.spr[SPR::SRR0] = ppc_next_instruction_address;
} else {
ppc_state.spr[SPR::SRR0] = (ppc_state.pc & 0xFFFFFFFCUL) + 4;
}
ppc_next_instruction_address = 0x0900;
break;
@ -100,16 +108,16 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) {
break;
default:
ABORT_F("Unknown exception occured: %X\n", (unsigned)exception_type);
ABORT_F("Unknown exception occurred: %X\n", (unsigned)exception_type);
break;
}
ppc_state.spr[SPR::SRR1] = (ppc_state.msr & 0x0000FF73) | srr1_bits;
ppc_state.msr &= 0xFFFB1041;
/* copy MSR[ILE] to MSR[LE] */
ppc_state.msr = (ppc_state.msr & 0xFFFFFFFE) | ((ppc_state.msr >> 16) & 1);
ppc_state.msr = (ppc_state.msr & ~MSR::LE) | !!(ppc_state.msr & MSR::ILE);
if (ppc_state.msr & 0x40) {
if (ppc_state.msr & MSR::IP) {
ppc_next_instruction_address |= 0xFFF00000;
}
@ -123,7 +131,7 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) {
mmu_change_mode();
if (exception_type != Except_Type::EXC_EXT_INT) {
if (exception_type != Except_Type::EXC_EXT_INT && exception_type != Except_Type::EXC_DECR) {
longjmp(exc_env, 2); /* return to the main execution loop. */
}
}
@ -134,11 +142,11 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) {
switch (exception_type) {
case Except_Type::EXC_SYSTEM_RESET:
exc_descriptor = "System reset exception occured";
exc_descriptor = "System reset exception occurred";
break;
case Except_Type::EXC_MACHINE_CHECK:
exc_descriptor = "Machine check exception occured";
exc_descriptor = "Machine check exception occurred";
break;
case Except_Type::EXC_DSI:
@ -156,33 +164,151 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) {
break;
case Except_Type::EXC_EXT_INT:
exc_descriptor = "External interrupt exception occured";
exc_descriptor = "External interrupt exception occurred";
break;
case Except_Type::EXC_ALIGNMENT:
exc_descriptor = "Alignment exception occured";
exc_descriptor = "Alignment exception occurred";
break;
case Except_Type::EXC_PROGRAM:
exc_descriptor = "Program exception occured";
exc_descriptor = "Program exception occurred";
break;
case Except_Type::EXC_NO_FPU:
exc_descriptor = "Floating-Point unavailable exception occured";
exc_descriptor = "Floating-Point unavailable exception occurred";
break;
case Except_Type::EXC_DECR:
exc_descriptor = "Decrementer exception occured";
exc_descriptor = "Decrementer exception occurred";
break;
case Except_Type::EXC_SYSCALL:
exc_descriptor = "Syscall exception occured";
exc_descriptor = "Syscall exception occurred";
break;
case Except_Type::EXC_TRACE:
exc_descriptor = "Trace exception occured";
exc_descriptor = "Trace exception occurred";
break;
}
throw std::invalid_argument(exc_descriptor);
}
void ppc_floating_point_exception() {
LOG_F(ERROR, "Floating point exception at 0x%08x for instruction 0x%08x",
ppc_state.pc, ppc_cur_instruction);
// mmu_exception_handler(Except_Type::EXC_PROGRAM, Exc_Cause::FPU_EXCEPTION);
}
void ppc_alignment_exception(uint32_t ea)
{
uint32_t dsisr;
switch (ppc_cur_instruction & 0xfc000000) {
case 0x80000000: // lwz
case 0x90000000: // stw
case 0xa0000000: // lhz
case 0xa8000000: // lha
case 0xb0000000: // sth
case 0xb8000000: // lmw
case 0xc0000000: // lfs
case 0xc8000000: // lfd
case 0xd0000000: // stfs
case 0xd8000000: // stfd
case 0x84000000: // lwzu
case 0x94000000: // stwu
case 0xa4000000: // lhzu
case 0xac000000: // lhau
case 0xb4000000: // sthu
case 0xbc000000: // stmw
case 0xc4000000: // lfsu
case 0xcc000000: // lfdu
case 0xd4000000: // stfsu
case 0xdc000000: // stfdu
indirect_with_immediate_index:
dsisr = ((ppc_cur_instruction >> 12) & 0x00004000) // bit 17 — Set to bit 5 of the instruction.
| ((ppc_cur_instruction >> 17) & 0x00003c00); // bits 1821 - set to bits 14 of the instruction.
break;
case 0x7c000000:
switch (ppc_cur_instruction & 0xfc0007ff) {
case 0x7c000028: // lwarx (invalid form - bits 15-21 of DSISR are identical to those of lwz)
case 0x7c0002aa: // lwax (64-bit only)
case 0x7c00042a: // lswx
case 0x7c0004aa: // lswi
case 0x7c00052a: // stswx
case 0x7c0005aa: // stswi
case 0x7c0002ea: // lwaux (64 bit only)
case 0x7c00012c: // stwcx
case 0x7c00042c: // lwbrx
case 0x7c00052c: // stwbrx
case 0x7c00062c: // lhbrx
case 0x7c00072c: // sthbrx
case 0x7c00026c: // eciwx // MPC7451
case 0x7c00036c: // ecowx // MPC7451
case 0x7c00002e: // lwzx
case 0x7c00012e: // stwx
case 0x7c00022e: // lhzx
case 0x7c0002ae: // lhax
case 0x7c00032e: // sthx
case 0x7c00042e: // lfsx
case 0x7c0004ae: // lfdx
case 0x7c00052e: // stfsx
case 0x7c0005ae: // stfdx
case 0x7c00006e: // lwzux
case 0x7c00016e: // stwux
case 0x7c00026e: // lhzux
case 0x7c0002ee: // lhaux
case 0x7c00036e: // sthux
case 0x7c00046e: // lfsux
case 0x7c0004ee: // lfdux
case 0x7c00056e: // stfsux
case 0x7c0005ee: // stfdux
indirect_with_index:
dsisr = ((ppc_cur_instruction << 14) & 0x00018000) // bits 1516 - set to bits 2930 of the instruction.
| ((ppc_cur_instruction << 8) & 0x00004000) // bit 17 - set to bit 25 of the instruction.
| ((ppc_cur_instruction << 3) & 0x00003c00); // bits 1821 - set to bits 2124 of the instruction.
break;
case 0x7c0007ec:
if ((ppc_cur_instruction & 0xffe007ff) == 0x7c0007ec) // dcbz
goto indirect_with_index;
/* fallthrough */
default:
goto unexpected_instruction;
}
break;
default:
unexpected_instruction:
dsisr = 0;
LOG_F(ERROR, "Alignment exception from unexpected instruction 0x%08x",
ppc_cur_instruction);
}
// bits 2226 - Set to bits 610 (source or destination) of the instruction.
// Undefined for dcbz.
dsisr |= ((ppc_cur_instruction >> 16) & 0x000003e0);
if ((ppc_cur_instruction & 0xfc000000) == 0xb8000000) { // lmw
LOG_F(ERROR, "Alignment exception from instruction 0x%08x (lmw). "
"What to set DSISR bits 27-31?", ppc_cur_instruction);
// dsisr |= ((ppc_cur_instruction >> ?) & 0x0000001f); // bits 2731
}
else if ((ppc_cur_instruction & 0xfc0007ff) == 0x7c0004aa) { // lswi
LOG_F(ERROR, "Alignment exception from instruction 0x%08x (lswi). "
"What to set DSISR bits 27-31?", ppc_cur_instruction);
// dsisr |= ((ppc_cur_instruction >> ?) & 0x0000001f); // bits 2731
}
else if ((ppc_cur_instruction & 0xfc0007ff) == 0x7c00042a) { // lswx
LOG_F(ERROR, "Alignment exception from instruction 0x%08x (lswx). "
"What to set DSISR bits 27-31?", ppc_cur_instruction);
// dsisr |= ((ppc_cur_instruction >> ?) & 0x0000001f); // bits 2731
}
else {
// bits 2731 - Set to bits 1115 of the instruction (rA)
dsisr |= ((ppc_cur_instruction >> 16) & 0x0000001f);
}
ppc_state.spr[SPR::DSISR] = dsisr;
ppc_state.spr[SPR::DAR] = ea;
ppc_exception_handler(Except_Type::EXC_ALIGNMENT, 0x0);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

178
cpu/ppc/ppcmacros.h Normal file
View File

@ -0,0 +1,178 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef PPC_MACROS_H
#define PPC_MACROS_H
#include <cinttypes>
#define ppc_grab_regsdasimm(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
int32_t simm = int32_t(int16_t(opcode)); \
uint32_t ppc_result_a = ppc_state.gpr[reg_a];
#define ppc_grab_regsdauimm(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
uint32_t uimm = uint16_t(opcode); \
uint32_t ppc_result_a = ppc_state.gpr[reg_a];
# define ppc_grab_regsasimm(opcode) \
int reg_a = (opcode >> 16) & 31; \
int32_t simm = int32_t(int16_t(opcode)); \
uint32_t ppc_result_a = ppc_state.gpr[reg_a];
# define ppc_grab_regssauimm(opcode) \
int reg_s = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
uint32_t uimm = uint16_t(opcode); \
uint32_t ppc_result_d = ppc_state.gpr[reg_s]; \
uint32_t ppc_result_a = ppc_state.gpr[reg_a];
#define ppc_grab_dab(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
int reg_b = (opcode >> 11) & 31;
#define ppc_grab_regsdab(opcode) \
int reg_d = (opcode >> 21) & 31; \
uint32_t reg_a = (opcode >> 16) & 31; \
uint32_t reg_b = (opcode >> 11) & 31; \
uint32_t ppc_result_a = ppc_state.gpr[reg_a]; \
uint32_t ppc_result_b = ppc_state.gpr[reg_b];
#define ppc_grab_regssab(opcode) \
uint32_t reg_s = (opcode >> 21) & 31; \
uint32_t reg_a = (opcode >> 16) & 31; \
uint32_t reg_b = (opcode >> 11) & 31; \
uint32_t ppc_result_d = ppc_state.gpr[reg_s]; \
uint32_t ppc_result_a = ppc_state.gpr[reg_a]; \
uint32_t ppc_result_b = ppc_state.gpr[reg_b]; \
#define ppc_grab_regssa(opcode) \
uint32_t reg_s = (opcode >> 21) & 31; \
uint32_t reg_a = (opcode >> 16) & 31; \
uint32_t ppc_result_d = ppc_state.gpr[reg_s]; \
uint32_t ppc_result_a = ppc_state.gpr[reg_a];
#define ppc_grab_regssash(opcode) \
uint32_t reg_s = (opcode >> 21) & 31; \
uint32_t reg_a = (opcode >> 16) & 31; \
uint32_t rot_sh = (opcode >> 11) & 31; \
uint32_t ppc_result_d = ppc_state.gpr[reg_s]; \
uint32_t ppc_result_a = ppc_state.gpr[reg_a];
#define ppc_grab_regssb(opcode) \
uint32_t reg_s = (opcode >> 21) & 31; \
uint32_t reg_b = (opcode >> 11) & 31; \
uint32_t ppc_result_d = ppc_state.gpr[reg_s]; \
uint32_t ppc_result_b = ppc_state.gpr[reg_b]; \
#define ppc_grab_regsda(opcode) \
int reg_d = (opcode >> 21) & 31; \
uint32_t reg_a = (opcode >> 16) & 31; \
uint32_t ppc_result_a = ppc_state.gpr[reg_a];
#define ppc_grab_regsdb(opcode) \
int reg_d = (opcode >> 21) & 31; \
uint32_t reg_b = (opcode >> 11) & 31; \
uint32_t ppc_result_b = ppc_state.gpr[reg_b];
#define ppc_store_iresult_reg(reg, ppc_result)\
ppc_state.gpr[reg] = ppc_result;
#define ppc_store_sfpresult_int(reg, ppc_result64_d)\
ppc_state.fpr[(reg)].int64_r = ppc_result64_d;
#define ppc_store_sfpresult_flt(reg, ppc_dblresult64_d)\
ppc_state.fpr[(reg)].dbl64_r = ppc_dblresult64_d;
#define ppc_store_dfpresult_int(reg, ppc_result64_d)\
ppc_state.fpr[(reg)].int64_r = ppc_result64_d;
#define ppc_store_dfpresult_flt(reg, ppc_dblresult64_d)\
ppc_state.fpr[(reg)].dbl64_r = ppc_dblresult64_d;
#define ppc_grab_regsfpdb(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_b = (opcode >> 11) & 31;
#define GET_FPR(reg) \
ppc_state.fpr[(reg)].dbl64_r
#define ppc_grab_regsfpdiab(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
int reg_b = (opcode >> 11) & 31; \
uint32_t val_reg_a = ppc_state.gpr[reg_a]; \
uint32_t val_reg_b = ppc_state.gpr[reg_b];
#define ppc_grab_regsfpdia(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
uint32_t val_reg_a = ppc_state.gpr[reg_a];
#define ppc_grab_regsfpsia(opcode) \
int reg_s = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
uint32_t val_reg_a = ppc_state.gpr[reg_a];
#define ppc_grab_regsfpsiab(opcode) \
int reg_s = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
int reg_b = (opcode >> 11) & 31; \
uint32_t val_reg_a = ppc_state.gpr[reg_a]; \
uint32_t val_reg_b = ppc_state.gpr[reg_b];
#define ppc_grab_regsfpsab(opcode) \
int reg_a = (opcode >> 16) & 31; \
int reg_b = (opcode >> 11) & 31; \
int crf_d = (opcode >> 21) & 0x1C; \
double db_test_a = GET_FPR(reg_a); \
double db_test_b = GET_FPR(reg_b);
#define ppc_grab_regsfpdab(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
int reg_b = (opcode >> 11) & 31; \
double val_reg_a = GET_FPR(reg_a); \
double val_reg_b = GET_FPR(reg_b);
#define ppc_grab_regsfpdac(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
int reg_c = (opcode >> 6) & 31; \
double val_reg_a = GET_FPR(reg_a); \
double val_reg_c = GET_FPR(reg_c);
#define ppc_grab_regsfpdabc(opcode) \
int reg_d = (opcode >> 21) & 31; \
int reg_a = (opcode >> 16) & 31; \
int reg_b = (opcode >> 11) & 31; \
int reg_c = (opcode >> 6) & 31; \
double val_reg_a = GET_FPR(reg_a); \
double val_reg_b = GET_FPR(reg_b); \
double val_reg_c = GET_FPR(reg_c);
#endif // PPC_MACROS_H

File diff suppressed because it is too large Load Diff

View File

@ -26,10 +26,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/memctrl/memctrlbase.h>
#include <array>
#include <cinttypes>
#include <functional>
#include <vector>
class MMIODevice;
/* Uncomment this to exhaustive MMU integrity checks. */
//#define MMU_INTEGRITY_CHECKS
@ -70,23 +70,40 @@ typedef struct PATResult {
uint8_t pte_c_status; // status of the C bit of the PTE
} PATResult;
#define PAGE_SIZE_BITS 12
#define PAGE_SIZE (1 << PAGE_SIZE_BITS)
#define PAGE_MASK ~(PAGE_SIZE - 1)
#define TLB_SIZE 4096
#define TLB2_WAYS 4
#define TLB_INVALID_TAG 0xFFFFFFFF
/** DMA memory mapping result. */
typedef struct MapDmaResult {
uint32_t type;
bool is_writable;
// for memory regions
uint8_t* host_va;
// for MMIO regions
MMIODevice* dev_obj;
uint32_t dev_base;
} MapDmaResult;
constexpr uint32_t PPC_PAGE_SIZE_BITS = 12;
constexpr uint32_t PPC_PAGE_SIZE = (1 << PPC_PAGE_SIZE_BITS);
constexpr uint32_t PPC_PAGE_MASK = ~(PPC_PAGE_SIZE - 1);
constexpr uint32_t TLB_SIZE = 4096;
constexpr uint32_t TLB2_WAYS = 4;
constexpr uint32_t TLB_INVALID_TAG = 0xFFFFFFFF;
typedef struct TLBEntry {
uint32_t tag;
uint16_t flags;
uint16_t lru_bits;
union {
int64_t host_va_offs_r;
AddressMapEntry* reg_desc;
struct { // for memory pages
int64_t host_va_offs_r;
int64_t host_va_offs_w;
};
struct { // for MMIO pages
AddressMapEntry* rgn_desc;
int64_t dev_base_va;
};
};
int64_t host_va_offs_w;
int64_t unused;
uint32_t phys_tag;
uint32_t reserved;
} TLBEntry;
enum TLBFlags : uint16_t {
@ -102,15 +119,15 @@ enum TLBFlags : uint16_t {
extern std::function<void(uint32_t bat_reg)> ibat_update;
extern std::function<void(uint32_t bat_reg)> dbat_update;
extern uint8_t* mmu_get_dma_mem(uint32_t addr, uint32_t size, bool* is_writable);
extern MapDmaResult mmu_map_dma_mem(uint32_t addr, uint32_t size, bool allow_mmio);
extern void mmu_change_mode(void);
extern void mmu_pat_ctx_changed();
extern void tlb_flush_entry(uint32_t ea);
extern void ppc_set_cur_instruction(const uint8_t* ptr);
extern uint64_t mem_read_dbg(uint32_t virt_addr, uint32_t size);
uint8_t *mmu_translate_imem(uint32_t vaddr);
uint8_t *mmu_translate_imem(uint32_t vaddr, uint32_t *paddr = nullptr);
bool mmu_translate_dbg(uint32_t guest_va, uint32_t &guest_pa);
template <class T>
extern T mmu_read_vmem(uint32_t guest_va);

File diff suppressed because it is too large Load Diff

260
cpu/ppc/test/genppctests.py Normal file → Executable file
View File

@ -1,3 +1,5 @@
import re
def gen_ppc_opcode(opc_str, imm):
if opc_str == "ADD":
return (0x1F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x10A << 1)
@ -100,53 +102,53 @@ def gen_ppc_opcode(opc_str, imm):
elif opc_str == "EXTSH.":
return (0x1F << 26) + (3 << 21) + (3 << 16) + (0x39A << 1) + 1
elif opc_str == "FABS":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x108 << 1)
return (0x3F << 26) + (3 << 21) + (0 << 16) + (4 << 11) + (0x108 << 1)
elif opc_str == "FABS.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x108 << 1) + 1
return (0x3F << 26) + (3 << 21) + (0 << 16) + (4 << 11) + (0x108 << 1) + 1
elif opc_str == "FADD":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x15 << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x15 << 1)
elif opc_str == "FADD.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x15 << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x15 << 1) + 1
elif opc_str == "FADDS":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x15 << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x15 << 1)
elif opc_str == "FADDS.":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x15 << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x15 << 1) + 1
elif opc_str == "FCMPO":
return (0x3F << 26)+ (4 << 21) + (3 << 16) + (4 << 11) + (0x20 << 1)
return (0x3F << 26) + (4 << 21) + (4 << 16) + (5 << 11) + (0x20 << 1)
elif opc_str == "FCMPU":
return (0x3F << 26)+ (4 << 21) + (3 << 16) + (4 << 11)
return (0x3F << 26) + (4 << 21) + (4 << 16) + (5 << 11)
elif opc_str == "FCTIW":
return (0x3B << 26) + (3 << 16) + (4 << 11) + (0xE << 1)
return (0x3F << 26) + (0 << 16) + (4 << 11) + (0xE << 1)
elif opc_str == "FCTIW.":
return (0x3B << 26) + (3 << 16) + (4 << 11) + (0xE << 1) + 1
return (0x3F << 26) + (0 << 16) + (4 << 11) + (0xE << 1) + 1
elif opc_str == "FCTIWZ":
return (0x3B << 26) + (3 << 16) + (4 << 11) + (0xF << 1)
return (0x3F << 26) + (0 << 16) + (4 << 11) + (0xF << 1)
elif opc_str == "FCTIWZ.":
return (0x3B << 26) + (3 << 16) + (4 << 11) + (0xF << 1) + 1
return (0x3F << 26) + (0 << 16) + (4 << 11) + (0xF << 1) + 1
elif opc_str == "FDIV":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x12 << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x12 << 1)
elif opc_str == "FDIV.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x12 << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x12 << 1) + 1
elif opc_str == "FDIVS":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x12 << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x12 << 1)
elif opc_str == "FDIVS.":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x12 << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x12 << 1) + 1
elif opc_str == "FMADD":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1D << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1D << 1)
elif opc_str == "FMADD.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1D << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1D << 1) + 1
elif opc_str == "FMADDS":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1D << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1D << 1)
elif opc_str == "FMADDS.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1D << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1D << 1) + 1
elif opc_str == "FMUL":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (5 << 6) + (0x19 << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (6 << 6) + (0x19 << 1)
elif opc_str == "FMUL.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (5 << 6) + (0x19 << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (6 << 6) + (0x19 << 1) + 1
elif opc_str == "FMULS":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (5 << 6) + (0x19 << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (6 << 6) + (0x19 << 1)
elif opc_str == "FMULS.":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (5 << 6) + (0x19 << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (6 << 6) + (0x19 << 1) + 1
elif opc_str == "FNABS":
return (0x3F << 26) + (3 << 21) + (4 << 11) + (0x88 << 1)
elif opc_str == "FNABS.":
@ -155,42 +157,40 @@ def gen_ppc_opcode(opc_str, imm):
return (0x3F << 26) + (3 << 21) + (4 << 11) + (0x28 << 1)
elif opc_str == "FNEG.":
return (0x3F << 26) + (3 << 21) + (4 << 11) + (0x28 << 1) + 1
elif opc_str == "FMULS.":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (4 << 6) + (0x28 << 1) + 1
elif opc_str == "FMSUB":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1C << 1)
elif opc_str == "FMSUB.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1C << 1) + 1
elif opc_str == "FMSUBS":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1C << 1)
elif opc_str == "FMSUBS.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1C << 1) + 1
elif opc_str == "FNMADD":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1F << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1F << 1)
elif opc_str == "FNMADD.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1F << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1F << 1) + 1
elif opc_str == "FNMADDS":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1F << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1F << 1)
elif opc_str == "FNMADDS.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1F << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1F << 1) + 1
elif opc_str == "FNMSUB":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1E << 1)
elif opc_str == "FNMSUB.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1E << 1) + 1
elif opc_str == "FNMSUBS":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1E << 1)
elif opc_str == "FNMSUBS.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (5 << 6) + (0x1C << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (6 << 6) + (0x1E << 1) + 1
elif opc_str == "FRES":
return (0x3B << 26) + (3 << 21) + (4 << 11) + (0x15 << 1)
return (0x3B << 26) + (3 << 21) + (4 << 11) + (0x18 << 1)
elif opc_str == "FSUB":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x14 << 1)
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x14 << 1)
elif opc_str == "FSUB.":
return (0x3F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x14 << 1) + 1
return (0x3F << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x14 << 1) + 1
elif opc_str == "FSUBS":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x14 << 1)
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x14 << 1)
elif opc_str == "FSUBS.":
return (0x3B << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x14 << 1) + 1
return (0x3B << 26) + (3 << 21) + (4 << 16) + (5 << 11) + (0x14 << 1) + 1
elif opc_str == "MULHW":
return (0x1F << 26) + (3 << 21) + (3 << 16) + (4 << 11) + (0x4B << 1)
elif opc_str == "MULHW.":
@ -403,6 +403,8 @@ if __name__ == "__main__":
out_file.write("\n")
floatRE = re.compile('^([\w]+\.?) +(?:\((\w+)\) )?:: (?:(fr[ABCD]) +([^ ]+) \| )?(?:(fr[ABCD]) +([^ ]+) \| )?(?:(fr[ABCD]) +([^ ]+) \| )?(?:(fr[ABCD]) +([^ ]+) \| )?FPSCR: (0x\w+) \| CR: (0x\w+)$')
with open("ppcfloattest.txt", "r") as in_file:
with open("ppcfloattests.csv", "w") as out_file:
lineno = 0
@ -410,153 +412,37 @@ if __name__ == "__main__":
lineno += 1
line = line.strip()
opcode = (line[0:8]).rstrip().upper()
out_file.write(opcode+ ",")
out_file.write("0x{:X}".format(gen_ppc_opcode(opcode, 0)))
p = floatRE.match(line)
if p:
opcode = p.group(1).upper();
out_file.write(opcode+ ",")
out_file.write("0x{:X}".format(gen_ppc_opcode(opcode, 0)))
pos = 10
checkstring = ''
while pos < len(line):
if (line[pos].isalnum()):
checkstring += line[pos]
if ("RTN" in checkstring):
if (line[pos+1:pos+2] == "I"):
out_file.write(",round=RNI")
checkstring = ''
else:
out_file.write(",round=RTN")
checkstring = ''
pos += 1
elif ("RTZ" in checkstring):
if p.group(2) == "RTNI":
out_file.write(",round=RNI")
elif p.group(2) == "RTN":
out_file.write(",round=RTN")
elif p.group(2) == "RTZ":
out_file.write(",round=RTZ")
checkstring = ''
pos += 1
elif ("RTPI" in checkstring):
elif p.group(2) == "RTPI":
out_file.write(",round=RPI")
checkstring = ''
pos += 1
elif ("VE" in checkstring):
elif p.group(2) == "VE":
out_file.write(",round=VEN")
checkstring = ''
pos += 1
elif ("frD" in checkstring):
out_file.write(",frD=0x" + line[pos+4:pos+20])
checkstring = ''
elif ("frA" in checkstring): #sloppy temp code
check2 = line[pos+1:pos+10]
if ("-inf" in check2):
out_file.write(",frA=-inf")
elif ("inf" in check2):
out_file.write(",frA=inf")
pos += 1
elif ("nan" in check2):
out_file.write(",frA=nan")
pos += 1
elif ("-0." or "-2." or "-4." or \
"-6." or "-8." or "-7." or\
"-5." or "-3." or "-1." or \
"-9." in check2):
get_pos = line[pos+2:pos+16].strip("|")
out_file.write(",frA=" + get_pos.strip())
elif ("0." or "2." or "4." or \
"6." or "8." or "7." or\
"5." or "3." or "1." or \
"9." in check2):
get_pos = line[pos+2:pos+15]
out_file.write(",frA=" + get_pos.strip())
checkstring = ''
elif ("frB" in checkstring): #sloppy temp code
check2 = line[pos+1:pos+10]
if ("-inf" in check2):
out_file.write(",frB=-inf")
elif ("inf" in check2):
out_file.write(",frB=inf")
pos += 1
elif ("nan" in check2):
out_file.write(",frB=nan")
pos += 1
elif ("-0." or "-2." or "-4." or \
"-6." or "-8." or "-7." or\
"-5." or "-3." or "-1." or \
"-9." in check2):
get_pos = line[pos+2:pos+16].strip("|")
out_file.write(",frB=" + get_pos.strip())
elif ("0." or "2." or "4." or \
"6." or "8." or "7." or\
"5." or "3." or "1." or \
"9." in check2):
get_pos = line[pos+2:pos+15]
out_file.write(",frB=" + get_pos.strip())
checkstring = ''
elif ("frC" in checkstring): #sloppy temp code
check2 = line[pos+1:pos+10]
if ("-inf" in check2):
out_file.write(",frC=-inf")
elif ("inf" in check2):
out_file.write(",frC=inf")
pos += 1
elif ("nan" in check2):
out_file.write(",frC=nan")
pos += 1
elif ("-0." or "-2." or "-4." or \
"-6." or "-8." or "-7." or\
"-5." or "-3." or "-1." or \
"-9." in check2):
get_pos = line[pos+2:pos+16].strip("|")
out_file.write(",frC=" + get_pos.strip())
elif ("0." or "2." or "4." or \
"6." or "8." or "7." or\
"5." or "3." or "1." or \
"9." in check2):
get_pos = line[pos+2:pos+15]
out_file.write(",frC=" + get_pos.strip())
checkstring = ''
elif ("FPSCR" in checkstring):
out_file.write(",FPSCR=" + line[pos+3:pos+13])
checkstring = ''
elif ("CR" in checkstring):
out_file.write(",CR=0x0" + line[pos+6:pos+14])
checkstring = ''
pos += 1
elif p.group(2) != None:
print("Warning: line: [%d] unknown round \"%s\"\n" % (lineno, p.group(2)))
# reg_id = line[pos:pos+4]
# if reg_id.startswith("frD"):
# out_file.write(",frD=" + line[pos+4:pos+22])
# pos += 24
# elif reg_id.startswith("frA"):
# out_file.write(",frA=" + line[pos+4:pos+14])
# pos += 16
# elif reg_id.startswith("frB"):
# out_file.write(",frB=" + line[pos+4:pos+14])
# pos += 16
# elif reg_id.startswith("frC"):
# out_file.write(",frC=" + line[pos+4:pos+14])
# pos += 16
# elif reg_id.startswith("FPSCR:"):
# out_file.write(",FPSCR=" + line[pos+7:pos+17])
# pos += 19
# elif reg_id.startswith("CR:"):
# out_file.write(",CR=" + line[pos+4:pos+14])
# pos += 17
# elif reg_id.startswith("VE)"):
# out_file.write("round=VEN" + line[pos+4:pos+20])
# pos += 17
# elif reg_id.startswith("TN)"):
# out_file.write("round=RTN" + line[pos+4:pos+20])
# pos += 17
# elif reg_id.startswith("TZ)"):
# out_file.write("round=RTZ" + line[pos+4:pos+20])
# pos += 17
# elif reg_id.startswith("NI)"):
# out_file.write("round=RNI" + line[pos+4:pos+20])
# pos += 17
# elif reg_id.startswith("PI)"):
# out_file.write("round=RPI" + line[pos+4:pos+20])
# pos += 17
# else:
# out_file.write("Unknown reg ID" + reg_id)
# break
if p.group(3) != None:
out_file.write(",%s=%s" % (p.group(3), p.group(4)))
if p.group(5) != None:
out_file.write(",%s=%s" % (p.group(5), p.group(6)))
if p.group(7) != None:
out_file.write(",%s=%s" % (p.group(7), p.group(8)))
if p.group(9) != None:
out_file.write(",%s=%s" % (p.group(9), p.group(10)))
out_file.write("\n")
out_file.write(",FPSCR=%s" % p.group(11))
out_file.write(",CR=%s" % p.group(12))
out_file.write("\n")
else:
print("Warning: line: [%d] \"%s\"\n" % (lineno, line))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-21 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -21,18 +21,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "../ppcdisasm.h"
#include "../ppcemu.h"
#include <cfenv>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
int ntested; /* number of tested instructions */
int nfailed; /* number of failed instructions */
int ntested; // number of tested instructions
int nfailed; // number of failed instructions
void xer_ov_test(string mnem, uint32_t opcode) {
ppc_state.gpr[3] = 2;
@ -111,7 +113,7 @@ static void read_test_data() {
continue;
}
opcode = stoul(tokens[1], NULL, 16);
opcode = (uint32_t)stoul(tokens[1], NULL, 16);
dest = 0;
src1 = 0;
@ -121,15 +123,15 @@ static void read_test_data() {
for (i = 2; i < tokens.size(); i++) {
if (tokens[i].rfind("rD=", 0) == 0) {
dest = stoul(tokens[i].substr(3), NULL, 16);
dest = (uint32_t)stoul(tokens[i].substr(3), NULL, 16);
} else if (tokens[i].rfind("rA=", 0) == 0) {
src1 = stoul(tokens[i].substr(3), NULL, 16);
src1 = (uint32_t)stoul(tokens[i].substr(3), NULL, 16);
} else if (tokens[i].rfind("rB=", 0) == 0) {
src2 = stoul(tokens[i].substr(3), NULL, 16);
src2 = (uint32_t)stoul(tokens[i].substr(3), NULL, 16);
} else if (tokens[i].rfind("XER=", 0) == 0) {
check_xer = stoul(tokens[i].substr(4), NULL, 16);
check_xer = (uint32_t)stoul(tokens[i].substr(4), NULL, 16);
} else if (tokens[i].rfind("CR=", 0) == 0) {
check_cr = stoul(tokens[i].substr(3), NULL, 16);
check_cr = (uint32_t)stoul(tokens[i].substr(3), NULL, 16);
} else {
cout << "Unknown parameter " << tokens[i] << " in line " << lineno << ". Exiting..."
<< endl;
@ -164,14 +166,38 @@ static void read_test_data() {
}
}
double double_from_string(string str) {
if (str == "snan")
return std::numeric_limits<double>::signaling_NaN();
if (str == "qnan")
return std::numeric_limits<double>::quiet_NaN();
if (str == "-FLT_MAX")
return -std::numeric_limits<float>::max();
if (str == "-FLT_MIN")
return -std::numeric_limits<float>::min();
if (str == "-DBL_MAX")
return -std::numeric_limits<double>::max();
if (str == "-DBL_MIN")
return -std::numeric_limits<double>::min();
if (str == "FLT_MIN")
return std::numeric_limits<float>::min();
if (str == "FLT_MAX")
return std::numeric_limits<float>::max();
if (str == "DBL_MIN")
return std::numeric_limits<double>::min();
if (str == "DBL_MAX")
return std::numeric_limits<double>::max();
return stod(str, NULL);
}
static void read_test_float_data() {
string line, token;
int i, lineno;
uint32_t opcode, dest, src1, src2, check_xer, check_cr, check_fpscr;
uint64_t dest_64, src1_64, src2_64;
uint32_t opcode, src1, src2, check_cr, check_fpscr;
uint64_t dest_64;
//float sfp_dest, sfp_src1, sfp_src2, sfp_src3;
double dfp_dest, dfp_src1, dfp_src2, dfp_src3;
double dfp_src1, dfp_src2, dfp_src3;
string rounding_mode;
ifstream tf2stream("ppcfloattests.csv");
@ -186,7 +212,7 @@ static void read_test_float_data() {
lineno++;
if (line.empty() || !line.rfind("#", 0))
continue; /* skip empty/comment lines */
continue; // skip empty/comment lines
istringstream lnstream(line);
@ -201,54 +227,35 @@ static void read_test_float_data() {
continue;
}
opcode = stoul(tokens[1], NULL, 16);
opcode = (uint32_t)stoul(tokens[1], NULL, 16);
src1 = 0;
src2 = 0;
check_xer = 0;
check_cr = 0;
check_fpscr = 0;
//sfp_dest = 0.0;
//sfp_src1 = 0.0;
//sfp_src2 = 0.0;
//sfp_src3 = 0.0;
dfp_dest = 0.0;
dfp_src1 = 0.0;
dfp_src2 = 0.0;
dfp_src3 = 0.0;
dest_64 = 0;
// switch to default rounding
fesetround(FE_TONEAREST);
for (i = 2; i < tokens.size(); i++) {
if (tokens[i].rfind("frD=", 0) == 0) {
dest_64 = stoull(tokens[i].substr(4), NULL, 16);
} else if (tokens[i].rfind("frA=", 0) == 0) {
dfp_src1 = stod(tokens[i].substr(4), NULL);
dfp_src1 = double_from_string(tokens[i].substr(4));
} else if (tokens[i].rfind("frB=", 0) == 0) {
dfp_src2 = stod(tokens[i].substr(4), NULL);
dfp_src2 = double_from_string(tokens[i].substr(4));
} else if (tokens[i].rfind("frC=", 0) == 0) {
dfp_src3 = stod(tokens[i].substr(4), NULL);
dfp_src3 = double_from_string(tokens[i].substr(4));
} else if (tokens[i].rfind("round=", 0) == 0) {
rounding_mode = tokens[i].substr(6, 3);
ppc_state.fpscr = 0;
if (rounding_mode.compare("RTN") == 0) {
ppc_state.fpscr = 0x0;
} else if (rounding_mode.compare("RTZ") == 0) {
ppc_state.fpscr = 0x1;
} else if (rounding_mode.compare("RPI") == 0) {
ppc_state.fpscr = 0x2;
} else if (rounding_mode.compare("RNI") == 0) {
ppc_state.fpscr = 0x3;
} else if (rounding_mode.compare("VEN") == 0) {
ppc_state.fpscr = FPSCR::VE;
} else {
cout << "ILLEGAL ROUNDING METHOD: " << tokens[i] << " in line " << lineno
<< ". Exiting..." << endl;
exit(0);
}
} else if (tokens[i].rfind("FPSCR=", 0) == 0) {
check_fpscr = stoul(tokens[i].substr(6), NULL, 16);
check_fpscr = (uint32_t)stoul(tokens[i].substr(6), NULL, 16);
} else if (tokens[i].rfind("CR=", 0) == 0) {
check_cr = stoul(tokens[i].substr(3), NULL, 16);
check_cr = (uint32_t)stoul(tokens[i].substr(3), NULL, 16);
} else {
cout << "Unknown parameter " << tokens[i] << " in line " << lineno << ". Exiting..."
<< endl;
@ -256,14 +263,31 @@ static void read_test_float_data() {
}
}
if (rounding_mode.compare("RTN") == 0) {
update_fpscr(0);
} else if (rounding_mode.compare("RTZ") == 0) {
update_fpscr(1);
} else if (rounding_mode.compare("RPI") == 0) {
update_fpscr(2);
} else if (rounding_mode.compare("RNI") == 0) {
update_fpscr(3);
} else if (rounding_mode.compare("VEN") == 0) {
update_fpscr(FPSCR::VE);
} else {
cout << "ILLEGAL ROUNDING METHOD: " << tokens[i] << " in line " << lineno
<< ". Exiting..." << endl;
exit(0);
}
ppc_state.gpr[3] = src1;
ppc_state.gpr[4] = src2;
ppc_state.fpr[3].dbl64_r = dfp_src1;
ppc_state.fpr[4].dbl64_r = dfp_src2;
ppc_state.fpr[5].dbl64_r = dfp_src3;
ppc_state.fpr[3].dbl64_r = 0;
ppc_state.fpr[4].dbl64_r = dfp_src1;
ppc_state.fpr[5].dbl64_r = dfp_src2;
ppc_state.fpr[6].dbl64_r = dfp_src3;
ppc_state.cr = 0;
ppc_state.cr = 0;
ppc_cur_instruction = opcode;
@ -271,6 +295,9 @@ static void read_test_float_data() {
ntested++;
// switch to default rounding
fesetround(FE_TONEAREST);
if ((tokens[0].rfind("FCMP") && (ppc_state.fpr[3].int64_r != dest_64)) ||
(ppc_state.fpscr != check_fpscr) ||
(ppc_state.cr != check_cr)) {
@ -288,7 +315,7 @@ static void read_test_float_data() {
}
int main() {
initialize_ppc_opcode_tables(); //kludge
initialize_ppc_opcode_tables(true); //kludge
cout << "Running DingusPPC emulator tests..." << endl << endl;

View File

@ -65,15 +65,15 @@ static vector<PPCDisasmContext> read_test_data() {
}
ctx = {0};
ctx.instr_addr = stoul(tokens[0], NULL, 16);
ctx.instr_code = stoul(tokens[1], NULL, 16);
ctx.instr_addr = (uint32_t)stoul(tokens[0], NULL, 16);
ctx.instr_code = (uint32_t)stoul(tokens[1], NULL, 16);
/* build disassembly string out of comma-separated parts */
ostringstream idisasm;
/* put instruction mnemonic padded with trailing spaces */
idisasm << tokens[2];
idisasm << setw(8 - tokens[2].length()) << "";
idisasm << setw(8 - (int)tokens[2].length()) << " ";
/* now add comma-separated operands */
for (i = 3; i < tokens.size(); i++) {

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -77,6 +77,7 @@ static void show_help() {
cout << " until X -- execute until address X is reached" << endl;
cout << " go -- exit debugger and continue emulator execution" << endl;
cout << " regs -- dump content of the GRPs" << endl;
cout << " mregs -- dump content of the MMU registers" << endl;
cout << " set R=X -- assign value X to register R" << endl;
cout << " if R=loglevel, set the internal" << endl;
cout << " log level to X whose range is -2...9" << endl;
@ -107,7 +108,7 @@ static void show_help() {
#ifdef ENABLE_68K_DEBUGGER
static void disasm_68k(uint32_t count, uint32_t address) {
static uint32_t disasm_68k(uint32_t count, uint32_t address) {
csh cs_handle;
uint8_t code[10];
size_t code_size;
@ -115,12 +116,12 @@ static void disasm_68k(uint32_t count, uint32_t address) {
if (cs_open(CS_ARCH_M68K, CS_MODE_M68K_040, &cs_handle) != CS_ERR_OK) {
cout << "Capstone initialization error" << endl;
return;
return address;
}
cs_insn* insn = cs_malloc(cs_handle);
for (; count > 0; count--) {
for (; power_on && count > 0; count--) {
/* prefetch opcode bytes (a 68k instruction can occupy 2...10 bytes) */
for (int i = 0; i < sizeof(code); i++) {
code[i] = mem_read_dbg(address + i, 1);
@ -153,6 +154,7 @@ print_bin:
cs_free(insn, 1);
cs_close(&cs_handle);
return address;
}
/* emulator opcode table size --> 512 KB */
@ -179,10 +181,10 @@ void exec_single_68k()
//printf("cur_instr_tab_entry = %X\n", cur_instr_tab_entry);
/* because the first two PPC instructions for each emulated 68k once
/* because the first two PPC instructions for each emulated 68k opcode
are resided in the emulator opcode table, we need to execute them
one by one until the execution goes outside the opcode table. */
while (ppc_pc >= cur_instr_tab_entry && ppc_pc < cur_instr_tab_entry + 8) {
while (power_on && ppc_pc >= cur_instr_tab_entry && ppc_pc < cur_instr_tab_entry + 8) {
ppc_exec_single();
ppc_pc = get_reg(string("PC"));
}
@ -199,8 +201,8 @@ void exec_until_68k(uint32_t target_addr)
emu_table_virt = get_reg(string("R29")) & 0xFFF80000;
while (target_addr != (get_reg(string("R24")) - 2)) {
ppc_pc = get_reg(string("PC"));
while (power_on && target_addr != (get_reg(string("R24")) - 2)) {
ppc_pc = static_cast<uint32_t>(get_reg(string("PC")));
if (ppc_pc >= emu_table_virt && ppc_pc < (emu_table_virt + EMU_68K_TABLE_SIZE - 1)) {
ppc_exec_single();
@ -330,17 +332,19 @@ static void dump_mem(string& params) {
cout << endl << endl;
}
static void disasm(uint32_t count, uint32_t address) {
static uint32_t disasm(uint32_t count, uint32_t address) {
PPCDisasmContext ctx;
ctx.instr_addr = address;
ctx.simplified = true;
for (int i = 0; i < count; i++) {
for (int i = 0; power_on && i < count; i++) {
ctx.instr_code = READ_DWORD_BE_A(mmu_translate_imem(ctx.instr_addr));
cout << uppercase << hex << ctx.instr_addr;
cout << " " << disassemble_single(&ctx) << endl;
cout << setfill('0') << setw(8) << right << uppercase << hex << ctx.instr_addr;
cout << ": " << setfill('0') << setw(8) << right << uppercase << hex << ctx.instr_code;
cout << " " << disassemble_single(&ctx) << setfill(' ') << left << endl;
}
return ctx.instr_addr;
}
static void print_gprs() {
@ -351,7 +355,7 @@ static void print_gprs() {
reg_name = "R" + to_string(i);
cout << right << std::setw(3) << setfill(' ') << reg_name << " : " <<
setw(8) << setfill('0') << right << uppercase << hex << get_reg(reg_name);
setw(8) << setfill('0') << right << uppercase << hex << get_reg(reg_name) << setfill(' ');
if (i & 1) {
cout << endl;
@ -364,7 +368,7 @@ static void print_gprs() {
for (auto &spr : sprs) {
cout << right << std::setw(3) << setfill(' ') << spr << " : " <<
setw(8) << setfill('0') << uppercase << hex << get_reg(spr);
setw(8) << setfill('0') << uppercase << hex << get_reg(spr) << setfill(' ');
if (i & 1) {
cout << endl;
@ -376,11 +380,43 @@ static void print_gprs() {
}
}
extern bool is_601;
static void print_mmu_regs()
{
printf("MSR : 0x%08X\n", ppc_state.msr);
printf("\nBAT registers:\n");
for (int i = 0; i < 4; i++) {
printf("IBAT%dU : 0x%08X, IBAT%dL : 0x%08X\n",
i, ppc_state.spr[528+i*2],
i, ppc_state.spr[529+i*2]);
}
if (!is_601) {
for (int i = 0; i < 4; i++) {
printf("DBAT%dU : 0x%08X, DBAT%dL : 0x%08X\n",
i, ppc_state.spr[536+i*2],
i, ppc_state.spr[537+i*2]);
}
}
printf("\n");
printf("SDR1 : 0x%08X\n", ppc_state.spr[SPR::SDR1]);
printf("\nSegment registers:\n");
for (int i = 0; i < 16; i++) {
printf("SR%-2d : 0x%08X\n", i, ppc_state.sr[i]);
}
}
#ifndef _WIN32
#include <signal.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
static struct sigaction old_act_sigint, new_act_sigint;
static struct sigaction old_act_sigterm, new_act_sigterm;
@ -409,35 +445,103 @@ void enter_debugger() {
std::stringstream ss;
int log_level, context;
size_t separator_pos;
bool did_message = false;
uint32_t next_addr_ppc;
uint32_t next_addr_68k;
bool cmd_repeat;
unique_ptr<OfConfigUtils> ofnvram = unique_ptr<OfConfigUtils>(new OfConfigUtils);
context = 1; /* start with the PowerPC context */
cout << "Welcome to the DingusPPC command line debugger." << endl;
cout << "Please enter a command or 'help'." << endl << endl;
#ifndef _WIN32
struct winsize win_size_previous;
ioctl(0, TIOCGWINSZ, &win_size_previous);
#endif
while (1) {
cout << "dingusdbg> ";
if (power_off_reason == po_shut_down) {
power_off_reason = po_shutting_down;
break;
}
if (power_off_reason == po_restart) {
power_off_reason = po_restarting;
break;
}
power_on = true;
/* reset string stream */
ss.str("");
ss.clear();
if (power_off_reason == po_starting_up) {
power_off_reason = po_none;
cmd = "go";
}
else if (power_off_reason == po_disassemble_on) {
inp = "si 1000000000";
ss.str("");
ss.clear();
ss.str(inp);
ss >> cmd;
}
else if (power_off_reason == po_disassemble_off) {
power_off_reason = po_none;
cmd = "go";
}
else
{
if (power_off_reason == po_enter_debugger) {
power_off_reason = po_entered_debugger;
}
if (!did_message) {
cout << "Welcome to the DingusPPC command line debugger." << endl;
cout << "Please enter a command or 'help'." << endl << endl;
did_message = true;
}
cmd = "";
getline(cin, inp, '\n');
ss.str(inp);
ss >> cmd;
printf("%08X: dingusdbg> ", ppc_state.pc);
if (cmd.empty() && !last_cmd.empty()) {
while (power_on) {
/* reset string stream */
ss.str("");
ss.clear();
cmd = "";
std::cin.clear();
getline(cin, inp, '\n');
ss.str(inp);
ss >> cmd;
#ifndef _WIN32
struct winsize win_size_current;
ioctl(0, TIOCGWINSZ, &win_size_current);
if (win_size_current.ws_col != win_size_previous.ws_col || win_size_current.ws_row != win_size_previous.ws_row) {
win_size_previous = win_size_current;
if (cmd.empty()) {
continue;
}
}
#endif
break;
}
}
if (power_off_reason == po_signal_interrupt) {
power_off_reason = po_enter_debugger;
// ignore command if interrupt happens because the input line is probably incomplete.
last_cmd = "";
continue;
}
cmd_repeat = cmd.empty() && !last_cmd.empty();
if (cmd_repeat) {
cmd = last_cmd;
cout << cmd << endl;
}
if (cmd == "help") {
cmd = "";
show_help();
} else if (cmd == "quit") {
cmd = "";
break;
} else if (cmd == "profile") {
cmd = "";
ss >> sub_cmd;
ss >> profile_name;
@ -450,6 +554,7 @@ void enter_debugger() {
}
}
else if (cmd == "regs") {
cmd = "";
if (context == 2) {
#ifdef ENABLE_68K_DEBUGGER
print_68k_regs();
@ -457,6 +562,9 @@ void enter_debugger() {
} else {
print_gprs();
}
} else if (cmd == "mregs") {
cmd = "";
print_mmu_regs();
} else if (cmd == "set") {
ss >> expr_str;
@ -529,8 +637,9 @@ void enter_debugger() {
cout << exc.what() << endl;
}
} else if (cmd == "go") {
cmd = "";
power_on = true;
ppc_exec(); // won't return!
ppc_exec();
} else if (cmd == "disas" || cmd == "da") {
expr_str = "";
ss >> expr_str;
@ -569,10 +678,10 @@ void enter_debugger() {
try {
if (context == 2) {
#ifdef ENABLE_68K_DEBUGGER
disasm_68k(inst_grab, addr);
next_addr_68k = disasm_68k(inst_grab, addr);
#endif
} else {
disasm(inst_grab, addr);
next_addr_ppc = disasm(inst_grab, addr);
}
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
@ -582,14 +691,24 @@ void enter_debugger() {
try {
if (context == 2) {
#ifdef ENABLE_68K_DEBUGGER
if (cmd_repeat) {
addr = next_addr_68k;
}
else {
addr_str = "R24";
addr = get_reg(addr_str);
disasm_68k(1, addr - 2);
addr = get_reg(addr_str) - 2;
}
next_addr_68k = disasm_68k(1, addr);
#endif
} else {
if (cmd_repeat) {
addr = next_addr_ppc;
}
else {
addr_str = "PC";
addr = (uint32_t)get_reg(addr_str);
disasm(1, addr);
}
next_addr_ppc = disasm(1, addr);
}
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
@ -601,6 +720,7 @@ void enter_debugger() {
dump_mem(expr_str);
#ifdef ENABLE_68K_DEBUGGER
} else if (cmd == "context") {
cmd = "";
expr_str = "";
ss >> expr_str;
if (expr_str == "ppc" || expr_str == "PPC") {
@ -612,18 +732,22 @@ void enter_debugger() {
}
#endif
} else if (cmd == "printenv") {
cmd = "";
if (ofnvram->init())
continue;
ofnvram->printenv();
} else if (cmd == "setenv") {
cmd = "";
string var_name, value;
ss >> var_name;
ss >> value;
std::istream::sentry se(ss); // skip white space
getline(ss, value); // get everything up to eol
if (ofnvram->init())
continue;
ofnvram->setenv(var_name, value);
#ifndef _WIN32
} else if (cmd == "nvedit") {
cmd = "";
cout << "===== press CNTRL-C to save =====" << endl;
// save original terminal state
@ -673,6 +797,7 @@ void enter_debugger() {
#endif
#ifdef DEBUG_CPU_INT
} else if (cmd == "amicint") {
cmd = "";
string value;
int irq_id;
ss >> value;
@ -686,6 +811,7 @@ void enter_debugger() {
gMachineObj->get_comp_by_type(HWCompType::INT_CTRL));
int_ctrl->ack_int(irq_id, 1);
} else if (cmd == "viaint") {
cmd = "";
string value;
int irq_bit;
ss >> value;
@ -700,8 +826,10 @@ void enter_debugger() {
via_obj->assert_int(irq_bit);
#endif
} else {
cout << "Unknown command: " << cmd << endl;
continue;
if (!cmd.empty()) {
cout << "Unknown command: " << cmd << endl;
cmd = "";
}
}
last_cmd = cmd;
}

View File

@ -22,6 +22,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef DEBUGGER_H_
#define DEBUGGER_H_
void enter_debugger(void);
void enter_debugger();
#endif // DEBUGGER_H_

View File

@ -1,23 +1,46 @@
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(PlatformGlob)
include_directories("${PROJECT_SOURCE_DIR}"
"${PROJECT_SOURCE_DIR}/thirdparty/loguru/"
)
file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/adb/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/i2c/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/ata/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/pci/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/scsi/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/ethernet/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/floppy/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/ioctrl/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/memctrl/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/serial/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sound/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/storage/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/video/*.cpp"
platform_glob(SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/adb/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/i2c/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/ata/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/pci/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/scsi/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/ethernet/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/floppy/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/ioctrl/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/memctrl/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/serial/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/sound/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/storage/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/video/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/common/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/common/adb/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/common/firewire/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/common/i2c/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/common/ata/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/common/pci/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/common/scsi/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/common/usb/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/ethernet/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/floppy/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/ioctrl/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/memctrl/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/serial/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/sound/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/storage/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/video/*.h"
)
add_library(devices OBJECT ${SOURCES})
target_link_libraries(devices PRIVATE cubeb SDL2::SDL2)
if (EMSCRIPTEN)
target_link_libraries(devices PRIVATE)
else()
target_link_libraries(devices PRIVATE cubeb SDL2::SDL2)
endif()

View File

@ -1,472 +0,0 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-21 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** ADB bus definitions
Simulates Apple Desktop Bus (ADB) bus
*/
#include <devices/common/adb/adb.h>
#include <loguru.hpp>
#include <cinttypes>
#include <cstring>
#if 0
#include <thirdparty/SDL2/include/SDL.h>
#include <thirdparty/SDL2/include/SDL_events.h>
#include <thirdparty/SDL2/include/SDL_keycode.h>
#include <thirdparty/SDL2/include/SDL_mouse.h>
#include <thirdparty/SDL2/include/SDL_stdinc.h>
#endif
using namespace std;
ADB_Bus::ADB_Bus() {
// set data streams as clear
this->adb_mouse_register0 = 0x8080;
input_stream_len = 0;
output_stream_len = 2;
adb_keybd_register3 = 0x6201;
adb_mouse_register3 = 0x6302;
keyboard_access_no = adb_encoded;
mouse_access_no = adb_relative;
}
bool ADB_Bus::listen(int device, int reg) {
if (device == keyboard_access_no) {
if (adb_keybd_listen(reg)) {
return true;
} else {
return false;
}
} else if (device == mouse_access_no) {
if (adb_mouse_listen(reg)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
bool ADB_Bus::talk(int device, int reg, uint16_t value) {
// temp code
return false;
}
bool ADB_Bus::bus_reset() {
// temp code
return true;
}
bool ADB_Bus::set_addr(int dev_addr, int new_addr) {
// temp code
return false;
}
bool ADB_Bus::flush(int dev_addr) {
// temp code
return false;
}
bool ADB_Bus::adb_keybd_listen(int reg) {
if (reg == 3) {
output_data_stream[0] = (adb_keybd_register3 >> 8);
output_data_stream[1] = (adb_keybd_register3 & 0xff);
return true;
}
#if 0
while (SDL_PollEvent(&adb_keybd_evt)) {
// Poll our SDL key event for any keystrokes.
switch (adb_keybd_evt.type) {
case SDL_KEYDOWN:
switch (adb_keybd_evt.key.keysym.sym) {
case SDLK_a:
ask_key_pressed = 0x00;
break;
case SDLK_s:
ask_key_pressed = 0x01;
break;
case SDLK_d:
ask_key_pressed = 0x02;
break;
case SDLK_f:
ask_key_pressed = 0x03;
break;
case SDLK_h:
ask_key_pressed = 0x04;
break;
case SDLK_g:
ask_key_pressed = 0x05;
break;
case SDLK_z:
ask_key_pressed = 0x06;
break;
case SDLK_x:
ask_key_pressed = 0x07;
break;
case SDLK_c:
ask_key_pressed = 0x08;
break;
case SDLK_v:
ask_key_pressed = 0x09;
break;
case SDLK_b:
ask_key_pressed = 0x0B;
break;
case SDLK_q:
ask_key_pressed = 0x0C;
break;
case SDLK_w:
ask_key_pressed = 0x0D;
break;
case SDLK_e:
ask_key_pressed = 0x0E;
break;
case SDLK_r:
ask_key_pressed = 0x0F;
break;
case SDLK_y:
ask_key_pressed = 0x10;
break;
case SDLK_t:
ask_key_pressed = 0x11;
break;
case SDLK_1:
ask_key_pressed = 0x12;
break;
case SDLK_2:
ask_key_pressed = 0x13;
break;
case SDLK_3:
ask_key_pressed = 0x14;
break;
case SDLK_4:
ask_key_pressed = 0x15;
break;
case SDLK_6:
ask_key_pressed = 0x16;
break;
case SDLK_5:
ask_key_pressed = 0x17;
break;
case SDLK_EQUALS:
ask_key_pressed = 0x18;
break;
case SDLK_9:
ask_key_pressed = 0x19;
break;
case SDLK_7:
ask_key_pressed = 0x1A;
break;
case SDLK_MINUS:
ask_key_pressed = 0x1B;
break;
case SDLK_8:
ask_key_pressed = 0x1C;
break;
case SDLK_0:
ask_key_pressed = 0x1D;
break;
case SDLK_RIGHTBRACKET:
ask_key_pressed = 0x1E;
break;
case SDLK_o:
ask_key_pressed = 0x1F;
break;
case SDLK_u:
ask_key_pressed = 0x20;
break;
case SDLK_LEFTBRACKET:
ask_key_pressed = 0x21;
break;
case SDLK_i:
ask_key_pressed = 0x22;
break;
case SDLK_p:
ask_key_pressed = 0x23;
break;
case SDLK_RETURN:
ask_key_pressed = 0x24;
break;
case SDLK_l:
ask_key_pressed = 0x25;
break;
case SDLK_j:
ask_key_pressed = 0x26;
break;
case SDLK_QUOTE:
ask_key_pressed = 0x27;
break;
case SDLK_k:
ask_key_pressed = 0x28;
break;
case SDLK_SEMICOLON:
ask_key_pressed = 0x29;
break;
case SDLK_BACKSLASH:
ask_key_pressed = 0x2A;
break;
case SDLK_COMMA:
ask_key_pressed = 0x2B;
break;
case SDLK_SLASH:
ask_key_pressed = 0x2C;
break;
case SDLK_n:
ask_key_pressed = 0x2D;
break;
case SDLK_m:
ask_key_pressed = 0x2E;
break;
case SDLK_PERIOD:
ask_key_pressed = 0x2F;
break;
case SDLK_BACKQUOTE:
ask_key_pressed = 0x32;
break;
case SDLK_ESCAPE:
ask_key_pressed = 0x35;
break;
case SDLK_LEFT:
ask_key_pressed = 0x3B;
break;
case SDLK_RIGHT:
ask_key_pressed = 0x3C;
break;
case SDLK_DOWN:
ask_key_pressed = 0x3D;
break;
case SDLK_UP:
ask_key_pressed = 0x3E;
break;
case SDLK_KP_PERIOD:
ask_key_pressed = 0x41;
break;
case SDLK_KP_MULTIPLY:
ask_key_pressed = 0x43;
break;
case SDLK_KP_PLUS:
ask_key_pressed = 0x45;
break;
case SDLK_DELETE:
ask_key_pressed = 0x47;
break;
case SDLK_KP_DIVIDE:
ask_key_pressed = 0x4B;
break;
case SDLK_KP_ENTER:
ask_key_pressed = 0x4C;
break;
case SDLK_KP_MINUS:
ask_key_pressed = 0x4E;
break;
case SDLK_KP_0:
ask_key_pressed = 0x52;
break;
case SDLK_KP_1:
ask_key_pressed = 0x53;
break;
case SDLK_KP_2:
ask_key_pressed = 0x54;
break;
case SDLK_KP_3:
ask_key_pressed = 0x55;
break;
case SDLK_KP_4:
ask_key_pressed = 0x56;
break;
case SDLK_KP_5:
ask_key_pressed = 0x57;
break;
case SDLK_KP_6:
ask_key_pressed = 0x58;
break;
case SDLK_KP_7:
ask_key_pressed = 0x59;
break;
case SDLK_KP_8:
ask_key_pressed = 0x5B;
break;
case SDLK_KP_9:
ask_key_pressed = 0x5C;
break;
case SDLK_BACKSPACE:
// ask_key_pressed = 0x33;
confirm_ask_reg_2 = true;
mod_key_pressed = 0x40;
break;
case SDLK_CAPSLOCK:
// ask_key_pressed = 0x39;
confirm_ask_reg_2 = true;
mod_key_pressed = 0x20;
break;
case SDLK_RALT:
case SDLK_RCTRL: // Temp key for Control key
// ask_key_pressed = 0x36;
confirm_ask_reg_2 = true;
mod_key_pressed = 0x8;
break;
case SDLK_LSHIFT:
case SDLK_RSHIFT:
// ask_key_pressed = 0x38;
confirm_ask_reg_2 = true;
mod_key_pressed = 0x4;
break;
case SDLK_LALT:
// ask_key_pressed = 0x3A;
confirm_ask_reg_2 = true;
mod_key_pressed = 0x2;
break;
case SDLK_LCTRL: // Temp key for the Command/Apple key
// ask_key_pressed = 0x37;
confirm_ask_reg_2 = true;
mod_key_pressed = 0x1;
break;
default:
break;
}
if (adb_keybd_register0 & 0x8000) {
adb_keybd_register0 &= 0x7FFF;
adb_keybd_register0 &= (ask_key_pressed << 8);
output_data_stream[0] = (adb_keybd_register0 >> 8);
output_data_stream[1] = (adb_keybd_register0 & 0xff);
} else if (adb_keybd_register0 & 0x80) {
adb_keybd_register0 &= 0xFF7F;
adb_keybd_register0 &= (ask_key_pressed);
output_data_stream[0] = (adb_keybd_register0 >> 8);
output_data_stream[1] = (adb_keybd_register0 & 0xff);
}
// check if mod keys are being pressed
if (confirm_ask_reg_2) {
adb_keybd_register0 |= (mod_key_pressed << 8);
output_data_stream[0] = (adb_keybd_register2 >> 8);
output_data_stream[1] = (adb_keybd_register2 & 0xff);
}
break;
case SDL_KEYUP:
if (!(adb_keybd_register0 & 0x8000)) {
adb_keybd_register0 |= 0x8000;
output_data_stream[0] = (adb_keybd_register0 >> 8);
output_data_stream[1] = (adb_keybd_register0 & 0xff);
} else if (adb_keybd_register0 & 0x80)
adb_keybd_register0 |= 0x0080;
output_data_stream[0] = (adb_keybd_register0 >> 8);
output_data_stream[1] = (adb_keybd_register0 & 0xff);
if (confirm_ask_reg_2) {
adb_keybd_register2 &= (mod_key_pressed << 8);
output_data_stream[0] = (adb_keybd_register2 >> 8);
output_data_stream[1] = (adb_keybd_register2 & 0xff);
confirm_ask_reg_2 = false;
}
}
}
#endif
if ((reg != 1)) {
return true;
} else {
return false;
}
}
bool ADB_Bus::adb_mouse_listen(int reg) {
if ((reg != 0) || (reg != 3)) {
return false;
}
#if 0
while (SDL_PollEvent(&adb_mouse_evt)) {
if (adb_mouse_evt.motion.x) {
this->adb_mouse_register0 &= 0x7F;
if (adb_mouse_evt.motion.xrel < 0) {
if (adb_mouse_evt.motion.xrel <= -64) {
this->adb_mouse_register0 |= 0x7F;
} else if (adb_mouse_evt.motion.xrel >= 63) {
this->adb_mouse_register0 |= 0x3F;
} else {
this->adb_mouse_register0 |= adb_mouse_evt.motion.xrel;
}
}
}
if (adb_mouse_evt.motion.y) {
this->adb_mouse_register0 &= 0x7F00;
if (adb_mouse_evt.motion.yrel < 0) {
if (adb_mouse_evt.motion.yrel <= -64) {
this->adb_mouse_register0 |= 0x7F00;
} else if (adb_mouse_evt.motion.yrel >= 63) {
this->adb_mouse_register0 |= 0x3F00;
} else {
this->adb_mouse_register0 |= (adb_mouse_evt.motion.yrel << 8);
}
}
}
switch (adb_mouse_evt.type) {
case SDL_MOUSEBUTTONDOWN:
this->adb_mouse_register0 &= 0x7FFF;
case SDL_MOUSEBUTTONUP:
this->adb_mouse_register0 |= 0x8000;
}
}
#endif
if (reg == 0) {
output_data_stream[0] = (adb_mouse_register0 >> 8);
output_data_stream[1] = (adb_mouse_register0 & 0xff);
} else if (reg == 3) {
output_data_stream[0] = (adb_mouse_register3 >> 8);
output_data_stream[1] = (adb_mouse_register3 & 0xff);
}
return true;
}
uint8_t ADB_Bus::get_input_byte(int offset) {
return input_data_stream[offset];
}
uint8_t ADB_Bus::get_output_byte(int offset) {
return output_data_stream[offset];
}
int ADB_Bus::get_input_len() {
return input_stream_len;
}
int ADB_Bus::get_output_len() {
return output_stream_len;
}

View File

@ -1,100 +0,0 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-21 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef ADB_H
#define ADB_H
#include <cinttypes>
#if 0
#include <thirdparty/SDL2/include/SDL.h>
#include <thirdparty/SDL2/include/SDL_events.h>
#endif
enum adb_default_values {
adb_reserved0,
adb_reserved1,
adb_encoded,
adb_relative,
adb_absolute,
adb_reserved5,
adb_reserved6,
adb_reserved7,
adb_other8,
adb_other9,
adb_other10,
adb_other11,
adb_other12,
adb_other13,
adb_other14,
adb_other15
};
class ADB_Bus {
public:
ADB_Bus();
~ADB_Bus() = default;
bool listen(int device, int reg);
bool talk(int device, int reg, uint16_t value);
bool bus_reset();
bool set_addr(int dev_addr, int new_addr);
bool flush(int dev_addr);
bool adb_keybd_listen(int reg);
bool adb_mouse_listen(int reg);
uint8_t get_input_byte(int offset);
uint8_t get_output_byte(int offset);
int get_input_len();
int get_output_len();
private:
int keyboard_access_no;
int mouse_access_no;
// Keyboard Variables
uint16_t adb_keybd_register0;
uint16_t adb_keybd_register2;
uint16_t adb_keybd_register3;
//SDL_Event adb_keybd_evt;
uint8_t ask_key_pressed;
uint8_t mod_key_pressed;
bool confirm_ask_reg_2;
// Mouse Variables
//SDL_Event adb_mouse_evt;
uint16_t adb_mouse_register0;
uint16_t adb_mouse_register3;
uint8_t input_data_stream[16]; // temp buffer
int input_stream_len;
uint8_t output_data_stream[16]; // temp buffer
int output_stream_len;
};
#endif /* ADB_H */

View File

@ -0,0 +1,109 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus emulation. */
#include <devices/common/adb/adbbus.h>
#include <devices/common/adb/adbdevice.h>
#include <devices/deviceregistry.h>
#include <loguru.hpp>
AdbBus::AdbBus(std::string name) {
this->set_name(name);
supports_types(HWCompType::ADB_HOST);
this->devices.clear();
}
void AdbBus::register_device(AdbDevice* dev_obj) {
this->devices.push_back(dev_obj);
}
uint8_t AdbBus::poll() {
for (auto dev : this->devices) {
uint8_t dev_poll = dev->poll();
if (dev_poll) {
return dev_poll;
}
}
return 0;
}
uint8_t AdbBus::process_command(const uint8_t* in_data, int data_size) {
uint8_t dev_addr, dev_reg;
this->output_count = 0;
if (!data_size)
return ADB_STAT_OK;
uint8_t cmd_byte = in_data[0];
uint8_t cmd = cmd_byte & 0xF;
if(!cmd) { // SendReset
LOG_F(9, "%s: SendReset issued", this->name.c_str());
for (auto dev : this->devices)
dev->reset();
} else if (cmd == 1) { // Flush
dev_addr = cmd_byte >> 4;
LOG_F(9, "%s: Flush issued, dev_addr=0x%X", this->name.c_str(), dev_addr);
} else if ((cmd & 0xC) == 8) { // Listen
dev_addr = cmd_byte >> 4;
dev_reg = cmd_byte & 3;
LOG_F(9, "%s: Listen R%d issued, dev_addr=0x%X", this->name.c_str(),
dev_reg, dev_addr);
this->input_buf = in_data + 1;
this->input_count = data_size - 1;
for (auto dev : this->devices)
dev->listen(dev_addr, dev_reg);
} else if ((cmd & 0xC) == 0xC) { // Talk
dev_addr = cmd_byte >> 4;
dev_reg = cmd_byte & 3;
LOG_F(9, "%s: Talk R%d issued, dev_addr=0x%X", this->name.c_str(),
dev_reg, dev_addr);
this->got_answer = false;
for (auto dev : this->devices) {
this->got_answer = dev->talk(dev_addr, dev_reg);
if (this->got_answer) {
break;
}
}
if (!this->got_answer)
return ADB_STAT_TIMEOUT;
} else {
LOG_F(ERROR, "%s: unsupported ADB command 0x%X", this->name.c_str(), cmd_byte);
}
return ADB_STAT_OK;
}
static const DeviceDescription AdbBus_Descriptor = {
AdbBus::create, {}, {}
};
REGISTER_DEVICE(AdbBus, AdbBus_Descriptor);

View File

@ -0,0 +1,80 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus definitions. */
#ifndef ADB_BUS_H
#define ADB_BUS_H
#include <devices/common/hwcomponent.h>
#include <memory>
#include <string>
#include <vector>
#define ADB_MAX_DATA_SIZE 8
/** ADB status. */
enum {
ADB_STAT_OK = 0,
ADB_STAT_SRQ_ACTIVE = 1 << 0,
ADB_STAT_TIMEOUT = 1 << 1,
ADB_STAT_AUTOPOLL = 1 << 6,
};
class AdbDevice; // forward declaration to prevent compiler errors
class AdbBus : public HWComponent {
public:
AdbBus(std::string name);
~AdbBus() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<AdbBus>(new AdbBus("ADB-BUS"));
}
void register_device(AdbDevice* dev_obj);
uint8_t process_command(const uint8_t* in_data, int data_size);
uint8_t get_output_count() { return this->output_count; };
// Polls devices that have a service request flag set. Returns the talk
// command corresponding to the first device that responded with data, or
// 0 if no device responded.
uint8_t poll();
// callbacks meant to be called by devices
const uint8_t* get_input_buf() { return this->input_buf; };
uint8_t* get_output_buf() { return this->output_buf; };
uint8_t get_input_count() { return this->input_count; };
void set_output_count(uint8_t count) { this->output_count = count; };
bool already_answered() { return this->got_answer; };
private:
std::vector<AdbDevice*> devices;
bool got_answer = false;
const uint8_t* input_buf = nullptr;
uint8_t output_buf[ADB_MAX_DATA_SIZE] = {};
uint8_t input_count = 0;
uint8_t output_count = 0;
};
#endif // ADB_BUS_H

View File

@ -0,0 +1,108 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Base class for Apple Desktop Bus devices. */
#include <core/timermanager.h>
#include <devices/common/adb/adbdevice.h>
#include <devices/common/adb/adbbus.h>
#include <machines/machinebase.h>
AdbDevice::AdbDevice(std::string name) {
this->set_name(name);
this->supports_types(HWCompType::ADB_DEV);
}
int AdbDevice::device_postinit() {
// register itself with the ADB host
this->host_obj = dynamic_cast<AdbBus*>(gMachineObj->get_comp_by_type(HWCompType::ADB_HOST));
this->host_obj->register_device(this);
return 0;
};
uint8_t AdbDevice::poll() {
if (!this->srq_flag) {
return 0;
}
bool has_data = this->get_register_0();
if (!has_data) {
return 0;
}
// Register 0 in bits 0-1 (both 0)
// Talk command in bits 2-3 (both 1)
// Device address in bits 4-7
return 0xC | (this->my_addr << 4);
}
bool AdbDevice::talk(const uint8_t dev_addr, const uint8_t reg_num) {
if (dev_addr == this->my_addr && !this->got_collision) {
// see if another device already responded to this command
if (this->host_obj->already_answered()) {
this->got_collision = true;
return false;
}
switch(reg_num & 3) {
case 0:
return this->get_register_0();
case 1:
return this->get_register_1();
case 2:
return this->get_register_2();
case 3:
return this->get_register_3();
default:
return false;
}
} else {
return false;
}
}
void AdbDevice::listen(const uint8_t dev_addr, const uint8_t reg_num) {
if (dev_addr == this->my_addr) {
switch(reg_num & 3) {
case 0:
this->set_register_0();
case 1:
this->set_register_1();
case 2:
this->set_register_2();
case 3:
this->set_register_3();
}
}
}
bool AdbDevice::get_register_3() {
uint8_t* out_buf = this->host_obj->get_output_buf();
out_buf[0] = this->gen_random_address() | (this->exc_event_flag << 6) |
(this->srq_flag << 5);
out_buf[1] = this->dev_handler_id;
this->host_obj->set_output_count(2);
return true;
}
uint8_t AdbDevice::gen_random_address() {
return (TimerManager::get_instance()->current_time_ns() + 8) & 0xF;
}

View File

@ -0,0 +1,78 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Base class for Apple Desktop Bus devices. */
#ifndef ADB_DEVICE_H
#define ADB_DEVICE_H
#include <devices/common/hwcomponent.h>
#include <string>
/** Common ADB device addresses/types. */
enum {
ADB_ADDR_KBD = 2, // keyboards
ADB_ADDR_RELPOS = 3, // relative position devices (mouse)
ADB_ADDR_ABSPOS = 4, // absolute position devices (graphic tablets)
};
class AdbBus; // forward declaration to prevent compiler errors
class AdbDevice : public HWComponent {
public:
AdbDevice(std::string name);
~AdbDevice() = default;
int device_postinit() override;
virtual void reset() = 0;
virtual bool talk(const uint8_t dev_addr, const uint8_t reg_num);
virtual void listen(const uint8_t dev_addr, const uint8_t reg_num);
virtual uint8_t get_address() { return this->my_addr; };
// Attempts to poll the device. Returns the talk command corresponding to
// the device if it has data, 0 otherwise.
uint8_t poll();
protected:
uint8_t gen_random_address();
virtual bool get_register_0() { return false; };
virtual bool get_register_1() { return false; };
virtual bool get_register_2() { return false; };
virtual bool get_register_3();
virtual void set_register_0() {};
virtual void set_register_1() {};
virtual void set_register_2() {};
virtual void set_register_3() {};
uint8_t exc_event_flag = 0;
uint8_t srq_flag = 0;
uint8_t my_addr = 0;
uint8_t dev_handler_id = 0;
bool got_collision = false;
AdbBus* host_obj = nullptr;
};
#endif // ADB_DEVICE_H

View File

@ -0,0 +1,117 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus Keyboard emulation. */
#include <devices/common/adb/adbkeyboard.h>
#include <devices/common/adb/adbbus.h>
#include <devices/deviceregistry.h>
#include <core/hostevents.h>
#include <loguru.hpp>
AdbKeyboard::AdbKeyboard(std::string name) : AdbDevice(name) {
EventManager::get_instance()->add_keyboard_handler(this, &AdbKeyboard::event_handler);
this->reset();
}
void AdbKeyboard::event_handler(const KeyboardEvent& event) {
this->pending_events.push_back(std::make_unique<KeyboardEvent>(event));
}
void AdbKeyboard::reset() {
this->my_addr = ADB_ADDR_KBD;
this->dev_handler_id = 2; // Extended ADB keyboard
this->exc_event_flag = 1;
this->srq_flag = 1; // enable service requests
this->pending_events.clear();
}
bool AdbKeyboard::get_register_0() {
if (this->pending_events.empty()) {
return false;
}
uint8_t* out_buf = this->host_obj->get_output_buf();
out_buf[0] = this->consume_pending_event();
out_buf[1] = this->consume_pending_event();
this->host_obj->set_output_count(2);
return true;
}
uint8_t AdbKeyboard::consume_pending_event() {
if (this->pending_events.empty()) {
// In most cases we have only one pending event when the host polls us,
// but we need to fill two entries of the output buffer. We need to set
// the key status bit to 1 (released), and the key to a non-existent
// one (0x7F). Otherwise if we leave it empty, the host will think that
// the 'a' key (code 0) is pressed (status 0).
return 0xFF;
}
std::unique_ptr<KeyboardEvent> event = std::move(this->pending_events.front());
this->pending_events.pop_front();
uint8_t key_state = 0;
if (event->flags & KEYBOARD_EVENT_DOWN) {
key_state = 0;
} else if (event->flags & KEYBOARD_EVENT_UP) {
key_state = 1;
} else {
LOG_F(WARNING, "%s: unknown keyboard event flags %x", this->name.c_str(), event->flags);
}
return (key_state << 7) | (event->key & 0x7F);
}
void AdbKeyboard::set_register_2() {
}
void AdbKeyboard::set_register_3() {
if (this->host_obj->get_input_count() < 2) // ensure we got enough data
return;
const uint8_t* in_data = this->host_obj->get_input_buf();
switch (in_data[1]) {
case 0:
this->my_addr = in_data[0] & 0xF;
this->srq_flag = !!(in_data[0] & 0x20);
break;
case 1:
case 2:
this->dev_handler_id = in_data[1];
break;
case 3: // extended keyboard protocol isn't supported yet
break;
case 0xFE: // move to a new address if there was no collision
if (!this->got_collision) {
this->my_addr = in_data[0] & 0xF;
}
break;
default:
LOG_F(WARNING, "%s: unknown handler ID = 0x%X", this->name.c_str(), in_data[1]);
}
}
static const DeviceDescription AdbKeyboard_Descriptor = {
AdbKeyboard::create, {}, {}
};
REGISTER_DEVICE(AdbKeyboard, AdbKeyboard_Descriptor);

View File

@ -0,0 +1,180 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus Keyboard definitions. */
#ifndef ADB_KEYBOARD_H
#define ADB_KEYBOARD_H
#include <devices/common/adb/adbdevice.h>
#include <devices/common/hwcomponent.h>
#include <deque>
#include <memory>
#include <string>
class KeyboardEvent;
class AdbKeyboard : public AdbDevice {
public:
AdbKeyboard(std::string name);
~AdbKeyboard() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<AdbKeyboard>(new AdbKeyboard("ADB-KEYBOARD"));
}
void reset() override;
void event_handler(const KeyboardEvent& event);
bool get_register_0() override;
void set_register_2() override;
void set_register_3() override;
private:
std::deque<std::unique_ptr<KeyboardEvent>> pending_events;
uint8_t consume_pending_event();
};
// ADB Extended Keyboard raw key codes (most of which eventually became virtual
// key codes, see HIToolbox/Events.h).
enum AdbKey {
AdbKey_A = 0x00,
AdbKey_B = 0x0b,
AdbKey_C = 0x08,
AdbKey_D = 0x02,
AdbKey_E = 0x0e,
AdbKey_F = 0x03,
AdbKey_G = 0x05,
AdbKey_H = 0x04,
AdbKey_I = 0x22,
AdbKey_J = 0x26,
AdbKey_K = 0x28,
AdbKey_L = 0x25,
AdbKey_M = 0x2e,
AdbKey_N = 0x2d,
AdbKey_O = 0x1f,
AdbKey_P = 0x23,
AdbKey_Q = 0x0c,
AdbKey_R = 0x0f,
AdbKey_S = 0x01,
AdbKey_T = 0x11,
AdbKey_U = 0x20,
AdbKey_V = 0x09,
AdbKey_W = 0x0d,
AdbKey_X = 0x07,
AdbKey_Y = 0x10,
AdbKey_Z = 0x06,
AdbKey_1 = 0x12,
AdbKey_2 = 0x13,
AdbKey_3 = 0x14,
AdbKey_4 = 0x15,
AdbKey_5 = 0x17,
AdbKey_6 = 0x16,
AdbKey_7 = 0x1a,
AdbKey_8 = 0x1c,
AdbKey_9 = 0x19,
AdbKey_0 = 0x1d,
AdbKey_Minus = 0x1b,
AdbKey_Equal = 0x18,
AdbKey_LeftBracket = 0x21,
AdbKey_RightBracket = 0x1e,
AdbKey_Backslash = 0x2a,
AdbKey_Semicolon = 0x29,
AdbKey_Quote = 0x27,
AdbKey_Comma = 0x2b,
AdbKey_Period = 0x2f,
AdbKey_Slash = 0x2c,
AdbKey_Tab = 0x30,
AdbKey_Return = 0x24,
AdbKey_Space = 0x31,
AdbKey_Delete = 0x33,
AdbKey_ForwardDelete = 0x75,
AdbKey_Help = 0x72,
AdbKey_Home = 0x73,
AdbKey_End = 0x77,
AdbKey_PageUp = 0x74,
AdbKey_PageDown = 0x79,
AdbKey_Grave = 0x32,
AdbKey_Escape = 0x35,
AdbKey_Control = 0x36,
AdbKey_Shift = 0x38,
AdbKey_Option = 0x3a,
AdbKey_Command = 0x37,
AdbKey_CapsLock = 0x39,
AdbKey_ArrowUp = 0x3e,
AdbKey_ArrowDown = 0x3d,
AdbKey_ArrowLeft = 0x3b,
AdbKey_ArrowRight = 0x3c,
AdbKey_Keypad0 = 0x52,
AdbKey_Keypad1 = 0x53,
AdbKey_Keypad2 = 0x54,
AdbKey_Keypad3 = 0x55,
AdbKey_Keypad4 = 0x56,
AdbKey_Keypad5 = 0x57,
AdbKey_Keypad6 = 0x58,
AdbKey_Keypad7 = 0x59,
AdbKey_Keypad9 = 0x5c,
AdbKey_Keypad8 = 0x5b,
AdbKey_KeypadDecimal = 0x41,
AdbKey_KeypadPlus = 0x45,
AdbKey_KeypadMinus = 0x4e,
AdbKey_KeypadMultiply = 0x43,
AdbKey_KeypadDivide = 0x4b,
AdbKey_KeypadEnter = 0x4c,
AdbKey_KeypadEquals = 0x51,
AdbKey_KeypadClear = 0x47,
AdbKey_F1 = 0x7a,
AdbKey_F2 = 0x78,
AdbKey_F3 = 0x63,
AdbKey_F4 = 0x76,
AdbKey_F5 = 0x60,
AdbKey_F6 = 0x61,
AdbKey_F7 = 0x62,
AdbKey_F8 = 0x64,
AdbKey_F9 = 0x65,
AdbKey_F10 = 0x6d,
AdbKey_F11 = 0x67,
AdbKey_F12 = 0x6f,
AdbKey_F13 = 0x69,
AdbKey_F14 = 0x6b,
AdbKey_F15 = 0x71,
AdbKey_ISO1= 0x0A,
AdbKey_JIS_Yen = 0x5D,
AdbKey_JIS_Underscore = 0x5E,
AdbKey_JIS_KP_Comma = 0x5F,
AdbKey_JIS_Eisu = 0x66,
AdbKey_JIS_Kana = 0x68,
};
#endif // ADB_KEYBOARD_H

View File

@ -0,0 +1,137 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus Mouse emulation. */
#include <devices/common/adb/adbmouse.h>
#include <devices/common/adb/adbbus.h>
#include <devices/deviceregistry.h>
#include <core/hostevents.h>
#include <loguru.hpp>
AdbMouse::AdbMouse(std::string name) : AdbDevice(name) {
EventManager::get_instance()->add_mouse_handler(this, &AdbMouse::event_handler);
this->reset();
}
void AdbMouse::event_handler(const MouseEvent& event) {
if (event.flags & MOUSE_EVENT_MOTION) {
this->x_rel += event.xrel;
this->y_rel += event.yrel;
} else if (event.flags & MOUSE_EVENT_BUTTON) {
this->buttons_state = event.buttons_state;
this->changed = true;
}
}
void AdbMouse::reset() {
this->my_addr = ADB_ADDR_RELPOS;
this->dev_handler_id = 1;
this->exc_event_flag = 1;
this->srq_flag = 1; // enable service requests
this->x_rel = 0;
this->y_rel = 0;
this->buttons_state = 0;
this->changed = false;
}
bool AdbMouse::get_register_0() {
if (this->x_rel || this->y_rel || this->changed) {
uint8_t* out_buf = this->host_obj->get_output_buf();
uint8_t button1_state = (this->buttons_state ^ 1) << 7;
// report Y-axis motion
if (this->y_rel < -64)
out_buf[0] = 0x40 | button1_state;
else if (this->y_rel > 63)
out_buf[0] = 0x3F | button1_state;
else
out_buf[0] = (this->y_rel & 0x7F) | button1_state;
// report X-axis motion
if (this->x_rel < -64)
out_buf[1] = 0x40 | 0x80;
else if (this->x_rel > 63)
out_buf[1] = 0x3F | 0x80;
else
out_buf[1] = (this->x_rel & 0x7F) | 0x80;
// reset accumulated motion data and button change flag
this->x_rel = 0;
this->y_rel = 0;
this->changed = false;
this->host_obj->set_output_count(2);
return true;
}
return false;
}
bool AdbMouse::get_register_1() {
uint8_t* out_buf = this->host_obj->get_output_buf();
// Identifier
out_buf[0] = 'a';
out_buf[1] = 'p';
out_buf[2] = 'p';
out_buf[3] = 'l';
// Slightly higher resolution of 300 units per inch
out_buf[4] = 300 >> 8;
out_buf[5] = 300 & 0xFF;
out_buf[6] = 1; // mouse
out_buf[7] = 1; // 1 button
this->host_obj->set_output_count(8);
return true;
}
void AdbMouse::set_register_3() {
if (this->host_obj->get_input_count() < 2) // ensure we got enough data
return;
const uint8_t* in_data = this->host_obj->get_input_buf();
switch (in_data[1]) {
case 0:
this->my_addr = in_data[0] & 0xF;
this->srq_flag = !!(in_data[0] & 0x20);
break;
case 1:
case 2:
case 4: // switch over to extended mouse protocol
this->dev_handler_id = in_data[1];
break;
case 0xFE: // move to a new address if there was no collision
if (!this->got_collision) {
this->my_addr = in_data[0] & 0xF;
}
break;
default:
LOG_F(WARNING, "%s: unknown handler ID = 0x%X", this->name.c_str(), in_data[1]);
}
}
static const DeviceDescription AdbMouse_Descriptor = {
AdbMouse::create, {}, {}
};
REGISTER_DEVICE(AdbMouse, AdbMouse_Descriptor);

View File

@ -0,0 +1,58 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file Apple Desktop Bus Mouse definitions. */
#ifndef ADB_MOUSE_H
#define ADB_MOUSE_H
#include <devices/common/adb/adbdevice.h>
#include <devices/common/hwcomponent.h>
#include <memory>
#include <string>
class MouseEvent;
class AdbMouse : public AdbDevice {
public:
AdbMouse(std::string name);
~AdbMouse() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<AdbMouse>(new AdbMouse("ADB-MOUSE"));
}
void reset() override;
void event_handler(const MouseEvent& event);
bool get_register_0() override;
bool get_register_1() override;
void set_register_3() override;
private:
int32_t x_rel = 0;
int32_t y_rel = 0;
uint8_t buttons_state = 0;
bool changed = false;
};
#endif // ADB_MOUSE_H

View File

@ -23,7 +23,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/ata/atabasedevice.h>
#include <devices/common/ata/atadefs.h>
#include <devices/deviceregistry.h>
#include <devices/common/ata/idechannel.h>
#include <loguru.hpp>
#include <cinttypes>
@ -62,8 +62,22 @@ void AtaBaseDevice::device_set_signature() {
uint16_t AtaBaseDevice::read(const uint8_t reg_addr) {
switch (reg_addr) {
case ATA_Reg::DATA:
LOG_F(WARNING, "Retrieving data from %s", this->name.c_str());
return 0xFFFFU;
if (this->has_data()) {
uint16_t ret_data = this->get_data();
this->chunk_cnt -= 2;
if (this->chunk_cnt <= 0) {
this->xfer_cnt -= this->chunk_size;
if (this->xfer_cnt <= 0)
this->r_status &= ~DRQ;
else {
this->chunk_cnt = this->chunk_size;
this->update_intrq(1);
}
}
return ret_data;
} else {
return 0xFFFFU;
}
case ATA_Reg::ERROR:
return this->r_error;
case ATA_Reg::SEC_COUNT:
@ -77,7 +91,7 @@ uint16_t AtaBaseDevice::read(const uint8_t reg_addr) {
case ATA_Reg::DEVICE_HEAD:
return this->r_dev_head;
case ATA_Reg::STATUS:
// TODO: clear pending interrupt
this->update_intrq(0);
return this->r_status;
case ATA_Reg::ALT_STATUS:
return this->r_status;
@ -90,7 +104,23 @@ uint16_t AtaBaseDevice::read(const uint8_t reg_addr) {
void AtaBaseDevice::write(const uint8_t reg_addr, const uint16_t value) {
switch (reg_addr) {
case ATA_Reg::DATA:
LOG_F(WARNING, "Pushing data to %s", this->name.c_str());
if (this->has_data()) {
*this->cur_data_ptr++ = BYTESWAP_16(value);
this->chunk_cnt -= 2;
if (this->chunk_cnt <= 0) {
this->post_xfer_action();
this->xfer_cnt -= this->chunk_size;
if (this->xfer_cnt <= 0) { // transfer complete?
this->r_status &= ~DRQ;
this->r_status &= ~BSY;
this->update_intrq(1);
} else {
this->cur_data_ptr = this->data_ptr;
this->chunk_cnt = this->chunk_size;
this->signal_data_ready();
}
}
}
break;
case ATA_Reg::FEATURES:
this->r_features = value;
@ -151,3 +181,15 @@ void AtaBaseDevice::update_intrq(uint8_t new_intrq_state) {
this->intrq_state = new_intrq_state;
this->host_obj->report_intrq(new_intrq_state);
}
void AtaBaseDevice::prepare_xfer(int xfer_size, int block_size) {
this->chunk_cnt = std::min(xfer_size, block_size);
this->xfer_cnt = xfer_size;
this->chunk_size = block_size;
}
void AtaBaseDevice::signal_data_ready() {
this->r_status |= DRQ;
this->r_status &= ~BSY;
this->update_intrq(1);
}

View File

@ -25,11 +25,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define ATA_BASE_DEVICE_H
#include <devices/common/ata/atadefs.h>
#include <devices/common/ata/idechannel.h>
#include <devices/common/hwcomponent.h>
#include "endianswap.h"
#include <cinttypes>
#include <string>
#include <functional>
class IdeChannel;
class AtaBaseDevice : public HWComponent, public AtaInterface
{
@ -54,10 +57,21 @@ public:
virtual void device_set_signature();
void device_control(const uint8_t new_ctrl);
void update_intrq(uint8_t new_intrq_state);
void signal_data_ready();
bool has_data() {
return data_ptr && xfer_cnt;
}
uint16_t get_data() {
return BYTESWAP_16(*this->data_ptr++);
}
protected:
bool is_selected() { return ((this->r_dev_head >> 4) & 1) == this->my_dev_id; };
void prepare_xfer(int xfer_size, int block_size);
uint8_t my_dev_id = 0; // my IDE device ID configured by the host
uint8_t device_type = ata_interface::DEVICE_TYPE_UNKNOWN;
uint8_t intrq_state = 0; // INTRQ deasserted
@ -77,10 +91,14 @@ protected:
uint8_t r_status_save;
uint8_t r_dev_ctrl = 0x08;
uint16_t *data_ptr = nullptr;
uint8_t data_buf[512] = {};
int data_pos = 0;
int xfer_cnt = 0;
uint16_t *data_ptr = nullptr;
uint16_t *cur_data_ptr = nullptr;
uint8_t data_buf[512] = {};
int xfer_cnt = 0;
int chunk_cnt = 0;
int chunk_size = 0;
std::function<void()> post_xfer_action = nullptr;
};
#endif // ATA_BASE_DEVICE_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -79,6 +79,11 @@ enum ATAPI_Features : uint8_t {
OVERLAP = 1 << 1
};
/** Device/Head register bits. */
enum ATA_Dev_Head : uint8_t {
LBA = 1 << 6,
};
/** Status register bits. */
enum ATA_Status : uint8_t {
ERR = 0x01,
@ -112,32 +117,34 @@ enum ATA_CTRL : uint8_t {
/* ATA commands. */
enum ATA_Cmd : uint8_t {
NOP = 0x00,
ATAPI_SOFT_RESET = 0x08,
RECALIBRATE = 0x10,
READ_SECTOR = 0x20,
READ_SECTOR_NR = 0x21,
READ_LONG = 0x22,
READ_SECTOR_EXT = 0x24,
WRITE_SECTOR = 0x30,
WRITE_SECTOR_NR = 0x31,
WRITE_LONG = 0x32,
READ_VERIFY = 0x40,
FORMAT_TRACKS = 0x50,
IDE_SEEK = 0x70,
DIAGNOSTICS = 0x90,
INIT_DEV_PARAM = 0x91,
ATAPI_PACKET = 0xA0,
ATAPI_IDFY_DEV = 0xA1,
ATAPI_SERVICE = 0xA2,
READ_MULTIPLE = 0xC4,
WRITE_MULTIPLE = 0xC5,
READ_DMA = 0xC8,
WRITE_DMA = 0xCA,
WRITE_BUFFER_DMA = 0xE9,
READ_BUFFER_DMA = 0xEB,
IDENTIFY_DEVICE = 0xEC,
SET_FEATURES = 0xEF,
NOP = 0x00,
ATAPI_SOFT_RESET = 0x08,
RECALIBRATE = 0x10,
READ_SECTOR = 0x20,
READ_SECTOR_NR = 0x21,
READ_LONG = 0x22,
READ_SECTOR_EXT = 0x24,
WRITE_SECTOR = 0x30,
WRITE_SECTOR_NR = 0x31,
WRITE_LONG = 0x32,
READ_VERIFY = 0x40,
FORMAT_TRACKS = 0x50,
IDE_SEEK = 0x70,
DIAGNOSTICS = 0x90,
INIT_DEV_PARAM = 0x91,
ATAPI_PACKET = 0xA0,
ATAPI_IDFY_DEV = 0xA1,
ATAPI_SERVICE = 0xA2,
READ_MULTIPLE = 0xC4,
WRITE_MULTIPLE = 0xC5,
READ_DMA = 0xC8,
WRITE_DMA = 0xCA,
STANDBY_IMMEDIATE_E0 = 0xE0,
FLUSH_CACHE = 0xE7, // ATA-5
WRITE_BUFFER_DMA = 0xE9,
READ_BUFFER_DMA = 0xEB,
IDENTIFY_DEVICE = 0xEC,
SET_FEATURES = 0xEF,
};
}; // namespace ata_interface

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -22,82 +22,152 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
/** @file ATA hard disk emulation. */
#include <devices/common/ata/atahd.h>
#include <devices/deviceregistry.h>
#include <devices/common/ata/idechannel.h>
#include <loguru.hpp>
#include <machines/machinebase.h>
#include <memaccess.h>
#include <cstring>
#include <sys/stat.h>
#include <fstream>
#include <string>
#include <loguru.hpp>
using namespace ata_interface;
AtaHardDisk::AtaHardDisk() : AtaBaseDevice("ATA-HD", DEVICE_TYPE_ATA) {
AtaHardDisk::AtaHardDisk(std::string name) : AtaBaseDevice(name, DEVICE_TYPE_ATA) {
}
int AtaHardDisk::device_postinit() {
std::string hdd_config = GET_STR_PROP("hdd_config");
if (hdd_config.empty()) {
LOG_F(ERROR, "%s: hdd_config property is empty", this->name.c_str());
return -1;
}
std::string hdd_image_path = GET_STR_PROP("hdd_img");
if (hdd_image_path.empty())
return 0;
std::string bus_id;
uint32_t dev_num;
parse_device_path(hdd_config, bus_id, dev_num);
auto bus_obj = dynamic_cast<IdeChannel*>(gMachineObj->get_comp_by_name(bus_id));
bus_obj->register_device(dev_num, this);
this->insert_image(hdd_image_path);
return 0;
}
void AtaHardDisk::insert_image(std::string filename) {
this->hdd_img.open(filename, std::fstream::out | std::fstream::in | std::fstream::binary);
struct stat stat_buf;
int rc = stat(filename.c_str(), &stat_buf);
if (!rc) {
this->img_size = stat_buf.st_size;
} else {
ABORT_F("AtaHardDisk: could not determine file size using stat()");
if (!this->hdd_img.open(filename)) {
ABORT_F("%s: could not open image file \"%s\"", this->name.c_str(),
filename.c_str());
}
this->hdd_img.seekg(0, std::ios_base::beg);
this->img_size = this->hdd_img.size();
uint64_t sectors = this->hdd_img.size() / ATA_HD_SEC_SIZE;
this->total_sectors = (uint32_t)sectors;
if (sectors != this->total_sectors) {
ABORT_F("%s: image file \"%s\" is too big", this->name.c_str(),
filename.c_str());
}
this->calc_chs_params();
}
int AtaHardDisk::perform_command()
{
LOG_F(INFO, "%s: command 0x%x requested", this->name.c_str(), this->r_command);
int AtaHardDisk::perform_command() {
this->r_status |= BSY;
this->r_status &= ~DRDY;
this->r_error = 0;
switch (this->r_command) {
case NOP:
break;
case RECALIBRATE:
hdd_img.seekg(0, std::ios::beg);
this->r_error = 0;
this->r_cylinder_lo = 0;
this->r_cylinder_hi = 0;
break;
case READ_SECTOR:
case READ_SECTOR_NR: {
this->r_status |= DRQ;
uint16_t sec_count = (this->r_sect_count == 0) ? 256 : this->r_sect_count;
uint32_t sector = (r_sect_num << 16);
sector |= ((this->r_cylinder_lo) << 8) + (this->r_cylinder_hi);
uint64_t offset = sector * ATA_HD_SEC_SIZE;
hdd_img.seekg(offset, std::ios::beg);
hdd_img.read(buffer, sec_count * ATA_HD_SEC_SIZE);
this->r_status &= ~DRQ;
}
break;
uint16_t sec_count = this->r_sect_count ? this->r_sect_count : 256;
int xfer_size = sec_count * ATA_HD_SEC_SIZE;
uint64_t offset = this->get_lba() * ATA_HD_SEC_SIZE;
hdd_img.read(buffer, offset, xfer_size);
this->data_ptr = (uint16_t *)this->buffer;
// those commands should generate IRQ for each sector
this->prepare_xfer(xfer_size, ATA_HD_SEC_SIZE);
this->signal_data_ready();
}
break;
case WRITE_SECTOR:
case WRITE_SECTOR_NR: {
this->r_status |= DRQ;
uint16_t sec_count = (this->r_sect_count == 0) ? 256 : this->r_sect_count;
uint32_t sector = (r_sect_num << 16);
sector |= ((this->r_cylinder_lo) << 8) + (this->r_cylinder_hi);
uint64_t offset = sector * ATA_HD_SEC_SIZE;
hdd_img.seekg(offset, std::ios::beg);
hdd_img.write(buffer, sec_count * ATA_HD_SEC_SIZE);
this->r_status &= ~DRQ;
}
break;
case INIT_DEV_PARAM:
uint16_t sec_count = this->r_sect_count ? this->r_sect_count : 256;
this->cur_fpos = this->get_lba() * ATA_HD_SEC_SIZE;
this->data_ptr = (uint16_t *)this->buffer;
this->cur_data_ptr = this->data_ptr;
this->prepare_xfer(sec_count * ATA_HD_SEC_SIZE, ATA_HD_SEC_SIZE);
this->post_xfer_action = [this]() {
this->hdd_img.write(this->data_ptr, this->cur_fpos, this->chunk_size);
this->cur_fpos += this->chunk_size;
};
this->r_status |= DRQ;
this->r_status &= ~BSY;
}
break;
case INIT_DEV_PARAM:
// update fictive disk geometry with parameters from host
this->sectors = this->r_sect_count;
this->heads = (this->r_dev_head & 0xF) + 1;
this->r_status &= ~BSY;
this->update_intrq(1);
break;
case DIAGNOSTICS:
this->r_error = 1;
this->device_set_signature();
break;
case FLUSH_CACHE: // used by the XNU kernel driver
this->r_status &= ~(BSY | DRQ | ERR);
this->update_intrq(1);
break;
case DIAGNOSTICS: {
this->r_status |= DRQ;
int ret_code = this->r_error;
this->r_status &= ~DRQ;
return ret_code;
}
break;
case IDENTIFY_DEVICE:
this->r_status |= DRQ;
std::memcpy(buffer, this->hd_id_data, ATA_HD_SEC_SIZE);
this->r_status &= ~DRQ;
this->prepare_identify_info();
this->data_ptr = (uint16_t *)this->data_buf;
this->prepare_xfer(ATA_HD_SEC_SIZE, ATA_HD_SEC_SIZE);
this->signal_data_ready();
break;
case SET_FEATURES:
if (this->r_features == 3) {
switch(this->r_sect_count >> 3) {
case 0:
LOG_F(INFO, "%s: default transfer mode requested", this->name.c_str());
break;
case 1:
LOG_F(INFO, "%s: PIO transfer mode set to 0x%X", this->name.c_str(),
this->r_sect_count & 7);
break;
case 4:
LOG_F(INFO, "%s: Multiword DMA mode set to 0x%X", this->name.c_str(),
this->r_sect_count & 7);
break;
default:
LOG_F(ERROR, "%s: unsupported transfer mode 0x%X", this->name.c_str(),
this->r_sect_count);
this->r_error |= ATA_Error::ABRT;
this->r_status |= ATA_Status::ERR;
}
} else {
LOG_F(WARNING, "%s: unsupported SET_FEATURES subcommand code 0x%X",
this->name.c_str(), this->r_features);
}
this->r_status &= ~BSY;
this->update_intrq(1);
break;
case STANDBY_IMMEDIATE_E0:
LOG_F(INFO, "%s: STANDBY_IMMEDIATE_E0", this->name.c_str());
this->r_status &= ~BSY;
this->update_intrq(1);
break;
default:
LOG_F(ERROR, "%s: unknown ATA command 0x%x", this->name.c_str(), this->r_command);
@ -105,7 +175,99 @@ int AtaHardDisk::perform_command()
this->r_status |= ERR;
return -1;
}
this->r_status &= ~BSY;
this->r_status |= DRDY;
return 0;
}
void AtaHardDisk::prepare_identify_info() {
uint16_t *buf_ptr = (uint16_t *)this->data_buf;
std::memset(this->data_buf, 0, sizeof(this->data_buf));
buf_ptr[ 0] = 0x0040; // ATA device, non-removable media, non-removable drive
buf_ptr[49] = 0x0200; // report LBA support
buf_ptr[ 1] = this->cylinders;
buf_ptr[ 3] = this->heads;
buf_ptr[ 6] = this->sectors;
buf_ptr[57] = this->total_sectors & 0xFFFFU;
buf_ptr[58] = (this->total_sectors >> 16) & 0xFFFFU;
// report LBA capacity
WRITE_WORD_LE_A(&buf_ptr[60], (this->total_sectors & 0xFFFFU));
WRITE_WORD_LE_A(&buf_ptr[61], (this->total_sectors >> 16) & 0xFFFFU);
}
uint64_t AtaHardDisk::get_lba() {
if (this->r_dev_head & ATA_Dev_Head::LBA) {
return ((this->r_dev_head & 0xF) << 24) | (this->r_cylinder_hi << 16) |
(this->r_cylinder_lo << 8) | (this->r_sect_num);
} else { // translate old fashioned CHS addressing to LBA
uint16_t c = (this->r_cylinder_hi << 8) + this->r_cylinder_lo;
uint8_t h = this->r_dev_head & 0xF;
uint8_t s = this->r_sect_num;
if (!s) {
LOG_F(ERROR, "%s: zero sector number is not allowed!", this->name.c_str());
return -1ULL;
} else
return (this->heads * c + h) * this->sectors + s - 1;
}
}
void AtaHardDisk::calc_chs_params() {
unsigned num_blocks, heads, sectors, max_sectors, cylinders, max_cylinders;
LOG_F(INFO, "%s: total sectors %d", this->name.c_str(), this->total_sectors);
if (this->total_sectors >= REAL_CHS_LIMIT) {
heads = 16;
sectors = 255;
cylinders = 65535;
LOG_F(WARNING, "%s: exceeds max CHS translation",
this->name.c_str());
goto done;
}
// use PC BIOS limit to keep number of sectors small for smaller disks
if (this->total_sectors >= ATA_BIOS_LIMIT) {
max_sectors = 255;
max_cylinders = 65535;
} else {
max_sectors = 63;
max_cylinders = 16383;
}
num_blocks = this->total_sectors;
for (heads = 16; heads > 0; heads--)
for (sectors = max_sectors; sectors > 0; sectors--)
if (!(num_blocks % (heads * sectors)))
if (heads * sectors * max_cylinders >= num_blocks) {
cylinders = num_blocks / (heads * sectors);
goto done;
}
heads = 16;
sectors = (num_blocks + heads * max_cylinders - 1) / (heads * max_cylinders);
cylinders = (num_blocks + heads * sectors - 1) / (heads * sectors);
LOG_F(WARNING, "%s: could not find a suitable CHS translation; increased sectors to %d",
this->name.c_str(), heads * sectors * cylinders);
done:
this->heads = heads;
this->sectors = sectors;
this->cylinders = cylinders;
LOG_F(INFO, "%s: C=%d, H=%d, S=%d", this->name.c_str(), cylinders,
heads, sectors);
}
static const PropMap AtaHardDiskProperties = {
{"hdd_img", new StrProperty("")},
};
static const DeviceDescription AtaHardDiskDescriptor =
{AtaHardDisk::create, {}, AtaHardDiskProperties};
REGISTER_DEVICE(AtaHardDisk, AtaHardDiskDescriptor);

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -25,23 +25,50 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define ATA_HARD_DISK_H
#include <devices/common/ata/atabasedevice.h>
#include <utils/imgfile.h>
#include <string>
#include <fstream>
#define ATA_HD_SEC_SIZE 512
// C:16383 x H:16 x S:63 = C:1032 x H:254 x S:63 = 8063.5078125 MiB = 8.46 GB
#define ATA_BIOS_LIMIT 16514064
// C:65535 x H:16 x S:255 = 127.498 GiB = 136.900 GB = largest identify
#define REAL_CHS_LIMIT 267382800
// C:65536 x H:16 x S:255 = 127.500 GiB = 136.902 GB = largest address
#define CHS_LIMIT 267386880
class AtaHardDisk : public AtaBaseDevice
{
public:
AtaHardDisk();
AtaHardDisk(std::string name);
~AtaHardDisk() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<AtaHardDisk>(new AtaHardDisk("ATA-HD"));
}
int device_postinit() override;
void insert_image(std::string filename);
int perform_command() override;
protected:
void prepare_identify_info();
uint64_t get_lba();
void calc_chs_params();
private:
std::fstream hdd_img;
uint64_t img_size;
ImgFile hdd_img;
uint64_t img_size = 0;
uint32_t total_sectors = 0;
uint64_t cur_fpos = 0;
// fictive disk geometry for CHS-to-LBA translation
uint16_t cylinders;
uint8_t heads;
uint8_t sectors;
char * buffer = new char[1 <<17];
uint8_t hd_id_data[ATA_HD_SEC_SIZE] = {};

View File

@ -37,8 +37,8 @@ AtapiBaseDevice::AtapiBaseDevice(const std::string name)
}
void AtapiBaseDevice::device_set_signature() {
this->r_sect_count = 1;
this->r_sect_num = 1;
this->r_int_reason = 1; // shadows ATA r_sect_count
this->r_sect_num = 1; // required for protocol identification in OF 2.x
this->r_dev_head = 0;
// set ATAPI protocol signature
@ -50,28 +50,26 @@ void AtapiBaseDevice::device_set_signature() {
uint16_t AtapiBaseDevice::read(const uint8_t reg_addr) {
switch (reg_addr) {
case ATA_Reg::DATA:
if (this->data_ptr && this->xfer_cnt) {
if (has_data()) {
uint16_t ret_data = get_data();
this->xfer_cnt -= 2;
if (this->xfer_cnt <= 0) {
this->r_status &= ~DRQ;
if ((this->r_int_reason & ATAPI_Int_Reason::IO) &&
!(this->r_int_reason & ATAPI_Int_Reason::CoD)) {
uint16_t ret_data = BYTESWAP_16(*this->data_ptr++);
!(this->r_int_reason & ATAPI_Int_Reason::CoD)
) {
if (this->data_available()) {
this->r_status &= ~DRQ;
this->r_status |= BSY;
this->update_intrq(1);
return ret_data;
this->update_intrq(1); // Is this going to work here? The interrupt happens before returning ret_data.
}
if (this->status_expected) {
else if (this->status_expected) {
this->present_status();
}
return ret_data;
}
}
return BYTESWAP_16(*this->data_ptr++);
return ret_data;
} else {
return 0xFFFFU;
}
@ -79,6 +77,8 @@ uint16_t AtapiBaseDevice::read(const uint8_t reg_addr) {
return this->r_error;
case ATAPI_Reg::INT_REASON:
return this->r_int_reason;
case ATA_Reg::SEC_NUM:
return this->r_sect_num;
case ATAPI_Reg::BYTE_COUNT_LO:
return this->r_byte_count & 0xFFU;
case ATAPI_Reg::BYTE_COUNT_HI:
@ -103,7 +103,7 @@ uint16_t AtapiBaseDevice::read(const uint8_t reg_addr) {
void AtapiBaseDevice::write(const uint8_t reg_addr, const uint16_t value) {
switch (reg_addr) {
case ATA_Reg::DATA:
if (this->data_ptr && this->xfer_cnt) {
if (has_data()) {
*this->data_ptr++ = BYTESWAP_16(value);
this->xfer_cnt -= 2;
if (this->xfer_cnt <= 0) {
@ -162,7 +162,6 @@ int AtapiBaseDevice::perform_command() {
this->data_buf[0] = 0xC0;
this->data_buf[1] = 0x85;
this->data_ptr = (uint16_t *)this->data_buf;
this->data_pos = 0;
this->xfer_cnt = 512;
this->status_expected = false;
this->data_out_phase();
@ -196,12 +195,6 @@ void AtapiBaseDevice::data_out_phase() {
this->signal_data_ready();
}
void AtapiBaseDevice::signal_data_ready() {
this->r_status |= DRQ;
this->r_status &= ~BSY;
this->update_intrq(1);
}
void AtapiBaseDevice::present_status() {
this->r_int_reason |= ATAPI_Int_Reason::IO;
this->r_int_reason |= ATAPI_Int_Reason::CoD;

View File

@ -25,7 +25,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define ATAPI_BASE_DEVICE_H
#include <devices/common/ata/atabasedevice.h>
#include <devices/common/ata/atadefs.h>
#include <cinttypes>
#include <string>
@ -49,7 +48,6 @@ public:
// methods with default implementation
virtual void data_out_phase();
virtual void signal_data_ready();
virtual void present_status();
protected:

View File

@ -23,6 +23,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/ata/atadefs.h>
#include <devices/common/ata/atapicdrom.h>
#include <devices/common/ata/idechannel.h>
#include <devices/common/scsi/scsi.h> // ATAPI CDROM reuses SCSI commands (sic!)
#include <devices/deviceregistry.h>
#include <machines/machinebase.h>
@ -33,7 +34,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
using namespace ata_interface;
AtapiCdrom::AtapiCdrom(std::string name) : AtapiBaseDevice(name) {
AtapiCdrom::AtapiCdrom(std::string name) : CdromDrive(), AtapiBaseDevice(name) {
this->set_error_callback(
[this](uint8_t sense_key, uint8_t asc) {
this->status_error(sense_key, asc);
@ -68,6 +69,11 @@ void AtapiCdrom::perform_packet_command() {
uint32_t lba, xfer_len;
this->r_status |= BSY;
this->sector_areas = 0;
if (this->doing_sector_areas) {
this->doing_sector_areas = false;
LOG_F(WARNING, "%s: doing_sector_areas reset", this->name.c_str());
}
switch (this->cmd_pkt[0]) {
case ScsiCommand::TEST_UNIT_READY:
@ -137,6 +143,32 @@ void AtapiCdrom::perform_packet_command() {
this->data_out_phase();
}
break;
case ScsiCommand::READ_6:
lba = this->cmd_pkt[1] << 16 | READ_WORD_BE_U(&this->cmd_pkt[2]);
xfer_len = this->cmd_pkt[4];
if (this->r_features & ATAPI_Features::DMA) {
LOG_F(WARNING, "ATAPI DMA transfer requsted");
}
this->set_fpos(lba);
this->xfer_cnt = this->read_begin(xfer_len, this->r_byte_count);
this->r_byte_count = this->xfer_cnt;
this->data_ptr = (uint16_t*)this->data_cache.get();
this->status_good();
this->data_out_phase();
break;
case ScsiCommand::READ_10:
lba = READ_DWORD_BE_U(&this->cmd_pkt[2]);
xfer_len = READ_WORD_BE_U(&this->cmd_pkt[7]);
if (this->r_features & ATAPI_Features::DMA) {
LOG_F(WARNING, "ATAPI DMA transfer requsted");
}
this->set_fpos(lba);
this->xfer_cnt = this->read_begin(xfer_len, this->r_byte_count);
this->r_byte_count = this->xfer_cnt;
this->data_ptr = (uint16_t*)this->data_cache.get();
this->status_good();
this->data_out_phase();
break;
case ScsiCommand::READ_12:
lba = READ_DWORD_BE_U(&this->cmd_pkt[2]);
xfer_len = READ_DWORD_BE_U(&this->cmd_pkt[6]);
@ -157,20 +189,70 @@ void AtapiCdrom::perform_packet_command() {
this->present_status();
break;
case ScsiCommand::READ_CD:
lba = READ_DWORD_BE_U(&this->cmd_pkt[2]);
{
lba = READ_DWORD_BE_U(&this->cmd_pkt[2]);
xfer_len = (this->cmd_pkt[6] << 16) | READ_WORD_BE_U(&this->cmd_pkt[7]);
if (this->cmd_pkt[1] || this->cmd_pkt[9] != 0x10 || this->cmd_pkt[10])
LOG_F(WARNING, "%s: unsupported READ_CD params", this->name.c_str());
if (this->cmd_pkt[1] || (this->cmd_pkt[9] & ~0xf8) || ((this->cmd_pkt[9] & 0xf8) == 0) || this->cmd_pkt[10])
LOG_F(WARNING, "%s: unsupported READ_CD params: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
this->name.c_str(),
this->cmd_pkt[0], this->cmd_pkt[1], this->cmd_pkt[2], this->cmd_pkt[3], this->cmd_pkt[4], this->cmd_pkt[5],
this->cmd_pkt[6], this->cmd_pkt[7], this->cmd_pkt[8], this->cmd_pkt[9], this->cmd_pkt[10], this->cmd_pkt[11]
);
if (this->r_features & ATAPI_Features::DMA) {
LOG_F(WARNING, "ATAPI DMA transfer requsted");
}
this->set_fpos(lba);
this->xfer_cnt = this->read_begin(xfer_len, this->r_byte_count);
this->r_byte_count = this->xfer_cnt;
this->sector_areas = cmd_pkt[9];
this->doing_sector_areas = true;
this->current_block = lba;
this->current_block_byte = 0;
int bytes_prolog = 0;
int bytes_data = 0;
int bytes_epilog = 0;
// For Mode 1 CD-ROMs:
if (this->sector_areas & 0x80) bytes_prolog += 12; // Sync
if (this->sector_areas & 0x20) bytes_prolog += 4; // Header
if (this->sector_areas & 0x40) bytes_prolog += 0; // SubHeader
if (this->sector_areas & 0x10) bytes_data += 2048; // User
if (this->sector_areas & 0x08) bytes_epilog += 288; // Auxiliary
if (this->sector_areas & 0x02) bytes_epilog += 294; // ErrorFlags
if (this->sector_areas & 0x01) bytes_epilog += 96; // SubChannel
int bytes_per_block = bytes_prolog + bytes_data + bytes_epilog;
int disk_image_byte_count;
if (bytes_per_block == 0) {
disk_image_byte_count = 0xffff;
}
else {
disk_image_byte_count = (this->r_byte_count / bytes_per_block) * this->block_size; // whole blocks
if ((this->r_byte_count % bytes_per_block) > bytes_prolog) { // partial block
int disk_image_byte_count_partial_block = (this->r_byte_count % bytes_per_block) - bytes_prolog; // remove prolog from partial block
if (disk_image_byte_count_partial_block > this->block_size) { // partial block includes some epilog?
disk_image_byte_count_partial_block = this->block_size; // // remove epilog from partial block
}
// add partial block
disk_image_byte_count += disk_image_byte_count_partial_block;
}
}
int disk_image_bytes_received = this->read_begin(xfer_len, disk_image_byte_count);
int bytes_received = (disk_image_bytes_received / this->block_size) * bytes_per_block + bytes_prolog + (disk_image_bytes_received % this->block_size); // whole blocks + prolog + partial block
if (bytes_received > this->r_byte_count) { // if partial epilog or partial prolog
bytes_received = this->r_byte_count; // confine to r_byte_count
}
this->r_byte_count = bytes_received;
this->xfer_cnt = bytes_received;
this->data_ptr = (uint16_t*)this->data_cache.get();
this->status_good();
this->data_out_phase();
break;
}
default:
LOG_F(ERROR, "%s: unsupported ATAPI command 0x%X", this->name.c_str(),
this->cmd_pkt[0]);
@ -179,6 +261,101 @@ void AtapiCdrom::perform_packet_command() {
}
}
int AtapiCdrom::request_data() {
// continuation of READ_CD above
this->data_ptr = (uint16_t*)this->data_cache.get();
this->xfer_cnt = this->read_more();
return this->xfer_cnt;
}
static const uint8_t mode_1_sync[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
uint16_t AtapiCdrom::get_data() {
uint16_t ret_data;
if (doing_sector_areas) {
// For Mode 1 CD-ROMs:
int area_start;
int area_end = 0;
ret_data = 0xffff;
if (this->sector_areas & 0x80) {
area_start = area_end;
area_end += 12; // Sync
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = BYTESWAP_16(*((uint16_t*)(&mode_1_sync[current_block_byte - area_start])));
}
}
if (this->sector_areas & 0x20) {
area_start = area_end;
area_end += 4; // Header
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
AddrMsf msf = lba_to_msf(this->current_block + 150);
uint8_t header[4];
header[0] = msf.min;
header[1] = msf.sec;
header[2] = msf.frm;
header[3] = 0x01; // Mode 1
ret_data = BYTESWAP_16(*((uint16_t*)(&header[current_block_byte - area_start])));
}
}
if (this->sector_areas & 0x40) {
area_start = area_end;
area_end += 0; // SubHeader
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
if (this->sector_areas & 0x10) {
area_start = area_end;
area_end += 2048; // User
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = AtaBaseDevice::get_data();
}
}
if (this->sector_areas & 0x08) {
area_start = area_end;
area_end += 288; // Auxiliary
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
if (this->sector_areas & 0x02) {
area_start = area_end;
area_end += 294; // ErrorFlags
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
if (this->sector_areas & 0x01) {
area_start = area_end;
area_end += 96; // SubChannel
if (this->current_block_byte >= area_start && this->current_block_byte < area_end) {
ret_data = 0;
}
}
current_block_byte += 2;
if (current_block_byte >= area_end)
current_block_byte = 0;
}
else {
ret_data = AtaBaseDevice::get_data();
}
return ret_data;
}
void AtapiCdrom::status_good() {
this->status_expected = true;
this->r_error = 0;

View File

@ -42,11 +42,7 @@ public:
void perform_packet_command() override;
int request_data() override {
this->data_ptr = (uint16_t*)this->data_cache.get();
this->xfer_cnt = this->read_more();
return this->xfer_cnt;
}
int request_data() override;
bool data_available() override {
return this->data_left() != 0;
@ -55,10 +51,16 @@ public:
void status_good();
void status_error(uint8_t sense_key, uint8_t asc);
uint16_t get_data();
private:
uint8_t sense_key = 0;
uint8_t asc = 0;
uint8_t ascq = 0;
bool doing_sector_areas = false;
uint8_t sector_areas;
uint32_t current_block;
uint16_t current_block_byte;
};
#endif // ATAPI_CDROM_H

View File

@ -32,6 +32,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/hwcomponent.h>
#include <devices/deviceregistry.h>
#include <machines/machinebase.h>
#include <loguru.hpp>
#include <cinttypes>
#include <memory>

View File

@ -21,9 +21,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
/** @file Descriptor-based direct memory access emulation. */
#include <core/timermanager.h>
#include <cpu/ppc/ppcmmu.h>
#include <devices/common/dbdma.h>
#include <devices/common/dmacore.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/mmiodevice.h>
#include <endianswap.h>
#include <memaccess.h>
@ -37,66 +40,34 @@ void DMAChannel::set_callbacks(DbdmaCallback start_cb, DbdmaCallback stop_cb) {
}
/* Load DMACmd from physical memory. */
void DMAChannel::fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd) {
memcpy((uint8_t*)p_cmd, mmu_get_dma_mem(cmd_addr, 16, nullptr), 16);
DMACmd* DMAChannel::fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd, bool *is_writable) {
MapDmaResult res = mmu_map_dma_mem(cmd_addr, 16, false);
if (is_writable) *is_writable = res.is_writable;
DMACmd* cmd_host = (DMACmd*)res.host_va;
p_cmd->req_count = READ_WORD_LE_A(&cmd_host->req_count);
p_cmd->cmd_bits = cmd_host->cmd_bits;
p_cmd->cmd_key = cmd_host->cmd_key;
p_cmd->address = READ_DWORD_LE_A(&cmd_host->address);
p_cmd->cmd_arg = READ_DWORD_LE_A(&cmd_host->cmd_arg);
p_cmd->res_count = READ_WORD_LE_A(&cmd_host->res_count);
p_cmd->xfer_stat = READ_WORD_LE_A(&cmd_host->xfer_stat);
return cmd_host;
}
uint8_t DMAChannel::interpret_cmd() {
DMACmd cmd_struct;
bool is_writable, branch_taken = false;
MapDmaResult res;
if (this->cmd_in_progress) {
// return current command if there is data to transfer
if (this->queue_len)
return this->cur_cmd;
// obtain real pointer to the descriptor of the completed command
uint8_t *cmd_desc = mmu_get_dma_mem(this->cmd_ptr, 16, &is_writable);
// get command code
this->cur_cmd = cmd_desc[3] >> 4;
// all commands except STOP update cmd.xferStatus and
// perform actions under control of "i", "b" and "w" bits
if (this->cur_cmd < DBDMA_Cmd::STOP) {
if (is_writable)
WRITE_WORD_LE_A(&cmd_desc[14], this->ch_stat | CH_STAT_ACTIVE);
if (cmd_desc[2] & 3) {
ABORT_F("DBDMA: cmd.w bit not implemented");
}
// react to cmd.b bit
if (cmd_desc[2] & 0xC) {
bool cond = true;
if ((cmd_desc[2] & 0xC) != 0xC) {
uint16_t br_mask = this->branch_select >> 16;
cond = (this->ch_stat & br_mask) == (this->branch_select & br_mask);
if ((cmd_desc[2] & 0xC) == 0x8) { // branch if cond cleared?
cond = !cond;
}
}
if (cond) {
this->cmd_ptr = READ_DWORD_LE_A(&cmd_desc[8]);
branch_taken = true;
}
}
this->update_irq();
}
// all INPUT and OUTPUT commands update cmd.resCount
if (this->cur_cmd < DBDMA_Cmd::STORE_QUAD && is_writable) {
WRITE_WORD_LE_A(&cmd_desc[12], this->queue_len & 0xFFFFUL);
}
if (!branch_taken)
this->cmd_ptr += 16;
this->cmd_in_progress = false;
this->finish_cmd();
}
fetch_cmd(this->cmd_ptr, &cmd_struct);
bool cmd_is_writable;
DMACmd *cmd_host = fetch_cmd(this->cmd_ptr, &cmd_struct, &cmd_is_writable);
this->ch_stat &= ~CH_STAT_WAKE; // clear wake bit (DMA spec, 5.5.3.4)
@ -108,28 +79,43 @@ uint8_t DMAChannel::interpret_cmd() {
case DBDMA_Cmd::INPUT_MORE:
case DBDMA_Cmd::INPUT_LAST:
if (cmd_struct.cmd_key & 7) {
LOG_F(ERROR, "Key > 0 not implemented");
LOG_F(ERROR, "%s: Key > 0 not implemented", this->get_name().c_str());
break;
}
this->queue_data = mmu_get_dma_mem(cmd_struct.address, cmd_struct.req_count, &is_writable);
this->queue_len = cmd_struct.req_count;
this->cmd_in_progress = true;
if (this->queue_len) {
res = mmu_map_dma_mem(cmd_struct.address, cmd_struct.req_count, false);
this->queue_data = res.host_va;
this->res_count = 0;
this->cmd_in_progress = true;
} else
this->finish_cmd();
break;
case DBDMA_Cmd::STORE_QUAD:
LOG_F(ERROR, "Unsupported DMA Command STORE_QUAD");
if ((cmd_struct.cmd_key & 7) != 6)
LOG_F(ERROR, "%s: Invalid key %d in STORE_QUAD", this->get_name().c_str(),
cmd_struct.cmd_key & 7);
this->xfer_quad(&cmd_struct, nullptr);
break;
case DBDMA_Cmd::LOAD_QUAD:
LOG_F(ERROR, "Unsupported DMA Command LOAD_QUAD");
if ((cmd_struct.cmd_key & 7) != 6) {
LOG_F(ERROR, "%s: Invalid key %d in LOAD_QUAD", this->get_name().c_str(),
cmd_struct.cmd_key & 7);
}
if (!cmd_is_writable)
LOG_F(ERROR, "%s: DMACmd is not writeable!", this->get_name().c_str());
this->xfer_quad(&cmd_struct, cmd_host);
break;
case DBDMA_Cmd::NOP:
LOG_F(ERROR, "Unsupported DMA Command NOP");
this->finish_cmd();
break;
case DBDMA_Cmd::STOP:
this->ch_stat &= ~CH_STAT_ACTIVE;
this->cmd_in_progress = false;
break;
default:
LOG_F(ERROR, "Unsupported DMA command 0x%X", this->cur_cmd);
LOG_F(ERROR, "%s: Unsupported DMA command 0x%X", this->get_name().c_str(),
this->cur_cmd);
this->ch_stat |= CH_STAT_DEAD;
this->ch_stat &= ~CH_STAT_ACTIVE;
}
@ -137,56 +123,183 @@ uint8_t DMAChannel::interpret_cmd() {
return this->cur_cmd;
}
void DMAChannel::update_irq() {
bool is_writable;
void DMAChannel::finish_cmd() {
bool branch_taken = false;
// obtain real pointer to the descriptor of the command to be finished
MapDmaResult res = mmu_map_dma_mem(this->cmd_ptr, 16, false);
uint8_t *cmd_desc = res.host_va;
// get command code
this->cur_cmd = cmd_desc[3] >> 4;
// all commands except STOP update cmd.xferStatus and
// perform actions under control of "i" interrupt, "b" branch, and "w" wait bits
if (this->cur_cmd < DBDMA_Cmd::STOP) {
// react to cmd.w (wait) bits
if (cmd_desc[2] & 3) {
bool cond = true;
if ((cmd_desc[2] & 3) != 3) {
uint16_t wt_mask = this->wait_select >> 16;
cond = (this->ch_stat & wt_mask) == (this->wait_select & wt_mask);
if ((cmd_desc[2] & 3) == 2) {
cond = !cond; // wait if cond = false
}
}
if (cond)
return;
}
if (res.is_writable)
WRITE_WORD_LE_A(&cmd_desc[14], this->ch_stat | CH_STAT_ACTIVE);
// react to cmd.b (branch) bits
if (cmd_desc[2] & 0xC) {
bool cond = true;
if ((cmd_desc[2] & 0xC) != 0xC) {
uint16_t br_mask = this->branch_select >> 16;
cond = (this->ch_stat & br_mask) == (this->branch_select & br_mask);
if ((cmd_desc[2] & 0xC) == 0x8) {
cond = !cond; // branch if cond = false
}
}
if (cond) {
this->cmd_ptr = READ_DWORD_LE_A(&cmd_desc[8]);
branch_taken = true;
}
}
this->update_irq();
}
// all INPUT and OUTPUT commands including LOAD_QUAD and STORE_QUAD update cmd.resCount
if (this->cur_cmd < DBDMA_Cmd::NOP && res.is_writable) {
WRITE_WORD_LE_A(&cmd_desc[12], this->res_count);
this->queue_len = 0;
this->res_count = 0;
}
if (!branch_taken)
this->cmd_ptr += 16;
this->cmd_in_progress = false;
}
void DMAChannel::xfer_quad(const DMACmd *cmd_desc, DMACmd *cmd_host) {
MapDmaResult res;
uint32_t addr;
// parse and fix reqCount
uint32_t xfer_size = cmd_desc->req_count & 7;
if (xfer_size & 4) {
xfer_size = 4;
} else if (xfer_size & 2) {
xfer_size = 2;
} else {
xfer_size = 1;
}
this->res_count = cmd_desc->req_count; // this is the value that gets written to cmd.resCount
addr = cmd_desc->address;
if (addr & (xfer_size - 1)) {
LOG_F(ERROR, "%s: QUAD address 0x%08x is not aligned!", this->get_name().c_str(), addr);
addr &= ~(xfer_size - 1);
}
res = mmu_map_dma_mem(addr, xfer_size, true);
// prepare data pointers and perform data transfer
if (!cmd_host) {
if (res.type & RT_MMIO) {
res.dev_obj->write(res.dev_base, addr - res.dev_base, cmd_desc->cmd_arg, xfer_size);
} else if (res.is_writable) {
switch (xfer_size) {
case 1: *res.host_va = cmd_desc->cmd_arg; break;
case 2: WRITE_WORD_LE_A(res.host_va, cmd_desc->cmd_arg); break;
case 4: WRITE_DWORD_LE_A(res.host_va, cmd_desc->cmd_arg); break;
}
} else {
LOG_F(ERROR, "SOS: DMA access is not to RAM %08X!\n", addr);
}
} else {
uint32_t value;
if (res.type & RT_MMIO) {
value = res.dev_obj->read(res.dev_base, addr - res.dev_base, xfer_size);
} else {
switch (xfer_size) {
case 1: value = *res.host_va; break;
case 2: value = READ_WORD_LE_A(res.host_va); break;
case 4: value = READ_DWORD_LE_A(res.host_va); break;
default: value = 0; break;
}
}
WRITE_DWORD_LE_A(&cmd_host->cmd_arg, value);
}
if (cmd_desc->cmd_bits & 0xC)
ABORT_F("%s: cmd_bits.b should be zero for LOAD/STORE_QUAD!",
this->get_name().c_str());
this->finish_cmd();
}
void DMAChannel::update_irq() {
// obtain real pointer to the descriptor of the completed command
uint8_t *cmd_desc = mmu_get_dma_mem(this->cmd_ptr, 16, &is_writable);
MapDmaResult res = mmu_map_dma_mem(this->cmd_ptr, 16, false);
uint8_t *cmd_desc = res.host_va;
// STOP doesn't generate interrupts
if (this->cur_cmd < DBDMA_Cmd::STOP) {
// react to cmd.i (interrupt) bits
if (cmd_desc[2] & 0x30) {
bool cond = true;
if ((cmd_desc[2] & 0x30) != 0x30) {
uint16_t int_mask = this->int_select >> 16;
cond = (this->ch_stat & int_mask) == (this->int_select & int_mask);
if ((cmd_desc[2] & 0x30) == 0x20) { // branch if cond cleared?
cond = !cond;
if ((cmd_desc[2] & 0x30) == 0x20) {
cond = !cond; // generate interrupt if cond = false
}
}
if (cond) {
this->int_ctrl->ack_dma_int(this->irq_id, 1);
if (int_ctrl) {
TimerManager::get_instance()->add_immediate_timer([this] {
this->int_ctrl->ack_dma_int(this->irq_id, 1);
});
} else
LOG_F(ERROR, "%s Interrupt ignored", this->get_name().c_str());
}
}
}
}
uint32_t DMAChannel::reg_read(uint32_t offset, int size) {
uint32_t res = 0;
if (size != 4) {
ABORT_F("DBDMA: non-DWORD read from a DMA channel not supported");
ABORT_F("%s: non-DWORD read from a DMA channel not supported",
this->get_name().c_str());
}
switch (offset) {
case DMAReg::CH_CTRL:
res = 0; // ChannelControl reads as 0 (DBDMA spec 5.5.1, table 74)
break;
return 0; // ChannelControl reads as 0 (DBDMA spec 5.5.1, table 74)
case DMAReg::CH_STAT:
res = BYTESWAP_32(this->ch_stat);
break;
return BYTESWAP_32(this->ch_stat);
case DMAReg::CMD_PTR_LO:
return BYTESWAP_32(this->cmd_ptr);
default:
LOG_F(WARNING, "Unsupported DMA channel register 0x%X", offset);
LOG_F(WARNING, "%s: Unsupported DMA channel register read at 0x%X",
this->get_name().c_str(), offset);
}
return res;
return 0;
}
void DMAChannel::reg_write(uint32_t offset, uint32_t value, int size) {
uint16_t mask, old_stat, new_stat;
if (size != 4) {
ABORT_F("DBDMA: non-DWORD writes to a DMA channel not supported");
ABORT_F("%s: non-DWORD writes to a DMA channel not supported",
this->get_name().c_str());
}
value = BYTESWAP_32(value);
@ -196,7 +309,7 @@ void DMAChannel::reg_write(uint32_t offset, uint32_t value, int size) {
case DMAReg::CH_CTRL:
mask = value >> 16;
new_stat = (value & mask & 0xF0FFU) | (old_stat & ~mask);
LOG_F(9, "New ChannelStatus value = 0x%X", new_stat);
LOG_F(9, "%s: New ChannelStatus value = 0x%X", this->get_name().c_str(), new_stat);
// update ch_stat.s0...s7 if requested (needed for interrupt generation)
if ((new_stat & 0xFF) != (old_stat & 0xFF)) {
@ -240,17 +353,30 @@ void DMAChannel::reg_write(uint32_t offset, uint32_t value, int size) {
break;
case DMAReg::CH_STAT:
break; // ingore writes to ChannelStatus
case DMAReg::CMD_PTR_HI:
if (value != 0) {
LOG_F(WARNING, "%s: Unsupported DMA channel register write @%02x.%c = %0*x", this->get_name().c_str(), offset, SIZE_ARG(size), size * 2, value);
}
break;
case DMAReg::CMD_PTR_LO:
if (!(this->ch_stat & CH_STAT_RUN) && !(this->ch_stat & CH_STAT_ACTIVE)) {
this->cmd_ptr = value;
LOG_F(9, "CommandPtrLo set to 0x%X", this->cmd_ptr);
LOG_F(9, "%s: CommandPtrLo set to 0x%X", this->get_name().c_str(),
this->cmd_ptr);
}
break;
case DMAReg::INT_SELECT:
this->int_select = value & 0xFF00FFUL;
break;
case DMAReg::BRANCH_SELECT:
this->branch_select = value & 0xFF00FFUL;
break;
case DMAReg::WAIT_SELECT:
this->wait_select = value & 0xFF00FFUL;
break;
default:
LOG_F(WARNING, "Unsupported DMA channel register 0x%X", offset);
LOG_F(WARNING, "%s: Unsupported DMA channel register write at 0x%X",
this->get_name().c_str(), offset);
}
}
@ -260,7 +386,7 @@ DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
// dead or idle channel? -> no more data
LOG_F(WARNING, "Dead/idle channel -> no more data");
LOG_F(WARNING, "%s: Dead/idle channel -> no more data", this->get_name().c_str());
return DmaPullResult::NoMoreData;
}
@ -272,15 +398,18 @@ DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8
// dequeue data if any
if (this->queue_len) {
if (this->queue_len >= req_len) {
LOG_F(9, "Return req_len = %d data", req_len);
LOG_F(9, "%s: Return req_len = %d data", this->get_name().c_str(), req_len);
*p_data = this->queue_data;
*avail_len = req_len;
this->queue_len -= req_len;
this->res_count += req_len;
this->queue_data += req_len;
} else { // return less data than req_len
LOG_F(9, "Return queue_len = %d data", this->queue_len);
LOG_F(9, "%s: Return queue_len = %d data", this->get_name().c_str(),
this->queue_len);
*p_data = this->queue_data;
*avail_len = this->queue_len;
this->res_count += this->queue_len;
this->queue_len = 0;
}
return DmaPullResult::MoreData; // tell the caller there is more data
@ -291,7 +420,8 @@ DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8
int DMAChannel::push_data(const char* src_ptr, int len) {
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
LOG_F(WARNING, "DBDMA: attempt to push data to dead/idle channel");
LOG_F(WARNING, "%s: attempt to push data to dead/idle channel",
this->get_name().c_str());
return -1;
}
@ -304,6 +434,7 @@ int DMAChannel::push_data(const char* src_ptr, int len) {
len = std::min((int)this->queue_len, len);
std::memcpy(this->queue_data, src_ptr, len);
this->queue_data += len;
this->res_count += len;
this->queue_len -= len;
}
@ -315,7 +446,16 @@ int DMAChannel::push_data(const char* src_ptr, int len) {
return 0;
}
bool DMAChannel::is_active() {
bool DMAChannel::is_out_active() {
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
return false;
}
else {
return true;
}
}
bool DMAChannel::is_in_active() {
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
return false;
}
@ -326,33 +466,46 @@ bool DMAChannel::is_active() {
void DMAChannel::start() {
if (this->ch_stat & CH_STAT_PAUSE) {
LOG_F(WARNING, "Cannot start DMA channel, PAUSE bit is set");
LOG_F(WARNING, "%s: Cannot start DMA channel, PAUSE bit is set",
this->get_name().c_str());
return;
}
this->queue_len = 0;
this->cmd_in_progress = false;
if (this->start_cb)
this->start_cb();
// some DBDMA programs contain commands that don't transfer data
// between a device and memory (LOAD_QUAD, STORE_QUAD, NOP and STOP).
// We thus interprete the DBDMA program until a data transfer between
// a device and memory is queued or the channel becomes idle/dead.
while (!this->cmd_in_progress && !(this->ch_stat & CH_STAT_DEAD) &&
(this->ch_stat & CH_STAT_ACTIVE)) {
this->interpret_cmd();
}
}
void DMAChannel::resume() {
if (this->ch_stat & CH_STAT_PAUSE) {
LOG_F(WARNING, "Cannot resume DMA channel, PAUSE bit is set");
LOG_F(WARNING, "%s: Cannot resume DMA channel, PAUSE bit is set",
this->get_name().c_str());
return;
}
LOG_F(INFO, "Resuming DMA channel");
LOG_F(INFO, "%s: Resuming DMA channel", this->get_name().c_str());
}
void DMAChannel::abort() {
LOG_F(9, "Aborting DMA channel");
LOG_F(9, "%s: Aborting DMA channel", this->get_name().c_str());
if (this->stop_cb)
this->stop_cb();
}
void DMAChannel::pause() {
LOG_F(INFO, "Pausing DMA channel");
LOG_F(INFO, "%s: Pausing DMA channel", this->get_name().c_str());
if (this->stop_cb)
this->stop_cb();
}

View File

@ -30,35 +30,58 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define DB_DMA_H
#include <devices/common/dmacore.h>
#include <devices/common/hwinterrupt.h>
#include <cinttypes>
#include <functional>
class InterruptCtrl;
/** DBDMA Channel registers offsets */
enum DMAReg : uint32_t {
CH_CTRL = 0,
CH_STAT = 4,
CMD_PTR_HI = 8, // not implemented
CMD_PTR_LO = 12,
INT_SELECT = 16,
BRANCH_SELECT = 20,
WAIT_SELECT = 24,
// TANSFER_MODES = 28,
// DATA_2_PTR_HI = 32, // not implemented
// DATA_2_PTR_LO = 36,
// RESERVED_1 = 40,
// ADDRESS_HI = 44,
// RESERVED_2_0 = 48,
// RESERVED_2_1 = 52,
// RESERVED_2_2 = 56,
// RESERVED_2_3 = 60,
// UNIMPLEMENTED = 64,
// UNDEFINED = 128,
};
/** Channel Status bits (DBDMA spec, 5.5.3) */
enum : uint16_t {
CH_STAT_ACTIVE = 0x400,
CH_STAT_DEAD = 0x800,
CH_STAT_WAKE = 0x1000,
CH_STAT_FLUSH = 0x2000,
CH_STAT_PAUSE = 0x4000,
CH_STAT_RUN = 0x8000
CH_STAT_S0 = 0x0001, // general purpose status and control
CH_STAT_S1 = 0x0002, // general purpose status and control
CH_STAT_S2 = 0x0004, // general purpose status and control
CH_STAT_S3 = 0x0008, // general purpose status and control
CH_STAT_S4 = 0x0010, // general purpose status and control
CH_STAT_S5 = 0x0020, // general purpose status and control
CH_STAT_S6 = 0x0040, // general purpose status and control
CH_STAT_S7 = 0x0080, // general purpose status and control
CH_STAT_BT = 0x0100, // hardware status bit
CH_STAT_ACTIVE = 0x0400, // hardware status bit
CH_STAT_DEAD = 0x0800, // hardware status bit
CH_STAT_WAKE = 0x1000, // command bit set by software and cleared by hardware once the action has been performed
CH_STAT_FLUSH = 0x2000, // command bit set by software and cleared by hardware once the action has been performed
CH_STAT_PAUSE = 0x4000, // control bit set and cleared by software
CH_STAT_RUN = 0x8000 // control bit set and cleared by software
};
/** DBDMA command (DBDMA spec, 5.6.1) - all fields are little-endian! */
typedef struct DMACmd {
uint16_t req_count;
uint8_t cmd_bits;
uint8_t cmd_key;
uint8_t cmd_bits; // wait: & 3, branch: & 0xC, interrupt: & 0x30, reserved: & 0xc0
uint8_t cmd_key; // key: & 7, reserved: & 8, cmd: >> 4
uint32_t address;
uint32_t cmd_arg;
uint16_t res_count;
@ -82,14 +105,16 @@ typedef std::function<void(void)> DbdmaCallback;
class DMAChannel : public DmaBidirChannel {
public:
DMAChannel() = default;
DMAChannel(std::string name) : DmaBidirChannel(name) {}
~DMAChannel() = default;
void set_callbacks(DbdmaCallback start_cb, DbdmaCallback stop_cb);
uint32_t reg_read(uint32_t offset, int size);
void reg_write(uint32_t offset, uint32_t value, int size);
void set_stat(uint8_t new_stat) { this->ch_stat = (this->ch_stat & 0xff00) | new_stat; }
bool is_active();
bool is_out_active();
bool is_in_active();
DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data);
int push_data(const char* src_ptr, int len);
@ -99,8 +124,10 @@ public:
};
protected:
void fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd);
DMACmd* fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd, bool *is_writable);
uint8_t interpret_cmd(void);
void finish_cmd();
void xfer_quad(const DMACmd *cmd_desc, DMACmd *cmd_host);
void update_irq();
void start(void);
@ -116,8 +143,10 @@ private:
uint32_t cmd_ptr = 0;
uint32_t queue_len = 0;
uint8_t* queue_data = 0;
uint32_t res_count = 0;
uint32_t int_select = 0;
uint32_t branch_select = 0;
uint32_t wait_select = 0;
bool cmd_in_progress = false;
uint8_t cur_cmd;

View File

@ -24,7 +24,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef DMA_CORE_H
#define DMA_CORE_H
#include <cinttypes>
#include <cstdint>
#include <string>
enum DmaPullResult : int {
MoreData, // data source has more data to be pulled
@ -33,18 +34,41 @@ enum DmaPullResult : int {
class DmaOutChannel {
public:
virtual bool is_active() { return true; };
DmaOutChannel(std::string name) { this->name = name; };
virtual bool is_out_active() { return true; };
virtual DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len,
uint8_t **p_data) = 0;
std::string get_name(void) { return this->name; };
private:
std::string name;
};
class DmaInChannel {
public:
DmaInChannel(std::string name) { this->name = name; };
virtual bool is_in_active() { return true; };
virtual int push_data(const char* src_ptr, int len) = 0;
std::string get_name(void) { return this->name; };
private:
std::string name;
};
// Base class for bidirectional DMA channels.
class DmaBidirChannel : public DmaOutChannel, public DmaInChannel {
public:
DmaBidirChannel(std::string name) : DmaOutChannel(name + " Out"),
DmaInChannel(name + std::string(" In")) { this->name = name; };
std::string get_name(void) { return this->name; };
private:
std::string name;
};
#endif // DMA_CORE_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -26,29 +26,32 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <string>
/** types of different HW components */
enum HWCompType {
UNKNOWN = 0ULL, /* unknown component type */
MEM_CTRL = 1ULL << 0, /* memory controller */
NVRAM = 1ULL << 1, /* non-volatile random access memory */
ROM = 1ULL << 2, /* read-only memory */
RAM = 1ULL << 3, /* random access memory */
MMIO_DEV = 1ULL << 4, /* memory mapped I/O device */
PCI_HOST = 1ULL << 5, /* PCI host */
PCI_DEV = 1ULL << 6, /* PCI device */
I2C_HOST = 1ULL << 8, /* I2C host */
I2C_DEV = 1ULL << 9, /* I2C device */
ADB_HOST = 1ULL << 12, /* ADB host */
ADB_DEV = 1ULL << 13, /* ADB device */
INT_CTRL = 1ULL << 16, /* interrupt controller */
SCSI_BUS = 1ULL << 20, /* SCSI bus */
SCSI_HOST = 1ULL << 21, /* SCSI host adapter */
SCSI_DEV = 1ULL << 22, /* SCSI device */
IDE_BUS = 1ULL << 23, /* IDE bus */
IDE_DEV = 1ULL << 25, /* IDE device */
SND_CODEC = 1ULL << 30, /* Sound codec */
SND_SERVER = 1ULL << 31, /* host sound server */
FLOPPY_CTRL = 1ULL << 32, /* floppy disk controller */
FLOPPY_DRV = 1ULL << 33, /* floppy disk drive */
enum HWCompType : uint64_t {
UNKNOWN = 0ULL, // unknown component type
MEM_CTRL = 1ULL << 0, // memory controller
NVRAM = 1ULL << 1, // non-volatile random access memory
ROM = 1ULL << 2, // read-only memory
RAM = 1ULL << 3, // random access memory
MMIO_DEV = 1ULL << 4, // memory mapped I/O device
PCI_HOST = 1ULL << 5, // PCI host
PCI_DEV = 1ULL << 6, // PCI device
I2C_HOST = 1ULL << 8, // I2C host
I2C_DEV = 1ULL << 9, // I2C device
ADB_HOST = 1ULL << 12, // ADB host
ADB_DEV = 1ULL << 13, // ADB device
IOBUS_HOST = 1ULL << 14, // IOBus host
IOBUS_DEV = 1ULL << 15, // IOBus device
INT_CTRL = 1ULL << 16, // interrupt controller
SCSI_BUS = 1ULL << 20, // SCSI bus
SCSI_HOST = 1ULL << 21, // SCSI host adapter
SCSI_DEV = 1ULL << 22, // SCSI device
IDE_BUS = 1ULL << 23, // IDE bus
IDE_DEV = 1ULL << 25, // IDE device
SND_CODEC = 1ULL << 30, // sound codec
SND_SERVER = 1ULL << 31, // host sound server
FLOPPY_CTRL = 1ULL << 32, // floppy disk controller
FLOPPY_DRV = 1ULL << 33, // floppy disk drive
ETHER_MAC = 1ULL << 40, // Ethernet media access controller
};
/** Base class for HW components. */
@ -81,4 +84,4 @@ protected:
uint64_t supported_types = HWCompType::UNKNOWN;
};
#endif /* HW_COMPONENT_H */
#endif // HW_COMPONENT_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -28,14 +28,49 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
/** Enumerator for various interrupt sources. */
enum IntSrc : uint32_t {
VIA_CUDA = 1,
SCSI1 = 2,
SWIM3 = 3,
SCC = 4,
ETHERNET = 5,
NMI = 6,
IDE0 = 7,
IDE1 = 8,
INT_UNKNOWN = 0,
VIA_CUDA,
SCSI_MESH,
SCSI_CURIO,
SWIM3,
SCCA,
SCCB,
ETHERNET,
NMI,
EXT1,
IDE0,
IDE1,
DAVBUS,
PERCH1,
PERCH2,
PCI_A,
PCI_B,
PCI_C,
PCI_D,
PCI_E,
PCI_F,
PCI_GPU,
PCI_PERCH,
BANDIT1,
BANDIT2,
CONTROL,
SIXTY6,
PLANB,
VCI,
PLATINUM,
DMA_SCSI_MESH,
DMA_SCSI_CURIO,
DMA_SWIM3,
DMA_IDE0,
DMA_IDE1,
DMA_SCCA_Tx,
DMA_SCCA_Rx,
DMA_SCCB_Tx,
DMA_SCCB_Rx,
DMA_DAVBUS_Tx,
DMA_DAVBUS_Rx,
DMA_ETHERNET_Tx,
DMA_ETHERNET_Rx,
};
/** Base class for interrupt controllers. */

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -40,14 +40,28 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
AthensClocks::AthensClocks(uint8_t dev_addr)
{
set_name("Athens");
supports_types(HWCompType::I2C_DEV);
this->my_addr = dev_addr;
// set up power on values
// This initialization is not prescribed
// but let's set them to acceptable values anyway
this->regs[AthensRegs::D2] = 2;
this->regs[AthensRegs::N2] = 2;
// set P2_MUX2 on power up as follows:
// - dot clock VCO is disabled
// - dot clock = reference clock / 2
this->regs[AthensRegs::P2_MUX2] = 0x62;
}
AthensClocks::AthensClocks(uint8_t dev_addr, const float crystal_freq)
: AthensClocks(dev_addr)
{
this->xtal_freq = crystal_freq;
}
void AthensClocks::start_transaction()
{
this->pos = 0; // reset read/write position
@ -55,7 +69,7 @@ void AthensClocks::start_transaction()
bool AthensClocks::send_subaddress(uint8_t sub_addr)
{
LOG_F(INFO, "Athens: subaddress set to 0x%X", sub_addr);
LOG_F(INFO, "%s: subaddress set to 0x%X", this->name.c_str(), sub_addr);
return true;
}
@ -68,16 +82,14 @@ bool AthensClocks::send_byte(uint8_t data)
break;
case 1:
if (this->reg_num >= ATHENS_NUM_REGS) {
LOG_F(WARNING, "Athens: invalid register number %d", this->reg_num);
LOG_F(WARNING, "%s: invalid register number %d", this->name.c_str(),
this->reg_num);
return false; // return NACK
}
this->regs[this->reg_num] = data;
if (reg_num == 3) {
LOG_F(INFO, "Athens: dot clock frequency set to %d Hz", get_dot_freq());
}
break;
default:
LOG_F(WARNING, "Athens: too much data received!");
LOG_F(WARNING, "%s: too much data received!", this->name.c_str());
return false; // return NACK
}
return true;
@ -102,7 +114,7 @@ int AthensClocks::get_dot_freq()
};
if (this->regs[AthensRegs::P2_MUX2] & 0xC0) {
LOG_F(INFO, "Athens: dot clock disabled");
LOG_F(INFO, "%s: dot clock disabled", this->name.c_str());
return 0;
}
@ -114,29 +126,31 @@ int AthensClocks::get_dot_freq()
int post_div = 1 << (3 - (this->regs[AthensRegs::P2_MUX2] & 3));
if (std::find(D2_commons.begin(), D2_commons.end(), d2) == D2_commons.end()) {
LOG_F(WARNING, "Athens: untested D2 value %d", d2);
LOG_F(WARNING, "%s: untested D2 value %d", this->name.c_str(), d2);
}
if (std::find(N2_commons.begin(), N2_commons.end(), n2) == N2_commons.end()) {
LOG_F(WARNING, "Athens: untested N2 value %d", d2);
LOG_F(WARNING, "%s: untested N2 value %d", this->name.c_str(), d2);
}
int mux = (this->regs[AthensRegs::P2_MUX2] >> 4) & 3;
switch (mux) {
case 0: // clock source -> dot cock VCO
out_freq = ATHENS_XTAL * ((float)n2 / (float)(d2 * post_div));
out_freq = this->xtal_freq * ((float)n2 / (float)(d2 * post_div));
break;
case 1: // clock source -> system clock VCO
LOG_F(WARNING, "Athens: system clock VCO not supported yet!");
LOG_F(WARNING, "%s: system clock VCO not supported yet!", this->name.c_str());
break;
case 2: // clock source -> crystal frequency
out_freq = ATHENS_XTAL / post_div;
out_freq = this->xtal_freq / post_div;
break;
case 3:
LOG_F(WARNING, "Athens: attempt to use reserved Mux value!");
LOG_F(WARNING, "%s: attempt to use reserved Mux value!", this->name.c_str());
break;
}
LOG_F(INFO, "%s: dot clock frequency set to %f Hz", this->name.c_str(), out_freq);
return static_cast<int>(out_freq + 0.5f);
}

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -31,7 +31,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define ATHENS_NUM_REGS 8
constexpr auto ATHENS_XTAL = 31334400.0f; // external crystal oscillator frequency
/** Default external crystal oscillator frequency. */
constexpr auto ATHENS_XTAL = 31334400.0f;
namespace AthensRegs {
@ -52,6 +53,7 @@ class AthensClocks : public I2CDevice, public HWComponent
{
public:
AthensClocks(uint8_t dev_addr);
AthensClocks(uint8_t dev_addr, const float crystal_freq);
~AthensClocks() = default;
// I2CDevice methods
@ -68,8 +70,9 @@ private:
uint8_t my_addr = 0;
uint8_t reg_num = 0;
int pos = 0;
float xtal_freq = ATHENS_XTAL;
uint8_t regs[ATHENS_NUM_REGS];
uint8_t regs[ATHENS_NUM_REGS] = {};
};
#endif // ATHENS_H

View File

@ -26,7 +26,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cinttypes>
#include <cstring>
#include <memory>
I2CProm::I2CProm(uint8_t dev_addr, int size)
{

View File

@ -0,0 +1,72 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file SAA7187 Digital video encoder emulation. */
#include <devices/common/i2c/saa7187.h>
#include <loguru.hpp>
Saa7187VideoEncoder::Saa7187VideoEncoder(uint8_t dev_addr)
{
supports_types(HWCompType::I2C_DEV);
this->my_addr = dev_addr;
}
void Saa7187VideoEncoder::start_transaction()
{
LOG_F(INFO, "Saa7187: start_transaction");
this->pos = 0; // reset read/write position
}
bool Saa7187VideoEncoder::send_subaddress(uint8_t sub_addr)
{
LOG_F(ERROR, "Saa7187: send_subaddress 0x%02x", sub_addr);
//this->pos = sub_addr;
return true;
}
bool Saa7187VideoEncoder::send_byte(uint8_t data)
{
if (this->pos == 0) {
this->reg_num = data;
LOG_F(INFO, "Saa7187: write 0x%02x", data);
}
else {
if (this->reg_num < Saa7187Regs::last_reg) {
this->regs[this->reg_num] = data;
LOG_F(INFO, "Saa7187: write 0x%02x = 0x%02x", this->reg_num, data);
}
else {
LOG_F(ERROR, "Saa7187: write 0x%02x = 0x%02x", this->reg_num, data);
}
this->reg_num++;
}
this->pos++;
return true;
}
bool Saa7187VideoEncoder::receive_byte(uint8_t* p_data)
{
LOG_F(ERROR, "Saa7187: receive_byte");
*p_data = 0x00;
return true;
}

View File

@ -0,0 +1,141 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file SAA7187 Digital video encoder definitions. */
#ifndef SAA7187_H
#define SAA7187_H
#include <devices/common/hwcomponent.h>
#include <devices/common/i2c/i2c.h>
namespace Saa7187Regs {
enum Saa7187Regs : uint8_t { // i2c address is 0x44; fatman is at 0x46?
IPC = 0x3a, // Input port control
FMT0 = 0x01,
FMT1 = 0x02,
VUV2C = 0x04,
VY2C = 0x08,
CBENB = 0x80,
OSDY0 = 0x42, // OSD LUT, 8 colors
OSDU0 ,
OSDV0 ,
CHPS = 0x5a, // Chrominance phase
GAINU = 0x5b, // Gain U
GAINV = 0x5c, // Gain V
BLCKL = 0x5d, // Gain U MSB, black level
BLCKL0_5 = 0x3f,
GAINU8 = 0x80,
BLNNL = 0x5e, // Gain V MSB, blanking level
BLNNL0_5 = 0x3f,
GAINV8 = 0x80,
CCRS = 0x60, // Cross-colour select
CCRS0 = 0x40,
CCRS1 = 0x80,
SCTRL = 0x61, // Standard control
FISE = 0x01,
PAL = 0x02,
SCBW = 0x04,
RTCE = 0x08,
YGS = 0x10,
INPI1 = 0x20,
DOWN = 0x40,
BSTA = 0x62, // Burst amplitude
BSTA0_6 = 0x7f,
SQP = 0x80,
FSC0 = 0x63, // Subcarrier 0
FSC8 = 0x64, // Subcarrier 1
FSC16 = 0x65, // Subcarrier 2
FSC24 = 0x66, // Subcarrier 3
// 0x25555555 = NTSC
// 0x26798c0c = PAL
L21O0 = 0x67, // Line 21 odd 0
L21O1 = 0x68, // Line 21 odd 1
L21E0 = 0x69, // Line 21 even 0
L21E1 = 0x6a, // Line 21 even 1
SCCLN = 0x6b, // CC line
SCCLN0_4 = 0x1f,
RCTRL = 0x6c, // RCV port control
PRCV2 = 0x01,
ORCV2 = 0x02,
CBLF = 0x04,
PRCV1 = 0x08,
ORCV1 = 0x10,
TRCV2 = 0x20,
SRCV10 = 0x40,
RCV11 = 0x80,
RCM_CC = 0x6d, // RCM, CC mode
CCEN0 = 0x01,
CCEN1 = 0x02,
SRCM10 = 0x04,
SRCM11 = 0x08,
HTRIG0 = 0x6e, // Horizontal trigger
HTRIG8 = 0x6f, // Horizontal trigger
HTRIG8_10 = 0x07,
RES_VTRIG = 0x70, // fsc reset mode, Vertical trigger
VTRIG0_4 = 0x1f,
SBLBN = 0x20,
PHRES0 = 0x40,
HRES1 = 0x80,
BMRQ0 = 0x71, // Begin master request
EMRQ0 = 0x72, // End master request
BMRQ8_EMRQ8 = 0x73, // MSBs master request
BMRQ08_10 = 0x07,
EMRQ08_10 = 0x70,
BRCV0 = 0x77, // Begin RCV2 output
ERCV0 = 0x78, // End RCV2 output
BRCV8_ERCV8 = 0x79, // MSBs RCV2 output
BRCV08_10 = 0x07,
ERCV08_10 = 0x70,
FLEN = 0x7a, // Field length
FAL = 0x7b, // First active line
LAL = 0x7c, // Last active line
FLEN8_FAL8_LAL8 = 0x7d, // MSBs field control
FLEN8_9 = 0x03,
FAL8 = 0x10,
LAL8 = 0x20,
last_reg = 0x7e,
};
}; // namespace Saa7187Regs
class Saa7187VideoEncoder : public I2CDevice, public HWComponent
{
public:
Saa7187VideoEncoder(uint8_t dev_addr);
~Saa7187VideoEncoder() = default;
// I2CDevice methods
void start_transaction();
bool send_subaddress(uint8_t sub_addr);
bool send_byte(uint8_t data);
bool receive_byte(uint8_t* p_data);
private:
uint8_t my_addr = 0;
uint8_t reg_num = 0;
int pos = 0;
uint8_t regs[Saa7187Regs::last_reg] = {0};
};
#endif // SAA7187_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-21 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -24,11 +24,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/hwcomponent.h>
#include <devices/common/mmiodevice.h>
#include <devices/ioctrl/macio.h>
#include <cinttypes>
#include <loguru.hpp>
#include <string>
/**
@file Contains definitions for PowerMacintosh machine ID registers.
@file Contains definitions for Power Macintosh machine ID registers.
The machine ID register is a memory-based register containing hardcoded
values the system software can read to identify machine/board it's running on.
@ -40,7 +43,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
Machine ID register for Nubus Power Macs.
It's located at physical address 0x5FFFFFFC and contains four bytes:
+0 uint16_t signature = 0xA55A
+1 uint8_t machine_type (3 - PowerMac)
+1 uint8_t machine_type (3 - Power Mac)
+2 uint8_t model (0x10 = PDM, 0x12 = Carl Sagan, 0x13 = Cold Fusion)
*/
class NubusMacID : public MMIODevice {
@ -56,7 +59,14 @@ public:
~NubusMacID() = default;
uint32_t read(uint32_t rgn_start, uint32_t offset, int size) {
return (offset < 4 ? this->id[offset] : 0);
if (size == 4 && offset == 0) {
return *(uint32_t*)this->id;
}
if (size == 1 && offset < 4) {
return this->id[offset];
}
ABORT_F("NubusMacID: invalid read size %d, offset %d!", size, offset);
return 0;
};
/* not writable */
@ -66,6 +76,36 @@ private:
uint8_t id[4];
};
/**
TNT-style machines and derivatives provide two board registers
telling whether some particular piece of HW is installed or not.
Both board registers are attached to the IOBus of the I/O controller.
See machines/machinetnt.cpp for further details.
**/
class BoardRegister : public HWComponent, public IobusDevice {
public:
BoardRegister(std::string name, const uint16_t data) {
this->set_name(name);
supports_types(HWCompType::IOBUS_DEV);
this->data = data;
};
~BoardRegister() = default;
uint16_t iodev_read(uint32_t address) {
return this->data;
};
// appears read-only to guest
void iodev_write(uint32_t address, uint16_t value) {};
void update_bits(const uint16_t val, const uint16_t mask) {
this->data = (this->data & ~mask) | (val & mask);
};
private:
uint16_t data;
};
/**
The machine ID for the Gossamer board is accesible at 0xFF000004 (phys).
It contains a 16-bit value revealing machine's capabilities like bus speed,
@ -92,4 +132,4 @@ private:
uint16_t id;
};
#endif /* MACHINE_ID_H */
#endif // MACHINE_ID_H

View File

@ -36,4 +36,7 @@ public:
virtual ~MMIODevice() = default;
};
#define SIZE_ARG(size) (size == 4 ? 'l' : size == 2 ? 'w' : \
size == 1 ? 'b' : '0' + size)
#endif /* MMIO_DEVICE_H */

View File

@ -26,7 +26,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cinttypes>
#include <cstring>
#include <fstream>
#include <iostream>
#include <loguru.hpp>
/** @file Non-volatile RAM implementation.
@ -74,7 +73,8 @@ void NVram::init() {
!f.read((char*)&data_size, sizeof(data_size)) ||
memcmp(sig, NVRAM_FILE_ID, sizeof(NVRAM_FILE_ID)) || data_size != this->ram_size ||
!f.read((char*)this->storage.get(), this->ram_size)) {
LOG_F(WARNING, "Could not restore NVRAM content from the given file.");
LOG_F(WARNING, "Could not restore NVRAM content from the given file \"%s\".", this->file_name.c_str());
memset(this->storage.get(), 0, this->ram_size);
}
f.close();

View File

@ -22,8 +22,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
/** Utilities for working with Apple Open Firmware and CHRP NVRAM partitions. */
#include <devices/common/ofnvram.h>
#include <devices/common/nvram.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <machines/machinebase.h>
#include <memaccess.h>
@ -89,7 +89,7 @@ bool OfConfigAppl::validate() {
return false;
return true;
};
}
uint16_t OfConfigAppl::checksum_partition() {
uint32_t acc = 0;
@ -178,7 +178,7 @@ const OfConfigImpl::config_dict& OfConfigAppl::get_config_vars() {
}
return _config_vars;
};
}
void OfConfigAppl::update_partition() {
// set checksum in the header to zero
@ -300,7 +300,7 @@ bool OfConfigAppl::set_var(std::string& var_name, std::string& value) {
}
return true;
};
}
uint8_t OfConfigChrp::checksum_hdr(const uint8_t* data)
{
@ -391,27 +391,47 @@ const OfConfigImpl::config_dict& OfConfigChrp::get_config_vars() {
this->_config_vars.clear();
this->data_length = 0;
if (!this->validate())
return _config_vars;
for (int pos = 0; pos < 4096;) {
char *pname = (char *)&this->buf[pos];
bool got_name = false;
// scan property name until '=' is encountered
// or max length is reached
for (len = 0; len < 32; pos++, len++) {
if (pname[len] == '=' || pname[len] == '\0')
for (len = 0; ; pos++, len++) {
if (len >= 32) {
cout << "name > 31 chars" << endl;
break;
}
if (pos >= 4096) {
cout << "no = sign before end of partition" << endl;
break;
}
if (pname[len] == '=') {
if (len) {
got_name = true;
}
else {
cout << "got = sign but no name" << endl;
}
break;
}
if (pname[len] == '\0') {
if (len) {
cout << "no = sign before termminating null" << endl;
}
else {
// empty property name -> free space reached
}
break;
}
}
// empty property name -> free space reached
if (!len) {
this->data_length = pos;
break;
}
if (pname[len] != '=') {
cout << "no = sign found or name > 31 chars" << endl;
if (!got_name) {
break;
}
@ -436,8 +456,11 @@ const OfConfigImpl::config_dict& OfConfigChrp::get_config_vars() {
this->_config_vars.push_back(std::make_pair(prop_name, pval));
pos++; // skip past null terminator
this->data_length = pos; // point to after null
}
//cout << "Read " << this->data_length << " bytes from nvram." << endl;
return this->_config_vars;
}
@ -447,7 +470,7 @@ bool OfConfigChrp::update_partition() {
memset(this->buf, 0, 4096);
for (auto& var : this->_config_vars) {
if ((var.first.length() + var.second.length() + 2) >= 4096) {
if ((pos + var.first.length() + var.second.length() + 2) > 4096) {
cout << "No room in the partition!" << endl;
return false;
}
@ -464,6 +487,7 @@ bool OfConfigChrp::update_partition() {
this->nvram_obj->write_byte(this->data_offset + i, this->buf[i]);
}
//cout << "Wrote " << pos << " bytes to nvram." << endl;
return true;
}
@ -471,25 +495,25 @@ bool OfConfigChrp::set_var(std::string& var_name, std::string& value) {
if (!this->validate())
return false;
bool found = false;
// see if we're about to change a flag
if (var_name.back() == '?') {
if (value != "true" && value != "false") {
cout << "Flag value can be 'true' or 'false'" << endl;
return false;
}
}
unsigned free_space = 4096 - this->data_length;
bool found = false;
// see if the user tries to change an existing property
for (auto& var : this->_config_vars) {
if (var.first == var_name) {
found = true;
// see if we're about to change a flag
if (var_name.back() == '?') {
if (value != "true" && value != "false") {
cout << "Flag value can be 'true' or 'false'" << endl;
return false;
}
}
if (value.length() > var.second.length()) {
unsigned free_space = 4096 - this->data_length;
if ((value.length() - var.second.length()) >= free_space) {
cout << "No room for new data!" << endl;
if ((value.length() - var.second.length()) > free_space) {
cout << "No room for updated nvram variable!" << endl;
return false;
}
}
@ -500,12 +524,15 @@ bool OfConfigChrp::set_var(std::string& var_name, std::string& value) {
}
if (!found) {
cout << "Attempt to change unknown variable " << var_name << endl;
return false;
if ((var_name.length() + value.length() + 2) > free_space) {
cout << "No room for new nvram variable!" << endl;
return false;
}
this->_config_vars.push_back(std::make_pair(var_name, value));
}
return this->update_partition();
};
}
int OfConfigUtils::init()
{
@ -541,16 +568,27 @@ bool OfConfigUtils::open_container() {
return false;
}
void OfConfigUtils::printenv() {
OfConfigImpl::config_dict vars;
static std::string ReplaceAll(std::string& str, const std::string& from, const std::string& to) {
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
}
return str;
}
void OfConfigUtils::printenv() {
if (!this->open_container())
return;
vars = this->cfg_impl->get_config_vars();
OfConfigImpl::config_dict vars = this->cfg_impl->get_config_vars();
for (auto& var : vars) {
cout << setw(34) << left << var.first << var.second << endl;
std::string val = var.second;
ReplaceAll(val, "\r\n", "\n");
ReplaceAll(val, "\r", "\n");
ReplaceAll(val, "\n", "\n "); // 34 spaces
cout << setw(34) << left << var.first << val << endl; // name column has width 34
}
}
@ -559,6 +597,8 @@ void OfConfigUtils::setenv(string var_name, string value)
if (!this->open_container())
return;
OfConfigImpl::config_dict vars = this->cfg_impl->get_config_vars();
if (!this->cfg_impl->set_var(var_name, value)) {
cout << " Please try again" << endl;
} else {

View File

@ -24,15 +24,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef OF_NVRAM_H
#define OF_NVRAM_H
#include <devices/common/nvram.h>
#include <cinttypes>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
class NVram;
/** ========== Apple Open Firmware 1.x/2.x partition definitions. ========== */
#define OF_NVRAM_OFFSET 0x1800
#define OF_NVRAM_SIG 0x1275

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -27,7 +27,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <endianswap.h>
#include <loguru.hpp>
#include <machines/machinebase.h>
#include <memaccess.h>
#include <cinttypes>
@ -80,6 +79,8 @@ uint32_t BanditPciDevice::pci_cfg_read(uint32_t reg_offs, AccessDetails &details
return this->mode_ctrl;
case BANDIT_ARBUS_RD_HOLD_OFF:
return this->rd_hold_off_cnt;
case BANDIT_DELAYED_AACK: // BANDIT_ONS
return 0;
default:
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
}
@ -104,6 +105,9 @@ void BanditPciDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDet
case BANDIT_ARBUS_RD_HOLD_OFF:
this->rd_hold_off_cnt = value & 0x1F;
return;
case BANDIT_DELAYED_AACK:
// implement this for CATALYST and Platinum
return;
default:
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
}
@ -144,17 +148,20 @@ uint32_t BanditHost::read(uint32_t rgn_start, uint32_t offset, int size)
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
AccessDetails details;
PCIDevice *device;
PCIBase *device;
cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device);
details.flags |= PCI_CONFIG_READ;
if (device) {
return pci_conv_rd_data(device->pci_cfg_read(reg_offs, details), details);
uint32_t value = device->pci_cfg_read(reg_offs, details);
// bytes 4 to 7 are random on bandit but
// we choose to repeat bytes 0 to 3 like grackle
return pci_conv_rd_data(value, value, details);
}
LOG_READ_NON_EXISTENT_PCI_DEVICE();
return 0xFFFFFFFFUL; // PCI spec §6.1
return 0xFFFFFFFFUL; // PCI spec §6.1
case 2: // CONFIG_ADDR
return BYTESWAP_32(this->config_addr);
return (this->is_aspen) ? this->config_addr : BYTESWAP_32(this->config_addr);
default: // I/O space
return pci_io_read_broadcast(offset, size);
@ -168,7 +175,7 @@ void BanditHost::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
AccessDetails details;
PCIDevice *device;
PCIBase *device;
cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device);
details.flags |= PCI_CONFIG_WRITE;
if (device) {
@ -186,7 +193,7 @@ void BanditHost::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int
break;
case 2: // CONFIG_ADDR
this->config_addr = BYTESWAP_32(value);
this->config_addr = (this->is_aspen) ? value : BYTESWAP_32(value);
break;
default: // I/O space
@ -196,9 +203,8 @@ void BanditHost::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int
inline void BanditHost::cfg_setup(uint32_t offset, int size, int &bus_num,
int &dev_num, int &fun_num, uint8_t &reg_offs,
AccessDetails &details, PCIDevice *&device)
AccessDetails &details, PCIBase *&device)
{
device = NULL;
details.size = size;
details.offset = offset & 3;
fun_num = FUN_NUM();
@ -212,28 +218,43 @@ inline void BanditHost::cfg_setup(uint32_t offset, int size, int &bus_num,
}
details.flags = PCI_CONFIG_TYPE_0;
bus_num = 0; // use dummy value for bus number
uint32_t idsel = this->config_addr & 0xFFFFF800U;
if (!SINGLE_BIT_SET(idsel)) {
for (dev_num = -1, idsel = this->config_addr; idsel; idsel >>= 1, dev_num++) {}
LOG_F(ERROR, "%s: config_addr 0x%08x does not contain valid IDSEL",
this->name.c_str(), (uint32_t)this->config_addr);
return;
}
dev_num = WHAT_BIT_SET(idsel);
if (this->dev_map.count(DEV_FUN(dev_num, fun_num))) {
device = this->dev_map[DEV_FUN(dev_num, fun_num)];
if (is_aspen)
dev_num = (this->config_addr >> 11) + 11; // IDSEL = 1 << (dev_num + 11)
else {
uint32_t idsel = this->config_addr & 0xFFFFF800U;
if (!SINGLE_BIT_SET(idsel)) {
for (dev_num = -1, idsel = this->config_addr; idsel; idsel >>= 1, dev_num++) {}
LOG_F(ERROR, "%s: config_addr 0x%08x does not contain valid IDSEL",
this->name.c_str(), (uint32_t)this->config_addr);
device = NULL;
return;
}
dev_num = WHAT_BIT_SET(idsel);
}
device = pci_find_device(dev_num, fun_num);
}
int BanditHost::device_postinit()
{
int BanditHost::device_postinit() {
std::string pci_dev_name;
static const std::map<std::string, int> pci_slots = {
static const std::map<std::string, int> pci_slots1 = {
{"pci_A1", DEV_FUN(0xD,0)}, {"pci_B1", DEV_FUN(0xE,0)}, {"pci_C1", DEV_FUN(0xF,0)}
};
for (auto& slot : pci_slots) {
static const std::map<std::string, int> pci_slots2 = {
{"pci_D2", DEV_FUN(0xD,0)}, {"pci_E2", DEV_FUN(0xE,0)}, {"pci_F2", DEV_FUN(0xF,0)}
};
static const std::map<std::string, int> vci_slots = {
{"vci_D", DEV_FUN(0xD,0)}, {"vci_E", DEV_FUN(0xE,0)}, {"vci_F", DEV_FUN(0xF,0)}
};
for (auto& slot :
this->bridge_num == 0 ? vci_slots :
this->bridge_num == 1 ? pci_slots1 :
this->bridge_num == 2 ? pci_slots2 :
pci_slots1
) {
pci_dev_name = GET_STR_PROP(slot.first);
if (!pci_dev_name.empty()) {
this->attach_pci_device(pci_dev_name, slot.second);
@ -243,7 +264,7 @@ int BanditHost::device_postinit()
}
Bandit::Bandit(int bridge_num, std::string name, int dev_id, int rev)
: BanditHost()
: BanditHost(bridge_num)
{
this->name = name;
@ -269,7 +290,7 @@ Bandit::Bandit(int bridge_num, std::string name, int dev_id, int rev)
this->pci_register_device(DEV_FUN(BANDIT_DEV,0), this->my_pci_device.get());
}
Chaos::Chaos(std::string name) : BanditHost()
Chaos::Chaos(std::string name) : BanditHost(0)
{
this->name = name;
@ -285,7 +306,24 @@ Chaos::Chaos(std::string name) : BanditHost()
mem_ctrl->add_mmio_region(0xF0000000UL, 0x01000000, this);
}
static const PropMap Bandit_Properties = {
AspenPci::AspenPci(std::string name) : BanditHost(1) {
this->name = name;
supports_types(HWCompType::PCI_HOST);
this->is_aspen = true;
MemCtrlBase *mem_ctrl = dynamic_cast<MemCtrlBase *>
(gMachineObj->get_comp_by_type(HWCompType::MEM_CTRL));
// add memory mapped I/O region for Aspen PCI control registers
// This region has the following layout:
// base_addr + 0x800000 --> CONFIG_ADDR
// base_addr + 0xC00000 --> CONFIG_DATA
mem_ctrl->add_mmio_region(0xF2000000UL, 0x01000000, this);
}
static const PropMap Bandit1_Properties = {
{"pci_A1",
new StrProperty("")},
{"pci_B1",
@ -294,18 +332,46 @@ static const PropMap Bandit_Properties = {
new StrProperty("")},
};
static const PropMap Bandit2_Properties = {
{"pci_D2",
new StrProperty("")},
{"pci_E2",
new StrProperty("")},
{"pci_F2",
new StrProperty("")},
};
static const PropMap Chaos_Properties = {
{"vci_D",
new StrProperty("")},
{"vci_E",
new StrProperty("")},
{"vci_F",
new StrProperty("")},
};
static const DeviceDescription Bandit1_Descriptor = {
Bandit::create_first, {}, Bandit_Properties
Bandit::create_first, {}, Bandit1_Properties
};
static const DeviceDescription Bandit2_Descriptor = {
Bandit::create_second, {}, Bandit2_Properties
};
static const DeviceDescription PsxPci1_Descriptor = {
Bandit::create_psx_first, {}, Bandit_Properties
Bandit::create_psx_first, {}, Bandit1_Properties
};
static const DeviceDescription Chaos_Descriptor = {
Chaos::create, {}, {}
Chaos::create, {}, Chaos_Properties
};
REGISTER_DEVICE(Bandit1, Bandit1_Descriptor);
REGISTER_DEVICE(PsxPci1, PsxPci1_Descriptor);
REGISTER_DEVICE(Chaos, Chaos_Descriptor);
static const DeviceDescription AspenPci1_Descriptor = {
AspenPci::create, {}, Bandit1_Properties
};
REGISTER_DEVICE(Bandit1, Bandit1_Descriptor);
REGISTER_DEVICE(Bandit2, Bandit2_Descriptor);
REGISTER_DEVICE(PsxPci1, PsxPci1_Descriptor);
REGISTER_DEVICE(AspenPci1, AspenPci1_Descriptor);
REGISTER_DEVICE(Chaos, Chaos_Descriptor);

View File

@ -60,6 +60,7 @@ enum {
BANDIT_ADDR_MASK = 0x48,
BANDIT_MODE_SELECT = 0x50,
BANDIT_ARBUS_RD_HOLD_OFF = 0x58,
BANDIT_DELAYED_AACK = 0x60,
};
/** checks if one bit is set at time, return 0 if not */
@ -70,6 +71,11 @@ enum {
*/
class BanditHost : public PCIHost, public MMIODevice {
public:
BanditHost(int bridge_num) { this->bridge_num = bridge_num; };
// PCIHost methods
virtual void pci_interrupt(uint8_t irq_line_state, PCIBase *dev) {}
// MMIODevice methods
uint32_t read(uint32_t rgn_start, uint32_t offset, int size);
void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size);
@ -78,11 +84,13 @@ public:
protected:
uint32_t config_addr;
int bridge_num;
bool is_aspen = false;
private:
void cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num,
int &fun_num, uint8_t &reg_offs, AccessDetails &details,
PCIDevice *&device);
PCIBase *&device);
};
/*
@ -118,13 +126,17 @@ public:
return std::unique_ptr<Bandit>(new Bandit(1, "Bandit-PCI1"));
};
static std::unique_ptr<HWComponent> create_second() {
return std::unique_ptr<Bandit>(new Bandit(2, "Bandit-PCI2"));
};
static std::unique_ptr<HWComponent> create_psx_first() {
return std::unique_ptr<Bandit>(new Bandit(1, "PSX-PCI1", 8, 0));
};
private:
uint32_t base_addr;
unique_ptr<BanditPciDevice> my_pci_device;
std::unique_ptr<BanditPciDevice> my_pci_device;
uint32_t base_addr;
};
/**
@ -140,4 +152,17 @@ public:
};
};
/**
Aspen PCI Bridge HLE emulation class.
*/
class AspenPci : public BanditHost {
public:
AspenPci(std::string name);
~AspenPci() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<AspenPci>(new AspenPci("Aspen-PCI1"));
};
};
#endif // BANDIT_PCI_H

View File

@ -24,7 +24,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/hwcomponent.h>
#include <devices/common/pci/dec21154.h>
#include <devices/deviceregistry.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <cinttypes>

View File

@ -0,0 +1,304 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <devices/common/pci/pcibase.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <memaccess.h>
#include <cinttypes>
#include <fstream>
#include <cstring>
#include <string>
PCIBase::PCIBase(std::string name, PCIHeaderType hdr_type, int num_bars)
{
this->name = name;
this->pci_name = name;
this->hdr_type = hdr_type;
this->num_bars = num_bars;
this->pci_rd_stat = [this]() { return this->status; };
this->pci_rd_cmd = [this]() { return this->command; };
this->pci_rd_bist = []() { return 0; };
this->pci_rd_lat_timer = [this]() { return this->lat_timer; };
this->pci_rd_cache_lnsz = [this]() { return this->cache_ln_sz; };
this->pci_wr_stat = [](uint16_t val) {};
this->pci_wr_cmd = [this](uint16_t cmd) {
/*
FIXME: should register or unregister BAR mmio regions if (cmd & 2) changes.
Or the mmio regions should be enabled/disabled.
*/
this->command = cmd & this->command_cfg;
};
this->pci_wr_bist = [](uint8_t val) {};
this->pci_wr_lat_timer = [this](uint8_t val) { this->lat_timer = val; };
this->pci_wr_cache_lnsz = [this](uint8_t val) { this->cache_ln_sz = val; };
this->pci_notify_bar_change = [](int bar_num) {};
};
uint32_t PCIBase::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_DEV_ID:
return (this->device_id << 16) | (this->vendor_id);
case PCI_CFG_STAT_CMD:
return (this->pci_rd_stat() << 16) | (this->pci_rd_cmd());
case PCI_CFG_CLASS_REV:
return this->class_rev;
case PCI_CFG_DWORD_3:
return (pci_rd_bist() << 24) | (this->hdr_type << 16) |
(pci_rd_lat_timer() << 8) | pci_rd_cache_lnsz();
}
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
return 0;
}
void PCIBase::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_STAT_CMD:
this->pci_wr_stat(value >> 16);
this->pci_wr_cmd(value & 0xFFFFU);
break;
case PCI_CFG_DWORD_3:
this->pci_wr_bist(value >> 24);
this->pci_wr_lat_timer((value >> 8) & 0xFF);
this->pci_wr_cache_lnsz(value & 0xFF);
break;
default:
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
}
}
void PCIBase::setup_bars(std::vector<BarConfig> cfg_data)
{
for (auto cfg_entry : cfg_data) {
if (cfg_entry.bar_num > this->num_bars) {
ABORT_F("BAR number %d out of range", cfg_entry.bar_num);
}
this->bars_cfg[cfg_entry.bar_num] = cfg_entry.bar_cfg;
}
this->finish_config_bars();
}
int PCIBase::attach_exp_rom_image(const std::string img_path)
{
std::ifstream img_file;
int result = 0;
this->exp_bar_cfg = 0; // tell the world we got no ROM for now
try {
img_file.open(img_path, std::ios::in | std::ios::binary);
if (img_file.fail()) {
throw std::runtime_error("could not open specified ROM dump image");
}
// validate image file
uint8_t buf[4] = { 0 };
img_file.seekg(0, std::ios::beg);
img_file.read((char *)buf, sizeof(buf));
if (buf[0] != 0x55 || buf[1] != 0xAA) {
throw std::runtime_error("invalid expansion ROM signature");
}
// determine image size
img_file.seekg(0, std::ios::end);
size_t exp_rom_image_size = img_file.tellg();
if (exp_rom_image_size > 4*1024*1024) {
throw std::runtime_error("expansion ROM file too large");
}
// verify PCI struct offset
uint16_t pci_struct_offset = 0;
img_file.seekg(0x18, std::ios::beg);
img_file.read((char *)&pci_struct_offset, sizeof(pci_struct_offset));
if (pci_struct_offset > exp_rom_image_size) {
throw std::runtime_error("invalid PCI structure offset");
}
// verify PCI struct signature
img_file.seekg(pci_struct_offset, std::ios::beg);
img_file.read((char *)buf, sizeof(buf));
if (buf[0] != 'P' || buf[1] != 'C' || buf[2] != 'I' || buf[3] != 'R') {
throw std::runtime_error("unexpected PCI struct signature");
}
// find minimum rom size for the rom file (power of 2 >= 0x800)
for (this->exp_rom_size = 1 << 11; this->exp_rom_size < exp_rom_image_size; this->exp_rom_size <<= 1) {}
// ROM image ok - go ahead and load it
this->exp_rom_data = std::unique_ptr<uint8_t[]> (new uint8_t[this->exp_rom_size]);
img_file.seekg(0, std::ios::beg);
img_file.read((char *)this->exp_rom_data.get(), exp_rom_image_size);
memset(&this->exp_rom_data[exp_rom_image_size], 0xff, this->exp_rom_size - exp_rom_image_size);
if (exp_rom_image_size == this->exp_rom_size) {
LOG_F(INFO, "%s: loaded expansion rom (%d bytes).",
this->pci_name.c_str(), this->exp_rom_size);
}
else {
LOG_F(WARNING, "%s: loaded expansion rom (%d bytes adjusted to %d bytes).",
this->pci_name.c_str(), (int)exp_rom_image_size, this->exp_rom_size);
}
this->exp_bar_cfg = ~(this->exp_rom_size - 1);
}
catch (const std::exception& exc) {
LOG_F(ERROR, "%s: %s", this->pci_name.c_str(), exc.what());
result = -1;
}
img_file.close();
return result;
}
void PCIBase::set_bar_value(int bar_num, uint32_t value)
{
uint32_t bar_cfg = this->bars_cfg[bar_num];
switch (bars_typ[bar_num]) {
case PCIBarType::Unused:
return;
case PCIBarType::Io_16_Bit:
case PCIBarType::Io_32_Bit:
this->bars[bar_num] = (value & bar_cfg & ~3) | (bar_cfg & 3);
if (value != 0xFFFFFFFFUL && (value & ~3) != (value & bar_cfg & ~3)) {
LOG_F(ERROR, "%s: BAR %d cannot be 0x%08x (set to 0x%08x)", this->pci_name.c_str(), bar_num, (value & ~3), (value & bar_cfg & ~3));
}
break;
case PCIBarType::Mem_20_Bit:
case PCIBarType::Mem_32_Bit:
case PCIBarType::Mem_64_Bit_Lo:
this->bars[bar_num] = (value & bar_cfg & ~0xF) | (bar_cfg & 0xF);
if (value != 0xFFFFFFFFUL && (value & ~0xF) != (value & bar_cfg & ~0xF)) {
LOG_F(ERROR, "%s: BAR %d cannot be 0x%08x (set to 0x%08x)", this->pci_name.c_str(), bar_num, (value & ~0xF), (value & bar_cfg & ~0xF));
}
break;
case PCIBarType::Mem_64_Bit_Hi:
this->bars[bar_num] = value & bar_cfg;
break;
}
if (value != 0xFFFFFFFFUL) // don't notify the device during BAR sizing
this->pci_notify_bar_change(bar_num);
}
void PCIBase::finish_config_bars()
{
for (int bar_num = 0; bar_num < this->num_bars; bar_num++) {
uint32_t bar_cfg = this->bars_cfg[bar_num];
if (!bar_cfg) // skip unimplemented BARs
continue;
if (bar_cfg & 1) {
bars_typ[bar_num] = (bar_cfg & 0xffff0000) ? PCIBarType::Io_32_Bit :
PCIBarType::Io_16_Bit;
has_io_space = true;
}
else {
int pci_space_type = (bar_cfg >> 1) & 3;
switch (pci_space_type) {
case 0:
bars_typ[bar_num] = PCIBarType::Mem_32_Bit;
break;
case 1:
bars_typ[bar_num] = PCIBarType::Mem_20_Bit;
break;
case 2:
if (bar_num >= num_bars - 1) {
ABORT_F("%s: BAR %d cannot be 64-bit",
this->pci_name.c_str(), bar_num);
}
else if (this->bars_cfg[bar_num+1] == 0) {
ABORT_F("%s: 64-bit BAR %d has zero for upper 32 bits",
this->pci_name.c_str(), bar_num);
}
else {
bars_typ[bar_num++] = PCIBarType::Mem_64_Bit_Lo;
bars_typ[bar_num ] = PCIBarType::Mem_64_Bit_Hi;
}
break;
default:
ABORT_F("%s: invalid or unsupported PCI space type %d for BAR %d",
this->pci_name.c_str(), pci_space_type, bar_num);
} // switch pci_space_type
}
} // for bar_num
}
void PCIBase::map_exp_rom_mem()
{
uint32_t rom_addr = this->exp_rom_bar & this->exp_bar_cfg;
if (rom_addr) {
if (this->exp_rom_addr != rom_addr) {
this->unmap_exp_rom_mem();
uint32_t rom_size = ~this->exp_bar_cfg + 1;
this->host_instance->pci_register_mmio_region(rom_addr, rom_size, this);
this->exp_rom_addr = rom_addr;
}
}
else {
this->unmap_exp_rom_mem();
}
}
void PCIBase::unmap_exp_rom_mem()
{
if (this->exp_rom_addr) {
uint32_t rom_size = ~this->exp_bar_cfg + 1;
this->host_instance->pci_unregister_mmio_region(exp_rom_addr, rom_size, this);
this->exp_rom_addr = 0;
}
}
void PCIBase::pci_wr_exp_rom_bar(uint32_t data)
{
if (!this->exp_bar_cfg) {
return;
}
if ((data & this->exp_bar_cfg) == this->exp_bar_cfg) {
// doing sizing
this->exp_rom_bar = (data & (this->exp_bar_cfg | 1));
} else {
this->exp_rom_bar = (data & (this->exp_bar_cfg | 1));
if (this->exp_rom_bar & 1) {
this->map_exp_rom_mem();
}
else {
this->unmap_exp_rom_mem();
}
}
}

View File

@ -0,0 +1,250 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef PCI_BASE_H
#define PCI_BASE_H
#include <devices/common/mmiodevice.h>
#include <devices/common/pci/pcihost.h>
#include <cinttypes>
#include <functional>
#include <memory>
#include <string>
#include <vector>
/** PCI configuration space header types */
enum PCIHeaderType : uint8_t {
PCI_HEADER_TYPE_0 = 0, // PCI Device
PCI_HEADER_TYPE_1 = 1, // PCI-PCI Bridge
PCI_HEADER_TYPE_2 = 2, // PCI-To-Cardbus Bridge
};
/** PCI configuration space registers offsets */
enum {
PCI_CFG_DEV_ID = 0x00, // device and vendor IDs
PCI_CFG_STAT_CMD = 0x04, // command/status register
PCI_CFG_CLASS_REV = 0x08, // class code and revision ID
PCI_CFG_DWORD_3 = 0x0C, // BIST, HeaderType, Lat_Timer and Cache_Line_Size
PCI_CFG_BAR0 = 0x10, // base address register 0 (type 0, 1, and 2)
PCI_CFG_BAR1 = 0x14, // base address register 1 (type 0 and 1)
PCI_CFG_DWORD_13 = 0x34, // capabilities pointer (type 0 and 1)
PCI_CFG_DWORD_15 = 0x3C, // Max_Lat, Min_Gnt (Type 0); Bridge Control (Type 1 and 2); Int_Pin and Int_Line registers (type 0, 1, and 2)
};
/** PCI Vendor IDs for devices used in Power Macintosh computers. */
enum {
PCI_VENDOR_ATI = 0x1002,
PCI_VENDOR_DEC = 0x1011,
PCI_VENDOR_MOTOROLA = 0x1057,
PCI_VENDOR_APPLE = 0x106B,
PCI_VENDOR_NVIDIA = 0x10DE,
};
/** PCI BAR types */
enum PCIBarType : uint32_t {
Unused = 0,
Io_16_Bit,
Io_32_Bit,
Mem_20_Bit, // legacy type for < 1MB memory
Mem_32_Bit,
Mem_64_Bit_Lo,
Mem_64_Bit_Hi
};
typedef struct {
uint32_t bar_num;
uint32_t bar_cfg;
} BarConfig;
class PCIBase : public MMIODevice {
public:
PCIBase(std::string name, PCIHeaderType hdr_type, int num_bars);
virtual ~PCIBase() = default;
virtual bool supports_io_space() {
return has_io_space;
};
/* I/O space access methods */
virtual bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) {
return false;
};
virtual bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size) {
return false;
};
// configuration space access methods
virtual uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
// plugin interface for using in the derived classes
std::function<uint16_t()> pci_rd_stat;
std::function<void(uint16_t)> pci_wr_stat;
std::function<uint16_t()> pci_rd_cmd;
std::function<void(uint16_t)> pci_wr_cmd;
std::function<uint8_t()> pci_rd_bist;
std::function<void(uint8_t)> pci_wr_bist;
std::function<uint8_t()> pci_rd_lat_timer;
std::function<void(uint8_t)> pci_wr_lat_timer;
std::function<uint8_t()> pci_rd_cache_lnsz;
std::function<void(uint8_t)> pci_wr_cache_lnsz;
std::function<void(int)> pci_notify_bar_change;
int attach_exp_rom_image(const std::string img_path);
virtual void set_host(PCIHost* host_instance) {
this->host_instance = host_instance;
}
virtual void set_multi_function(bool is_multi_function) {
this->hdr_type = is_multi_function ? (this->hdr_type | 0x80) : (this->hdr_type & 0x7f);
}
virtual void set_irq_pin(uint8_t irq_pin) {
this->irq_pin = irq_pin;
}
virtual void pci_interrupt(uint8_t irq_line_state) {
this->host_instance->pci_interrupt(irq_line_state, this);
}
// MMIODevice methods
virtual uint32_t read(uint32_t rgn_start, uint32_t offset, int size) { return 0; }
virtual void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { }
protected:
void set_bar_value(int bar_num, uint32_t value);
void setup_bars(std::vector<BarConfig> cfg_data);
void finish_config_bars();
void pci_wr_exp_rom_bar(uint32_t data);
void map_exp_rom_mem();
void unmap_exp_rom_mem();
std::string pci_name; // human-readable device name
PCIHost* host_instance; // host bridge instance to call back
// PCI configuration space state (type 0, 1, and 2)
uint16_t vendor_id;
uint16_t device_id;
uint16_t command = 0;
uint16_t status = 0;
uint32_t class_rev; // class code and revision id
uint8_t cache_ln_sz = 0; // cache line size
uint8_t lat_timer = 0; // latency timer
uint8_t hdr_type; // header type, single function
uint8_t cap_ptr = 0; // capabilities ptr
uint8_t irq_pin = 0;
uint8_t irq_line = 0;
bool has_io_space = false;
int num_bars; // number of BARs. Type 0:6, type 1:2, type 2:1 (4K)
uint32_t bars[6] = { 0 }; // BARs (base address registers)
uint32_t bars_cfg[6] = { 0 }; // configuration values for BARs
PCIBarType bars_typ[6] = { PCIBarType::Unused }; // types for BARs
// PCI configuration space state (type 0 and 1)
uint32_t exp_bar_cfg = 0; // expansion ROM configuration
uint32_t exp_rom_bar = 0; // expansion ROM base address register
uint32_t exp_rom_addr = 0; // expansion ROM base address
uint32_t exp_rom_size = 0; // expansion ROM size in bytes
std::unique_ptr<uint8_t[]> exp_rom_data;
// 0 = not writable; 1 = bit is enabled in command register
uint16_t command_cfg = 0xffff - (1<<3) - (1<<7); // disable: special cycles and stepping
};
inline uint32_t pci_cfg_log(uint32_t value, AccessDetails &details) {
switch (details.size << 2 | details.offset) {
case 0x04: return (uint8_t) value;
case 0x05: return (uint8_t)(value >> 8);
case 0x06: return (uint8_t)(value >> 16);
case 0x07: return (uint8_t)(value >> 24);
case 0x08: return (uint16_t) value;
case 0x09: return (uint16_t) (value >> 8);
case 0x0a: return (uint16_t) (value >> 16);
case 0x0b: return (uint16_t)((value >> 24) | (value << 8));
case 0x10: return value;
case 0x11: return (value >> 8) | (value << 24);
case 0x12: return (value >> 16) | (value << 16);
case 0x13: return (value >> 24) | (value << 8);
default: return 0xffffffff;
}
}
#define LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER() \
do { if ((details.flags & PCI_CONFIG_DIRECTION) == PCI_CONFIG_READ) { \
VLOG_F( \
(~-details.size & details.offset) ? loguru::Verbosity_ERROR : loguru::Verbosity_WARNING, \
"%s: read unimplemented config register @%02x.%c", \
this->name.c_str(), reg_offs + details.offset, \
SIZE_ARG(details.size) \
); \
} } while(0)
#define LOG_NAMED_CONFIG_REGISTER(reg_verb, reg_name) \
VLOG_F( \
(~-details.size & details.offset) ? loguru::Verbosity_ERROR : loguru::Verbosity_WARNING, \
"%s: %s %s register @%02x.%c = %0*x", \
this->name.c_str(), reg_verb, reg_name, reg_offs + details.offset, \
SIZE_ARG(details.size), \
details.size * 2, pci_cfg_log(value, details) \
)
#define LOG_READ_NAMED_CONFIG_REGISTER(reg_name) \
do { if ((details.flags & PCI_CONFIG_DIRECTION) == PCI_CONFIG_READ) { \
LOG_NAMED_CONFIG_REGISTER("read ", reg_name); \
} } while(0)
#define LOG_WRITE_NAMED_CONFIG_REGISTER(reg_name) \
LOG_NAMED_CONFIG_REGISTER("write", reg_name)
#define LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER_WITH_VALUE() \
LOG_READ_NAMED_CONFIG_REGISTER("unimplemented config")
#define LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER() \
LOG_WRITE_NAMED_CONFIG_REGISTER("unimplemented config")
#define LOG_READ_NON_EXISTENT_PCI_DEVICE() \
LOG_F( \
ERROR, \
"%s err: read attempt from non-existent PCI device %02x:%02x.%x @%02x.%c", \
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + details.offset, \
SIZE_ARG(details.size) \
)
#define LOG_WRITE_NON_EXISTENT_PCI_DEVICE() \
LOG_F( \
ERROR, \
"%s err: write attempt to non-existent PCI device %02x:%02x.%x @%02x.%c = %0*x", \
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + details.offset, \
SIZE_ARG(details.size), \
details.size * 2, BYTESWAP_SIZED(value, details.size) \
)
#endif /* PCI_BASE_H */

View File

@ -20,18 +20,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <devices/common/pci/pcibridge.h>
#include <memaccess.h>
#include <devices/common/pci/pcidevice.h>
#include <devices/common/pci/pcihost.h>
#include <loguru.hpp>
PCIBridge::PCIBridge(std::string name) : PCIDevice(name)
PCIBridge::PCIBridge(std::string name) : PCIBridgeBase(name, PCI_HEADER_TYPE_1, 2)
{
this->num_bars = 2;
this->hdr_type = 1;
this->pci_rd_primary_bus = [this]() { return this->primary_bus; };
this->pci_rd_secondary_bus = [this]() { return this->secondary_bus; };
this->pci_rd_subordinate_bus = [this]() { return this->subordinate_bus; };
this->pci_rd_sec_latency_timer = [this]() { return this->sec_latency_timer; };
this->pci_rd_sec_status = [this]() { return this->sec_status; };
this->pci_rd_memory_base = [this]() { return this->memory_base; };
this->pci_rd_memory_limit = [this]() { return this->memory_limit; };
this->pci_rd_io_base = [this]() { return this->io_base; };
@ -42,18 +36,6 @@ PCIBridge::PCIBridge(std::string name) : PCIDevice(name)
this->pci_rd_io_limit_upper16 = [this]() { return this->io_limit_upper16; };
this->pci_rd_pref_base_upper32 = [this]() { return this->pref_base_upper32; };
this->pci_rd_pref_limit_upper32 = [this]() { return this->pref_limit_upper32; };
this->pci_rd_bridge_control = [this]() { return this->bridge_control; };
this->pci_wr_primary_bus = [this](uint8_t val) { this->primary_bus = val; };
this->pci_wr_secondary_bus = [this](uint8_t val) { this->secondary_bus = val; };
this->pci_wr_subordinate_bus = [this](uint8_t val) { this->subordinate_bus = val; };
this->pci_wr_sec_latency_timer = [this](uint8_t val) {
this->sec_latency_timer = (this->sec_latency_timer & ~this->sec_latency_timer_cfg) |
(val & this->sec_latency_timer_cfg);
};
this->pci_wr_sec_status = [this](uint16_t val) {};
this->pci_wr_memory_base = [this](uint16_t val) {
this->memory_base = (val & this->memory_cfg) | (this->memory_cfg & 15);
@ -112,33 +94,14 @@ PCIBridge::PCIBridge(std::string name) : PCIDevice(name)
this->pref_mem_limit_64 = (((uint64_t)this->pref_limit_upper32 << 32) |
((this->pref_mem_limit & 0xfff0) << 16)) + 0x100000;
};
this->pci_wr_bridge_control = [this](uint16_t val) { this->bridge_control = val; };
};
bool PCIBridge::pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj)
{
// FIXME: constrain region to memory range
return this->host_instance->pci_register_mmio_region(start_addr, size, obj);
}
bool PCIBridge::pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj)
{
return this->host_instance->pci_unregister_mmio_region(start_addr, size, obj);
}
uint32_t PCIBridge::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
if (reg_offs < 0x18) {
return PCIDevice::pci_cfg_read(reg_offs, details);
}
switch (reg_offs) {
case PCI_CFG_PRIMARY_BUS:
return (this->pci_rd_sec_latency_timer() << 24) |
(this->pci_rd_subordinate_bus() << 16) |
(this->pci_rd_secondary_bus() << 8) |
(this->pci_rd_primary_bus());
case PCI_CFG_BAR0:
case PCI_CFG_BAR1:
return this->bars[(reg_offs - 0x10) >> 2];
case PCI_CFG_IO_BASE:
return (this->pci_rd_sec_status() << 16) |
(this->pci_rd_io_limit() << 8) | (this->pci_rd_io_base());
@ -152,29 +115,21 @@ uint32_t PCIBridge::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
return this->pci_rd_pref_limit_upper32();
case PCI_CFG_IO_BASE_UPPER16:
return (this->pci_rd_io_limit_upper16() << 16) | (this->pci_rd_io_base_upper16());
case PCI_CFG_CAP_PTR:
case PCI_CFG_DWORD_13:
return cap_ptr;
case PCI_CFG_BRIDGE_ROM_ADDRESS:
return exp_rom_bar;
case PCI_CFG_INTERRUPT_LINE:
return (this->pci_rd_bridge_control() << 16) | (irq_pin << 8) | irq_line;
default:
return PCIBridgeBase::pci_cfg_read(reg_offs, details);
}
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
return 0;
}
void PCIBridge::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
if (reg_offs < 0x18) {
return PCIDevice::pci_cfg_write(reg_offs, value, details);
}
switch (reg_offs) {
case PCI_CFG_PRIMARY_BUS:
this->pci_wr_sec_latency_timer(value >> 24);
this->pci_wr_subordinate_bus(value >> 16);
this->pci_wr_secondary_bus(value >> 8);
this->pci_wr_primary_bus(value & 0xFFU);
case PCI_CFG_BAR0:
case PCI_CFG_BAR1:
this->set_bar_value((reg_offs - 0x10) >> 2, value);
break;
case PCI_CFG_IO_BASE:
this->pci_wr_sec_status(value >> 16);
@ -202,12 +157,9 @@ void PCIBridge::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &
case PCI_CFG_BRIDGE_ROM_ADDRESS:
this->pci_wr_exp_rom_bar(value);
break;
case PCI_CFG_INTERRUPT_LINE:
this->irq_line = value >> 24;
this->pci_wr_bridge_control(value >> 16);
break;
default:
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
PCIBridgeBase::pci_cfg_write(reg_offs, value, details);
break;
}
}

View File

@ -22,63 +22,36 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef PCI_BRIDGE_H
#define PCI_BRIDGE_H
#include <devices/deviceregistry.h>
#include <devices/common/pci/pcidevice.h>
#include <devices/common/pci/pcibridgebase.h>
#include <devices/common/pci/pcihost.h>
#include <cinttypes>
#include <string>
#include <unordered_map>
#include <vector>
/** PCI configuration space registers offsets */
enum {
PCI_CFG_PRIMARY_BUS = 0x18, // PRIMARY_BUS, SECONDARY_BUS, SUBORDINATE_BUS, SEC_LATENCY_TIMER
PCI_CFG_IO_BASE = 0x1C, // IO_BASE.b, IO_LIMIT.b, SEC_STATUS.w
PCI_CFG_MEMORY_BASE = 0x20, // MEMORY_BASE.w, MEMORY_LIMIT.w
PCI_CFG_PREF_MEM_BASE = 0x24, // PREF_MEMORY_BASE.w, PREF_MEMORY_LIMIT.w
PCI_CFG_PREF_BASE_UPPER32 = 0x28, // PREF_BASE_UPPER32
PCI_CFG_PREF_LIMIT_UPPER32 = 0x2c, // PREF_LIMIT_UPPER32
PCI_CFG_IO_BASE_UPPER16 = 0x30, // IO_BASE_UPPER16.w, IO_LIMIT_UPPER16.w
// PCI_CFG_CAP_PTR
PCI_CFG_BRIDGE_ROM_ADDRESS = 0x38, // BRIDGE_ROM_ADDRESS
PCI_CFG_INTERRUPT_LINE = 0x3C, // INTERRUPT_LINE.b, INTERRUPT_PIN.b, BRIDGE_CONTROL.w
};
class PCIBridge : public PCIHost, public PCIDevice {
friend class PCIHost;
class PCIBridge : public PCIBridgeBase {
public:
PCIBridge(std::string name);
~PCIBridge() = default;
// PCIHost methods
virtual bool pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj);
virtual bool pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj);
// PCIDevice methods
// PCIBase methods
virtual uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
virtual bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res);
virtual bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size);
// MMIODevice methods
virtual uint32_t read(uint32_t rgn_start, uint32_t offset, int size) {
return 0;
};
virtual void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { };
// plugin interface for using in the derived classes
std::function<uint8_t()> pci_rd_primary_bus;
std::function<void(uint8_t)> pci_wr_primary_bus;
std::function<uint8_t()> pci_rd_secondary_bus;
std::function<void(uint8_t)> pci_wr_secondary_bus;
std::function<uint8_t()> pci_rd_subordinate_bus;
std::function<void(uint8_t)> pci_wr_subordinate_bus;
std::function<uint8_t()> pci_rd_sec_latency_timer;
std::function<void(uint8_t)> pci_wr_sec_latency_timer;
std::function<uint16_t()> pci_rd_sec_status;
std::function<void(uint16_t)> pci_wr_sec_status;
std::function<uint8_t()> pci_rd_io_base;
std::function<void(uint8_t)> pci_wr_io_base;
std::function<uint8_t()> pci_rd_io_limit;
@ -99,16 +72,9 @@ public:
std::function<void(uint16_t)> pci_wr_io_base_upper16;
std::function<uint16_t()> pci_rd_io_limit_upper16;
std::function<void(uint16_t)> pci_wr_io_limit_upper16;
std::function<uint16_t()> pci_rd_bridge_control;
std::function<void(uint16_t)> pci_wr_bridge_control;
protected:
// PCI configuration space state
uint8_t primary_bus = 0;
uint8_t secondary_bus = 0;
uint8_t subordinate_bus = 0;
uint8_t sec_latency_timer = 0; // if supportss r/w then must reset to 0
uint16_t sec_status = 0;
uint8_t io_base = 0;
uint8_t io_limit = 0;
uint16_t memory_base = 0;
@ -119,12 +85,8 @@ protected:
uint32_t pref_limit_upper32 = 0;
uint16_t io_base_upper16 = 0;
uint16_t io_limit_upper16 = 0;
uint16_t bridge_control = 0;
// 0 = not writable, 0xf8 = limits the granularity to eight PCI clocks
uint8_t sec_latency_timer_cfg = 0;
// 0 = not writable, 0xf0 = supports 16 bit io range, 0xf1 = supports 32 bit I/O range
// 0 = not writable, 0xf0 = supports 16 bit I/O range, 0xf1 = supports 32 bit I/O range
uint8_t io_cfg = 0xf0;
// 0 = not writable, 0xfff0 = supports 32 bit memory range
@ -134,14 +96,13 @@ protected:
// 0xfff1 = supports 64 bit prefetchable memory range
uint16_t pref_mem_cfg = 0xfff0;
// calculated address ranges
uint32_t io_base_32 = 0;
uint32_t io_limit_32 = 0;
uint64_t memory_base_32 = 0;
uint64_t memory_limit_32 = 0;
uint64_t pref_mem_base_64 = 0;
uint64_t pref_mem_limit_64 = 0;
private:
};
#endif /* PCI_BRIDGE_H */

View File

@ -0,0 +1,87 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <devices/common/pci/pcibridgebase.h>
#include <memaccess.h>
PCIBridgeBase::PCIBridgeBase(std::string name, PCIHeaderType hdr_type, int num_bars) : PCIBase(name, hdr_type, num_bars)
{
this->pci_rd_primary_bus = [this]() { return this->primary_bus; };
this->pci_rd_secondary_bus = [this]() { return this->secondary_bus; };
this->pci_rd_subordinate_bus = [this]() { return this->subordinate_bus; };
this->pci_rd_sec_latency_timer = [this]() { return this->sec_latency_timer; };
this->pci_rd_sec_status = [this]() { return this->sec_status; };
this->pci_rd_bridge_control = [this]() { return this->bridge_control; };
this->pci_wr_primary_bus = [this](uint8_t val) { this->primary_bus = val; };
this->pci_wr_secondary_bus = [this](uint8_t val) { this->secondary_bus = val; };
this->pci_wr_subordinate_bus = [this](uint8_t val) { this->subordinate_bus = val; };
this->pci_wr_sec_latency_timer = [this](uint8_t val) {
this->sec_latency_timer = (this->sec_latency_timer & ~this->sec_latency_timer_cfg) |
(val & this->sec_latency_timer_cfg);
};
this->pci_wr_sec_status = [this](uint16_t val) {};
this->pci_wr_bridge_control = [this](uint16_t val) { this->bridge_control = val; };
};
bool PCIBridgeBase::pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj)
{
// FIXME: constrain region to memory range
return this->host_instance->pci_register_mmio_region(start_addr, size, obj);
}
bool PCIBridgeBase::pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj)
{
return this->host_instance->pci_unregister_mmio_region(start_addr, size, obj);
}
uint32_t PCIBridgeBase::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_PRIMARY_BUS:
return (this->pci_rd_sec_latency_timer() << 24) |
(this->pci_rd_subordinate_bus() << 16) |
(this->pci_rd_secondary_bus() << 8) |
(this->pci_rd_primary_bus());
case PCI_CFG_DWORD_15:
return (this->pci_rd_bridge_control() << 16) | (irq_pin << 8) | irq_line;
default:
return PCIBase::pci_cfg_read(reg_offs, details);
}
}
void PCIBridgeBase::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_PRIMARY_BUS:
this->pci_wr_sec_latency_timer(value >> 24);
this->pci_wr_subordinate_bus(value >> 16);
this->pci_wr_secondary_bus(value >> 8);
this->pci_wr_primary_bus(value & 0xFFU);
break;
case PCI_CFG_DWORD_15:
this->irq_line = value >> 24;
this->pci_wr_bridge_control(value >> 16);
break;
default:
return PCIBase::pci_cfg_write(reg_offs, value, details);
}
}

View File

@ -0,0 +1,80 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef PCI_BRIDGE_BASE_H
#define PCI_BRIDGE_BASE_H
#include <devices/deviceregistry.h>
#include <devices/common/pci/pcibase.h>
#include <devices/common/pci/pcihost.h>
#include <cinttypes>
#include <string>
#include <unordered_map>
#include <vector>
/** PCI configuration space registers offsets (type 1 and 2) */
enum {
PCI_CFG_PRIMARY_BUS = 0x18, // PRIMARY_BUS, SECONDARY_BUS, SUBORDINATE_BUS, SEC_LATENCY_TIMER
};
class PCIBridgeBase : public PCIHost, public PCIBase {
friend class PCIHost;
public:
PCIBridgeBase(std::string name, PCIHeaderType hdr_type, int num_bars);
~PCIBridgeBase() = default;
// PCIHost methods
virtual bool pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj);
virtual bool pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj);
// PCIBase methods
virtual uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
// plugin interface for using in the derived classes
std::function<uint8_t()> pci_rd_primary_bus;
std::function<void(uint8_t)> pci_wr_primary_bus;
std::function<uint8_t()> pci_rd_secondary_bus;
std::function<void(uint8_t)> pci_wr_secondary_bus;
std::function<uint8_t()> pci_rd_subordinate_bus;
std::function<void(uint8_t)> pci_wr_subordinate_bus;
std::function<uint8_t()> pci_rd_sec_latency_timer;
std::function<void(uint8_t)> pci_wr_sec_latency_timer;
std::function<uint16_t()> pci_rd_sec_status;
std::function<void(uint16_t)> pci_wr_sec_status;
std::function<uint16_t()> pci_rd_bridge_control;
std::function<void(uint16_t)> pci_wr_bridge_control;
protected:
// PCI configuration space state
uint8_t primary_bus = 0;
uint8_t secondary_bus = 0;
uint8_t subordinate_bus = 0;
uint8_t sec_latency_timer = 0; // if supportss r/w then must reset to 0
uint16_t sec_status = 0;
uint16_t bridge_control = 0;
// 0 = not writable, 0xf8 = limits the granularity to eight PCI clocks
uint8_t sec_latency_timer_cfg = 0xff;
};
#endif /* PCI_BRIDGE_BASE_H */

View File

@ -0,0 +1,223 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <devices/common/pci/pcicardbusbridge.h>
#include <memaccess.h>
typedef struct {
// CardBus Registers
/* 0x00 */ uint32_t Event;
/* 0x04 */ uint32_t Mask;
/* 0x08 */ uint32_t PresentState;
/* 0x0C */ uint32_t Force;
/* 0x10 */ uint32_t Control;
/* 0x14 */ uint32_t Reserved[4];
/* 0x20 */ uint32_t UserDefined20h[504];
// 16-Bit Registers
/* 0x800 */ uint8_t IDAndRevision;
/* 0x801 */ uint8_t IFStatus;
/* 0x802 */ uint8_t PowerAndResetDrvControl;
/* 0x803 */ uint8_t InterruptAndGeneralControl;
/* 0x804 */ uint8_t CardStatusChange;
/* 0x805 */ uint8_t CardStatusChangeInterruptConfiguration;
/* 0x806 */ uint8_t AddressWindowEnable;
/* 0x807 */ uint8_t IOControl;
/* 0x808 */ uint16_t IOAdd0Start;
/* 0x80A */ uint16_t IOAdd0Stop;
/* 0x80C */ uint16_t IOAdd1Start;
/* 0x80E */ uint16_t IOAdd1Stop;
/* 0x810 */ uint16_t SysMemAdd0Start;
/* 0x812 */ uint16_t SysMemAdd0Stop;
/* 0x814 */ uint16_t CardMemoryOffsetAdd0;
/* 0x816 */ uint16_t UserDefined814h;
/* 0x818 */ uint16_t SysMemAdd1Start;
/* 0x81A */ uint16_t SysMemAdd1Stop;
/* 0x81C */ uint16_t CardMemoryOffsetAdd1;
/* 0x81E */ uint16_t UserDefined81Ch;
/* 0x820 */ uint16_t SysMemAdd2Start;
/* 0x822 */ uint16_t SysMemAdd2Stop;
/* 0x824 */ uint16_t CardMemoryOffsetAdd2;
/* 0x826 */ uint16_t UserDefined824h;
/* 0x828 */ uint16_t SysMemAdd3Start;
/* 0x82A */ uint16_t SysMemAdd3Stop;
/* 0x82C */ uint16_t CardMemoryOffsetAdd3;
/* 0x82E */ uint16_t UserDefined82Ch;
/* 0x830 */ uint16_t SysMemAdd4Start;
/* 0x832 */ uint16_t SysMemAdd4Stop;
/* 0x834 */ uint16_t CardMemoryOffsetAdd4;
/* 0x836 */ uint16_t UserDefined834h;
/* 0x838 */ uint32_t UserDefined838h[4];
/* 0x840 */ uint8_t SysMemAdd0MappingStartUp;
/* 0x841 */ uint8_t SysMemAdd1MappingStartUp;
/* 0x842 */ uint8_t SysMemAdd2MappingStartUp;
/* 0x843 */ uint8_t SysMemAdd3MappingStartUp;
/* 0x844 */ uint8_t SysMemAdd4MappingStartUp;
/* 0x845 */ uint8_t UserDefined845h;
/* 0x846 */ uint16_t UserDefined846h;
/* 0x848 */ uint32_t UserDefined848h[494];
} CardBusStatusAnfControlRegisters;
PCICardbusBridge::PCICardbusBridge(std::string name) : PCIBridgeBase(name, PCI_HEADER_TYPE_2, 1)
{
this->pci_rd_memory_base_0 = [this]() { return this->memory_base_0 ; };
this->pci_rd_memory_limit_0 = [this]() { return this->memory_limit_0 ; };
this->pci_rd_memory_base_1 = [this]() { return this->memory_base_1 ; };
this->pci_rd_memory_limit_1 = [this]() { return this->memory_limit_1 ; };
this->pci_rd_io_base_0 = [this]() { return this->io_base_0 ; };
this->pci_rd_io_limit_0 = [this]() { return this->io_limit_0 ; };
this->pci_rd_io_base_1 = [this]() { return this->io_base_1 ; };
this->pci_rd_io_limit_1 = [this]() { return this->io_limit_1 ; };
this->pci_wr_memory_base_0 = [this](uint32_t val) {
this->memory_base_0 = val & this->memory_0_cfg;
this->memory_base_0_32 = this->memory_base_0;
};
this->pci_wr_memory_limit_0 = [this](uint32_t val) {
this->memory_limit_0 = val & this->memory_0_cfg;
this->memory_limit_0_32 = this->memory_limit_0 + 0x1000;
};
this->pci_wr_memory_base_1 = [this](uint32_t val) {
this->memory_base_1 = val & this->memory_1_cfg;
this->memory_base_1_32 = this->memory_base_1;
};
this->pci_wr_memory_limit_1 = [this](uint32_t val) {
this->memory_limit_1 = val & this->memory_1_cfg;
this->memory_limit_1_32 = this->memory_limit_1 + 0x1000;
};
this->pci_wr_io_base_0 = [this](uint32_t val) {
this->io_base_0 = (val & this->io_0_cfg) | (io_0_cfg & 3);
this->io_base_0_32 = (this->io_base_0 & ~3);
};
this->pci_wr_io_limit_0 = [this](uint32_t val) {
this->io_limit_0 = (val & this->io_0_cfg);
this->io_limit_0_32 = this->io_limit_0 + 4;
};
this->pci_wr_io_base_1 = [this](uint32_t val) {
this->io_base_1 = (val & this->io_1_cfg) | (io_1_cfg & 3);
this->io_base_1_32 = (this->io_base_1 & ~3);
};
this->pci_wr_io_limit_1 = [this](uint32_t val) {
this->io_limit_1 = (val & this->io_1_cfg);
this->io_limit_1_32 = this->io_limit_1 + 4;
};
};
uint32_t PCICardbusBridge::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_BAR0:
return this->bars[(reg_offs - 0x10) >> 2];
case PCI_CFG_CB_CAPABILITIES:
return (this->pci_rd_sec_status() << 16) | cap_ptr;
case PCI_CFG_CB_MEMORY_BASE_0:
return this->pci_rd_memory_base_0();
case PCI_CFG_CB_MEMORY_LIMIT_0:
return this->pci_rd_memory_limit_0();
case PCI_CFG_CB_MEMORY_BASE_1:
return this->pci_rd_memory_base_0();
case PCI_CFG_CB_MEMORY_LIMIT_1:
return this->pci_rd_memory_limit_0();
case PCI_CFG_CB_IO_BASE_0:
return this->pci_rd_io_base_0();
case PCI_CFG_CB_IO_LIMIT_0:
return this->pci_rd_io_limit_0();
case PCI_CFG_CB_IO_BASE_1:
return this->pci_rd_io_base_0();
case PCI_CFG_CB_IO_LIMIT_1:
return this->pci_rd_io_limit_0();
case PCI_CFG_CB_SUBSYSTEM_IDS:
return (this->subsys_id << 16) | (this->subsys_vndr);
case PCI_CFG_CB_LEGACY_MODE_BASE:
return this->legacy_mode_base;
default:
return PCIBridgeBase::pci_cfg_read(reg_offs, details);
}
}
void PCICardbusBridge::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_BAR0:
this->set_bar_value((reg_offs - 0x10) >> 2, value);
break;
case PCI_CFG_CB_CAPABILITIES:
this->pci_wr_sec_status(value >> 16);
break;
case PCI_CFG_CB_MEMORY_BASE_0:
this->pci_wr_memory_base_0(value);
break;
case PCI_CFG_CB_MEMORY_LIMIT_0:
this->pci_wr_memory_limit_0(value);
break;
case PCI_CFG_CB_MEMORY_BASE_1:
this->pci_wr_memory_base_0(value);
break;
case PCI_CFG_CB_MEMORY_LIMIT_1:
this->pci_wr_memory_limit_0(value);
break;
case PCI_CFG_CB_IO_BASE_0:
this->pci_wr_io_base_0(value);
break;
case PCI_CFG_CB_IO_LIMIT_0:
this->pci_wr_io_limit_0(value);
break;
case PCI_CFG_CB_IO_BASE_1:
this->pci_wr_io_base_0(value);
break;
case PCI_CFG_CB_IO_LIMIT_1:
this->pci_wr_io_limit_0(value);
break;
/*
case PCI_CFG_CB_LEGACY_MODE_BASE:
this->legacy_mode_base = value;
break;
*/
default:
PCIBridgeBase::pci_cfg_write(reg_offs, value, details);
break;
}
}
bool PCICardbusBridge::pci_io_read(uint32_t offset, uint32_t size, uint32_t* res)
{
if (!(this->command & 1)) return false;
if ((offset < this->io_base_0_32 || offset + size >= this->io_limit_0_32) &&
(offset < this->io_base_1_32 || offset + size >= this->io_limit_1_32)
) return false;
return this->pci_io_read_loop(offset, size, *res);
}
bool PCICardbusBridge::pci_io_write(uint32_t offset, uint32_t value, uint32_t size)
{
if (!(this->command & 1)) return false;
if ((offset < this->io_base_0_32 || offset + size >= this->io_limit_0_32) &&
(offset < this->io_base_1_32 || offset + size >= this->io_limit_1_32)
) return false;
return this->pci_io_read_loop(offset, size, value);
}

View File

@ -0,0 +1,112 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef PCI_CARDBUSBRIDGE_H
#define PCI_CARDBUSBRIDGE_H
#include <devices/deviceregistry.h>
#include <devices/common/pci/pcibridgebase.h>
#include <devices/common/pci/pcihost.h>
#include <cinttypes>
#include <string>
#include <unordered_map>
#include <vector>
/** PCI configuration space registers offsets */
enum {
PCI_CFG_CB_CAPABILITIES = 0x14, // CB_CAPABILITIES.b, 0.b, CB_SEC_STATUS.w
PCI_CFG_CB_MEMORY_BASE_0 = 0x1C,
PCI_CFG_CB_MEMORY_LIMIT_0 = 0x20,
PCI_CFG_CB_MEMORY_BASE_1 = 0x24,
PCI_CFG_CB_MEMORY_LIMIT_1 = 0x28,
PCI_CFG_CB_IO_BASE_0 = 0x2C,
PCI_CFG_CB_IO_LIMIT_0 = 0x30,
PCI_CFG_CB_IO_BASE_1 = 0x34,
PCI_CFG_CB_IO_LIMIT_1 = 0x38,
PCI_CFG_CB_SUBSYSTEM_IDS = 0x40, // CB_SUBSYSTEM_VENDOR_ID.w, CB_SUBSYSTEM_ID.w
PCI_CFG_CB_LEGACY_MODE_BASE = 0x44,
};
class PCICardbusBridge : public PCIBridgeBase {
public:
PCICardbusBridge(std::string name);
~PCICardbusBridge() = default;
// PCIBase methods
virtual uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
virtual bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res);
virtual bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size);
// plugin interface for using in the derived classes
std::function<uint32_t()> pci_rd_memory_base_0;
std::function<void(uint32_t)> pci_wr_memory_base_0;
std::function<uint32_t()> pci_rd_memory_limit_0;
std::function<void(uint32_t)> pci_wr_memory_limit_0;
std::function<uint32_t()> pci_rd_memory_base_1;
std::function<void(uint32_t)> pci_wr_memory_base_1;
std::function<uint32_t()> pci_rd_memory_limit_1;
std::function<void(uint32_t)> pci_wr_memory_limit_1;
std::function<uint32_t()> pci_rd_io_base_0;
std::function<void(uint32_t)> pci_wr_io_base_0;
std::function<uint32_t()> pci_rd_io_limit_0;
std::function<void(uint32_t)> pci_wr_io_limit_0;
std::function<uint32_t()> pci_rd_io_base_1;
std::function<void(uint32_t)> pci_wr_io_base_1;
std::function<uint32_t()> pci_rd_io_limit_1;
std::function<void(uint32_t)> pci_wr_io_limit_1;
protected:
// PCI configuration space state
uint32_t memory_base_0 = 0;
uint32_t memory_limit_0 = 0;
uint32_t memory_base_1 = 0;
uint32_t memory_limit_1 = 0;
uint32_t io_base_0 = 0;
uint32_t io_limit_0 = 0;
uint32_t io_base_1 = 0;
uint32_t io_limit_1 = 0;
uint16_t subsys_id = 0;
uint16_t subsys_vndr = 0;
uint32_t legacy_mode_base = 0;
// 0 = not writable
uint32_t memory_0_cfg = 0xfffff000;
uint32_t memory_1_cfg = 0xfffff000;
// 0 = not writable, 0x0000fffc = supports 16 bit I/O range, 0xfffffffd = supports 32 bit I/O range
uint32_t io_0_cfg = 0x0000fffc;
uint32_t io_1_cfg = 0x0000fffc;
// calculated address ranges
uint32_t memory_base_0_32 = 0;
uint32_t memory_limit_0_32 = 0;
uint32_t memory_base_1_32 = 0;
uint32_t memory_limit_1_32 = 0;
uint32_t io_base_0_32 = 0;
uint32_t io_limit_0_32 = 0;
uint32_t io_base_1_32 = 0;
uint32_t io_limit_1_32 = 0;
};
#endif /* PCI_CARDBUSBRIDGE_H */

View File

@ -20,47 +20,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <devices/common/pci/pcidevice.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <memaccess.h>
#include <cinttypes>
#include <fstream>
#include <cstring>
#include <string>
PCIDevice::PCIDevice(std::string name)
PCIDevice::PCIDevice(std::string name) : PCIBase(name, PCI_HEADER_TYPE_0, 6)
{
this->name = name;
this->pci_name = name;
this->pci_rd_stat = [this]() { return this->status; };
this->pci_rd_cmd = [this]() { return this->command; };
this->pci_rd_bist = []() { return 0; };
this->pci_rd_lat_timer = [this]() { return this->lat_timer; };
this->pci_rd_cache_lnsz = [this]() { return this->cache_ln_sz; };
this->pci_wr_stat = [](uint16_t val) {};
this->pci_wr_cmd = [this](uint16_t cmd) { this->command = cmd; };
this->pci_wr_bist = [](uint8_t val) {};
this->pci_wr_lat_timer = [this](uint8_t val) { this->lat_timer = val; };
this->pci_wr_cache_lnsz = [this](uint8_t val) { this->cache_ln_sz = val; };
this->pci_notify_bar_change = [](int bar_num) {};
};
uint32_t PCIDevice::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_DEV_ID:
return (this->device_id << 16) | (this->vendor_id);
case PCI_CFG_STAT_CMD:
return (this->pci_rd_stat() << 16) | (this->pci_rd_cmd());
case PCI_CFG_CLASS_REV:
return this->class_rev;
case PCI_CFG_DWORD_3:
return (pci_rd_bist() << 24) | (this->hdr_type << 16) |
(pci_rd_lat_timer() << 8) | pci_rd_cache_lnsz();
case PCI_CFG_BAR0:
case PCI_CFG_BAR1:
case PCI_CFG_BAR2:
@ -72,27 +45,18 @@ uint32_t PCIDevice::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
return (this->subsys_id << 16) | (this->subsys_vndr);
case PCI_CFG_ROM_BAR:
return this->exp_rom_bar;
case PCI_CFG_DWORD_13:
return cap_ptr;
case PCI_CFG_DWORD_15:
return (max_lat << 24) | (min_gnt << 16) | (irq_pin << 8) | irq_line;
case PCI_CFG_CAP_PTR:
return cap_ptr;
default:
return PCIBase::pci_cfg_read(reg_offs, details);
}
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
return 0;
}
void PCIDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
switch (reg_offs) {
case PCI_CFG_STAT_CMD:
this->pci_wr_stat(value >> 16);
this->pci_wr_cmd(value & 0xFFFFU);
break;
case PCI_CFG_DWORD_3:
this->pci_wr_bist(value >> 24);
this->pci_wr_lat_timer((value >> 8) & 0xFF);
this->pci_wr_cache_lnsz(value & 0xFF);
break;
case PCI_CFG_BAR0:
case PCI_CFG_BAR1:
case PCI_CFG_BAR2:
@ -108,212 +72,6 @@ void PCIDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &
this->irq_line = value >> 24;
break;
default:
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
}
}
void PCIDevice::setup_bars(std::vector<BarConfig> cfg_data)
{
for (auto cfg_entry : cfg_data) {
if (cfg_entry.bar_num > 5) {
ABORT_F("BAR number %d out of range", cfg_entry.bar_num);
}
this->bars_cfg[cfg_entry.bar_num] = cfg_entry.bar_cfg;
}
this->finish_config_bars();
}
int PCIDevice::attach_exp_rom_image(const std::string img_path)
{
std::ifstream img_file;
int result = 0;
this->exp_bar_cfg = 0; // tell the world we got no ROM for now
try {
img_file.open(img_path, std::ios::in | std::ios::binary);
if (img_file.fail()) {
throw std::runtime_error("could not open specified ROM dump image");
}
// validate image file
uint8_t buf[4] = { 0 };
img_file.seekg(0, std::ios::beg);
img_file.read((char *)buf, sizeof(buf));
if (buf[0] != 0x55 || buf[1] != 0xAA) {
throw std::runtime_error("invalid expansion ROM signature");
}
// determine image size
img_file.seekg(0, std::ios::end);
size_t exp_rom_image_size = img_file.tellg();
if (exp_rom_image_size > 4*1024*1024) {
throw std::runtime_error("expansion ROM file too large");
}
// verify PCI struct offset
uint16_t pci_struct_offset = 0;
img_file.seekg(0x18, std::ios::beg);
img_file.read((char *)&pci_struct_offset, sizeof(pci_struct_offset));
if (pci_struct_offset > exp_rom_image_size) {
throw std::runtime_error("invalid PCI structure offset");
}
// verify PCI struct signature
img_file.seekg(pci_struct_offset, std::ios::beg);
img_file.read((char *)buf, sizeof(buf));
if (buf[0] != 'P' || buf[1] != 'C' || buf[2] != 'I' || buf[3] != 'R') {
throw std::runtime_error("unexpected PCI struct signature");
}
// find minimum rom size for the rom file (power of 2 >= 0x800)
for (this->exp_rom_size = 1 << 11; this->exp_rom_size < exp_rom_image_size; this->exp_rom_size <<= 1) {}
// ROM image ok - go ahead and load it
this->exp_rom_data = std::unique_ptr<uint8_t[]> (new uint8_t[this->exp_rom_size]);
img_file.seekg(0, std::ios::beg);
img_file.read((char *)this->exp_rom_data.get(), exp_rom_image_size);
memset(&this->exp_rom_data[exp_rom_image_size], 0xff, this->exp_rom_size - exp_rom_image_size);
if (exp_rom_image_size == this->exp_rom_size) {
LOG_F(INFO, "%s: loaded expansion rom (%d bytes).",
this->pci_name.c_str(), this->exp_rom_size);
}
else {
LOG_F(WARNING, "%s: loaded expansion rom (%d bytes adjusted to %d bytes).",
this->pci_name.c_str(), (int)exp_rom_image_size, this->exp_rom_size);
}
this->exp_bar_cfg = ~(this->exp_rom_size - 1);
}
catch (const std::exception& exc) {
LOG_F(ERROR, "PCIDevice: %s", exc.what());
result = -1;
}
img_file.close();
return result;
}
void PCIDevice::set_bar_value(int bar_num, uint32_t value)
{
uint32_t bar_cfg = this->bars_cfg[bar_num];
switch (bars_typ[bar_num]) {
case PCIBarType::Unused:
return;
case PCIBarType::Io_16_Bit:
case PCIBarType::Io_32_Bit:
this->bars[bar_num] = (value & bar_cfg & ~3) | (bar_cfg & 3);
break;
case PCIBarType::Mem_20_Bit:
case PCIBarType::Mem_32_Bit:
case PCIBarType::Mem_64_Bit_Lo:
this->bars[bar_num] = (value & bar_cfg & ~0xF) | (bar_cfg & 0xF);
break;
case PCIBarType::Mem_64_Bit_Hi:
this->bars[bar_num] = value & bar_cfg;
break;
}
if (value != 0xFFFFFFFFUL) // don't notify the device during BAR sizing
this->pci_notify_bar_change(bar_num);
}
void PCIDevice::finish_config_bars()
{
for (int bar_num = 0; bar_num < this->num_bars; bar_num++) {
uint32_t bar_cfg = this->bars_cfg[bar_num];
if (!bar_cfg) // skip unimplemented BARs
continue;
if (bar_cfg & 1) {
bars_typ[bar_num] = (bar_cfg & 0xffff0000) ? PCIBarType::Io_32_Bit :
PCIBarType::Io_16_Bit;
has_io_space = true;
}
else {
int pci_space_type = (bar_cfg >> 1) & 3;
switch (pci_space_type) {
case 0:
bars_typ[bar_num] = PCIBarType::Mem_32_Bit;
break;
case 1:
bars_typ[bar_num] = PCIBarType::Mem_20_Bit;
break;
case 2:
if (bar_num >= num_bars - 1) {
ABORT_F("%s: BAR %d cannot be 64-bit",
this->pci_name.c_str(), bar_num);
}
else if (this->bars_cfg[bar_num+1] == 0) {
ABORT_F("%s: 64-bit BAR %d has zero for upper 32 bits",
this->pci_name.c_str(), bar_num);
}
else {
bars_typ[bar_num++] = PCIBarType::Mem_64_Bit_Lo;
bars_typ[bar_num ] = PCIBarType::Mem_64_Bit_Hi;
}
break;
default:
ABORT_F("%s: invalid or unsupported PCI space type %d for BAR %d",
this->pci_name.c_str(), pci_space_type, bar_num);
} // switch pci_space_type
}
} // for bar_num
}
void PCIDevice::map_exp_rom_mem()
{
uint32_t rom_addr = this->exp_rom_bar & this->exp_bar_cfg;
if (rom_addr) {
if (this->exp_rom_addr != rom_addr) {
this->unmap_exp_rom_mem();
uint32_t rom_size = ~this->exp_bar_cfg + 1;
this->host_instance->pci_register_mmio_region(rom_addr, rom_size, this);
this->exp_rom_addr = rom_addr;
}
}
else {
this->unmap_exp_rom_mem();
}
}
void PCIDevice::unmap_exp_rom_mem()
{
if (this->exp_rom_addr) {
uint32_t rom_size = ~this->exp_bar_cfg + 1;
this->host_instance->pci_unregister_mmio_region(exp_rom_addr, rom_size, this);
this->exp_rom_addr = 0;
}
}
void PCIDevice::pci_wr_exp_rom_bar(uint32_t data)
{
if (!this->exp_bar_cfg) {
return;
}
if ((data & this->exp_bar_cfg) == this->exp_bar_cfg) {
// doing sizing
this->exp_rom_bar = (data & (this->exp_bar_cfg | 1));
} else {
this->exp_rom_bar = (data & (this->exp_bar_cfg | 1));
if (this->exp_rom_bar & 1) {
this->map_exp_rom_mem();
}
else {
this->unmap_exp_rom_mem();
}
PCIBase::pci_cfg_write(reg_offs, value, details);
}
}

View File

@ -22,23 +22,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef PCI_DEVICE_H
#define PCI_DEVICE_H
#include <devices/common/mmiodevice.h>
#include <devices/common/pci/pcihost.h>
#include <cinttypes>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <devices/common/pci/pcibase.h>
/** PCI configuration space registers offsets */
enum {
PCI_CFG_DEV_ID = 0x00, // device and vendor IDs
PCI_CFG_STAT_CMD = 0x04, // command/status register
PCI_CFG_CLASS_REV = 0x08, // class code and revision ID
PCI_CFG_DWORD_3 = 0x0C, // BIST, HeaderType, Lat_Timer and Cache_Line_Size
PCI_CFG_BAR0 = 0x10, // base address register 0
PCI_CFG_BAR1 = 0x14, // base address register 1
PCI_CFG_BAR2 = 0x18, // base address register 2
PCI_CFG_BAR3 = 0x1C, // base address register 3
PCI_CFG_BAR4 = 0x20, // base address register 4
@ -46,203 +33,23 @@ enum {
PCI_CFG_CIS_PTR = 0x28, // Cardbus CIS Pointer
PCI_CFG_SUBSYS_ID = 0x2C, // Subsysten IDs
PCI_CFG_ROM_BAR = 0x30, // expansion ROM base address
PCI_CFG_CAP_PTR = 0x34, // capabilities pointer
PCI_CFG_DWORD_14 = 0x38, // reserved
PCI_CFG_DWORD_15 = 0x3C, // Max_Lat, Min_Gnt, Int_Pin and Int_Line registers
};
/** PCI Vendor IDs for devices used in Power Macintosh computers. */
enum {
PCI_VENDOR_ATI = 0x1002,
PCI_VENDOR_DEC = 0x1011,
PCI_VENDOR_MOTOROLA = 0x1057,
PCI_VENDOR_APPLE = 0x106B,
PCI_VENDOR_NVIDIA = 0x10DE,
};
/** PCI BAR types */
enum PCIBarType : uint32_t {
Unused = 0,
Io_16_Bit,
Io_32_Bit,
Mem_20_Bit, // legacy type for < 1MB memory
Mem_32_Bit,
Mem_64_Bit_Lo,
Mem_64_Bit_Hi
};
typedef struct {
uint32_t bar_num;
uint32_t bar_cfg;
} BarConfig;
class PCIDevice : public MMIODevice {
class PCIDevice : public PCIBase {
public:
PCIDevice(std::string name);
virtual ~PCIDevice() = default;
virtual bool supports_io_space() {
return has_io_space;
};
/* I/O space access methods */
virtual bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) {
return false;
};
virtual bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size) {
return false;
};
// configuration space access methods
virtual uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
// plugin interface for using in the derived classes
std::function<uint16_t()> pci_rd_stat;
std::function<void(uint16_t)> pci_wr_stat;
std::function<uint16_t()> pci_rd_cmd;
std::function<void(uint16_t)> pci_wr_cmd;
std::function<uint8_t()> pci_rd_bist;
std::function<void(uint8_t)> pci_wr_bist;
std::function<uint8_t()> pci_rd_lat_timer;
std::function<void(uint8_t)> pci_wr_lat_timer;
std::function<uint8_t()> pci_rd_cache_lnsz;
std::function<void(uint8_t)> pci_wr_cache_lnsz;
std::function<void(int)> pci_notify_bar_change;
int attach_exp_rom_image(const std::string img_path);
virtual void set_host(PCIHost* host_instance) {
this->host_instance = host_instance;
};
virtual void set_multi_function(bool is_multi_function) {
this->hdr_type = is_multi_function ? (this->hdr_type | 0x80) : (this->hdr_type & 0x7f);
}
virtual void set_irq_pin(uint8_t irq_pin) {
this->irq_pin = irq_pin;
}
// MMIODevice methods
virtual uint32_t read(uint32_t rgn_start, uint32_t offset, int size) { return 0; }
virtual void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { }
protected:
void set_bar_value(int bar_num, uint32_t value);
void setup_bars(std::vector<BarConfig> cfg_data);
void finish_config_bars();
void pci_wr_exp_rom_bar(uint32_t data);
void map_exp_rom_mem();
void unmap_exp_rom_mem();
std::string pci_name; // human-readable device name
PCIHost* host_instance; // host bridge instance to call back
// PCI configuration space state
uint16_t vendor_id;
uint16_t device_id;
uint32_t class_rev; // class code and revision id
uint16_t status = 0;
uint16_t command = 0;
uint8_t hdr_type = 0; // header type, single function
uint8_t lat_timer = 0; // latency timer
uint8_t cache_ln_sz = 0; // cache line size
uint16_t subsys_id = 0;
uint16_t subsys_vndr = 0;
uint8_t cap_ptr = 0;
uint8_t max_lat = 0;
uint8_t min_gnt = 0;
uint8_t irq_pin = 0;
uint8_t irq_line = 0;
bool has_io_space = false;
int num_bars = 6;
uint32_t bars[6] = { 0 }; // base address registers
uint32_t bars_cfg[6] = { 0 }; // configuration values for base address registers
PCIBarType bars_typ[6] = { PCIBarType::Unused }; // types for base address registers
uint32_t exp_bar_cfg = 0; // expansion ROM configuration
uint32_t exp_rom_bar = 0; // expansion ROM base address register
uint32_t exp_rom_addr = 0; // expansion ROM base address
uint32_t exp_rom_size = 0; // expansion ROM size in bytes
std::unique_ptr<uint8_t[]> exp_rom_data;
uint16_t subsys_id = 0;
uint16_t subsys_vndr = 0;
};
inline uint32_t pci_cfg_log(uint32_t value, AccessDetails &details) {
switch (details.size << 2 | details.offset) {
case 0x04: return (uint8_t) value;
case 0x05: return (uint8_t)(value >> 8);
case 0x06: return (uint8_t)(value >> 16);
case 0x07: return (uint8_t)(value >> 24);
case 0x08: return (uint16_t) value;
case 0x09: return (uint16_t) (value >> 8);
case 0x0a: return (uint16_t) (value >> 16);
case 0x0b: return (uint16_t)((value >> 24) | (value << 8));
case 0x10: return value;
case 0x11: return (value >> 8) | (value << 24);
case 0x12: return (value >> 16) | (value << 16);
case 0x13: return (value >> 24) | (value << 8);
default: return 0xffffffff;
}
}
#define SIZE_ARGS details.size == 4 ? 'l' : details.size == 2 ? 'w' : \
details.size == 1 ? 'b' : '0' + details.size
#define LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER() \
do { if ((details.flags & PCI_CONFIG_DIRECTION) == PCI_CONFIG_READ) { \
VLOG_F( \
(~-details.size & details.offset) ? loguru::Verbosity_ERROR : loguru::Verbosity_WARNING, \
"%s: read unimplemented config register @%02x.%c", \
this->name.c_str(), reg_offs + details.offset, \
SIZE_ARGS \
); \
} } while(0)
#define LOG_NAMED_CONFIG_REGISTER(reg_verb, reg_name) \
VLOG_F( \
(~-details.size & details.offset) ? loguru::Verbosity_ERROR : loguru::Verbosity_WARNING, \
"%s: %s %s register @%02x.%c = %0*x", \
this->name.c_str(), reg_verb, reg_name, reg_offs + details.offset, \
SIZE_ARGS, \
details.size * 2, pci_cfg_log(value, details) \
)
#define LOG_READ_NAMED_CONFIG_REGISTER(reg_name) \
do { if ((details.flags & PCI_CONFIG_DIRECTION) == PCI_CONFIG_READ) { \
LOG_NAMED_CONFIG_REGISTER("read ", reg_name); \
} } while(0)
#define LOG_WRITE_NAMED_CONFIG_REGISTER(reg_name) \
LOG_NAMED_CONFIG_REGISTER("write", reg_name)
#define LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER_WITH_VALUE() \
LOG_READ_NAMED_CONFIG_REGISTER("unimplemented config")
#define LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER() \
LOG_WRITE_NAMED_CONFIG_REGISTER("unimplemented config")
#define LOG_READ_NON_EXISTENT_PCI_DEVICE() \
LOG_F( \
ERROR, \
"%s err: read attempt from non-existent PCI device %02x:%02x.%x @%02x.%c", \
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + details.offset, \
SIZE_ARGS \
)
#define LOG_WRITE_NON_EXISTENT_PCI_DEVICE() \
LOG_F( \
ERROR, \
"%s err: write attempt to non-existent PCI device %02x:%02x.%x @%02x.%c = %0*x", \
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + details.offset, \
SIZE_ARGS, \
details.size * 2, BYTESWAP_SIZED(value, details.size) \
)
#endif /* PCI_DEVICE_H */

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -22,18 +22,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/hwcomponent.h>
#include <devices/common/pci/pcibridge.h>
#include <devices/common/pci/pcihost.h>
#include <devices/deviceregistry.h>
#include <devices/memctrl/memctrlbase.h>
#include <machines/machinefactory.h>
#include <machines/machinebase.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <cinttypes>
bool PCIHost::pci_register_device(int dev_fun_num, PCIDevice* dev_instance)
bool PCIHost::pci_register_device(int dev_fun_num, PCIBase* dev_instance)
{
// return false if dev_fun_num already registered
if (this->dev_map.count(dev_fun_num))
return false;
if (this->dev_map.count(dev_fun_num)) {
pci_unregister_device(dev_fun_num);
}
int fun_num = dev_fun_num & 7;
int dev_num = (dev_fun_num >> 3) & 0x1f;
@ -59,7 +62,7 @@ bool PCIHost::pci_register_device(int dev_fun_num, PCIDevice* dev_instance)
this->io_space_devs.push_back(dev_instance);
}
PCIBridge *bridge = dynamic_cast<PCIBridge*>(dev_instance);
PCIBridgeBase *bridge = dynamic_cast<PCIBridgeBase*>(dev_instance);
if (bridge) {
this->bridge_devs.push_back(bridge);
}
@ -67,7 +70,23 @@ bool PCIHost::pci_register_device(int dev_fun_num, PCIDevice* dev_instance)
return true;
}
bool PCIHost::pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj)
void PCIHost::pci_unregister_device(int dev_fun_num)
{
if (!this->dev_map.count(dev_fun_num)) {
return;
}
PCIBase* dev_instance = this->dev_map[dev_fun_num];
HWComponent *hwc = dynamic_cast<HWComponent*>(this);
LOG_F(
ERROR, "%s: pci_unregister_device(%s) not supported yet (every PCI device needs a working destructor)",
hwc ? hwc->get_name().c_str() : "PCIHost", dev_instance->get_name().c_str()
);
delete dev_instance;
}
bool PCIHost::pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj)
{
MemCtrlBase *mem_ctrl = dynamic_cast<MemCtrlBase *>
(gMachineObj->get_comp_by_type(HWCompType::MEM_CTRL));
@ -75,7 +94,7 @@ bool PCIHost::pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDe
return mem_ctrl->add_mmio_region(start_addr, size, obj);
}
bool PCIHost::pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj)
bool PCIHost::pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj)
{
MemCtrlBase *mem_ctrl = dynamic_cast<MemCtrlBase *>
(gMachineObj->get_comp_by_type(HWCompType::MEM_CTRL));
@ -88,7 +107,7 @@ void PCIHost::attach_pci_device(const std::string& dev_name, int slot_id)
this->attach_pci_device(dev_name, slot_id, "");
}
PCIDevice *PCIHost::attach_pci_device(const std::string& dev_name, int slot_id, const std::string& dev_suffix)
PCIBase *PCIHost::attach_pci_device(const std::string& dev_name, int slot_id, const std::string& dev_suffix)
{
if (!DeviceRegistry::device_registered(dev_name)) {
HWComponent *hwc = dynamic_cast<HWComponent*>(this);
@ -100,7 +119,10 @@ PCIDevice *PCIHost::attach_pci_device(const std::string& dev_name, int slot_id,
}
// attempt to create device object
auto dev_obj = DeviceRegistry::get_descriptor(dev_name).m_create_func();
auto desc = DeviceRegistry::get_descriptor(dev_name);
map<string, string> settings;
MachineFactory::get_device_settings(desc, settings);
auto dev_obj = desc.m_create_func();
if (!dev_obj->supports_type(HWCompType::PCI_DEV)) {
HWComponent *hwc = dynamic_cast<HWComponent*>(this);
@ -115,7 +137,7 @@ PCIDevice *PCIHost::attach_pci_device(const std::string& dev_name, int slot_id,
// add device to the machine object
gMachineObj->add_device(dev_name + dev_suffix, std::move(dev_obj));
PCIDevice *dev = dynamic_cast<PCIDevice*>(gMachineObj->get_comp_by_name(dev_name + dev_suffix));
PCIBase *dev = dynamic_cast<PCIBase*>(gMachineObj->get_comp_by_name(dev_name + dev_suffix));
// register device with the PCI host
this->pci_register_device(slot_id, dev);
@ -155,7 +177,7 @@ uint32_t PCIHost::pci_io_read_broadcast(uint32_t offset, int size)
LOG_F(
ERROR, "%s: Attempt to read from unmapped PCI I/O space @%08x.%c",
hwc ? hwc->get_name().c_str() : "PCIHost", offset,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
SIZE_ARG(size)
);
// FIXME: add machine check exception (DEFAULT CATCH!, code=FFF00200)
return 0;
@ -172,24 +194,30 @@ void PCIHost::pci_io_write_broadcast(uint32_t offset, int size, uint32_t value)
LOG_F(
ERROR, "%s: Attempt to write to unmapped PCI I/O space @%08x.%c = %0*x",
hwc ? hwc->get_name().c_str() : "PCIHost", offset,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
SIZE_ARG(size),
size * 2, BYTESWAP_SIZED(value, size)
);
}
PCIDevice *PCIHost::pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num)
PCIBase *PCIHost::pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num)
{
for (auto& bridge : this->bridge_devs) {
if (bridge->secondary_bus <= bus_num) {
if (bridge->secondary_bus == bus_num) {
if (bridge->dev_map.count(DEV_FUN(dev_num, fun_num))) {
return bridge->dev_map[DEV_FUN(dev_num, fun_num)];
}
return bridge->pci_find_device(dev_num, fun_num);
}
else if (bridge->subordinate_bus >= bus_num) {
if (bridge->subordinate_bus >= bus_num) {
return bridge->pci_find_device(bus_num, dev_num, fun_num);
}
}
}
return NULL;
}
PCIBase *PCIHost::pci_find_device(uint8_t dev_num, uint8_t fun_num)
{
if (this->dev_map.count(DEV_FUN(dev_num, fun_num))) {
return this->dev_map[DEV_FUN(dev_num, fun_num)];
}
return NULL;
}

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -23,10 +23,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define PCI_HOST_H
#include <core/bitops.h>
#include <devices/deviceregistry.h>
#include <endianswap.h>
#include <array>
#include <cinttypes>
#include <string>
#include <unordered_map>
@ -40,7 +38,7 @@ enum {
PCI_CONFIG_TYPE = 4,
PCI_CONFIG_TYPE_0 = 0,
PCI_CONFIG_TYPE_1 = 4,
};
}; // PCIAccessFlags
/** PCI config space access details */
typedef struct AccessDetails {
@ -51,8 +49,8 @@ typedef struct AccessDetails {
#define DEV_FUN(dev_num,fun_num) (((dev_num) << 3) | (fun_num))
class PCIDevice;
class PCIBridge;
class PCIBase;
class PCIBridgeBase;
class PCIHost {
public:
@ -62,14 +60,15 @@ public:
};
~PCIHost() = default;
virtual bool pci_register_device(int dev_fun_num, PCIDevice* dev_instance);
virtual bool pci_register_device(int dev_fun_num, PCIBase* dev_instance);
virtual void pci_unregister_device(int dev_fun_num);
virtual bool pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj);
virtual bool pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj);
virtual bool pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj);
virtual bool pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIBase* obj);
virtual void attach_pci_device(const std::string& dev_name, int slot_id);
PCIDevice *attach_pci_device(const std::string& dev_name, int slot_id,
const std::string& dev_suffix);
PCIBase *attach_pci_device(const std::string& dev_name, int slot_id,
const std::string& dev_suffix);
virtual bool pci_io_read_loop (uint32_t offset, int size, uint32_t &res);
virtual bool pci_io_write_loop(uint32_t offset, int size, uint32_t value);
@ -77,29 +76,25 @@ public:
virtual uint32_t pci_io_read_broadcast (uint32_t offset, int size);
virtual void pci_io_write_broadcast(uint32_t offset, int size, uint32_t value);
virtual PCIDevice *pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num);
virtual PCIBase *pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num);
virtual PCIBase *pci_find_device(uint8_t dev_num, uint8_t fun_num);
virtual uint32_t pci_t1_read(uint8_t dev, uint32_t fun, uint32_t reg, AccessDetails &details) {
return 0;
};
virtual void pci_t1_write(uint8_t dev, uint32_t fun, uint32_t reg, uint32_t value,
AccessDetails &details) {};
virtual void pci_interrupt(uint8_t irq_line_state, PCIBase *dev) {}
protected:
std::unordered_map<int, PCIDevice*> dev_map;
std::vector<PCIDevice*> io_space_devs;
std::vector<PCIBridge*> bridge_devs;
std::unordered_map<int, PCIBase*> dev_map;
std::vector<PCIBase*> io_space_devs;
std::vector<PCIBridgeBase*> bridge_devs;
};
// Helpers for data conversion in the PCI Configuration space.
/**
Perform size dependent endian swapping for value that is dword from PCI config.
Perform size dependent endian swapping for value that is dword from PCI config or any other dword little endian register.
Unaligned data is handled properly by wrapping around if needed.
Unaligned data is handled properly by using bytes from the next dword.
*/
inline uint32_t pci_conv_rd_data(uint32_t value, AccessDetails &details) {
inline uint32_t pci_conv_rd_data(uint32_t value, uint32_t value2, AccessDetails &details) {
switch (details.size << 2 | details.offset) {
// Bytes
case 0x04:
@ -119,17 +114,20 @@ inline uint32_t pci_conv_rd_data(uint32_t value, AccessDetails &details) {
case 0x0A:
return BYTESWAP_16((value >> 16) & 0xFFFFU); // 2 3
case 0x0B:
return ((value >> 16) & 0xFF00) | (value & 0xFF); // 3 0
return ((value >> 16) & 0xFF00) | (value2 & 0xFF); // 3 4
// Dwords
case 0x10:
return BYTESWAP_32(value); // 0 1 2 3
return BYTESWAP_32(value); // 0 1 2 3
case 0x11:
return ROTL_32(BYTESWAP_32(value), 8); // 1 2 3 0
value = (uint32_t)((((uint64_t)value2 << 32) | value) >> 8);
return BYTESWAP_32(value); // 1 2 3 4
case 0x12:
return ROTL_32(BYTESWAP_32(value), 16); // 2 3 0 1
value = (uint32_t)((((uint64_t)value2 << 32) | value) >> 16);
return BYTESWAP_32(value); // 2 3 4 5
case 0x13:
return ROTR_32(BYTESWAP_32(value), 8); // 3 0 1 2
value = (uint32_t)((((uint64_t)value2 << 32) | value) >> 24);
return BYTESWAP_32(value); // 3 4 5 6
default:
return 0xFFFFFFFFUL;
}

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -24,6 +24,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <core/timermanager.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/scsi/mesh.h>
#include <devices/common/scsi/scsi.h>
#include <devices/deviceregistry.h>
#include <loguru.hpp>
#include <machines/machinebase.h>
@ -34,11 +35,11 @@ using namespace MeshScsi;
int MeshController::device_postinit()
{
this->bus_obj = dynamic_cast<ScsiBus*>(gMachineObj->get_comp_by_name("Scsi0"));
this->bus_obj = dynamic_cast<ScsiBus*>(gMachineObj->get_comp_by_name("ScsiMesh"));
this->int_ctrl = dynamic_cast<InterruptCtrl*>(
gMachineObj->get_comp_by_type(HWCompType::INT_CTRL));
this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI1);
this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI_MESH);
return 0;
}
@ -46,23 +47,32 @@ int MeshController::device_postinit()
void MeshController::reset(bool is_hard_reset)
{
this->cur_cmd = SeqCmd::NoOperation;
this->fifo_cnt = 0;
this->int_mask = 0;
this->xfer_count = 0;
this->src_id = 7;
if (is_hard_reset) {
this->bus_stat = 0;
this->sync_params = 2; // guessed
this->sync_params = (0 << 16) | 2; // fast async operation (guessed)
}
}
uint8_t MeshController::read(uint8_t reg_offset)
{
switch(reg_offset) {
case MeshReg::XferCount0:
return this->xfer_count & 0xFFU;
case MeshReg::XferCount1:
return (this->xfer_count >> 8) & 0xFFU;
case MeshReg::Sequence:
return this->cur_cmd;
case MeshReg::BusStatus0:
return this->bus_obj->test_ctrl_lines(0xFFU);
case MeshReg::BusStatus1:
return this->bus_obj->test_ctrl_lines(0xE000U) >> 8;
case MeshReg::FIFOCount:
return this->fifo_cnt;
case MeshReg::Exception:
return 0;
case MeshReg::Error:
@ -71,6 +81,10 @@ uint8_t MeshController::read(uint8_t reg_offset)
return this->int_mask;
case MeshReg::Interrupt:
return this->int_stat;
case MeshReg::DestID:
return this->dst_id;
case MeshReg::SyncParms:
return this->sync_params;
case MeshReg::MeshID:
return this->chip_id; // tell them who we are
default:
@ -94,9 +108,9 @@ void MeshController::write(uint8_t reg_offset, uint8_t value)
for (uint16_t mask = SCSI_CTRL_RST; mask >= SCSI_CTRL_SEL; mask >>= 1) {
if ((new_stat ^ this->bus_stat) & mask) {
if (new_stat & mask)
this->bus_obj->assert_ctrl_line(new_stat, mask);
this->bus_obj->assert_ctrl_line(this->src_id, mask);
else
this->bus_obj->release_ctrl_line(new_stat, mask);
this->bus_obj->release_ctrl_line(this->src_id, mask);
}
}
this->bus_stat = new_stat;
@ -135,6 +149,7 @@ void MeshController::perform_command(const uint8_t cmd)
switch (this->cur_cmd & 0xF) {
case SeqCmd::Arbitrate:
this->bus_obj->release_ctrl_lines(this->src_id);
this->cur_state = SeqState::BUS_FREE;
this->sequencer();
break;
@ -142,15 +157,25 @@ void MeshController::perform_command(const uint8_t cmd)
this->cur_state = SeqState::SEL_BEGIN;
this->sequencer();
break;
case SeqCmd::BusFree:
LOG_F(INFO, "MESH: BusFree stub invoked");
this->int_stat |= INT_CMD_DONE;
break;
case SeqCmd::EnaReselect:
LOG_F(INFO, "MESH: EnaReselect stub invoked");
this->int_stat |= INT_CMD_DONE;
break;
case SeqCmd::DisReselect:
LOG_F(INFO, "MESH: DisReselect command requested");
LOG_F(9, "MESH: DisReselect stub invoked");
this->int_stat |= INT_CMD_DONE;
break;
case SeqCmd::ResetMesh:
this->reset(false);
this->int_stat |= INT_CMD_DONE;
break;
case SeqCmd::FlushFIFO:
LOG_F(INFO, "MESH: FlushFIFO command requested");
LOG_F(INFO, "MESH: FlushFIFO stub invoked");
this->int_stat |= INT_CMD_DONE;
break;
default:
LOG_F(ERROR, "MESH: unsupported sequencer command 0x%X", this->cur_cmd);
@ -238,8 +263,18 @@ void MeshController::update_irq()
}
}
static const DeviceDescription Mesh_Descriptor = {
MeshController::create, {}, {}
static const PropMap Mesh_properties = {
{"hdd_img2", new StrProperty("")},
{"cdr_img2", new StrProperty("")},
};
REGISTER_DEVICE(Mesh, Mesh_Descriptor);
static const DeviceDescription Mesh_Tnt_Descriptor = {
MeshController::create_for_tnt, {}, Mesh_properties
};
static const DeviceDescription Mesh_Heathrow_Descriptor = {
MeshController::create_for_heathrow, {}, Mesh_properties
};
REGISTER_DEVICE(MeshTnt, Mesh_Tnt_Descriptor);
REGISTER_DEVICE(MeshHeathrow, Mesh_Heathrow_Descriptor);

View File

@ -24,13 +24,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef MESH_H
#define MESH_H
#include <devices/common/dmacore.h>
#include <devices/common/hwcomponent.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/scsi/scsi.h>
#include <cinttypes>
#include <memory>
class InterruptCtrl;
class ScsiBus;
// Chip ID returned by the MESH ASIC on TNT machines (Apple part 343S1146-a)
#define TntMeshID 0xE2
// Chip ID returned by the MESH cell inside the Heathrow ASIC
#define HeathrowMESHID 4
@ -61,6 +66,8 @@ enum SeqCmd : uint8_t {
NoOperation = 0,
Arbitrate = 1,
Select = 2,
BusFree = 9,
EnaReselect = 0xC,
DisReselect = 0xD,
ResetMesh = 0xE,
FlushFIFO = 0xF,
@ -102,16 +109,31 @@ enum SeqState : uint32_t {
}; // namespace MeshScsi
class MeshStub : public HWComponent {
public:
MeshStub() = default;
~MeshStub() = default;
// registers access
uint8_t read(uint8_t reg_offset) { return 0; };
void write(uint8_t reg_offset, uint8_t value) {};
};
class MeshController : public HWComponent {
public:
MeshController(uint8_t mesh_id) {
supports_types(HWCompType::SCSI_HOST | HWCompType::SCSI_DEV);
this->chip_id = mesh_id;
this->set_name("MESH");
this->reset(true);
};
~MeshController() = default;
static std::unique_ptr<HWComponent> create() {
static std::unique_ptr<HWComponent> create_for_tnt() {
return std::unique_ptr<MeshController>(new MeshController(TntMeshID));
}
static std::unique_ptr<HWComponent> create_for_heathrow() {
return std::unique_ptr<MeshController>(new MeshController(HeathrowMESHID));
}
@ -122,6 +144,10 @@ public:
// HWComponent methods
int device_postinit();
void set_dma_channel(DmaBidirChannel *dma_ch) {
this->dma_ch = dma_ch;
};
protected:
void reset(bool is_hard_reset);
void perform_command(const uint8_t cmd);
@ -138,7 +164,9 @@ private:
uint8_t dst_id;
uint8_t cur_cmd;
uint8_t error;
uint8_t fifo_cnt;
uint8_t exception;
uint32_t xfer_count;
ScsiBus* bus_obj;
uint16_t bus_stat;
@ -152,6 +180,9 @@ private:
InterruptCtrl* int_ctrl = nullptr;
uint32_t irq_id = 0;
uint8_t irq = 0;
// DMA related stuff
DmaBidirChannel* dma_ch;
};
#endif // MESH_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -22,6 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
/** @file NCR53C94/Am53CF94 SCSI controller emulation. */
#include <core/timermanager.h>
#include <devices/common/dmacore.h>
#include <devices/common/hwcomponent.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/scsi/sc53c94.h>
@ -32,7 +33,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cinttypes>
#include <cstring>
Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) : ScsiDevice(my_id)
Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) : ScsiDevice("SC53C94", my_id)
{
this->chip_id = chip_id;
this->my_bus_id = my_id;
@ -42,12 +43,15 @@ Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) : ScsiDevice(my_id)
int Sc53C94::device_postinit()
{
this->bus_obj = dynamic_cast<ScsiBus*>(gMachineObj->get_comp_by_name("Scsi0"));
this->bus_obj->register_device(7, static_cast<ScsiDevice*>(this));
ScsiBus* bus = dynamic_cast<ScsiBus*>(gMachineObj->get_comp_by_name("ScsiCurio"));
if (bus) {
bus->register_device(7, static_cast<ScsiDevice*>(this));
bus->attach_scsi_devices("");
}
this->int_ctrl = dynamic_cast<InterruptCtrl*>(
gMachineObj->get_comp_by_type(HWCompType::INT_CTRL));
this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI1);
this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI_CURIO);
return 0;
}
@ -55,7 +59,7 @@ int Sc53C94::device_postinit()
void Sc53C94::reset_device()
{
// part-unique ID to be read using a magic sequence
this->set_xfer_count = this->chip_id << 16;
this->xfer_count = this->chip_id << 16;
this->clk_factor = 2;
this->sel_timeout = 0;
@ -109,7 +113,7 @@ uint8_t Sc53C94::read(uint8_t reg_offset)
}
break;
default:
LOG_F(INFO, "SC53C94: reading from register %d", reg_offset);
LOG_F(INFO, "%s: reading from register %d", this->name.c_str(), reg_offset);
}
return 0;
}
@ -143,7 +147,7 @@ void Sc53C94::write(uint8_t reg_offset, uint8_t value)
break;
case Write::Reg53C94::Config_1:
if ((value & 7) != this->my_bus_id) {
ABORT_F("SC53C94: HBA bus ID mismatch!");
ABORT_F("%s: HBA bus ID mismatch!", this->name.c_str());
}
this->config1 = value;
break;
@ -154,7 +158,8 @@ void Sc53C94::write(uint8_t reg_offset, uint8_t value)
this->config3 = value;
break;
default:
LOG_F(INFO, "SC53C94: writing 0x%X to %d register", value, reg_offset);
LOG_F(INFO, "%s: writing 0x%X to %d register", this->name.c_str(), value,
reg_offset);
}
}
@ -170,7 +175,7 @@ uint16_t Sc53C94::pseudo_dma_read()
std:memmove(this->data_fifo, &this->data_fifo[2], this->data_fifo_pos);
// update DMA status
if ((this->cmd_fifo[0] & 0x80)) {
if (this->is_dma_cmd) {
this->xfer_count -= 2;
if (!this->xfer_count) {
is_done = true;
@ -180,6 +185,10 @@ uint16_t Sc53C94::pseudo_dma_read()
}
}
}
else {
LOG_F(ERROR, "SC53C94: FIFO underrun %d", data_fifo_pos);
data_word = 0;
}
// see if we need to refill FIFO
if (!this->data_fifo_pos && !is_done) {
@ -189,16 +198,31 @@ uint16_t Sc53C94::pseudo_dma_read()
return data_word;
}
void Sc53C94::pseudo_dma_write(uint16_t data) {
this->fifo_push((data >> 8) & 0xFFU);
this->fifo_push(data & 0xFFU);
// update DMA status
if (this->is_dma_cmd) {
this->xfer_count -= 2;
if (!this->xfer_count) {
this->status |= STAT_TC; // signal zero transfer count
//this->cur_state = SeqState::XFER_END;
this->sequencer();
}
}
}
void Sc53C94::update_command_reg(uint8_t cmd)
{
if (this->on_reset && (cmd & 0x7F) != CMD_NOP) {
LOG_F(WARNING, "SC53C94: command register blocked after RESET!");
if (this->on_reset && (cmd & CMD_OPCODE) != CMD_NOP) {
LOG_F(WARNING, "%s: command register blocked after RESET!", this->name.c_str());
return;
}
// NOTE: Reset Device (chip), Reset Bus and DMA Stop commands execute
// immediately while all others are placed into the command FIFO
switch (cmd & 0x7F) {
switch (cmd & CMD_OPCODE) {
case CMD_RESET_DEVICE:
case CMD_RESET_BUS:
case CMD_DMA_STOP:
@ -212,7 +236,7 @@ void Sc53C94::update_command_reg(uint8_t cmd)
exec_command();
}
} else {
LOG_F(ERROR, "SC53C94: the top of the command FIFO overwritten!");
LOG_F(ERROR, "%s: the top of the command FIFO overwritten!", this->name.c_str());
this->status |= STAT_GE; // signal IOE/Gross Error
}
}
@ -220,9 +244,10 @@ void Sc53C94::update_command_reg(uint8_t cmd)
void Sc53C94::exec_command()
{
uint8_t cmd = this->cur_cmd = this->cmd_fifo[0] & 0x7F;
bool is_dma_cmd = !!(this->cmd_fifo[0] & 0x80);
if (is_dma_cmd) {
this->is_dma_cmd = !!(this->cmd_fifo[0] & 0x80);
if (this->is_dma_cmd) {
if (this->config2 & CFG2_ENF) { // extended mode: 24-bit
this->xfer_count = this->set_xfer_count & 0xFFFFFFUL;
} else { // standard mode: 16-bit
@ -251,18 +276,23 @@ void Sc53C94::exec_command()
this->on_reset = true; // block the command register
return;
case CMD_RESET_BUS:
LOG_F(INFO, "SC53C94: resetting SCSI bus...");
LOG_F(INFO, "%s: resetting SCSI bus...", this->name.c_str());
// assert RST line
this->bus_obj->assert_ctrl_line(this->my_bus_id, SCSI_CTRL_RST);
// release RST line after 25 us
if (my_timer_id) {
TimerManager::get_instance()->cancel_timer(this->my_timer_id);
my_timer_id = 0;
}
my_timer_id = TimerManager::get_instance()->add_oneshot_timer(
USECS_TO_NSECS(25),
[this]() {
my_timer_id = 0;
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_RST);
});
if (!(config1 & 0x40)) {
LOG_F(INFO, "SC53C94: reset interrupt issued");
this->int_status |= INTSTAT_SRST;
LOG_F(INFO, "%s: reset interrupt issued", this->name.c_str());
this->int_status = INTSTAT_SRST;
}
exec_next_command();
break;
@ -270,7 +300,7 @@ void Sc53C94::exec_command()
if (!this->is_initiator) {
// clear command FIFO
this->cmd_fifo_pos = 0;
this->int_status |= INTSTAT_ICMD;
this->int_status = INTSTAT_ICMD;
this->update_irq();
} else {
this->seq_step = 0;
@ -281,12 +311,12 @@ void Sc53C94::exec_command()
break;
case CMD_COMPLETE_STEPS:
static SeqDesc * complete_steps_desc = new SeqDesc[3]{
{SeqState::RCV_STATUS, 0, 0},
{SeqState::RCV_MESSAGE, 0, 0},
{SeqState::CMD_COMPLETE, 0, INTSTAT_SR}
{(CMD_COMPLETE_STEPS << 8) + 1, SeqState::RCV_STATUS, 0, 0},
{(CMD_COMPLETE_STEPS << 8) + 2, SeqState::RCV_MESSAGE, 0, 0},
{(CMD_COMPLETE_STEPS << 8) + 3, SeqState::CMD_COMPLETE, 0, INTSTAT_SR | INTSTAT_SO}
};
if (this->bus_obj->current_phase() != ScsiPhase::STATUS) {
ABORT_F("Sc53C94: complete steps only works in the STATUS phase");
ABORT_F("%s: complete steps only works in the STATUS phase", this->name.c_str());
}
this->seq_step = 0;
this->cmd_steps = complete_steps_desc;
@ -298,44 +328,44 @@ void Sc53C94::exec_command()
this->bus_obj->target_next_step();
}
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_ACK);
this->int_status |= INTSTAT_SR;
this->int_status = INTSTAT_SR;
this->int_status |= INTSTAT_DIS; // TODO: handle target disconnection properly
this->update_irq();
exec_next_command();
break;
case CMD_SELECT_NO_ATN:
static SeqDesc * sel_no_atn_desc = new SeqDesc[3]{
{SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
{SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
{SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO},
{(CMD_SELECT_NO_ATN << 8) + 1, SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
{(CMD_SELECT_NO_ATN << 8) + 2, SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
{(CMD_SELECT_NO_ATN << 8) + 3, SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO},
};
this->seq_step = 0;
this->cmd_steps = sel_no_atn_desc;
this->cur_state = SeqState::BUS_FREE;
this->sequencer();
LOG_F(9, "SC53C94: SELECT W/O ATN command started");
LOG_F(9, "%s: SELECT W/O ATN command started", this->name.c_str());
break;
case CMD_SELECT_WITH_ATN:
static SeqDesc * sel_with_atn_desc = new SeqDesc[4]{
{SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
{SeqState::SEND_MSG, 2, INTSTAT_SR | INTSTAT_SO},
{SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
{SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO},
{(CMD_SELECT_WITH_ATN << 8) + 1, SeqState::SEL_BEGIN, 0, INTSTAT_DIS },
{(CMD_SELECT_WITH_ATN << 8) + 2, SeqState::SEND_MSG, 2, INTSTAT_SR | INTSTAT_SO},
{(CMD_SELECT_WITH_ATN << 8) + 3, SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO},
{(CMD_SELECT_WITH_ATN << 8) + 4, SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO},
};
this->seq_step = 0;
this->bytes_out = 1; // set message length
this->cmd_steps = sel_with_atn_desc;
this->cur_state = SeqState::BUS_FREE;
this->sequencer();
LOG_F(9, "SC53C94: SELECT WITH ATN command started");
LOG_F(9, "%s: SELECT WITH ATN command started", this->name.c_str());
break;
case CMD_ENA_SEL_RESEL:
exec_next_command();
break;
default:
LOG_F(ERROR, "SC53C94: invalid/unimplemented command 0x%X", cmd);
LOG_F(ERROR, "%s: invalid/unimplemented command 0x%X", this->name.c_str(), cmd);
this->cmd_fifo_pos--; // remove invalid command from FIFO
this->int_status |= INTSTAT_ICMD;
this->int_status = INTSTAT_ICMD;
this->update_irq();
}
}
@ -351,12 +381,14 @@ void Sc53C94::exec_next_command()
}
}
#define DATA_FIFO_MAX 16
void Sc53C94::fifo_push(const uint8_t data)
{
if (this->data_fifo_pos < DATA_FIFO_MAX) {
this->data_fifo[this->data_fifo_pos++] = data;
} else {
LOG_F(ERROR, "SC53C94: data FIFO overflow!");
LOG_F(ERROR, "%s: data FIFO overflow!", this->name.c_str());
this->status |= STAT_GE; // signal IOE/Gross Error
}
}
@ -366,7 +398,7 @@ uint8_t Sc53C94::fifo_pop()
uint8_t data = 0;
if (this->data_fifo_pos < 1) {
LOG_F(ERROR, "SC53C94: data FIFO underflow!");
LOG_F(ERROR, "%s: data FIFO underflow!", this->name.c_str());
this->status |= STAT_GE; // signal IOE/Gross Error
} else {
data = this->data_fifo[0];
@ -379,10 +411,16 @@ uint8_t Sc53C94::fifo_pop()
void Sc53C94::seq_defer_state(uint64_t delay_ns)
{
if (seq_timer_id) {
TimerManager::get_instance()->cancel_timer(this->seq_timer_id);
seq_timer_id = 0;
}
seq_timer_id = TimerManager::get_instance()->add_oneshot_timer(
delay_ns,
[this]() {
// re-enter the sequencer with the state specified in next_state
this->seq_timer_id = 0;
this->cur_state = this->next_state;
this->sequencer();
});
@ -404,7 +442,7 @@ void Sc53C94::sequencer()
break;
case SeqState::ARB_BEGIN:
if (!this->bus_obj->begin_arbitration(this->my_bus_id)) {
LOG_F(ERROR, "SC53C94: arbitration error, bus not free!");
LOG_F(ERROR, "%s: arbitration error, bus not free!", this->name.c_str());
this->bus_obj->release_ctrl_lines(this->my_bus_id);
this->next_state = SeqState::BUS_FREE;
this->seq_defer_state(BUS_CLEAR_DELAY);
@ -418,7 +456,7 @@ void Sc53C94::sequencer()
this->next_state = this->cmd_steps->next_step;
this->seq_defer_state(BUS_CLEAR_DELAY + BUS_SETTLE_DELAY);
} else { // arbitration lost
LOG_F(INFO, "SC53C94: arbitration lost!");
LOG_F(INFO, "%s: arbitration lost!", this->name.c_str());
this->bus_obj->release_ctrl_lines(this->my_bus_id);
this->next_state = SeqState::BUS_FREE;
this->seq_defer_state(BUS_CLEAR_DELAY);
@ -434,10 +472,10 @@ void Sc53C94::sequencer()
case SeqState::SEL_END:
if (this->bus_obj->end_selection(this->my_bus_id, this->target_id)) {
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_SEL);
LOG_F(9, "SC53C94: selection completed");
LOG_F(9, "%s: selection completed", this->name.c_str());
} else { // selection timeout
this->seq_step = this->cmd_steps->step_num;
this->int_status |= this->cmd_steps->status;
this->int_status = this->cmd_steps->status;
this->bus_obj->disconnect(this->my_bus_id);
this->cur_state = SeqState::IDLE;
this->update_irq();
@ -445,22 +483,30 @@ void Sc53C94::sequencer()
}
break;
case SeqState::SEND_MSG:
this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out);
this->bus_obj->push_data(this->target_id, this->data_fifo, this->bytes_out);
this->data_fifo_pos -= this->bytes_out;
if (this->data_fifo_pos > 0) {
std::memmove(this->data_fifo, &this->data_fifo[this->bytes_out], this->data_fifo_pos);
if (this->data_fifo_pos < 1 && this->is_dma_cmd) {
if (this->drq_cb)
this->drq_cb(1);
this->int_status = INTSTAT_SR;
this->update_irq();
break;
}
this->bus_obj->target_xfer_data();
this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_ATN);
break;
case SeqState::SEND_CMD:
this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out);
this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos);
this->data_fifo_pos = 0;
if (this->data_fifo_pos < 1 && this->is_dma_cmd) {
if (this->drq_cb)
this->drq_cb(1);
this->int_status |= INTSTAT_SR;
this->update_irq();
break;
}
this->bus_obj->target_xfer_data();
break;
case SeqState::CMD_COMPLETE:
this->seq_step = this->cmd_steps->step_num;
this->int_status |= this->cmd_steps->status;
this->seq_step = this->cmd_steps->step_num;
this->int_status = this->cmd_steps->status;
this->cur_state = SeqState::IDLE;
this->update_irq();
exec_next_command();
break;
@ -468,7 +514,7 @@ void Sc53C94::sequencer()
this->cur_bus_phase = this->bus_obj->current_phase();
switch (this->cur_bus_phase) {
case ScsiPhase::DATA_OUT:
if (this->cmd_fifo[0] & 0x80) {
if (this->is_dma_cmd) {
this->cur_state = SeqState::SEND_DATA;
break;
}
@ -481,7 +527,7 @@ void Sc53C94::sequencer()
this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out);
this->cur_state = SeqState::RCV_DATA;
this->rcv_data();
if (!(this->cmd_fifo[0] & 0x80)) {
if (!(this->is_dma_cmd)) {
this->cur_state = SeqState::XFER_END;
this->sequencer();
}
@ -491,7 +537,8 @@ void Sc53C94::sequencer()
if (this->is_initiator) {
this->bus_obj->target_next_step();
}
this->int_status |= INTSTAT_SR;
this->int_status = INTSTAT_SR;
this->cur_state = SeqState::IDLE;
this->update_irq();
exec_next_command();
break;
@ -501,7 +548,7 @@ void Sc53C94::sequencer()
// check for unexpected bus phase changes
if (this->bus_obj->current_phase() != this->cur_bus_phase) {
this->cmd_fifo_pos = 0; // clear command FIFO
this->int_status |= INTSTAT_SR;
this->int_status = INTSTAT_SR;
this->update_irq();
} else {
this->rcv_data();
@ -523,7 +570,7 @@ void Sc53C94::sequencer()
}
break;
default:
ABORT_F("SC53C94: unimplemented sequencer state %d", this->cur_state);
ABORT_F("%s: unimplemented sequencer state %d", this->name.c_str(), this->cur_state);
}
}
@ -532,25 +579,28 @@ void Sc53C94::update_irq()
uint8_t new_irq = !!(this->int_status != 0);
if (new_irq != this->irq) {
this->irq = new_irq;
this->status = (this->status & 0x7F) | (new_irq << 7);
this->status = (this->status & ~STAT_INT) | (new_irq << 7);
this->int_ctrl->ack_int(this->irq_id, new_irq);
}
}
void Sc53C94::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
void Sc53C94::notify(ScsiMsg msg_type, int param)
{
switch (msg_type) {
case ScsiMsg::CONFIRM_SEL:
if (this->target_id == param) {
// cancel selection timeout timer
TimerManager::get_instance()->cancel_timer(this->seq_timer_id);
seq_timer_id = 0;
this->cur_state = SeqState::SEL_END;
this->sequencer();
} else {
LOG_F(WARNING, "SC53C94: ignore invalid selection confirmation message");
LOG_F(WARNING, "%s: ignore invalid selection confirmation message",
this->name.c_str());
}
break;
case ScsiMsg::BUS_PHASE_CHANGE:
this->cur_bus_phase = param;
if (param != ScsiPhase::BUS_FREE && this->cmd_steps != nullptr) {
this->cmd_steps++;
this->cur_state = this->cmd_steps->next_step;
@ -558,7 +608,8 @@ void Sc53C94::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
}
break;
default:
LOG_F(9, "SC53C94: ignore notification message, type: %d", msg_type);
LOG_F(9, "%s: ignore notification message, type: %d", this->name.c_str(),
msg_type);
}
}
@ -577,7 +628,7 @@ int Sc53C94::send_data(uint8_t* dst_ptr, int count)
this->data_fifo_pos -= actual_count;
if (this->data_fifo_pos > 0) {
std::memmove(this->data_fifo, &this->data_fifo[actual_count], this->data_fifo_pos);
} else {
} else if (this->cur_bus_phase == ScsiPhase::DATA_OUT) {
this->cmd_steps++;
this->cur_state = this->cmd_steps->next_step;
this->sequencer();
@ -595,7 +646,7 @@ bool Sc53C94::rcv_data()
return false;
}
if ((this->cmd_fifo[0] & 0x80) && this->cur_bus_phase == ScsiPhase::DATA_IN) {
if (this->is_dma_cmd && this->cur_bus_phase == ScsiPhase::DATA_IN) {
req_count = std::min((int)this->xfer_count, DATA_FIFO_MAX - this->data_fifo_pos);
} else {
req_count = 1;
@ -606,55 +657,108 @@ bool Sc53C94::rcv_data()
return true;
}
void Sc53C94::real_dma_xfer(int direction)
void Sc53C94::real_dma_xfer_out()
{
bool is_done = false;
// transfer data from host's memory to target
if (direction) {
if (this->xfer_count) {
uint32_t got_bytes;
uint8_t* src_ptr;
this->dma_ch->pull_data(std::min((int)this->xfer_count, DATA_FIFO_MAX),
&got_bytes, &src_ptr);
std::memcpy(this->data_fifo, src_ptr, got_bytes);
this->data_fifo_pos = got_bytes;
this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos);
while (this->xfer_count) {
this->dma_ch->pull_data(std::min((int)this->xfer_count, DATA_FIFO_MAX),
&got_bytes, &src_ptr);
std::memcpy(this->data_fifo, src_ptr, got_bytes);
this->data_fifo_pos = got_bytes;
this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos);
this->xfer_count -= this->data_fifo_pos;
this->data_fifo_pos = 0;
if (!this->xfer_count) {
is_done = true;
this->status |= STAT_TC; // signal zero transfer count
this->cur_state = SeqState::XFER_END;
this->sequencer();
}
this->xfer_count -= this->data_fifo_pos;
this->data_fifo_pos = 0;
if (!this->xfer_count) {
this->status |= STAT_TC; // signal zero transfer count
this->cur_state = SeqState::XFER_END;
this->sequencer();
}
} else { // transfer data from target to host's memory
while (this->xfer_count) {
if (this->data_fifo_pos) {
this->dma_ch->push_data((char*)this->data_fifo, this->data_fifo_pos);
}
this->xfer_count -= this->data_fifo_pos;
this->data_fifo_pos = 0;
if (!this->xfer_count) {
is_done = true;
this->status |= STAT_TC; // signal zero transfer count
this->cur_state = SeqState::XFER_END;
this->sequencer();
}
}
// see if we need to refill FIFO
if (!this->data_fifo_pos && !is_done) {
this->sequencer();
}
}
if (this->xfer_count) {
this->dma_timer_id = TimerManager::get_instance()->add_oneshot_timer(
10000,
[this]() {
// re-enter the sequencer with the state specified in next_state
this->dma_timer_id = 0;
this->real_dma_xfer_out();
});
}
}
void Sc53C94::real_dma_xfer_in()
{
bool is_done = false;
// transfer data from target to host's memory
if (this->xfer_count && this->data_fifo_pos) {
this->dma_ch->push_data((char*)this->data_fifo, this->data_fifo_pos);
this->xfer_count -= this->data_fifo_pos;
this->data_fifo_pos = 0;
if (!this->xfer_count) {
is_done = true;
this->status |= STAT_TC; // signal zero transfer count
this->cur_state = SeqState::XFER_END;
this->sequencer();
}
}
// see if we need to refill FIFO
if (!this->data_fifo_pos && !is_done) {
this->sequencer();
this->dma_timer_id = TimerManager::get_instance()->add_oneshot_timer(
10000,
[this]() {
// re-enter the sequencer with the state specified in next_state
this->dma_timer_id = 0;
this->real_dma_xfer_in();
});
}
}
void Sc53C94::dma_wait() {
if (this->cur_bus_phase == ScsiPhase::DATA_IN && this->cur_state == SeqState::RCV_DATA) {
real_dma_xfer_in();
}
else if (this->cur_bus_phase == ScsiPhase::DATA_OUT && this->cur_state == SeqState::SEND_DATA) {
real_dma_xfer_out();
}
else {
this->dma_timer_id = TimerManager::get_instance()->add_oneshot_timer(
10000,
[this]() {
this->dma_timer_id = 0;
this->dma_wait();
});
}
}
void Sc53C94::dma_start()
{
dma_wait();
}
void Sc53C94::dma_stop()
{
if (this->dma_timer_id) {
TimerManager::get_instance()->cancel_timer(this->dma_timer_id);
this->dma_timer_id = 0;
}
}
static const PropMap Sc53C94_properties = {
{"hdd_img", new StrProperty("")},
{"cdr_img", new StrProperty("")},
};
static const DeviceDescription Sc53C94_Descriptor = {
Sc53C94::create, {}, {}
Sc53C94::create, {}, Sc53C94_properties
};
REGISTER_DEVICE(Sc53C94, Sc53C94_Descriptor);

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -29,75 +29,115 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef SC_53C94_H
#define SC_53C94_H
#include <devices/common/dmacore.h>
#include <devices/common/scsi/scsi.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/dbdma.h>
#include <cinttypes>
#include <functional>
#include <memory>
#define DATA_FIFO_MAX 16
class InterruptCtrl;
/** 53C94 read registers */
namespace Read {
enum Reg53C94 : uint8_t {
Xfer_Cnt_LSB = 0,
Xfer_Cnt_MSB = 1,
FIFO = 2,
Command = 3,
Status = 4,
Int_Status = 5,
Seq_Step = 6,
FIFO_Flags = 7,
Config_1 = 8,
Config_2 = 0xB,
Config_3 = 0xC,
Config_4 = 0xD, // Am53CF94 extension
Xfer_Cnt_Hi = 0xE, // Am53CF94 extension
Xfer_Cnt_LSB = 0, // Current Transfer Count Register LSB
Xfer_Cnt_MSB = 1, // Current Transfer Count Register MSB
FIFO = 2, // FIFO Register
Command = 3, // Command Register
Status = 4, // Status Register
Int_Status = 5, // Interrupt Status Register
Seq_Step = 6, // Internal State Register
FIFO_Flags = 7, // Current FIFO/Internal State Register
Config_1 = 8, // Control Register 1
//
//
Config_2 = 0xB, // Control Register 2
Config_3 = 0xC, // Control Register 3
Config_4 = 0xD, // Control Register 4
Xfer_Cnt_Hi = 0xE, // Current Transfer Count Register High ; Am53CF94 extension
//
};
};
/** 53C94 write registers */
namespace Write {
enum Reg53C94 : uint8_t {
Xfer_Cnt_LSB = 0,
Xfer_Cnt_MSB = 1,
FIFO = 2,
Command = 3,
Dest_Bus_ID = 4,
Sel_Timeout = 5,
Sync_Period = 6,
Sync_Offset = 7,
Config_1 = 8,
Clock_Factor = 9,
Test_Mode = 0xA,
Config_2 = 0xB,
Config_3 = 0xC,
Config_4 = 0xD, // Am53CF94 extension
Xfer_Cnt_Hi = 0xE, // Am53CF94 extension
Data_Align = 0xF
Xfer_Cnt_LSB = 0, // Start Transfer Count Register LSB
Xfer_Cnt_MSB = 1, // Start Transfer Count Register MSB
FIFO = 2, // FIFO Register
Command = 3, // Command Register
Dest_Bus_ID = 4, // SCSI Destination ID Register (DID)
Sel_Timeout = 5, // SCSI Timeout Register
Sync_Period = 6, // Synchronous Transfer Period Register
Sync_Offset = 7, // Synchronous Offset Register
Config_1 = 8, // Control Register 1
Clock_Factor = 9, // Clock Factor Register
Test_Mode = 0xA, // Forced Test Mode Register
Config_2 = 0xB, // Control Register 2
Config_3 = 0xC, // Control Register 3
Config_4 = 0xD, // Control Register 4 ; Am53CF94 extension
Xfer_Cnt_Hi = 0xE, // Start Transfer Count Register High ; Am53CF94 extension
Data_Align = 0xF, // Data Alignment Register
};
};
/** NCR53C94/Am53CF94 commands. */
enum {
CMD_NOP = 0,
CMD_CLEAR_FIFO = 1,
CMD_RESET_DEVICE = 2,
CMD_RESET_BUS = 3,
CMD_DMA_STOP = 4,
CMD_XFER = 0x10,
CMD_COMPLETE_STEPS = 0x11,
CMD_MSG_ACCEPTED = 0x12,
CMD_SELECT_NO_ATN = 0x41,
CMD_SELECT_WITH_ATN = 0x42,
CMD_ENA_SEL_RESEL = 0x44,
// General Commands
CMD_NOP = 0x00, // no interrupt
CMD_CLEAR_FIFO = 0x01, // no interrupt
CMD_RESET_DEVICE = 0x02, // no interrupt
CMD_RESET_BUS = 0x03,
// Initiator commands
CMD_XFER = 0x10,
CMD_COMPLETE_STEPS = 0x11,
CMD_MSG_ACCEPTED = 0x12,
//CMD_TRANSFER_PAD_BYTES = 0x18,
CMD_SET_ATN = 0x1A, // no interrupt
//CMD_RESET_ATN = 0x1B, // no interrupt
// Target commands
//CMD_SEND_MESSAGE = 0x20,
//CMD_SEND_STATUS = 0x21,
//CMD_SEND_DATA = 0x22,
//CMD_DISCONNECT_STEPS = 0x23,
//CMD_TERMINATE_STEPS = 0x24,
//CMD_TARGET_COMMAND_COMPLETE_STEPS = 0x25,
//CMD_DISCONNECT = 0x27, // no interrupt
//CMD_RECEIVE_MESSAGE_STEPS = 0x28,
//CMD_RECEIVE_COMMAND = 0x29,
//CMD_RECEIVE_DATA = 0x2A,
//CMD_RECEIVE_COMMAND_STEPS = 0x2B,
CMD_DMA_STOP = 0x04, // no interrupt
CMD_ACCESS_FIFO_COMMAND = 0x05,
// Idle Commands
//CMD_RESELECT_STEPS = 0x40,
CMD_SELECT_NO_ATN = 0x41,
CMD_SELECT_WITH_ATN = 0x42,
//CMD_SELECT_WITH_ATN_AND_STOP = 0x43,
CMD_ENA_SEL_RESEL = 0x44, // no interrupt
//CMD_DISABLE_SEL_RESEL = 0x45,
//CMD_SELECT_WITH_ATN3_STEPS = 0x46,
//CMD_RESELECT_WITH_ATN3_STEPS = 0x47,
// Flags
CMD_OPCODE = 0x7F,
CMD_ISDMA = 0x80,
};
/** Status register bits. **/
enum {
STAT_TC = 1 << 4, // Terminal count (NCR) / count to zero (AMD)
STAT_GE = 1 << 6, // Gross Error (NCR) / Illegal Operation Error (AMD)
//SCSI_CTRL_IO = 0x01, // Input/Output
//SCSI_CTRL_CD = 0x02, // Command/Data
//SCSI_CTRL_MSG = 0x04, // Message
//STAT_GCV = 0x08, // Group Code Valid
STAT_TC = 0x10, // Terminal count (NCR) / count to zero (AMD)
//STAT_PE = 0x20, // Parity Error
STAT_GE = 0x40, // Gross Error (NCR) / Illegal Operation Error (AMD)
STAT_INT = 0x80, // Interrupt
};
/** Interrupt status register bits. */
@ -139,11 +179,14 @@ namespace SeqState {
/** Sequence descriptor for sequencer commands. */
typedef struct {
int seq_id;
int next_step;
int step_num;
int status;
} SeqDesc;
typedef std::function<void(const uint8_t drq_state)> DrqCb;
class Sc53C94 : public ScsiDevice {
public:
Sc53C94(uint8_t chip_id=12, uint8_t my_id=7);
@ -160,17 +203,34 @@ public:
uint8_t read(uint8_t reg_offset);
void write(uint8_t reg_offset, uint8_t value);
uint16_t pseudo_dma_read();
void pseudo_dma_write(uint16_t data);
// real DMA control
void real_dma_xfer(int direction);
void real_dma_xfer_out();
void real_dma_xfer_in();
void dma_start();
void dma_wait();
void dma_stop();
void set_dma_channel(DmaBidirChannel *dma_ch) {
this->dma_ch = dma_ch;
auto dbdma_ch = dynamic_cast<DMAChannel*>(dma_ch);
if (dbdma_ch) {
dbdma_ch->set_callbacks(
std::bind(&Sc53C94::dma_start, this),
std::bind(&Sc53C94::dma_stop, this)
);
}
};
void set_drq_callback(DrqCb cb) {
this->drq_cb = cb;
}
// ScsiDevice methods
void notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param);
void notify(ScsiMsg msg_type, int param);
bool prepare_data() { return false; };
bool get_more_data() { return false; };
bool has_data() { return this->data_fifo_pos != 0; };
int send_data(uint8_t* dst_ptr, int count);
void process_command() {};
@ -191,38 +251,38 @@ protected:
void update_irq();
private:
uint8_t chip_id;
uint8_t my_bus_id;
ScsiBus* bus_obj;
uint32_t my_timer_id;
uint8_t chip_id = 0;
uint8_t my_bus_id = 0;
uint32_t my_timer_id = 0;
uint8_t cmd_fifo[2];
uint8_t data_fifo[16];
int cmd_fifo_pos;
int data_fifo_pos;
int bytes_out;
int cmd_fifo_pos = 0;
int data_fifo_pos = 0;
int bytes_out = 0;
bool on_reset = false;
uint32_t xfer_count;
uint32_t set_xfer_count;
uint8_t status;
uint8_t target_id;
uint8_t int_status;
uint8_t seq_step;
uint8_t sel_timeout;
uint8_t sync_offset;
uint8_t clk_factor;
uint8_t config1;
uint8_t config2;
uint8_t config3;
uint32_t xfer_count = 0;
uint32_t set_xfer_count = 0;
uint8_t status = 0;
uint8_t target_id = 0;
uint8_t int_status = 0;
uint8_t seq_step = 0;
uint8_t sel_timeout = 0;
uint8_t sync_offset = 0;
uint8_t clk_factor = 0;
uint8_t config1 = 0;
uint8_t config2 = 0;
uint8_t config3 = 0;
// sequencer state
uint32_t seq_timer_id;
uint32_t cur_state;
uint32_t next_state;
SeqDesc* cmd_steps;
bool is_initiator;
uint8_t cur_cmd;
int cur_bus_phase;
uint32_t seq_timer_id = 0;
uint32_t cur_state = 0;
uint32_t next_state = 0;
SeqDesc* cmd_steps = nullptr;
bool is_initiator = false;
uint8_t cur_cmd = 0;
bool is_dma_cmd = false;
int cur_bus_phase = 0;
// interrupt related stuff
InterruptCtrl* int_ctrl = nullptr;
@ -230,7 +290,9 @@ private:
uint8_t irq = 0;
// DMA related stuff
DmaBidirChannel* dma_ch;
DmaBidirChannel* dma_ch = nullptr;
DrqCb drq_cb = nullptr;
uint32_t dma_timer_id = 0;
};
#endif // SC_53C94_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -73,6 +73,12 @@ namespace ScsiStatus {
namespace ScsiMessage {
enum : uint8_t {
COMMAND_COMPLETE = 0,
TARGET_ROUTINE_NUMBER_MASK = 0x07,
LOGICAL_UNIT_NUMBER_MASK = 0x07,
IS_TARGET_ROUTINE_NuMBER = 0x20,
HAS_DISCONNECT_PRIVILEDGE = 0x40,
IDENTIFY = 0x80,
};
};
@ -89,7 +95,7 @@ enum ScsiCommand : uint8_t {
TEST_UNIT_READY = 0x00,
REWIND = 0x01,
REQ_SENSE = 0x03,
FORMAT = 0x04,
FORMAT_UNIT = 0x04,
READ_BLK_LIMITS = 0x05,
READ_6 = 0x08,
WRITE_6 = 0x0A,
@ -109,6 +115,8 @@ enum ScsiCommand : uint8_t {
WRITE_10 = 0x2A,
VERIFY_10 = 0x2F,
READ_LONG_10 = 0x35,
WRITE_BUFFER = 0x3B,
READ_BUFFER = 0x3C,
MODE_SENSE_10 = 0x5A,
READ_12 = 0xA8,
@ -162,36 +170,55 @@ typedef std::function<void()> action_callback;
class ScsiDevice : public HWComponent {
public:
ScsiDevice(int my_id) {
ScsiDevice(std::string name, int my_id) {
this->set_name(name);
supports_types(HWCompType::SCSI_DEV);
this->scsi_id = my_id;
this->lun = 0,
this->cur_phase = ScsiPhase::BUS_FREE;
};
~ScsiDevice() = default;
virtual void notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param);
virtual void next_step(ScsiBus* bus_obj);
virtual void notify(ScsiMsg msg_type, int param);
virtual void next_step();
virtual void prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out);
virtual void switch_phase(const int new_phase);
virtual bool has_data() { return this->data_size != 0; };
virtual int xfer_data();
virtual int send_data(uint8_t* dst_ptr, int count);
virtual int rcv_data(const uint8_t* src_ptr, const int count);
virtual bool check_lun();
void illegal_command(const uint8_t* cmd);
virtual bool prepare_data() = 0;
virtual bool get_more_data() = 0;
virtual void process_command() = 0;
void set_bus_object_ptr(ScsiBus *bus_obj_ptr) {
this->bus_obj = bus_obj_ptr;
}
protected:
uint8_t cmd_buf[16] = {};
uint8_t msg_buf[16] = {}; // TODO: clarify how big this one should be
int scsi_id;
int lun;
int initiator_id;
int cur_phase;
uint8_t* data_ptr = nullptr;
int data_size;
int incoming_size;
uint8_t status;
int sense;
int sense;
int asc;
int ascq;
int sksv;
int field;
bool last_selection_has_atention = false;
uint8_t last_selection_message = 0;
ScsiBus* bus_obj;
action_callback pre_xfer_action = nullptr;
@ -204,14 +231,16 @@ public:
ScsiBus(const std::string name);
~ScsiBus() = default;
static std::unique_ptr<HWComponent> create_first() {
return std::unique_ptr<ScsiBus>(new ScsiBus("SCSIO"));
static std::unique_ptr<HWComponent> create_ScsiMesh() {
return std::unique_ptr<ScsiBus>(new ScsiBus("ScsiMesh"));
}
static std::unique_ptr<HWComponent> create_second() {
return std::unique_ptr<ScsiBus>(new ScsiBus("SCSI1"));
static std::unique_ptr<HWComponent> create_ScsiCurio() {
return std::unique_ptr<ScsiBus>(new ScsiBus("ScsiCurio"));
}
void attach_scsi_devices(const std::string bus_suffix);
// low-level state management
void register_device(int id, ScsiDevice* dev_obj);
int current_phase() { return this->cur_phase; };
@ -237,6 +266,7 @@ public:
void disconnect(int dev_id);
bool pull_data(const int id, uint8_t* dst_ptr, const int size);
bool push_data(const int id, const uint8_t* src_ptr, const int size);
int target_xfer_data();
void target_next_step();
bool negotiate_xfer(int& bytes_in, int& bytes_out);
@ -251,7 +281,7 @@ private:
uint16_t dev_ctrl_lines[SCSI_MAX_DEVS] = {};
uint16_t ctrl_lines;
int cur_phase;
int cur_phase = ScsiPhase::BUS_FREE;
int arb_winner_id;
int initiator_id;
int target_id;

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -23,10 +23,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/hwcomponent.h>
#include <devices/common/scsi/scsi.h>
#include <devices/common/scsi/scsihd.h>
#include <devices/common/scsi/scsicdrom.h>
#include <devices/deviceregistry.h>
#include <machines/machinebase.h>
#include <loguru.hpp>
#include <cinttypes>
#include <sstream>
ScsiBus::ScsiBus(const std::string name)
{
@ -52,6 +56,8 @@ void ScsiBus::register_device(int id, ScsiDevice* dev_obj)
}
this->devices[id] = dev_obj;
dev_obj->set_bus_object_ptr(this);
}
void ScsiBus::change_bus_phase(int initiator_id)
@ -60,13 +66,15 @@ void ScsiBus::change_bus_phase(int initiator_id)
if (i == initiator_id)
continue; // don't notify the initiator
if (this->devices[i] != nullptr) {
this->devices[i]->notify(this, ScsiMsg::BUS_PHASE_CHANGE, this->cur_phase);
this->devices[i]->notify(ScsiMsg::BUS_PHASE_CHANGE, this->cur_phase);
}
}
}
void ScsiBus::assert_ctrl_line(int initiator_id, uint16_t mask)
{
DCHECK_F(initiator_id >= 0 && initiator_id < SCSI_MAX_DEVS, "ScsiBus: invalid initiator ID %d", initiator_id);
uint16_t new_state = 0xFFFFU & mask;
this->dev_ctrl_lines[initiator_id] |= new_state;
@ -84,6 +92,8 @@ void ScsiBus::assert_ctrl_line(int initiator_id, uint16_t mask)
void ScsiBus::release_ctrl_line(int id, uint16_t mask)
{
DCHECK_F(id >= 0 && id < SCSI_MAX_DEVS, "ScsiBus: invalid initiator ID %d", id);
uint16_t new_state = 0;
this->dev_ctrl_lines[id] &= ~mask;
@ -139,6 +149,9 @@ int ScsiBus::switch_phase(int id, int new_phase)
case ScsiPhase::MESSAGE_OUT:
this->release_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG);
break;
case ScsiPhase::MESSAGE_IN:
this->release_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG | SCSI_CTRL_IO);
break;
}
// enter new phase (low-level)
@ -155,6 +168,9 @@ int ScsiBus::switch_phase(int id, int new_phase)
case ScsiPhase::MESSAGE_OUT:
this->assert_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG);
break;
case ScsiPhase::MESSAGE_IN:
this->assert_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG | SCSI_CTRL_IO);
break;
}
// switch the bus to the new phase (high-level)
@ -221,7 +237,7 @@ void ScsiBus::confirm_selection(int target_id)
// notify initiator about selection confirmation from target
if (this->initiator_id >= 0) {
this->devices[this->initiator_id]->notify(this, ScsiMsg::CONFIRM_SEL, target_id);
this->devices[this->initiator_id]->notify(ScsiMsg::CONFIRM_SEL, target_id);
}
}
@ -247,17 +263,28 @@ bool ScsiBus::pull_data(const int id, uint8_t* dst_ptr, const int size)
bool ScsiBus::push_data(const int id, const uint8_t* src_ptr, const int size)
{
if (!this->devices[id]->rcv_data(src_ptr, size)) {
LOG_F(ERROR, "ScsiBus: error while transferring I->T data!");
if (!this->devices[id]) {
LOG_F(ERROR, "%s: no device %d for push_data %d bytes", this->get_name().c_str(), id, size);
return false;
}
if (!this->devices[id]->rcv_data(src_ptr, size)) {
if (size) {
LOG_F(ERROR, "%s: error while transferring I->T data!", this->get_name().c_str());
return false;
}
}
return true;
}
int ScsiBus::target_xfer_data() {
return this->devices[this->target_id]->xfer_data();
}
void ScsiBus::target_next_step()
{
this->devices[this->target_id]->next_step(this);
this->devices[this->target_id]->next_step();
}
bool ScsiBus::negotiate_xfer(int& bytes_in, int& bytes_out)
@ -276,13 +303,60 @@ void ScsiBus::disconnect(int dev_id)
}
}
static const DeviceDescription Scsi0_Descriptor = {
ScsiBus::create_first, {}, {}
void ScsiBus::attach_scsi_devices(const std::string bus_suffix)
{
std::string path;
int scsi_id;
std::string image_path;
image_path = GET_STR_PROP("hdd_img" + bus_suffix);
if (!image_path.empty()) {
std::istringstream image_stream(image_path);
while (std::getline(image_stream, path, ':')) {
// do two passes because we skip ID 3.
for (scsi_id = 0; scsi_id < SCSI_MAX_DEVS * 2 && (scsi_id == 3 || this->devices[scsi_id % SCSI_MAX_DEVS]); scsi_id++) {}
if (scsi_id < SCSI_MAX_DEVS * 2) {
scsi_id = scsi_id % SCSI_MAX_DEVS;
std::string scsi_device_name = "ScsiHD" + bus_suffix + "," + std::to_string(scsi_id);
ScsiHardDisk *scsi_device = new ScsiHardDisk(scsi_device_name, scsi_id);
gMachineObj->add_device(scsi_device_name, std::unique_ptr<ScsiHardDisk>(scsi_device));
this->register_device(scsi_id, scsi_device);
scsi_device->insert_image(path);
}
else {
LOG_F(ERROR, "%s: Too many devices. HDD \"%s\" was not added.", this->get_name().c_str(), path.c_str());
}
}
}
image_path = GET_STR_PROP("cdr_img" + bus_suffix);
if (!image_path.empty()) {
std::istringstream image_stream(image_path);
while (std::getline(image_stream, path, ':')) {
// do two passes because we start at ID 3.
for (scsi_id = 3; scsi_id < SCSI_MAX_DEVS * 2 && this->devices[scsi_id % SCSI_MAX_DEVS]; scsi_id++) {}
if (scsi_id < SCSI_MAX_DEVS * 2) {
scsi_id = scsi_id % SCSI_MAX_DEVS;
std::string scsi_device_name = "ScsiCdrom" + bus_suffix + "," + std::to_string(scsi_id);
ScsiCdrom *scsi_device = new ScsiCdrom(scsi_device_name, scsi_id);
gMachineObj->add_device(scsi_device_name, std::unique_ptr<ScsiCdrom>(scsi_device));
this->register_device(scsi_id, scsi_device);
scsi_device->insert_image(path);
}
else {
LOG_F(ERROR, "%s: Too many devices. CD-ROM \"%s\" was not added.", this->get_name().c_str(), path.c_str());
}
}
}
}
static const DeviceDescription ScsiCurio_Descriptor = {
ScsiBus::create_ScsiCurio, {}, {}
};
static const DeviceDescription Scsi1_Descriptor = {
ScsiBus::create_second, {}, {}
static const DeviceDescription ScsiMesh_Descriptor = {
ScsiBus::create_ScsiMesh, {}, {}
};
REGISTER_DEVICE(Scsi0, Scsi0_Descriptor);
REGISTER_DEVICE(Scsi1, Scsi1_Descriptor);
REGISTER_DEVICE(ScsiCurio, ScsiCurio_Descriptor);
REGISTER_DEVICE(ScsiMesh, ScsiMesh_Descriptor);

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -28,53 +28,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <machines/machineproperties.h>
#include <memaccess.h>
#include <cinttypes>
#include <cstring>
#include <stdio.h>
#include <sys/stat.h>
using namespace std;
static char cdrom_vendor_sony_id[] = "SONY ";
static char cdu8003a_product_id[] = "CD-ROM CDU-8003A";
static char cdu8003a_revision_id[] = "1.9a";
ScsiCdrom::ScsiCdrom(int my_id) : ScsiDevice(my_id)
ScsiCdrom::ScsiCdrom(std::string name, int my_id) : CdromDrive(), ScsiDevice(name, my_id)
{
supports_types(HWCompType::SCSI_DEV);
this->sector_size = 2048;
this->pre_xfer_action = nullptr;
this->post_xfer_action = nullptr;
}
void ScsiCdrom::insert_image(std::string filename)
{
this->cdr_img.open(filename, ios::out | ios::in | ios::binary);
struct stat stat_buf;
int rc = stat(filename.c_str(), &stat_buf);
if (!rc) {
this->img_size = stat_buf.st_size;
this->total_frames = (this->img_size + this->sector_size - 1) / this->sector_size;
// create single track descriptor
this->tracks[0] = {.trk_num = 1, .adr_ctrl = 0x14, .start_lba = 0};
this->num_tracks = 1;
// create Lead-out descriptor containing all data
this->tracks[1] = {.trk_num = LEAD_OUT_TRK_NUM, .adr_ctrl = 0x14,
.start_lba = static_cast<uint32_t>(this->total_frames)};
} else {
ABORT_F("SCSI-CDROM: could not determine file size using stat()");
}
this->cdr_img.seekg(0, std::ios_base::beg);
}
void ScsiCdrom::process_command()
{
uint32_t lba, xfer_len;
uint32_t lba;
this->pre_xfer_action = nullptr;
this->post_xfer_action = nullptr;
@ -82,44 +46,106 @@ void ScsiCdrom::process_command()
// assume successful command execution
this->status = ScsiStatus::GOOD;
switch (cmd_buf[0]) {
// use internal data buffer by default
this->data_ptr = this->data_buf;
uint8_t* cmd = this->cmd_buf;
switch (cmd[0]) {
case ScsiCommand::TEST_UNIT_READY:
this->switch_phase(ScsiPhase::STATUS);
this->test_unit_ready();
break;
case ScsiCommand::REWIND:
this->illegal_command(cmd);
break;
case ScsiCommand::REQ_SENSE:
this->illegal_command(cmd);
break;
case ScsiCommand::FORMAT_UNIT:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_BLK_LIMITS:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_6:
lba = ((cmd_buf[1] & 0x1F) << 16) + (cmd_buf[2] << 8) + cmd_buf[3];
this->read(lba, cmd_buf[4], 6);
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
this->read(lba, cmd[4], 6);
break;
case ScsiCommand::WRITE_6:
this->illegal_command(cmd);
break;
case ScsiCommand::SEEK_6:
this->illegal_command(cmd);
break;
case ScsiCommand::INQUIRY:
this->inquiry();
break;
case ScsiCommand::VERIFY_6:
this->illegal_command(cmd);
break;
case ScsiCommand::MODE_SELECT_6:
this->incoming_size = this->cmd_buf[4];
this->switch_phase(ScsiPhase::DATA_OUT);
this->mode_select_6(cmd[4]);
break;
case ScsiCommand::RELEASE_UNIT:
this->illegal_command(cmd);
break;
case ScsiCommand::ERASE_6:
this->illegal_command(cmd);
break;
case ScsiCommand::MODE_SENSE_6:
this->mode_sense();
this->mode_sense_6();
break;
case ScsiCommand::START_STOP_UNIT:
this->illegal_command(cmd);
break;
case ScsiCommand::DIAG_RESULTS:
this->illegal_command(cmd);
break;
case ScsiCommand::SEND_DIAGS:
this->illegal_command(cmd);
break;
case ScsiCommand::PREVENT_ALLOW_MEDIUM_REMOVAL:
this->eject_allowed = (this->cmd_buf[4] & 1) == 0;
this->eject_allowed = (cmd[4] & 1) == 0;
this->switch_phase(ScsiPhase::STATUS);
break;
case ScsiCommand::READ_CAPACITY_10:
this->read_capacity();
this->read_capacity_10();
break;
case ScsiCommand::READ_10:
lba = READ_DWORD_BE_U(&this->cmd_buf[2]);
xfer_len = READ_WORD_BE_U(&this->cmd_buf[7]);
if (this->cmd_buf[1] & 1) {
ABORT_F("SCSI-CDROM: RelAdr bit set in READ_10");
lba = READ_DWORD_BE_U(&cmd[2]);
if (cmd[1] & 1) {
ABORT_F("%s: RelAdr bit set in READ_10", this->name.c_str());
}
read(lba, xfer_len, 10);
read(lba, READ_WORD_BE_U(&cmd[7]), 10);
break;
case ScsiCommand::WRITE_10:
this->illegal_command(cmd);
break;
case ScsiCommand::VERIFY_10:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_LONG_10:
this->illegal_command(cmd);
break;
case ScsiCommand::MODE_SENSE_10:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_12:
this->illegal_command(cmd);
break;
// CD-ROM specific commands
case ScsiCommand::READ_TOC:
this->read_toc();
break;
case ScsiCommand::SET_CD_SPEED:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_CD:
this->illegal_command(cmd);
break;
default:
ABORT_F("SCSI_CDROM: unsupported command %d", this->cmd_buf[0]);
this->illegal_command(cmd);
}
}
@ -127,58 +153,74 @@ bool ScsiCdrom::prepare_data()
{
switch (this->cur_phase) {
case ScsiPhase::DATA_IN:
this->data_ptr = (uint8_t*)this->data_buf;
this->data_size = this->bytes_out;
break;
case ScsiPhase::DATA_OUT:
this->data_ptr = (uint8_t*)this->data_buf;
this->data_size = 0;
break;
case ScsiPhase::STATUS:
break;
default:
LOG_F(WARNING, "SCSI_CDROM: unexpected phase in prepare_data");
LOG_F(WARNING, "%s: unexpected phase in prepare_data", this->name.c_str());
return false;
}
return true;
}
void ScsiCdrom::read(const uint32_t lba, const uint16_t transfer_len, const uint8_t cmd_len)
{
uint32_t transfer_size = transfer_len;
std::memset(this->data_buf, 0, sizeof(this->data_buf));
if (cmd_len == 6 && transfer_len == 0) {
transfer_size = 256;
bool ScsiCdrom::get_more_data() {
if (this->data_left()) {
this->data_size = this->read_more();
this->data_ptr = (uint8_t *)this->data_cache.get();
}
transfer_size *= this->sector_size;
uint64_t device_offset = lba * this->sector_size;
return this->data_size != 0;
}
this->cdr_img.seekg(device_offset, this->cdr_img.beg);
this->cdr_img.read(this->data_buf, transfer_size);
void ScsiCdrom::read(uint32_t lba, uint16_t nblocks, uint8_t cmd_len)
{
if (!check_lun())
return;
if (cmd_len == 6 && nblocks == 0)
nblocks = 256;
this->set_fpos(lba);
this->data_ptr = (uint8_t *)this->data_cache.get();
this->bytes_out = this->read_begin(nblocks, UINT32_MAX);
this->bytes_out = transfer_size;
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
this->switch_phase(ScsiPhase::DATA_IN);
}
void ScsiCdrom::inquiry()
int ScsiCdrom::test_unit_ready()
{
this->switch_phase(ScsiPhase::STATUS);
return ScsiError::NO_ERROR;
}
void ScsiCdrom::inquiry() {
int page_num = cmd_buf[2];
int alloc_len = cmd_buf[4];
if (page_num) {
ABORT_F("SCSI_CDROM: invalid page number in INQUIRY");
ABORT_F("%s: invalid page number in INQUIRY", this->name.c_str());
}
if (alloc_len > 36) {
LOG_F(WARNING, "SCSI_CDROM: more than 36 bytes requested in INQUIRY");
LOG_F(ERROR, "%s: more than 36 bytes requested in INQUIRY", this->name.c_str());
}
this->data_buf[0] = 5; // device type: CD-ROM
int lun;
if (this->last_selection_has_atention) {
LOG_F(INFO, "%s: INQUIRY (%d bytes) with ATN LUN = %02x & 7", this->name.c_str(), alloc_len, this->last_selection_message);
lun = this->last_selection_message & 7;
}
else {
LOG_F(INFO, "%s: INQUIRY (%d bytes) with NO ATN LUN = %02x >> 5", this->name.c_str(), alloc_len, cmd_buf[1]);
lun = cmd_buf[1] >> 5;
}
this->data_buf[0] = (lun == this->lun) ? 5 : 0x7f; // device type: CD-ROM
this->data_buf[1] = 0x80; // removable media
this->data_buf[2] = 2; // ANSI version: SCSI-2
this->data_buf[3] = 1; // response data format
@ -186,11 +228,20 @@ void ScsiCdrom::inquiry()
this->data_buf[5] = 0;
this->data_buf[6] = 0;
this->data_buf[7] = 0x18; // supports synchronous xfers and linked commands
std::memcpy(&this->data_buf[8], cdrom_vendor_sony_id, 8);
std::memcpy(&this->data_buf[16], cdu8003a_product_id, 16);
std::memcpy(&this->data_buf[32], cdu8003a_revision_id, 4);
std::memcpy(&this->data_buf[8], vendor_info, 8);
std::memcpy(&this->data_buf[16], prod_info, 16);
std::memcpy(&this->data_buf[32], rev_info, 4);
//std::memcpy(&this->data_buf[36], serial_number, 8);
//etc.
this->bytes_out = 36;
if (alloc_len < 36) {
LOG_F(ERROR, "Inappropriate Allocation Length: %d", alloc_len);
}
else {
memset(&this->data_buf[36], 0, alloc_len - 36);
}
this->bytes_out = alloc_len;
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
this->switch_phase(ScsiPhase::DATA_IN);
@ -198,12 +249,10 @@ void ScsiCdrom::inquiry()
static char Apple_Copyright_Page_Data[] = "APPLE COMPUTER, INC ";
void ScsiCdrom::mode_sense()
void ScsiCdrom::mode_sense_6()
{
uint8_t page_code = this->cmd_buf[2] & 0x3F;
uint8_t alloc_len = this->cmd_buf[4];
int num_blocks = (this->img_size + this->sector_size) / this->sector_size;
//uint8_t alloc_len = this->cmd_buf[4];
this->data_buf[ 0] = 13; // initial data size
this->data_buf[ 1] = 0; // medium type
@ -211,13 +260,13 @@ void ScsiCdrom::mode_sense()
this->data_buf[ 3] = 8; // block description length
this->data_buf[ 4] = 0; // density code
this->data_buf[ 5] = (num_blocks >> 16) & 0xFFU;
this->data_buf[ 6] = (num_blocks >> 8) & 0xFFU;
this->data_buf[ 7] = (num_blocks ) & 0xFFU;
this->data_buf[ 5] = (this->size_blocks >> 16) & 0xFFU;
this->data_buf[ 6] = (this->size_blocks >> 8) & 0xFFU;
this->data_buf[ 7] = (this->size_blocks ) & 0xFFU;
this->data_buf[ 8] = 0;
this->data_buf[ 9] = 0;
this->data_buf[10] = (this->sector_size >> 8) & 0xFFU;
this->data_buf[11] = (this->sector_size ) & 0xFFU;
this->data_buf[10] = (this->block_size >> 8) & 0xFFU;
this->data_buf[11] = (this->block_size ) & 0xFFU;
this->data_buf[12] = page_code;
@ -232,8 +281,24 @@ void ScsiCdrom::mode_sense()
std::memcpy(&this->data_buf[14], Apple_Copyright_Page_Data, 22);
this->data_buf[0] += 23;
break;
case 0x31:
this->data_buf[13] = 6; // data size
std::memset(&this->data_buf[14], 0, 6);
this->data_buf[14] = '.';
this->data_buf[15] = 'A';
this->data_buf[16] = 'p';
this->data_buf[17] = 'p';
break;
default:
ABORT_F("SCSI-HD: unsupported page %d in MODE_SENSE_6", page_code);
LOG_F(WARNING, "%s: unsupported page 0x%02x in MODE_SENSE_6", this->name.c_str(), page_code);
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 2;
this->switch_phase(ScsiPhase::STATUS);
return;
}
this->bytes_out = this->data_buf[0];
@ -242,6 +307,19 @@ void ScsiCdrom::mode_sense()
this->switch_phase(ScsiPhase::DATA_IN);
}
int ScsiCdrom::mode_select_6(uint8_t param_len)
{
if (param_len == 0) {
return 0x0;
}
else {
LOG_F(ERROR, "Mode Select calling for param length of: %d", param_len);
this->incoming_size = param_len;
this->switch_phase(ScsiPhase::DATA_OUT);
return param_len;
}
}
void ScsiCdrom::read_toc()
{
int tot_tracks;
@ -250,11 +328,11 @@ void ScsiCdrom::read_toc()
bool is_msf = !!(this->cmd_buf[1] & 2);
if (this->cmd_buf[2] & 0xF) {
ABORT_F("SCSI-CDROM: unsupported format in READ_TOC");
ABORT_F("%s: unsupported format in READ_TOC", this->name.c_str());
}
if (!alloc_len) {
LOG_F(WARNING, "SCSI-CDROM: zero allocation length passed to READ_TOC");
LOG_F(WARNING, "%s: zero allocation length passed to READ_TOC", this->name.c_str());
return;
}
@ -268,14 +346,19 @@ void ScsiCdrom::read_toc()
} else if (start_track <= this->num_tracks) {
tot_tracks = (this->num_tracks - start_track) + 2;
} else {
LOG_F(ERROR, "SCSI-CDROM: invalid starting track %d in READ_TOC", start_track);
LOG_F(ERROR, "%s: invalid starting track %d in READ_TOC", this->name.c_str(),
start_track);
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 6; // offset of start_track
this->switch_phase(ScsiPhase::STATUS);
return;
}
char* buf_ptr = this->data_buf;
uint8_t* buf_ptr = this->data_buf;
int data_len = (tot_tracks * 8) + 2;
@ -316,49 +399,36 @@ void ScsiCdrom::read_toc()
this->switch_phase(ScsiPhase::DATA_IN);
}
void ScsiCdrom::read_capacity()
void ScsiCdrom::read_capacity_10()
{
uint32_t lba = READ_DWORD_BE_U(&this->cmd_buf[2]);
if (this->cmd_buf[1] & 1) {
ABORT_F("SCSI-CDROM: RelAdr bit set in READ_CAPACITY_10");
ABORT_F("%s: RelAdr bit set in READ_CAPACITY_10", this->name.c_str());
}
if (!(this->cmd_buf[8] & 1) && lba) {
LOG_F(ERROR, "SCSI-CDROM: non-zero LBA for PMI=0");
LOG_F(ERROR, "%s: non-zero LBA for PMI=0", this->name.c_str());
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 8;
this->switch_phase(ScsiPhase::STATUS);
return;
}
int last_lba = this->total_frames - 1;
if (!check_lun())
return;
int last_lba = (int)this->size_blocks - 1;
this->data_buf[0] = (last_lba >> 24) & 0xFFU;
this->data_buf[1] = (last_lba >> 16) & 0xFFU;
this->data_buf[2] = (last_lba >> 8) & 0xFFU;
this->data_buf[3] = (last_lba >> 0) & 0xFFU;
this->data_buf[4] = 0;
this->data_buf[5] = 0;
this->data_buf[6] = (this->sector_size >> 8) & 0xFFU;
this->data_buf[7] = this->sector_size & 0xFFU;
WRITE_DWORD_BE_A(&this->data_buf[0], last_lba);
WRITE_DWORD_BE_A(&this->data_buf[4], this->block_size);
this->bytes_out = 8;
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
this->switch_phase(ScsiPhase::DATA_IN);
}
AddrMsf ScsiCdrom::lba_to_msf(const int lba)
{
return {.min = lba / 4500, .sec = (lba / 75) % 60, .frm = lba % 75};
}
static const PropMap ScsiCdromProperties = {
{"cdr_img", new StrProperty("")},
};
static const DeviceDescription ScsiCdromDescriptor =
{ScsiCdrom::create, {}, ScsiCdromProperties};
REGISTER_DEVICE(ScsiCdrom, ScsiCdromDescriptor);

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -25,65 +25,41 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define SCSI_CDROM_H
#include <devices/common/scsi/scsi.h>
#include <devices/storage/cdromdrive.h>
#include <utils/imgfile.h>
#include <cinttypes>
#include <fstream>
#include <memory>
#include <string>
/* Original CD-ROM addressing mode expressed
in minutes, seconds and frames */
typedef struct {
int min;
int sec;
int frm;
} AddrMsf;
/* Descriptor for CD-ROM tracks in TOC */
typedef struct {
uint8_t trk_num;
uint8_t adr_ctrl;
uint32_t start_lba;
} TrackDescriptor;
#define CDROM_MAX_TRACKS 100
#define LEAD_OUT_TRK_NUM 0xAA
class ScsiCdrom : public ScsiDevice {
class ScsiCdrom : public ScsiDevice, public CdromDrive {
public:
ScsiCdrom(int my_id);
ScsiCdrom(std::string name, int my_id);
~ScsiCdrom() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<ScsiCdrom>(new ScsiCdrom(3));
}
void insert_image(std::string filename);
virtual void process_command();
virtual bool prepare_data();
virtual void process_command() override;
virtual bool prepare_data() override;
virtual bool get_more_data() override;
protected:
void read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len);
int test_unit_ready();
void read(uint32_t lba, uint16_t nblocks, uint8_t cmd_len);
void inquiry();
void mode_sense();
int mode_select_6(uint8_t param_len);
void mode_sense_6();
void read_toc();
void read_capacity();
void read_capacity_10();
private:
AddrMsf lba_to_msf(const int lba);
bool eject_allowed = true;
int bytes_out = 0;
uint8_t data_buf[2048] = {};
private:
std::fstream cdr_img;
uint64_t img_size;
int total_frames;
int num_tracks;
int sector_size;
bool eject_allowed = true;
int bytes_out = 0;
TrackDescriptor tracks[CDROM_MAX_TRACKS];
char data_buf[1 << 18]; // TODO: proper buffer management
//inquiry info
char vendor_info[9] = "SONY ";
char prod_info[17] = "CD-ROM CDU-8003A";
char rev_info[5] = "1.9a";
};
#endif // SCSI_CDROM_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -26,7 +26,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cinttypes>
#include <cstring>
void ScsiDevice::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
void ScsiDevice::notify(ScsiMsg msg_type, int param)
{
if (msg_type == ScsiMsg::BUS_PHASE_CHANGE) {
switch (param) {
@ -35,30 +35,25 @@ void ScsiDevice::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param)
break;
case ScsiPhase::SELECTION:
// check if something tries to select us
if (bus_obj->get_data_lines() & (1 << scsi_id)) {
if (this->bus_obj->get_data_lines() & (1 << scsi_id)) {
LOG_F(9, "ScsiDevice %d selected", this->scsi_id);
TimerManager::get_instance()->add_oneshot_timer(
BUS_SETTLE_DELAY,
[this, bus_obj]() {
[this]() {
// don't confirm selection if BSY or I/O are asserted
if (bus_obj->test_ctrl_lines(SCSI_CTRL_BSY | SCSI_CTRL_IO))
if (this->bus_obj->test_ctrl_lines(SCSI_CTRL_BSY | SCSI_CTRL_IO))
return;
bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_BSY);
bus_obj->confirm_selection(this->scsi_id);
this->initiator_id = bus_obj->get_initiator_id();
this->bus_obj = bus_obj;
if (bus_obj->test_ctrl_lines(SCSI_CTRL_ATN)) {
this->bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_BSY);
this->bus_obj->confirm_selection(this->scsi_id);
this->initiator_id = this->bus_obj->get_initiator_id();
if (this->bus_obj->test_ctrl_lines(SCSI_CTRL_ATN)) {
this->switch_phase(ScsiPhase::MESSAGE_OUT);
if (this->msg_buf[0] != 0x80) {
LOG_F(INFO, "ScsiDevice: received message 0x%X", this->msg_buf[0]);
}
}
this->switch_phase(ScsiPhase::COMMAND);
this->process_command();
if (this->prepare_data()) {
bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_REQ);
this->last_selection_has_atention = true;
this->last_selection_message = this->msg_buf[0];
//LOG_F(SCSIDEVICE, "%s: received message:0x%02x", this->get_name().c_str(), this->msg_buf[0]);
} else {
ABORT_F("ScsiDevice: prepare_data() failed");
this->last_selection_has_atention = false;
this->switch_phase(ScsiPhase::COMMAND);
}
});
}
@ -73,13 +68,9 @@ void ScsiDevice::switch_phase(const int new_phase)
this->bus_obj->switch_phase(this->scsi_id, this->cur_phase);
}
void ScsiDevice::next_step(ScsiBus* bus_obj)
void ScsiDevice::next_step()
{
switch (this->cur_phase) {
case ScsiPhase::COMMAND:
this->process_command();
this->switch_phase(ScsiPhase::DATA_IN);
break;
case ScsiPhase::DATA_OUT:
if (this->data_size >= this->incoming_size) {
if (this->post_xfer_action != nullptr) {
@ -93,12 +84,25 @@ void ScsiDevice::next_step(ScsiBus* bus_obj)
this->switch_phase(ScsiPhase::STATUS);
}
break;
case ScsiPhase::COMMAND:
this->process_command();
if (this->cur_phase != ScsiPhase::COMMAND) {
if (this->prepare_data()) {
this->bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_REQ);
} else {
ABORT_F("ScsiDevice: prepare_data() failed");
}
}
break;
case ScsiPhase::STATUS:
this->switch_phase(ScsiPhase::MESSAGE_IN);
break;
case ScsiPhase::MESSAGE_OUT:
this->switch_phase(ScsiPhase::COMMAND);
break;
case ScsiPhase::MESSAGE_IN:
case ScsiPhase::BUS_FREE:
bus_obj->release_ctrl_lines(this->scsi_id);
this->bus_obj->release_ctrl_lines(this->scsi_id);
this->switch_phase(ScsiPhase::BUS_FREE);
break;
default:
@ -113,7 +117,8 @@ void ScsiDevice::prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out)
switch (this->cur_phase) {
case ScsiPhase::COMMAND:
this->data_ptr = this->cmd_buf;
this->data_size = bytes_in;
this->data_size = 0;
bytes_out = 0;
break;
case ScsiPhase::STATUS:
this->data_ptr = &this->status;
@ -128,6 +133,7 @@ void ScsiDevice::prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out)
case ScsiPhase::MESSAGE_OUT:
this->data_ptr = this->msg_buf;
this->data_size = bytes_in;
bytes_out = 0;
break;
case ScsiPhase::MESSAGE_IN:
this->data_ptr = this->msg_buf;
@ -139,6 +145,42 @@ void ScsiDevice::prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out)
}
}
static const int CmdGroupLen[8] = {6, 10, 10, -1, -1, 12, -1, -1};
int ScsiDevice::xfer_data() {
this->cur_phase = bus_obj->current_phase();
switch (this->cur_phase) {
case ScsiPhase::MESSAGE_OUT:
if (this->bus_obj->pull_data(this->initiator_id, this->msg_buf, 1)) {
if (this->msg_buf[0] & 0x80) {
LOG_F(9, "%s: IDENTIFY MESSAGE received, code = 0x%X",
this->name.c_str(), this->msg_buf[0]);
this->next_step();
} else {
ABORT_F("%s: unsupported message received, code = 0x%X",
this->name.c_str(), this->msg_buf[0]);
}
}
break;
case ScsiPhase::COMMAND:
if (this->bus_obj->pull_data(this->initiator_id, this->cmd_buf, 1)) {
int cmd_len = CmdGroupLen[this->cmd_buf[0] >> 5];
if (cmd_len < 0) {
ABORT_F("%s: unsupported command received, code = 0x%X",
this->name.c_str(), this->msg_buf[0]);
}
if (this->bus_obj->pull_data(this->initiator_id, &this->cmd_buf[1], cmd_len - 1))
this->next_step();
}
break;
default:
ABORT_F("ScsiDevice: unhandled phase %d in xfer_data()", this->cur_phase);
}
return 0;
}
int ScsiDevice::send_data(uint8_t* dst_ptr, const int count)
{
if (dst_ptr == nullptr || !count) {
@ -151,6 +193,18 @@ int ScsiDevice::send_data(uint8_t* dst_ptr, const int count)
this->data_ptr += actual_count;
this->data_size -= actual_count;
// attempt to return the requested amount of data
// when data_size drops down to zero
if (!this->data_size) {
if (this->get_more_data() && count > actual_count) {
dst_ptr += actual_count;
actual_count = std::min(this->data_size, count - actual_count);
std::memcpy(dst_ptr, this->data_ptr, actual_count);
this->data_ptr += actual_count;
this->data_size -= actual_count;
}
}
return actual_count;
}
@ -161,5 +215,36 @@ int ScsiDevice::rcv_data(const uint8_t* src_ptr, const int count)
this->data_ptr += count;
this->data_size += count;
if (this->cur_phase == ScsiPhase::COMMAND)
this->next_step();
return count;
}
bool ScsiDevice::check_lun()
{
if (this->cmd_buf[1] >> 5 != this->lun) {
LOG_F(ERROR, "%s: non-matching LUN", this->name.c_str());
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x25; // Logical Unit Not Supported
this->ascq = 0;
this->sksv = 0;
this->field = 0;
this->switch_phase(ScsiPhase::STATUS);
return false;
}
return true;
}
void ScsiDevice::illegal_command(const uint8_t *cmd)
{
LOG_F(ERROR, "%s: unsupported command: 0x%02x", this->name.c_str(), cmd[0]);
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x20; // Invalid command operation code
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 0;
this->switch_phase(ScsiPhase::STATUS);
}

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -21,150 +21,178 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
/** @file Generic SCSI Hard Disk emulation. */
#include <core/timermanager.h>
#include <devices/common/scsi/scsi.h>
#include <devices/common/scsi/scsihd.h>
#include <devices/deviceregistry.h>
#include <loguru.hpp>
#include <machines/machinebase.h>
#include <machines/machineproperties.h>
#include <memaccess.h>
#include <fstream>
#include <cstring>
#include <stdio.h>
#include <sys/stat.h>
#define HDD_SECTOR_SIZE 512
using namespace std;
ScsiHardDisk::ScsiHardDisk(int my_id) : ScsiDevice(my_id) {
supports_types(HWCompType::SCSI_DEV);
ScsiHardDisk::ScsiHardDisk(std::string name, int my_id) : ScsiDevice(name, my_id) {
}
void ScsiHardDisk::insert_image(std::string filename) {
//We don't want to store everything in memory, but
//we want to keep the hard disk available.
this->hdd_img.open(filename, ios::out | ios::in | ios::binary);
if (!this->disk_img.open(filename))
ABORT_F("%s: could not open image file %s", this->name.c_str(), filename.c_str());
struct stat stat_buf;
int rc = stat(filename.c_str(), &stat_buf);
if (!rc) {
this->img_size = stat_buf.st_size;
this->total_blocks = (this->img_size + HDD_SECTOR_SIZE - 1) / HDD_SECTOR_SIZE;
} else {
ABORT_F("ScsiHardDisk: could not determine file size using stat()");
this->img_size = this->disk_img.size();
uint64_t tb = (this->img_size + this->sector_size - 1) / this->sector_size;
this->total_blocks = static_cast<int>(tb);
if (this->total_blocks < 0 || tb != this->total_blocks) {
ABORT_F("%s: file size is too large", this->name.c_str());
}
this->hdd_img.seekg(0, std::ios_base::beg);
}
void ScsiHardDisk::process_command() {
uint32_t lba = 0;
uint16_t transfer_len = 0;
uint16_t alloc_len = 0;
uint8_t param_len = 0;
uint8_t page_code = 0;
uint8_t subpage_code = 0;
uint32_t lba;
this->pre_xfer_action = nullptr;
this->post_xfer_action = nullptr;
// assume successful command execution
this->status = ScsiStatus::GOOD;
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
uint8_t* cmd = this->cmd_buf;
if (cmd[0] != 0 && cmd[0] != 8 && cmd[0] != 0xA && cmd[0] != 0x28
&& cmd[0] != 0x2A && cmd[0] != 0x25) {
ABORT_F("SCSI-HD: untested command 0x%X", cmd[0]);
}
switch (cmd[0]) {
case ScsiCommand::TEST_UNIT_READY:
test_unit_ready();
this->test_unit_ready();
break;
case ScsiCommand::REWIND:
rewind();
this->illegal_command(cmd);
break;
case ScsiCommand::REQ_SENSE:
alloc_len = cmd[4];
req_sense(alloc_len);
this->req_sense(cmd[4]);
break;
case ScsiCommand::INQUIRY:
alloc_len = (cmd[3] << 8) + cmd[4];
inquiry(alloc_len);
case ScsiCommand::FORMAT_UNIT:
this->format();
break;
case ScsiCommand::READ_BLK_LIMITS:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_6:
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
transfer_len = cmd[4];
read(lba, transfer_len, 6);
break;
case ScsiCommand::READ_10:
lba = (cmd[2] << 24) + (cmd[3] << 16) + (cmd[4] << 8) + cmd[5];
transfer_len = (cmd[7] << 8) + cmd[8];
read(lba, transfer_len, 10);
this->read(lba, cmd[4], 6);
break;
case ScsiCommand::WRITE_6:
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
transfer_len = cmd[4];
write(lba, transfer_len, 6);
break;
case ScsiCommand::WRITE_10:
lba = (cmd[2] << 24) + (cmd[3] << 16) + (cmd[4] << 8) + cmd[5];
transfer_len = (cmd[7] << 8) + cmd[8];
write(lba, transfer_len, 10);
this->switch_phase(ScsiPhase::DATA_OUT);
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
this->write(lba, cmd[4], 6);
break;
case ScsiCommand::SEEK_6:
lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3];
seek(lba);
this->illegal_command(cmd);
break;
case ScsiCommand::INQUIRY:
this->inquiry();
break;
case ScsiCommand::VERIFY_6:
this->illegal_command(cmd);
break;
case ScsiCommand::MODE_SELECT_6:
param_len = cmd[4];
mode_select_6(param_len);
mode_select_6(cmd[4]);
break;
case ScsiCommand::RELEASE_UNIT:
this->illegal_command(cmd);
break;
case ScsiCommand::ERASE_6:
this->illegal_command(cmd);
break;
case ScsiCommand::MODE_SENSE_6:
page_code = cmd[2] & 0x1F;
subpage_code = cmd[3];
alloc_len = cmd[4];
mode_sense_6(page_code, subpage_code, alloc_len);
this->mode_sense_6();
break;
case ScsiCommand::START_STOP_UNIT:
this->illegal_command(cmd);
break;
case ScsiCommand::DIAG_RESULTS:
this->illegal_command(cmd);
break;
case ScsiCommand::SEND_DIAGS:
this->illegal_command(cmd);
break;
case ScsiCommand::PREVENT_ALLOW_MEDIUM_REMOVAL:
this->eject_allowed = (cmd[4] & 1) == 0;
this->switch_phase(ScsiPhase::STATUS);
break;
case ScsiCommand::READ_CAPACITY_10:
read_capacity_10();
this->read_capacity_10();
break;
case ScsiCommand::READ_10:
lba = READ_DWORD_BE_U(&cmd[2]);
if (cmd[1] & 1) {
ABORT_F("%s: RelAdr bit set in READ_10", this->name.c_str());
}
this->read(lba, READ_WORD_BE_U(&cmd[7]), 10);
break;
case ScsiCommand::WRITE_10:
lba = READ_DWORD_BE_U(&cmd[2]);
this->write(lba, READ_WORD_BE_U(&cmd[7]), 10);
this->switch_phase(ScsiPhase::DATA_OUT);
break;
case ScsiCommand::VERIFY_10:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_BUFFER:
read_buffer();
break;
case ScsiCommand::MODE_SENSE_10:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_12:
this->illegal_command(cmd);
break;
// CD-ROM specific commands
case ScsiCommand::READ_TOC:
this->illegal_command(cmd);
break;
case ScsiCommand::SET_CD_SPEED:
this->illegal_command(cmd);
break;
case ScsiCommand::READ_CD:
this->illegal_command(cmd);
break;
default:
LOG_F(WARNING, "SCSI_HD: unrecognized command: %x", cmd[0]);
this->illegal_command(cmd);
}
}
bool ScsiHardDisk::prepare_data() {
switch (this->cur_phase) {
case ScsiPhase::DATA_IN:
this->data_ptr = (uint8_t*)this->img_buffer;
this->data_size = this->cur_buf_cnt;
this->data_ptr = (uint8_t*)this->data_buf;
this->data_size = this->bytes_out;
break;
case ScsiPhase::DATA_OUT:
this->data_ptr = (uint8_t*)this->img_buffer;
this->data_ptr = (uint8_t*)this->data_buf;
this->data_size = 0;
break;
case ScsiPhase::STATUS:
if (!error) {
this->img_buffer[0] = ScsiStatus::GOOD;
this->data_buf[0] = ScsiStatus::GOOD;
} else {
this->img_buffer[0] = ScsiStatus::CHECK_CONDITION;
this->data_buf[0] = ScsiStatus::CHECK_CONDITION;
}
this->cur_buf_cnt = 1;
this->bytes_out = 1;
this->data_ptr = (uint8_t*)this->data_buf;
this->data_size = this->bytes_out;
break;
case ScsiPhase::MESSAGE_IN:
this->img_buffer[0] = this->msg_code;
this->cur_buf_cnt = 1;
this->data_buf[0] = this->msg_code;
this->bytes_out = 1;
this->data_ptr = (uint8_t*)this->data_buf;
this->data_size = this->bytes_out;
break;
default:
LOG_F(WARNING, "SCSI_HD: unexpected phase in prepare_data");
LOG_F(WARNING, "%s: unexpected phase in prepare_data", this->name.c_str());
return false;
}
return true;
}
@ -174,96 +202,320 @@ int ScsiHardDisk::test_unit_ready() {
}
int ScsiHardDisk::req_sense(uint16_t alloc_len) {
if (alloc_len != 252) {
LOG_F(WARNING, "Inappropriate Allocation Length: %d", alloc_len);
}
return ScsiError::NO_ERROR; // placeholder - no sense
}
//if (!check_lun())
// return;
void ScsiHardDisk::inquiry(uint16_t alloc_len) {
if (alloc_len >= 48) {
uint8_t empty_filler[1 << 17] = {0x0};
std::memcpy(img_buffer, empty_filler, (1 << 17));
img_buffer[2] = 0x1;
img_buffer[3] = 0x2;
img_buffer[4] = 0x31;
img_buffer[7] = 0x1C;
std::memcpy(img_buffer + 8, vendor_info, 8);
std::memcpy(img_buffer + 16, prod_info, 16);
std::memcpy(img_buffer + 32, rev_info, 8);
std::memcpy(img_buffer + 40, serial_info, 8);
int next_phase;
int lun;
if (this->last_selection_has_atention) {
lun = this->last_selection_message & 7;
}
else {
LOG_F(WARNING, "Inappropriate Allocation Length: %d", alloc_len);
lun = cmd_buf[1] >> 5;
}
if (lun == this->lun) {
this->status = ScsiStatus::GOOD;
this->data_buf[ 2] = this->sense; // Reserved:0xf0, Sense Key:0x0f ; e.g. ScsiSense::ILLEGAL_REQ
this->data_buf[12] = this->asc; // addition sense code
this->data_buf[13] = this->ascq; // additional sense qualifier
this->data_buf[15] = this->sksv; // SKSV:0x80, C/D:0x40, Reserved:0x30, BPV:8, Bit Pointer:7
this->data_buf[16] = this->field >> 8; // field pointer
this->data_buf[17] = this->field;
}
else {
this->data_buf[ 2] = this->sense; // Reserved:0xf0, Sense Key:0x0f ; e.g. ScsiSense::ILLEGAL_REQ
this->data_buf[12] = 0x25; // addition sense code = Logical Unit Not Supported
this->data_buf[13] = 0; // additional sense qualifier
this->data_buf[15] = 0; // SKSV:0x80, C/D:0x40, Reserved:0x30, BPV:8, Bit Pointer:7
this->data_buf[16] = 0; // field pointer
this->data_buf[17] = 0;
}
{
// FIXME: there should be a way to set the VALID and ILI bits.
this->data_buf[ 0] = 0x70; // Valid:0x80, Error Code:0x7f
this->data_buf[ 1] = 0; // segment number
this->data_buf[ 3] = 0; // information
this->data_buf[ 4] = 0;
this->data_buf[ 5] = 0;
this->data_buf[ 6] = 0;
this->data_buf[ 7] = 10; // additional sense length
this->data_buf[ 8] = 0; // command specific information
this->data_buf[ 9] = 0;
this->data_buf[10] = 0;
this->data_buf[11] = 0;
this->data_buf[14] = 0; // field replaceable unit code
this->data_buf[18] = 0; // reserved
this->data_buf[19] = 0; // reserved
}
this->bytes_out = alloc_len; // Open Firmware 1.0.5 asks for 18 bytes.
this->switch_phase(ScsiPhase::DATA_IN);
return ScsiError::NO_ERROR;
}
void ScsiHardDisk::inquiry() {
int page_num = cmd_buf[2];
int alloc_len = cmd_buf[4];
if (page_num) {
ABORT_F("%s: invalid page number in INQUIRY", this->name.c_str());
}
if (alloc_len > 36) {
LOG_F(INFO, "%s: %d bytes requested in INQUIRY", this->name.c_str(), alloc_len);
}
int lun;
if (this->last_selection_has_atention) {
LOG_F(INFO, "%s: INQUIRY (%d bytes) with ATN LUN = %02x & 7", this->name.c_str(), alloc_len, this->last_selection_message);
lun = this->last_selection_message & 7;
}
else {
LOG_F(INFO, "%s: INQUIRY (%d bytes) with NO ATN LUN = %02x >> 5", this->name.c_str(), alloc_len, cmd_buf[1]);
lun = cmd_buf[1] >> 5;
}
this->data_buf[0] = (lun == this->lun) ? 0 : 0x7f; // device type: Direct-access block device (hard drive)
this->data_buf[1] = 0; // non-removable media; 0x80 = removable media
this->data_buf[2] = 2; // ANSI version: SCSI-2
this->data_buf[3] = 1; // response data format
this->data_buf[4] = 0x1F; // additional length
this->data_buf[5] = 0;
this->data_buf[6] = 0;
this->data_buf[7] = 0x18; // supports synchronous xfers and linked commands
std::memcpy(&this->data_buf[8], vendor_info, 8);
std::memcpy(&this->data_buf[16], prod_info, 16);
std::memcpy(&this->data_buf[32], rev_info, 4);
//std::memcpy(&this->data_buf[36], serial_number, 8);
//etc.
if (alloc_len < 36) {
LOG_F(ERROR, "%s: allocation length too small: %d", this->name.c_str(),
alloc_len);
}
else {
memset(&this->data_buf[36], 0, alloc_len - 36);
}
this->bytes_out = alloc_len;
this->switch_phase(ScsiPhase::DATA_IN);
}
int ScsiHardDisk::send_diagnostic() {
return 0x0;
}
int ScsiHardDisk::mode_select_6(uint8_t param_len) {
if (param_len == 0) {
return 0x0;
}
else {
LOG_F(WARNING, "Mode Select calling for param length of: %d", param_len);
return param_len;
void ScsiHardDisk::mode_select_6(uint8_t param_len) {
if (!param_len) {
this->switch_phase(ScsiPhase::STATUS);
return;
}
this->incoming_size = param_len;
std::memset(&this->data_buf[0], 0xDD, this->sector_size);
this->post_xfer_action = [this]() {
// TODO: parse the received mode parameter list here
};
this->switch_phase(ScsiPhase::DATA_OUT);
}
void ScsiHardDisk::mode_sense_6(uint8_t page_code, uint8_t subpage_code, uint8_t alloc_len) {
LOG_F(WARNING, "Page Code %d; Subpage Code: %d", page_code, subpage_code);
static char Apple_Copyright_Page_Data[] = "APPLE COMPUTER, INC ";
void ScsiHardDisk::mode_sense_6() {
uint8_t page_code = this->cmd_buf[2] & 0x3F;
uint8_t page_ctrl = this->cmd_buf[2] >> 6;
uint8_t sub_page_code = this->cmd_buf[3];
uint8_t alloc_len = this->cmd_buf[4];
if (page_ctrl == 1) {
LOG_F(INFO, "%s: page_ctrl 1 CHANGEABLE VALUES is not implemented", this->name.c_str());
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 2;
this->switch_phase(ScsiPhase::STATUS);
return;
}
if (page_ctrl == 2) {
LOG_F(ERROR, "%s: page_ctrl 2 DEFAULT VALUES is not implemented", this->name.c_str());
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 2;
this->switch_phase(ScsiPhase::STATUS);
return;
}
if (page_ctrl == 3) {
LOG_F(INFO, "%s: page_ctrl 3 SAVED VALUES is not implemented", this->name.c_str());
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x39; // Saving Parameters Not Supported
this->ascq = 0;
this->sksv = 0;
this->field = 0;
this->switch_phase(ScsiPhase::STATUS);
return;
}
this->data_buf[ 1] = 0; // medium type
this->data_buf[ 2] = 0; // 0:medium is not write protected; 0x80 write protected
this->data_buf[ 3] = 8; // block description length
WRITE_DWORD_BE_A(&this->data_buf[4], this->total_blocks);
WRITE_DWORD_BE_A(&this->data_buf[8], this->sector_size);
uint8_t *p_buf = &this->data_buf[12];
bool got_page = false;
int page_size;
if (page_code == 1 || page_code == 0x3f) { // read-write error recovery page
if (sub_page_code != 0x00 && sub_page_code != 0xff)
goto bad_sub_page;
page_size = 8;
p_buf[0] = 1; // page code
p_buf[1] = page_size - 2; // data size - 1
std::memset(&p_buf[2], 0, 6);
p_buf += page_size;
got_page = true;
}
if (page_code == 3 || page_code == 0x3f) { // Format device page
if (sub_page_code != 0x00 && sub_page_code != 0xff)
goto bad_sub_page;
page_size = 24;
p_buf[ 0] = 3; // page code
p_buf[ 1] = page_size - 2; // data size - 1
std::memset(&p_buf[2], 0, 22);
// default values taken from Empire 540/1080S manual
WRITE_WORD_BE_U(&p_buf[ 2], 6); // tracks per defect zone
WRITE_WORD_BE_U(&p_buf[ 4], 1); // alternate sectors per zone
WRITE_WORD_BE_U(&p_buf[10], 92); // sectors per track in the outermost zone
WRITE_WORD_BE_U(&p_buf[12], 512); // bytes per sector
WRITE_WORD_BE_U(&p_buf[14], 1); // interleave factor
WRITE_WORD_BE_U(&p_buf[16], 19); // track skew factor
WRITE_WORD_BE_U(&p_buf[18], 25); // cylinder skew factor
p_buf[20] = 0x80; // SSEC=1, HSEC=0, RMB=0, SURF=0, INS=0
p_buf += page_size;
got_page = true;
}
if (page_code == 0x30 || page_code == 0x3f) { // Copyright page for Apple certified drives
if (sub_page_code != 0x00 && sub_page_code != 0xff)
goto bad_sub_page;
page_size = 24;
p_buf[0] = 0x30; // page code
p_buf[1] = page_size - 2; // data size - 1
std::memcpy(&p_buf[2], Apple_Copyright_Page_Data, 22);
p_buf += page_size;
got_page = true;
}
if (!(got_page || page_code == 0x3f)) { // not any of the supported pages or all pages
LOG_F(WARNING, "%s: unsupported page 0x%02x in MODE_SENSE_6", this->name.c_str(), page_code);
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 2;
this->switch_phase(ScsiPhase::STATUS);
return;
bad_sub_page:
LOG_F(WARNING, "%s: unsupported page/subpage %02xh/%02xh in MODE_SENSE_6", this->name.c_str(), page_code, sub_page_code);
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 3;
this->switch_phase(ScsiPhase::STATUS);
return;
}
// adjust for overall mode sense data length
this->data_buf[0] = p_buf - this->data_buf - 1;
this->bytes_out = std::min((int)alloc_len, (int)this->data_buf[0] + 1);
this->switch_phase(ScsiPhase::DATA_IN);
}
void ScsiHardDisk::read_capacity_10() {
uint32_t lba = READ_DWORD_BE_U(&this->cmd_buf[2]);
if (this->cmd_buf[1] & 1) {
ABORT_F("SCSI-HD: RelAdr bit set in READ_CAPACITY_10");
ABORT_F("%s: RelAdr bit set in READ_CAPACITY_10", this->name.c_str());
}
if (!(this->cmd_buf[8] & 1) && lba) {
LOG_F(ERROR, "SCSI-HD: non-zero LBA for PMI=0");
LOG_F(ERROR, "%s: non-zero LBA for PMI=0", this->name.c_str());
this->status = ScsiStatus::CHECK_CONDITION;
this->sense = ScsiSense::ILLEGAL_REQ;
this->asc = 0x24; // Invalid Field in CDB
this->ascq = 0;
this->sksv = 0xc0; // sksv=1, C/D=Command, BPV=0, BP=0
this->field = 8;
this->switch_phase(ScsiPhase::STATUS);
return;
}
if (!check_lun())
return;
uint32_t last_lba = this->total_blocks - 1;
uint32_t blk_len = HDD_SECTOR_SIZE;
uint32_t blk_len = this->sector_size;
WRITE_DWORD_BE_A(&img_buffer[0], last_lba);
WRITE_DWORD_BE_A(&img_buffer[4], blk_len);
WRITE_DWORD_BE_A(&this->data_buf[0], last_lba);
WRITE_DWORD_BE_A(&this->data_buf[4], blk_len);
this->cur_buf_cnt = 8;
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
this->bytes_out = 8;
this->switch_phase(ScsiPhase::DATA_IN);
}
void ScsiHardDisk::format() {
LOG_F(WARNING, "%s: attempt to format the disk!", this->name.c_str());
if (this->cmd_buf[1] & 0x10)
ABORT_F("%s: defect list isn't supported yet", this->name.c_str());
TimerManager::get_instance()->add_oneshot_timer(NS_PER_SEC, [this]() {
this->switch_phase(ScsiPhase::STATUS);
});
}
void ScsiHardDisk::read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) {
if (!check_lun())
return;
uint32_t transfer_size = transfer_len;
std::memset(img_buffer, 0, sizeof(img_buffer));
std::memset(this->data_buf, 0, sizeof(this->data_buf));
if (cmd_len == 6 && transfer_len == 0) {
transfer_size = 256;
}
transfer_size *= HDD_SECTOR_SIZE;
uint64_t device_offset = lba * HDD_SECTOR_SIZE;
transfer_size *= this->sector_size;
uint64_t device_offset = (uint64_t)lba * this->sector_size;
this->hdd_img.seekg(device_offset, this->hdd_img.beg);
this->hdd_img.read(img_buffer, transfer_size);
this->disk_img.read(this->data_buf, device_offset, transfer_size);
this->cur_buf_cnt = transfer_size;
this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE;
this->bytes_out = transfer_size;
this->switch_phase(ScsiPhase::DATA_IN);
}
@ -275,33 +527,30 @@ void ScsiHardDisk::write(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) {
transfer_size = 256;
}
transfer_size *= HDD_SECTOR_SIZE;
uint64_t device_offset = lba * HDD_SECTOR_SIZE;
transfer_size *= this->sector_size;
uint64_t device_offset = (uint64_t)lba * this->sector_size;
this->incoming_size = transfer_size;
this->hdd_img.seekg(device_offset, this->hdd_img.beg);
this->post_xfer_action = [this]() {
this->hdd_img.write(this->img_buffer, this->incoming_size);
this->post_xfer_action = [this, device_offset]() {
this->disk_img.write(this->data_buf, device_offset, this->incoming_size);
};
}
void ScsiHardDisk::seek(uint32_t lba) {
uint64_t device_offset = lba * HDD_SECTOR_SIZE;
this->hdd_img.seekg(device_offset, this->hdd_img.beg);
void ScsiHardDisk::read_buffer() {
uint8_t mode = this->cmd_buf[1];
uint32_t alloc_len = (this->cmd_buf[6] << 24) | (this->cmd_buf[7] << 16) |
this->cmd_buf[8];
switch(mode) {
case 0: // Combined header and data mode
WRITE_DWORD_BE_A(&this->data_buf[0], 0x10000); // report buffer size of 64K
break;
default:
ABORT_F("%s: unsupported mode %d in READ_BUFFER", this->name.c_str(), mode);
}
this->bytes_out = alloc_len;
this->switch_phase(ScsiPhase::DATA_IN);
}
void ScsiHardDisk::rewind() {
this->hdd_img.seekg(0, this->hdd_img.beg);
}
static const PropMap SCSI_HD_Properties = {
{"hdd_img", new StrProperty("")},
{"hdd_wr_prot", new BinProperty(0)},
};
static const DeviceDescription SCSI_HD_Descriptor =
{ScsiHardDisk::create, {}, SCSI_HD_Properties};
REGISTER_DEVICE(ScsiHD, SCSI_HD_Descriptor);

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-24 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -25,58 +25,56 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#define SCSI_HD_H
#include <devices/common/scsi/scsi.h>
#include <utils/imgfile.h>
#include <cinttypes>
#include <fstream>
#include <memory>
#include <stdio.h>
#include <string>
class ScsiHardDisk : public ScsiDevice {
public:
ScsiHardDisk(int my_id);
ScsiHardDisk(std::string name, int my_id);
~ScsiHardDisk() = default;
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<ScsiHardDisk>(new ScsiHardDisk(0));
}
void insert_image(std::string filename);
void process_command();
bool prepare_data();
bool get_more_data() { return false; };
protected:
int test_unit_ready();
int req_sense(uint16_t alloc_len);
int send_diagnostic();
int mode_select_6(uint8_t param_len);
int test_unit_ready();
int req_sense(uint16_t alloc_len);
int send_diagnostic();
void mode_select_6(uint8_t param_len);
void mode_sense_6(uint8_t page_code, uint8_t subpage_code, uint8_t alloc_len);
void mode_sense_6();
void format();
void inquiry(uint16_t alloc_len);
void inquiry();
void read_capacity_10();
void read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len);
void write(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len);
void seek(uint32_t lba);
void rewind();
void read_buffer();
private:
std::fstream hdd_img;
ImgFile disk_img;
uint64_t img_size;
int total_blocks;
uint64_t file_offset = 0;
static const int sector_size = 512;
bool eject_allowed = true;
int bytes_out = 0;
char img_buffer[1 << 21]; // TODO: add proper buffer management!
uint8_t data_buf[1 << 21]; // TODO: add proper buffer management!
uint32_t cur_buf_cnt = 0;
uint8_t error = ScsiError::NO_ERROR;
uint8_t msg_code = 0;
uint8_t error = ScsiError::NO_ERROR;
uint8_t msg_code = 0;
//inquiry info
char vendor_info[8] = {'D', 'i', 'n', 'g', 'u', 's', 'D', '\0'};
char prod_info[16] = {'E', 'm', 'u', 'l', 'a', 't', 'e', 'd', ' ', 'D', 'i', 's', 'k', '\0'};
char rev_info[8] = {'d', 'i', '0', '0', '0', '0', '0', '1'};
char serial_info[8] = {'0', '0', '0', '0', '0', '0', '0', '0'};
char rev_info[4] = {'d', 'i', '0', '1'};
};
#endif // SCSI_HD_H

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -22,8 +22,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
/** High-level VIA-CUDA combo device emulation.
*/
#include <core/hostevents.h>
#include <core/timermanager.h>
#include <devices/common/adb/adb.h>
#include <devices/common/adb/adbbus.h>
#include <cpu/ppc/ppcemu.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/viacuda.h>
#include <devices/deviceregistry.h>
@ -32,7 +34,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <memaccess.h>
#include <cinttypes>
#include <memory>
#include <string>
#include <vector>
using namespace std;
@ -75,14 +78,37 @@ ViaCuda::ViaCuda() {
// PRAM is part of Cuda
this->pram_obj = std::unique_ptr<NVram> (new NVram("pram.bin", 256));
// ADB bus is driven by Cuda
this->adb_bus = std::unique_ptr<ADB_Bus> (new ADB_Bus());
// establish ADB bus connection
this->adb_bus_obj = dynamic_cast<AdbBus*>(gMachineObj->get_comp_by_type(HWCompType::ADB_HOST));
// autopoll handler will be called during post-processing of the host events
EventManager::get_instance()->add_post_handler(this, &ViaCuda::autopoll_handler);
this->cuda_init();
this->int_ctrl = nullptr;
}
ViaCuda::~ViaCuda()
{
if (this->sr_timer_on) {
TimerManager::get_instance()->cancel_timer(this->sr_timer_id);
this->sr_timer_on = false;
}
if (this->t1_active) {
TimerManager::get_instance()->cancel_timer(this->t1_timer_id);
this->t1_active = false;
}
if (this->t2_active) {
TimerManager::get_instance()->cancel_timer(this->t2_timer_id);
this->t2_active = false;
}
if (this->treq_timer_id) {
TimerManager::get_instance()->cancel_timer(this->treq_timer_id);
this->treq_timer_id = 0;
}
}
int ViaCuda::device_postinit()
{
this->int_ctrl = dynamic_cast<InterruptCtrl*>(
@ -325,32 +351,31 @@ void ViaCuda::write(uint8_t new_state) {
if (new_tip == this->old_tip && new_byteack == this->old_byteack)
return;
LOG_F(9, "Cuda state changed!");
this->old_tip = new_tip;
this->old_byteack = new_byteack;
if (new_tip) {
if (new_byteack) {
this->via_regs[VIA_B] |= CUDA_TREQ; /* negate TREQ */
this->via_regs[VIA_B] |= CUDA_TREQ; // negate TREQ
this->treq = 1;
if (this->in_count) {
process_packet();
/* start response transaction */
TimerManager::get_instance()->add_oneshot_timer(
// start response transaction
this->treq_timer_id = TimerManager::get_instance()->add_oneshot_timer(
USECS_TO_NSECS(13), // delay TREQ assertion for New World
[this]() {
this->via_regs[VIA_B] &= ~CUDA_TREQ; // assert TREQ
this->treq = 0;
this->treq_timer_id = 0;
});
}
this->in_count = 0;
} else {
LOG_F(9, "Cuda: enter sync state");
this->via_regs[VIA_B] &= ~CUDA_TREQ; /* assert TREQ */
this->via_regs[VIA_B] &= ~CUDA_TREQ; // assert TREQ
this->treq = 0;
this->in_count = 0;
this->out_count = 0;
@ -359,7 +384,7 @@ void ViaCuda::write(uint8_t new_state) {
// send dummy byte as idle acknowledge or attention
schedule_sr_int(USECS_TO_NSECS(61));
} else {
if (this->via_regs[VIA_ACR] & 0x10) { /* data transfer: Host --> Cuda */
if (this->via_regs[VIA_ACR] & 0x10) { // data transfer: Host --> Cuda
if (this->in_count < sizeof(this->in_buf)) {
this->in_buf[this->in_count++] = this->via_regs[VIA_SR];
// tell the system we've read the byte after 71 usecs
@ -367,7 +392,7 @@ void ViaCuda::write(uint8_t new_state) {
} else {
LOG_F(WARNING, "Cuda input buffer too small. Truncating data!");
}
} else { /* data transfer: Cuda --> Host */
} else { // data transfer: Cuda --> Host
(this->*out_handler)();
// tell the system we've written next byte after 88 usecs
schedule_sr_int(USECS_TO_NSECS(88));
@ -389,17 +414,19 @@ void ViaCuda::pram_out_handler()
/* sends data from out_buf until exhausted, then switches to next_out_handler */
void ViaCuda::out_buf_handler() {
if (this->out_pos < this->out_count) {
LOG_F(9, "OutBufHandler: sending next byte 0x%X", this->out_buf[this->out_pos]);
this->via_regs[VIA_SR] = this->out_buf[this->out_pos++];
if (!this->is_open_ended && this->out_pos >= this->out_count) {
// tell the host this will be the last byte
this->via_regs[VIA_B] |= CUDA_TREQ; // negate TREQ
this->treq = 1;
}
} else if (this->is_open_ended) {
LOG_F(9, "OutBufHandler: switching to next handler");
this->out_handler = this->next_out_handler;
this->next_out_handler = &ViaCuda::null_out_handler;
(this->*out_handler)();
} else {
LOG_F(9, "Sending last byte");
this->out_count = 0;
this->via_regs[VIA_B] |= CUDA_TREQ; /* negate TREQ */
this->via_regs[VIA_B] |= CUDA_TREQ; // negate TREQ
this->treq = 1;
}
}
@ -407,7 +434,7 @@ void ViaCuda::out_buf_handler() {
void ViaCuda::response_header(uint32_t pkt_type, uint32_t pkt_flag) {
this->out_buf[0] = pkt_type;
this->out_buf[1] = pkt_flag;
this->out_buf[2] = this->in_buf[1]; /* copy original cmd */
this->out_buf[2] = this->in_buf[1]; // copy original cmd
this->out_count = 3;
this->out_pos = 0;
this->out_handler = &ViaCuda::out_buf_handler;
@ -419,7 +446,7 @@ void ViaCuda::error_response(uint32_t error) {
this->out_buf[0] = CUDA_PKT_ERROR;
this->out_buf[1] = error;
this->out_buf[2] = this->in_buf[0];
this->out_buf[3] = this->in_buf[1]; /* copy original cmd */
this->out_buf[3] = this->in_buf[1]; // copy original cmd
this->out_count = 4;
this->out_pos = 0;
this->out_handler = &ViaCuda::out_buf_handler;
@ -437,7 +464,7 @@ void ViaCuda::process_packet() {
switch (this->in_buf[0]) {
case CUDA_PKT_ADB:
LOG_F(9, "Cuda: ADB packet received");
process_adb_command(this->in_buf[1], this->in_count - 2);
this->process_adb_command();
break;
case CUDA_PKT_PSEUDO:
LOG_F(9, "Cuda: pseudo command packet received");
@ -454,39 +481,45 @@ void ViaCuda::process_packet() {
}
}
void ViaCuda::process_adb_command(uint8_t cmd_byte, int data_count) {
int adb_dev = cmd_byte >> 4; // 2 for keyboard, 3 for mouse
int cmd = cmd_byte & 0xF;
void ViaCuda::process_adb_command() {
uint8_t adb_stat, output_size;
if (!cmd) {
LOG_F(9, "Cuda: ADB SendReset command requested");
response_header(CUDA_PKT_ADB, 0);
} else if (cmd == 1) {
LOG_F(9, "Cuda: ADB Flush command requested");
response_header(CUDA_PKT_ADB, 0);
} else if ((cmd & 0xC) == 8) {
LOG_F(9, "Cuda: ADB Listen command requested");
int adb_reg = cmd_byte & 0x3;
if (adb_bus->listen(adb_dev, adb_reg)) {
response_header(CUDA_PKT_ADB, 0);
for (int data_ptr = 0; data_ptr < adb_bus->get_output_len(); data_ptr++) {
this->in_buf[(2 + data_ptr)] = adb_bus->get_output_byte(data_ptr);
}
} else {
response_header(CUDA_PKT_ADB, 2);
adb_stat = this->adb_bus_obj->process_command(&this->in_buf[1],
this->in_count - 1);
response_header(CUDA_PKT_ADB, adb_stat);
output_size = this->adb_bus_obj->get_output_count();
if (output_size) {
std::memcpy(&this->out_buf[3], this->adb_bus_obj->get_output_buf(), output_size);
this->out_count += output_size;
}
}
void ViaCuda::autopoll_handler() {
if (!this->autopoll_enabled)
return;
uint8_t poll_command = this->adb_bus_obj->poll();
if (poll_command) {
if (!this->old_tip || !this->treq) {
LOG_F(WARNING, "Cuda transaction probably in progress");
}
} else if ((cmd & 0xC) == 0xC) {
LOG_F(9, "Cuda: ADB Talk command requested");
response_header(CUDA_PKT_ADB, 0);
int adb_reg = cmd_byte & 0x3;
if (adb_bus->talk(adb_dev, adb_reg, this->in_buf[2])) {
response_header(CUDA_PKT_ADB, 0);
} else {
response_header(CUDA_PKT_ADB, 2);
// prepare autopoll packet
response_header(CUDA_PKT_ADB, ADB_STAT_OK | ADB_STAT_AUTOPOLL);
this->out_buf[2] = poll_command; // put the proper ADB command
uint8_t output_size = this->adb_bus_obj->get_output_count();
if (output_size) {
std::memcpy(&this->out_buf[3], this->adb_bus_obj->get_output_buf(), output_size);
this->out_count += output_size;
}
} else {
LOG_F(ERROR, "Cuda: unsupported ADB command 0x%X", cmd);
error_response(CUDA_ERR_BAD_CMD);
// assert TREQ
this->via_regs[VIA_B] &= ~CUDA_TREQ;
this->treq = 0;
// draw guest system's attention
schedule_sr_int(USECS_TO_NSECS(30));
}
}
@ -498,8 +531,10 @@ void ViaCuda::pseudo_command(int cmd, int data_count) {
case CUDA_START_STOP_AUTOPOLL:
if (this->in_buf[2]) {
LOG_F(INFO, "Cuda: autopoll started, rate: %d ms", this->poll_rate);
this->autopoll_enabled = true;
} else {
LOG_F(INFO, "Cuda: autopoll stopped");
this->autopoll_enabled = false;
}
response_header(CUDA_PKT_PSEUDO, 0);
break;
@ -620,10 +655,14 @@ void ViaCuda::pseudo_command(int cmd, int data_count) {
LOG_F(INFO, "Cuda: send %d to PB0", (int)(this->in_buf[2]));
response_header(CUDA_PKT_PSEUDO, 0);
break;
case CUDA_RESTART_SYSTEM:
LOG_F(INFO, "Cuda: system restart");
power_on = false;
power_off_reason = po_restart;
break;
case CUDA_WARM_START:
case CUDA_POWER_DOWN:
case CUDA_MONO_STABLE_RESET:
case CUDA_RESTART_SYSTEM:
/* really kludge temp code */
LOG_F(INFO, "Cuda: Restart/Shutdown signal sent with command 0x%x!", cmd);
//exit(0);
@ -712,8 +751,12 @@ void ViaCuda::i2c_comb_transaction(
}
}
static const vector<string> Cuda_Subdevices = {
"AdbBus", "AdbMouse", "AdbKeyboard"
};
static const DeviceDescription ViaCuda_Descriptor = {
ViaCuda::create, {}, {}
ViaCuda::create, Cuda_Subdevices, {}
};
REGISTER_DEVICE(ViaCuda, ViaCuda_Descriptor);

View File

@ -1,6 +1,6 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-22 divingkatae and maximum
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
@ -43,14 +43,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef VIACUDA_H
#define VIACUDA_H
#include <devices/common/adb/adb.h>
#include <devices/common/hwcomponent.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/i2c/i2c.h>
#include <devices/common/nvram.h>
#include <memory>
class AdbBus;
class InterruptCtrl;
#define VIA_CLOCK_HZ 783360
/** VIA register offsets. */
@ -154,7 +155,7 @@ enum {
class ViaCuda : public HWComponent, public I2CBus {
public:
ViaCuda();
~ViaCuda() = default;
~ViaCuda();
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<ViaCuda>(new ViaCuda());
@ -205,6 +206,7 @@ private:
uint8_t old_tip;
uint8_t old_byteack;
uint8_t treq;
uint32_t treq_timer_id = 0;
uint8_t in_buf[CUDA_IN_BUF_SIZE];
int32_t in_count;
uint8_t out_buf[16];
@ -223,7 +225,9 @@ private:
void (ViaCuda::*next_out_handler)(void);
std::unique_ptr<NVram> pram_obj;
std::unique_ptr<ADB_Bus> adb_bus;
AdbBus* adb_bus_obj = nullptr;
bool autopoll_enabled = false;
// VIA methods
void print_enabled_ints(); // print enabled VIA interrupts and their sources
@ -241,7 +245,7 @@ private:
void response_header(uint32_t pkt_type, uint32_t pkt_flag);
void error_response(uint32_t error);
void process_packet();
void process_adb_command(uint8_t cmd_byte, int data_count);
void process_adb_command();
void pseudo_command(int cmd, int data_count);
void null_out_handler(void);
@ -249,6 +253,8 @@ private:
void out_buf_handler(void);
void i2c_handler(void);
void autopoll_handler();
/* I2C related methods */
void i2c_simple_transaction(uint8_t dev_addr, const uint8_t* in_buf, int in_bytes);
void i2c_comb_transaction(

View File

@ -24,7 +24,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/deviceregistry.h>
#include <devices/common/hwcomponent.h>
#include <memory>
#include <string>
bool DeviceRegistry::add(const std::string name, DeviceDescription desc)

View File

@ -24,7 +24,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef DEVICE_REGISTRY_H
#define DEVICE_REGISTRY_H
#include <devices/common/hwcomponent.h>
#include <machines/machineproperties.h>
#include <functional>
@ -33,6 +32,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <map>
#include <vector>
class HWComponent;
typedef std::function<unique_ptr<HWComponent> ()> CreateFunc;
struct DeviceDescription {

455
devices/ethernet/bigmac.cpp Normal file
View File

@ -0,0 +1,455 @@
/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** @file BigMac Ethernet controller emulation. */
#include <devices/deviceregistry.h>
#include <devices/ethernet/bigmac.h>
#include <loguru.hpp>
BigMac::BigMac(uint8_t id) {
set_name("BigMac");
supports_types(HWCompType::MMIO_DEV | HWCompType::ETHER_MAC);
this->chip_id = id;
this->chip_reset();
}
void BigMac::chip_reset() {
this->event_mask = 0xFFFFU; // disable HW events causing on-chip interrupts
this->stat = 0;
this->phy_reset();
this->mii_reset();
this->srom_reset();
}
uint16_t BigMac::read(uint16_t reg_offset) {
switch (reg_offset) {
case BigMacReg::XIFC:
return this->tx_if_ctrl;
case BigMacReg::CHIP_ID:
return this->chip_id;
case BigMacReg::MIF_CSR:
return (this->mif_csr_old & ~Mif_Data_In) | (this->mii_in_bit << 3);
case BigMacReg::GLOB_STAT: {
uint16_t old_stat = this->stat;
this->stat = 0; // clear-on-read
return old_stat;
}
case BigMacReg::EVENT_MASK:
return this->event_mask;
case BigMacReg::SROM_CSR:
return (this->srom_csr_old & ~Srom_Data_In) | (this->srom_in_bit << 2);
case BigMacReg::TX_SW_RST:
return this->tx_reset;
case BigMacReg::TX_CONFIG:
return this->tx_config;
case BigMacReg::PEAK_ATT: {
uint8_t old_val = this->peak_attempts;
this->peak_attempts = 0; // clear-on-read
return old_val;
}
case BigMacReg::NC_CNT:
return this->norm_coll_cnt;
case BigMacReg::EX_CNT:
return this->excs_coll_cnt;
case BigMacReg::LT_CNT:
return this->late_coll_cnt;
case BigMacReg::RX_CONFIG:
return this->rx_config;
default:
LOG_F(WARNING, "%s: unimplemented register at 0x%X", this->name.c_str(),
reg_offset);
}
return 0;
}
void BigMac::write(uint16_t reg_offset, uint16_t value) {
switch (reg_offset) {
case BigMacReg::XIFC:
this->tx_if_ctrl = value;
break;
case BigMacReg::TX_FIFO_CSR:
this->tx_fifo_enable = !!(value & 1);
this->tx_fifo_size = (((value >> 1) & 0xFF) + 1) << 7;
break;
case BigMacReg::TX_FIFO_TH:
this->tx_fifo_tresh = value;
break;
case BigMacReg::RX_FIFO_CSR:
this->rx_fifo_enable = !!(value & 1);
this->rx_fifo_size = (((value >> 1) & 0xFF) + 1) << 7;
break;
case BigMacReg::MIF_CSR:
if (value & Mif_Data_Out_En) {
// send bits one by one on each low-to-high transition of Mif_Clock
if (((this->mif_csr_old ^ value) & Mif_Clock) && (value & Mif_Clock))
this->mii_xmit_bit(!!(value & Mif_Data_Out));
} else {
if (((this->mif_csr_old ^ value) & Mif_Clock) && (value & Mif_Clock))
this->mii_rcv_bit();
}
this->mif_csr_old = value;
break;
case BigMacReg::EVENT_MASK:
this->event_mask = value;
break;
case BigMacReg::SROM_CSR:
if (value & Srom_Chip_Select) {
// exchange data on each low-to-high transition of Srom_Clock
if (((this->srom_csr_old ^ value) & Srom_Clock) && (value & Srom_Clock))
this->srom_xmit_bit(!!(value & Srom_Data_Out));
} else {
this->srom_reset();
}
this->srom_csr_old = value;
break;
case BigMacReg::TX_SW_RST:
if (value == 1) {
LOG_F(INFO, "%s: transceiver soft reset asserted", this->name.c_str());
this->tx_reset = 0; // acknowledge SW reset
}
break;
case BigMacReg::TX_CONFIG:
this->tx_config = value;
break;
case BigMacReg::NC_CNT:
this->norm_coll_cnt = value;
break;
case BigMacReg::NT_CNT:
this->net_coll_cnt = value;
break;
case BigMacReg::EX_CNT:
this->excs_coll_cnt = value;
break;
case BigMacReg::LT_CNT:
this->late_coll_cnt = value;
break;
case BigMacReg::RNG_SEED:
this->rng_seed = value;
break;
case BigMacReg::RX_SW_RST:
if (!value) {
LOG_F(INFO, "%s: receiver soft reset asserted", this->name.c_str());
}
break;
case BigMacReg::RX_CONFIG:
this->rx_config = value;
break;
case BigMacReg::MAC_ADDR_0:
case BigMacReg::MAC_ADDR_1:
case BigMacReg::MAC_ADDR_2:
this->mac_addr_flt[8 - ((reg_offset >> 4) & 0xF)] = value;
break;
case BigMacReg::RX_FRM_CNT:
this->rcv_frame_cnt = value;
break;
case BigMacReg::RX_LE_CNT:
this->len_err_cnt = value;
break;
case BigMacReg::RX_AE_CNT:
this->align_err_cnt = value;
break;
case BigMacReg::RX_FE_CNT:
this->fcs_err_cnt = value;
break;
case BigMacReg::RX_CVE_CNT:
this->cv_err_cnt = value;
break;
case BigMacReg::HASH_TAB_0:
case BigMacReg::HASH_TAB_1:
case BigMacReg::HASH_TAB_2:
case BigMacReg::HASH_TAB_3:
this->hash_table[(reg_offset >> 4) & 3] = value;
break;
default:
LOG_F(WARNING, "%s: unimplemented register at 0x%X is written with 0x%X",
this->name.c_str(), reg_offset, value);
}
}
// ================ Media Independent Interface (MII) emulation ================
bool BigMac::mii_rcv_value(uint16_t& var, uint8_t num_bits, uint8_t next_bit) {
var = (var << 1) | (next_bit & 1);
this->mii_bit_counter++;
if (this->mii_bit_counter >= num_bits) {
this->mii_bit_counter = 0;
return true; // all bits have been received -> return true
}
return false; // more bits expected
}
void BigMac::mii_rcv_bit() {
switch(this->mii_state) {
case MII_FRAME_SM::Preamble:
this->mii_in_bit = 1; // required for OSX
this->mii_reset();
break;
case MII_FRAME_SM::Turnaround:
this->mii_in_bit = 0;
this->mii_bit_counter = 16;
this->mii_state = MII_FRAME_SM::Read_Data;
break;
case MII_FRAME_SM::Read_Data:
if (this->mii_bit_counter) {
--this->mii_bit_counter;
this->mii_in_bit = (this->mii_data >> this->mii_bit_counter) & 1;
if (!this->mii_bit_counter) {
this->mii_state = MII_FRAME_SM::Preamble;
}
} else { // out of sync (shouldn't happen)
this->mii_reset();
}
break;
case MII_FRAME_SM::Stop:
this->mii_reset();
break;
default:
LOG_F(ERROR, "%s: unhandled state %d in mii_rcv_bit", this->name.c_str(),
this->mii_state);
this->mii_reset();
}
}
void BigMac::mii_xmit_bit(const uint8_t bit_val) {
switch(this->mii_state) {
case MII_FRAME_SM::Preamble:
if (bit_val) {
this->mii_bit_counter++;
if (this->mii_bit_counter >= 32) {
this->mii_state = MII_FRAME_SM::Start;
this->mii_in_bit = 1; // checked in OSX
this->mii_bit_counter = 0;
}
} else { // zero bit -> out of sync
this->mii_reset();
}
break;
case MII_FRAME_SM::Start:
if (this->mii_rcv_value(this->mii_start, 2, bit_val)) {
LOG_F(9, "MII_Start=0x%X", this->mii_start);
this->mii_state = MII_FRAME_SM::Opcode;
}
break;
case MII_FRAME_SM::Opcode:
if (this->mii_rcv_value(this->mii_opcode, 2, bit_val)) {
LOG_F(9, "MII_Opcode=0x%X", this->mii_opcode);
this->mii_state = MII_FRAME_SM::Phy_Address;
}
break;
case MII_FRAME_SM::Phy_Address:
if (this->mii_rcv_value(this->mii_phy_address, 5, bit_val)) {
LOG_F(9, "MII_PHY_Address=0x%X", this->mii_phy_address);
this->mii_state = MII_FRAME_SM::Reg_Address;
}
break;
case MII_FRAME_SM::Reg_Address:
if (this->mii_rcv_value(this->mii_reg_address, 5, bit_val)) {
LOG_F(9, "MII_REG_Address=0x%X", this->mii_reg_address);
if (this->mii_start != 1)
LOG_F(ERROR, "%s: unsupported frame type %d", this->name.c_str(),
this->mii_start);
if (this->mii_phy_address)
LOG_F(ERROR, "%s: unsupported PHY address %d", this->name.c_str(),
this->mii_phy_address);
switch (this->mii_opcode) {
case 1: // write
this->mii_state = MII_FRAME_SM::Turnaround;
break;
case 2: // read
this->mii_data = this->phy_reg_read(this->mii_reg_address);
this->mii_state = MII_FRAME_SM::Turnaround;
break;
default:
LOG_F(ERROR, "%s: invalid MII opcode %d", this->name.c_str(),
this->mii_opcode);
}
}
break;
case MII_FRAME_SM::Turnaround:
if (this->mii_rcv_value(this->mii_turnaround, 2, bit_val)) {
if (this->mii_turnaround != 2)
LOG_F(ERROR, "%s: unexpected turnaround 0x%X", this->name.c_str(),
this->mii_turnaround);
this->mii_state = MII_FRAME_SM::Write_Data;
}
break;
case MII_FRAME_SM::Write_Data:
if (this->mii_rcv_value(this->mii_data, 16, bit_val)) {
LOG_F(9, "%s: MII data received = 0x%X", this->name.c_str(),
this->mii_data);
this->phy_reg_write(this->mii_reg_address, this->mii_data);
this->mii_state = MII_FRAME_SM::Stop;
}
break;
case MII_FRAME_SM::Stop:
if (this->mii_rcv_value(this->mii_stop, 2, bit_val)) {
LOG_F(9, "MII_Stop=0x%X", this->mii_stop);
this->mii_reset();
}
break;
default:
LOG_F(ERROR, "%s: unhandled state %d in mii_xmit_bit", this->name.c_str(),
this->mii_state);
this->mii_reset();
}
}
void BigMac::mii_reset() {
mii_start = 0;
mii_opcode = 0;
mii_phy_address = 0;
mii_reg_address = 0;
mii_turnaround = 0;
mii_data = 0;
mii_stop = 0;
this->mii_bit_counter = 0;
this->mii_state = MII_FRAME_SM::Preamble;
}
// ===================== Ethernet PHY interface emulation =====================
void BigMac::phy_reset() {
// TODO: add PHY type property to be able to select another PHY (DP83843)
if (this->chip_id == EthernetCellId::Paddington) {
this->phy_oui = 0x1E0400; // LXT970 aka ST10040 PHY
this->phy_model = 0;
this->phy_rev = 0;
} else { // assume Heathrow with LXT907 PHY
this->phy_oui = 0; // LXT907 doesn't support MII, MDIO is pulled low
this->phy_model = 0;
this->phy_rev = 0;
}
this->phy_anar = 0xA1; // tell the world we support 10BASE-T and 100BASE-TX
}
uint16_t BigMac::phy_reg_read(uint8_t reg_num) {
switch(reg_num) {
case PHY_BMCR:
return this->phy_bmcr;
case PHY_BMSR:
return 0x7809; // value from LXT970 datasheet
case PHY_ID1:
return (this->phy_oui >> 6) & 0xFFFFU;
case PHY_ID2:
return ((this->phy_oui << 10) | (phy_model << 4) | phy_rev) & 0xFFFFU;
case PHY_ANAR:
return this->phy_anar;
default:
LOG_F(ERROR, "Reading unimplemented PHY register %d", reg_num);
}
return 0;
}
void BigMac::phy_reg_write(uint8_t reg_num, uint16_t value) {
switch(reg_num) {
case PHY_BMCR:
if (value & 0x8000) {
LOG_F(INFO, "PHY reset requested");
value &= ~0x8000; // Reset bit is self-clearing
}
this->phy_bmcr = value;
break;
case PHY_ANAR:
this->phy_anar = value;
break;
default:
LOG_F(ERROR, "Writing unimplemented PHY register %d", reg_num);
}
}
// ======================== MAC Serial EEPROM emulation ========================
void BigMac::srom_reset() {
this->srom_csr_old = 0;
this->srom_bit_counter = 0;
this->srom_opcode = 0;
this->srom_address = 0;
this->srom_state = Srom_Start;
}
bool BigMac::srom_rcv_value(uint16_t& var, uint8_t num_bits, uint8_t next_bit) {
var = (var << 1) | (next_bit & 1);
this->srom_bit_counter++;
if (this->srom_bit_counter >= num_bits) {
this->srom_bit_counter = 0;
return true; // all bits have been received -> return true
}
return false; // more bits expected
}
void BigMac::srom_xmit_bit(const uint8_t bit_val) {
switch(this->srom_state) {
case Srom_Start:
if (bit_val)
this->srom_state = Srom_Opcode;
else
this->srom_reset();
break;
case Srom_Opcode:
if (this->srom_rcv_value(this->srom_opcode, 2, bit_val)) {
switch(this->srom_opcode) {
case 2: // read
this->srom_state = Srom_Address;
break;
default:
LOG_F(ERROR, "%s: unsupported SROM opcode %d", this->name.c_str(),
this->srom_opcode);
this->srom_reset();
}
}
break;
case Srom_Address:
if (this->srom_rcv_value(this->srom_address, 6, bit_val)) {
LOG_F(9, "SROM address received = 0x%X", this->srom_address);
this->srom_bit_counter = 16;
this->srom_state = Srom_Read_Data;
}
break;
case Srom_Read_Data:
if (this->srom_bit_counter) {
this->srom_bit_counter--;
this->srom_in_bit = (this->srom_data[this->srom_address] >> this->srom_bit_counter) & 1;
if (!this->srom_bit_counter) {
this->srom_address++;
this->srom_bit_counter = 16;
}
}
break;
default:
LOG_F(ERROR, "%s: unhandled state %d in srom_xmit_bit", this->name.c_str(),
this->srom_state);
this->srom_reset();
}
}
static const DeviceDescription BigMac_Heathrow_Descriptor = {
BigMac::create_for_heathrow, {}, {}
};
static const DeviceDescription BigMac_Paddington_Descriptor = {
BigMac::create_for_paddington, {}, {}
};
REGISTER_DEVICE(BigMacHeathrow, BigMac_Heathrow_Descriptor);
REGISTER_DEVICE(BigMacPaddington, BigMac_Paddington_Descriptor);

Some files were not shown because too many files have changed in this diff Show More