mirror of
https://github.com/dingusdev/dingusppc.git
synced 2024-06-13 02:29:46 +00:00
Compare commits
725 Commits
c363a254b9
...
f92cb92353
Author | SHA1 | Date | |
---|---|---|---|
|
f92cb92353 | ||
|
9a70c3bdb0 | ||
|
103ef6169c | ||
|
1a165ff64c | ||
|
8cbbb2ed50 | ||
|
781d9b46da | ||
|
ffa221192d | ||
|
751f964139 | ||
|
14f75d834a | ||
|
96efc99a00 | ||
|
45d2c8854d | ||
|
c22843f238 | ||
|
1c5009fcb0 | ||
|
bd63d1dcda | ||
|
8aaf211c5b | ||
|
8cc5838efe | ||
|
4b965c623b | ||
|
be27ceed00 | ||
|
1d75730d44 | ||
|
95d74a6940 | ||
|
c6ea3a374e | ||
|
e8ce805f2a | ||
|
3d898ebdf3 | ||
|
f45b7c47c8 | ||
|
916cb47b9d | ||
|
bce816139b | ||
|
24bce16c4d | ||
|
a928c67913 | ||
|
2b8f510603 | ||
|
e8273ecc61 | ||
|
e1f31a2da3 | ||
|
d897acfd3c | ||
|
1e57ac408a | ||
|
ef8522e101 | ||
|
c71d856a08 | ||
|
df7ff76404 | ||
|
0d1ce68d19 | ||
|
88aa249ce1 | ||
|
ff626ae0b5 | ||
|
529f23d836 | ||
|
cb88bab67d | ||
|
67a5c39b1c | ||
|
0273867c49 | ||
|
1e50d88183 | ||
|
29a832c68d | ||
|
cb05bd05eb | ||
|
4f45d7de35 | ||
|
a6ba9a0554 | ||
|
9c95bc17fe | ||
|
2c94cfee03 | ||
|
3c16870f86 | ||
|
22f45902ca | ||
|
28b1eb8524 | ||
|
1c8702d67a | ||
|
cf4913deb0 | ||
|
bdd441b1b6 | ||
|
4c9fe06229 | ||
|
e7da98b6bd | ||
|
dcdfaabedf | ||
|
524daa45a5 | ||
|
073b8fd981 | ||
|
d7749e0a2c | ||
|
7972a0f2a8 | ||
|
19dcb43658 | ||
|
9ed1a118e6 | ||
|
a5a5410515 | ||
|
40a4ca31b9 | ||
|
7f44ab2262 | ||
|
123c927b1a | ||
|
74274f164d | ||
|
5c2bd0b3bb | ||
|
d0a5a1e7be | ||
|
077e6ebae5 | ||
|
abe0c14301 | ||
|
61576d4032 | ||
|
782a8d2c3c | ||
|
e619dd2493 | ||
|
8a1055ed1b | ||
|
ff766b10eb | ||
|
ceb2276098 | ||
|
82f4d05f4b | ||
|
8a81cb4f9c | ||
|
1504bd2227 | ||
|
475f894582 | ||
|
7007e002e6 | ||
|
9af1b1a720 | ||
|
ca9657baf1 | ||
|
3e347746f9 | ||
|
b5987afaa6 | ||
|
ea46d08835 | ||
|
789114cc7d | ||
|
cf292fafcb | ||
|
08fca7de69 | ||
|
b42437c458 | ||
|
98e1787f93 | ||
|
7a0ec0ecd3 | ||
|
55b9f8bbe5 | ||
|
2968645f2e | ||
|
92dea0e404 | ||
|
bfd3077bd0 | ||
|
2d1616894d | ||
|
7c203b40c8 | ||
|
2f63a2fa17 | ||
|
e3e065a6d7 | ||
|
afd8ba8cf2 | ||
|
43d87b4791 | ||
|
6267685920 | ||
|
48882f3fec | ||
|
0ac54ea1ea | ||
|
4395ce01d7 | ||
|
2c097da12d | ||
|
5f316dc7a4 | ||
|
02a475e113 | ||
|
338bbe27a8 | ||
|
44564065f6 | ||
|
c54ec7be2d | ||
|
274e380b34 | ||
|
e872f08273 | ||
|
a6fda3b787 | ||
|
a48851888f | ||
|
f1abb66f9a | ||
|
c999c51d77 | ||
|
6bb5227ee1 | ||
|
cefe8698da | ||
|
4be6bad526 | ||
|
d3c913e384 | ||
|
a868f4eee4 | ||
|
44da89979f | ||
|
a79f07e4dc | ||
|
e17a96f5ec | ||
|
5062508940 | ||
|
9cf91328c1 | ||
|
6a30ef7017 | ||
|
71dabf5334 | ||
|
155b8cdad9 | ||
|
96dc02b249 | ||
|
eaddcab0ba | ||
|
d9b02ecd8d | ||
|
b9c12e44a4 | ||
|
58ed5bb56e | ||
|
094f44e92c | ||
|
566706dd62 | ||
|
60a76e9348 | ||
|
1d9b0f7fa5 | ||
|
f55ad323b4 | ||
|
78558e4c52 | ||
|
c9d4cc3321 | ||
|
0f8a464157 | ||
|
e4a675babb | ||
|
5b4ed01bec | ||
|
64df253053 | ||
|
d8129bd643 | ||
|
60a4738694 | ||
|
9ade14e076 | ||
|
6aa54b8dda | ||
|
0ff911cc26 | ||
|
b5b14b2f9d | ||
|
2b6f41e0d0 | ||
|
9b429cc751 | ||
|
ec56dffd19 | ||
|
a09f2093b5 | ||
|
b15d3be88a | ||
|
a26628ed50 | ||
|
0b5a798343 | ||
|
224ae50e91 | ||
|
03d7728d46 | ||
|
19ba15f2f1 | ||
|
9da9967b83 | ||
|
1510c45ecb | ||
|
9b76c9fe3e | ||
|
ea4564c827 | ||
|
0a97e4e038 | ||
|
8e19164977 | ||
|
ab60bb8d0b | ||
|
c7ca4d9b97 | ||
|
3c3d0b46db | ||
|
f08d9ba81e | ||
|
15a9ffd340 | ||
|
b9aae48517 | ||
|
e2864ab08c | ||
|
effe0198ce | ||
|
c5ac862cef | ||
|
cd77e361ab | ||
|
bc5fd44172 | ||
|
c781820bf6 | ||
|
30802affd4 | ||
|
eab021a5cb | ||
|
505b5e6468 | ||
|
5631485465 | ||
|
fafbd9a04f | ||
|
bc582e64cc | ||
|
df0044a110 | ||
|
503556196a | ||
|
fd961f9ff9 | ||
|
e3411670cb | ||
|
d134107aba | ||
|
5de1c23aba | ||
|
72b257e5d1 | ||
|
9d0bae2d03 | ||
|
ad6d5e9ec9 | ||
|
6462ceef24 | ||
|
c281b27220 | ||
|
1d5502dc3c | ||
|
4ef3c792de | ||
|
a5aac5754c | ||
|
23903a969d | ||
|
78020c4794 | ||
|
682f1900ae | ||
|
a9cb0cfb2a | ||
|
fc6a4872d6 | ||
|
284f58dec4 | ||
|
e51bc0cea5 | ||
|
da9770c99b | ||
|
266d45e13a | ||
|
703662cb5b | ||
|
833f74dce6 | ||
|
014aa90462 | ||
|
f5dcaebbf8 | ||
|
02a9e8d886 | ||
|
4fe8cf76bb | ||
|
d4ee43179c | ||
|
5afe1f1a25 | ||
|
7eb9a66837 | ||
|
aa33a1644c | ||
|
c42e1f28d6 | ||
|
fe21108f08 | ||
|
81f3b95914 | ||
|
2daad2d223 | ||
|
09becbfb04 | ||
|
1f9f2d2cf1 | ||
|
939d6d42bb | ||
|
45a9d45e3f | ||
|
cfca42e577 | ||
|
4c9b125cc8 | ||
|
e1e00c951b | ||
|
bc5153dd4a | ||
|
aed74479fd | ||
|
c14974d167 | ||
|
f07de5401d | ||
|
faf066f2b9 | ||
|
06640844e9 | ||
|
bfc703a556 | ||
|
ec01993d84 | ||
|
5a54b6a761 | ||
|
a605c435b6 | ||
|
05da1708eb | ||
|
3f826b8971 | ||
|
de8388f9a7 | ||
|
569f782a60 | ||
|
96b9b6a375 | ||
|
410502fa7e | ||
|
a8cd73cc69 | ||
|
54ce23d0a8 | ||
|
2d68b72dbd | ||
|
091cf4337c | ||
|
ff9b8a59e2 | ||
|
6e4544450e | ||
|
d4922beefe | ||
|
6f37ff9ea3 | ||
|
1dfa671405 | ||
|
eb1d5d0a6d | ||
|
a190d5cbd9 | ||
|
ab647ec0eb | ||
|
1fb9e37ec5 | ||
|
bcd057d45b | ||
|
0a63e2946d | ||
|
b63e42ecf2 | ||
|
ee2ec7fe54 | ||
|
1f63342c96 | ||
|
6e094a8edb | ||
|
2ece059c5e | ||
|
7866675a55 | ||
|
619579bee3 | ||
|
b5dbaff748 | ||
|
a0ce1efabe | ||
|
3fd45abad8 | ||
|
c1b557fbb9 | ||
|
babefd09f3 | ||
|
578e5dc063 | ||
|
2a290ff9c1 | ||
|
bfd60155b6 | ||
|
df09a1e3bf | ||
|
3cced5e29b | ||
|
bd5ecf8cbb | ||
|
3e6f7ef541 | ||
|
ac5b434641 | ||
|
00f917f52e | ||
|
a11770961e | ||
|
b0d33a5385 | ||
|
2d8f2422b3 | ||
|
0166059d1b | ||
|
50fcb45b88 | ||
|
29d13aef09 | ||
|
31036b8dee | ||
|
6f231f3367 | ||
|
97f08f21b7 | ||
|
9a26016ed4 | ||
|
f541613c6b | ||
|
6931b2944b | ||
|
3b3634bf5f | ||
|
0af9d0c972 | ||
|
daeecbe99e | ||
|
5d9194d03d | ||
|
3c7ce3de8b | ||
|
e56d4e63f4 | ||
|
57e6e90002 | ||
|
c7d2eb87ac | ||
|
7226fe5303 | ||
|
10af336cd1 | ||
|
f218a38294 | ||
|
2f326a8199 | ||
|
eb07a3c2f1 | ||
|
04526012f9 | ||
|
b5bb214920 | ||
|
1e587b0848 | ||
|
97727e0d1e | ||
|
723eab59d6 | ||
|
1421ccc81e | ||
|
e1d43b8eb2 | ||
|
67bd47f11f | ||
|
e44676e491 | ||
|
7b4d513e22 | ||
|
5b51cd06c0 | ||
|
052a47734f | ||
|
54767bf97d | ||
|
e5bace03f7 | ||
|
691fcfb657 | ||
|
49f7da4402 | ||
|
6c0ca42fff | ||
|
30c6cbefbd | ||
|
5a049642ea | ||
|
b168459007 | ||
|
d5c7b5f537 | ||
|
506ed000a0 | ||
|
b92e9216f4 | ||
|
6ff5079df8 | ||
|
d686fc04f4 | ||
|
9aef78be4f | ||
|
f6b1c080ad | ||
|
d4fa85688d | ||
|
20b4a33c00 | ||
|
f61055ebc0 | ||
|
777a02cbe9 | ||
|
61b1940397 | ||
|
eef6d267c3 | ||
|
9c48c296c8 | ||
|
214c61669a | ||
|
fb0396923f | ||
|
6503a300cc | ||
|
be80595834 | ||
|
54bda0ea95 | ||
|
6d23e18c11 | ||
|
5f8e7fcb73 | ||
|
214b52a96a | ||
|
d426d0faeb | ||
|
ebb51addd7 | ||
|
c64fab6ecb | ||
|
696bd6f316 | ||
|
7a3a661e2a | ||
|
f0949d296d | ||
|
d2ebcb24b9 | ||
|
644087b592 | ||
|
be2f5273d1 | ||
|
10053a8a1b | ||
|
9cefaec49c | ||
|
55b79c1518 | ||
|
c2ab86d4ba | ||
|
0e5fcde1e9 | ||
|
002cce886c | ||
|
151ea2ece4 | ||
|
568882a2ea | ||
|
f38d6d73f4 | ||
|
569893861d | ||
|
e81ac6f61e | ||
|
177098c957 | ||
|
1e78512c95 | ||
|
1b147151f0 | ||
|
ad8a26616f | ||
|
8f28823217 | ||
|
b509df78df | ||
|
5876cc7e17 | ||
|
de73a36399 | ||
|
c9aed600b6 | ||
|
318e035344 | ||
|
cd097232cb | ||
|
b7b783b6be | ||
|
968f503d80 | ||
|
7fc92e236b | ||
|
3062a29b78 | ||
|
3bea3ec3d8 | ||
|
0f66d454c1 | ||
|
6738d7472e | ||
|
500f38a496 | ||
|
3a5a70b56d | ||
|
15e132c824 | ||
|
2b3cf58b8a | ||
|
2998796c2c | ||
|
3978d0754d | ||
|
655b9a17e1 | ||
|
84a694d4c2 | ||
|
ebac8b92ba | ||
|
6a4326af39 | ||
|
6a51e8a1c9 | ||
|
006a90f681 | ||
|
17983e7fad | ||
|
a15b1805fb | ||
|
11e0bd79b0 | ||
|
bfbf4cb453 | ||
|
229509a067 | ||
|
ff5c43e6cb | ||
|
7cd3aae753 | ||
|
fe05b1de12 | ||
|
1903c8b557 | ||
|
456a96042f | ||
|
888df0ac53 | ||
|
18afe91a82 | ||
|
b8d0ed39d9 | ||
|
0c3f399de3 | ||
|
45ccabb11d | ||
|
d71a213c4b | ||
|
8e9123bdce | ||
|
2e3e65f3e7 | ||
|
9db53a4e3f | ||
|
c2e098e535 | ||
|
a6de2c2b44 | ||
|
9dad9ea38b | ||
|
1d938c93b6 | ||
|
35bc1bcb44 | ||
|
1438ebc12a | ||
|
61b29f6fab | ||
|
fb9b6886fa | ||
|
0e3eaf724b | ||
|
2a05ccbee1 | ||
|
59bee01c0a | ||
|
4e4c8d71be | ||
|
c7ae31dfce | ||
|
2ea80b0aab | ||
|
5bbf5ee3af | ||
|
748e9c5d86 | ||
|
8764beba39 | ||
|
1fc551fae0 | ||
|
8baf722343 | ||
|
cf4ce01ddd | ||
|
57d919e424 | ||
|
b0dc893a05 | ||
|
f5bb484226 | ||
|
29f3ffd474 | ||
|
b160e38f8f | ||
|
bc2714ab2a | ||
|
38d94e509f | ||
|
ec23a532f6 | ||
|
8a800062dd | ||
|
cf14144d5b | ||
|
28e7a806b4 | ||
|
a0e56aa4cf | ||
|
b3e3b73159 | ||
|
046452fc56 | ||
|
e77b8785ff | ||
|
061fc5a24d | ||
|
cb8c2cb450 | ||
|
98d661eda1 | ||
|
8ddbc9c427 | ||
|
5902cd5c28 | ||
|
ce2f6ddadd | ||
|
cdc5589bcf | ||
|
833534bdaa | ||
|
8d30fea63b | ||
|
7d06c5b37a | ||
|
dd95468d74 | ||
|
478bd31dc7 | ||
|
44b1d34cc7 | ||
|
d0b0b8070c | ||
|
996857b10d | ||
|
fd81d7b040 | ||
|
267a9448ea | ||
|
01e45d656e | ||
|
9199b1e520 | ||
|
3be22dac99 | ||
|
ff895aa8a4 | ||
|
c9c4280e6e | ||
|
dac9c1e52c | ||
|
a7e6ab33a1 | ||
|
6c49b87a06 | ||
|
29e5bbdcc0 | ||
|
4fcb357e2f | ||
|
ddb5259464 | ||
|
52dfc0cf93 | ||
|
ad58d102df | ||
|
9847f5ba6c | ||
|
5f06be6226 | ||
|
a68afbf79a | ||
|
bf425884fb | ||
|
4430fd89a9 | ||
|
7432369162 | ||
|
43dc9ed88a | ||
|
d413e4a278 | ||
|
5c460c9f3b | ||
|
cf9237f7d6 | ||
|
a0b1d6394a | ||
|
c6af1e31fe | ||
|
a5ce6a806f | ||
|
a59475af1c | ||
|
1cc1ac2e68 | ||
|
924b80574a | ||
|
f3a759c80d | ||
|
79ee8543f5 | ||
|
9b30dfb474 | ||
|
0100e67ebf | ||
|
bd419912b5 | ||
|
cb85d358d1 | ||
|
5b114c2412 | ||
|
c25b027de4 | ||
|
8595dd7d99 | ||
|
61a90e2cfb | ||
|
593508df22 | ||
|
1f3505f371 | ||
|
679e80a7c3 | ||
|
ebdefb5acd | ||
|
e36e1cf282 | ||
|
fef5bde0c7 | ||
|
dc00879419 | ||
|
bae488fd97 | ||
|
0a8c1df968 | ||
|
4c49558120 | ||
|
750f91e339 | ||
|
d24b5d21b8 | ||
|
9dbfde1a4c | ||
|
7f229b0fe8 | ||
|
920c2024be | ||
|
2a0f391113 | ||
|
c3b770ef17 | ||
|
a14fbe865f | ||
|
ae0bb838bf | ||
|
587eb48f61 | ||
|
0e0de638d4 | ||
|
fd92d86954 | ||
|
c41f5355fd | ||
|
ea9de4feaf | ||
|
114737db41 | ||
|
bf278af950 | ||
|
e1b231882e | ||
|
fa04cde25d | ||
|
705dd390e9 | ||
|
078aa79270 | ||
|
a1ad0a3e07 | ||
|
858f699750 | ||
|
7cf3d9cd94 | ||
|
c28e1fa0be | ||
|
e9bc8926ab | ||
|
4d7204debc | ||
|
30ded5e803 | ||
|
65a343ce5c | ||
|
8841c3e7f9 | ||
|
1e4579a076 | ||
|
be2721cd67 | ||
|
58281520d3 | ||
|
58dacfa263 | ||
|
ae903082d8 | ||
|
9e54fb88f8 | ||
|
5859b4cd66 | ||
|
8a987afefd | ||
|
1a859669eb | ||
|
56f2480caa | ||
|
0096d063dd | ||
|
7c3bb41728 | ||
|
99ae0c3d31 | ||
|
6582536591 | ||
|
628d7249b1 | ||
|
7eb2fd23c3 | ||
|
46961711e4 | ||
|
819d475181 | ||
|
07030378c8 | ||
|
277be165b6 | ||
|
1b4de3b64e | ||
|
a1d8f8aa4e | ||
|
8c3dfe94c7 | ||
|
0a9107b602 | ||
|
680cab52f3 | ||
|
6abb07e61b | ||
|
b59c2be12d | ||
|
d49d03846f | ||
|
b51670cb25 | ||
|
487c6c2c7c | ||
|
87b8a8e0a0 | ||
|
47e0c23e64 | ||
|
dd454689e0 | ||
|
94872b3ebb | ||
|
9ae863d7c4 | ||
|
457accf329 | ||
|
b9c1ecf65f | ||
|
d08b486db0 | ||
|
d37d83c5b6 | ||
|
d45bba924d | ||
|
69f7fbe703 | ||
|
4753ba5361 | ||
|
ada68ffc71 | ||
|
446b1b8d99 | ||
|
27ff05607c | ||
|
f2558cd379 | ||
|
4f76a4ead2 | ||
|
7835aec034 | ||
|
f4f035682c | ||
|
d1f9b5631a | ||
|
d92ae6136a | ||
|
be633d3872 | ||
|
453930ff75 | ||
|
b7341d0ab8 | ||
|
f814822ca3 | ||
|
c3f2c9e84c | ||
|
074a760b6a | ||
|
931060db7c | ||
|
417f988ca0 | ||
|
d4c9db7fcf | ||
|
04956c19d5 | ||
|
b350beafa8 | ||
|
37cca00e13 | ||
|
e5c50640e3 | ||
|
351ac78e4b | ||
|
e011d86742 | ||
|
f019902f41 | ||
|
35c86ad6bf | ||
|
6ffc2b2f10 | ||
|
ec155bf7ba | ||
|
8cad7ee509 | ||
|
1f7edfdb3b | ||
|
55a354406a | ||
|
a9fd2453a4 | ||
|
ea0eae467d | ||
|
5a5ae9fd16 | ||
|
838ccdd7b4 | ||
|
cfb1999caf | ||
|
36cb84eaaa | ||
|
73272b28dd | ||
|
aa5ef742f6 | ||
|
47d2e235a3 | ||
|
576912dd55 | ||
|
5b366e592c | ||
|
170a9d78e7 | ||
|
63b10175bf | ||
|
a5fb124e69 | ||
|
72d45fb0de | ||
|
f754f63f8f | ||
|
8cf290c034 | ||
|
b408a02ef9 | ||
|
67146028bf | ||
|
acdb14a10a | ||
|
dcd4384d46 | ||
|
8348370142 | ||
|
6b3cdad877 | ||
|
6b40caf63a | ||
|
99f596ea19 | ||
|
637844269f | ||
|
ec5bf8e985 | ||
|
4bbc5ab0af | ||
|
6cfde29f00 | ||
|
c115a887d8 | ||
|
4cdb81e822 | ||
|
f4f7edcc28 | ||
|
ae97d7bcc7 | ||
|
5f48a3ab5b | ||
|
9db3076a48 | ||
|
6eb6a5892d | ||
|
0ebcd15a3d | ||
|
04acf120d6 | ||
|
278799795c | ||
|
c47cbb354d | ||
|
8ff2125312 | ||
|
a69763c6de | ||
|
5e32b599d6 | ||
|
fd6327ab62 | ||
|
732977db27 | ||
|
45528bfc6d | ||
|
c690049246 | ||
|
932f2bbceb | ||
|
a7ef177164 | ||
|
300965ab10 | ||
|
d2e7c9a5df | ||
|
ee9c692115 | ||
|
de1f0c8a9b | ||
|
52a64168d7 | ||
|
b571ff8412 | ||
|
762319055c | ||
|
cd9ccb66ed | ||
|
ac64f9e30d | ||
|
16123dea45 | ||
|
814260f0b6 | ||
|
439509b408 | ||
|
07f57a1e9b | ||
|
0c9ddaccf7 | ||
|
b23bb04dac | ||
|
a7601c36bd | ||
|
1a883ba73e | ||
|
2e50b364c4 | ||
|
b4b41a47b2 | ||
|
5b90a3e21d | ||
|
ca83f7e8ef | ||
|
337a9d6dd0 | ||
|
ddf139a659 | ||
|
6b8fe50f50 | ||
|
233ab778b6 | ||
|
4872af1053 | ||
|
0ca1ebf724 | ||
|
632479b1ba | ||
|
9b81891467 | ||
|
7b2e1d90e6 | ||
|
6fa6b4d4dc | ||
|
6efe6f13a9 | ||
|
c254749493 | ||
|
fd9f8c90a5 | ||
|
0a0761c7e0 | ||
|
e9d91175c4 | ||
|
0c0166b565 | ||
|
7bb7ff9f0f | ||
|
c2cd076662 | ||
|
a9f73e7384 | ||
|
14c7d18bdb | ||
|
5787d49e9b | ||
|
25150268cd | ||
|
f7a1412ec7 | ||
|
a424d48447 | ||
|
e76e3afa87 | ||
|
482fe3eb80 | ||
|
4de2afc0c5 | ||
|
742003b6f3 | ||
|
10b8366219 |
240
.github/workflows/cmake.yml
vendored
240
.github/workflows/cmake.yml
vendored
|
@ -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
7
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
32
CREDITS.md
Normal 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
|
23
README.md
23
README.md
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
theme: jekyll-theme-minimal
|
21
cmake/PlatformGlob.cmake
Normal file
21
cmake/PlatformGlob.cmake
Normal 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()
|
|
@ -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()
|
||||
|
|
|
@ -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
74
core/coresignal.h
Normal 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
|
|
@ -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
311
core/hostevents_sdl.cpp
Normal 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)");
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
|
|
535
cpu/ppc/ppcemu.h
535
cpu/ppc/ppcemu.h
|
@ -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
|
||||
|
|
|
@ -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 18–21 - set to bits 1–4 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 15–16 - set to bits 29–30 of the instruction.
|
||||
| ((ppc_cur_instruction << 8) & 0x00004000) // bit 17 - set to bit 25 of the instruction.
|
||||
| ((ppc_cur_instruction << 3) & 0x00003c00); // bits 18–21 - set to bits 21–24 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 22–26 - Set to bits 6–10 (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 27–31
|
||||
}
|
||||
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 27–31
|
||||
}
|
||||
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 27–31
|
||||
}
|
||||
else {
|
||||
// bits 27–31 - Set to bits 11–15 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
178
cpu/ppc/ppcmacros.h
Normal 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
|
1239
cpu/ppc/ppcmmu.cpp
1239
cpu/ppc/ppcmmu.cpp
File diff suppressed because it is too large
Load Diff
|
@ -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
258
cpu/ppc/test/genppctests.py
Normal file → Executable file
258
cpu/ppc/test/genppctests.py
Normal file → Executable 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)))
|
||||
|
||||
pos = 10
|
||||
checkstring = ''
|
||||
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)))
|
||||
|
||||
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
|
@ -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;
|
||||
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
109
devices/common/adb/adbbus.cpp
Normal file
109
devices/common/adb/adbbus.cpp
Normal 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);
|
80
devices/common/adb/adbbus.h
Normal file
80
devices/common/adb/adbbus.h
Normal 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
|
108
devices/common/adb/adbdevice.cpp
Normal file
108
devices/common/adb/adbdevice.cpp
Normal 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;
|
||||
}
|
78
devices/common/adb/adbdevice.h
Normal file
78
devices/common/adb/adbdevice.h
Normal 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
|
117
devices/common/adb/adbkeyboard.cpp
Normal file
117
devices/common/adb/adbkeyboard.cpp
Normal 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);
|
180
devices/common/adb/adbkeyboard.h
Normal file
180
devices/common/adb/adbkeyboard.h
Normal 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
|
137
devices/common/adb/adbmouse.cpp
Normal file
137
devices/common/adb/adbmouse.cpp
Normal 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);
|
58
devices/common/adb/adbmouse.h
Normal file
58
devices/common/adb/adbmouse.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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] = {};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
72
devices/common/i2c/saa7187.cpp
Normal file
72
devices/common/i2c/saa7187.cpp
Normal 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;
|
||||
}
|
141
devices/common/i2c/saa7187.h
Normal file
141
devices/common/i2c/saa7187.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ®_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);
|
||||
|
|
|
@ -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 ®_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
|
||||
|
|
|
@ -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>
|
||||
|
|
304
devices/common/pci/pcibase.cpp
Normal file
304
devices/common/pci/pcibase.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
250
devices/common/pci/pcibase.h
Normal file
250
devices/common/pci/pcibase.h
Normal 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 */
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
87
devices/common/pci/pcibridgebase.cpp
Normal file
87
devices/common/pci/pcibridgebase.cpp
Normal 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);
|
||||
}
|
||||
}
|
80
devices/common/pci/pcibridgebase.h
Normal file
80
devices/common/pci/pcibridgebase.h
Normal 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 */
|
223
devices/common/pci/pcicardbusbridge.cpp
Normal file
223
devices/common/pci/pcicardbusbridge.cpp
Normal 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);
|
||||
}
|
112
devices/common/pci/pcicardbusbridge.h
Normal file
112
devices/common/pci/pcicardbusbridge.h
Normal 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 */
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
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;
|
||||
int last_lba = (int)this->size_blocks - 1;
|
||||
|
||||
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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
455
devices/ethernet/bigmac.cpp
Normal 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
Loading…
Reference in New Issue
Block a user