Compare commits
614 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0c92b80b1 | ||
|
|
81a44d38ee | ||
|
|
5a9df3c675 | ||
|
|
c996787d84 | ||
|
|
4030f600dc | ||
|
|
78443a7f29 | ||
|
|
c1811ed3d1 | ||
|
|
be51120d23 | ||
|
|
827bb08c91 | ||
|
|
c164f02c48 | ||
|
|
9da1552417 | ||
|
|
6ff20dc6a7 | ||
|
|
3dab0e284b | ||
|
|
15e68cdbaa | ||
|
|
e3ca561ea0 | ||
|
|
ddb767f1b6 | ||
|
|
5410b4659d | ||
|
|
a9cacd03f6 | ||
|
|
6e0eba9917 | ||
|
|
65c664560c | ||
|
|
76b475faf7 | ||
|
|
6269a01b4e | ||
|
|
0d46f0df12 | ||
|
|
638956aa81 | ||
|
|
92c7135065 | ||
|
|
a6d4903aaf | ||
|
|
6e4e0b2b41 | ||
|
|
055f1546d7 | ||
|
|
79167fc989 | ||
|
|
9685dd5840 | ||
|
|
18c8ae7750 | ||
|
|
d480b63e0d | ||
|
|
c1f55c32c8 | ||
|
|
0f929762b3 | ||
|
|
4c59105adf | ||
|
|
fca3d1cc65 | ||
|
|
cc73bad293 | ||
|
|
3d41fdfbba | ||
|
|
ca633a5a3c | ||
|
|
80c5e8ae99 | ||
|
|
e9d147349b | ||
|
|
6c0c81dfdc | ||
|
|
a093feca62 | ||
|
|
066d6184d4 | ||
|
|
b611d852db | ||
|
|
85a60e2044 | ||
|
|
f33e406ff2 | ||
|
|
c0e2d52758 | ||
|
|
b11072d54a | ||
|
|
c96da97630 | ||
|
|
50ef2beb58 | ||
|
|
c18425ef98 | ||
|
|
da2bdbc0d7 | ||
|
|
7fa9177830 | ||
|
|
1dd754590f | ||
|
|
8af1ae46aa | ||
|
|
c7c379bd19 | ||
|
|
6a28a66832 | ||
|
|
1bbbd26563 | ||
|
|
3f9f047375 | ||
|
|
ff6785f3e8 | ||
|
|
9f2f819bb6 | ||
|
|
5f57ab1b2a | ||
|
|
84cadf9918 | ||
|
|
10422f3c18 | ||
|
|
dfac394e60 | ||
|
|
73de9bab1a | ||
|
|
0399d98cd9 | ||
|
|
8447d20a11 | ||
|
|
20b58bab9c | ||
|
|
41b3725d28 | ||
|
|
2981408722 | ||
|
|
1669911b1d | ||
|
|
36dedae842 | ||
|
|
1da0ee57fd | ||
|
|
ad39bab271 | ||
|
|
c9e821e93e | ||
|
|
11fb17054e | ||
|
|
838724c588 | ||
|
|
0b831dd2ba | ||
|
|
81f24f5685 | ||
|
|
ea1880f47c | ||
|
|
6d7941042b | ||
|
|
52a78228dd | ||
|
|
a27befe456 | ||
|
|
067ac434ba | ||
|
|
5f8aa02584 | ||
|
|
08d454e30d | ||
|
|
b7162c32a4 | ||
|
|
dc70a87af1 | ||
|
|
63f26d5c40 | ||
|
|
8f8fe62a19 | ||
|
|
62bd1299ea | ||
|
|
4f81bc4e1b | ||
|
|
9d71ce88ce | ||
|
|
c06d6b27f3 | ||
|
|
7e191dccc1 | ||
|
|
c3e95086b6 | ||
|
|
a3d82ef5d9 | ||
|
|
be1f5dedfb | ||
|
|
7a0bb406d5 | ||
|
|
0d8ae773f1 | ||
|
|
1ab133d7fa | ||
|
|
38989bef43 | ||
|
|
eab7457c00 | ||
|
|
0e13d9cb7b | ||
|
|
c11cfaa705 | ||
|
|
4ac4b308e4 | ||
|
|
ea080501fb | ||
|
|
bf4e2b2f0b | ||
|
|
7c7f4a9be2 | ||
|
|
61779fa072 | ||
|
|
d2277b825e | ||
|
|
fe906fff36 | ||
|
|
f9af74201c | ||
|
|
afdd657d30 | ||
|
|
5673ce39c7 | ||
|
|
3c43ea5c68 | ||
|
|
d383043e07 | ||
|
|
fb5bd0920d | ||
|
|
46cbb6b090 | ||
|
|
55c49d5bf4 | ||
|
|
61f9d9c4ab | ||
|
|
acc8fe5a2a | ||
|
|
f969ddb54e | ||
|
|
9f8fbce35b | ||
|
|
94d27b1717 | ||
|
|
ac88d3e89f | ||
|
|
a353322b58 | ||
|
|
50153a1cb2 | ||
|
|
17f3590d59 | ||
|
|
7786f41cc0 | ||
|
|
019d7208c8 | ||
|
|
2015a1b180 | ||
|
|
db0497b808 | ||
|
|
987a170665 | ||
|
|
33dbf24b56 | ||
|
|
2dc8b5c224 | ||
|
|
5f3d6c85db | ||
|
|
2f9c0e7c7e | ||
|
|
09b8a16414 | ||
|
|
004b1b3830 | ||
|
|
281fd881a0 | ||
|
|
2a7653142d | ||
|
|
b366b885a1 | ||
|
|
3cb753eeb1 | ||
|
|
d81aaa3ed3 | ||
|
|
e2176dc7ce | ||
|
|
174c22e5f6 | ||
|
|
5440b9c634 | ||
|
|
abec5f82e2 | ||
|
|
bbc4f369ed | ||
|
|
79e9c2e237 | ||
|
|
83517cb53a | ||
|
|
9949e4d508 | ||
|
|
c116b220e9 | ||
|
|
c011b6f67e | ||
|
|
c712dafaee | ||
|
|
a931cf9e8b | ||
|
|
a941a94148 | ||
|
|
8d9534d830 | ||
|
|
47dc5e0dab | ||
|
|
f3885845fc | ||
|
|
c0d3e2da4e | ||
|
|
517112f549 | ||
|
|
6324d86c71 | ||
|
|
5aff2d38a9 | ||
|
|
ee318d4015 | ||
|
|
86146ef819 | ||
|
|
a2efb1dd48 | ||
|
|
ee1eb8cfdf | ||
|
|
0639e03055 | ||
|
|
930487c7fb | ||
|
|
92209f905f | ||
|
|
f22e090b86 | ||
|
|
218a08df93 | ||
|
|
0cb7ce71e0 | ||
|
|
9f21f20d7c | ||
|
|
128aeba0f3 | ||
|
|
03f877919d | ||
|
|
37f50c8773 | ||
|
|
4732e1f064 | ||
|
|
00c830405b | ||
|
|
4b114e1b8a | ||
|
|
0a49c46353 | ||
|
|
47629c89a8 | ||
|
|
89e81a9be2 | ||
|
|
0ff2929644 | ||
|
|
cfc9effa6c | ||
|
|
4669f15f8b | ||
|
|
4112aa68a6 | ||
|
|
6e386a334b | ||
|
|
dbfc39d214 | ||
|
|
61fbf5c8e6 | ||
|
|
be09dfeed9 | ||
|
|
2f842a86fe | ||
|
|
ce026332a5 | ||
|
|
fa220dd709 | ||
|
|
a776464a55 | ||
|
|
9a85277d83 | ||
|
|
05dc93399b | ||
|
|
39fb3e362c | ||
|
|
566f97b580 | ||
|
|
bf0543af23 | ||
|
|
c5684411a0 | ||
|
|
2abe5e39fc | ||
|
|
adf47cd59a | ||
|
|
c531a92eda | ||
|
|
14afc704d4 | ||
|
|
8d70d1ea45 | ||
|
|
5fb99e6a16 | ||
|
|
5c3d5d0849 | ||
|
|
79de0f8fe8 | ||
|
|
6e8e1a4110 | ||
|
|
9232fbdf34 | ||
|
|
38eb33f150 | ||
|
|
b54a72afc0 | ||
|
|
62cd19e4ae | ||
|
|
7e3d746b06 | ||
|
|
e7dfcdde74 | ||
|
|
4b89348c00 | ||
|
|
8c99dd055c | ||
|
|
799e632ccb | ||
|
|
c23c30c76f | ||
|
|
00749f5ab3 | ||
|
|
6ea1576513 | ||
|
|
81a16c073a | ||
|
|
23b1e6eded | ||
|
|
438a9b70cc | ||
|
|
e8bfff7b4b | ||
|
|
f564822e78 | ||
|
|
6cf6fa2842 | ||
|
|
d27279092f | ||
|
|
37fd4e6d9b | ||
|
|
cdd92dc692 | ||
|
|
38d25a4cb2 | ||
|
|
2933521a08 | ||
|
|
f6679ce422 | ||
|
|
5d55403f94 | ||
|
|
0a0233f39f | ||
|
|
9936d1b9e2 | ||
|
|
4fad069870 | ||
|
|
0c688b421c | ||
|
|
cb47abecc6 | ||
|
|
fbef849c04 | ||
|
|
0641950f9a | ||
|
|
b7c64f0ded | ||
|
|
90cddf1996 | ||
|
|
7c181fd4f4 | ||
|
|
d16f83fda3 | ||
|
|
5c82400ef8 | ||
|
|
bb081dd1d2 | ||
|
|
019778707d | ||
|
|
afdd2f4cad | ||
|
|
df4336a85e | ||
|
|
51d8a2c322 | ||
|
|
049ce242a4 | ||
|
|
b481d8a00d | ||
|
|
06c72b4fcf | ||
|
|
876b805e50 | ||
|
|
2dcb98226b | ||
|
|
9fedfbe141 | ||
|
|
d73c22bf4d | ||
|
|
ba117854f9 | ||
|
|
527c098ff6 | ||
|
|
d57333406d | ||
|
|
1efcba346a | ||
|
|
bb9d39b8fe | ||
|
|
27c0f9e02d | ||
|
|
41faeeeb03 | ||
|
|
63270e588b | ||
|
|
e54ea773fc | ||
|
|
0d64ddc6dd | ||
|
|
9cd87a6352 | ||
|
|
99f9d47d16 | ||
|
|
bbbe34429e | ||
|
|
11568c2ea3 | ||
|
|
888eb345c0 | ||
|
|
4c727d0ba8 | ||
|
|
bdd68fc210 | ||
|
|
f1bded1270 | ||
|
|
49309b5848 | ||
|
|
c02d7c8ce7 | ||
|
|
3957b0c34e | ||
|
|
ca5a4a704b | ||
|
|
15086a22be | ||
|
|
94fecef137 | ||
|
|
d1f9c750a6 | ||
|
|
99f12b05fa | ||
|
|
8df011a57f | ||
|
|
9a273bb23b | ||
|
|
6fcc7e9c36 | ||
|
|
c8e1383fa9 | ||
|
|
68937a662d | ||
|
|
734106dcb9 | ||
|
|
6306655665 | ||
|
|
0658973a4e | ||
|
|
0d843eaba6 | ||
|
|
5a763e8a5a | ||
|
|
220d4672df | ||
|
|
7757cc1a7f | ||
|
|
2abf39ea4a | ||
|
|
d809f65827 | ||
|
|
7f155ba713 | ||
|
|
7029daa32e | ||
|
|
15c388e0d6 | ||
|
|
9e30f5574f | ||
|
|
7ddc872b52 | ||
|
|
6138075df0 | ||
|
|
0d681f7a7a | ||
|
|
c23ce3365d | ||
|
|
ee53688ca7 | ||
|
|
b16e5c6a81 | ||
|
|
87f21657f8 | ||
|
|
e6df4b37db | ||
|
|
aee356bd10 | ||
|
|
cd7665218d | ||
|
|
e2cdf54177 | ||
|
|
e3a92b09ba | ||
|
|
e2db7a83f6 | ||
|
|
126270d963 | ||
|
|
e6a87428ae | ||
|
|
55e6296e71 | ||
|
|
1ce7942dc2 | ||
|
|
6b6287dda0 | ||
|
|
c74d24f841 | ||
|
|
4cb92b776c | ||
|
|
a55f112cb1 | ||
|
|
5cdc277dd2 | ||
|
|
74efdd6928 | ||
|
|
3825b703fa | ||
|
|
1efb81a61d | ||
|
|
3c26b7179d | ||
|
|
8c648b59cd | ||
|
|
f217d6c66f | ||
|
|
58d9078742 | ||
|
|
58857b9f46 | ||
|
|
c6eaf0b2cf | ||
|
|
693f78e6c2 | ||
|
|
898f0fa029 | ||
|
|
ff54287a73 | ||
|
|
882111c4f2 | ||
|
|
6486544e09 | ||
|
|
2dbfcd32d7 | ||
|
|
8440cef223 | ||
|
|
fd500d3da6 | ||
|
|
525492428d | ||
|
|
72b5c448cf | ||
|
|
03388c3071 | ||
|
|
353e1dd7e4 | ||
|
|
a215f63235 | ||
|
|
dc26601860 | ||
|
|
2a35a36251 | ||
|
|
c74f2555b6 | ||
|
|
fab3dd98fe | ||
|
|
c50393e066 | ||
|
|
a056d5ad8c | ||
|
|
98b143c2d6 | ||
|
|
370ab5df9b | ||
|
|
21959ddfef | ||
|
|
abe79b2724 | ||
|
|
536cfb13e6 | ||
|
|
e35cfc1b03 | ||
|
|
fd86cdb2e2 | ||
|
|
0984e9d601 | ||
|
|
1b5c02fc37 | ||
|
|
e07218906d | ||
|
|
e7b0e8a3cc | ||
|
|
811dae12f9 | ||
|
|
46ec9a9bc9 | ||
|
|
edc52250b8 | ||
|
|
fbd7afefaa | ||
|
|
91af2f94e8 | ||
|
|
e6671190a5 | ||
|
|
4822765fef | ||
|
|
8aa5d25f82 | ||
|
|
d6e3cd9a17 | ||
|
|
6ea8b3ef60 | ||
|
|
1c36f2a798 | ||
|
|
6a890023e9 | ||
|
|
5c0421ebd8 | ||
|
|
9bf2a428f9 | ||
|
|
cba69fdcd4 | ||
|
|
a434fdcb10 | ||
|
|
9776ff9179 | ||
|
|
5590245930 | ||
|
|
5e9c547952 | ||
|
|
266703b50e | ||
|
|
9eccb5de9d | ||
|
|
8c665d6752 | ||
|
|
732a77d0e8 | ||
|
|
9f3641755e | ||
|
|
1147db9dd1 | ||
|
|
b1a8e5914b | ||
|
|
902182f80c | ||
|
|
7c9644646f | ||
|
|
fadab1d5f3 | ||
|
|
acc10c7ee2 | ||
|
|
8262aeeac8 | ||
|
|
ff2f0d980a | ||
|
|
0c8b7c00e8 | ||
|
|
f362cf46ee | ||
|
|
0197e28cc9 | ||
|
|
81a0082f6b | ||
|
|
225ff1130f | ||
|
|
b3962e7d1e | ||
|
|
3abba08080 | ||
|
|
e8bbafb746 | ||
|
|
40d2dcabd7 | ||
|
|
f41eb95e13 | ||
|
|
08b8fcbe6d | ||
|
|
316327f487 | ||
|
|
c7ce472eeb | ||
|
|
869075867b | ||
|
|
da32c648bf | ||
|
|
a71346cd7c | ||
|
|
6c464a2a4a | ||
|
|
49d92aa661 | ||
|
|
334e859ab1 | ||
|
|
17b16cf6f6 | ||
|
|
faa431b274 | ||
|
|
f87ea8fa8b | ||
|
|
0c01c34eff | ||
|
|
e73927cfc2 | ||
|
|
c691fa4074 | ||
|
|
f2dcb39049 | ||
|
|
a7b5ab4d9a | ||
|
|
3d9126ba87 | ||
|
|
7fd54fed92 | ||
|
|
5d9ee12b1a | ||
|
|
bad00085ca | ||
|
|
e56e2a1528 | ||
|
|
99ac33de20 | ||
|
|
d43c49264f | ||
|
|
6b365f7703 | ||
|
|
41dde2394b | ||
|
|
5a579f66a0 | ||
|
|
16198f979e | ||
|
|
843dd62c81 | ||
|
|
e6242ab5e6 | ||
|
|
acede1f1d3 | ||
|
|
8475496630 | ||
|
|
3c40496409 | ||
|
|
abc23416e8 | ||
|
|
4f120a9ec0 | ||
|
|
18f8012233 | ||
|
|
3e0ec3dbd7 | ||
|
|
3cfe77ae75 | ||
|
|
3062eb52f4 | ||
|
|
376f6397c6 | ||
|
|
6a3d59fdc1 | ||
|
|
bc43946140 | ||
|
|
7391741a20 | ||
|
|
f1f7f2cba9 | ||
|
|
4ac9b47dca | ||
|
|
c33755e2b9 | ||
|
|
87a92ef062 | ||
|
|
c9d7abe9c9 | ||
|
|
37fa9a15cd | ||
|
|
3dd3cdeafd | ||
|
|
623d772476 | ||
|
|
4f281b3829 | ||
|
|
08da0b7acc | ||
|
|
f81b915fd8 | ||
|
|
dd6c67c627 | ||
|
|
42d43ea741 | ||
|
|
d0825c9519 | ||
|
|
1913cf4783 | ||
|
|
40dee76c57 | ||
|
|
23d68a07dc | ||
|
|
60746e4e52 | ||
|
|
20d86d8a36 | ||
|
|
e9ad8e9185 | ||
|
|
1740aa5444 | ||
|
|
5ab597041f | ||
|
|
1dd4861d38 | ||
|
|
a6da2b93c1 | ||
|
|
378c881427 | ||
|
|
b1109931b9 | ||
|
|
c16cfbbc6c | ||
|
|
a994446b6e | ||
|
|
e2f2a49d2d | ||
|
|
3f78a61f09 | ||
|
|
5aafc83cc9 | ||
|
|
6d00780045 | ||
|
|
59dae03dbe | ||
|
|
22420612db | ||
|
|
b7551e457b | ||
|
|
2ba4e2263c | ||
|
|
0369ee7248 | ||
|
|
ea3151f475 | ||
|
|
6c9ca8cbca | ||
|
|
c30cd898fc | ||
|
|
f1a4a004fb | ||
|
|
0a023cfb4f | ||
|
|
9022d926eb | ||
|
|
fbb3cd110c | ||
|
|
bc0f1896fc | ||
|
|
4415e00181 | ||
|
|
cc2e14ec2a | ||
|
|
10c6d89119 | ||
|
|
239ac8abe2 | ||
|
|
9e11a76e92 | ||
|
|
e8c2bb24b2 | ||
|
|
b7953d2ebf | ||
|
|
d08cfb55fe | ||
|
|
88eb612718 | ||
|
|
bd9c2aa51f | ||
|
|
bfe49edb2a | ||
|
|
82413a6c89 | ||
|
|
62c69f4a1e | ||
|
|
2a3f3bf977 | ||
|
|
32ece18bb6 | ||
|
|
13f9cf2bd0 | ||
|
|
72b497e876 | ||
|
|
285d8d8b7d | ||
|
|
022fc59dcd | ||
|
|
47054327c2 | ||
|
|
28b92db7fd | ||
|
|
dabfd90dfe | ||
|
|
9cd7485cd7 | ||
|
|
8fc4003dab | ||
|
|
b5b613ea29 | ||
|
|
299f943202 | ||
|
|
d1b23b2b51 | ||
|
|
c6a740d7c2 | ||
|
|
8d32bf9a96 | ||
|
|
d652e41365 | ||
|
|
e74dbfc572 | ||
|
|
5483c08b44 | ||
|
|
f85d880ac6 | ||
|
|
7495142688 | ||
|
|
a0179e5ca5 | ||
|
|
07dc0bbf3e | ||
|
|
f208953585 | ||
|
|
0214351f4f | ||
|
|
bf25299272 | ||
|
|
d4f87e9af4 | ||
|
|
3967f9c6ef | ||
|
|
55d0b0609d | ||
|
|
9531a29283 | ||
|
|
46572d027d | ||
|
|
0d6eafe11a | ||
|
|
e4bd0bddea | ||
|
|
e862c50a70 | ||
|
|
f823c1d599 | ||
|
|
010227e149 | ||
|
|
27650499bc | ||
|
|
df669bc540 | ||
|
|
f3137d3bc1 | ||
|
|
ebb8e06df0 | ||
|
|
c079cf4eec | ||
|
|
62937798a0 | ||
|
|
f48d5e4c4c | ||
|
|
f8764bb5d3 | ||
|
|
f8a037ead4 | ||
|
|
d08fd7e86d | ||
|
|
8b28dc55e6 | ||
|
|
e1630c4d43 | ||
|
|
9f6d305eab | ||
|
|
8ac3a3f45e | ||
|
|
634d9ee18b | ||
|
|
ba6f3e8f9f | ||
|
|
17a0ef1e1e | ||
|
|
c3a8ea76f1 | ||
|
|
0a5e01b710 | ||
|
|
a70ed9c8ae | ||
|
|
013778aa21 | ||
|
|
be155f4d9d | ||
|
|
5fdfbfe25a | ||
|
|
b18ccf9399 | ||
|
|
e81a2080eb | ||
|
|
1723b4d8d4 | ||
|
|
2e7ce96b1d | ||
|
|
eafdcc1b8a | ||
|
|
ab71997b2c | ||
|
|
7db0b8d74f | ||
|
|
659a612368 | ||
|
|
bec05db746 | ||
|
|
1df3a7710e | ||
|
|
d03fc77475 | ||
|
|
2e0a9f66a0 | ||
|
|
326b044c19 | ||
|
|
87f89ac82d | ||
|
|
fae2dd0344 | ||
|
|
a904d70afe | ||
|
|
b11f6f90e7 | ||
|
|
4d96997447 | ||
|
|
b5c204ac6f | ||
|
|
701dd649e6 | ||
|
|
79c1ed80e9 | ||
|
|
cb267093bb | ||
|
|
b2febaff2f | ||
|
|
0eba5911f2 | ||
|
|
b134e6afcf | ||
|
|
d9e316e353 | ||
|
|
902fc61ef8 | ||
|
|
16ffecd8fb | ||
|
|
e8e5041955 | ||
|
|
ccca5e7c28 | ||
|
|
2c8afe1140 | ||
|
|
2ef04f69b2 | ||
|
|
14bf88a777 | ||
|
|
3990da488b | ||
|
|
80982748c8 | ||
|
|
e61a4dd485 | ||
|
|
b05f8ea5b5 | ||
|
|
3841ec4200 | ||
|
|
17ad56c1dc | ||
|
|
e3b6f6c016 | ||
|
|
412b31ad72 | ||
|
|
aa26baa3db | ||
|
|
4ef392906b | ||
|
|
5922f2c46d |
@@ -23,7 +23,7 @@ matrix:
|
||||
- os: osx
|
||||
env: NAME="macos build"
|
||||
sudo: false
|
||||
osx_image: xcode9.2
|
||||
osx_image: xcode9.3
|
||||
install: "./.travis/macos/deps.sh"
|
||||
script: "./.travis/macos/build.sh"
|
||||
after_success: "./.travis/macos/upload.sh"
|
||||
@@ -42,3 +42,7 @@ notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://api.yuzu-emu.org/code/travis/notify
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.ccache
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
docker run -v $(pwd):/yuzu ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
docker run -e CCACHE_DIR=/ccache -v $HOME/.ccache:/ccache -v $(pwd):/yuzu ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
apt-get update
|
||||
apt-get install -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget
|
||||
|
||||
# Get a recent version of CMake
|
||||
wget https://cmake.org/files/v3.10/cmake-3.10.1-Linux-x86_64.sh
|
||||
sh cmake-3.10.1-Linux-x86_64.sh --exclude-subdir --prefix=/ --skip-license
|
||||
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget cmake ninja-build ccache
|
||||
|
||||
cd /yuzu
|
||||
|
||||
export PATH=/usr/lib/ccache:$PATH
|
||||
ln -sf /usr/bin/ccache /usr/lib/ccache/cc
|
||||
ln -sf /usr/bin/ccache /usr/lib/ccache/c++
|
||||
mkdir build && cd build
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release
|
||||
make -j4
|
||||
ccache --show-stats > ccache_before
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
ninja
|
||||
ccache --show-stats > ccache_after
|
||||
diff -U100 ccache_before ccache_after || true
|
||||
|
||||
ctest -VV -C Release
|
||||
|
||||
@@ -7,8 +7,12 @@ export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export UNICORNDIR=$(pwd)/externals/unicorn
|
||||
|
||||
mkdir build && cd build
|
||||
export PATH=/usr/local/opt/ccache/libexec:$PATH
|
||||
ccache --show-stats > ccache_before
|
||||
cmake --version
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release
|
||||
make -j4
|
||||
ccache --show-stats > ccache_after
|
||||
diff -U100 ccache_before ccache_after || true
|
||||
|
||||
ctest -VV -C Release
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
brew update
|
||||
brew install dylibbundler p7zip qt5 sdl2
|
||||
brew install dylibbundler p7zip qt5 sdl2 ccache
|
||||
brew outdated cmake || brew upgrade cmake
|
||||
|
||||
@@ -3,7 +3,9 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
|
||||
set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin")
|
||||
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
|
||||
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
|
||||
set(PLATFORMS ${DLL_DEST}platforms/)
|
||||
set(STYLES ${DLL_DEST}styles/)
|
||||
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
|
||||
icudt*.dll
|
||||
icuin*.dll
|
||||
@@ -14,4 +16,5 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
Qt5Widgets$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
||||
endfunction(copy_yuzu_Qt5_deps)
|
||||
|
||||
@@ -116,6 +116,7 @@ after_build:
|
||||
|
||||
mkdir $RELEASE_DIST
|
||||
mkdir $RELEASE_DIST/platforms
|
||||
mkdir $RELEASE_DIST/styles
|
||||
|
||||
# copy the compiled binaries and other release files to the release folder
|
||||
Get-ChildItem "$CMAKE_BINARY_DIR" -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
@@ -136,6 +137,9 @@ after_build:
|
||||
# copy the qt windows plugin dll to platforms
|
||||
Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/platforms/qwindows.dll" -force -destination "$RELEASE_DIST/platforms"
|
||||
|
||||
# copy the qt windows vista style dll to platforms
|
||||
Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/styles/qwindowsvistastyle.dll" -force -destination "$RELEASE_DIST/styles"
|
||||
|
||||
7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\*
|
||||
7z a $MINGW_SEVENZIP $RELEASE_DIST
|
||||
}
|
||||
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 9cc12d80b9...990a569b7a
@@ -31,10 +31,8 @@ add_library(common STATIC
|
||||
bit_set.h
|
||||
break_points.cpp
|
||||
break_points.h
|
||||
chunk_file.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
code_block.h
|
||||
color.h
|
||||
common_funcs.h
|
||||
common_paths.h
|
||||
@@ -42,7 +40,6 @@ add_library(common STATIC
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
hash.h
|
||||
linear_disk_cache.h
|
||||
logging/backend.cpp
|
||||
logging/backend.h
|
||||
logging/filter.cpp
|
||||
|
||||
@@ -1,623 +0,0 @@
|
||||
// Copyright (C) 2003 Dolphin Project.
|
||||
|
||||
// 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, version 2.0 or later versions.
|
||||
|
||||
// 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 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official SVN repository and contact information can be found at
|
||||
// http://code.google.com/p/dolphin-emu/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Extremely simple serialization framework.
|
||||
|
||||
// (mis)-features:
|
||||
// + Super fast
|
||||
// + Very simple
|
||||
// + Same code is used for serialization and deserializaition (in most cases)
|
||||
// - Zero backwards/forwards compatibility
|
||||
// - Serialization code for anything complex has to be manually written.
|
||||
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
template <class T>
|
||||
struct LinkedListItem : public T {
|
||||
LinkedListItem<T>* next;
|
||||
};
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
class PointerWrapSection {
|
||||
public:
|
||||
PointerWrapSection(PointerWrap& p, int ver, const char* title)
|
||||
: p_(p), ver_(ver), title_(title) {}
|
||||
~PointerWrapSection();
|
||||
|
||||
bool operator==(const int& v) const {
|
||||
return ver_ == v;
|
||||
}
|
||||
bool operator!=(const int& v) const {
|
||||
return ver_ != v;
|
||||
}
|
||||
bool operator<=(const int& v) const {
|
||||
return ver_ <= v;
|
||||
}
|
||||
bool operator>=(const int& v) const {
|
||||
return ver_ >= v;
|
||||
}
|
||||
bool operator<(const int& v) const {
|
||||
return ver_ < v;
|
||||
}
|
||||
bool operator>(const int& v) const {
|
||||
return ver_ > v;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return ver_ > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
PointerWrap& p_;
|
||||
int ver_;
|
||||
const char* title_;
|
||||
};
|
||||
|
||||
// Wrapper class
|
||||
class PointerWrap {
|
||||
// This makes it a compile error if you forget to define DoState() on non-POD.
|
||||
// Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason...
|
||||
#ifdef _MSC_VER
|
||||
template <typename T, bool isPOD = std::is_pod<T>::value,
|
||||
bool isPointer = std::is_pointer<T>::value>
|
||||
#else
|
||||
template <typename T, bool isPOD = __is_pod(T), bool isPointer = std::is_pointer<T>::value>
|
||||
#endif
|
||||
struct DoHelper {
|
||||
static void DoArray(PointerWrap* p, T* x, int count) {
|
||||
for (int i = 0; i < count; ++i)
|
||||
p->Do(x[i]);
|
||||
}
|
||||
|
||||
static void Do(PointerWrap* p, T& x) {
|
||||
p->DoClass(x);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct DoHelper<T, true, false> {
|
||||
static void DoArray(PointerWrap* p, T* x, int count) {
|
||||
p->DoVoid((void*)x, sizeof(T) * count);
|
||||
}
|
||||
|
||||
static void Do(PointerWrap* p, T& x) {
|
||||
p->DoVoid((void*)&x, sizeof(x));
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
enum Mode {
|
||||
MODE_READ = 1, // load
|
||||
MODE_WRITE, // save
|
||||
MODE_MEASURE, // calculate size
|
||||
MODE_VERIFY, // compare
|
||||
};
|
||||
|
||||
enum Error {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_WARNING = 1,
|
||||
ERROR_FAILURE = 2,
|
||||
};
|
||||
|
||||
u8** ptr;
|
||||
Mode mode;
|
||||
Error error;
|
||||
|
||||
public:
|
||||
PointerWrap(u8** ptr_, Mode mode_) : ptr(ptr_), mode(mode_), error(ERROR_NONE) {}
|
||||
PointerWrap(unsigned char** ptr_, int mode_)
|
||||
: ptr((u8**)ptr_), mode((Mode)mode_), error(ERROR_NONE) {}
|
||||
|
||||
PointerWrapSection Section(const char* title, int ver) {
|
||||
return Section(title, ver, ver);
|
||||
}
|
||||
|
||||
// The returned object can be compared against the version that was loaded.
|
||||
// This can be used to support versions as old as minVer.
|
||||
// Version = 0 means the section was not found.
|
||||
PointerWrapSection Section(const char* title, int minVer, int ver) {
|
||||
char marker[16] = {0};
|
||||
int foundVersion = ver;
|
||||
|
||||
strncpy(marker, title, sizeof(marker));
|
||||
if (!ExpectVoid(marker, sizeof(marker))) {
|
||||
// Might be before we added name markers for safety.
|
||||
if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion)))
|
||||
DoMarker(title);
|
||||
// Wasn't found, but maybe we can still load the state.
|
||||
else
|
||||
foundVersion = 0;
|
||||
} else
|
||||
Do(foundVersion);
|
||||
|
||||
if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) {
|
||||
LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion,
|
||||
title);
|
||||
SetError(ERROR_FAILURE);
|
||||
return PointerWrapSection(*this, -1, title);
|
||||
}
|
||||
return PointerWrapSection(*this, foundVersion, title);
|
||||
}
|
||||
|
||||
void SetMode(Mode mode_) {
|
||||
mode = mode_;
|
||||
}
|
||||
Mode GetMode() const {
|
||||
return mode;
|
||||
}
|
||||
u8** GetPPtr() {
|
||||
return ptr;
|
||||
}
|
||||
void SetError(Error error_) {
|
||||
if (error < error_)
|
||||
error = error_;
|
||||
if (error > ERROR_WARNING)
|
||||
mode = PointerWrap::MODE_MEASURE;
|
||||
}
|
||||
|
||||
bool ExpectVoid(void* data, int size) {
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
if (memcmp(data, *ptr, size) != 0)
|
||||
return false;
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, data, size);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break; // MODE_MEASURE - don't need to do anything
|
||||
case MODE_VERIFY:
|
||||
for (int i = 0; i < size; i++) {
|
||||
DEBUG_ASSERT_MSG(
|
||||
((u8*)data)[i] == (*ptr)[i],
|
||||
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
||||
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
|
||||
&(*ptr)[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break; // throw an error?
|
||||
}
|
||||
(*ptr) += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DoVoid(void* data, int size) {
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
memcpy(data, *ptr, size);
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, data, size);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break; // MODE_MEASURE - don't need to do anything
|
||||
case MODE_VERIFY:
|
||||
for (int i = 0; i < size; i++) {
|
||||
DEBUG_ASSERT_MSG(
|
||||
((u8*)data)[i] == (*ptr)[i],
|
||||
"Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n",
|
||||
((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i],
|
||||
&(*ptr)[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break; // throw an error?
|
||||
}
|
||||
(*ptr) += size;
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::map<K, T*>& x) {
|
||||
if (mode == MODE_READ) {
|
||||
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||
if (it->second != nullptr)
|
||||
delete it->second;
|
||||
}
|
||||
}
|
||||
T* dv = nullptr;
|
||||
DoMap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::map<K, T>& x) {
|
||||
T dv = T();
|
||||
DoMap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void DoMap(std::map<K, T>& x, T& default_val) {
|
||||
unsigned int number = (unsigned int)x.size();
|
||||
Do(number);
|
||||
switch (mode) {
|
||||
case MODE_READ: {
|
||||
x.clear();
|
||||
while (number > 0) {
|
||||
K first = K();
|
||||
Do(first);
|
||||
T second = default_val;
|
||||
Do(second);
|
||||
x[first] = second;
|
||||
--number;
|
||||
}
|
||||
} break;
|
||||
case MODE_WRITE:
|
||||
case MODE_MEASURE:
|
||||
case MODE_VERIFY: {
|
||||
typename std::map<K, T>::iterator itr = x.begin();
|
||||
while (number > 0) {
|
||||
K first = itr->first;
|
||||
Do(first);
|
||||
Do(itr->second);
|
||||
--number;
|
||||
++itr;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::multimap<K, T*>& x) {
|
||||
if (mode == MODE_READ) {
|
||||
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||
if (it->second != nullptr)
|
||||
delete it->second;
|
||||
}
|
||||
}
|
||||
T* dv = nullptr;
|
||||
DoMultimap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void Do(std::multimap<K, T>& x) {
|
||||
T dv = T();
|
||||
DoMultimap(x, dv);
|
||||
}
|
||||
|
||||
template <class K, class T>
|
||||
void DoMultimap(std::multimap<K, T>& x, T& default_val) {
|
||||
unsigned int number = (unsigned int)x.size();
|
||||
Do(number);
|
||||
switch (mode) {
|
||||
case MODE_READ: {
|
||||
x.clear();
|
||||
while (number > 0) {
|
||||
K first = K();
|
||||
Do(first);
|
||||
T second = default_val;
|
||||
Do(second);
|
||||
x.insert(std::make_pair(first, second));
|
||||
--number;
|
||||
}
|
||||
} break;
|
||||
case MODE_WRITE:
|
||||
case MODE_MEASURE:
|
||||
case MODE_VERIFY: {
|
||||
typename std::multimap<K, T>::iterator itr = x.begin();
|
||||
while (number > 0) {
|
||||
Do(itr->first);
|
||||
Do(itr->second);
|
||||
--number;
|
||||
++itr;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Store vectors.
|
||||
template <class T>
|
||||
void Do(std::vector<T*>& x) {
|
||||
T* dv = nullptr;
|
||||
DoVector(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::vector<T>& x) {
|
||||
T dv = T();
|
||||
DoVector(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoPOD(std::vector<T>& x) {
|
||||
T dv = T();
|
||||
DoVectorPOD(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::vector<T>& x, T& default_val) {
|
||||
DoVector(x, default_val);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoVector(std::vector<T>& x, T& default_val) {
|
||||
u32 vec_size = (u32)x.size();
|
||||
Do(vec_size);
|
||||
x.resize(vec_size, default_val);
|
||||
if (vec_size > 0)
|
||||
DoArray(&x[0], vec_size);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoVectorPOD(std::vector<T>& x, T& default_val) {
|
||||
u32 vec_size = (u32)x.size();
|
||||
Do(vec_size);
|
||||
x.resize(vec_size, default_val);
|
||||
if (vec_size > 0)
|
||||
DoArray(&x[0], vec_size);
|
||||
}
|
||||
|
||||
// Store deques.
|
||||
template <class T>
|
||||
void Do(std::deque<T*>& x) {
|
||||
T* dv = nullptr;
|
||||
DoDeque(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::deque<T>& x) {
|
||||
T dv = T();
|
||||
DoDeque(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoDeque(std::deque<T>& x, T& default_val) {
|
||||
u32 deq_size = (u32)x.size();
|
||||
Do(deq_size);
|
||||
x.resize(deq_size, default_val);
|
||||
u32 i;
|
||||
for (i = 0; i < deq_size; i++)
|
||||
Do(x[i]);
|
||||
}
|
||||
|
||||
// Store STL lists.
|
||||
template <class T>
|
||||
void Do(std::list<T*>& x) {
|
||||
T* dv = nullptr;
|
||||
Do(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::list<T>& x) {
|
||||
T dv = T();
|
||||
DoList(x, dv);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::list<T>& x, T& default_val) {
|
||||
DoList(x, default_val);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoList(std::list<T>& x, T& default_val) {
|
||||
u32 list_size = (u32)x.size();
|
||||
Do(list_size);
|
||||
x.resize(list_size, default_val);
|
||||
|
||||
typename std::list<T>::iterator itr, end;
|
||||
for (itr = x.begin(), end = x.end(); itr != end; ++itr)
|
||||
Do(*itr);
|
||||
}
|
||||
|
||||
// Store STL sets.
|
||||
template <class T>
|
||||
void Do(std::set<T*>& x) {
|
||||
if (mode == MODE_READ) {
|
||||
for (auto it = x.begin(), end = x.end(); it != end; ++it) {
|
||||
if (*it != nullptr)
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
DoSet(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(std::set<T>& x) {
|
||||
DoSet(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoSet(std::set<T>& x) {
|
||||
unsigned int number = (unsigned int)x.size();
|
||||
Do(number);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_READ: {
|
||||
x.clear();
|
||||
while (number-- > 0) {
|
||||
T it = T();
|
||||
Do(it);
|
||||
x.insert(it);
|
||||
}
|
||||
} break;
|
||||
case MODE_WRITE:
|
||||
case MODE_MEASURE:
|
||||
case MODE_VERIFY: {
|
||||
typename std::set<T>::iterator itr = x.begin();
|
||||
while (number-- > 0)
|
||||
Do(*itr++);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Store strings.
|
||||
void Do(std::string& x) {
|
||||
int stringLen = (int)x.length() + 1;
|
||||
Do(stringLen);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
x = (char*)*ptr;
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, x.c_str(), stringLen);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break;
|
||||
case MODE_VERIFY:
|
||||
DEBUG_ASSERT_MSG((x == (char*)*ptr),
|
||||
"Savestate verification failure: \"%s\" != \"%s\" (at %p).\n",
|
||||
x.c_str(), (char*)*ptr, ptr);
|
||||
break;
|
||||
}
|
||||
(*ptr) += stringLen;
|
||||
}
|
||||
|
||||
void Do(std::wstring& x) {
|
||||
int stringLen = sizeof(wchar_t) * ((int)x.length() + 1);
|
||||
Do(stringLen);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
x = (wchar_t*)*ptr;
|
||||
break;
|
||||
case MODE_WRITE:
|
||||
memcpy(*ptr, x.c_str(), stringLen);
|
||||
break;
|
||||
case MODE_MEASURE:
|
||||
break;
|
||||
case MODE_VERIFY:
|
||||
DEBUG_ASSERT_MSG((x == (wchar_t*)*ptr),
|
||||
"Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n",
|
||||
x.c_str(), (wchar_t*)*ptr, ptr);
|
||||
break;
|
||||
}
|
||||
(*ptr) += stringLen;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoClass(T& x) {
|
||||
x.DoState(*this);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoClass(T*& x) {
|
||||
if (mode == MODE_READ) {
|
||||
if (x != nullptr)
|
||||
delete x;
|
||||
x = new T();
|
||||
}
|
||||
x->DoState(*this);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoArray(T* x, int count) {
|
||||
DoHelper<T>::DoArray(this, x, count);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Do(T& x) {
|
||||
DoHelper<T>::Do(this, x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoPOD(T& x) {
|
||||
DoHelper<T>::Do(this, x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void DoPointer(T*& x, T* const base) {
|
||||
// pointers can be more than 2^31 apart, but you're using this function wrong if you need
|
||||
// that much range
|
||||
s32 offset = x - base;
|
||||
Do(offset);
|
||||
if (mode == MODE_READ)
|
||||
x = base + offset;
|
||||
}
|
||||
|
||||
template <class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*),
|
||||
void (*TDo)(PointerWrap&, T*)>
|
||||
void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = nullptr) {
|
||||
LinkedListItem<T>* list_cur = list_start;
|
||||
LinkedListItem<T>* prev = nullptr;
|
||||
|
||||
while (true) {
|
||||
u8 shouldExist = (list_cur ? 1 : 0);
|
||||
Do(shouldExist);
|
||||
if (shouldExist == 1) {
|
||||
LinkedListItem<T>* cur = list_cur ? list_cur : TNew();
|
||||
TDo(*this, (T*)cur);
|
||||
if (!list_cur) {
|
||||
if (mode == MODE_READ) {
|
||||
cur->next = nullptr;
|
||||
list_cur = cur;
|
||||
if (prev)
|
||||
prev->next = cur;
|
||||
else
|
||||
list_start = cur;
|
||||
} else {
|
||||
TFree(cur);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mode == MODE_READ) {
|
||||
if (prev)
|
||||
prev->next = nullptr;
|
||||
if (list_end)
|
||||
*list_end = prev;
|
||||
if (list_cur) {
|
||||
if (list_start == list_cur)
|
||||
list_start = nullptr;
|
||||
do {
|
||||
LinkedListItem<T>* next = list_cur->next;
|
||||
TFree(list_cur);
|
||||
list_cur = next;
|
||||
} while (list_cur);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev = list_cur;
|
||||
list_cur = list_cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
void DoMarker(const char* prevName, u32 arbitraryNumber = 0x42) {
|
||||
u32 cookie = arbitraryNumber;
|
||||
Do(cookie);
|
||||
if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) {
|
||||
LOG_ERROR(Common,
|
||||
"After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). "
|
||||
"Aborting savestate load...",
|
||||
prevName, cookie, cookie, arbitraryNumber, arbitraryNumber);
|
||||
SetError(ERROR_FAILURE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline PointerWrapSection::~PointerWrapSection() {
|
||||
if (ver_ > 0) {
|
||||
p_.DoMarker(title_);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_util.h"
|
||||
|
||||
// Everything that needs to generate code should inherit from this.
|
||||
// You get memory management for free, plus, you can use all emitter functions without
|
||||
// having to prefix them with gen-> or something similar.
|
||||
// Example implementation:
|
||||
// class JIT : public CodeBlock<ARMXEmitter> {}
|
||||
template <class T>
|
||||
class CodeBlock : public T, NonCopyable {
|
||||
private:
|
||||
// A privately used function to set the executable RAM space to something invalid.
|
||||
// For debugging usefulness it should be used to set the RAM to a host specific breakpoint
|
||||
// instruction
|
||||
virtual void PoisonMemory() = 0;
|
||||
|
||||
protected:
|
||||
u8* region;
|
||||
size_t region_size;
|
||||
|
||||
public:
|
||||
CodeBlock() : region(nullptr), region_size(0) {}
|
||||
virtual ~CodeBlock() {
|
||||
if (region)
|
||||
FreeCodeSpace();
|
||||
}
|
||||
|
||||
// Call this before you generate any code.
|
||||
void AllocCodeSpace(int size) {
|
||||
region_size = size;
|
||||
region = (u8*)AllocateExecutableMemory(region_size);
|
||||
T::SetCodePtr(region);
|
||||
}
|
||||
|
||||
// Always clear code space with breakpoints, so that if someone accidentally executes
|
||||
// uninitialized, it just breaks into the debugger.
|
||||
void ClearCodeSpace() {
|
||||
PoisonMemory();
|
||||
ResetCodePtr();
|
||||
}
|
||||
|
||||
// Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
|
||||
void FreeCodeSpace() {
|
||||
#ifdef __SYMBIAN32__
|
||||
ResetExecutableMemory(region);
|
||||
#else
|
||||
FreeMemoryPages(region, region_size);
|
||||
#endif
|
||||
region = nullptr;
|
||||
region_size = 0;
|
||||
}
|
||||
|
||||
bool IsInSpace(const u8* ptr) {
|
||||
return (ptr >= region) && (ptr < (region + region_size));
|
||||
}
|
||||
|
||||
// Cannot currently be undone. Will write protect the entire code region.
|
||||
// Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()).
|
||||
void WriteProtect() {
|
||||
WriteProtectMemory(region, region_size, true);
|
||||
}
|
||||
|
||||
void ResetCodePtr() {
|
||||
T::SetCodePtr(region);
|
||||
}
|
||||
|
||||
size_t GetSpaceLeft() const {
|
||||
return region_size - (T::GetCodePtr() - region);
|
||||
}
|
||||
|
||||
u8* GetBasePtr() {
|
||||
return region;
|
||||
}
|
||||
|
||||
size_t GetOffset(const u8* ptr) const {
|
||||
return ptr - region;
|
||||
}
|
||||
};
|
||||
@@ -9,8 +9,6 @@
|
||||
#endif
|
||||
#include "common/common_types.h"
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
/// Textually concatenates two tokens. The double-expansion is required by the C preprocessor.
|
||||
#define CONCAT2(x, y) DO_CONCAT2(x, y)
|
||||
#define DO_CONCAT2(x, y) x##y
|
||||
@@ -74,11 +72,6 @@ inline u64 _rotr64(u64 x, unsigned int shift) {
|
||||
|
||||
#else // _MSC_VER
|
||||
|
||||
#if (_MSC_VER < 1900)
|
||||
// Function Cross-Compatibility
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
// Locale Cross-Compatibility
|
||||
#define locale_t _locale_t
|
||||
|
||||
|
||||
@@ -32,12 +32,15 @@
|
||||
#define SDMC_DIR "sdmc"
|
||||
#define NAND_DIR "nand"
|
||||
#define SYSDATA_DIR "sysdata"
|
||||
#define LOG_DIR "log"
|
||||
|
||||
// Filenames
|
||||
// Files in the directory returned by GetUserPath(D_CONFIG_IDX)
|
||||
#define EMU_CONFIG "emu.ini"
|
||||
#define DEBUGGER_CONFIG "debugger.ini"
|
||||
#define LOGGER_CONFIG "logger.ini"
|
||||
// Files in the directory returned by GetUserPath(D_LOGS_IDX)
|
||||
#define LOG_FILE "citra_log.txt"
|
||||
|
||||
// Sys files
|
||||
#define SHARED_FONT "shared_font.bin"
|
||||
|
||||
@@ -27,29 +27,23 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifndef __func__
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
#endif
|
||||
using u8 = std::uint8_t; ///< 8-bit unsigned byte
|
||||
using u16 = std::uint16_t; ///< 16-bit unsigned short
|
||||
using u32 = std::uint32_t; ///< 32-bit unsigned word
|
||||
using u64 = std::uint64_t; ///< 64-bit unsigned int
|
||||
|
||||
typedef std::uint8_t u8; ///< 8-bit unsigned byte
|
||||
typedef std::uint16_t u16; ///< 16-bit unsigned short
|
||||
typedef std::uint32_t u32; ///< 32-bit unsigned word
|
||||
typedef std::uint64_t u64; ///< 64-bit unsigned int
|
||||
using s8 = std::int8_t; ///< 8-bit signed byte
|
||||
using s16 = std::int16_t; ///< 16-bit signed short
|
||||
using s32 = std::int32_t; ///< 32-bit signed word
|
||||
using s64 = std::int64_t; ///< 64-bit signed int
|
||||
|
||||
typedef std::int8_t s8; ///< 8-bit signed byte
|
||||
typedef std::int16_t s16; ///< 16-bit signed short
|
||||
typedef std::int32_t s32; ///< 32-bit signed word
|
||||
typedef std::int64_t s64; ///< 64-bit signed int
|
||||
|
||||
typedef float f32; ///< 32-bit floating point
|
||||
typedef double f64; ///< 64-bit floating point
|
||||
using f32 = float; ///< 32-bit floating point
|
||||
using f64 = double; ///< 64-bit floating point
|
||||
|
||||
// TODO: It would be nice to eventually replace these with strong types that prevent accidental
|
||||
// conversion between each other.
|
||||
typedef u64 VAddr; ///< Represents a pointer in the userspace virtual address space.
|
||||
typedef u64 PAddr; ///< Represents a pointer in the ARM11 physical address space.
|
||||
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
|
||||
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
|
||||
|
||||
using u128 = std::array<std::uint64_t, 2>;
|
||||
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
|
||||
|
||||
@@ -118,7 +118,7 @@ bool IsDirectory(const std::string& filename) {
|
||||
#endif
|
||||
|
||||
if (result < 0) {
|
||||
LOG_DEBUG(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -128,31 +128,29 @@ bool IsDirectory(const std::string& filename) {
|
||||
// Deletes a given filename, return true on success
|
||||
// Doesn't supports deleting a directory
|
||||
bool Delete(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "file %s", filename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "file {}", filename);
|
||||
|
||||
// Return true because we care about the file no
|
||||
// being there, not the actual delete.
|
||||
if (!Exists(filename)) {
|
||||
LOG_DEBUG(Common_Filesystem, "%s does not exist", filename.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can't delete a directory
|
||||
if (IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
|
||||
LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s", filename.c_str(),
|
||||
GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (unlink(filename.c_str()) == -1) {
|
||||
LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s", filename.c_str(),
|
||||
GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
@@ -162,16 +160,16 @@ bool Delete(const std::string& filename) {
|
||||
|
||||
// Returns true if successful, or path already exists.
|
||||
bool CreateDir(const std::string& path) {
|
||||
LOG_TRACE(Common_Filesystem, "directory %s", path.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", path);
|
||||
#ifdef _WIN32
|
||||
if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
|
||||
return true;
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_ALREADY_EXISTS) {
|
||||
LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
|
||||
return true;
|
||||
}
|
||||
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error);
|
||||
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
|
||||
return false;
|
||||
#else
|
||||
if (mkdir(path.c_str(), 0755) == 0)
|
||||
@@ -180,11 +178,11 @@ bool CreateDir(const std::string& path) {
|
||||
int err = errno;
|
||||
|
||||
if (err == EEXIST) {
|
||||
LOG_DEBUG(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "mkdir failed on %s: %s", path.c_str(), strerror(err));
|
||||
LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -192,10 +190,10 @@ bool CreateDir(const std::string& path) {
|
||||
// Creates the full path of fullPath returns true on success
|
||||
bool CreateFullPath(const std::string& fullPath) {
|
||||
int panicCounter = 100;
|
||||
LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
|
||||
|
||||
if (FileUtil::Exists(fullPath)) {
|
||||
LOG_DEBUG(Common_Filesystem, "path exists %s", fullPath.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -227,11 +225,11 @@ bool CreateFullPath(const std::string& fullPath) {
|
||||
|
||||
// Deletes a directory filename, returns true on success
|
||||
bool DeleteDir(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "directory %s", filename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", filename);
|
||||
|
||||
// check if a directory
|
||||
if (!FileUtil::IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -242,14 +240,14 @@ bool DeleteDir(const std::string& filename) {
|
||||
if (rmdir(filename.c_str()) == 0)
|
||||
return true;
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// renames file srcFilename to destFilename, returns true on success
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
||||
LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
||||
#ifdef _WIN32
|
||||
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
||||
@@ -258,20 +256,20 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
||||
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
||||
return true;
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
// copies file srcFilename to destFilename, returns true on success
|
||||
bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
||||
#ifdef _WIN32
|
||||
if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
|
||||
return true;
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(),
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
#else
|
||||
@@ -284,8 +282,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
// Open input file
|
||||
FILE* input = fopen(srcFilename.c_str(), "rb");
|
||||
if (!input) {
|
||||
LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s", srcFilename.c_str(),
|
||||
destFilename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -293,8 +291,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
FILE* output = fopen(destFilename.c_str(), "wb");
|
||||
if (!output) {
|
||||
fclose(input);
|
||||
LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s", srcFilename.c_str(),
|
||||
destFilename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -304,8 +302,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
size_t rnum = fread(buffer, sizeof(char), BSIZE, input);
|
||||
if (rnum != BSIZE) {
|
||||
if (ferror(input) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s",
|
||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
|
||||
srcFilename, destFilename, GetLastErrorMsg());
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
@@ -313,8 +311,8 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
// write output
|
||||
size_t wnum = fwrite(buffer, sizeof(char), rnum, output);
|
||||
if (wnum != rnum) {
|
||||
LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s",
|
||||
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
@@ -334,12 +332,12 @@ bail:
|
||||
// Returns the size of filename (64bit)
|
||||
u64 GetSize(const std::string& filename) {
|
||||
if (!Exists(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -350,11 +348,11 @@ u64 GetSize(const std::string& filename) {
|
||||
if (stat(filename.c_str(), &buf) == 0)
|
||||
#endif
|
||||
{
|
||||
LOG_TRACE(Common_Filesystem, "%s: %lld", filename.c_str(), (long long)buf.st_size);
|
||||
LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
|
||||
return buf.st_size;
|
||||
}
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "Stat failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -362,7 +360,7 @@ u64 GetSize(const std::string& filename) {
|
||||
u64 GetSize(const int fd) {
|
||||
struct stat buf;
|
||||
if (fstat(fd, &buf) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s", fd, GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
return buf.st_size;
|
||||
@@ -373,12 +371,12 @@ u64 GetSize(FILE* f) {
|
||||
// can't use off_t here because it can be 32-bit
|
||||
u64 pos = ftello(f);
|
||||
if (fseeko(f, 0, SEEK_END) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
u64 size = ftello(f);
|
||||
if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
return size;
|
||||
@@ -386,10 +384,10 @@ u64 GetSize(FILE* f) {
|
||||
|
||||
// creates an empty file filename, returns true on success
|
||||
bool CreateEmptyFile(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "%s", filename.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "{}", filename);
|
||||
|
||||
if (!FileUtil::IOFile(filename, "wb")) {
|
||||
LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -398,7 +396,7 @@ bool CreateEmptyFile(const std::string& filename) {
|
||||
|
||||
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string& directory,
|
||||
DirectoryEntryCallable callback) {
|
||||
LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", directory);
|
||||
|
||||
// How many files + directories we found
|
||||
unsigned found_entries = 0;
|
||||
@@ -556,7 +554,7 @@ std::string GetCurrentDir() {
|
||||
char* dir;
|
||||
if (!(dir = getcwd(nullptr, 0))) {
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
@@ -653,12 +651,12 @@ static const std::string GetUserDirectory(const std::string& envvar) {
|
||||
else if (envvar == "XDG_CACHE_HOME")
|
||||
subdirectory = DIR_SEP ".cache";
|
||||
else
|
||||
ASSERT_MSG(false, "Unknown XDG variable %s.", envvar.c_str());
|
||||
ASSERT_MSG(false, "Unknown XDG variable {}.", envvar);
|
||||
user_dir = GetHomeDirectory() + subdirectory;
|
||||
}
|
||||
|
||||
ASSERT_MSG(!user_dir.empty(), "User directory %s musn’t be empty.", envvar.c_str());
|
||||
ASSERT_MSG(user_dir[0] == '/', "User directory %s must be absolute.", envvar.c_str());
|
||||
ASSERT_MSG(!user_dir.empty(), "User directory {} mustn’t be empty.", envvar);
|
||||
ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar);
|
||||
|
||||
return user_dir;
|
||||
}
|
||||
@@ -676,7 +674,7 @@ std::string GetSysDirectory() {
|
||||
#endif
|
||||
sysDir += DIR_SEP;
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "Setting to %s:", sysDir.c_str());
|
||||
LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
|
||||
return sysDir;
|
||||
}
|
||||
|
||||
@@ -715,11 +713,13 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new
|
||||
paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP;
|
||||
paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP;
|
||||
paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP;
|
||||
// TODO: Put the logs in a better location for each OS
|
||||
paths[D_LOGS_IDX] = paths[D_USER_IDX] + LOG_DIR DIR_SEP;
|
||||
}
|
||||
|
||||
if (!newPath.empty()) {
|
||||
if (!FileUtil::IsDirectory(newPath)) {
|
||||
LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str());
|
||||
LOG_ERROR(Common_Filesystem, "Invalid path specified {}", newPath);
|
||||
return paths[DirIDX];
|
||||
} else {
|
||||
paths[DirIDX] = newPath;
|
||||
@@ -801,33 +801,38 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
|
||||
|
||||
IOFile::IOFile() {}
|
||||
|
||||
IOFile::IOFile(const std::string& filename, const char openmode[]) {
|
||||
Open(filename, openmode);
|
||||
IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
|
||||
Open(filename, openmode, flags);
|
||||
}
|
||||
|
||||
IOFile::~IOFile() {
|
||||
Close();
|
||||
}
|
||||
|
||||
IOFile::IOFile(IOFile&& other) {
|
||||
IOFile::IOFile(IOFile&& other) noexcept {
|
||||
Swap(other);
|
||||
}
|
||||
|
||||
IOFile& IOFile::operator=(IOFile&& other) {
|
||||
IOFile& IOFile::operator=(IOFile&& other) noexcept {
|
||||
Swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void IOFile::Swap(IOFile& other) {
|
||||
void IOFile::Swap(IOFile& other) noexcept {
|
||||
std::swap(m_file, other.m_file);
|
||||
std::swap(m_good, other.m_good);
|
||||
}
|
||||
|
||||
bool IOFile::Open(const std::string& filename, const char openmode[]) {
|
||||
bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
|
||||
Close();
|
||||
#ifdef _WIN32
|
||||
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str());
|
||||
if (flags != 0) {
|
||||
m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str(), flags);
|
||||
} else {
|
||||
_wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str());
|
||||
}
|
||||
#else
|
||||
m_file = fopen(filename.c_str(), openmode);
|
||||
#endif
|
||||
|
||||
@@ -156,26 +156,25 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
|
||||
class IOFile : public NonCopyable {
|
||||
public:
|
||||
IOFile();
|
||||
IOFile(const std::string& filename, const char openmode[]);
|
||||
// flags is used for windows specific file open mode flags, which
|
||||
// allows yuzu to open the logs in shared write mode, so that the file
|
||||
// isn't considered "locked" while yuzu is open and people can open the log file and view it
|
||||
IOFile(const std::string& filename, const char openmode[], int flags = 0);
|
||||
|
||||
~IOFile();
|
||||
|
||||
IOFile(IOFile&& other);
|
||||
IOFile& operator=(IOFile&& other);
|
||||
IOFile(IOFile&& other) noexcept;
|
||||
IOFile& operator=(IOFile&& other) noexcept;
|
||||
|
||||
void Swap(IOFile& other);
|
||||
void Swap(IOFile& other) noexcept;
|
||||
|
||||
bool Open(const std::string& filename, const char openmode[]);
|
||||
bool Open(const std::string& filename, const char openmode[], int flags = 0);
|
||||
bool Close();
|
||||
|
||||
template <typename T>
|
||||
size_t ReadArray(T* data, size_t length) {
|
||||
static_assert(std::is_standard_layout<T>(),
|
||||
"Given array does not consist of standard layout objects");
|
||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
@@ -191,12 +190,8 @@ public:
|
||||
|
||||
template <typename T>
|
||||
size_t WriteArray(const T* data, size_t length) {
|
||||
static_assert(std::is_standard_layout<T>(),
|
||||
"Given array does not consist of standard layout objects");
|
||||
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
|
||||
static_assert(std::is_trivially_copyable<T>(),
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
@@ -210,11 +205,15 @@ public:
|
||||
return items_written;
|
||||
}
|
||||
|
||||
size_t ReadBytes(void* data, size_t length) {
|
||||
template <typename T>
|
||||
size_t ReadBytes(T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
|
||||
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||
}
|
||||
|
||||
size_t WriteBytes(const void* data, size_t length) {
|
||||
template <typename T>
|
||||
size_t WriteBytes(const T* data, size_t length) {
|
||||
static_assert(std::is_trivially_copyable<T>(), "T must be trivially copyable");
|
||||
return WriteArray(reinterpret_cast<const char*>(data), length);
|
||||
}
|
||||
|
||||
@@ -224,6 +223,10 @@ public:
|
||||
return WriteArray(&object, 1);
|
||||
}
|
||||
|
||||
size_t WriteString(const std::string& str) {
|
||||
return WriteArray(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
bool IsOpen() const {
|
||||
return nullptr != m_file;
|
||||
}
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include "common/common_types.h"
|
||||
|
||||
// defined in Version.cpp
|
||||
extern const char* scm_rev_git_str;
|
||||
|
||||
// On disk format:
|
||||
// header{
|
||||
// u32 'DCAC';
|
||||
// u32 version; // svn_rev
|
||||
// u16 sizeof(key_type);
|
||||
// u16 sizeof(value_type);
|
||||
//}
|
||||
|
||||
// key_value_pair{
|
||||
// u32 value_size;
|
||||
// key_type key;
|
||||
// value_type[value_size] value;
|
||||
//}
|
||||
|
||||
template <typename K, typename V>
|
||||
class LinearDiskCacheReader {
|
||||
public:
|
||||
virtual void Read(const K& key, const V* value, u32 value_size) = 0;
|
||||
};
|
||||
|
||||
// Dead simple unsorted key-value store with append functionality.
|
||||
// No random read functionality, all reading is done in OpenAndRead.
|
||||
// Keys and values can contain any characters, including \0.
|
||||
//
|
||||
// Suitable for caching generated shader bytecode between executions.
|
||||
// Not tuned for extreme performance but should be reasonably fast.
|
||||
// Does not support keys or values larger than 2GB, which should be reasonable.
|
||||
// Keys must have non-zero length; values can have zero length.
|
||||
|
||||
// K and V are some POD type
|
||||
// K : the key type
|
||||
// V : value array type
|
||||
template <typename K, typename V>
|
||||
class LinearDiskCache {
|
||||
public:
|
||||
// return number of read entries
|
||||
u32 OpenAndRead(const char* filename, LinearDiskCacheReader<K, V>& reader) {
|
||||
using std::ios_base;
|
||||
|
||||
// close any currently opened file
|
||||
Close();
|
||||
m_num_entries = 0;
|
||||
|
||||
// try opening for reading/writing
|
||||
OpenFStream(m_file, filename, ios_base::in | ios_base::out | ios_base::binary);
|
||||
|
||||
m_file.seekg(0, std::ios::end);
|
||||
std::fstream::pos_type end_pos = m_file.tellg();
|
||||
m_file.seekg(0, std::ios::beg);
|
||||
std::fstream::pos_type start_pos = m_file.tellg();
|
||||
std::streamoff file_size = end_pos - start_pos;
|
||||
|
||||
if (m_file.is_open() && ValidateHeader()) {
|
||||
// good header, read some key/value pairs
|
||||
K key;
|
||||
|
||||
V* value = nullptr;
|
||||
u32 value_size;
|
||||
u32 entry_number;
|
||||
|
||||
std::fstream::pos_type last_pos = m_file.tellg();
|
||||
|
||||
while (Read(&value_size)) {
|
||||
std::streamoff next_extent =
|
||||
(last_pos - start_pos) + sizeof(value_size) + value_size;
|
||||
if (next_extent > file_size)
|
||||
break;
|
||||
|
||||
delete[] value;
|
||||
value = new V[value_size];
|
||||
|
||||
// read key/value and pass to reader
|
||||
if (Read(&key) && Read(value, value_size) && Read(&entry_number) &&
|
||||
entry_number == m_num_entries + 1) {
|
||||
reader.Read(key, value, value_size);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
m_num_entries++;
|
||||
last_pos = m_file.tellg();
|
||||
}
|
||||
m_file.seekp(last_pos);
|
||||
m_file.clear();
|
||||
|
||||
delete[] value;
|
||||
return m_num_entries;
|
||||
}
|
||||
|
||||
// failed to open file for reading or bad header
|
||||
// close and recreate file
|
||||
Close();
|
||||
m_file.open(filename, ios_base::out | ios_base::trunc | ios_base::binary);
|
||||
WriteHeader();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
m_file.flush();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
if (m_file.is_open())
|
||||
m_file.close();
|
||||
// clear any error flags
|
||||
m_file.clear();
|
||||
}
|
||||
|
||||
// Appends a key-value pair to the store.
|
||||
void Append(const K& key, const V* value, u32 value_size) {
|
||||
// TODO: Should do a check that we don't already have "key"? (I think each caller does that
|
||||
// already.)
|
||||
Write(&value_size);
|
||||
Write(&key);
|
||||
Write(value, value_size);
|
||||
m_num_entries++;
|
||||
Write(&m_num_entries);
|
||||
}
|
||||
|
||||
private:
|
||||
void WriteHeader() {
|
||||
Write(&m_header);
|
||||
}
|
||||
|
||||
bool ValidateHeader() {
|
||||
char file_header[sizeof(Header)];
|
||||
|
||||
return (Read(file_header, sizeof(Header)) &&
|
||||
!memcmp((const char*)&m_header, file_header, sizeof(Header)));
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
bool Write(const D* data, u32 count = 1) {
|
||||
return m_file.write((const char*)data, count * sizeof(D)).good();
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
bool Read(const D* data, u32 count = 1) {
|
||||
return m_file.read((char*)data, count * sizeof(D)).good();
|
||||
}
|
||||
|
||||
struct Header {
|
||||
Header() : id(*(u32*)"DCAC"), key_t_size(sizeof(K)), value_t_size(sizeof(V)) {
|
||||
memcpy(ver, scm_rev_git_str, 40);
|
||||
}
|
||||
|
||||
const u32 id;
|
||||
const u16 key_t_size, value_t_size;
|
||||
char ver[40];
|
||||
|
||||
} m_header;
|
||||
|
||||
std::fstream m_file;
|
||||
u32 m_num_entries;
|
||||
};
|
||||
@@ -4,17 +4,143 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#ifdef _WIN32
|
||||
#include <share.h> // For _SH_DENYWR
|
||||
#else
|
||||
#define _SH_DENYWR 0
|
||||
#endif
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h" // snprintf compatibility define
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace Log {
|
||||
|
||||
/**
|
||||
* Static state as a singleton.
|
||||
*/
|
||||
class Impl {
|
||||
public:
|
||||
static Impl& Instance() {
|
||||
static Impl backend;
|
||||
return backend;
|
||||
}
|
||||
|
||||
Impl(Impl const&) = delete;
|
||||
const Impl& operator=(Impl const&) = delete;
|
||||
|
||||
void PushEntry(Entry e) {
|
||||
std::lock_guard<std::mutex> lock(message_mutex);
|
||||
message_queue.Push(std::move(e));
|
||||
message_cv.notify_one();
|
||||
}
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
backends.push_back(std::move(backend));
|
||||
}
|
||||
|
||||
void RemoveBackend(const std::string& backend_name) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
auto it = std::remove_if(backends.begin(), backends.end(), [&backend_name](const auto& i) {
|
||||
return !strcmp(i->GetName(), backend_name.c_str());
|
||||
});
|
||||
backends.erase(it, backends.end());
|
||||
}
|
||||
|
||||
const Filter& GetGlobalFilter() const {
|
||||
return filter;
|
||||
}
|
||||
|
||||
void SetGlobalFilter(const Filter& f) {
|
||||
filter = f;
|
||||
}
|
||||
|
||||
Backend* GetBackend(const std::string& backend_name) {
|
||||
auto it = std::find_if(backends.begin(), backends.end(), [&backend_name](const auto& i) {
|
||||
return !strcmp(i->GetName(), backend_name.c_str());
|
||||
});
|
||||
if (it == backends.end())
|
||||
return nullptr;
|
||||
return it->get();
|
||||
}
|
||||
|
||||
private:
|
||||
Impl() {
|
||||
backend_thread = std::thread([&] {
|
||||
Entry entry;
|
||||
auto write_logs = [&](Entry& e) {
|
||||
std::lock_guard<std::mutex> lock(writing_mutex);
|
||||
for (const auto& backend : backends) {
|
||||
backend->Write(e);
|
||||
}
|
||||
};
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lock(message_mutex);
|
||||
message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
|
||||
if (!running) {
|
||||
break;
|
||||
}
|
||||
write_logs(entry);
|
||||
}
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
|
||||
// where a system is repeatedly spamming logs even on close.
|
||||
constexpr int MAX_LOGS_TO_WRITE = 100;
|
||||
int logs_written = 0;
|
||||
while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
|
||||
write_logs(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
running = false;
|
||||
message_cv.notify_one();
|
||||
backend_thread.join();
|
||||
}
|
||||
|
||||
std::atomic_bool running{true};
|
||||
std::mutex message_mutex, writing_mutex;
|
||||
std::condition_variable message_cv;
|
||||
std::thread backend_thread;
|
||||
std::vector<std::unique_ptr<Backend>> backends;
|
||||
Common::MPSCQueue<Log::Entry> message_queue;
|
||||
Filter filter;
|
||||
};
|
||||
|
||||
void ConsoleBackend::Write(const Entry& entry) {
|
||||
PrintMessage(entry);
|
||||
}
|
||||
|
||||
void ColorConsoleBackend::Write(const Entry& entry) {
|
||||
PrintColoredMessage(entry);
|
||||
}
|
||||
|
||||
// _SH_DENYWR allows read only access to the file for other programs.
|
||||
// It is #defined to 0 on other platforms
|
||||
FileBackend::FileBackend(const std::string& filename)
|
||||
: file(filename, "w", _SH_DENYWR), bytes_written(0) {}
|
||||
|
||||
void FileBackend::Write(const Entry& entry) {
|
||||
// prevent logs from going over the maximum size (in case its spamming and the user doesn't
|
||||
// know)
|
||||
constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
|
||||
if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
|
||||
return;
|
||||
}
|
||||
bytes_written += file.WriteString(FormatLogMessage(entry) + '\n');
|
||||
if (entry.log_level >= Level::Error) {
|
||||
file.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||
#define ALL_LOG_CLASSES() \
|
||||
CLS(Log) \
|
||||
@@ -38,16 +164,19 @@ namespace Log {
|
||||
SUB(Service, AM) \
|
||||
SUB(Service, AOC) \
|
||||
SUB(Service, APM) \
|
||||
SUB(Service, BCAT) \
|
||||
SUB(Service, Fatal) \
|
||||
SUB(Service, Friend) \
|
||||
SUB(Service, FS) \
|
||||
SUB(Service, HID) \
|
||||
SUB(Service, LM) \
|
||||
SUB(Service, MM) \
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NS) \
|
||||
SUB(Service, NVDRV) \
|
||||
SUB(Service, PCTL) \
|
||||
SUB(Service, PREPO) \
|
||||
SUB(Service, SET) \
|
||||
SUB(Service, SM) \
|
||||
SUB(Service, SPL) \
|
||||
@@ -125,35 +254,32 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign
|
||||
return entry;
|
||||
}
|
||||
|
||||
static Filter* filter = nullptr;
|
||||
|
||||
void SetFilter(Filter* new_filter) {
|
||||
filter = new_filter;
|
||||
void SetGlobalFilter(const Filter& filter) {
|
||||
Impl::Instance().SetGlobalFilter(filter);
|
||||
}
|
||||
|
||||
void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, const char* format, ...) {
|
||||
if (filter && !filter->CheckMessage(log_class, log_level))
|
||||
return;
|
||||
std::array<char, 4 * 1024> formatting_buffer;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args);
|
||||
va_end(args);
|
||||
Entry entry = CreateEntry(log_class, log_level, filename, line_num, function,
|
||||
std::string(formatting_buffer.data()));
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
Impl::Instance().AddBackend(std::move(backend));
|
||||
}
|
||||
|
||||
PrintColoredMessage(entry);
|
||||
void RemoveBackend(const std::string& backend_name) {
|
||||
Impl::Instance().RemoveBackend(backend_name);
|
||||
}
|
||||
|
||||
Backend* GetBackend(const std::string& backend_name) {
|
||||
return Impl::Instance().GetBackend(backend_name);
|
||||
}
|
||||
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
const fmt::format_args& args) {
|
||||
if (filter && !filter->CheckMessage(log_class, log_level))
|
||||
auto filter = Impl::Instance().GetGlobalFilter();
|
||||
if (!filter.CheckMessage(log_class, log_level))
|
||||
return;
|
||||
|
||||
Entry entry =
|
||||
CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
|
||||
|
||||
PrintColoredMessage(entry);
|
||||
Impl::Instance().PushEntry(std::move(entry));
|
||||
}
|
||||
} // namespace Log
|
||||
} // namespace Log
|
||||
@@ -1,13 +1,15 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Log {
|
||||
@@ -34,6 +36,80 @@ struct Entry {
|
||||
Entry& operator=(const Entry& o) = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for logging backends. As loggers can be created and removed at runtime, this can be
|
||||
* used by a frontend for adding a custom logging backend as needed
|
||||
*/
|
||||
class Backend {
|
||||
public:
|
||||
virtual ~Backend() = default;
|
||||
virtual void SetFilter(const Filter& new_filter) {
|
||||
filter = new_filter;
|
||||
}
|
||||
virtual const char* GetName() const = 0;
|
||||
virtual void Write(const Entry& entry) = 0;
|
||||
|
||||
private:
|
||||
Filter filter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to stderr without any color commands
|
||||
*/
|
||||
class ConsoleBackend : public Backend {
|
||||
public:
|
||||
static const char* Name() {
|
||||
return "console";
|
||||
}
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to stderr and with color
|
||||
*/
|
||||
class ColorConsoleBackend : public Backend {
|
||||
public:
|
||||
static const char* Name() {
|
||||
return "color_console";
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
void Write(const Entry& entry) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend that writes to a file passed into the constructor
|
||||
*/
|
||||
class FileBackend : public Backend {
|
||||
public:
|
||||
explicit FileBackend(const std::string& filename);
|
||||
|
||||
static const char* Name() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return Name();
|
||||
}
|
||||
|
||||
void Write(const Entry& entry) override;
|
||||
|
||||
private:
|
||||
FileUtil::IOFile file;
|
||||
size_t bytes_written;
|
||||
};
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend);
|
||||
|
||||
void RemoveBackend(const std::string& backend_name);
|
||||
|
||||
Backend* GetBackend(const std::string& backend_name);
|
||||
|
||||
/**
|
||||
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
||||
* instead of underscores as in the enumeration.
|
||||
@@ -49,5 +125,10 @@ const char* GetLevelName(Level log_level);
|
||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
|
||||
const char* function, std::string message);
|
||||
|
||||
void SetFilter(Filter* filter);
|
||||
} // namespace Log
|
||||
/**
|
||||
* The global filter will prevent any messages from even being processed if they are filtered. Each
|
||||
* backend can have a filter, but if the level is lower than the global filter, the backend will
|
||||
* never get the message
|
||||
*/
|
||||
void SetGlobalFilter(const Filter& filter);
|
||||
} // namespace Log
|
||||
@@ -65,14 +65,14 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
|
||||
const std::string::const_iterator end) {
|
||||
auto level_separator = std::find(begin, end, ':');
|
||||
if (level_separator == end) {
|
||||
NGLOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
|
||||
std::string(begin, end).c_str());
|
||||
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}",
|
||||
std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
NGLOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
|
||||
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin,
|
||||
|
||||
const Class log_class = GetClassByName(begin, level_separator);
|
||||
if (log_class == Class::Count) {
|
||||
NGLOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
|
||||
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,16 +55,19 @@ enum class Class : ClassType {
|
||||
Service_AOC, ///< The AOC (AddOn Content) service
|
||||
Service_APM, ///< The APM (Performance) service
|
||||
Service_Audio, ///< The Audio (Audio control) service
|
||||
Service_BCAT, ///< The BCAT service
|
||||
Service_Fatal, ///< The Fatal service
|
||||
Service_Friend, ///< The friend service
|
||||
Service_FS, ///< The FS (Filesystem) service
|
||||
Service_HID, ///< The HID (Human interface device) service
|
||||
Service_LM, ///< The LM (Logger) service
|
||||
Service_MM, ///< The MM (Multimedia) service
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NS, ///< The NS services
|
||||
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
||||
Service_PCTL, ///< The PCTL (Parental control) service
|
||||
Service_PREPO, ///< The PREPO (Play report) service
|
||||
Service_SET, ///< The SET (Settings) service
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
@@ -91,19 +94,6 @@ enum class Class : ClassType {
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
/// Logs a message to the global logger.
|
||||
void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function,
|
||||
#ifdef _MSC_VER
|
||||
_Printf_format_string_
|
||||
#endif
|
||||
const char* format,
|
||||
...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 6, 7)))
|
||||
#endif
|
||||
;
|
||||
|
||||
/// Logs a message to the global logger, using fmt
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
@@ -118,48 +108,26 @@ void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsig
|
||||
|
||||
} // namespace Log
|
||||
|
||||
#define LOG_GENERIC(log_class, log_level, ...) \
|
||||
::Log::LogMessage(log_class, log_level, __FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define LOG_TRACE(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Trace, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_TRACE(log_class, ...) (void(0))
|
||||
#endif
|
||||
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Debug, __VA_ARGS__)
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Info, __VA_ARGS__)
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Warning, __VA_ARGS__)
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Error, __VA_ARGS__)
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Critical, __VA_ARGS__)
|
||||
|
||||
// Define the fmt lib macros
|
||||
#ifdef _DEBUG
|
||||
#define NGLOG_TRACE(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#else
|
||||
#define NGLOG_TRACE(log_class, fmt, ...) (void(0))
|
||||
#define LOG_TRACE(log_class, fmt, ...) (void(0))
|
||||
#endif
|
||||
|
||||
#define NGLOG_DEBUG(log_class, ...) \
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_INFO(log_class, ...) \
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_WARNING(log_class, ...) \
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_ERROR(log_class, ...) \
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
#define NGLOG_CRITICAL(log_class, ...) \
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
|
||||
@@ -17,11 +17,6 @@ inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start
|
||||
return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T Clamp(const T val, const T& min, const T& max) {
|
||||
return std::max(min, std::min(max, val));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct Rectangle {
|
||||
T left;
|
||||
|
||||
@@ -113,7 +113,7 @@ void FreeMemoryPages(void* ptr, size_t size) {
|
||||
if (ptr) {
|
||||
#ifdef _WIN32
|
||||
if (!VirtualFree(ptr, 0, MEM_RELEASE))
|
||||
LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n%s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
munmap(ptr, size);
|
||||
#endif
|
||||
@@ -134,7 +134,7 @@ void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) {
|
||||
#ifdef _WIN32
|
||||
DWORD oldValue;
|
||||
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue))
|
||||
LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n%s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ);
|
||||
#endif
|
||||
@@ -145,7 +145,7 @@ void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) {
|
||||
DWORD oldValue;
|
||||
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE,
|
||||
&oldValue))
|
||||
LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n%s", GetLastErrorMsg());
|
||||
LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
mprotect(ptr, size,
|
||||
allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ);
|
||||
@@ -167,8 +167,7 @@ std::string MemUsage() {
|
||||
return "MemUsage Error";
|
||||
|
||||
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
|
||||
Ret = Common::StringFromFormat(
|
||||
"%s K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7).c_str());
|
||||
Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7));
|
||||
|
||||
CloseHandle(hProcess);
|
||||
return Ret;
|
||||
|
||||
@@ -25,7 +25,7 @@ ParamPackage::ParamPackage(const std::string& serialized) {
|
||||
std::vector<std::string> key_value;
|
||||
Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value);
|
||||
if (key_value.size() != 2) {
|
||||
LOG_ERROR(Common, "invalid key pair %s", pair.c_str());
|
||||
LOG_ERROR(Common, "invalid key pair {}", pair);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ std::string ParamPackage::Serialize() const {
|
||||
std::string ParamPackage::Get(const std::string& key, const std::string& default_value) const {
|
||||
auto pair = data.find(key);
|
||||
if (pair == data.end()) {
|
||||
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||
LOG_DEBUG(Common, "key '{}' not found", key);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
@@ -74,14 +74,14 @@ std::string ParamPackage::Get(const std::string& key, const std::string& default
|
||||
int ParamPackage::Get(const std::string& key, int default_value) const {
|
||||
auto pair = data.find(key);
|
||||
if (pair == data.end()) {
|
||||
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||
LOG_DEBUG(Common, "key '{}' not found", key);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stoi(pair->second);
|
||||
} catch (const std::logic_error&) {
|
||||
LOG_ERROR(Common, "failed to convert %s to int", pair->second.c_str());
|
||||
LOG_ERROR(Common, "failed to convert {} to int", pair->second);
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
@@ -89,14 +89,14 @@ int ParamPackage::Get(const std::string& key, int default_value) const {
|
||||
float ParamPackage::Get(const std::string& key, float default_value) const {
|
||||
auto pair = data.find(key);
|
||||
if (pair == data.end()) {
|
||||
LOG_DEBUG(Common, "key %s not found", key.c_str());
|
||||
LOG_DEBUG(Common, "key {} not found", key);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stof(pair->second);
|
||||
} catch (const std::logic_error&) {
|
||||
LOG_ERROR(Common, "failed to convert %s to float", pair->second.c_str());
|
||||
LOG_ERROR(Common, "failed to convert {} to float", pair->second);
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,76 +46,6 @@ bool AsciiToHex(const char* _szValue, u32& result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args) {
|
||||
int writtenCount;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// You would think *printf are simple, right? Iterate on each character,
|
||||
// if it's a format specifier handle it properly, etc.
|
||||
//
|
||||
// Nooooo. Not according to the C standard.
|
||||
//
|
||||
// According to the C99 standard (7.19.6.1 "The fprintf function")
|
||||
// The format shall be a multibyte character sequence
|
||||
//
|
||||
// Because some character encodings might have '%' signs in the middle of
|
||||
// a multibyte sequence (SJIS for example only specifies that the first
|
||||
// byte of a 2 byte sequence is "high", the second byte can be anything),
|
||||
// printf functions have to decode the multibyte sequences and try their
|
||||
// best to not screw up.
|
||||
//
|
||||
// Unfortunately, on Windows, the locale for most languages is not UTF-8
|
||||
// as we would need. Notably, for zh_TW, Windows chooses EUC-CN as the
|
||||
// locale, and completely fails when trying to decode UTF-8 as EUC-CN.
|
||||
//
|
||||
// On the other hand, the fix is simple: because we use UTF-8, no such
|
||||
// multibyte handling is required as we can simply assume that no '%' char
|
||||
// will be present in the middle of a multibyte sequence.
|
||||
//
|
||||
// This is why we lookup an ANSI (cp1252) locale here and use _vsnprintf_l.
|
||||
static locale_t c_locale = nullptr;
|
||||
if (!c_locale)
|
||||
c_locale = _create_locale(LC_ALL, ".1252");
|
||||
writtenCount = _vsnprintf_l(out, outsize, format, c_locale, args);
|
||||
#else
|
||||
writtenCount = vsnprintf(out, outsize, format, args);
|
||||
#endif
|
||||
|
||||
if (writtenCount > 0 && writtenCount < outsize) {
|
||||
out[writtenCount] = '\0';
|
||||
return true;
|
||||
} else {
|
||||
out[outsize - 1] = '\0';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string StringFromFormat(const char* format, ...) {
|
||||
va_list args;
|
||||
char* buf = nullptr;
|
||||
#ifdef _WIN32
|
||||
int required = 0;
|
||||
|
||||
va_start(args, format);
|
||||
required = _vscprintf(format, args);
|
||||
buf = new char[required + 1];
|
||||
CharArrayFromFormatV(buf, required + 1, format, args);
|
||||
va_end(args);
|
||||
|
||||
std::string temp = buf;
|
||||
delete[] buf;
|
||||
#else
|
||||
va_start(args, format);
|
||||
if (vasprintf(&buf, format, args) < 0)
|
||||
LOG_ERROR(Common, "Unable to allocate memory for string");
|
||||
va_end(args);
|
||||
|
||||
std::string temp = buf;
|
||||
free(buf);
|
||||
#endif
|
||||
return temp;
|
||||
}
|
||||
|
||||
// For Debugging. Read out an u8 array.
|
||||
std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces) {
|
||||
std::ostringstream oss;
|
||||
@@ -134,6 +64,10 @@ std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string StringFromBuffer(const std::vector<u8>& data) {
|
||||
return std::string(data.begin(), std::find(data.begin(), data.end(), '\0'));
|
||||
}
|
||||
|
||||
// Turns " hej " into "hej". Also handles tabs.
|
||||
std::string StripSpaces(const std::string& str) {
|
||||
const size_t s = str.find_first_not_of(" \t\r\n");
|
||||
@@ -347,7 +281,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
|
||||
|
||||
iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno));
|
||||
LOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
@@ -376,7 +310,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>&
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [%s]: %s", fromcode, strerror(errno));
|
||||
LOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -395,7 +329,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
|
||||
iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: %s", strerror(errno));
|
||||
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
@@ -424,7 +358,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [UTF-8]: %s", strerror(errno));
|
||||
LOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
@@ -20,21 +19,10 @@ std::string ToLower(std::string str);
|
||||
/// Make a string uppercase
|
||||
std::string ToUpper(std::string str);
|
||||
|
||||
std::string StringFromFormat(const char* format, ...);
|
||||
// Cheap!
|
||||
bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args);
|
||||
|
||||
template <size_t Count>
|
||||
inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
CharArrayFromFormatV(out, Count, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// Good
|
||||
std::string ArrayToString(const u8* data, size_t size, int line_len = 20, bool spaces = true);
|
||||
|
||||
std::string StringFromBuffer(const std::vector<u8>& data);
|
||||
|
||||
std::string StripSpaces(const std::string& s);
|
||||
std::string StripQuotes(const std::string& s);
|
||||
|
||||
|
||||
@@ -11,25 +11,6 @@
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
|
||||
// Support for C++11's thread_local keyword was surprisingly spotty in compilers until very
|
||||
// recently. Fortunately, thread local variables have been well supported for compilers for a while,
|
||||
// but with semantics supporting only POD types, so we can use a few defines to get some amount of
|
||||
// backwards compat support.
|
||||
// WARNING: This only works correctly with POD types.
|
||||
#if defined(__clang__)
|
||||
#if !__has_feature(cxx_thread_local)
|
||||
#define thread_local __thread
|
||||
#endif
|
||||
#elif defined(__GNUC__)
|
||||
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
|
||||
#define thread_local __thread
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
#if _MSC_VER < 1900
|
||||
#define thread_local __declspec(thread)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
int CurrentThreadId();
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <time.h>
|
||||
#include <ctime>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
// windows.h needs to be included before other windows headers
|
||||
@@ -104,8 +107,8 @@ std::string Timer::GetTimeElapsedFormatted() const {
|
||||
// Hours
|
||||
u32 Hours = Minutes / 60;
|
||||
|
||||
std::string TmpStr = StringFromFormat("%02i:%02i:%02i:%03i", Hours, Minutes % 60, Seconds % 60,
|
||||
Milliseconds % 1000);
|
||||
std::string TmpStr = fmt::format("{:02}:{:02}:{:02}:{:03}", Hours, Minutes % 60, Seconds % 60,
|
||||
Milliseconds % 1000);
|
||||
return TmpStr;
|
||||
}
|
||||
|
||||
@@ -165,11 +168,11 @@ std::string Timer::GetTimeFormatted() {
|
||||
#ifdef _WIN32
|
||||
struct timeb tp;
|
||||
(void)::ftime(&tp);
|
||||
return StringFromFormat("%s:%03i", tmp, tp.millitm);
|
||||
return fmt::format("{}:{:03}", tmp, tp.millitm);
|
||||
#else
|
||||
struct timeval t;
|
||||
(void)gettimeofday(&t, nullptr);
|
||||
return StringFromFormat("%s:%03d", tmp, (int)(t.tv_usec / 1000));
|
||||
return fmt::format("{}:{:03}", tmp, static_cast<int>(t.tv_usec / 1000));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ static inline Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w);
|
||||
template <typename T>
|
||||
class Vec2 {
|
||||
public:
|
||||
T x;
|
||||
T y;
|
||||
T x{};
|
||||
T y{};
|
||||
|
||||
Vec2() = default;
|
||||
Vec2(const T& _x, const T& _y) : x(_x), y(_y) {}
|
||||
@@ -192,9 +192,9 @@ inline float Vec2<float>::Normalize() {
|
||||
template <typename T>
|
||||
class Vec3 {
|
||||
public:
|
||||
T x;
|
||||
T y;
|
||||
T z;
|
||||
T x{};
|
||||
T y{};
|
||||
T z{};
|
||||
|
||||
Vec3() = default;
|
||||
Vec3(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {}
|
||||
@@ -392,10 +392,10 @@ typedef Vec3<float> Vec3f;
|
||||
template <typename T>
|
||||
class Vec4 {
|
||||
public:
|
||||
T x;
|
||||
T y;
|
||||
T z;
|
||||
T w;
|
||||
T x{};
|
||||
T y{};
|
||||
T z{};
|
||||
T w{};
|
||||
|
||||
Vec4() = default;
|
||||
Vec4(const T& _x, const T& _y, const T& _z, const T& _w) : x(_x), y(_y), z(_z), w(_w) {}
|
||||
|
||||
@@ -4,6 +4,8 @@ add_library(core STATIC
|
||||
arm/unicorn/arm_unicorn.h
|
||||
core.cpp
|
||||
core.h
|
||||
core_cpu.cpp
|
||||
core_cpu.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
file_sys/directory.h
|
||||
@@ -38,12 +40,12 @@ add_library(core STATIC
|
||||
hle/config_mem.h
|
||||
hle/ipc.h
|
||||
hle/ipc_helpers.h
|
||||
hle/kernel/address_arbiter.cpp
|
||||
hle/kernel/address_arbiter.h
|
||||
hle/kernel/client_port.cpp
|
||||
hle/kernel/client_port.h
|
||||
hle/kernel/client_session.cpp
|
||||
hle/kernel/client_session.h
|
||||
hle/kernel/condition_variable.cpp
|
||||
hle/kernel/condition_variable.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/event.cpp
|
||||
hle/kernel/event.h
|
||||
@@ -124,6 +126,12 @@ add_library(core STATIC
|
||||
hle/service/audio/audren_u.h
|
||||
hle/service/audio/codecctl.cpp
|
||||
hle/service/audio/codecctl.h
|
||||
hle/service/audio/hwopus.cpp
|
||||
hle/service/audio/hwopus.h
|
||||
hle/service/bcat/module.cpp
|
||||
hle/service/bcat/module.h
|
||||
hle/service/bcat/bcat.cpp
|
||||
hle/service/bcat/bcat.h
|
||||
hle/service/fatal/fatal.cpp
|
||||
hle/service/fatal/fatal.h
|
||||
hle/service/fatal/fatal_p.cpp
|
||||
@@ -144,6 +152,8 @@ add_library(core STATIC
|
||||
hle/service/hid/hid.h
|
||||
hle/service/lm/lm.cpp
|
||||
hle/service/lm/lm.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
hle/service/mm/mm_u.h
|
||||
hle/service/nifm/nifm.cpp
|
||||
hle/service/nifm/nifm.h
|
||||
hle/service/nifm/nifm_a.cpp
|
||||
@@ -171,6 +181,8 @@ add_library(core STATIC
|
||||
hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
|
||||
hle/service/nvdrv/devices/nvhost_gpu.cpp
|
||||
hle/service/nvdrv/devices/nvhost_gpu.h
|
||||
hle/service/nvdrv/devices/nvhost_nvdec.cpp
|
||||
hle/service/nvdrv/devices/nvhost_nvdec.h
|
||||
hle/service/nvdrv/devices/nvmap.cpp
|
||||
hle/service/nvdrv/devices/nvmap.h
|
||||
hle/service/nvdrv/interface.cpp
|
||||
@@ -183,10 +195,12 @@ add_library(core STATIC
|
||||
hle/service/nvflinger/buffer_queue.h
|
||||
hle/service/nvflinger/nvflinger.cpp
|
||||
hle/service/nvflinger/nvflinger.h
|
||||
hle/service/pctl/module.cpp
|
||||
hle/service/pctl/module.h
|
||||
hle/service/pctl/pctl.cpp
|
||||
hle/service/pctl/pctl.h
|
||||
hle/service/pctl/pctl_a.cpp
|
||||
hle/service/pctl/pctl_a.h
|
||||
hle/service/prepo/prepo.cpp
|
||||
hle/service/prepo/prepo.h
|
||||
hle/service/service.cpp
|
||||
hle/service/service.h
|
||||
hle/service/set/set.cpp
|
||||
@@ -247,12 +261,15 @@ add_library(core STATIC
|
||||
loader/linker.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/nca.cpp
|
||||
loader/nca.h
|
||||
loader/nro.cpp
|
||||
loader/nro.h
|
||||
loader/nso.cpp
|
||||
loader/nso.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
memory_setup.h
|
||||
perf_stats.cpp
|
||||
|
||||
@@ -55,8 +55,8 @@ public:
|
||||
}
|
||||
|
||||
void InterpreterFallback(u64 pc, size_t num_instructions) override {
|
||||
LOG_INFO(Core_ARM, "Unicorn fallback @ 0x%" PRIx64 " for %zu instructions (instr = %08x)",
|
||||
pc, num_instructions, MemoryReadCode(pc));
|
||||
LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc,
|
||||
num_instructions, MemoryReadCode(pc));
|
||||
|
||||
ARM_Interface::ThreadContext ctx;
|
||||
parent.SaveContext(ctx);
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
case Dynarmic::A64::Exception::Yield:
|
||||
return;
|
||||
default:
|
||||
ASSERT_MSG(false, "ExceptionRaised(exception = %zu, pc = %" PRIx64 ")",
|
||||
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})",
|
||||
static_cast<size_t>(exception), pc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,22 @@ LoadDll LoadDll::g_load_dll;
|
||||
#define CHECKED(expr) \
|
||||
do { \
|
||||
if (auto _cerr = (expr)) { \
|
||||
ASSERT_MSG(false, "Call " #expr " failed with error: %u (%s)\n", _cerr, \
|
||||
ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \
|
||||
uc_strerror(_cerr)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) {
|
||||
GDBStub::BreakpointAddress bkpt =
|
||||
GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute);
|
||||
if (GDBStub::IsMemoryBreak() ||
|
||||
(bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) {
|
||||
auto core = static_cast<ARM_Unicorn*>(user_data);
|
||||
core->RecordBreak(bkpt);
|
||||
uc_emu_stop(uc);
|
||||
}
|
||||
}
|
||||
|
||||
static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
|
||||
u32 esr{};
|
||||
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
|
||||
@@ -52,8 +63,8 @@ static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
|
||||
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
|
||||
void* user_data) {
|
||||
ARM_Interface::ThreadContext ctx{};
|
||||
Core::CPU().SaveContext(ctx);
|
||||
ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x%lx, pc=0x%lx, lr=0x%lx", addr,
|
||||
Core::CurrentArmInterface().SaveContext(ctx);
|
||||
ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr,
|
||||
ctx.pc, ctx.cpu_registers[30]);
|
||||
return {};
|
||||
}
|
||||
@@ -67,6 +78,10 @@ ARM_Unicorn::ARM_Unicorn() {
|
||||
uc_hook hook{};
|
||||
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, -1));
|
||||
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, 0, -1));
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, -1));
|
||||
last_bkpt_hit = false;
|
||||
}
|
||||
}
|
||||
|
||||
ARM_Unicorn::~ARM_Unicorn() {
|
||||
@@ -155,7 +170,11 @@ void ARM_Unicorn::SetTlsAddress(VAddr base) {
|
||||
}
|
||||
|
||||
void ARM_Unicorn::Run() {
|
||||
ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0));
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
ExecuteInstructions(std::max(4000000, 0));
|
||||
} else {
|
||||
ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Unicorn::Step() {
|
||||
@@ -168,6 +187,18 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
MICROPROFILE_SCOPE(ARM_Jit);
|
||||
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
|
||||
CoreTiming::AddTicks(num_instructions);
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
if (last_bkpt_hit) {
|
||||
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
|
||||
}
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
if (last_bkpt_hit) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
}
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Unicorn::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
@@ -233,3 +264,8 @@ void ARM_Unicorn::PrepareReschedule() {
|
||||
}
|
||||
|
||||
void ARM_Unicorn::ClearInstructionCache() {}
|
||||
|
||||
void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
||||
last_bkpt = bkpt;
|
||||
last_bkpt_hit = true;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <unicorn/unicorn.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
|
||||
class ARM_Unicorn final : public ARM_Interface {
|
||||
public:
|
||||
@@ -35,7 +36,10 @@ public:
|
||||
void Step() override;
|
||||
void ClearInstructionCache() override;
|
||||
void PageTableChanged() override{};
|
||||
void RecordBreak(GDBStub::BreakpointAddress bkpt);
|
||||
|
||||
private:
|
||||
uc_engine* uc{};
|
||||
GDBStub::BreakpointAddress last_bkpt{};
|
||||
bool last_bkpt_hit;
|
||||
};
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "common/logging/log.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#endif
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory_setup.h"
|
||||
@@ -26,11 +25,33 @@ namespace Core {
|
||||
|
||||
/*static*/ System System::s_instance;
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
/// Runs a CPU core while the system is powered on
|
||||
static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
|
||||
while (Core::System().GetInstance().IsPoweredOn()) {
|
||||
cpu_state->RunLoop(true);
|
||||
}
|
||||
}
|
||||
|
||||
Cpu& System::CurrentCpuCore() {
|
||||
// If multicore is enabled, use host thread to figure out the current CPU core
|
||||
if (Settings::values.use_multi_core) {
|
||||
const auto& search = thread_to_cpu.find(std::this_thread::get_id());
|
||||
ASSERT(search != thread_to_cpu.end());
|
||||
ASSERT(search->second);
|
||||
return *search->second;
|
||||
}
|
||||
|
||||
// Otherwise, use single-threaded mode active_core variable
|
||||
return *cpu_cores[active_core];
|
||||
}
|
||||
|
||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
status = ResultStatus::Success;
|
||||
if (!cpu_core) {
|
||||
return ResultStatus::ErrorNotInitialized;
|
||||
}
|
||||
|
||||
// Update thread_to_cpu in case Core 0 is run from a different host thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::HandlePacket();
|
||||
@@ -47,25 +68,14 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (Kernel::GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core_ARM, "Idling");
|
||||
CoreTiming::Idle();
|
||||
CoreTiming::Advance();
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
CoreTiming::Advance();
|
||||
if (tight_loop) {
|
||||
cpu_core->Run();
|
||||
} else {
|
||||
cpu_core->Step();
|
||||
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
|
||||
cpu_cores[active_core]->RunLoop(tight_loop);
|
||||
if (Settings::values.use_multi_core) {
|
||||
// Cores 1-3 are run on other threads in this mode
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HW::Update();
|
||||
Reschedule();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -77,14 +87,14 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||
app_loader = Loader::GetLoader(filepath);
|
||||
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for %s!", filepath.c_str());
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
return ResultStatus::ErrorGetLoader;
|
||||
}
|
||||
std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode =
|
||||
app_loader->LoadKernelSystemMode();
|
||||
|
||||
if (system_mode.second != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error %i)!",
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
|
||||
static_cast<int>(system_mode.second));
|
||||
|
||||
switch (system_mode.second) {
|
||||
@@ -101,7 +111,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||
|
||||
ResultStatus init_result{Init(emu_window, system_mode.first.get())};
|
||||
if (init_result != ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error %i)!",
|
||||
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
||||
static_cast<int>(init_result));
|
||||
System::Shutdown();
|
||||
return init_result;
|
||||
@@ -109,7 +119,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||
|
||||
const Loader::ResultStatus load_result{app_loader->Load(current_process)};
|
||||
if (Loader::ResultStatus::Success != load_result) {
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", static_cast<int>(load_result));
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
||||
System::Shutdown();
|
||||
|
||||
switch (load_result) {
|
||||
@@ -128,21 +138,26 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
|
||||
}
|
||||
|
||||
void System::PrepareReschedule() {
|
||||
cpu_core->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
CurrentCpuCore().PrepareReschedule();
|
||||
}
|
||||
|
||||
PerfStats::Results System::GetAndResetPerfStats() {
|
||||
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
void System::Reschedule() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return cpu_cores[core_index]->Scheduler();
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
Core::System::GetInstance().Scheduler().Reschedule();
|
||||
ARM_Interface& System::ArmInterface(size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return cpu_cores[core_index]->ArmInterface();
|
||||
}
|
||||
|
||||
Cpu& System::CpuCore(size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return *cpu_cores[core_index];
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||
@@ -152,31 +167,35 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||
|
||||
current_process = Kernel::Process::Create("main");
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
cpu_core = std::make_shared<ARM_Dynarmic>();
|
||||
#else
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
for (size_t index = 0; index < cpu_cores.size(); ++index) {
|
||||
cpu_cores[index] = std::make_shared<Cpu>(cpu_barrier, index);
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>();
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
HW::Init();
|
||||
Kernel::Init(system_mode);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get());
|
||||
Service::Init();
|
||||
Service::Init(service_manager);
|
||||
GDBStub::Init();
|
||||
|
||||
if (!VideoCore::Init(emu_window)) {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
// CPU core 0 is run on the main thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
if (Settings::values.use_multi_core) {
|
||||
for (size_t index = 0; index < cpu_core_threads.size(); ++index) {
|
||||
cpu_core_threads[index] =
|
||||
std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
|
||||
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
@@ -200,17 +219,41 @@ void System::Shutdown() {
|
||||
VideoCore::Shutdown();
|
||||
GDBStub::Shutdown();
|
||||
Service::Shutdown();
|
||||
scheduler = nullptr;
|
||||
Kernel::Shutdown();
|
||||
HW::Shutdown();
|
||||
telemetry_session = nullptr;
|
||||
gpu_core = nullptr;
|
||||
cpu_core = nullptr;
|
||||
service_manager.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
// Close all CPU/threading state
|
||||
cpu_barrier->NotifyEnd();
|
||||
if (Settings::values.use_multi_core) {
|
||||
for (auto& thread : cpu_core_threads) {
|
||||
thread->join();
|
||||
thread.reset();
|
||||
}
|
||||
}
|
||||
thread_to_cpu.clear();
|
||||
for (auto& cpu_core : cpu_cores) {
|
||||
cpu_core.reset();
|
||||
}
|
||||
cpu_barrier.reset();
|
||||
|
||||
// Close core timing
|
||||
CoreTiming::Shutdown();
|
||||
|
||||
app_loader = nullptr;
|
||||
// Close app loader
|
||||
app_loader.reset();
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& System::ServiceManager() {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
const Service::SM::ServiceManager& System::ServiceManager() const {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/loader/loader.h"
|
||||
@@ -19,10 +22,16 @@
|
||||
class EmuWindow;
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System {
|
||||
public:
|
||||
~System();
|
||||
|
||||
/**
|
||||
* Gets the instance of the System singleton class.
|
||||
* @returns Reference to the instance of the System singleton class.
|
||||
@@ -83,7 +92,7 @@ public:
|
||||
* @returns True if the emulated system is powered on, otherwise false.
|
||||
*/
|
||||
bool IsPoweredOn() const {
|
||||
return cpu_core != nullptr;
|
||||
return cpu_barrier && cpu_barrier->IsAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,24 +106,34 @@ public:
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule();
|
||||
|
||||
/// Gets and resets core performance statistics
|
||||
PerfStats::Results GetAndResetPerfStats();
|
||||
|
||||
/**
|
||||
* Gets a reference to the emulated CPU.
|
||||
* @returns A reference to the emulated CPU.
|
||||
*/
|
||||
ARM_Interface& CPU() {
|
||||
return *cpu_core;
|
||||
/// Gets an ARM interface to the CPU core that is currently running
|
||||
ARM_Interface& CurrentArmInterface() {
|
||||
return CurrentCpuCore().ArmInterface();
|
||||
}
|
||||
|
||||
/// Gets an ARM interface to the CPU core with the specified index
|
||||
ARM_Interface& ArmInterface(size_t core_index);
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
Cpu& CpuCore(size_t core_index);
|
||||
|
||||
/// Gets the GPU interface
|
||||
Tegra::GPU& GPU() {
|
||||
return *gpu_core;
|
||||
}
|
||||
|
||||
Kernel::Scheduler& Scheduler() {
|
||||
return *scheduler;
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
Kernel::Scheduler& CurrentScheduler() {
|
||||
return *CurrentCpuCore().Scheduler();
|
||||
}
|
||||
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
|
||||
|
||||
/// Gets the current process
|
||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
|
||||
return current_process;
|
||||
}
|
||||
@@ -137,6 +156,9 @@ public:
|
||||
return *app_loader;
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& ServiceManager();
|
||||
const Service::SM::ServiceManager& ServiceManager() const;
|
||||
|
||||
void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
|
||||
debug_context = std::move(context);
|
||||
}
|
||||
@@ -146,6 +168,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
/// Returns the currently running CPU core
|
||||
Cpu& CurrentCpuCore();
|
||||
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
* @param emu_window Pointer to the host-system window used for video output and keyboard input.
|
||||
@@ -154,22 +179,18 @@ private:
|
||||
*/
|
||||
ResultStatus Init(EmuWindow* emu_window, u32 system_mode);
|
||||
|
||||
/// Reschedule the core emulation
|
||||
void Reschedule();
|
||||
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
|
||||
std::shared_ptr<ARM_Interface> cpu_core;
|
||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
|
||||
size_t active_core{}; ///< Active core, only used in single thread mode
|
||||
|
||||
/// When true, signals that a reschedule should happen
|
||||
bool reschedule_pending{};
|
||||
/// Service manager
|
||||
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
||||
|
||||
/// Telemetry session for this emulation session
|
||||
std::unique_ptr<Core::TelemetrySession> telemetry_session;
|
||||
@@ -178,10 +199,13 @@ private:
|
||||
|
||||
ResultStatus status = ResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
|
||||
/// Map of guest threads to CPU cores
|
||||
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
|
||||
};
|
||||
|
||||
inline ARM_Interface& CPU() {
|
||||
return System::GetInstance().CPU();
|
||||
inline ARM_Interface& CurrentArmInterface() {
|
||||
return System::GetInstance().CurrentArmInterface();
|
||||
}
|
||||
|
||||
inline TelemetrySession& Telemetry() {
|
||||
|
||||
119
src/core/core_cpu.cpp
Normal file
119
src/core/core_cpu.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#endif
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
void CpuBarrier::NotifyEnd() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
end = true;
|
||||
condition.notify_all();
|
||||
}
|
||||
|
||||
bool CpuBarrier::Rendezvous() {
|
||||
if (!Settings::values.use_multi_core) {
|
||||
// Meaningless when running in single-core mode
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!end) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
--cores_waiting;
|
||||
if (!cores_waiting) {
|
||||
cores_waiting = NUM_CPU_CORES;
|
||||
condition.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
condition.wait(lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index)
|
||||
: cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_shared<ARM_Dynarmic>();
|
||||
#else
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
arm_interface = std::make_shared<ARM_Unicorn>();
|
||||
}
|
||||
|
||||
scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get());
|
||||
}
|
||||
|
||||
void Cpu::RunLoop(bool tight_loop) {
|
||||
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
|
||||
if (!cpu_barrier->Rendezvous()) {
|
||||
// If rendezvous failed, session has been killed
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (Kernel::GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core, "Core-{} idling", core_index);
|
||||
|
||||
if (IsMainCore()) {
|
||||
CoreTiming::Idle();
|
||||
CoreTiming::Advance();
|
||||
}
|
||||
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
if (IsMainCore()) {
|
||||
CoreTiming::Advance();
|
||||
}
|
||||
|
||||
if (tight_loop) {
|
||||
arm_interface->Run();
|
||||
} else {
|
||||
arm_interface->Step();
|
||||
}
|
||||
}
|
||||
|
||||
Reschedule();
|
||||
}
|
||||
|
||||
void Cpu::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
void Cpu::PrepareReschedule() {
|
||||
arm_interface->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
}
|
||||
|
||||
void Cpu::Reschedule() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
scheduler->Reschedule();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
78
src/core/core_cpu.h
Normal file
78
src/core/core_cpu.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Kernel {
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr unsigned NUM_CPU_CORES{4};
|
||||
|
||||
class CpuBarrier {
|
||||
public:
|
||||
bool IsAlive() const {
|
||||
return !end;
|
||||
}
|
||||
|
||||
void NotifyEnd();
|
||||
|
||||
bool Rendezvous();
|
||||
|
||||
private:
|
||||
unsigned cores_waiting{NUM_CPU_CORES};
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
std::atomic<bool> end{};
|
||||
};
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index);
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
|
||||
void SingleStep();
|
||||
|
||||
void PrepareReschedule();
|
||||
|
||||
ARM_Interface& ArmInterface() {
|
||||
return *arm_interface;
|
||||
}
|
||||
|
||||
const ARM_Interface& ArmInterface() const {
|
||||
return *arm_interface;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
bool IsMainCore() const {
|
||||
return core_index == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void Reschedule();
|
||||
|
||||
std::shared_ptr<ARM_Interface> arm_interface;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::shared_ptr<Kernel::Scheduler> scheduler;
|
||||
|
||||
bool reschedule_pending{};
|
||||
size_t core_index;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -57,7 +58,8 @@ static u64 event_fifo_id;
|
||||
// to the event_queue by the emu thread
|
||||
static Common::MPSCQueue<Event, false> ts_queue;
|
||||
|
||||
static constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
|
||||
|
||||
static s64 idled_cycles;
|
||||
|
||||
@@ -70,11 +72,59 @@ static EventType* ev_lost = nullptr;
|
||||
|
||||
static void EmptyTimedCallback(u64 userdata, s64 cyclesLate) {}
|
||||
|
||||
s64 usToCycles(s64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * us) / 1000000;
|
||||
}
|
||||
|
||||
s64 usToCycles(u64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(s64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (ns / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * ns) / 1000000000;
|
||||
}
|
||||
|
||||
s64 nsToCycles(u64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
EventType* RegisterEvent(const std::string& name, TimedCallback callback) {
|
||||
// check for existing type with same name.
|
||||
// we want event type names to remain unique so that we can use them for serialization.
|
||||
ASSERT_MSG(event_types.find(name) == event_types.end(),
|
||||
"CoreTiming Event \"%s\" is already registered. Events should only be registered "
|
||||
"CoreTiming Event \"{}\" is already registered. Events should only be registered "
|
||||
"during Init to avoid breaking save states.",
|
||||
name.c_str());
|
||||
|
||||
|
||||
@@ -18,15 +18,14 @@
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
@@ -49,29 +48,9 @@ inline s64 usToCycles(int us) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us) / 1000000);
|
||||
}
|
||||
|
||||
inline s64 usToCycles(s64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * us) / 1000000;
|
||||
}
|
||||
s64 usToCycles(s64 us);
|
||||
|
||||
inline s64 usToCycles(u64 us) {
|
||||
if (us / 1000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (us > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(us / 1000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(us)) / 1000000;
|
||||
}
|
||||
s64 usToCycles(u64 us);
|
||||
|
||||
inline s64 nsToCycles(float ns) {
|
||||
return static_cast<s64>(BASE_CLOCK_RATE * (0.000000001f) * ns);
|
||||
@@ -81,29 +60,9 @@ inline s64 nsToCycles(int ns) {
|
||||
return BASE_CLOCK_RATE * static_cast<s64>(ns) / 1000000000;
|
||||
}
|
||||
|
||||
inline s64 nsToCycles(s64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (ns / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * ns) / 1000000000;
|
||||
}
|
||||
s64 nsToCycles(s64 ns);
|
||||
|
||||
inline s64 nsToCycles(u64 ns) {
|
||||
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return BASE_CLOCK_RATE * (static_cast<s64>(ns) / 1000000000);
|
||||
}
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
s64 nsToCycles(u64 ns);
|
||||
|
||||
inline u64 cyclesToNs(s64 cycles) {
|
||||
return cycles * 1000000000 / BASE_CLOCK_RATE;
|
||||
@@ -117,8 +76,6 @@ inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
namespace CoreTiming {
|
||||
|
||||
/**
|
||||
* CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
|
||||
* required to end slice -1 and start slice 0 before the first cycle of code is executed.
|
||||
|
||||
@@ -67,10 +67,16 @@ ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
ResultCode Disk_FileSystem::RenameFile(const std::string& src_path,
|
||||
const std::string& dest_path) const {
|
||||
const std::string full_src_path = base_directory + src_path;
|
||||
const std::string full_dest_path = base_directory + dest_path;
|
||||
|
||||
if (!FileUtil::Exists(full_src_path)) {
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
return FileUtil::Rename(full_src_path, full_dest_path) ? RESULT_SUCCESS : ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const {
|
||||
@@ -114,7 +120,7 @@ ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", full_path.c_str());
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", full_path);
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
@@ -157,7 +163,7 @@ ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& p
|
||||
}
|
||||
|
||||
ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
|
||||
LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length);
|
||||
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
|
||||
file->Seek(offset, SEEK_SET);
|
||||
return MakeResult<size_t>(file->ReadBytes(buffer, length));
|
||||
}
|
||||
@@ -183,7 +189,7 @@ bool Disk_Storage::SetSize(const u64 size) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Disk_Directory::Disk_Directory(const std::string& path) : directory() {
|
||||
Disk_Directory::Disk_Directory(const std::string& path) {
|
||||
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
|
||||
directory.size = size;
|
||||
directory.isDirectory = true;
|
||||
@@ -198,8 +204,7 @@ u64 Disk_Directory::Read(const u64 count, Entry* entries) {
|
||||
const std::string& filename = file.virtualName;
|
||||
Entry& entry = entries[entries_read];
|
||||
|
||||
LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size,
|
||||
file.isDirectory);
|
||||
LOG_TRACE(Service_FS, "File {}: size={} dir={}", filename, file.size, file.isDirectory);
|
||||
|
||||
// TODO(Link Mauve): use a proper conversion to UTF-16.
|
||||
for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
|
||||
Mode mode) const override;
|
||||
ResultCode DeleteFile(const std::string& path) const override;
|
||||
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
|
||||
ResultCode DeleteDirectory(const Path& path) const override;
|
||||
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||
ResultCode CreateFile(const std::string& path, u64 size) const override;
|
||||
@@ -43,7 +43,7 @@ protected:
|
||||
|
||||
class Disk_Storage : public StorageBackend {
|
||||
public:
|
||||
Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {}
|
||||
explicit Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {}
|
||||
|
||||
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
|
||||
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
|
||||
@@ -60,7 +60,7 @@ private:
|
||||
|
||||
class Disk_Directory : public DirectoryBackend {
|
||||
public:
|
||||
Disk_Directory(const std::string& path);
|
||||
explicit Disk_Directory(const std::string& path);
|
||||
|
||||
~Disk_Directory() override {
|
||||
Close();
|
||||
@@ -74,7 +74,6 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 total_entries_in_directory;
|
||||
FileUtil::FSTEntry directory;
|
||||
|
||||
// We need to remember the last entry we returned, so a subsequent call to Read will continue
|
||||
|
||||
@@ -126,7 +126,8 @@ public:
|
||||
* @param dest_path Destination path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
|
||||
virtual ResultCode RenameFile(const std::string& src_path,
|
||||
const std::string& dest_path) const = 0;
|
||||
|
||||
/**
|
||||
* Rename a Directory specified by its path
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -20,13 +19,20 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, siz
|
||||
if (file.GetSize() < sizeof(Header))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
file.Seek(offset, SEEK_SET);
|
||||
// For cartridges, HFSs can get very large, so we need to calculate the size up to
|
||||
// the actual content itself instead of just blindly reading in the entire file.
|
||||
Header pfs_header;
|
||||
if (!file.ReadBytes(&pfs_header, sizeof(Header)))
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
bool is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0);
|
||||
if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
|
||||
pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
|
||||
|
||||
size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
|
||||
size_t metadata_size =
|
||||
sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size;
|
||||
@@ -40,7 +46,7 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, siz
|
||||
|
||||
Loader::ResultStatus result = Load(file_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
LOG_ERROR(Service_FS, "Failed to load PFS from file %s!", file_path.c_str());
|
||||
LOG_ERROR(Service_FS, "Failed to load PFS from file {}!", file_path);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -51,7 +57,12 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data,
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
memcpy(&pfs_header, &file_data[offset], sizeof(Header));
|
||||
is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0);
|
||||
if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
|
||||
pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
|
||||
|
||||
size_t entries_offset = offset + sizeof(Header);
|
||||
size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
|
||||
@@ -74,21 +85,21 @@ u32 PartitionFilesystem::GetNumEntries() const {
|
||||
return pfs_header.num_entries;
|
||||
}
|
||||
|
||||
u64 PartitionFilesystem::GetEntryOffset(int index) const {
|
||||
u64 PartitionFilesystem::GetEntryOffset(u32 index) const {
|
||||
if (index > GetNumEntries())
|
||||
return 0;
|
||||
|
||||
return content_offset + pfs_entries[index].fs_entry.offset;
|
||||
}
|
||||
|
||||
u64 PartitionFilesystem::GetEntrySize(int index) const {
|
||||
u64 PartitionFilesystem::GetEntrySize(u32 index) const {
|
||||
if (index > GetNumEntries())
|
||||
return 0;
|
||||
|
||||
return pfs_entries[index].fs_entry.size;
|
||||
}
|
||||
|
||||
std::string PartitionFilesystem::GetEntryName(int index) const {
|
||||
std::string PartitionFilesystem::GetEntryName(u32 index) const {
|
||||
if (index > GetNumEntries())
|
||||
return "";
|
||||
|
||||
@@ -114,12 +125,12 @@ u64 PartitionFilesystem::GetFileSize(const std::string& name) const {
|
||||
}
|
||||
|
||||
void PartitionFilesystem::Print() const {
|
||||
NGLOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic.data());
|
||||
NGLOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries);
|
||||
LOG_DEBUG(Service_FS, "Magic: {}", pfs_header.magic);
|
||||
LOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries);
|
||||
for (u32 i = 0; i < pfs_header.num_entries; i++) {
|
||||
NGLOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i,
|
||||
pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size,
|
||||
GetFileOffset(pfs_entries[i].name));
|
||||
LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i,
|
||||
pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size,
|
||||
GetFileOffset(pfs_entries[i].name));
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -27,9 +27,9 @@ public:
|
||||
Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0);
|
||||
|
||||
u32 GetNumEntries() const;
|
||||
u64 GetEntryOffset(int index) const;
|
||||
u64 GetEntrySize(int index) const;
|
||||
std::string GetEntryName(int index) const;
|
||||
u64 GetEntryOffset(u32 index) const;
|
||||
u64 GetEntrySize(u32 index) const;
|
||||
std::string GetEntryName(u32 index) const;
|
||||
u64 GetFileOffset(const std::string& name) const;
|
||||
u64 GetFileSize(const std::string& name) const;
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Header {
|
||||
std::array<char, 4> magic;
|
||||
u32_le magic;
|
||||
u32_le num_entries;
|
||||
u32_le strtab_size;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
@@ -22,7 +21,7 @@ Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) {
|
||||
|
||||
Loader::ResultStatus result = Load(file_data);
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
LOG_ERROR(Service_FS, "Failed to load NPDM from file %s!", file_path.c_str());
|
||||
LOG_ERROR(Service_FS, "Failed to load NPDM from file {}!", file_path);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -77,13 +76,13 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
|
||||
}
|
||||
|
||||
void ProgramMetadata::Print() const {
|
||||
LOG_DEBUG(Service_FS, "Magic: %.4s", npdm_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Main thread priority: 0x%02x", npdm_header.main_thread_priority);
|
||||
LOG_DEBUG(Service_FS, "Main thread core: %u", npdm_header.main_thread_cpu);
|
||||
LOG_DEBUG(Service_FS, "Main thread stack size: 0x%x bytes", npdm_header.main_stack_size);
|
||||
LOG_DEBUG(Service_FS, "Process category: %u", npdm_header.process_category);
|
||||
LOG_DEBUG(Service_FS, "Flags: %02x", npdm_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > 64-bit instructions: %s",
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority);
|
||||
LOG_DEBUG(Service_FS, "Main thread core: {}", npdm_header.main_thread_cpu);
|
||||
LOG_DEBUG(Service_FS, "Main thread stack size: 0x{:X} bytes", npdm_header.main_stack_size);
|
||||
LOG_DEBUG(Service_FS, "Process category: {}", npdm_header.process_category);
|
||||
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", npdm_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > 64-bit instructions: {}",
|
||||
npdm_header.has_64_bit_instructions ? "YES" : "NO");
|
||||
|
||||
auto address_space = "Unknown";
|
||||
@@ -96,19 +95,19 @@ void ProgramMetadata::Print() const {
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, " > Address space: %s\n", address_space);
|
||||
LOG_DEBUG(Service_FS, " > Address space: {}\n", address_space);
|
||||
|
||||
// Begin ACID printing (potential perms, signed)
|
||||
LOG_DEBUG(Service_FS, "Magic: %.4s", acid_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Flags: %02x", acid_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > Is Retail: %s", acid_header.is_retail ? "YES" : "NO");
|
||||
LOG_DEBUG(Service_FS, "Title ID Min: %016" PRIX64, acid_header.title_id_min);
|
||||
LOG_DEBUG(Service_FS, "Title ID Max: %016" PRIX64, acid_header.title_id_max);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", acid_file_access.permissions);
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags);
|
||||
LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO");
|
||||
LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min);
|
||||
LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
|
||||
|
||||
// Begin ACI0 printing (actual perms, unsigned)
|
||||
LOG_DEBUG(Service_FS, "Magic: %.4s", aci_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Title ID: %016" PRIX64, aci_header.title_id);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", aci_file_access.permissions);
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data());
|
||||
LOG_DEBUG(Service_FS, "Title ID: 0x{:016X}", aci_header.title_id);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", aci_file_access.permissions);
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -24,13 +24,13 @@ ResultVal<std::unique_ptr<FileSystemBackend>> RomFS_Factory::Open(const Path& pa
|
||||
}
|
||||
|
||||
ResultCode RomFS_Factory::Format(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str());
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> RomFS_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
@@ -21,50 +21,47 @@ ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std:
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName());
|
||||
// TODO(bunnei): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path,
|
||||
const std::string& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName());
|
||||
// TODO(bunnei): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).",
|
||||
GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
@@ -81,13 +78,13 @@ u64 RomFS_FileSystem::GetFreeSpaceSize() const {
|
||||
}
|
||||
|
||||
ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path %s).", path.c_str());
|
||||
LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path);
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
|
||||
LOG_TRACE(Service_FS, "called offset=%llu, length=%zu", offset, length);
|
||||
LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
|
||||
romfs_file->Seek(data_offset + offset, SEEK_SET);
|
||||
size_t read_length = (size_t)std::min((u64)length, data_size - offset);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
|
||||
Mode mode) const override;
|
||||
ResultCode DeleteFile(const std::string& path) const override;
|
||||
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
|
||||
ResultCode DeleteDirectory(const Path& path) const override;
|
||||
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||
ResultCode CreateFile(const std::string& path, u64 size) const override;
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/disk_filesystem.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
@@ -30,7 +28,7 @@ ResultVal<std::unique_ptr<FileSystemBackend>> SaveData_Factory::Open(const Path&
|
||||
}
|
||||
|
||||
ResultCode SaveData_Factory::Format(const Path& path) {
|
||||
LOG_WARNING(Service_FS, "Format archive %s", GetName().c_str());
|
||||
LOG_WARNING(Service_FS, "Format archive {}", GetName());
|
||||
// Create the save data directory.
|
||||
if (!FileUtil::CreateFullPath(GetFullPath())) {
|
||||
// TODO(Subv): Find the correct error code.
|
||||
@@ -41,7 +39,7 @@ ResultCode SaveData_Factory::Format(const Path& path) {
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> SaveData_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
@@ -50,8 +48,7 @@ std::string SaveData_Factory::GetFullPath() const {
|
||||
u64 title_id = Core::CurrentProcess()->program_id;
|
||||
// TODO(Subv): Somehow obtain this value.
|
||||
u32 user = 0;
|
||||
return Common::StringFromFormat("%ssave/%016" PRIX64 "/%08X/", nand_directory.c_str(), title_id,
|
||||
user);
|
||||
return fmt::format("{}save/{:016X}/{:08X}/", nand_directory, title_id, user);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -26,13 +25,13 @@ ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& pat
|
||||
}
|
||||
|
||||
ResultCode SDMC_Factory::Format(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str());
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive {}", GetName());
|
||||
// TODO(Subv): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive {}", GetName());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ template <typename InputDeviceType>
|
||||
void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
|
||||
auto pair = std::make_pair(name, std::move(factory));
|
||||
if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
|
||||
LOG_ERROR(Input, "Factory %s already registered", name.c_str());
|
||||
LOG_ERROR(Input, "Factory '{}' already registered", name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDevic
|
||||
template <typename InputDeviceType>
|
||||
void UnregisterFactory(const std::string& name) {
|
||||
if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
|
||||
LOG_ERROR(Input, "Factory %s not registered", name.c_str());
|
||||
LOG_ERROR(Input, "Factory '{}' not registered", name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
|
||||
const auto pair = factory_list.find(engine);
|
||||
if (pair == factory_list.end()) {
|
||||
if (engine != "null") {
|
||||
LOG_ERROR(Input, "Unknown engine name: %s", engine.c_str());
|
||||
LOG_ERROR(Input, "Unknown engine name: {}", engine);
|
||||
}
|
||||
return std::make_unique<InputDeviceType>();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cinttypes>
|
||||
#include <climits>
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
@@ -33,9 +32,13 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -138,15 +141,17 @@ static u8 command_buffer[GDB_BUFFER_SIZE];
|
||||
static u32 command_length;
|
||||
|
||||
static u32 latest_signal = 0;
|
||||
static bool step_break = false;
|
||||
static bool memory_break = false;
|
||||
|
||||
static Kernel::Thread* current_thread = nullptr;
|
||||
|
||||
// Binding to a port within the reserved ports range (0-1023) requires root permissions,
|
||||
// so default to a port outside of that range.
|
||||
static u16 gdbstub_port = 24689;
|
||||
|
||||
static bool halt_loop = true;
|
||||
static bool step_loop = false;
|
||||
static bool send_trap = false;
|
||||
|
||||
// If set to false, the server will never be started and no
|
||||
// gdbstub-related functions will be executed.
|
||||
@@ -166,6 +171,53 @@ static std::map<u64, Breakpoint> breakpoints_execute;
|
||||
static std::map<u64, Breakpoint> breakpoints_read;
|
||||
static std::map<u64, Breakpoint> breakpoints_write;
|
||||
|
||||
static Kernel::Thread* FindThreadById(int id) {
|
||||
for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto thread : threads) {
|
||||
if (thread->GetThreadId() == id) {
|
||||
current_thread = thread.get();
|
||||
return current_thread;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static u64 RegRead(int id, Kernel::Thread* thread = nullptr) {
|
||||
if (!thread) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (id < SP_REGISTER) {
|
||||
return thread->context.cpu_registers[id];
|
||||
} else if (id == SP_REGISTER) {
|
||||
return thread->context.sp;
|
||||
} else if (id == PC_REGISTER) {
|
||||
return thread->context.pc;
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
return thread->context.cpsr;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void RegWrite(int id, u64 val, Kernel::Thread* thread = nullptr) {
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id < SP_REGISTER) {
|
||||
thread->context.cpu_registers[id] = val;
|
||||
} else if (id == SP_REGISTER) {
|
||||
thread->context.sp = val;
|
||||
} else if (id == PC_REGISTER) {
|
||||
thread->context.pc = val;
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
thread->context.cpsr = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns hex string character into the equivalent byte.
|
||||
*
|
||||
@@ -180,7 +232,7 @@ static u8 HexCharToValue(u8 hex) {
|
||||
return hex - 'A' + 0xA;
|
||||
}
|
||||
|
||||
LOG_ERROR(Debug_GDBStub, "Invalid nibble: %c (%02x)\n", hex, hex);
|
||||
LOG_ERROR(Debug_GDBStub, "Invalid nibble: {} ({:02X})", hex, hex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -194,7 +246,7 @@ static u8 NibbleToHex(u8 n) {
|
||||
if (n < 0xA) {
|
||||
return '0' + n;
|
||||
} else {
|
||||
return 'A' + n - 0xA;
|
||||
return 'a' + n - 0xA;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +372,7 @@ static u8 ReadByte() {
|
||||
u8 c;
|
||||
size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL);
|
||||
if (received_size != 1) {
|
||||
LOG_ERROR(Debug_GDBStub, "recv failed : %ld", received_size);
|
||||
LOG_ERROR(Debug_GDBStub, "recv failed: {}", received_size);
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
@@ -361,8 +413,7 @@ static void RemoveBreakpoint(BreakpointType type, PAddr addr) {
|
||||
|
||||
auto bp = p.find(static_cast<u64>(addr));
|
||||
if (bp != p.end()) {
|
||||
LOG_DEBUG(Debug_GDBStub,
|
||||
"gdb: removed a breakpoint: %016" PRIx64 " bytes at %016" PRIx64 " of type %d\n",
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}",
|
||||
bp->second.len, bp->second.addr, static_cast<int>(type));
|
||||
p.erase(static_cast<u64>(addr));
|
||||
}
|
||||
@@ -409,8 +460,8 @@ bool CheckBreakpoint(PAddr addr, BreakpointType type) {
|
||||
|
||||
if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
|
||||
LOG_DEBUG(Debug_GDBStub,
|
||||
"Found breakpoint type %d @ %016" PRIx64 ", range: %016" PRIx64
|
||||
" - %016" PRIx64 " (%" PRIx64 " bytes)\n",
|
||||
"Found breakpoint type {} @ {:016X}, range: {:016X}"
|
||||
" - {:016X} ({:X} bytes)",
|
||||
static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len);
|
||||
return true;
|
||||
}
|
||||
@@ -441,6 +492,8 @@ static void SendReply(const char* reply) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "Reply: {}", reply);
|
||||
|
||||
memset(command_buffer, 0, sizeof(command_buffer));
|
||||
|
||||
command_length = static_cast<u32>(strlen(reply));
|
||||
@@ -473,7 +526,7 @@ static void SendReply(const char* reply) {
|
||||
|
||||
/// Handle query command from gdb client.
|
||||
static void HandleQuery() {
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: query '%s'\n", command_buffer + 1);
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: query '{}'", command_buffer + 1);
|
||||
|
||||
const char* query = reinterpret_cast<const char*>(command_buffer + 1);
|
||||
|
||||
@@ -485,6 +538,22 @@ static void HandleQuery() {
|
||||
} else if (strncmp(query, "Xfer:features:read:target.xml:",
|
||||
strlen("Xfer:features:read:target.xml:")) == 0) {
|
||||
SendReply(target_xml);
|
||||
} else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
|
||||
std::string buffer = fmt::format("TextSeg={:0x}", Memory::PROCESS_IMAGE_VADDR);
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
|
||||
std::string val = "m";
|
||||
for (int core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
auto threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto thread : threads) {
|
||||
val += fmt::format("{:x}", thread->GetThreadId());
|
||||
val += ",";
|
||||
}
|
||||
}
|
||||
val.pop_back();
|
||||
SendReply(val.c_str());
|
||||
} else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) {
|
||||
SendReply("l");
|
||||
} else {
|
||||
SendReply("");
|
||||
}
|
||||
@@ -492,11 +561,40 @@ static void HandleQuery() {
|
||||
|
||||
/// Handle set thread command from gdb client.
|
||||
static void HandleSetThread() {
|
||||
if (memcmp(command_buffer, "Hg0", 3) == 0 || memcmp(command_buffer, "Hc-1", 4) == 0 ||
|
||||
memcmp(command_buffer, "Hc0", 4) == 0 || memcmp(command_buffer, "Hc1", 4) == 0) {
|
||||
return SendReply("OK");
|
||||
if (memcmp(command_buffer, "Hc", 2) == 0 || memcmp(command_buffer, "Hg", 2) == 0) {
|
||||
int thread_id = -1;
|
||||
if (command_buffer[2] != '-') {
|
||||
thread_id = static_cast<int>(HexToInt(
|
||||
command_buffer + 2,
|
||||
command_length - 2 /*strlen(reinterpret_cast<char*>(command_buffer) + 2)*/));
|
||||
}
|
||||
if (thread_id >= 1) {
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (!current_thread) {
|
||||
thread_id = 1;
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (current_thread) {
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
SendReply("E01");
|
||||
}
|
||||
|
||||
/// Handle thread alive command from gdb client.
|
||||
static void HandleThreadAlive() {
|
||||
int thread_id = static_cast<int>(
|
||||
HexToInt(command_buffer + 1,
|
||||
command_length - 1 /*strlen(reinterpret_cast<char*>(command_buffer) + 1)*/));
|
||||
if (thread_id == 0) {
|
||||
thread_id = 1;
|
||||
}
|
||||
if (FindThreadById(thread_id)) {
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
SendReply("E01");
|
||||
}
|
||||
|
||||
@@ -505,15 +603,24 @@ static void HandleSetThread() {
|
||||
*
|
||||
* @param signal Signal to be sent to client.
|
||||
*/
|
||||
static void SendSignal(u32 signal) {
|
||||
static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
|
||||
if (gdbserver_socket == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
latest_signal = signal;
|
||||
|
||||
std::string buffer = Common::StringFromFormat("T%02x", latest_signal);
|
||||
LOG_DEBUG(Debug_GDBStub, "Response: %s", buffer.c_str());
|
||||
std::string buffer;
|
||||
if (full) {
|
||||
buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};", latest_signal, PC_REGISTER,
|
||||
Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER,
|
||||
Common::swap64(RegRead(SP_REGISTER, thread)));
|
||||
} else {
|
||||
buffer = fmt::format("T{:02x};", latest_signal);
|
||||
}
|
||||
|
||||
buffer += fmt::format("thread:{:x};", thread->GetThreadId());
|
||||
|
||||
SendReply(buffer.c_str());
|
||||
}
|
||||
|
||||
@@ -527,18 +634,18 @@ static void ReadCommand() {
|
||||
// ignore ack
|
||||
return;
|
||||
} else if (c == 0x03) {
|
||||
LOG_INFO(Debug_GDBStub, "gdb: found break command\n");
|
||||
LOG_INFO(Debug_GDBStub, "gdb: found break command");
|
||||
halt_loop = true;
|
||||
SendSignal(SIGTRAP);
|
||||
SendSignal(current_thread, SIGTRAP);
|
||||
return;
|
||||
} else if (c != GDB_STUB_START) {
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte %02x\n", c);
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02X}", c);
|
||||
return;
|
||||
}
|
||||
|
||||
while ((c = ReadByte()) != GDB_STUB_END) {
|
||||
if (command_length >= sizeof(command_buffer)) {
|
||||
LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow\n");
|
||||
LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow");
|
||||
SendPacket(GDB_STUB_NACK);
|
||||
return;
|
||||
}
|
||||
@@ -552,7 +659,7 @@ static void ReadCommand() {
|
||||
|
||||
if (checksum_received != checksum_calculated) {
|
||||
LOG_ERROR(Debug_GDBStub,
|
||||
"gdb: invalid checksum: calculated %02x and read %02x for $%s# (length: %d)\n",
|
||||
"gdb: invalid checksum: calculated {:02X} and read {:02X} for ${}# (length: {})",
|
||||
checksum_calculated, checksum_received, command_buffer, command_length);
|
||||
|
||||
command_length = 0;
|
||||
@@ -599,11 +706,11 @@ static void ReadRegister() {
|
||||
}
|
||||
|
||||
if (id <= SP_REGISTER) {
|
||||
LongToGdbHex(reply, Core::CPU().GetReg(static_cast<int>(id)));
|
||||
LongToGdbHex(reply, RegRead(id, current_thread));
|
||||
} else if (id == PC_REGISTER) {
|
||||
LongToGdbHex(reply, Core::CPU().GetPC());
|
||||
LongToGdbHex(reply, RegRead(id, current_thread));
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
IntToGdbHex(reply, Core::CPU().GetCPSR());
|
||||
IntToGdbHex(reply, (u32)RegRead(id, current_thread));
|
||||
} else {
|
||||
return SendReply("E01");
|
||||
}
|
||||
@@ -619,16 +726,16 @@ static void ReadRegisters() {
|
||||
u8* bufptr = buffer;
|
||||
|
||||
for (int reg = 0; reg <= SP_REGISTER; reg++) {
|
||||
LongToGdbHex(bufptr + reg * 16, Core::CPU().GetReg(reg));
|
||||
LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread));
|
||||
}
|
||||
|
||||
bufptr += (32 * 16);
|
||||
|
||||
LongToGdbHex(bufptr, Core::CPU().GetPC());
|
||||
LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread));
|
||||
|
||||
bufptr += 16;
|
||||
|
||||
IntToGdbHex(bufptr, Core::CPU().GetCPSR());
|
||||
IntToGdbHex(bufptr, (u32)RegRead(CPSR_REGISTER, current_thread));
|
||||
|
||||
bufptr += 8;
|
||||
|
||||
@@ -647,11 +754,11 @@ static void WriteRegister() {
|
||||
}
|
||||
|
||||
if (id <= SP_REGISTER) {
|
||||
Core::CPU().SetReg(id, GdbHexToLong(buffer_ptr));
|
||||
RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else if (id == PC_REGISTER) {
|
||||
Core::CPU().SetPC(GdbHexToLong(buffer_ptr));
|
||||
RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr));
|
||||
RegWrite(id, GdbHexToInt(buffer_ptr), current_thread);
|
||||
} else {
|
||||
return SendReply("E01");
|
||||
}
|
||||
@@ -668,11 +775,11 @@ static void WriteRegisters() {
|
||||
|
||||
for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) {
|
||||
if (reg <= SP_REGISTER) {
|
||||
Core::CPU().SetReg(reg, GdbHexToLong(buffer_ptr + i * 16));
|
||||
RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == PC_REGISTER) {
|
||||
Core::CPU().SetPC(GdbHexToLong(buffer_ptr + i * 16));
|
||||
RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread);
|
||||
} else if (reg == CPSR_REGISTER) {
|
||||
Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * 16));
|
||||
RegWrite(CPSR_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread);
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
@@ -693,7 +800,7 @@ static void ReadMemory() {
|
||||
u64 len =
|
||||
HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: addr: %016lx len: %016lx\n", addr, len);
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len);
|
||||
|
||||
if (len * 2 > sizeof(reply)) {
|
||||
SendReply("E01");
|
||||
@@ -735,7 +842,7 @@ static void WriteMemory() {
|
||||
void Break(bool is_memory_break) {
|
||||
if (!halt_loop) {
|
||||
halt_loop = true;
|
||||
SendSignal(SIGTRAP);
|
||||
send_trap = true;
|
||||
}
|
||||
|
||||
memory_break = is_memory_break;
|
||||
@@ -745,10 +852,10 @@ void Break(bool is_memory_break) {
|
||||
static void Step() {
|
||||
step_loop = true;
|
||||
halt_loop = true;
|
||||
step_break = true;
|
||||
SendSignal(SIGTRAP);
|
||||
send_trap = true;
|
||||
}
|
||||
|
||||
/// Tell the CPU if we hit a memory breakpoint.
|
||||
bool IsMemoryBreak() {
|
||||
if (IsConnected()) {
|
||||
return false;
|
||||
@@ -760,7 +867,6 @@ bool IsMemoryBreak() {
|
||||
/// Tell the CPU to continue executing.
|
||||
static void Continue() {
|
||||
memory_break = false;
|
||||
step_break = false;
|
||||
step_loop = false;
|
||||
halt_loop = false;
|
||||
}
|
||||
@@ -781,7 +887,7 @@ static bool CommitBreakpoint(BreakpointType type, PAddr addr, u64 len) {
|
||||
breakpoint.len = len;
|
||||
p.insert({addr, breakpoint});
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %016" PRIx64 " bytes at %016" PRIx64 "\n",
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}",
|
||||
static_cast<int>(type), breakpoint.len, breakpoint.addr);
|
||||
|
||||
return true;
|
||||
@@ -889,7 +995,7 @@ void HandlePacket() {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "Packet: %s", command_buffer);
|
||||
LOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer);
|
||||
|
||||
switch (command_buffer[0]) {
|
||||
case 'q':
|
||||
@@ -899,7 +1005,7 @@ void HandlePacket() {
|
||||
HandleSetThread();
|
||||
break;
|
||||
case '?':
|
||||
SendSignal(latest_signal);
|
||||
SendSignal(current_thread, latest_signal);
|
||||
break;
|
||||
case 'k':
|
||||
Shutdown();
|
||||
@@ -936,6 +1042,9 @@ void HandlePacket() {
|
||||
case 'Z':
|
||||
AddBreakpoint();
|
||||
break;
|
||||
case 'T':
|
||||
HandleThreadAlive();
|
||||
break;
|
||||
default:
|
||||
SendReply("");
|
||||
break;
|
||||
@@ -982,7 +1091,7 @@ static void Init(u16 port) {
|
||||
breakpoints_write.clear();
|
||||
|
||||
// Start gdb server
|
||||
LOG_INFO(Debug_GDBStub, "Starting GDB server on port %d...", port);
|
||||
LOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port);
|
||||
|
||||
sockaddr_in saddr_server = {};
|
||||
saddr_server.sin_family = AF_INET;
|
||||
@@ -1016,7 +1125,7 @@ static void Init(u16 port) {
|
||||
}
|
||||
|
||||
// Wait for gdb to connect
|
||||
LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...\n");
|
||||
LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...");
|
||||
sockaddr_in saddr_client;
|
||||
sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
|
||||
socklen_t client_addrlen = sizeof(saddr_client);
|
||||
@@ -1029,7 +1138,7 @@ static void Init(u16 port) {
|
||||
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client");
|
||||
} else {
|
||||
LOG_INFO(Debug_GDBStub, "Client connected.\n");
|
||||
LOG_INFO(Debug_GDBStub, "Client connected.");
|
||||
saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
@@ -1080,4 +1189,11 @@ bool GetCpuStepFlag() {
|
||||
void SetCpuStepFlag(bool is_step) {
|
||||
step_loop = is_step;
|
||||
}
|
||||
|
||||
void SendTrap(Kernel::Thread* thread, int trap) {
|
||||
if (send_trap) {
|
||||
send_trap = false;
|
||||
SendSignal(thread, trap);
|
||||
}
|
||||
}
|
||||
}; // namespace GDBStub
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace GDBStub {
|
||||
|
||||
@@ -91,4 +92,12 @@ bool GetCpuStepFlag();
|
||||
* @param is_step
|
||||
*/
|
||||
void SetCpuStepFlag(bool is_step);
|
||||
|
||||
/**
|
||||
* Send trap signal from thread back to the gdbstub server.
|
||||
*
|
||||
* @param thread Sending thread.
|
||||
* @param trap Trap no.
|
||||
*/
|
||||
void SendTrap(Kernel::Thread* thread, int trap);
|
||||
} // namespace GDBStub
|
||||
|
||||
@@ -29,9 +29,14 @@ enum class ControlCommand : u32 {
|
||||
};
|
||||
|
||||
enum class CommandType : u32 {
|
||||
Invalid = 0,
|
||||
LegacyRequest = 1,
|
||||
Close = 2,
|
||||
LegacyControl = 3,
|
||||
Request = 4,
|
||||
Control = 5,
|
||||
RequestWithContext = 6,
|
||||
ControlWithContext = 7,
|
||||
Unspecified,
|
||||
};
|
||||
|
||||
@@ -167,6 +172,7 @@ struct DomainMessageHeader {
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 8, CommandType> command;
|
||||
BitField<8, 8, u32_le> input_object_count;
|
||||
BitField<16, 16, u32_le> size;
|
||||
};
|
||||
u32_le object_id;
|
||||
|
||||
@@ -298,6 +298,13 @@ public:
|
||||
|
||||
template <typename T>
|
||||
Kernel::SharedPtr<T> GetCopyObject(size_t index);
|
||||
|
||||
template <class T>
|
||||
std::shared_ptr<T> PopIpcInterface() {
|
||||
ASSERT(context->Session()->IsDomain());
|
||||
ASSERT(context->GetDomainMessageHeader()->input_object_count > 0);
|
||||
return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
|
||||
}
|
||||
};
|
||||
|
||||
/// Pop ///
|
||||
|
||||
173
src/core/hle/kernel/address_arbiter.cpp
Normal file
173
src/core/hle/kernel/address_arbiter.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
namespace AddressArbiter {
|
||||
|
||||
// Performs actual address waiting logic.
|
||||
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->arb_wait_address = address;
|
||||
current_thread->status = THREADSTATUS_WAIT_ARB;
|
||||
current_thread->wakeup_callback = nullptr;
|
||||
|
||||
current_thread->WakeAfterDelay(timeout);
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
// Gets the threads waiting on an address.
|
||||
static void GetThreadsWaitingOnAddress(std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr address) {
|
||||
auto RetrieveWaitingThreads =
|
||||
[](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr arb_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
auto& thread_list = scheduler->GetThreadList();
|
||||
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->arb_wait_address == arb_addr)
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve a list of all threads that are waiting for this address.
|
||||
RetrieveWaitingThreads(0, waiting_threads, address);
|
||||
RetrieveWaitingThreads(1, waiting_threads, address);
|
||||
RetrieveWaitingThreads(2, waiting_threads, address);
|
||||
RetrieveWaitingThreads(3, waiting_threads, address);
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(waiting_threads.begin(), waiting_threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
});
|
||||
}
|
||||
|
||||
// Wake up num_to_wake (or all) threads in a vector.
|
||||
static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
|
||||
// Only process up to 'target' threads, unless 'target' is <= 0, in which case process
|
||||
// them all.
|
||||
size_t last = waiting_threads.size();
|
||||
if (num_to_wake > 0)
|
||||
last = num_to_wake;
|
||||
|
||||
// Signal the waiting threads.
|
||||
for (size_t i = 0; i < last; i++) {
|
||||
ASSERT(waiting_threads[i]->status = THREADSTATUS_WAIT_ARB);
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->arb_wait_address = 0;
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
|
||||
// Signals an address being waited on.
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake) {
|
||||
// Get threads waiting on the address.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads;
|
||||
GetThreadsWaitingOnAddress(waiting_threads, address);
|
||||
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Signals an address being waited on and increments its value if equal to the value argument.
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) == value) {
|
||||
Memory::Write32(address, static_cast<u32>(value + 1));
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return SignalToAddress(address, num_to_wake);
|
||||
}
|
||||
|
||||
// Signals an address being waited on and modifies its value based on waiting thread count if equal
|
||||
// to the value argument.
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
// Get threads waiting on the address.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads;
|
||||
GetThreadsWaitingOnAddress(waiting_threads, address);
|
||||
|
||||
// Determine the modified value depending on the waiting count.
|
||||
s32 updated_value;
|
||||
if (waiting_threads.size() == 0) {
|
||||
updated_value = value - 1;
|
||||
} else if (num_to_wake <= 0 || waiting_threads.size() <= num_to_wake) {
|
||||
updated_value = value + 1;
|
||||
} else {
|
||||
updated_value = value;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) == value) {
|
||||
Memory::Write32(address, static_cast<u32>(updated_value));
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Waits on an address if the value passed is less than the argument value, optionally decrementing.
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
s32 cur_value = static_cast<s32>(Memory::Read32(address));
|
||||
if (cur_value < value) {
|
||||
Memory::Write32(address, static_cast<u32>(cur_value - 1));
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
}
|
||||
|
||||
// Waits on an address if the value passed is equal to the argument value.
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
// Only wait for the address if equal.
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
}
|
||||
} // namespace AddressArbiter
|
||||
} // namespace Kernel
|
||||
32
src/core/hle/kernel/address_arbiter.h
Normal file
32
src/core/hle/kernel/address_arbiter.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
namespace AddressArbiter {
|
||||
enum class ArbitrationType {
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2,
|
||||
};
|
||||
|
||||
enum class SignalType {
|
||||
Signal = 0,
|
||||
IncrementAndSignalIfEqual = 1,
|
||||
ModifyByWaitingCountAndSignalIfEqual = 2,
|
||||
};
|
||||
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake);
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement);
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
|
||||
} // namespace AddressArbiter
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/kernel/condition_variable.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object_address_table.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
ConditionVariable::ConditionVariable() {}
|
||||
ConditionVariable::~ConditionVariable() {}
|
||||
|
||||
ResultVal<SharedPtr<ConditionVariable>> ConditionVariable::Create(VAddr guest_addr,
|
||||
std::string name) {
|
||||
SharedPtr<ConditionVariable> condition_variable(new ConditionVariable);
|
||||
|
||||
condition_variable->name = std::move(name);
|
||||
condition_variable->guest_addr = guest_addr;
|
||||
condition_variable->mutex_addr = 0;
|
||||
|
||||
// Condition variables are referenced by guest address, so track this in the kernel
|
||||
g_object_address_table.Insert(guest_addr, condition_variable);
|
||||
|
||||
return MakeResult<SharedPtr<ConditionVariable>>(std::move(condition_variable));
|
||||
}
|
||||
|
||||
bool ConditionVariable::ShouldWait(Thread* thread) const {
|
||||
return GetAvailableCount() <= 0;
|
||||
}
|
||||
|
||||
void ConditionVariable::Acquire(Thread* thread) {
|
||||
if (GetAvailableCount() <= 0)
|
||||
return;
|
||||
|
||||
SetAvailableCount(GetAvailableCount() - 1);
|
||||
}
|
||||
|
||||
ResultCode ConditionVariable::Release(s32 target) {
|
||||
if (target == -1) {
|
||||
// When -1, wake up all waiting threads
|
||||
SetAvailableCount(static_cast<s32>(GetWaitingThreads().size()));
|
||||
WakeupAllWaitingThreads();
|
||||
} else {
|
||||
// Otherwise, wake up just a single thread
|
||||
SetAvailableCount(target);
|
||||
WakeupWaitingThread(GetHighestPriorityReadyThread());
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
s32 ConditionVariable::GetAvailableCount() const {
|
||||
return Memory::Read32(guest_addr);
|
||||
}
|
||||
|
||||
void ConditionVariable::SetAvailableCount(s32 value) const {
|
||||
Memory::Write32(guest_addr, value);
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class ConditionVariable final : public WaitObject {
|
||||
public:
|
||||
/**
|
||||
* Creates a condition variable.
|
||||
* @param guest_addr Address of the object tracking the condition variable in guest memory. If
|
||||
* specified, this condition variable will update the guest object when its state changes.
|
||||
* @param name Optional name of condition variable.
|
||||
* @return The created condition variable.
|
||||
*/
|
||||
static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr,
|
||||
std::string name = "Unknown");
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "ConditionVariable";
|
||||
}
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
static const HandleType HANDLE_TYPE = HandleType::ConditionVariable;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
s32 GetAvailableCount() const;
|
||||
void SetAvailableCount(s32 value) const;
|
||||
|
||||
std::string name; ///< Name of condition variable (optional)
|
||||
VAddr guest_addr; ///< Address of the guest condition variable value
|
||||
VAddr mutex_addr; ///< (optional) Address of guest mutex value associated with this condition
|
||||
///< variable, used for implementing events
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
/**
|
||||
* Releases a slot from a condition variable.
|
||||
* @param target The number of threads to wakeup, -1 is all.
|
||||
* @return ResultCode indicating if the operation succeeded.
|
||||
*/
|
||||
ResultCode Release(s32 target);
|
||||
|
||||
private:
|
||||
ConditionVariable();
|
||||
~ConditionVariable() override;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -20,10 +20,16 @@ enum {
|
||||
MaxConnectionsReached = 52,
|
||||
|
||||
// Confirmed Switch OS error codes
|
||||
InvalidAddress = 102,
|
||||
InvalidMemoryState = 106,
|
||||
InvalidProcessorId = 113,
|
||||
InvalidHandle = 114,
|
||||
InvalidCombination = 116,
|
||||
Timeout = 117,
|
||||
SynchronizationCanceled = 118,
|
||||
TooLarge = 119,
|
||||
InvalidEnumValue = 120,
|
||||
InvalidState = 125,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,14 +42,15 @@ constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);
|
||||
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(-1);
|
||||
constexpr ResultCode ERR_WRONG_PERMISSION(-1);
|
||||
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(-1);
|
||||
constexpr ResultCode ERR_INVALID_ENUM_VALUE(-1);
|
||||
constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue);
|
||||
constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(-1);
|
||||
constexpr ResultCode ERR_INVALID_COMBINATION(-1);
|
||||
constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(-1);
|
||||
constexpr ResultCode ERR_OUT_OF_MEMORY(-1);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS(-1);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(-1);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
|
||||
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
|
||||
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
|
||||
constexpr ResultCode ERR_INVALID_POINTER(-1);
|
||||
constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1);
|
||||
constexpr ResultCode ERR_NOT_AUTHORIZED(-1);
|
||||
|
||||
@@ -48,7 +48,7 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
|
||||
ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
|
||||
SharedPtr<Object> object = GetGeneric(handle);
|
||||
if (object == nullptr) {
|
||||
LOG_ERROR(Kernel, "Tried to duplicate invalid handle: %08X", handle);
|
||||
LOG_ERROR(Kernel, "Tried to duplicate invalid handle: {:08X}", handle);
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
return Create(std::move(object));
|
||||
|
||||
@@ -110,7 +110,9 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
// Padding to align to 16 bytes
|
||||
rp.AlignWithPadding();
|
||||
|
||||
if (Session()->IsDomain() && (command_header->type == IPC::CommandType::Request || !incoming)) {
|
||||
if (Session()->IsDomain() && ((command_header->type == IPC::CommandType::Request ||
|
||||
command_header->type == IPC::CommandType::RequestWithContext) ||
|
||||
!incoming)) {
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
@@ -251,50 +253,60 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
std::vector<u8> HLERequestContext::ReadBuffer() const {
|
||||
std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const {
|
||||
std::vector<u8> buffer;
|
||||
const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()};
|
||||
const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()};
|
||||
|
||||
if (is_buffer_a) {
|
||||
buffer.resize(BufferDescriptorA()[0].Size());
|
||||
Memory::ReadBlock(BufferDescriptorA()[0].Address(), buffer.data(), buffer.size());
|
||||
buffer.resize(BufferDescriptorA()[buffer_index].Size());
|
||||
Memory::ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(),
|
||||
buffer.size());
|
||||
} else {
|
||||
buffer.resize(BufferDescriptorX()[0].Size());
|
||||
Memory::ReadBlock(BufferDescriptorX()[0].Address(), buffer.data(), buffer.size());
|
||||
buffer.resize(BufferDescriptorX()[buffer_index].Size());
|
||||
Memory::ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(),
|
||||
buffer.size());
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size) const {
|
||||
const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()};
|
||||
const size_t buffer_size{GetWriteBufferSize()};
|
||||
size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size, int buffer_index) const {
|
||||
if (size == 0) {
|
||||
LOG_WARNING(Core, "skip empty buffer write");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()};
|
||||
const size_t buffer_size{GetWriteBufferSize(buffer_index)};
|
||||
if (size > buffer_size) {
|
||||
LOG_CRITICAL(Core, "size (%016zx) is greater than buffer_size (%016zx)", size, buffer_size);
|
||||
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
|
||||
buffer_size);
|
||||
size = buffer_size; // TODO(bunnei): This needs to be HW tested
|
||||
}
|
||||
|
||||
if (is_buffer_b) {
|
||||
Memory::WriteBlock(BufferDescriptorB()[0].Address(), buffer, size);
|
||||
Memory::WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
|
||||
} else {
|
||||
Memory::WriteBlock(BufferDescriptorC()[0].Address(), buffer, size);
|
||||
Memory::WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer) const {
|
||||
size_t HLERequestContext::WriteBuffer(const std::vector<u8>& buffer, int buffer_index) const {
|
||||
return WriteBuffer(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
size_t HLERequestContext::GetReadBufferSize() const {
|
||||
const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[0].Size()};
|
||||
return is_buffer_a ? BufferDescriptorA()[0].Size() : BufferDescriptorX()[0].Size();
|
||||
size_t HLERequestContext::GetReadBufferSize(int buffer_index) const {
|
||||
const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()};
|
||||
return is_buffer_a ? BufferDescriptorA()[buffer_index].Size()
|
||||
: BufferDescriptorX()[buffer_index].Size();
|
||||
}
|
||||
|
||||
size_t HLERequestContext::GetWriteBufferSize() const {
|
||||
const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[0].Size()};
|
||||
return is_buffer_b ? BufferDescriptorB()[0].Size() : BufferDescriptorC()[0].Size();
|
||||
size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const {
|
||||
const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()};
|
||||
return is_buffer_b ? BufferDescriptorB()[buffer_index].Size()
|
||||
: BufferDescriptorC()[buffer_index].Size();
|
||||
}
|
||||
|
||||
std::string HLERequestContext::Description() const {
|
||||
|
||||
@@ -164,19 +164,19 @@ public:
|
||||
}
|
||||
|
||||
/// Helper function to read a buffer using the appropriate buffer descriptor
|
||||
std::vector<u8> ReadBuffer() const;
|
||||
std::vector<u8> ReadBuffer(int buffer_index = 0) const;
|
||||
|
||||
/// Helper function to write a buffer using the appropriate buffer descriptor
|
||||
size_t WriteBuffer(const void* buffer, size_t size) const;
|
||||
size_t WriteBuffer(const void* buffer, size_t size, int buffer_index = 0) const;
|
||||
|
||||
/// Helper function to write a buffer using the appropriate buffer descriptor
|
||||
size_t WriteBuffer(const std::vector<u8>& buffer) const;
|
||||
size_t WriteBuffer(const std::vector<u8>& buffer, int buffer_index = 0) const;
|
||||
|
||||
/// Helper function to get the size of the input buffer
|
||||
size_t GetReadBufferSize() const;
|
||||
size_t GetReadBufferSize(int buffer_index = 0) const;
|
||||
|
||||
/// Helper function to get the size of the output buffer
|
||||
size_t GetWriteBufferSize() const;
|
||||
size_t GetWriteBufferSize(int buffer_index = 0) const;
|
||||
|
||||
template <typename T>
|
||||
SharedPtr<T> GetCopyObject(size_t index) {
|
||||
@@ -202,6 +202,16 @@ public:
|
||||
domain_objects.emplace_back(std::move(object));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> GetDomainRequestHandler(size_t index) const {
|
||||
return std::static_pointer_cast<T>(domain_request_handlers[index]);
|
||||
}
|
||||
|
||||
void SetDomainRequestHandlers(
|
||||
const std::vector<std::shared_ptr<SessionRequestHandler>>& handlers) {
|
||||
domain_request_handlers = handlers;
|
||||
}
|
||||
|
||||
/// Clears the list of objects so that no lingering objects are written accidentally to the
|
||||
/// response buffer.
|
||||
void ClearIncomingObjects() {
|
||||
@@ -245,6 +255,8 @@ private:
|
||||
unsigned data_payload_offset{};
|
||||
unsigned buffer_c_offset{};
|
||||
u32_le command{};
|
||||
|
||||
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -18,12 +18,10 @@ using Handle = u32;
|
||||
enum class HandleType : u32 {
|
||||
Unknown,
|
||||
Event,
|
||||
Mutex,
|
||||
SharedMemory,
|
||||
Thread,
|
||||
Process,
|
||||
AddressArbiter,
|
||||
ConditionVariable,
|
||||
Timer,
|
||||
ResourceLimit,
|
||||
CodeSet,
|
||||
@@ -63,9 +61,7 @@ public:
|
||||
bool IsWaitable() const {
|
||||
switch (GetHandleType()) {
|
||||
case HandleType::Event:
|
||||
case HandleType::Mutex:
|
||||
case HandleType::Thread:
|
||||
case HandleType::ConditionVariable:
|
||||
case HandleType::Timer:
|
||||
case HandleType::ServerPort:
|
||||
case HandleType::ServerSession:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
@@ -15,124 +16,119 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
void ReleaseThreadMutexes(Thread* thread) {
|
||||
for (auto& mtx : thread->held_mutexes) {
|
||||
mtx->SetHasWaiters(false);
|
||||
mtx->SetHoldingThread(nullptr);
|
||||
mtx->WakeupAllWaitingThreads();
|
||||
}
|
||||
thread->held_mutexes.clear();
|
||||
}
|
||||
/// Returns the number of threads that are waiting for a mutex, and the highest priority one among
|
||||
/// those.
|
||||
static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
|
||||
SharedPtr<Thread> current_thread, VAddr mutex_addr) {
|
||||
|
||||
Mutex::Mutex() {}
|
||||
Mutex::~Mutex() {}
|
||||
SharedPtr<Thread> highest_priority_thread;
|
||||
u32 num_waiters = 0;
|
||||
|
||||
SharedPtr<Mutex> Mutex::Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr,
|
||||
std::string name) {
|
||||
SharedPtr<Mutex> mutex(new Mutex);
|
||||
for (auto& thread : current_thread->wait_mutex_threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
continue;
|
||||
|
||||
mutex->guest_addr = guest_addr;
|
||||
mutex->name = std::move(name);
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
|
||||
// If mutex was initialized with a holding thread, acquire it by the holding thread
|
||||
if (holding_thread) {
|
||||
mutex->Acquire(holding_thread.get());
|
||||
++num_waiters;
|
||||
if (highest_priority_thread == nullptr ||
|
||||
thread->GetPriority() < highest_priority_thread->GetPriority()) {
|
||||
highest_priority_thread = thread;
|
||||
}
|
||||
}
|
||||
|
||||
// Mutexes are referenced by guest address, so track this in the kernel
|
||||
g_object_address_table.Insert(guest_addr, mutex);
|
||||
|
||||
return mutex;
|
||||
return {highest_priority_thread, num_waiters};
|
||||
}
|
||||
|
||||
bool Mutex::ShouldWait(Thread* thread) const {
|
||||
auto holding_thread = GetHoldingThread();
|
||||
return holding_thread != nullptr && thread != holding_thread;
|
||||
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
|
||||
static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
|
||||
SharedPtr<Thread> new_owner) {
|
||||
auto threads = current_thread->wait_mutex_threads;
|
||||
for (auto& thread : threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
continue;
|
||||
|
||||
ASSERT(thread->lock_owner == current_thread);
|
||||
current_thread->RemoveMutexWaiter(thread);
|
||||
if (new_owner != thread)
|
||||
new_owner->AddMutexWaiter(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void Mutex::Acquire(Thread* thread) {
|
||||
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
|
||||
ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress);
|
||||
}
|
||||
|
||||
priority = thread->current_priority;
|
||||
thread->held_mutexes.insert(this);
|
||||
SetHoldingThread(thread);
|
||||
thread->UpdatePriority();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
}
|
||||
SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
|
||||
SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
|
||||
|
||||
ResultCode Mutex::Release(Thread* thread) {
|
||||
auto holding_thread = GetHoldingThread();
|
||||
ASSERT(holding_thread);
|
||||
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
|
||||
// thread.
|
||||
ASSERT(requesting_thread == GetCurrentThread());
|
||||
|
||||
// We can only release the mutex if it's held by the calling thread.
|
||||
ASSERT(thread == holding_thread);
|
||||
u32 addr_value = Memory::Read32(address);
|
||||
|
||||
// If the mutex isn't being held, just return success.
|
||||
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
if (holding_thread == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
// Wait until the mutex is released
|
||||
GetCurrentThread()->mutex_wait_address = address;
|
||||
GetCurrentThread()->wait_handle = requesting_thread_handle;
|
||||
|
||||
GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX;
|
||||
GetCurrentThread()->wakeup_callback = nullptr;
|
||||
|
||||
// Update the lock holder thread's priority to prevent priority inversion.
|
||||
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||
|
||||
holding_thread->held_mutexes.erase(this);
|
||||
holding_thread->UpdatePriority();
|
||||
SetHoldingThread(nullptr);
|
||||
SetHasWaiters(!GetWaitingThreads().empty());
|
||||
WakeupAllWaitingThreads();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void Mutex::AddWaitingThread(SharedPtr<Thread> thread) {
|
||||
WaitObject::AddWaitingThread(thread);
|
||||
thread->pending_mutexes.insert(this);
|
||||
SetHasWaiters(true);
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Mutex::RemoveWaitingThread(Thread* thread) {
|
||||
WaitObject::RemoveWaitingThread(thread);
|
||||
thread->pending_mutexes.erase(this);
|
||||
if (!GetHasWaiters())
|
||||
SetHasWaiters(!GetWaitingThreads().empty());
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Mutex::UpdatePriority() {
|
||||
if (!GetHoldingThread())
|
||||
return;
|
||||
|
||||
u32 best_priority = THREADPRIO_LOWEST;
|
||||
for (auto& waiter : GetWaitingThreads()) {
|
||||
if (waiter->current_priority < best_priority)
|
||||
best_priority = waiter->current_priority;
|
||||
ResultCode Mutex::Release(VAddr address) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress);
|
||||
}
|
||||
|
||||
if (best_priority != priority) {
|
||||
priority = best_priority;
|
||||
GetHoldingThread()->UpdatePriority();
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
|
||||
|
||||
// There are no more threads waiting for the mutex, release it completely.
|
||||
if (thread == nullptr) {
|
||||
Memory::Write32(address, 0);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
Handle Mutex::GetOwnerHandle() const {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
return guest_state.holding_thread_handle;
|
||||
}
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||
|
||||
SharedPtr<Thread> Mutex::GetHoldingThread() const {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
return g_handle_table.Get<Thread>(guest_state.holding_thread_handle);
|
||||
}
|
||||
u32 mutex_value = thread->wait_handle;
|
||||
|
||||
void Mutex::SetHoldingThread(SharedPtr<Thread> thread) {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
guest_state.holding_thread_handle.Assign(thread ? thread->guest_handle : 0);
|
||||
Memory::Write32(guest_addr, guest_state.raw);
|
||||
}
|
||||
if (num_waiters >= 2) {
|
||||
// Notify the guest that there are still some threads waiting for the mutex
|
||||
mutex_value |= Mutex::MutexHasWaitersFlag;
|
||||
}
|
||||
|
||||
bool Mutex::GetHasWaiters() const {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
return guest_state.has_waiters != 0;
|
||||
}
|
||||
// Grant the mutex to the next waiting thread and resume it.
|
||||
Memory::Write32(address, mutex_value);
|
||||
|
||||
void Mutex::SetHasWaiters(bool has_waiters) {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
guest_state.has_waiters.Assign(has_waiters ? 1 : 0);
|
||||
Memory::Write32(guest_addr, guest_state.raw);
|
||||
}
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -15,87 +15,23 @@ namespace Kernel {
|
||||
|
||||
class Thread;
|
||||
|
||||
class Mutex final : public WaitObject {
|
||||
class Mutex final {
|
||||
public:
|
||||
/**
|
||||
* Creates a mutex.
|
||||
* @param holding_thread Specifies a thread already holding the mutex. If not nullptr, this
|
||||
* thread will acquire the mutex.
|
||||
* @param guest_addr Address of the object tracking the mutex in guest memory. If specified,
|
||||
* this mutex will update the guest object when its state changes.
|
||||
* @param name Optional name of mutex
|
||||
* @return Pointer to new Mutex object
|
||||
*/
|
||||
static SharedPtr<Mutex> Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr = 0,
|
||||
std::string name = "Unknown");
|
||||
/// Flag that indicates that a mutex still has threads waiting for it.
|
||||
static constexpr u32 MutexHasWaitersFlag = 0x40000000;
|
||||
/// Mask of the bits in a mutex address value that contain the mutex owner.
|
||||
static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "Mutex";
|
||||
}
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
/// Attempts to acquire a mutex at the specified address.
|
||||
static ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle);
|
||||
|
||||
static const HandleType HANDLE_TYPE = HandleType::Mutex;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
u32 priority; ///< The priority of the mutex, used for priority inheritance.
|
||||
std::string name; ///< Name of mutex (optional)
|
||||
VAddr guest_addr; ///< Address of the guest mutex value
|
||||
|
||||
/**
|
||||
* Elevate the mutex priority to the best priority
|
||||
* among the priorities of all its waiting threads.
|
||||
*/
|
||||
void UpdatePriority();
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
void AddWaitingThread(SharedPtr<Thread> thread) override;
|
||||
void RemoveWaitingThread(Thread* thread) override;
|
||||
|
||||
/**
|
||||
* Attempts to release the mutex from the specified thread.
|
||||
* @param thread Thread that wants to release the mutex.
|
||||
* @returns The result code of the operation.
|
||||
*/
|
||||
ResultCode Release(Thread* thread);
|
||||
|
||||
/// Gets the handle to the holding process stored in the guest state.
|
||||
Handle GetOwnerHandle() const;
|
||||
|
||||
/// Gets the Thread pointed to by the owner handle
|
||||
SharedPtr<Thread> GetHoldingThread() const;
|
||||
/// Sets the holding process handle in the guest state.
|
||||
void SetHoldingThread(SharedPtr<Thread> thread);
|
||||
|
||||
/// Returns the has_waiters bit in the guest state.
|
||||
bool GetHasWaiters() const;
|
||||
/// Sets the has_waiters bit in the guest state.
|
||||
void SetHasWaiters(bool has_waiters);
|
||||
/// Releases the mutex at the specified address.
|
||||
static ResultCode Release(VAddr address);
|
||||
|
||||
private:
|
||||
Mutex();
|
||||
~Mutex() override;
|
||||
|
||||
/// Object in guest memory used to track the mutex state
|
||||
union GuestState {
|
||||
u32_le raw;
|
||||
/// Handle of the thread that currently holds the mutex, 0 if available
|
||||
BitField<0, 30, u32_le> holding_thread_handle;
|
||||
/// 1 when there are threads waiting for this mutex, otherwise 0
|
||||
BitField<30, 1, u32_le> has_waiters;
|
||||
};
|
||||
static_assert(sizeof(GuestState) == 4, "GuestState size is incorrect");
|
||||
Mutex() = default;
|
||||
~Mutex() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Releases all the mutexes held by the specified thread
|
||||
* @param thread Thread that is holding the mutexes
|
||||
*/
|
||||
void ReleaseThreadMutexes(Thread* thread);
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace Kernel {
|
||||
ObjectAddressTable g_object_address_table;
|
||||
|
||||
void ObjectAddressTable::Insert(VAddr addr, SharedPtr<Object> obj) {
|
||||
ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x%lx", addr);
|
||||
ASSERT_MSG(objects.find(addr) == objects.end(), "Object already exists with addr=0x{:X}", addr);
|
||||
objects[addr] = obj;
|
||||
}
|
||||
|
||||
void ObjectAddressTable::Close(VAddr addr) {
|
||||
ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x%lx", addr);
|
||||
ASSERT_MSG(objects.find(addr) != objects.end(), "Object does not exist with addr=0x{:X}", addr);
|
||||
objects.erase(addr);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,9 +109,9 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {
|
||||
|
||||
int minor = kernel_version & 0xFF;
|
||||
int major = (kernel_version >> 8) & 0xFF;
|
||||
LOG_INFO(Loader, "ExHeader kernel version: %d.%d", major, minor);
|
||||
LOG_INFO(Loader, "ExHeader kernel version: {}.{}", major, minor);
|
||||
} else {
|
||||
LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x%08X", descriptor);
|
||||
LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
|
||||
HandleSpecialMapping(vm_manager, mapping);
|
||||
}
|
||||
|
||||
vm_manager.LogLayout(Log::Level::Debug);
|
||||
vm_manager.LogLayout();
|
||||
status = ProcessStatus::Running;
|
||||
|
||||
Kernel::SetupMainThread(entry_point, main_thread_priority, this);
|
||||
|
||||
@@ -34,57 +34,57 @@ SharedPtr<ResourceLimit> ResourceLimit::GetForCategory(ResourceLimitCategory cat
|
||||
}
|
||||
}
|
||||
|
||||
s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const {
|
||||
s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
|
||||
switch (resource) {
|
||||
case COMMIT:
|
||||
case ResourceType::Commit:
|
||||
return current_commit;
|
||||
case THREAD:
|
||||
case ResourceType::Thread:
|
||||
return current_threads;
|
||||
case EVENT:
|
||||
case ResourceType::Event:
|
||||
return current_events;
|
||||
case MUTEX:
|
||||
case ResourceType::Mutex:
|
||||
return current_mutexes;
|
||||
case SEMAPHORE:
|
||||
case ResourceType::Semaphore:
|
||||
return current_semaphores;
|
||||
case TIMER:
|
||||
case ResourceType::Timer:
|
||||
return current_timers;
|
||||
case SHARED_MEMORY:
|
||||
case ResourceType::SharedMemory:
|
||||
return current_shared_mems;
|
||||
case ADDRESS_ARBITER:
|
||||
case ResourceType::AddressArbiter:
|
||||
return current_address_arbiters;
|
||||
case CPU_TIME:
|
||||
case ResourceType::CPUTime:
|
||||
return current_cpu_time;
|
||||
default:
|
||||
LOG_ERROR(Kernel, "Unknown resource type=%08X", resource);
|
||||
LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource));
|
||||
UNIMPLEMENTED();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 ResourceLimit::GetMaxResourceValue(u32 resource) const {
|
||||
u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
|
||||
switch (resource) {
|
||||
case PRIORITY:
|
||||
case ResourceType::Priority:
|
||||
return max_priority;
|
||||
case COMMIT:
|
||||
case ResourceType::Commit:
|
||||
return max_commit;
|
||||
case THREAD:
|
||||
case ResourceType::Thread:
|
||||
return max_threads;
|
||||
case EVENT:
|
||||
case ResourceType::Event:
|
||||
return max_events;
|
||||
case MUTEX:
|
||||
case ResourceType::Mutex:
|
||||
return max_mutexes;
|
||||
case SEMAPHORE:
|
||||
case ResourceType::Semaphore:
|
||||
return max_semaphores;
|
||||
case TIMER:
|
||||
case ResourceType::Timer:
|
||||
return max_timers;
|
||||
case SHARED_MEMORY:
|
||||
case ResourceType::SharedMemory:
|
||||
return max_shared_mems;
|
||||
case ADDRESS_ARBITER:
|
||||
case ResourceType::AddressArbiter:
|
||||
return max_address_arbiters;
|
||||
case CPU_TIME:
|
||||
case ResourceType::CPUTime:
|
||||
return max_cpu_time;
|
||||
default:
|
||||
LOG_ERROR(Kernel, "Unknown resource type=%08X", resource);
|
||||
LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource));
|
||||
UNIMPLEMENTED();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@ enum class ResourceLimitCategory : u8 {
|
||||
OTHER = 3
|
||||
};
|
||||
|
||||
enum ResourceTypes {
|
||||
PRIORITY = 0,
|
||||
COMMIT = 1,
|
||||
THREAD = 2,
|
||||
EVENT = 3,
|
||||
MUTEX = 4,
|
||||
SEMAPHORE = 5,
|
||||
TIMER = 6,
|
||||
SHARED_MEMORY = 7,
|
||||
ADDRESS_ARBITER = 8,
|
||||
CPU_TIME = 9,
|
||||
enum class ResourceType {
|
||||
Priority = 0,
|
||||
Commit = 1,
|
||||
Thread = 2,
|
||||
Event = 3,
|
||||
Mutex = 4,
|
||||
Semaphore = 5,
|
||||
Timer = 6,
|
||||
SharedMemory = 7,
|
||||
AddressArbiter = 8,
|
||||
CPUTime = 9,
|
||||
};
|
||||
|
||||
class ResourceLimit final : public Object {
|
||||
@@ -60,14 +60,14 @@ public:
|
||||
* @param resource Requested resource type
|
||||
* @returns The current value of the resource type
|
||||
*/
|
||||
s32 GetCurrentResourceValue(u32 resource) const;
|
||||
s32 GetCurrentResourceValue(ResourceType resource) const;
|
||||
|
||||
/**
|
||||
* Gets the max value for the specified resource.
|
||||
* @param resource Requested resource type
|
||||
* @returns The max value of the resource type
|
||||
*/
|
||||
u32 GetMaxResourceValue(u32 resource) const;
|
||||
u32 GetMaxResourceValue(ResourceType resource) const;
|
||||
|
||||
/// Name of resource limit object.
|
||||
std::string name;
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
std::mutex Scheduler::scheduler_mutex;
|
||||
|
||||
Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
@@ -18,6 +20,7 @@ Scheduler::~Scheduler() {
|
||||
}
|
||||
|
||||
bool Scheduler::HaveReadyThreads() {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
return ready_queue.get_first() != nullptr;
|
||||
}
|
||||
|
||||
@@ -90,41 +93,53 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
}
|
||||
|
||||
void Scheduler::Reschedule() {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
Thread* cur = GetCurrentThread();
|
||||
Thread* next = PopNextReadyThread();
|
||||
|
||||
if (cur && next) {
|
||||
LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
|
||||
LOG_TRACE(Kernel, "context switch {} -> {}", cur->GetObjectId(), next->GetObjectId());
|
||||
} else if (cur) {
|
||||
LOG_TRACE(Kernel, "context switch %u -> idle", cur->GetObjectId());
|
||||
LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
|
||||
} else if (next) {
|
||||
LOG_TRACE(Kernel, "context switch idle -> %u", next->GetObjectId());
|
||||
LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
|
||||
}
|
||||
|
||||
SwitchContext(next);
|
||||
}
|
||||
|
||||
void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
thread_list.push_back(thread);
|
||||
ready_queue.prepare(priority);
|
||||
}
|
||||
|
||||
void Scheduler::RemoveThread(Thread* thread) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
|
||||
thread_list.end());
|
||||
}
|
||||
|
||||
void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
ASSERT(thread->status == THREADSTATUS_READY);
|
||||
ready_queue.push_back(priority, thread);
|
||||
}
|
||||
|
||||
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
ASSERT(thread->status == THREADSTATUS_READY);
|
||||
ready_queue.remove(priority, thread);
|
||||
}
|
||||
|
||||
void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
// If thread was ready, adjust queues
|
||||
if (thread->status == THREADSTATUS_READY)
|
||||
ready_queue.move(thread, thread->current_priority, priority);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread_queue_list.h"
|
||||
@@ -68,6 +69,8 @@ private:
|
||||
SharedPtr<Thread> current_thread = nullptr;
|
||||
|
||||
ARM_Interface* cpu_core;
|
||||
|
||||
static std::mutex scheduler_mutex;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -61,6 +61,9 @@ void ServerSession::Acquire(Thread* thread) {
|
||||
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
|
||||
auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
if (domain_message_header) {
|
||||
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
|
||||
context.SetDomainRequestHandlers(domain_request_handlers);
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const u32 object_id{context.GetDomainMessageHeader()->object_id};
|
||||
switch (domain_message_header->command) {
|
||||
@@ -68,7 +71,7 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
|
||||
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
|
||||
|
||||
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x%08X", object_id);
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
|
||||
|
||||
domain_request_handlers[object_id - 1] = nullptr;
|
||||
|
||||
@@ -78,7 +81,7 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(IPC, "Unknown domain command=%d",
|
||||
LOG_CRITICAL(IPC, "Unknown domain command={}",
|
||||
static_cast<int>(domain_message_header->command.Value()));
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
@@ -107,16 +107,16 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
|
||||
|
||||
// Error out if the requested permissions don't match what the creator process allows.
|
||||
if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
|
||||
LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, permissions don't match",
|
||||
GetObjectId(), address, name.c_str());
|
||||
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
|
||||
GetObjectId(), address, name);
|
||||
return ERR_INVALID_COMBINATION;
|
||||
}
|
||||
|
||||
// Error out if the provided permissions are not compatible with what the creator process needs.
|
||||
if (other_permissions != MemoryPermission::DontCare &&
|
||||
static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
|
||||
LOG_ERROR(Kernel, "cannot map id=%u, address=0x%lx name=%s, permissions don't match",
|
||||
GetObjectId(), address, name.c_str());
|
||||
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
|
||||
GetObjectId(), address, name);
|
||||
return ERR_WRONG_PERMISSION;
|
||||
}
|
||||
|
||||
@@ -131,9 +131,10 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
|
||||
auto result = target_process->vm_manager.MapMemoryBlock(
|
||||
target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
|
||||
if (result.Failed()) {
|
||||
LOG_ERROR(Kernel,
|
||||
"cannot map id=%u, target_address=0x%lx name=%s, error mapping to virtual memory",
|
||||
GetObjectId(), target_address, name.c_str());
|
||||
LOG_ERROR(
|
||||
Kernel,
|
||||
"cannot map id={}, target_address=0x{:X} name={}, error mapping to virtual memory",
|
||||
GetObjectId(), target_address, name);
|
||||
return result.Code();
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
|
||||
u32 masked_permissions =
|
||||
static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute);
|
||||
return static_cast<VMAPermission>(masked_permissions);
|
||||
};
|
||||
}
|
||||
|
||||
u8* SharedMemory::GetPointer(u32 offset) {
|
||||
return backing_block->data() + backing_block_offset + offset;
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <iterator>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/condition_variable.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
@@ -31,7 +32,7 @@ namespace Kernel {
|
||||
|
||||
/// Set the process heap to a given Size. It can both extend and shrink the heap.
|
||||
static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, heap_size=0x%llx", heap_size);
|
||||
LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size);
|
||||
auto& process = *Core::CurrentProcess();
|
||||
CASCADE_RESULT(*heap_addr,
|
||||
process.HeapAllocate(Memory::HEAP_VADDR, heap_size, VMAPermission::ReadWrite));
|
||||
@@ -39,20 +40,20 @@ static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
|
||||
}
|
||||
|
||||
static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x%lx", addr);
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, addr=0x{:X}", addr);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Maps a memory range into a different range.
|
||||
static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr,
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
src_addr, size);
|
||||
return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
/// Unmaps a region that was previously mapped with svcMapMemory
|
||||
static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x%llx, src_addr=0x%llx, size=0x%llx", dst_addr,
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
src_addr, size);
|
||||
return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size);
|
||||
}
|
||||
@@ -68,11 +69,11 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
|
||||
if (port_name.size() > PortNameMaxLength)
|
||||
return ERR_PORT_NAME_TOO_LONG;
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called port_name=%s", port_name.c_str());
|
||||
LOG_TRACE(Kernel_SVC, "called port_name={}", port_name);
|
||||
|
||||
auto it = Service::g_kernel_named_ports.find(port_name);
|
||||
if (it == Service::g_kernel_named_ports.end()) {
|
||||
LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: %s", port_name.c_str());
|
||||
LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name);
|
||||
return ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
@@ -90,11 +91,11 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
|
||||
static ResultCode SendSyncRequest(Handle handle) {
|
||||
SharedPtr<ClientSession> session = g_handle_table.Get<ClientSession>(handle);
|
||||
if (!session) {
|
||||
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x%08X", handle);
|
||||
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str());
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
|
||||
@@ -105,7 +106,7 @@ static ResultCode SendSyncRequest(Handle handle) {
|
||||
|
||||
/// Get the ID for the specified thread.
|
||||
static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle);
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
|
||||
|
||||
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
@@ -118,7 +119,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
|
||||
|
||||
/// Get the ID of the specified process
|
||||
static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x%08X", process_handle);
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
|
||||
|
||||
const SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle);
|
||||
if (!process) {
|
||||
@@ -145,40 +146,10 @@ static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thr
|
||||
return true;
|
||||
};
|
||||
|
||||
/// Wait for a kernel object to synchronize, timeout after the specified nanoseconds
|
||||
static ResultCode WaitSynchronization1(
|
||||
SharedPtr<WaitObject> object, Thread* thread, s64 nano_seconds = -1,
|
||||
std::function<Thread::WakeupCallback> wakeup_callback = DefaultThreadWakeupCallback) {
|
||||
|
||||
if (!object) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (object->ShouldWait(thread)) {
|
||||
if (nano_seconds == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
thread->wait_objects = {object};
|
||||
object->AddWaitingThread(thread);
|
||||
thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
thread->wakeup_callback = wakeup_callback;
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
} else {
|
||||
object->Acquire(thread);
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
|
||||
static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 handle_count,
|
||||
s64 nano_seconds) {
|
||||
LOG_TRACE(Kernel_SVC, "called handles_address=0x%llx, handle_count=%d, nano_seconds=%d",
|
||||
LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, handle_count={}, nano_seconds={}",
|
||||
handles_address, handle_count, nano_seconds);
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(handles_address))
|
||||
@@ -232,14 +203,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
thread->wakeup_callback = DefaultThreadWakeupCallback;
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
/// Resumes a thread waiting on WaitSynchronization
|
||||
static ResultCode CancelSynchronization(Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle);
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle);
|
||||
|
||||
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
@@ -257,36 +228,18 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
|
||||
static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||
Handle requesting_thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called holding_thread_handle=0x%08X, mutex_addr=0x%llx, "
|
||||
"requesting_current_thread_handle=0x%08X",
|
||||
"called holding_thread_handle=0x{:08X}, mutex_addr=0x{:X}, "
|
||||
"requesting_current_thread_handle=0x{:08X}",
|
||||
holding_thread_handle, mutex_addr, requesting_thread_handle);
|
||||
|
||||
SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
|
||||
SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
|
||||
|
||||
ASSERT(requesting_thread);
|
||||
ASSERT(requesting_thread == GetCurrentThread());
|
||||
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
||||
if (!mutex) {
|
||||
// Create a new mutex for the specified address if one does not already exist
|
||||
mutex = Mutex::Create(holding_thread, mutex_addr);
|
||||
mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
|
||||
}
|
||||
|
||||
ASSERT(holding_thread == mutex->GetHoldingThread());
|
||||
|
||||
return WaitSynchronization1(mutex, requesting_thread.get());
|
||||
return Mutex::TryAcquire(mutex_addr, holding_thread_handle, requesting_thread_handle);
|
||||
}
|
||||
|
||||
/// Unlock a mutex
|
||||
static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr);
|
||||
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr);
|
||||
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
||||
ASSERT(mutex);
|
||||
|
||||
return mutex->Release(GetCurrentThread());
|
||||
return Mutex::Release(mutex_addr);
|
||||
}
|
||||
|
||||
/// Break program execution
|
||||
@@ -297,14 +250,14 @@ static void Break(u64 unk_0, u64 unk_1, u64 unk_2) {
|
||||
|
||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
||||
static void OutputDebugString(VAddr address, s32 len) {
|
||||
std::vector<char> string(len);
|
||||
Memory::ReadBlock(address, string.data(), len);
|
||||
LOG_DEBUG(Debug_Emulated, "%.*s", len, string.data());
|
||||
std::string str(len, '\0');
|
||||
Memory::ReadBlock(address, str.data(), str.size());
|
||||
LOG_DEBUG(Debug_Emulated, "{}", str);
|
||||
}
|
||||
|
||||
/// Gets system/memory information for the current process
|
||||
static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) {
|
||||
LOG_TRACE(Kernel_SVC, "called info_id=0x%X, info_sub_id=0x%X, handle=0x%08X", info_id,
|
||||
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
|
||||
info_sub_id, handle);
|
||||
|
||||
auto& vm_manager = Core::CurrentProcess()->vm_manager;
|
||||
@@ -361,7 +314,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
break;
|
||||
case GetInfoType::PrivilegedProcessId:
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"(STUBBED) Attempted to query priviledged process id bounds, returned 0");
|
||||
"(STUBBED) Attempted to query privileged process id bounds, returned 0");
|
||||
*result = 0;
|
||||
break;
|
||||
case GetInfoType::UserExceptionContextAddr:
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"(STUBBED) Attempted to query user exception context address, returned 0");
|
||||
*result = 0;
|
||||
break;
|
||||
default:
|
||||
@@ -373,13 +331,13 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
|
||||
/// Sets the thread activity
|
||||
static ResultCode SetThreadActivity(Handle handle, u32 unknown) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X, unknown=0x%08X", handle, unknown);
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, unknown=0x{:08X}", handle, unknown);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Gets the thread context
|
||||
static ResultCode GetThreadContext(Handle handle, VAddr addr) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X, addr=0x%" PRIx64, handle, addr);
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, addr=0x{:X}", handle, addr);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -406,31 +364,26 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
// Note: The kernel uses the current process's resource limit instead of
|
||||
// the one from the thread owner's resource limit.
|
||||
SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
|
||||
if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
thread->SetPriority(priority);
|
||||
thread->UpdatePriority();
|
||||
|
||||
// Update the mutexes that this thread is waiting for
|
||||
for (auto& mutex : thread->pending_mutexes)
|
||||
mutex->UpdatePriority();
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Get which CPU core is executing the current thread
|
||||
static u32 GetCurrentProcessorNumber() {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, defaulting to processor 0");
|
||||
return 0;
|
||||
LOG_TRACE(Kernel_SVC, "called");
|
||||
return GetCurrentThread()->processor_id;
|
||||
}
|
||||
|
||||
static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
|
||||
u32 permissions) {
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called, shared_memory_handle=0x%08X, addr=0x%llx, size=0x%llx, permissions=0x%08X",
|
||||
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
|
||||
shared_memory_handle, addr, size, permissions);
|
||||
|
||||
SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
|
||||
@@ -451,15 +404,14 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
|
||||
return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type,
|
||||
MemoryPermission::DontCare);
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "unknown permissions=0x%08X", permissions);
|
||||
LOG_ERROR(Kernel_SVC, "unknown permissions=0x{:08X}", permissions);
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"called, shared_memory_handle=0x%08X, addr=0x%" PRIx64 ", size=0x%" PRIx64 "",
|
||||
LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
|
||||
shared_memory_handle, addr, size);
|
||||
|
||||
SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
|
||||
@@ -488,41 +440,47 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
|
||||
memory_info->type = static_cast<u32>(vma->second.meminfo_state);
|
||||
}
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x%08X addr=%llx", process_handle, addr);
|
||||
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Query memory
|
||||
static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) {
|
||||
LOG_TRACE(Kernel_SVC, "called, addr=%llx", addr);
|
||||
LOG_TRACE(Kernel_SVC, "called, addr={:X}", addr);
|
||||
return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr);
|
||||
}
|
||||
|
||||
/// Exits the current process
|
||||
static void ExitProcess() {
|
||||
LOG_INFO(Kernel_SVC, "Process %u exiting", Core::CurrentProcess()->process_id);
|
||||
LOG_INFO(Kernel_SVC, "Process {} exiting", Core::CurrentProcess()->process_id);
|
||||
|
||||
ASSERT_MSG(Core::CurrentProcess()->status == ProcessStatus::Running,
|
||||
"Process has already exited");
|
||||
|
||||
Core::CurrentProcess()->status = ProcessStatus::Exited;
|
||||
|
||||
// Stop all the process threads that are currently waiting for objects.
|
||||
auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->owner_process != Core::CurrentProcess())
|
||||
continue;
|
||||
auto stop_threads = [](const std::vector<SharedPtr<Thread>>& thread_list) {
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->owner_process != Core::CurrentProcess())
|
||||
continue;
|
||||
|
||||
if (thread == GetCurrentThread())
|
||||
continue;
|
||||
if (thread == GetCurrentThread())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
|
||||
thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
|
||||
"Exiting processes with non-waiting threads is currently unimplemented");
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
|
||||
thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
|
||||
"Exiting processes with non-waiting threads is currently unimplemented");
|
||||
|
||||
thread->Stop();
|
||||
}
|
||||
thread->Stop();
|
||||
}
|
||||
};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
stop_threads(system.Scheduler(0)->GetThreadList());
|
||||
stop_threads(system.Scheduler(1)->GetThreadList());
|
||||
stop_threads(system.Scheduler(2)->GetThreadList());
|
||||
stop_threads(system.Scheduler(3)->GetThreadList());
|
||||
|
||||
// Kill the current thread
|
||||
GetCurrentThread()->Stop();
|
||||
@@ -533,14 +491,14 @@ static void ExitProcess() {
|
||||
/// Creates a new thread
|
||||
static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top,
|
||||
u32 priority, s32 processor_id) {
|
||||
std::string name = Common::StringFromFormat("unknown-%llx", entry_point);
|
||||
std::string name = fmt::format("unknown-{:X}", entry_point);
|
||||
|
||||
if (priority > THREADPRIO_LOWEST) {
|
||||
return ERR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
|
||||
if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
@@ -552,17 +510,12 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
|
||||
switch (processor_id) {
|
||||
case THREADPROCESSORID_0:
|
||||
break;
|
||||
case THREADPROCESSORID_1:
|
||||
case THREADPROCESSORID_2:
|
||||
case THREADPROCESSORID_3:
|
||||
// TODO(bunnei): Implement support for other processor IDs
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Newly created thread must run in another thread (%u), unimplemented.",
|
||||
processor_id);
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(false, "Unsupported thread processor ID: %d", processor_id);
|
||||
ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -573,32 +526,36 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
*out_handle = thread->guest_handle;
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, "
|
||||
"threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X",
|
||||
entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle);
|
||||
"called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
|
||||
"threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}",
|
||||
entry_point, name, arg, stack_top, priority, processor_id, *out_handle);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Starts the thread for the provided handle
|
||||
static ResultCode StartThread(Handle thread_handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x%08X", thread_handle);
|
||||
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
|
||||
|
||||
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ASSERT(thread->status == THREADSTATUS_DORMANT);
|
||||
|
||||
thread->ResumeFromWait();
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Called when a thread exits
|
||||
static void ExitThread() {
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC());
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
|
||||
|
||||
ExitCurrentThread();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
@@ -606,11 +563,11 @@ static void ExitThread() {
|
||||
|
||||
/// Sleep the current thread
|
||||
static void SleepThread(s64 nanoseconds) {
|
||||
LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds);
|
||||
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
|
||||
|
||||
// Don't attempt to yield execution if there are no available threads to run,
|
||||
// this way we avoid a useless reschedule to the idle thread.
|
||||
if (nanoseconds == 0 && !Core::System::GetInstance().Scheduler().HaveReadyThreads())
|
||||
if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads())
|
||||
return;
|
||||
|
||||
// Sleep current thread and check for next thread to schedule
|
||||
@@ -622,119 +579,166 @@ static void SleepThread(s64 nanoseconds) {
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Signal process wide key atomic
|
||||
/// Wait process wide key atomic
|
||||
static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_variable_addr,
|
||||
Handle thread_handle, s64 nano_seconds) {
|
||||
LOG_TRACE(
|
||||
Kernel_SVC,
|
||||
"called mutex_addr=%llx, condition_variable_addr=%llx, thread_handle=0x%08X, timeout=%d",
|
||||
"called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
|
||||
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
|
||||
|
||||
SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
ASSERT(thread);
|
||||
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
||||
if (!mutex) {
|
||||
// Create a new mutex for the specified address if one does not already exist
|
||||
mutex = Mutex::Create(thread, mutex_addr);
|
||||
mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
|
||||
}
|
||||
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||
|
||||
SharedPtr<ConditionVariable> condition_variable =
|
||||
g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
|
||||
if (!condition_variable) {
|
||||
// Create a new condition_variable for the specified address if one does not already exist
|
||||
condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
|
||||
condition_variable->name =
|
||||
Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
|
||||
}
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->condvar_wait_address = condition_variable_addr;
|
||||
current_thread->mutex_wait_address = mutex_addr;
|
||||
current_thread->wait_handle = thread_handle;
|
||||
current_thread->status = THREADSTATUS_WAIT_MUTEX;
|
||||
current_thread->wakeup_callback = nullptr;
|
||||
|
||||
if (condition_variable->mutex_addr) {
|
||||
// Previously created the ConditionVariable using WaitProcessWideKeyAtomic, verify
|
||||
// everything is correct
|
||||
ASSERT(condition_variable->mutex_addr == mutex_addr);
|
||||
} else {
|
||||
// Previously created the ConditionVariable using SignalProcessWideKey, set the mutex
|
||||
// associated with it
|
||||
condition_variable->mutex_addr = mutex_addr;
|
||||
}
|
||||
current_thread->WakeAfterDelay(nano_seconds);
|
||||
|
||||
if (mutex->GetOwnerHandle()) {
|
||||
// Release the mutex if the current thread is holding it
|
||||
mutex->Release(thread.get());
|
||||
}
|
||||
|
||||
auto wakeup_callback = [mutex, nano_seconds](ThreadWakeupReason reason,
|
||||
SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, size_t index) {
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
|
||||
|
||||
if (reason == ThreadWakeupReason::Timeout) {
|
||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
||||
return true;
|
||||
}
|
||||
|
||||
ASSERT(reason == ThreadWakeupReason::Signal);
|
||||
|
||||
// Now try to acquire the mutex and don't resume if it's not available.
|
||||
if (!mutex->ShouldWait(thread.get())) {
|
||||
mutex->Acquire(thread.get());
|
||||
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nano_seconds == 0) {
|
||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
||||
return true;
|
||||
}
|
||||
|
||||
thread->wait_objects = {mutex};
|
||||
mutex->AddWaitingThread(thread);
|
||||
thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
|
||||
|
||||
// Create an event to wake the thread up after the
|
||||
// specified nanosecond delay has passed
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
thread->wakeup_callback = DefaultThreadWakeupCallback;
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
|
||||
return false;
|
||||
};
|
||||
CASCADE_CODE(
|
||||
WaitSynchronization1(condition_variable, thread.get(), nano_seconds, wakeup_callback));
|
||||
// Note: Deliberately don't attempt to inherit the lock owner's priority.
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Signal process wide key
|
||||
static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target) {
|
||||
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x%llx, target=0x%08x",
|
||||
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
|
||||
condition_variable_addr, target);
|
||||
|
||||
// Wakeup all or one thread - Any other value is unimplemented
|
||||
ASSERT(target == -1 || target == 1);
|
||||
auto RetrieveWaitingThreads =
|
||||
[](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr condvar_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
auto& thread_list = scheduler->GetThreadList();
|
||||
|
||||
SharedPtr<ConditionVariable> condition_variable =
|
||||
g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
|
||||
if (!condition_variable) {
|
||||
// Create a new condition_variable for the specified address if one does not already exist
|
||||
condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
|
||||
condition_variable->name =
|
||||
Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
|
||||
}
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->condvar_wait_address == condvar_addr)
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
};
|
||||
|
||||
CASCADE_CODE(condition_variable->Release(target));
|
||||
// Retrieve a list of all threads that are waiting for this condition variable.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads;
|
||||
RetrieveWaitingThreads(0, waiting_threads, condition_variable_addr);
|
||||
RetrieveWaitingThreads(1, waiting_threads, condition_variable_addr);
|
||||
RetrieveWaitingThreads(2, waiting_threads, condition_variable_addr);
|
||||
RetrieveWaitingThreads(3, waiting_threads, condition_variable_addr);
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(waiting_threads.begin(), waiting_threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
});
|
||||
|
||||
if (condition_variable->mutex_addr) {
|
||||
// If a mutex was created for this condition_variable, wait the current thread on it
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(condition_variable->mutex_addr);
|
||||
return WaitSynchronization1(mutex, GetCurrentThread());
|
||||
// Only process up to 'target' threads, unless 'target' is -1, in which case process
|
||||
// them all.
|
||||
size_t last = waiting_threads.size();
|
||||
if (target != -1)
|
||||
last = target;
|
||||
|
||||
// If there are no threads waiting on this condition variable, just exit
|
||||
if (last > waiting_threads.size())
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
for (size_t index = 0; index < last; ++index) {
|
||||
auto& thread = waiting_threads[index];
|
||||
|
||||
ASSERT(thread->condvar_wait_address == condition_variable_addr);
|
||||
|
||||
// If the mutex is not yet acquired, acquire it.
|
||||
u32 mutex_val = Memory::Read32(thread->mutex_wait_address);
|
||||
|
||||
if (mutex_val == 0) {
|
||||
// We were able to acquire the mutex, resume this thread.
|
||||
Memory::Write32(thread->mutex_wait_address, thread->wait_handle);
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
if (lock_owner)
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
} else {
|
||||
// Couldn't acquire the mutex, block the thread.
|
||||
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
auto owner = g_handle_table.Get<Thread>(owner_handle);
|
||||
ASSERT(owner);
|
||||
ASSERT(thread->status != THREADSTATUS_RUNNING);
|
||||
thread->status = THREADSTATUS_WAIT_MUTEX;
|
||||
thread->wakeup_callback = nullptr;
|
||||
|
||||
// Signal that the mutex now has a waiting thread.
|
||||
Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag);
|
||||
|
||||
owner->AddMutexWaiter(thread);
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Wait for an address (via Address Arbiter)
|
||||
static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout) {
|
||||
LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}",
|
||||
address, type, value, timeout);
|
||||
// If the passed address is a kernel virtual address, return invalid memory state.
|
||||
if (Memory::IsKernelVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
// If the address is not properly aligned to 4 bytes, return invalid address.
|
||||
if (address % sizeof(u32) != 0) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
switch (static_cast<AddressArbiter::ArbitrationType>(type)) {
|
||||
case AddressArbiter::ArbitrationType::WaitIfLessThan:
|
||||
return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, false);
|
||||
case AddressArbiter::ArbitrationType::DecrementAndWaitIfLessThan:
|
||||
return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, true);
|
||||
case AddressArbiter::ArbitrationType::WaitIfEqual:
|
||||
return AddressArbiter::WaitForAddressIfEqual(address, value, timeout);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Signals to an address (via Address Arbiter)
|
||||
static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to_wake) {
|
||||
LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}",
|
||||
address, type, value, num_to_wake);
|
||||
// If the passed address is a kernel virtual address, return invalid memory state.
|
||||
if (Memory::IsKernelVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
// If the address is not properly aligned to 4 bytes, return invalid address.
|
||||
if (address % sizeof(u32) != 0) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
switch (static_cast<AddressArbiter::SignalType>(type)) {
|
||||
case AddressArbiter::SignalType::Signal:
|
||||
return AddressArbiter::SignalToAddress(address, num_to_wake);
|
||||
case AddressArbiter::SignalType::IncrementAndSignalIfEqual:
|
||||
return AddressArbiter::IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
case AddressArbiter::SignalType::ModifyByWaitingCountAndSignalIfEqual:
|
||||
return AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(address, value,
|
||||
num_to_wake);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
/// This returns the total CPU ticks elapsed since the CPU was powered-on
|
||||
static u64 GetSystemTick() {
|
||||
const u64 result{CoreTiming::GetTicks()};
|
||||
@@ -747,13 +751,13 @@ static u64 GetSystemTick() {
|
||||
|
||||
/// Close a handle
|
||||
static ResultCode CloseHandle(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "Closing handle 0x%08X", handle);
|
||||
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
|
||||
return g_handle_table.Close(handle);
|
||||
}
|
||||
|
||||
/// Reset an event
|
||||
static ResultCode ResetSignal(Handle handle) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x%08X", handle);
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle);
|
||||
auto event = g_handle_table.Get<Event>(handle);
|
||||
ASSERT(event != nullptr);
|
||||
event->Clear();
|
||||
@@ -762,28 +766,68 @@ static ResultCode ResetSignal(Handle handle) {
|
||||
|
||||
/// Creates a TransferMemory object
|
||||
static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x%lx, size=0x%lx, perms=%08X", addr, size,
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size,
|
||||
permissions);
|
||||
*handle = 0;
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode GetThreadCoreMask(Handle handle, u32* mask, u64* unknown) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X", handle);
|
||||
*mask = 0x0;
|
||||
*unknown = 0xf;
|
||||
static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) {
|
||||
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
|
||||
|
||||
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
*core = thread->ideal_core;
|
||||
*mask = thread->affinity_mask;
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode SetThreadCoreMask(Handle handle, u32 mask, u64 unknown) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x%08X, mask=0x%08X, unknown=0x%lx", handle,
|
||||
mask, unknown);
|
||||
static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle,
|
||||
mask, core);
|
||||
|
||||
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (core == THREADPROCESSORID_DEFAULT) {
|
||||
ASSERT(thread->owner_process->ideal_processor != THREADPROCESSORID_DEFAULT);
|
||||
// Set the target CPU to the one specified in the process' exheader.
|
||||
core = thread->owner_process->ideal_processor;
|
||||
mask = 1ull << core;
|
||||
}
|
||||
|
||||
if (mask == 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination);
|
||||
}
|
||||
|
||||
/// This value is used to only change the affinity mask without changing the current ideal core.
|
||||
static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
|
||||
|
||||
if (core == OnlyChangeMask) {
|
||||
core = thread->ideal_core;
|
||||
} else if (core >= Core::NUM_CPU_CORES && core != -1) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
|
||||
}
|
||||
|
||||
// Error out if the input core isn't enabled in the input mask.
|
||||
if (core < Core::NUM_CPU_CORES && (mask & (1ull << core)) == 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination);
|
||||
}
|
||||
|
||||
thread->ChangeCore(core, mask);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permissions,
|
||||
u32 remote_permissions) {
|
||||
LOG_TRACE(Kernel_SVC, "called, size=0x%llx, localPerms=0x%08x, remotePerms=0x%08x", size,
|
||||
LOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size,
|
||||
local_permissions, remote_permissions);
|
||||
auto sharedMemHandle =
|
||||
SharedMemory::Create(g_handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
|
||||
@@ -795,7 +839,7 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
|
||||
}
|
||||
|
||||
static ResultCode ClearEvent(Handle handle) {
|
||||
LOG_TRACE(Kernel_SVC, "called, event=0xX", handle);
|
||||
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
|
||||
|
||||
SharedPtr<Event> evt = g_handle_table.Get<Event>(handle);
|
||||
if (evt == nullptr)
|
||||
@@ -867,8 +911,8 @@ static const FunctionDef SVC_Table[] = {
|
||||
{0x31, nullptr, "GetResourceLimitCurrentValue"},
|
||||
{0x32, SvcWrap<SetThreadActivity>, "SetThreadActivity"},
|
||||
{0x33, SvcWrap<GetThreadContext>, "GetThreadContext"},
|
||||
{0x34, nullptr, "WaitForAddress"},
|
||||
{0x35, nullptr, "SignalToAddress"},
|
||||
{0x34, SvcWrap<WaitForAddress>, "WaitForAddress"},
|
||||
{0x35, SvcWrap<SignalToAddress>, "SignalToAddress"},
|
||||
{0x36, nullptr, "Unknown"},
|
||||
{0x37, nullptr, "Unknown"},
|
||||
{0x38, nullptr, "Unknown"},
|
||||
@@ -946,8 +990,8 @@ static const FunctionDef SVC_Table[] = {
|
||||
};
|
||||
|
||||
static const FunctionDef* GetSVCInfo(u32 func_num) {
|
||||
if (func_num >= ARRAY_SIZE(SVC_Table)) {
|
||||
LOG_ERROR(Kernel_SVC, "unknown svc=0x%02X", func_num);
|
||||
if (func_num >= std::size(SVC_Table)) {
|
||||
LOG_ERROR(Kernel_SVC, "Unknown svc=0x{:02X}", func_num);
|
||||
return nullptr;
|
||||
}
|
||||
return &SVC_Table[func_num];
|
||||
@@ -966,10 +1010,10 @@ void CallSVC(u32 immediate) {
|
||||
if (info->func) {
|
||||
info->func();
|
||||
} else {
|
||||
LOG_CRITICAL(Kernel_SVC, "unimplemented SVC function %s(..)", info->name);
|
||||
LOG_CRITICAL(Kernel_SVC, "Unimplemented SVC function {}(..)", info->name);
|
||||
}
|
||||
} else {
|
||||
LOG_CRITICAL(Kernel_SVC, "unknown SVC function 0x%x", immediate);
|
||||
LOG_CRITICAL(Kernel_SVC, "Unknown SVC function 0x{:X}", immediate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,12 @@ enum class GetInfoType : u64 {
|
||||
NewMapRegionSize = 15,
|
||||
// 3.0.0+
|
||||
IsVirtualAddressMemoryEnabled = 16,
|
||||
PersonalMmHeapUsage = 17,
|
||||
TitleId = 18,
|
||||
// 4.0.0+
|
||||
PrivilegedProcessId = 19,
|
||||
// 5.0.0+
|
||||
UserExceptionContextAddr = 20,
|
||||
};
|
||||
|
||||
void CallSVC(u32 immediate);
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
#define PARAM(n) Core::CPU().GetReg(n)
|
||||
#define PARAM(n) Core::CurrentArmInterface().GetReg(n)
|
||||
|
||||
/**
|
||||
* HLE a function return from the current ARM userland process
|
||||
* @param res Result to return
|
||||
*/
|
||||
static inline void FuncReturn(u64 res) {
|
||||
Core::CPU().SetReg(0, res);
|
||||
Core::CurrentArmInterface().SetReg(0, res);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -45,7 +45,7 @@ template <ResultCode func(u32*, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, (u32)PARAM(1)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ template <ResultCode func(u32*, u64)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, PARAM(1)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ template <ResultCode func(u64*, u64)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
u32 retval = func(¶m_1, PARAM(1)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u64 param_2 = 0;
|
||||
ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), ¶m_1, ¶m_2);
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CPU().SetReg(2, param_2);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(2, param_2);
|
||||
FuncReturn(retval.raw);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ template <ResultCode func(u32*, u64, u64, s64)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
ResultCode retval = func(¶m_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3));
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval.raw);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ template <ResultCode func(u64*, u64, u64, u64)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
u32 retval = func(¶m_1, PARAM(1), PARAM(2), PARAM(3)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ void SvcWrap() {
|
||||
u32 retval =
|
||||
func(¶m_1, PARAM(1), PARAM(2), PARAM(3), (u32)PARAM(4), (s32)(PARAM(5) & 0xFFFFFFFF))
|
||||
.raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ template <ResultCode func(u32*, u64, u64, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, PARAM(1), PARAM(2), (u32)(PARAM(3) & 0xFFFFFFFF)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
@@ -175,10 +175,24 @@ void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval =
|
||||
func(¶m_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw;
|
||||
Core::CPU().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u32, s32, s64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(
|
||||
func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3))
|
||||
.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u32, s32, s32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF),
|
||||
(s32)(PARAM(3) & 0xFFFFFFFF))
|
||||
.raw);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Function wrappers that return type u32
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ void Thread::Stop() {
|
||||
// Clean up thread from ready queue
|
||||
// This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
|
||||
if (status == THREADSTATUS_READY) {
|
||||
Core::System::GetInstance().Scheduler().UnscheduleThread(this, current_priority);
|
||||
scheduler->UnscheduleThread(this, current_priority);
|
||||
}
|
||||
|
||||
status = THREADSTATUS_DEAD;
|
||||
@@ -77,9 +77,6 @@ void Thread::Stop() {
|
||||
}
|
||||
wait_objects.clear();
|
||||
|
||||
// Release all the mutexes that this thread holds
|
||||
ReleaseThreadMutexes(this);
|
||||
|
||||
// Mark the TLS slot in the thread's page as free.
|
||||
u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
|
||||
u64 tls_slot =
|
||||
@@ -95,7 +92,7 @@ void WaitCurrentThread_Sleep() {
|
||||
void ExitCurrentThread() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->Stop();
|
||||
Core::System::GetInstance().Scheduler().RemoveThread(thread);
|
||||
Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,9 +101,10 @@ void ExitCurrentThread() {
|
||||
* @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
|
||||
*/
|
||||
static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
|
||||
SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>((Handle)thread_handle);
|
||||
const auto proper_handle = static_cast<Handle>(thread_handle);
|
||||
SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>(proper_handle);
|
||||
if (thread == nullptr) {
|
||||
LOG_CRITICAL(Kernel, "Callback fired for invalid thread %08X", (Handle)thread_handle);
|
||||
LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,6 +124,27 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
|
||||
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||
}
|
||||
|
||||
if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
|
||||
thread->wait_handle) {
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
|
||||
// and don't have a lock owner unless SignalProcessWideKey was called first and the thread
|
||||
// wasn't awakened due to the mutex already being acquired.
|
||||
if (lock_owner) {
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->arb_wait_address != 0) {
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_ARB);
|
||||
thread->arb_wait_address = 0;
|
||||
}
|
||||
|
||||
if (resume)
|
||||
thread->ResumeFromWait();
|
||||
}
|
||||
@@ -135,13 +154,26 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
if (nanoseconds == -1)
|
||||
return;
|
||||
|
||||
CoreTiming::ScheduleEvent(nsToCycles(nanoseconds), ThreadWakeupEventType, callback_handle);
|
||||
CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType,
|
||||
callback_handle);
|
||||
}
|
||||
|
||||
void Thread::CancelWakeupTimer() {
|
||||
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
|
||||
}
|
||||
|
||||
static boost::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
|
||||
if (mask & (1ULL << index)) {
|
||||
if (!Core::System().GetInstance().Scheduler(index)->GetCurrentThread()) {
|
||||
// Core is enabled and not running any threads, use this one
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Thread::ResumeFromWait() {
|
||||
ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
|
||||
|
||||
@@ -151,6 +183,8 @@ void Thread::ResumeFromWait() {
|
||||
case THREADSTATUS_WAIT_HLE_EVENT:
|
||||
case THREADSTATUS_WAIT_SLEEP:
|
||||
case THREADSTATUS_WAIT_IPC:
|
||||
case THREADSTATUS_WAIT_MUTEX:
|
||||
case THREADSTATUS_WAIT_ARB:
|
||||
break;
|
||||
|
||||
case THREADSTATUS_READY:
|
||||
@@ -163,11 +197,11 @@ void Thread::ResumeFromWait() {
|
||||
return;
|
||||
|
||||
case THREADSTATUS_RUNNING:
|
||||
DEBUG_ASSERT_MSG(false, "Thread with object id %u has already resumed.", GetObjectId());
|
||||
DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId());
|
||||
return;
|
||||
case THREADSTATUS_DEAD:
|
||||
// This should never happen, as threads must complete before being stopped.
|
||||
DEBUG_ASSERT_MSG(false, "Thread with object id %u cannot be resumed because it's DEAD.",
|
||||
DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.",
|
||||
GetObjectId());
|
||||
return;
|
||||
}
|
||||
@@ -175,8 +209,37 @@ void Thread::ResumeFromWait() {
|
||||
wakeup_callback = nullptr;
|
||||
|
||||
status = THREADSTATUS_READY;
|
||||
Core::System::GetInstance().Scheduler().ScheduleThread(this, current_priority);
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
|
||||
boost::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
|
||||
if (!new_processor_id) {
|
||||
new_processor_id = processor_id;
|
||||
}
|
||||
if (ideal_core != -1 &&
|
||||
Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
|
||||
new_processor_id = ideal_core;
|
||||
}
|
||||
|
||||
ASSERT(*new_processor_id < 4);
|
||||
|
||||
// Add thread to new core's scheduler
|
||||
auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id);
|
||||
|
||||
if (*new_processor_id != processor_id) {
|
||||
// Remove thread from previous core's scheduler
|
||||
scheduler->RemoveThread(this);
|
||||
next_scheduler->AddThread(this, current_priority);
|
||||
}
|
||||
|
||||
processor_id = *new_processor_id;
|
||||
|
||||
// If the thread was ready, unschedule from the previous core and schedule on the new core
|
||||
scheduler->UnscheduleThread(this, current_priority);
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler;
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,27 +290,25 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||
SharedPtr<Process> owner_process) {
|
||||
// Check if priority is in ranged. Lowest priority -> highest priority id.
|
||||
if (priority > THREADPRIO_LOWEST) {
|
||||
LOG_ERROR(Kernel_SVC, "Invalid thread priority: %u", priority);
|
||||
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
|
||||
return ERR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
if (processor_id > THREADPROCESSORID_MAX) {
|
||||
LOG_ERROR(Kernel_SVC, "Invalid processor id: %d", processor_id);
|
||||
LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id);
|
||||
return ERR_OUT_OF_RANGE_KERNEL;
|
||||
}
|
||||
|
||||
// TODO(yuriks): Other checks, returning 0xD9001BEA
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) {
|
||||
LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %016" PRIx64, name.c_str(), entry_point);
|
||||
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
|
||||
// TODO (bunnei): Find the correct error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
SharedPtr<Thread> thread(new Thread);
|
||||
|
||||
Core::System::GetInstance().Scheduler().AddThread(thread, priority);
|
||||
|
||||
thread->thread_id = NewThreadId();
|
||||
thread->status = THREADSTATUS_DORMANT;
|
||||
thread->entry_point = entry_point;
|
||||
@@ -255,11 +316,17 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||
thread->nominal_priority = thread->current_priority = priority;
|
||||
thread->last_running_ticks = CoreTiming::GetTicks();
|
||||
thread->processor_id = processor_id;
|
||||
thread->ideal_core = processor_id;
|
||||
thread->affinity_mask = 1ULL << processor_id;
|
||||
thread->wait_objects.clear();
|
||||
thread->wait_address = 0;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
|
||||
thread->owner_process = owner_process;
|
||||
thread->scheduler = Core::System().GetInstance().Scheduler(processor_id);
|
||||
thread->scheduler->AddThread(thread, priority);
|
||||
|
||||
// Find the next available TLS index, and mark it as used
|
||||
auto& tls_slots = owner_process->tls_slots;
|
||||
@@ -317,21 +384,12 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||
void Thread::SetPriority(u32 priority) {
|
||||
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
|
||||
"Invalid priority value.");
|
||||
Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
|
||||
nominal_priority = current_priority = priority;
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
u32 best_priority = nominal_priority;
|
||||
for (auto& mutex : held_mutexes) {
|
||||
if (mutex->priority < best_priority)
|
||||
best_priority = mutex->priority;
|
||||
}
|
||||
BoostPriority(best_priority);
|
||||
nominal_priority = priority;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::BoostPriority(u32 priority) {
|
||||
Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
|
||||
scheduler->SetThreadPriority(this, priority);
|
||||
current_priority = priority;
|
||||
}
|
||||
|
||||
@@ -377,13 +435,86 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||
return GetTLSAddress() + CommandHeaderOffset;
|
||||
}
|
||||
|
||||
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
thread->lock_owner = this;
|
||||
wait_mutex_threads.emplace_back(std::move(thread));
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||
boost::remove_erase(wait_mutex_threads, thread);
|
||||
thread->lock_owner = nullptr;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
// Find the highest priority among all the threads that are waiting for this thread's lock
|
||||
u32 new_priority = nominal_priority;
|
||||
for (const auto& thread : wait_mutex_threads) {
|
||||
if (thread->nominal_priority < new_priority)
|
||||
new_priority = thread->nominal_priority;
|
||||
}
|
||||
|
||||
if (new_priority == current_priority)
|
||||
return;
|
||||
|
||||
scheduler->SetThreadPriority(this, new_priority);
|
||||
|
||||
current_priority = new_priority;
|
||||
|
||||
// Recursively update the priority of the thread that depends on the priority of this one.
|
||||
if (lock_owner)
|
||||
lock_owner->UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
ideal_core = core;
|
||||
affinity_mask = mask;
|
||||
|
||||
if (status != THREADSTATUS_READY) {
|
||||
return;
|
||||
}
|
||||
|
||||
boost::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
|
||||
|
||||
if (!new_processor_id) {
|
||||
new_processor_id = processor_id;
|
||||
}
|
||||
if (ideal_core != -1 &&
|
||||
Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
|
||||
new_processor_id = ideal_core;
|
||||
}
|
||||
|
||||
ASSERT(*new_processor_id < 4);
|
||||
|
||||
// Add thread to new core's scheduler
|
||||
auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id);
|
||||
|
||||
if (*new_processor_id != processor_id) {
|
||||
// Remove thread from previous core's scheduler
|
||||
scheduler->RemoveThread(this);
|
||||
next_scheduler->AddThread(this, current_priority);
|
||||
}
|
||||
|
||||
processor_id = *new_processor_id;
|
||||
|
||||
// If the thread was ready, unschedule from the previous core and schedule on the new core
|
||||
scheduler->UnscheduleThread(this, current_priority);
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler;
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Gets the current thread
|
||||
*/
|
||||
Thread* GetCurrentThread() {
|
||||
return Core::System::GetInstance().Scheduler().GetCurrentThread();
|
||||
return Core::System::GetInstance().CurrentScheduler().GetCurrentThread();
|
||||
}
|
||||
|
||||
void ThreadingInit() {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -18,7 +19,7 @@
|
||||
enum ThreadPriority : u32 {
|
||||
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
|
||||
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
|
||||
THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps
|
||||
THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
|
||||
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
|
||||
};
|
||||
|
||||
@@ -43,6 +44,8 @@ enum ThreadStatus {
|
||||
THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request
|
||||
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
|
||||
THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
|
||||
THREADSTATUS_WAIT_MUTEX, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc
|
||||
THREADSTATUS_WAIT_ARB, ///< Waiting due to a SignalToAddress/WaitForAddress svc
|
||||
THREADSTATUS_DORMANT, ///< Created but not yet made ready
|
||||
THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
|
||||
};
|
||||
@@ -54,8 +57,8 @@ enum class ThreadWakeupReason {
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class Mutex;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
|
||||
class Thread final : public WaitObject {
|
||||
public:
|
||||
@@ -103,18 +106,24 @@ public:
|
||||
*/
|
||||
void SetPriority(u32 priority);
|
||||
|
||||
/**
|
||||
* Boost's a thread's priority to the best priority among the thread's held mutexes.
|
||||
* This prevents priority inversion via priority inheritance.
|
||||
*/
|
||||
void UpdatePriority();
|
||||
|
||||
/**
|
||||
* Temporarily boosts the thread's priority until the next time it is scheduled
|
||||
* @param priority The new priority
|
||||
*/
|
||||
void BoostPriority(u32 priority);
|
||||
|
||||
/// Adds a thread to the list of threads that are waiting for a lock held by this thread.
|
||||
void AddMutexWaiter(SharedPtr<Thread> thread);
|
||||
|
||||
/// Removes a thread from the list of threads that are waiting for a lock held by this thread.
|
||||
void RemoveMutexWaiter(SharedPtr<Thread> thread);
|
||||
|
||||
/// Recalculates the current priority taking into account priority inheritance.
|
||||
void UpdatePriority();
|
||||
|
||||
/// Changes the core that the thread is running or scheduled to run on.
|
||||
void ChangeCore(u32 core, u64 mask);
|
||||
|
||||
/**
|
||||
* Gets the thread's thread ID
|
||||
* @return The thread's ID
|
||||
@@ -205,19 +214,25 @@ public:
|
||||
|
||||
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
|
||||
|
||||
/// Mutexes currently held by this thread, which will be released when it exits.
|
||||
boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
|
||||
|
||||
/// Mutexes that this thread is currently waiting for.
|
||||
boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes;
|
||||
|
||||
SharedPtr<Process> owner_process; ///< Process that owns this thread
|
||||
|
||||
/// Objects that the thread is waiting on, in the same order as they were
|
||||
// passed to WaitSynchronization1/N.
|
||||
std::vector<SharedPtr<WaitObject>> wait_objects;
|
||||
|
||||
VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
|
||||
/// List of threads that are waiting for a mutex that is held by this thread.
|
||||
std::vector<SharedPtr<Thread>> wait_mutex_threads;
|
||||
|
||||
/// Thread that owns the lock that this thread is waiting for.
|
||||
SharedPtr<Thread> lock_owner;
|
||||
|
||||
// If waiting on a ConditionVariable, this is the ConditionVariable address
|
||||
VAddr condvar_wait_address;
|
||||
VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
|
||||
Handle wait_handle; ///< The handle used to wait for the mutex.
|
||||
|
||||
// If waiting for an AddressArbiter, this is the address being waited on.
|
||||
VAddr arb_wait_address{0};
|
||||
|
||||
std::string name;
|
||||
|
||||
@@ -234,6 +249,11 @@ public:
|
||||
// available. In case of a timeout, the object will be nullptr.
|
||||
std::function<WakeupCallback> wakeup_callback;
|
||||
|
||||
std::shared_ptr<Scheduler> scheduler;
|
||||
|
||||
u32 ideal_core{0xFFFFFFFF};
|
||||
u64 affinity_mask{0x1};
|
||||
|
||||
private:
|
||||
Thread();
|
||||
~Thread() override;
|
||||
|
||||
@@ -57,7 +57,8 @@ void Timer::Set(s64 initial, s64 interval) {
|
||||
// Immediately invoke the callback
|
||||
Signal(0);
|
||||
} else {
|
||||
CoreTiming::ScheduleEvent(nsToCycles(initial), timer_callback_event_type, callback_handle);
|
||||
CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(initial), timer_callback_event_type,
|
||||
callback_handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ void Timer::WakeupAllWaitingThreads() {
|
||||
}
|
||||
|
||||
void Timer::Signal(int cycles_late) {
|
||||
LOG_TRACE(Kernel, "Timer %u fired", GetObjectId());
|
||||
LOG_TRACE(Kernel, "Timer {} fired", GetObjectId());
|
||||
|
||||
signaled = true;
|
||||
|
||||
@@ -86,7 +87,7 @@ void Timer::Signal(int cycles_late) {
|
||||
|
||||
if (interval_delay != 0) {
|
||||
// Reschedule the timer with the interval delay
|
||||
CoreTiming::ScheduleEvent(nsToCycles(interval_delay) - cycles_late,
|
||||
CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(interval_delay) - cycles_late,
|
||||
timer_callback_event_type, callback_handle);
|
||||
}
|
||||
}
|
||||
@@ -97,7 +98,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) {
|
||||
timer_callback_handle_table.Get<Timer>(static_cast<Handle>(timer_handle));
|
||||
|
||||
if (timer == nullptr) {
|
||||
LOG_CRITICAL(Kernel, "Callback fired for invalid timer %08" PRIx64, timer_handle);
|
||||
LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016X}", timer_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <iterator>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -105,8 +104,15 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
|
||||
VirtualMemoryArea& final_vma = vma_handle->second;
|
||||
ASSERT(final_vma.size == size);
|
||||
|
||||
Core::CPU().MapBackingMemory(target, size, block->data() + offset,
|
||||
VMAPermission::ReadWriteExecute);
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset,
|
||||
VMAPermission::ReadWriteExecute);
|
||||
system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset,
|
||||
VMAPermission::ReadWriteExecute);
|
||||
system.ArmInterface(2).MapBackingMemory(target, size, block->data() + offset,
|
||||
VMAPermission::ReadWriteExecute);
|
||||
system.ArmInterface(3).MapBackingMemory(target, size, block->data() + offset,
|
||||
VMAPermission::ReadWriteExecute);
|
||||
|
||||
final_vma.type = VMAType::AllocatedMemoryBlock;
|
||||
final_vma.permissions = VMAPermission::ReadWrite;
|
||||
@@ -127,7 +133,11 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
|
||||
VirtualMemoryArea& final_vma = vma_handle->second;
|
||||
ASSERT(final_vma.size == size);
|
||||
|
||||
Core::CPU().MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
|
||||
system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
|
||||
system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
|
||||
system.ArmInterface(3).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
|
||||
|
||||
final_vma.type = VMAType::BackingMemory;
|
||||
final_vma.permissions = VMAPermission::ReadWrite;
|
||||
@@ -185,7 +195,11 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) {
|
||||
|
||||
ASSERT(FindVMA(target)->second.size >= size);
|
||||
|
||||
Core::CPU().UnmapMemory(target, size);
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.ArmInterface(0).UnmapMemory(target, size);
|
||||
system.ArmInterface(1).UnmapMemory(target, size);
|
||||
system.ArmInterface(2).UnmapMemory(target, size);
|
||||
system.ArmInterface(3).UnmapMemory(target, size);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -225,16 +239,15 @@ void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) {
|
||||
}
|
||||
}
|
||||
|
||||
void VMManager::LogLayout(Log::Level log_level) const {
|
||||
void VMManager::LogLayout() const {
|
||||
for (const auto& p : vma_map) {
|
||||
const VirtualMemoryArea& vma = p.second;
|
||||
LOG_GENERIC(Log::Class::Kernel, log_level,
|
||||
"%016" PRIx64 " - %016" PRIx64 " size: %16" PRIx64 " %c%c%c %s", vma.base,
|
||||
vma.base + vma.size, vma.size,
|
||||
(u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
|
||||
(u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
|
||||
(u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-',
|
||||
GetMemoryStateName(vma.meminfo_state));
|
||||
LOG_DEBUG(Kernel, "{:016X} - {:016X} size: {:016X} {}{}{} {}", vma.base,
|
||||
vma.base + vma.size, vma.size,
|
||||
(u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
|
||||
(u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
|
||||
(u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-',
|
||||
GetMemoryStateName(vma.meminfo_state));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,8 +258,8 @@ VMManager::VMAIter VMManager::StripIterConstness(const VMAHandle& iter) {
|
||||
}
|
||||
|
||||
ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) {
|
||||
ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size);
|
||||
ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, base);
|
||||
ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size);
|
||||
ASSERT_MSG((base & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", base);
|
||||
|
||||
VMAIter vma_handle = StripIterConstness(FindVMA(base));
|
||||
if (vma_handle == vma_map.end()) {
|
||||
@@ -281,8 +294,8 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMA(VAddr base, u64 size) {
|
||||
}
|
||||
|
||||
ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u64 size) {
|
||||
ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x%16" PRIx64, size);
|
||||
ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x%016" PRIx64, target);
|
||||
ASSERT_MSG((size & Memory::PAGE_MASK) == 0, "non-page aligned size: 0x{:016X}", size);
|
||||
ASSERT_MSG((target & Memory::PAGE_MASK) == 0, "non-page aligned base: 0x{:016X}", target);
|
||||
|
||||
VAddr target_end = target + size;
|
||||
ASSERT(target_end >= target);
|
||||
|
||||
@@ -187,7 +187,7 @@ public:
|
||||
void RefreshMemoryBlockMappings(const std::vector<u8>* block);
|
||||
|
||||
/// Dumps the address space layout to the log, for debugging
|
||||
void LogLayout(Log::Level log_level) const;
|
||||
void LogLayout() const;
|
||||
|
||||
/// Gets the total memory usage, used by svcGetInfo
|
||||
u64 GetTotalMemoryUsage();
|
||||
|
||||
@@ -32,7 +32,8 @@ enum class ErrorModule : u32 {
|
||||
Common = 0,
|
||||
Kernel = 1,
|
||||
FS = 2,
|
||||
NvidiaTransferMemory = 3,
|
||||
OS = 3, // used for Memory, Thread, Mutex, Nvidia
|
||||
HTCS = 4,
|
||||
NCM = 5,
|
||||
DD = 6,
|
||||
LR = 8,
|
||||
@@ -42,41 +43,80 @@ enum class ErrorModule : u32 {
|
||||
PM = 15,
|
||||
NS = 16,
|
||||
HTC = 18,
|
||||
NCMContent = 20,
|
||||
SM = 21,
|
||||
RO = 22,
|
||||
SDMMC = 24,
|
||||
OVLN = 25,
|
||||
SPL = 26,
|
||||
ETHC = 100,
|
||||
I2C = 101,
|
||||
GPIO = 102,
|
||||
UART = 103,
|
||||
Settings = 105,
|
||||
WLAN = 107,
|
||||
XCD = 108,
|
||||
NIFM = 110,
|
||||
Display = 114,
|
||||
NTC = 116,
|
||||
Hwopus = 111,
|
||||
Bluetooth = 113,
|
||||
VI = 114,
|
||||
NFP = 115,
|
||||
Time = 116,
|
||||
FGM = 117,
|
||||
PCIE = 120,
|
||||
OE = 118,
|
||||
PCIe = 120,
|
||||
Friends = 121,
|
||||
BCAT = 122,
|
||||
SSL = 123,
|
||||
Account = 124,
|
||||
News = 125,
|
||||
Mii = 126,
|
||||
NFC = 127,
|
||||
AM = 128,
|
||||
PlayReport = 129,
|
||||
AHID = 130,
|
||||
Qlaunch = 132,
|
||||
PCV = 133,
|
||||
OMM = 134,
|
||||
BPC = 135,
|
||||
PSM = 136,
|
||||
NIM = 137,
|
||||
PSC = 138,
|
||||
TC = 139,
|
||||
USB = 140,
|
||||
NSD = 141,
|
||||
PCTL = 142,
|
||||
BTM = 143,
|
||||
ETicket = 145,
|
||||
NGC = 146,
|
||||
ERPT = 147,
|
||||
APM = 148,
|
||||
Profiler = 150,
|
||||
ErrorUpload = 151,
|
||||
Audio = 153,
|
||||
NPNS = 154,
|
||||
NPNSHTTPSTREAM = 155,
|
||||
ARP = 157,
|
||||
BOOT = 158,
|
||||
NFC = 161,
|
||||
SWKBD = 158,
|
||||
BOOT = 159,
|
||||
NFCMifare = 161,
|
||||
UserlandAssert = 162,
|
||||
Fatal = 163,
|
||||
NIMShop = 164,
|
||||
SPSM = 165,
|
||||
BGTC = 167,
|
||||
UserlandCrash = 168,
|
||||
HID = 203,
|
||||
SREPO = 180,
|
||||
Dauth = 181,
|
||||
HID = 202,
|
||||
LDN = 203,
|
||||
Irsensor = 205,
|
||||
Capture = 206,
|
||||
TC = 651,
|
||||
Manu = 208,
|
||||
ATK = 209,
|
||||
GRC = 212,
|
||||
Migration = 216,
|
||||
MigrationLdcServ = 217,
|
||||
GeneralWebApplet = 800,
|
||||
WifiWebAuthApplet = 809,
|
||||
WhitelistedApplet = 810,
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
#include "core/hle/service/acc/acc_u0.h"
|
||||
#include "core/hle/service/acc/acc_u1.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
// TODO: RE this structure
|
||||
struct UserData {
|
||||
@@ -148,5 +147,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<ACC_U1>(module)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
@@ -31,5 +30,4 @@ public:
|
||||
/// Registers all ACC services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "core/hle/service/acc/acc_aa.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -18,5 +17,4 @@ ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_AA final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_AA(std::shared_ptr<Module> module);
|
||||
};
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "core/hle/service/acc/acc_su.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -51,5 +50,4 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "core/hle/service/acc/acc_u0.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -31,5 +30,4 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_U0 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U0(std::shared_ptr<Module> module);
|
||||
};
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "core/hle/service/acc/acc_u1.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -38,5 +37,4 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(mod
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_U1 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U1(std::shared_ptr<Module> module);
|
||||
};
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <stack>
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
@@ -12,10 +13,10 @@
|
||||
#include "core/hle/service/apm/apm.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
#include "core/hle/service/set/set.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service {
|
||||
namespace AM {
|
||||
namespace Service::AM {
|
||||
|
||||
IWindowController::IWindowController() : ServiceFramework("IWindowController") {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -74,7 +75,40 @@ void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestCo
|
||||
rb.Push(volume);
|
||||
}
|
||||
|
||||
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {}
|
||||
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetLastForegroundCaptureImage"},
|
||||
{1, nullptr, "UpdateLastForegroundCaptureImage"},
|
||||
{2, nullptr, "GetLastApplicationCaptureImage"},
|
||||
{3, nullptr, "GetCallerAppletCaptureImage"},
|
||||
{4, nullptr, "UpdateCallerAppletCaptureImage"},
|
||||
{5, nullptr, "GetLastForegroundCaptureImageEx"},
|
||||
{6, nullptr, "GetLastApplicationCaptureImageEx"},
|
||||
{7, nullptr, "GetCallerAppletCaptureImageEx"},
|
||||
{8, nullptr, "TakeScreenShotOfOwnLayer"}, // 2.0.0+
|
||||
{9, nullptr, "CopyBetweenCaptureBuffers"}, // 5.0.0+
|
||||
{10, nullptr, "AcquireLastApplicationCaptureBuffer"},
|
||||
{11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
|
||||
{12, nullptr, "AcquireLastForegroundCaptureBuffer"},
|
||||
{13, nullptr, "ReleaseLastForegroundCaptureBuffer"},
|
||||
{14, nullptr, "AcquireCallerAppletCaptureBuffer"},
|
||||
{15, nullptr, "ReleaseCallerAppletCaptureBuffer"},
|
||||
{16, nullptr, "AcquireLastApplicationCaptureBufferEx"},
|
||||
{17, nullptr, "AcquireLastForegroundCaptureBufferEx"},
|
||||
{18, nullptr, "AcquireCallerAppletCaptureBufferEx"},
|
||||
// 2.0.0+
|
||||
{20, nullptr, "ClearCaptureBuffer"},
|
||||
{21, nullptr, "ClearAppletTransitionBuffer"},
|
||||
// 4.0.0+
|
||||
{22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
|
||||
{23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
|
||||
{24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"},
|
||||
{25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
|
||||
{26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"},
|
||||
{27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
|
||||
|
||||
@@ -103,7 +137,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
|
||||
{40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
|
||||
{41, nullptr, "IsSystemBufferSharingEnabled"},
|
||||
{42, nullptr, "GetSystemSharedLayerHandle"},
|
||||
{50, nullptr, "SetHandlesRequestToDisplay"},
|
||||
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
|
||||
{51, nullptr, "ApproveToDisplay"},
|
||||
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
|
||||
{61, nullptr, "SetMediaPlaybackState"},
|
||||
@@ -121,7 +155,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
|
||||
RegisterHandlers(functions);
|
||||
|
||||
launchable_event =
|
||||
Kernel::Event::Create(Kernel::ResetType::OneShot, "ISelfController:LaunchableEvent");
|
||||
Kernel::Event::Create(Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent");
|
||||
}
|
||||
|
||||
void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
|
||||
@@ -158,7 +192,7 @@ void ISelfController::SetPerformanceModeChangedNotification(Kernel::HLERequestCo
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag));
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag);
|
||||
}
|
||||
|
||||
void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) {
|
||||
@@ -176,7 +210,7 @@ void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestCont
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called flag=%u", static_cast<u32>(flag));
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called flag={}", flag);
|
||||
}
|
||||
|
||||
void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) {
|
||||
@@ -189,7 +223,7 @@ void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext&
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called enabled=%u", static_cast<u32>(enabled));
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called enabled={}", enabled);
|
||||
}
|
||||
|
||||
void ISelfController::LockExit(Kernel::HLERequestContext& ctx) {
|
||||
@@ -229,6 +263,13 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx)
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
|
||||
@@ -308,77 +349,13 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
|
||||
public:
|
||||
explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
|
||||
{1, nullptr, "IsCompleted"},
|
||||
{10, nullptr, "Start"},
|
||||
{20, nullptr, "RequestExit"},
|
||||
{25, nullptr, "Terminate"},
|
||||
{30, nullptr, "GetResult"},
|
||||
{50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
|
||||
{100, nullptr, "PushInData"},
|
||||
{101, nullptr, "PopOutData"},
|
||||
{102, nullptr, "PushExtraStorage"},
|
||||
{103, nullptr, "PushInteractiveInData"},
|
||||
{104, nullptr, "PopInteractiveOutData"},
|
||||
{105, nullptr, "GetPopOutDataEvent"},
|
||||
{106, nullptr, "GetPopInteractiveOutDataEvent"},
|
||||
{110, nullptr, "NeedsToExitProcess"},
|
||||
{120, nullptr, "GetLibraryAppletInfo"},
|
||||
{150, nullptr, "RequestForAppletToGetForeground"},
|
||||
{160, nullptr, "GetIndirectLayerConsumerHandle"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
state_changed_event = Kernel::Event::Create(Kernel::ResetType::OneShot,
|
||||
"ILibraryAppletAccessor:StateChangedEvent");
|
||||
}
|
||||
|
||||
private:
|
||||
void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) {
|
||||
state_changed_event->Signal();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(state_changed_event);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Event> state_changed_event;
|
||||
};
|
||||
|
||||
ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
|
||||
{1, nullptr, "TerminateAllLibraryApplets"},
|
||||
{2, nullptr, "AreAnyLibraryAppletsLeft"},
|
||||
{10, nullptr, "CreateStorage"},
|
||||
{11, nullptr, "CreateTransferMemoryStorage"},
|
||||
{12, nullptr, "CreateHandleStorage"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::ILibraryAppletAccessor>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
|
||||
public:
|
||||
explicit IStorageAccessor(std::vector<u8> buffer)
|
||||
: ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IStorageAccessor::GetSize, "GetSize"},
|
||||
{10, nullptr, "Write"},
|
||||
{10, &IStorageAccessor::Write, "Write"},
|
||||
{11, &IStorageAccessor::Read, "Read"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
@@ -396,22 +373,36 @@ private:
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void Write(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
const std::vector<u8> data{ctx.ReadBuffer()};
|
||||
|
||||
ASSERT(offset + data.size() <= buffer.size());
|
||||
|
||||
std::memcpy(&buffer[offset], data.data(), data.size());
|
||||
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
}
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
u64 offset = rp.Pop<u64>();
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
const size_t size{ctx.GetWriteBufferSize()};
|
||||
|
||||
ASSERT(offset + size <= buffer.size());
|
||||
|
||||
ctx.WriteBuffer(buffer.data() + offset, size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
LOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -439,17 +430,129 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
|
||||
public:
|
||||
explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
|
||||
{1, nullptr, "IsCompleted"},
|
||||
{10, &ILibraryAppletAccessor::Start, "Start"},
|
||||
{20, nullptr, "RequestExit"},
|
||||
{25, nullptr, "Terminate"},
|
||||
{30, &ILibraryAppletAccessor::GetResult, "GetResult"},
|
||||
{50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
|
||||
{100, &ILibraryAppletAccessor::PushInData, "PushInData"},
|
||||
{101, &ILibraryAppletAccessor::PopOutData, "PopOutData"},
|
||||
{102, nullptr, "PushExtraStorage"},
|
||||
{103, nullptr, "PushInteractiveInData"},
|
||||
{104, nullptr, "PopInteractiveOutData"},
|
||||
{105, nullptr, "GetPopOutDataEvent"},
|
||||
{106, nullptr, "GetPopInteractiveOutDataEvent"},
|
||||
{110, nullptr, "NeedsToExitProcess"},
|
||||
{120, nullptr, "GetLibraryAppletInfo"},
|
||||
{150, nullptr, "RequestForAppletToGetForeground"},
|
||||
{160, nullptr, "GetIndirectLayerConsumerHandle"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
state_changed_event = Kernel::Event::Create(Kernel::ResetType::OneShot,
|
||||
"ILibraryAppletAccessor:StateChangedEvent");
|
||||
}
|
||||
|
||||
private:
|
||||
void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) {
|
||||
state_changed_event->Signal();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(state_changed_event);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void GetResult(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Start(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void PushInData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
storage_stack.push(rp.PopIpcInterface<AM::IStorage>());
|
||||
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void PopOutData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top()));
|
||||
|
||||
storage_stack.pop();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
std::stack<std::shared_ptr<AM::IStorage>> storage_stack;
|
||||
Kernel::SharedPtr<Kernel::Event> state_changed_event;
|
||||
};
|
||||
|
||||
ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
|
||||
{1, nullptr, "TerminateAllLibraryApplets"},
|
||||
{2, nullptr, "AreAnyLibraryAppletsLeft"},
|
||||
{10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"},
|
||||
{11, nullptr, "CreateTransferMemoryStorage"},
|
||||
{12, nullptr, "CreateHandleStorage"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::ILibraryAppletAccessor>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 size{rp.Pop<u64>()};
|
||||
std::vector<u8> buffer(size);
|
||||
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 1)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::IStorage>(std::move(buffer));
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, size={}", size);
|
||||
}
|
||||
|
||||
IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
|
||||
{10, nullptr, "CreateApplicationAndPushAndRequestToStart"},
|
||||
{11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"},
|
||||
{12, nullptr, "CreateApplicationAndRequestToStart"},
|
||||
{13, nullptr, "CreateApplicationAndRequestToStartForQuest"},
|
||||
{13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest,
|
||||
"CreateApplicationAndRequestToStartForQuest"},
|
||||
{20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"},
|
||||
{21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"},
|
||||
{22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"},
|
||||
{23, nullptr, "GetDisplayVersion"},
|
||||
{23, &IApplicationFunctions::GetDisplayVersion, "GetDisplayVersion"},
|
||||
{24, nullptr, "GetLaunchStorageInfoForDebug"},
|
||||
{25, nullptr, "ExtendSaveData"},
|
||||
{26, nullptr, "GetSaveDataSize"},
|
||||
@@ -458,7 +561,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
|
||||
{32, nullptr, "BeginBlockingHomeButton"},
|
||||
{33, nullptr, "EndBlockingHomeButton"},
|
||||
{40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
|
||||
{50, nullptr, "GetPseudoDeviceId"},
|
||||
{50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"},
|
||||
{60, nullptr, "SetMediaPlaybackStateForApplication"},
|
||||
{65, nullptr, "IsGamePlayRecordingSupported"},
|
||||
{66, &IApplicationFunctions::InitializeGamePlayRecording, "InitializeGamePlayRecording"},
|
||||
@@ -502,11 +605,18 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
|
||||
Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u128 uid = rp.PopRaw<u128>();
|
||||
|
||||
LOG_WARNING(Service, "(STUBBED) called uid = %016" PRIX64 "%016" PRIX64, uid[1], uid[0]);
|
||||
LOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
@@ -534,14 +644,23 @@ void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, result=0x%08X", result);
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, result=0x{:08X}", result);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(1);
|
||||
rb.Push<u64>(0);
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
|
||||
// TODO(bunnei): This should be configurable
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(SystemLanguage::English);
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
rb.Push(static_cast<u64>(Service::Set::LanguageCode::EN_US));
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) {
|
||||
@@ -565,11 +684,81 @@ void IApplicationFunctions::NotifyRunning(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void IApplicationFunctions::GetPseudoDeviceId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
// Returns a 128-bit UUID
|
||||
rb.Push<u64>(0);
|
||||
rb.Push<u64>(0);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger) {
|
||||
std::make_shared<AppletAE>(nvflinger)->InstallAsService(service_manager);
|
||||
std::make_shared<AppletOE>(nvflinger)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace AM
|
||||
} // namespace Service
|
||||
IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"},
|
||||
{11, nullptr, "LockForeground"},
|
||||
{12, nullptr, "UnlockForeground"},
|
||||
{20, nullptr, "PopFromGeneralChannel"},
|
||||
{21, nullptr, "GetPopFromGeneralChannelEvent"},
|
||||
{30, nullptr, "GetHomeButtonWriterLockAccessor"},
|
||||
{31, nullptr, "GetWriterLockAccessorEx"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestToEnterSleep"},
|
||||
{1, nullptr, "EnterSleep"},
|
||||
{2, nullptr, "StartSleepSequence"},
|
||||
{3, nullptr, "StartShutdownSequence"},
|
||||
{4, nullptr, "StartRebootSequence"},
|
||||
{10, nullptr, "LoadAndApplyIdlePolicySettings"},
|
||||
{11, nullptr, "NotifyCecSettingsChanged"},
|
||||
{12, nullptr, "SetDefaultHomeButtonLongPressTime"},
|
||||
{13, nullptr, "UpdateDefaultDisplayResolution"},
|
||||
{14, nullptr, "ShouldSleepOnBoot"},
|
||||
{15, nullptr, "GetHdcpAuthenticationFailedEvent"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CreateApplication"},
|
||||
{1, nullptr, "PopLaunchRequestedApplication"},
|
||||
{10, nullptr, "CreateSystemApplication"},
|
||||
{100, nullptr, "PopFloatingApplicationForDevelopment"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IProcessWindingController::IProcessWindingController()
|
||||
: ServiceFramework("IProcessWindingController") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetLaunchReason"},
|
||||
{11, nullptr, "OpenCallingLibraryApplet"},
|
||||
{21, nullptr, "PushContext"},
|
||||
{22, nullptr, "PopContext"},
|
||||
{23, nullptr, "CancelWindingReservation"},
|
||||
{30, nullptr, "WindAndDoReserved"},
|
||||
{40, nullptr, "ReserveToStartAndWaitAndUnwindThis"},
|
||||
{41, nullptr, "ReserveToStartAndWait"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -18,10 +18,25 @@ class NVFlinger;
|
||||
|
||||
namespace AM {
|
||||
|
||||
// TODO: Add more languages
|
||||
enum SystemLanguage {
|
||||
Japanese = 0,
|
||||
English = 1,
|
||||
English = 1, // en-US
|
||||
French = 2,
|
||||
German = 3,
|
||||
Italian = 4,
|
||||
Spanish = 5,
|
||||
Chinese = 6,
|
||||
Korean = 7,
|
||||
Dutch = 8,
|
||||
Portuguese = 9,
|
||||
Russian = 10,
|
||||
Taiwanese = 11,
|
||||
BritishEnglish = 12, // en-GB
|
||||
CanadianFrench = 13,
|
||||
LatinAmericanSpanish = 14, // es-419
|
||||
// 4.0.0+
|
||||
SimplifiedChinese = 15,
|
||||
TraditionalChinese = 16,
|
||||
};
|
||||
|
||||
class IWindowController final : public ServiceFramework<IWindowController> {
|
||||
@@ -70,6 +85,7 @@ private:
|
||||
void GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx);
|
||||
void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx);
|
||||
void SetScreenShotPermission(Kernel::HLERequestContext& ctx);
|
||||
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
Kernel::SharedPtr<Kernel::Event> launchable_event;
|
||||
@@ -105,6 +121,7 @@ public:
|
||||
|
||||
private:
|
||||
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
|
||||
void CreateStorage(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
|
||||
@@ -113,12 +130,38 @@ public:
|
||||
|
||||
private:
|
||||
void PopLaunchParameter(Kernel::HLERequestContext& ctx);
|
||||
void CreateApplicationAndRequestToStartForQuest(Kernel::HLERequestContext& ctx);
|
||||
void EnsureSaveData(Kernel::HLERequestContext& ctx);
|
||||
void SetTerminateResult(Kernel::HLERequestContext& ctx);
|
||||
void GetDisplayVersion(Kernel::HLERequestContext& ctx);
|
||||
void GetDesiredLanguage(Kernel::HLERequestContext& ctx);
|
||||
void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx);
|
||||
void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx);
|
||||
void NotifyRunning(Kernel::HLERequestContext& ctx);
|
||||
void GetPseudoDeviceId(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
|
||||
public:
|
||||
IHomeMenuFunctions();
|
||||
|
||||
private:
|
||||
void RequestToGetForeground(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
class IGlobalStateController final : public ServiceFramework<IGlobalStateController> {
|
||||
public:
|
||||
IGlobalStateController();
|
||||
};
|
||||
|
||||
class IApplicationCreator final : public ServiceFramework<IApplicationCreator> {
|
||||
public:
|
||||
IApplicationCreator();
|
||||
};
|
||||
|
||||
class IProcessWindingController final : public ServiceFramework<IProcessWindingController> {
|
||||
public:
|
||||
IProcessWindingController();
|
||||
};
|
||||
|
||||
/// Registers all AM services with the specified service manager.
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include "core/hle/service/am/applet_ae.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
|
||||
namespace Service {
|
||||
namespace AM {
|
||||
namespace Service::AM {
|
||||
|
||||
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
|
||||
public:
|
||||
@@ -21,7 +20,7 @@ public:
|
||||
{2, &ILibraryAppletProxy::GetWindowController, "GetWindowController"},
|
||||
{3, &ILibraryAppletProxy::GetAudioController, "GetAudioController"},
|
||||
{4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"},
|
||||
{10, nullptr, "GetProcessWindingController"},
|
||||
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
|
||||
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
|
||||
{20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
|
||||
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
|
||||
@@ -29,6 +28,93 @@ public:
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ICommonStateGetter>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetSelfController(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISelfController>(nvflinger);
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetWindowController(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IWindowController>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetAudioController(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IAudioController>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetDisplayController(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IDisplayController>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetProcessWindingController(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IProcessWindingController>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IDebugFunctions>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ILibraryAppletCreator>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IApplicationFunctions>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
};
|
||||
|
||||
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
|
||||
public:
|
||||
explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
||||
{1, &ISystemAppletProxy::GetSelfController, "GetSelfController"},
|
||||
{2, &ISystemAppletProxy::GetWindowController, "GetWindowController"},
|
||||
{3, &ISystemAppletProxy::GetAudioController, "GetAudioController"},
|
||||
{4, &ISystemAppletProxy::GetDisplayController, "GetDisplayController"},
|
||||
{10, nullptr, "GetProcessWindingController"},
|
||||
{11, &ISystemAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
|
||||
{20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
|
||||
{21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
|
||||
{22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
|
||||
{1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
@@ -79,16 +165,43 @@ private:
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
||||
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IApplicationFunctions>();
|
||||
rb.PushIpcInterface<IHomeMenuFunctions>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetGlobalStateController(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IGlobalStateController>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void GetApplicationCreator(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IApplicationCreator>();
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
};
|
||||
|
||||
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger);
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger);
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -99,9 +212,9 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
|
||||
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{100, nullptr, "OpenSystemAppletProxy"},
|
||||
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
|
||||
{200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"},
|
||||
{201, nullptr, "OpenLibraryAppletProxy"},
|
||||
{201, &AppletAE::OpenLibraryAppletProxy, "OpenLibraryAppletProxy"},
|
||||
{300, nullptr, "OpenOverlayAppletProxy"},
|
||||
{350, nullptr, "OpenSystemApplicationProxy"},
|
||||
{400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
|
||||
@@ -109,5 +222,4 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace AM
|
||||
} // namespace Service
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -21,6 +21,8 @@ public:
|
||||
~AppletAE() = default;
|
||||
|
||||
private:
|
||||
void OpenSystemAppletProxy(Kernel::HLERequestContext& ctx);
|
||||
void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx);
|
||||
void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include "core/hle/service/am/applet_oe.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
|
||||
namespace Service {
|
||||
namespace AM {
|
||||
namespace Service::AM {
|
||||
|
||||
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
|
||||
public:
|
||||
@@ -99,10 +98,9 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
|
||||
AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
: ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0x00000000, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
|
||||
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace AM
|
||||
} // namespace Service
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/aoc/aoc_u.h"
|
||||
|
||||
namespace Service {
|
||||
namespace AOC {
|
||||
namespace Service::AOC {
|
||||
|
||||
AOC_U::AOC_U() : ServiceFramework("aoc:u") {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -42,5 +41,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<AOC_U>()->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace AOC
|
||||
} // namespace Service
|
||||
} // namespace Service::AOC
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace AOC {
|
||||
namespace Service::AOC {
|
||||
|
||||
class AOC_U final : public ServiceFramework<AOC_U> {
|
||||
public:
|
||||
@@ -22,5 +21,4 @@ private:
|
||||
/// Registers all AOC services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
} // namespace AOC
|
||||
} // namespace Service
|
||||
} // namespace Service::AOC
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
#include "core/hle/service/apm/apm.h"
|
||||
#include "core/hle/service/apm/interface.h"
|
||||
|
||||
namespace Service {
|
||||
namespace APM {
|
||||
namespace Service::APM {
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
auto module_ = std::make_shared<Module>();
|
||||
@@ -16,5 +15,4 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<APM>(module_, "apm:p")->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace APM
|
||||
} // namespace Service
|
||||
} // namespace Service::APM
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user