BAR 0 exists on a real Power Mac 8600 and the dingusppc 7500.
On a Power Mac 8600, the initial value is 0x84000003. In Open Firmware, you can write to all bits of the BAR and read the value back except the 2 least significant bits are always %11. Bit 0 indicates I/O space. Bit 1 is reserved and should be zero so maybe this is not a real I/O space BAR. 0x8400000 is written to the BAR by Open Firmware. It doesn't look like a normal I/O address which are usually 16 bits.
On the emulated 7500, 0x02000000 is written to the BAR by Open Firmware sometime during probe-all. The BAR did not behave as it does in the Power Mac 8600. This commit fixes that.
Two questions remain:
1) Which fcode writes to the BAR? Is it the probe fcode or is it the control fcode? There's no config-_! in the control fcode.
2) What is the purpose of the BAR? Writing to it can cause a hang. The testbits code below seems to succeed - it restores the original value after reading the result of testing each bit and before displaying the result. The values shown for the MSB (0x84 on the 8600 and 0x02 on the 7500) could be three flag bits.
```
dev vci0
: testbits { adr ; org }
cr
adr config-l@ dup -> org ." original : " 8 u.r cr
20 0 do
1 1f i - << dup 8 u.r ." : "
adr config-l!
adr config-l@
org adr config-l!
8 u.r cr
loop
;
15810 testbits \ 15810 is the address of the BAR on the emulated 7500.
```
Usually bit 1 of I/O BARs is 0 since it is reserved, but control has an I/O BAR where this bit is set.
bar_cfg is used to determine the default values of the least significant bits (2 bits for I/O BARs and 4 bits for Memory BARs).
The upper bits of bar_cfg determine which bits can be set and also determines the size of the BAR.
Writes to config registers of invalid or non-existent PCI devices are logged. They should be logged with most significant byte first.
The values enter the methods in reverse byte order so they need to be byte swapped (except when size is 1) for logging.
The result is that this command in Open Firmware:
`12345678 16800 config-l!`
will log this:
`VCI0 err: write attempt to non-existing VCI device ??:0d.0 @00.l = 12345678`
The bits that can be set are the enable bit (bit 0) plus the bits represented by exp_bar_cfg which is determined by the size of the ROM which is calculated to be a power of 2 and a minimum of 2K.
The Pointer to PCI Data Structure is supposed to be two bytes. It is described in the PCI Firmware Specification Revision 3.0, section 5.1.1. PCI Expansion ROM Header Format.
The pointer is two bytes at 0x18. The pointer is supposed to be a multiple of 4 which means there's always at least two bytes of padding after the pointer. Some BIOS firmware images may use the 2 bytes following the pointer for other purposes (plus additional bytes before the PCI Data Structure) so we cannot assume the bytes will be zero.
Some PCI expansion ROMs may include both BIOS and Open Firmware images.
dingusppc could not read bytes from offset 1,2,3 or words from offset 2.
dingusppc did not read words from offset 1,3 and longs from offset 1,2,3 in the same way as a real Power Mac 8600 or B&W G3.
This commit fixes those issues.
- Added pci_cfg_rev_read. It takes a 32 bit value from offset 0 and returns a value of the specified size using bytes starting from the specified offset. Offsets 4,5, & 6 wrap around to 0,1, & 2 respectively. The result bytes are in flipped order as required by the pci_cfg_read method (so a value of 0x12345678 is returned as 0x78563412)
A real Power Mac 8600 might return a random byte for offset 4, 5, 6 for vci0 but usually not for pci1. A B&W G3 seems to always wrap around correctly. We won't read random bytes, and we won't read a default such as 00 or FF. We'll do the wrap around which makes the most sense because writing 0x12345678 to any offset and reading from the same offset should produce the value that was written.
- Added pci_cfg_rev_write. It takes a 32 bit value from offset 0, and modifies a specified number of bytes starting at a specified offset with the offset wrapping around to 0 if it exceeds 3. The modified bytes take their new values from the flipped bytes passed to pci_cfg_write. When size is 4, the original value is not used since all bytes will be modified.
Basically, those two functions handle all the sizes and all the offsets and replace calls to BYTESWAP_32, read_mem or read_mem_rev, and write_mem or write_mem_rev.
read_mem_rev, as it was used by pcidevice and some other places, could read beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always read the wrong byte or word if they were not at offset 0. Same for read_mem as used by mpc106.
write_mem_rev, as it was used by pcidevice and some other places, could write beyond offset 3 if it were ever passed a reg_offs value that did not have offset as 0. Since the offset was always zero, it would always write the wrong byte or word if they were not at offset 0. Same for write_mem as used by mpc106.
The PCI controllers (bandit, chaos, mpc106) need to encode the offset (0,1,2,3) into the reg_offs parameter passed to pci_cfg_read and pci_cfg_write so they can return or modify the correct bytes of the dword at reg_offs & 3.
The pci_cfg_read and pci_cfg_write methods extract the offset from reg_offs and report unaligned accesses.
pci_cfg_read uses pci_cfg_rev_read to read from the reg using the size and offset to determine which bytes to read.
pci_cfg_write uses pci_cfg_rev_write to write to the reg using the size and offset to determine which bytes to modify.
Other changes:
- for unimplemented config register reads and writes, bandit and ATIRage now includes offset and size (and value in the case of writes) in log warnings.
- for unimplemented config register reads and writes, pcidevice now includes offset in log warnings.
- pci_read and pci_write of mpc106 require an offset parameter since config_addr does not contain the offset (it is always a multiple of 4). The offset is included in the log warninings for non-existent PCI devices.
- ATIRage uses pci_cfg_rev_read and pci_cfg_rev_write which correctly places user_cfg at byte 0x40 instead of 0x43 and writes the correct byte depending on size and offset.
Notes:
- pci_cfg_read calls READ_DWORD_LE_A and pci_cfg_write calls WRITE_DWORD_LE_A. When reading or writing memory that is organized as little endian dwords, such as my_pci_cfg_hdr of mpc106, the function should explicitly state that it's little endian so that the emulator may be ported one day to a CPU architecture that is not little endian.
Writes to config registers of invalid or non-existent PCI devices are logged. They should be logged with most significant byte first.
The values enter the methods in reverse byte order so they need to be byte swapped (except when size is 1) for logging.
The result is that this command in Open Firmware:
`12345678 16800 config-l!`
will log this:
`VCI0 err: write attempt to non-existing VCI device ??:0d.0 @00.l = 12345678`
BAR 0 exists on a real Power Mac 8600 and the dingusppc 7500.
On a Power Mac 8600, the initial value is 0x84000003. In Open Firmware, you can write to all bits of the BAR and read the value back except the 2 least significant bits are always %11. Bit 0 indicates I/O space. Bit 1 is reserved and should be zero so maybe this is not a real I/O space BAR. 0x8400000 is written to the BAR by Open Firmware. It doesn't look like a normal I/O address which are usually 16 bits.
On the emulated 7500, 0x02000000 is written to the BAR by Open Firmware sometime during probe-all. The BAR did not behave as it does in the Power Mac 8600. This commit fixes that.
Two questions remain:
1) Which fcode writes to the BAR? Is it the probe fcode or is it the control fcode? There's no config-_! in the control fcode.
2) What is the purpose of the BAR? Writing to it can cause a hang. The testbits code below seems to succeed - it restores the original value after reading the result of testing each bit and before displaying the result. The values shown for the MSB (0x84 on the 8600 and 0x02 on the 7500) could be three flag bits.
```
dev vci0
: testbits { adr ; org }
cr
adr config-l@ dup -> org ." original : " 8 u.r cr
20 0 do
1 1f i - << dup 8 u.r ." : "
adr config-l!
adr config-l@
org adr config-l!
8 u.r cr
loop
;
15810 testbits \ 15810 is the address of the BAR on the emulated 7500.
```
Usually bit 1 of I/O BARs is 0 since it is reserved, but control has an I/O BAR where this bit is set.
bar_cfg is used to determine the default values of the least significant bits (2 bits for I/O BARs and 4 bits for Memory BARs).
The upper bits of bar_cfg determine which bits can be set and also determines the size of the BAR.
The bits that can be set are the enable bit (bit 0) plus the bits represented by exp_bar_cfg which is determined by the size of the ROM which is calculated to be a power of 2 and a minimum of 2K.
PCI config read fails should return all 1 bits.
All unused registers in an existing PCI device should return 0.
Because that's what my Power Mac 8600 returns when I run my Open Firmware lspci command.
Any bus/device/function that doesn't exist returns FF and won't be listed by lspci.
Any registers that are unused will show as 00 in the lspci output.
Make grackle log bus:device.function @register.size in all cases.
setenv doesn't allow entering strings that include multiple lines which is useful for nvramrc.
nvedit uses CTRL-C to end editing, like Open Firmware. It does not support deleting characters from a line or editing previous lines or any other special keys. It doesn't have nvstore or nvquit or nvrecover commands. Basically, you should edit your nvramrc in a text editor, then copy and paste into the debugger.
- Allow changing integer variables. Previously they could not be changed.
- Allow changing string variables. Previously they could only be changed if their length increased.
- printenv had a bug with strings longer than 32 characters.
- printenv now translates carriage return (used in nvramrc) as endl (which should perform carriage return and linefeed)
- For integers, setenv assumes decimal number input but if that fails it will try hex conversion. 0x prefix for hex numbers is optional unless the number only has digits 0 - 9.
- setenv converts linefeed to carriage return for string variables. nvramrc requires this for proper editing in Open Firmware.
- Added Open Firmware 2.4 nvram field names to OfNvramHdr comments.
- increase delay to 800 consecutive characters (reduces times when Open Firmware throws away pasted text)
- don't log the first sock read error (when accept hasn't been called yet) but do log sock read error if accept was called successfully already.
The expansion rom base register indicates the size of the expansion rom by the number of bits that remain zero after code attempts to set them all to 1. For example, a result of fffe0000 means 128K. The 11 least significant bits are ignored in the size calculation, which means the minimum rom size is indicated by fffff800 = 2K.
Handle the case where an expansion rom file might not have a size that is a multiple of 2 or is not greater than 2K.
Bytes between the end of the file and the end of the calculated rom size are set to 0xff.
Don't always return 0 when reading it. The status register contains some bits that Open Firmware uses to set some properties.
A PCI device can set a default status register value to set those bits.
this->status = 0x02B0; // 0000 0 01 0 1 0 1 1 0000 Capabilities List, 66 MHz, Fast Back-to-Back, DevSel Speed: Medium
For invalid or unsupported PCI accesses, do the following:
- log a device's full pci address using pciutils setpci command format bb:dd.f @rr.s (bus:device:function @register+offset.size).
- report as read or write access.
- log value for writes.
- bus, device, function, and register values cannot be determined from Invalid IDSEL values so they will output as ??.
- for invalid IDSEL values, report the entire value of the config_addr.
- for valid IDSEL values, the bus number cannot be determined since IDSEL only specifies device number. It's probably bus 00 but we'll show ?? to indicate an IDSEL type access.
Add missing config type read access logging for chaos.
With the option --serial_backend=socket, input and output to a serial port will use a SOCK_STREAM type UNIX domain socket. This allows you to do Open Firmware in one window, while the first window can be used for dingusppc debugger.
Other fixes:
- Added SIGTERM handler so that if the user force quits dingusppc, the terminal settings are properly restored. A user needs to force quit when --serial_backend=stdio and Open Firmware is taking input from ttya. If terminal settings are not restored, then running dingusppc after a force quit will cause Control-C to not work when --serial_backend is not stdio.
- Added a couple numbers to rcv_char_available - 15 is the number of consecutive characters that can processed. 400 is the total number of calls to rcv_char_available after 15 consecutive characters have been read before additional characters can be read. This delay in processing additional characters allows pasting arbitrarily large amounts of text into Open Firmware. A real serial port terminal app might have a text pacing option to limit the number of output characters per second but that is not an option since the emulator is not limiting character data to a baud rate.
Related Notes:
The socket file is created when dingusppc starts.
The socket file is named dingusppcsocket and is created in the current working directory (usually where the executable is located and where the dingusppc.log, nvram.bin, and pram.bin files are created).
The socket file is not visible in the Finder. You can see it in the terminal using the ls command.
The socket file can be used with the following command in a new terminal window:
socat UNIX-CLIENT:dingussocket -,cs8,parenb=0,echo=0,icanon=0,isig=0,icrnl=0
When dingusppc quits, the socat command ends.
Other notes:
The dingusppc --debugger option causes dingusppc to enter the debugger before Open Firmware outputs anything. You can connect to the socket while dingusppc is in the debugger. Then enter the go command to leave the debugger and start Open Firmware. However, since the startup sound takes a long time, you can probably connect to the socket before Open Firmware starts even without the --debugger option. It's like with a real Power Mac - you have a few seconds to hold Command-Option-O-F except in this case you have a few seconds to press the up arrow and press enter (for executing the last command from the terminal command history) and if you do it too late you'll still get into Open Firmware if auto-boot? was previously set to false using the dingusppc debugger which is actually the only way to get into Open Firmware since a keyboard is currently not emulated?).
To set ttya as the input and output device in Open Firmware, you can use the setenv command in the dingusppc debugger. The device path needs to be longer than the current device path (because code for handling shortening of the paths is currently not implemented). For example, ttya can replace kbd for the input-device, but to replace screen for the output-device you need to add some extra characters like this: ttya,11 (I think the number is for baud but we're not using a real serial port so baud doesn't matter).
Future ideas:
- Have dingusppc execute the socat command for you so that it opens a terminal window before Open Firmware starts.
- Add another --serial_backend for the printer port (ttyb) since now we have more than one type of serial backend. If both serial ports use socket backend, then a different name for the second socket is required.
- Have an option to make dingusppc block until something connects to the socket (this means calling accept after listen instead of after select).
- Test compatibility with serial port socket created by Parallels Desktop virtual machines in macOS.
- Find a solution that works with Windows.
- Test with Linux.
- Create a serial_backend type for tty devices. I suppose maybe socat can pipe the file socket to tty but a direct connection might be easier to setup.
- Allow using a socket created by some other app (for example, socat UNIX-LISTEN). This means dingusppc will assume the client role and will call connect instead of accept.
68k boot code in ROM uses those commands for applying patches
to Cuda and getting Cuda firmware version. This commit
implements as much as needed for boot code to work.