Compare commits
744 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3583ee0186 | |||
| 475067ca8b | |||
| b9de373b31 | |||
| af05babbb2 | |||
| 899dcb810c | |||
| 0c8be761eb | |||
| 7e21799845 | |||
| 5715f86dc6 | |||
| 54a33a7a15 | |||
| 2e477bbc90 | |||
| 4d914a0321 | |||
| 7c03b6dcc6 | |||
| 95bbc56f97 | |||
| 17791421c4 | |||
| 0046c76e43 | |||
| ac2f1ccbc2 | |||
| b2eaa8d96f | |||
| 4fe34d543f | |||
| 5f0c54ebfe | |||
| 80c9bccc09 | |||
| 676a4342ce | |||
| e5b82a644a | |||
| 2c7de8dea6 | |||
| fb2de33c6d | |||
| 3890840001 | |||
| b360748b82 | |||
| ad2e85079d | |||
| 1a0a684765 | |||
| e178e0fd86 | |||
| bb4f757ba0 | |||
| 4fe734d1c2 | |||
| 0834c21607 | |||
| 40ba2caaf6 | |||
| 8f8c69a04e | |||
| ae46dabeb0 | |||
| b1af8a356f | |||
| 6ca3e47383 | |||
| 7999a896d0 | |||
| 56be23ce6e | |||
| 9233726f58 | |||
| 861e6f88d2 | |||
| a9051ebb91 | |||
| 2a5eff93ec | |||
| ba5e010bb7 | |||
| b1022dfa39 | |||
| f3bef6b403 | |||
| 419ff3c536 | |||
| ecc120b013 | |||
| f6c2eb2a8e | |||
| 7798ed86eb | |||
| b40daef15c | |||
| 2bfe4b427b | |||
| 20853c41f5 | |||
| ef999ebc8f | |||
| 081ed10a8b | |||
| cee25393d8 | |||
| 7914626765 | |||
| a5df2280bc | |||
| 13c5d239ca | |||
| 104edae141 | |||
| dd0cc3725a | |||
| 3129fdccd2 | |||
| a23484237c | |||
| 8e5792303a | |||
| ff4ff35918 | |||
| 28c97afe8e | |||
| 19c65be7b1 | |||
| aa612ade26 | |||
| 9012a0b55b | |||
| daa875fc56 | |||
| 4e24760a22 | |||
| d551d5dc41 | |||
| b35977b654 | |||
| 49f9cb2f29 | |||
| e5366f3ce5 | |||
| 2b8dd96a5c | |||
| c27a28a0c8 | |||
| cfddfe4ebe | |||
| 7dfb749b3d | |||
| bcedb7cb8f | |||
| ab6d312dd5 | |||
| bea8595fd4 | |||
| e839007b6f | |||
| 4c6a53c76d | |||
| 4672ddd20c | |||
| bf803aed94 | |||
| 8ea2ac82ed | |||
| 84416f679e | |||
| 2f11cc39e2 | |||
| c1f2082a72 | |||
| 987350d9f5 | |||
| cd6d23b3b4 | |||
| 12d773a380 | |||
| 01d3cec897 | |||
| 7ec448504f | |||
| ee66b92218 | |||
| 8b5d9209c0 | |||
| 1a46659555 | |||
| 2fb61f3cde | |||
| ccb3e319bf | |||
| 1a281b6805 | |||
| 8557b30fd0 | |||
| 83fec96974 | |||
| c7ed251250 | |||
| 8b45905b5b | |||
| 950edaa65f | |||
| b3efb0e400 | |||
| 27daa8e1f0 | |||
| d2c1c1a5b1 | |||
| 3917bf5dfe | |||
| c03165e98d | |||
| eeec04b0a9 | |||
| 7316ffc203 | |||
| 8cd3f6035a | |||
| f5d8431c08 | |||
| 6f431603a3 | |||
| ce4c6beb24 | |||
| a77325310b | |||
| 8920083308 | |||
| 37d6e51ad4 | |||
| 8932eccf65 | |||
| f92886b990 | |||
| 0aceb22e55 | |||
| 6949e3e869 | |||
| e13b14cdeb | |||
| 882c2974ec | |||
| 182f5dafa1 | |||
| bc7ca1f035 | |||
| 68cea5a830 | |||
| de1398c772 | |||
| ebd3da74d0 | |||
| e90a637fd3 | |||
| 94f5ceed78 | |||
| 5305110a36 | |||
| a307e3c6cf | |||
| 7ad3bd0aba | |||
| 07c812d9b6 | |||
| fba39555a9 | |||
| a92bad74b9 | |||
| 3c442003a3 | |||
| 699108e898 | |||
| cc152ff3b5 | |||
| fcdb1120b5 | |||
| da06324e7c | |||
| 94fa99c526 | |||
| ba538112a6 | |||
| 97dbb7ba22 | |||
| 02b805935f | |||
| 6416a608a2 | |||
| 56057856c0 | |||
| 0393267fbf | |||
| 8f40d8969a | |||
| f5470542e0 | |||
| af28fd5b72 | |||
| 08fb79f1c8 | |||
| 242227cd11 | |||
| 8ce1ab35dd | |||
| eb87e76461 | |||
| f39895a159 | |||
| 244dae57da | |||
| eeba9877f1 | |||
| 9b43ddabcf | |||
| 96e4a29592 | |||
| 2f700e380e | |||
| 11988b8a86 | |||
| 85ad2039b0 | |||
| 140ad98d35 | |||
| 121be48556 | |||
| de2d74c37e | |||
| 6b7e635bc2 | |||
| 176624a008 | |||
| 2a0628cba2 | |||
| e82c736a5e | |||
| bf2bbfcbb3 | |||
| d34f2d11b3 | |||
| 8e71fcfcf6 | |||
| fca6532a21 | |||
| 408023e8fd | |||
| 6a1f1bfeb3 | |||
| 08411ea679 | |||
| 36d0ecf91b | |||
| 001da36b16 | |||
| ea125b0b45 | |||
| 4513edb2ca | |||
| 6e75983c59 | |||
| b8df26b37e | |||
| 7c5eb6727f | |||
| 52ae07ace6 | |||
| 1b98b3be01 | |||
| 45853f0b24 | |||
| 3ad461340b | |||
| 3043098a11 | |||
| 6b91f2f272 | |||
| 8761e80d44 | |||
| 506d0a67b8 | |||
| 5b4a23006b | |||
| edeba499c5 | |||
| 1a76f76faf | |||
| 2b0ce0e6af | |||
| 66e6358b65 | |||
| 94c16b5acf | |||
| c68ace12de | |||
| f65bc145a1 | |||
| d8af8e33c2 | |||
| dc59144946 | |||
| 3dbfed1323 | |||
| 2e6592a7d1 | |||
| 3dee740906 | |||
| a8a99c3295 | |||
| 5214621ff8 | |||
| e368f56817 | |||
| bd1a5320d5 | |||
| 596ff45011 | |||
| 18bba8572c | |||
| d1c04f2651 | |||
| 439a5e6799 | |||
| 73e832504a | |||
| d91c09b54d | |||
| 1a5ff9a83f | |||
| d8791a91db | |||
| 765d9f3421 | |||
| e506611289 | |||
| bfeedfe268 | |||
| b723b276f5 | |||
| 48ec92b486 | |||
| 1b5c19b22a | |||
| 7f2bf081c5 | |||
| adead56cd0 | |||
| 4b14b14964 | |||
| 69d6c8881f | |||
| 94fb198b8a | |||
| 15d0707e74 | |||
| b31fd7d3e5 | |||
| 8f06bbdee4 | |||
| bbcb8131d6 | |||
| fbce4f2db9 | |||
| 453097df1c | |||
| f08027d77c | |||
| c87f1d3063 | |||
| 36438e4fff | |||
| 98e68b7999 | |||
| 85d0783dc2 | |||
| 4116ab3fcc | |||
| 06c6c5efcc | |||
| c163dd3b11 | |||
| 90847d6e6d | |||
| cf655ac368 | |||
| 44868d2ca6 | |||
| 2bfc1b3d2a | |||
| 374008c54a | |||
| 18f6e809a1 | |||
| 480d1a56f4 | |||
| 01b1a67837 | |||
| 0fb5bb187f | |||
| 3ed5bd9754 | |||
| 5c8ecbe3aa | |||
| 7bcf01b4af | |||
| a98f1bffdf | |||
| dcb672ec6e | |||
| 07da88c5bf | |||
| ce7826fb8a | |||
| 68a363ad27 | |||
| 8ddf5782cc | |||
| 4928e62f19 | |||
| 3e9b79fb8d | |||
| e3452e697b | |||
| 26c23e796b | |||
| 6bb8e2e2b3 | |||
| 9198963a86 | |||
| a8d0166f51 | |||
| 2f9fcbc9ba | |||
| ff24f58b9c | |||
| a48739ec8a | |||
| 32034f2700 | |||
| 23c8146c60 | |||
| 62a3c9f3fa | |||
| bdb0d6bd9a | |||
| 98c050ff99 | |||
| 9af8da420a | |||
| b998fe6256 | |||
| 5f2f2eacb3 | |||
| 6abbba92ab | |||
| 81c60dbe6b | |||
| 38ee11cebc | |||
| 495b8e3a1d | |||
| e74be4a92e | |||
| 636aeb12bf | |||
| c29274dc3c | |||
| 97f5a870a2 | |||
| eb49211fad | |||
| b909b1e71d | |||
| 17b63e5437 | |||
| f14f8a1b33 | |||
| 4aa45fdc9e | |||
| a736d897ca | |||
| 8617bff223 | |||
| b3670088e3 | |||
| 58b8311124 | |||
| 15d503bdec | |||
| 5c1a481aae | |||
| 9ab11d59ca | |||
| caac662ef0 | |||
| 2180f06ec9 | |||
| 81f8b2d996 | |||
| b0783213dd | |||
| 6b79a7b98c | |||
| 9d4ae88e25 | |||
| c4ce98a006 | |||
| 16b4d535c6 | |||
| 165046c401 | |||
| b7e174344d | |||
| 9651313246 | |||
| 68f7eb8829 | |||
| 3d4c8a120a | |||
| bb18e729fb | |||
| d6d2a6b971 | |||
| 89ae14faa6 | |||
| 6b6ef2eaff | |||
| 5a8a73e7af | |||
| a140014226 | |||
| 2e99d4073b | |||
| 7d5d364c01 | |||
| 46197a111e | |||
| ccec16e817 | |||
| c8234eb6d9 | |||
| 20c7181dfd | |||
| 4d87f2c7e7 | |||
| 00ef80caef | |||
| 1829a0ef07 | |||
| 44436d748b | |||
| e896983add | |||
| eb67cd6577 | |||
| fc62074952 | |||
| d5f64c2621 | |||
| b1080cbf98 | |||
| ca17bb2b00 | |||
| 9369c8428e | |||
| 9a04e95842 | |||
| ef0fc6911e | |||
| 5b64e840d8 | |||
| ee02327f88 | |||
| 3e306d3e1e | |||
| 85d6a370d5 | |||
| 721d70a7e7 | |||
| ed8b067f6d | |||
| 7474b3ebdf | |||
| 5e0dc0a003 | |||
| 833b4bf518 | |||
| 9656ac9049 | |||
| 392da2a3e6 | |||
| 668dd0a42c | |||
| 005033aa33 | |||
| c6fdac112f | |||
| d9d09a424c | |||
| 5c45a57bd7 | |||
| a86717be2f | |||
| 02471fcf23 | |||
| ae28463a3a | |||
| 3bee1033c4 | |||
| 3208b8b912 | |||
| 5170a0ae04 | |||
| ffdc6c7006 | |||
| 7cc4222180 | |||
| 1a32d13714 | |||
| 3e8229d482 | |||
| 4c364547b0 | |||
| cca2cb5c84 | |||
| 8d0168c55d | |||
| 12faa455c9 | |||
| 8563faf8a2 | |||
| 4f7605518f | |||
| bf916086c0 | |||
| 7502175a8f | |||
| dcf84f1cfb | |||
| a2f410e118 | |||
| 3ef693be92 | |||
| 189ba935e9 | |||
| 45aeaa7768 | |||
| 1757f506e2 | |||
| 80caac88fb | |||
| 2f4f5485f8 | |||
| 33703fd8eb | |||
| e301360da3 | |||
| 1079fc6634 | |||
| 25b3206abc | |||
| ece8d2964c | |||
| e640f1f33c | |||
| dc3e0ff53f | |||
| 817af4f247 | |||
| 26e7861cbb | |||
| 0ed5e90cdf | |||
| 268eea75ad | |||
| 8e5d35d91c | |||
| ff3a0721cf | |||
| c77f526fee | |||
| c82c947772 | |||
| 71dd9859dd | |||
| 311f92e356 | |||
| 86a0784617 | |||
| 114c09b4aa | |||
| 313939b55c | |||
| 764444bda1 | |||
| 0373fdff87 | |||
| 50bfe7b6f9 | |||
| 5b9394ae18 | |||
| d01cdce3d5 | |||
| c80e2c2f3d | |||
| 484e34fc63 | |||
| a0ab25f324 | |||
| 9550b533d1 | |||
| a43fcf1ea1 | |||
| f60095407c | |||
| 42645797c7 | |||
| 1b76250add | |||
| 82f704f0b6 | |||
| 79d75e1080 | |||
| b6ec5c027f | |||
| b262a43894 | |||
| e49ef5b35a | |||
| 2149dd5b52 | |||
| 18b304bc73 | |||
| 1dc2861e1f | |||
| 6fa895652f | |||
| c3a91a5c4b | |||
| dbbbfebfb5 | |||
| f78c3a3107 | |||
| 7bca07990f | |||
| a548b4a91a | |||
| b045b380bb | |||
| d531d703df | |||
| aff846a709 | |||
| 9f5fe06e33 | |||
| 4a2b2a9b83 | |||
| 1278a48b90 | |||
| 3d4343d7a6 | |||
| 8c49ccf541 | |||
| 5a8b2dce24 | |||
| 041daa993f | |||
| 93bb5f2712 | |||
| cefa0a2685 | |||
| d30d2834a4 | |||
| 7d41d61e5d | |||
| 62468ee440 | |||
| 5ed419c970 | |||
| 9e636b23fa | |||
| 2e640249ba | |||
| 817db78f82 | |||
| 9852f7f8a5 | |||
| 2021bc6d05 | |||
| 0e175fa52b | |||
| 05ce7625cf | |||
| 6f5aa6010b | |||
| b50a5bcb0a | |||
| 88b96e5504 | |||
| ea0e91c6d2 | |||
| 5791fc2ff7 | |||
| 88c05525ca | |||
| 900d60d1bb | |||
| 52fa0c75bc | |||
| f8cfd1ca76 | |||
| f473067c1f | |||
| e5a46c4e70 | |||
| ceb28ed109 | |||
| 979499f442 | |||
| 59b46b33c3 | |||
| f8690e1564 | |||
| d90181bdcf | |||
| 95ef8c20ad | |||
| 871149098c | |||
| 451873637f | |||
| 678ccb6c42 | |||
| 89c92a5c96 | |||
| 89949ad627 | |||
| 2ce2559cde | |||
| e2506addb5 | |||
| adebff3edf | |||
| 78c9e5cbfb | |||
| 8f70608030 | |||
| 6ecbc8b033 | |||
| ddb6b34d27 | |||
| 0f5194e888 | |||
| 3faa6ceba6 | |||
| 6a8ce4e041 | |||
| 07d9432d10 | |||
| 5d7653e08d | |||
| 7526955b36 | |||
| cf2c30b98e | |||
| 20162fccf8 | |||
| b6d3e1eb9f | |||
| 2b67d1b35e | |||
| a0dcb44600 | |||
| 3f4dd7fcca | |||
| af51b4a0af | |||
| 7fca4bf655 | |||
| 17dcee17c6 | |||
| f5e760ea39 | |||
| e854bc98ea | |||
| 285a7f8836 | |||
| 63a96285bc | |||
| 8de06ff09c | |||
| 7c593e7dab | |||
| 5bbb45bdf3 | |||
| d91247fd75 | |||
| e1b117955c | |||
| a81be44b67 | |||
| d4572549cc | |||
| bd6bb9a5d5 | |||
| cc0225f24f | |||
| cf7256dc52 | |||
| a86e10e349 | |||
| 0ffc101b7a | |||
| 03465c051e | |||
| 87f794aa66 | |||
| 6fae228ffb | |||
| d24ab92d54 | |||
| e39ea2b4e1 | |||
| b3dac7304b | |||
| d6b6b90c2c | |||
| f33d645fef | |||
| 1ed0558ddb | |||
| 6158ec5cba | |||
| 015d059cb9 | |||
| 24e03c6cb6 | |||
| c8746290b8 | |||
| 3e21fa0ab0 | |||
| 2cb285b8be | |||
| 5c9afa7ba6 | |||
| 8b19d06b18 | |||
| cdd081c664 | |||
| a486eb0791 | |||
| 3aa0145c6b | |||
| 6ee4b00707 | |||
| c30327aeab | |||
| 21d8d4f989 | |||
| 67c1908f7d | |||
| 290cf04e68 | |||
| d2632f08f8 | |||
| 005e1adad1 | |||
| 37149cf8ca | |||
| 3d930cb713 | |||
| 7b803d2dde | |||
| ac2792ef73 | |||
| a1d0a35071 | |||
| 7c50fba50d | |||
| c944c0aaa8 | |||
| 9f126d91d4 | |||
| fc42011e83 | |||
| 1865296ed6 | |||
| a475de7c75 | |||
| e3d776aa9a | |||
| e778af2103 | |||
| f46b2fad04 | |||
| ef334d31f9 | |||
| b11a422bfb | |||
| 5c5fbea171 | |||
| 4b40fc2826 | |||
| 718b71ad2b | |||
| 9d1dd4ad13 | |||
| d2b41aad8d | |||
| 95c632307c | |||
| 13bc13160d | |||
| 9369247e1e | |||
| f9e0a2a3e1 | |||
| 1677be954c | |||
| e9ec6d23c6 | |||
| 9b8523baaf | |||
| 5ca8f73789 | |||
| f7ffda8125 | |||
| 2ea43f1035 | |||
| 37515a6f31 | |||
| 172c298bc0 | |||
| 94b4fc0992 | |||
| f10e951be8 | |||
| 450f9a4ca3 | |||
| 8155fb82c6 | |||
| eb017beb38 | |||
| 7a64f4926e | |||
| 7b449cf8ee | |||
| ed0b32c7dd | |||
| 45625c06ee | |||
| 9b79692876 | |||
| 04779002e2 | |||
| 12ceea8bcc | |||
| 2cb4144c89 | |||
| 62e5cca2f7 | |||
| f29e6408ac | |||
| c4d4575ad6 | |||
| 7d4fc737ed | |||
| 5518d85d99 | |||
| 8968b9b574 | |||
| e1d04c7a6d | |||
| 12c5cffb04 | |||
| 99a952650a | |||
| b9475fddf2 | |||
| 0063dff222 | |||
| 5d7fffe2fc | |||
| 7b6be4b69b | |||
| 4183d27593 | |||
| 4248ad272d | |||
| 5a93015545 | |||
| 70b935972c | |||
| 182952a5cf | |||
| 984f9fdb3e | |||
| 1656139991 | |||
| edacc89db8 | |||
| 11dae52a4c | |||
| 05a31de115 | |||
| f205405ff0 | |||
| 2227b1e53f | |||
| 7460bc8190 | |||
| 7b250db6e6 | |||
| c4bb87e1dd | |||
| bf417316bd | |||
| 0a6c731f60 | |||
| 76d3fa012e | |||
| f3cbc48bfd | |||
| 6c361066e2 | |||
| 1341c9077a | |||
| 4c6788f2f9 | |||
| f522c8fcff | |||
| a03483c956 | |||
| a100fc32d5 | |||
| 950f8715cb | |||
| 5b651c4462 | |||
| 1b7578132e | |||
| c230776cc9 | |||
| 650a70a309 | |||
| e4b0fb8798 | |||
| 4a4a3e863d | |||
| 5c2c99b21f | |||
| 87270603ef | |||
| 9b6ea8119f | |||
| b8af486e03 | |||
| 5eea2a974f | |||
| ff1e2363e8 | |||
| 62a8708d6f | |||
| 7997bff21f | |||
| 175316f8c5 | |||
| d1b99a2694 | |||
| 52df3ad5f6 | |||
| 6360b4fa9c | |||
| ec16a049d6 | |||
| 373a3aedad | |||
| 1c8f921114 | |||
| c925808697 | |||
| e12a20e1e5 | |||
| 930983dea3 | |||
| e6e1eb8520 | |||
| 159787463e | |||
| 2034d46129 | |||
| 739704cbfa | |||
| e46dc1813d | |||
| 046b9cdd26 | |||
| 5d7d3a2761 | |||
| e285ccc9e0 | |||
| b41d0b01e1 | |||
| d71788fb7e | |||
| 39ae086a73 | |||
| 6502dd8614 | |||
| c725436721 | |||
| 42a1ffc622 | |||
| 0735fb9085 | |||
| 8d56b7bb8b | |||
| 0d1bb48ec2 | |||
| 4688a08169 | |||
| 86708e1eee | |||
| e210f6d0cb | |||
| f47f7a9c57 | |||
| 2ce2e408c5 | |||
| d2fa9576e4 | |||
| 1e2a9e37ef | |||
| 4bbd6058d8 | |||
| 169aa5d8cb | |||
| a8bf8b65f9 | |||
| 6241599c57 | |||
| 2bb085d3c1 | |||
| 35f224d59a | |||
| 5110aac16c | |||
| d238fb31d6 | |||
| b2a80d5de5 | |||
| a4833b69c5 | |||
| 6c432dd7be | |||
| ed04c28b91 | |||
| 638a2b90e3 | |||
| d77860bd0f | |||
| 3dd64d32db | |||
| 1da869b1c4 | |||
| a2e13f591c | |||
| d1557d7ce5 | |||
| b0fdd3f818 | |||
| 32574c98bf | |||
| 61f124b77f | |||
| d8d7d1a693 | |||
| f5e9cda070 | |||
| 6113a6d450 | |||
| df22174f5a | |||
| bee7ab2e0e | |||
| 68ff6a6656 | |||
| f5a130575d | |||
| c416834c8b | |||
| 8422452444 | |||
| 7970084d4f | |||
| 10bda5a299 | |||
| c7b093bc42 | |||
| e285b5bcec | |||
| 8a558d5afd | |||
| 0fe7074232 | |||
| 31b105ad78 | |||
| 94cfdda09f | |||
| eb6b79e625 | |||
| 7633a25cbd | |||
| 0a98bff07a | |||
| 66ea2422a0 | |||
| 27dbb101fa | |||
| bb57c1031f | |||
| c1d8fc839b | |||
| d47c9298ab | |||
| 3d78bb8194 | |||
| 40ce88bed7 | |||
| b4b6408771 | |||
| de23fe7826 | |||
| a8bb15797f | |||
| b16afee2b3 | |||
| cb2d021312 | |||
| 024beab5a1 | |||
| 5404799f12 | |||
| 68aa94ce98 | |||
| 5b95e9aa9c | |||
| 2d4baebbe0 | |||
| 2140bcad79 | |||
| 808a6eee6a | |||
| 86195579a0 | |||
| ca71566fdb | |||
| 8fadf583ec | |||
| d863784d9a | |||
| 39d0814ebd | |||
| 7f15039178 | |||
| 88702627ec | |||
| e743624685 | |||
| 1667d5d349 | |||
| 824e50ca9c | |||
| 20807a0ebf | |||
| ad710d13bc | |||
| 0d1a04df87 |
@@ -19,7 +19,113 @@ human-initiated operations. Durable Red Bear state belongs in `local/patches/`,
|
|||||||
The current baseline is **Red Bear OS 0.1.0** (Redox snapshot at build-system commit `f55acba68`).
|
The current baseline is **Red Bear OS 0.1.0** (Redox snapshot at build-system commit `f55acba68`).
|
||||||
All recipe sources are pinned and archived in `sources/redbear-0.1.0/`.
|
All recipe sources are pinned and archived in `sources/redbear-0.1.0/`.
|
||||||
|
|
||||||
## NO SILENT UPSTREAM PULLS — OFFLINE-FIRST POLICY
|
## BUILD SYSTEM DURABILITY — THE CARDINAL RULE
|
||||||
|
|
||||||
|
**THE `recipes/*/source/` DIRECTORY WILL ALWAYS BE REWRITTEN. DO NOT EVER USE IT FOR ANY
|
||||||
|
WORK THAT YOU INTEND TO KEEP. THOSE TREES ARE EPHEMERAL — THEY ARE DESTROYED AND REGENERATED
|
||||||
|
ON EVERY `repo fetch`, `repo cook`, `make clean`, AND `make distclean`. ANY EDIT MADE THERE
|
||||||
|
WILL BE SILENTLY LOST ON THE NEXT BUILD. COMMITTING TO A SUBMODULE INSIDE `source/` DOES NOT
|
||||||
|
PROTECT YOUR WORK — THE ENTIRE DIRECTORY IS DELETED AND RE-CLONED/RE-EXTRACTED FROM SCRATCH.**
|
||||||
|
|
||||||
|
This is the #1 mistake AI agents and new contributors make. It has caused repeated work loss
|
||||||
|
in this project. The rule is:
|
||||||
|
|
||||||
|
| What you want to do | Where to do it |
|
||||||
|
|---|---|
|
||||||
|
| Change a kernel source file | Create or update a patch in `local/patches/kernel/` |
|
||||||
|
| Change an init or daemon source file | Create or update a patch in `local/patches/base/` |
|
||||||
|
| Change relibc | Create or update a patch in `local/patches/relibc/` |
|
||||||
|
| Change a driver | Create or update a patch in `local/patches/base/` or `local/patches/<driver>/` |
|
||||||
|
| Add a new package | Create a recipe in `local/recipes/<category>/<name>/` |
|
||||||
|
| Change build config | Edit `config/redbear-*.toml` |
|
||||||
|
| Add documentation | Write to `local/docs/` |
|
||||||
|
|
||||||
|
### How the build system works
|
||||||
|
|
||||||
|
```
|
||||||
|
repo cook <package>
|
||||||
|
├── repo fetch <package>
|
||||||
|
│ ├── Clone/fetch upstream source → recipes/<pkg>/source/
|
||||||
|
│ ├── Apply patches from recipe.toml → patches are read from local/patches/<pkg>/
|
||||||
|
│ └── Source tree is now fully patched and ready for build
|
||||||
|
├── Cargo/cmake/configure build
|
||||||
|
└── Stage artifacts into sysroot
|
||||||
|
```
|
||||||
|
|
||||||
|
The `source/` directory is a disposable working copy. It is produced at the start of every
|
||||||
|
build by cloning the upstream source + applying patches sequentially. The recipe's
|
||||||
|
`patches = [...]` list in `recipe.toml` controls which patches are applied.
|
||||||
|
|
||||||
|
### Two-layer architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Layer 1: Ephemeral (destroyed on clean/fetch/rebuild)
|
||||||
|
recipes/<pkg>/source/ ← working tree, cloned + patched
|
||||||
|
build/ ← build outputs
|
||||||
|
target/ ← cargo target dir
|
||||||
|
|
||||||
|
Layer 2: Durable (survives clean/fetch/rebuild/release provisioning)
|
||||||
|
local/patches/<pkg>/ ← .patch files — the actual source code changes
|
||||||
|
local/recipes/<pkg>/ ← custom recipe directories
|
||||||
|
config/redbear-*.toml ← Red Bear OS build configs
|
||||||
|
local/docs/ ← planning and integration docs
|
||||||
|
recipes/<pkg>/recipe.toml ← the patches list (git-tracked)
|
||||||
|
```
|
||||||
|
|
||||||
|
### The correct workflow for any source change
|
||||||
|
|
||||||
|
1. **Make the change** in `recipes/<pkg>/source/` to validate it compiles
|
||||||
|
2. **Generate a patch**: `cd recipes/<pkg>/source && git diff > ../../../local/patches/<pkg>/my-fix.patch`
|
||||||
|
3. **Wire the patch**: add `"my-fix.patch"` to the recipe's `recipe.toml` `patches = [...]` list
|
||||||
|
4. **Validate**: `./target/release/repo validate-patches <pkg>`
|
||||||
|
5. **Rebuild**: `./target/release/repo cook <pkg>`
|
||||||
|
6. **Commit**: `git add local/patches/ recipes/<pkg>/recipe.toml && git commit`
|
||||||
|
|
||||||
|
### Common anti-patterns
|
||||||
|
|
||||||
|
| Anti-pattern | Why it fails |
|
||||||
|
|---|---|
|
||||||
|
| Editing `source/` files then running `make all` | `make all` calls `repo fetch` which regenerates `source/` — edits are lost |
|
||||||
|
| Creating a patch but not wiring it into `recipe.toml` | Patch file exists but is never applied — build uses unpatched source |
|
||||||
|
| **Hand-writing patches manually** | **FORBIDDEN. Unified diffs hand-written by humans routinely have incorrect line counts, wrong context, malformed hunks, or timestamp headers — all of which cause `patch(1)` to reject them. The ONLY acceptable way to generate patches is `git diff -U0 -w` from a committed source tree baseline.** |
|
||||||
|
| Editing `recipe.toml` patches list without creating the actual `.patch` file | Build fails with "missing patch" error |
|
||||||
|
| Editing `recipe.toml` patches list without creating the actual `.patch` file | Build fails with "missing patch" error |
|
||||||
|
| Expecting `source/` changes to survive `make clean` | `make clean` deletes `source/` directories |
|
||||||
|
| Running `repo cook` without `--allow-protected` for core packages | Protected recipes (kernel, relibc, base) are offline-only by default |
|
||||||
|
|
||||||
|
### Patch file location convention
|
||||||
|
|
||||||
|
- `local/patches/base/` — for the `base` package (init, daemon, all drivers)
|
||||||
|
- `local/patches/kernel/` — for the kernel
|
||||||
|
- `local/patches/relibc/` — for relibc
|
||||||
|
- `local/patches/installer/` — for the installer
|
||||||
|
- `local/patches/bootloader/` — for the bootloader
|
||||||
|
- `local/patches/<package>/` — for any other patched package
|
||||||
|
|
||||||
|
### Recipe patch wiring
|
||||||
|
|
||||||
|
Each recipe's `recipe.toml` lists patches relative to `local/patches/<pkg>/`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[source]
|
||||||
|
git = "https://gitlab.redox-os.org/redox-os/base.git"
|
||||||
|
rev = "463f76b96..."
|
||||||
|
patches = [
|
||||||
|
"P0-daemon-fix-init-notify-unwrap.patch", # applied first
|
||||||
|
"P9-init-scheduler-completed.patch", # applied second
|
||||||
|
# ... more patches
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Patches are applied in listed order. Dependencies between patches must be respected (a patch
|
||||||
|
that defines a type must come before a patch that uses it).
|
||||||
|
|
||||||
|
### Kernel-specific notes
|
||||||
|
|
||||||
|
The kernel source at `recipes/core/kernel/source/` is a separate git worktree (rev `866dfad`).
|
||||||
|
The kernel recipe is at `recipes/core/kernel/recipe.toml` and patches are at
|
||||||
|
`local/patches/kernel/`. The same durability rules apply — all kernel changes must be
|
||||||
|
in `local/patches/kernel/*.patch`, never in the `source/` tree directly.
|
||||||
|
|
||||||
**Red Bear OS is offline-first by default. No script, build target, or tool may silently pull
|
**Red Bear OS is offline-first by default. No script, build target, or tool may silently pull
|
||||||
from any upstream repository without explicit user instruction.**
|
from any upstream repository without explicit user instruction.**
|
||||||
@@ -178,10 +284,24 @@ make all
|
|||||||
→ mk/fstools.mk (build cookbook repo binary + fstools)
|
→ mk/fstools.mk (build cookbook repo binary + fstools)
|
||||||
→ mk/repo.mk (repo cook --filesystem=config/*.toml)
|
→ mk/repo.mk (repo cook --filesystem=config/*.toml)
|
||||||
→ For each recipe: fetch source → apply patches → build → stage into sysroot
|
→ For each recipe: fetch source → apply patches → build → stage into sysroot
|
||||||
|
→ Each successful build produces repo/<arch>/<name>.pkgar + <name>.toml
|
||||||
→ mk/disk.mk (create filesystem.img, harddrive.img, redbear-live.iso or harddrive.img)
|
→ mk/disk.mk (create filesystem.img, harddrive.img, redbear-live.iso or harddrive.img)
|
||||||
→ redoxfs-mkfs → redox_installer → bootloader embedding
|
→ redoxfs-mkfs → redox_installer → bootloader embedding
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Build Outputs
|
||||||
|
|
||||||
|
Every successful `repo cook <package>` produces:
|
||||||
|
|
||||||
|
| Artifact | Location | Purpose |
|
||||||
|
|----------|----------|---------|
|
||||||
|
| Package archive | `repo/x86_64-unknown-redox/<name>.pkgar` | Binary package for image assembly |
|
||||||
|
| Package manifest | `repo/x86_64-unknown-redox/<name>.toml` | Metadata, version, deps, hashes |
|
||||||
|
| Staged sysroot | `recipes/*/<name>/target/.../stage/` | Files for `repo push` |
|
||||||
|
| Source tree | `recipes/*/<name>/source/` | Fetched + patched source (disposable) |
|
||||||
|
|
||||||
|
**A build is not complete until the .pkgar and .toml exist in `repo/`.**
|
||||||
|
|
||||||
## CONVENTIONS
|
## CONVENTIONS
|
||||||
|
|
||||||
- **Rust edition 2024**, nightly channel
|
- **Rust edition 2024**, nightly channel
|
||||||
@@ -444,6 +564,65 @@ or any path that is already git-tracked and not inside a fetched source tree.
|
|||||||
|
|
||||||
## BUILD SYSTEM POLICIES
|
## BUILD SYSTEM POLICIES
|
||||||
|
|
||||||
|
### Build Durability Rule — Every Build Lands in the Repo
|
||||||
|
|
||||||
|
Every successful `repo cook` produces two durable artifacts:
|
||||||
|
|
||||||
|
1. **Package in the repo**: `repo/x86_64-unknown-redox/<name>.pkgar` + `<name>.toml`
|
||||||
|
2. **Patched source form**: All source modifications are in `local/patches/<component>/` and wired into `recipe.toml`
|
||||||
|
|
||||||
|
A build is **not complete** until both artifacts exist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# After cooking, verify the package is in the repo
|
||||||
|
./target/release/repo find <package>
|
||||||
|
|
||||||
|
# Check the repo manifest exists
|
||||||
|
ls repo/x86_64-unknown-redox/<package>.toml
|
||||||
|
ls repo/x86_64-unknown-redox/<package>.pkgar
|
||||||
|
```
|
||||||
|
|
||||||
|
If a package was built but the repo artifacts are missing, the build did not complete.
|
||||||
|
Re-run `repo cook <package>` to regenerate them.
|
||||||
|
|
||||||
|
If source patches were applied but not mirrored to `local/patches/`, see the
|
||||||
|
DURABILITY POLICY section above.
|
||||||
|
|
||||||
|
### Cascade Rebuild Rule
|
||||||
|
|
||||||
|
When a low-level package changes (relibc, kernel, base, or any library), **all
|
||||||
|
packages that depend on it must be rebuilt**. A stale dependent silently produces
|
||||||
|
link errors, ABI mismatches, or runtime crashes.
|
||||||
|
|
||||||
|
Use the cascade rebuild script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rebuild relibc and everything that depends on it
|
||||||
|
./local/scripts/rebuild-cascade.sh relibc
|
||||||
|
|
||||||
|
# Dry run: show what would be rebuilt without building
|
||||||
|
./local/scripts/rebuild-cascade.sh --dry-run relibc
|
||||||
|
|
||||||
|
# Multiple root packages
|
||||||
|
./local/scripts/rebuild-cascade.sh relibc ncurses
|
||||||
|
```
|
||||||
|
|
||||||
|
The script:
|
||||||
|
1. Finds all packages whose `recipe.toml` lists the target in `dependencies`
|
||||||
|
2. Transitively expands the reverse dependency graph (BFS)
|
||||||
|
3. Builds the root package(s) first, then dependents in order
|
||||||
|
4. Pushes all rebuilt packages to the sysroot
|
||||||
|
|
||||||
|
**When to use cascade rebuilds:**
|
||||||
|
- After changing relibc headers or ABI
|
||||||
|
- After rebuilding a shared library (ncurses, zlib, openssl, etc.)
|
||||||
|
- After kernel ABI changes that affect userspace
|
||||||
|
- After any change to a package listed in other packages' `dependencies`
|
||||||
|
|
||||||
|
**When NOT to use cascade rebuilds:**
|
||||||
|
- Standalone applications with no dependents (editors, games, utilities)
|
||||||
|
- Terminal/leaf packages that nothing depends on
|
||||||
|
|
||||||
### Atomic Patch Application
|
### Atomic Patch Application
|
||||||
|
|
||||||
The cookbook tool (`src/cook/fetch.rs`) applies patches **atomically**:
|
The cookbook tool (`src/cook/fetch.rs`) applies patches **atomically**:
|
||||||
@@ -466,12 +645,78 @@ Patches may use either format:
|
|||||||
|
|
||||||
Git-specific headers (`diff --git`, `diff -ruN`, `index`, `new file mode`, `rename from/to`,
|
Git-specific headers (`diff --git`, `diff -ruN`, `index`, `new file mode`, `rename from/to`,
|
||||||
`similarity index`, `dissimilarity index`) are automatically stripped before
|
`similarity index`, `dissimilarity index`) are automatically stripped before
|
||||||
`patch` is invoked. The build system uses `--fuzz=0` for strict context matching.
|
`patch` is invoked. The build system uses `--fuzz=3` for resilient context matching.
|
||||||
|
|
||||||
**Timestamps in `---`/`+++` lines** (common in `diff -ruN` output) should be removed.
|
**Timestamps in `---`/`+++` lines** (common in `diff -ruN` output) should be removed.
|
||||||
Use `--- a/path` and `+++ b/path` without timestamps. The `normalize_patch` function
|
Use `--- a/path` and `+++ b/path` without timestamps. The `normalize_patch` function
|
||||||
does NOT strip timestamps — they should be removed from the patch file directly.
|
does NOT strip timestamps — they should be removed from the patch file directly.
|
||||||
|
|
||||||
|
### Robust Patch Generation (REQUIRED)
|
||||||
|
|
||||||
|
**MANDATORY: All patches MUST be generated using `git diff -U0 -w` from a committed source tree.
|
||||||
|
Hand-writing unified diffs is FORBIDDEN — it routinely produces incorrect line counts, malformed
|
||||||
|
hunks, or timestamp headers that cause `patch(1)` to reject them. The build system uses
|
||||||
|
`--fuzz=3` for resilient context matching, which requires properly generated diffs.**
|
||||||
|
|
||||||
|
Context-line mismatches (renamed variables, shifted line numbers, upstream refactors)
|
||||||
|
are the single largest source of patch application failures. Use the zero-context,
|
||||||
|
whitespace-ignored technique to make patches resilient to drift:
|
||||||
|
|
||||||
|
**Workflow (mandatory):**
|
||||||
|
```bash
|
||||||
|
# 1. Start with a clean P0..P(N-1) source tree (repo fetch already applied earlier patches)
|
||||||
|
cd recipes/<component>/source
|
||||||
|
|
||||||
|
# 2. Commit the P0..P(N-1) state as a git baseline
|
||||||
|
git add -A && git commit -m "P0..P(N-1) baseline"
|
||||||
|
|
||||||
|
# 3. Make P(N) edits in the source tree
|
||||||
|
# (edit files, test compile, etc.)
|
||||||
|
|
||||||
|
# 4. Generate the P(N) patch using ONLY git diff -U0 -w:
|
||||||
|
git diff -U0 -w > ../../../local/patches/<component>/P<N>-<description>.patch
|
||||||
|
|
||||||
|
# 5. Wire the patch into recipe.toml patches list
|
||||||
|
|
||||||
|
# 6. Validate: repo validate-patches <package>
|
||||||
|
# 7. Rebuild: repo cook <package>
|
||||||
|
# 8. Commit: git add local/patches/ recipes/<pkg>/recipe.toml && git commit
|
||||||
|
```
|
||||||
|
|
||||||
|
**Apply (for manual testing):**
|
||||||
|
```bash
|
||||||
|
patch -p1 --fuzz=3 < local/patches/<component>/P<N>-<description>.patch>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this works:**
|
||||||
|
- `-U0` produces zero lines of surrounding context, so the patch has no fragile context
|
||||||
|
lines that can drift when surrounding code changes
|
||||||
|
- `-w` ignores all whitespace changes, so indentation-only refactors don't break the patch
|
||||||
|
- `--fuzz=3` allows `patch(1)` to find the target location even when nearby lines have shifted
|
||||||
|
- Together these three flags eliminate the entire class of "context mismatch" failures
|
||||||
|
|
||||||
|
**Why hand-writing is forbidden:**
|
||||||
|
- Human-written diffs routinely have wrong `@@` line counts, missing or extra context lines,
|
||||||
|
incorrect `--- a/` / `+++ b/` paths, or embedded timestamps — all of which cause `patch(1)`
|
||||||
|
to reject the patch or silently apply it to the wrong location
|
||||||
|
- The `git diff -U0 -w` command produces mechanically correct diffs every time
|
||||||
|
|
||||||
|
**Before this technique**, patches routinely broke when:
|
||||||
|
- A variable was renamed (e.g., `deamon` → `daemon` in context)
|
||||||
|
- Lines were added or removed above the changed code
|
||||||
|
- Indentation was reformatted
|
||||||
|
- An earlier patch in the chain shifted line numbers
|
||||||
|
|
||||||
|
**With this technique**, patches survive all of the above. A hunk consists only of the
|
||||||
|
changed lines themselves — no context that can go stale.
|
||||||
|
|
||||||
|
**Conventions:**
|
||||||
|
- Always use `--- a/path` and `+++ b/path` headers (no timestamps)
|
||||||
|
- Always name patches `P<N>-<description>.patch` with sequential numbering
|
||||||
|
- Always wire patches into `recipe.toml` `patches = [...]` in application order
|
||||||
|
- Always validate with `repo validate-patches <package>` after creating or editing a patch
|
||||||
|
- When updating an existing patch, regenerate it entirely rather than editing line numbers manually
|
||||||
|
|
||||||
### Protected Recipes
|
### Protected Recipes
|
||||||
|
|
||||||
Core recipes (`base`, `kernel`, `relibc`, `bootloader`, etc.) and any recipe carrying
|
Core recipes (`base`, `kernel`, `relibc`, `bootloader`, etc.) and any recipe carrying
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
+1
-1
@@ -18,7 +18,7 @@ path = "/usr/lib/init.d/10_acid.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Acid test runner"
|
description = "Acid test runner"
|
||||||
requires_weak = ["00_pcid-spawner.service"]
|
requires_weak = ["00_driver-manager.service"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "ion"
|
cmd = "ion"
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Red Bear OS boot stage targets
|
||||||
|
#
|
||||||
|
# Semantic boot stages that create ordering through the init system's
|
||||||
|
# BFS dependency traversal. Each target depends on the previous one.
|
||||||
|
#
|
||||||
|
# Stage mapping:
|
||||||
|
# 00_base.target — kernel schemes ready (defined in base package initfs)
|
||||||
|
# 02_early_hw.target — ACPI + PCI bus access ready
|
||||||
|
# 04_drivers.target — driver spawning complete
|
||||||
|
# 06_services.target — system services (D-Bus, session broker)
|
||||||
|
# 08_userland.target — user-facing (console, greeter, desktop)
|
||||||
|
#
|
||||||
|
# Services use requires_weak against their stage target.
|
||||||
|
# Targets use requires_weak to chain to the previous stage.
|
||||||
|
#
|
||||||
|
# Serial boot markers (02-08_serial_*.service) echo a stage completion
|
||||||
|
# message to stderr, which appears on the serial console for diagnostics.
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/02_early_hw.target"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Early hardware: ACPI + PCI bus access"
|
||||||
|
requires_weak = [
|
||||||
|
"00_base.target",
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/02_serial_early_hw.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Serial boot marker: early hardware stage"
|
||||||
|
requires_weak = ["02_early_hw.target"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "echo"
|
||||||
|
args = ["RB_STAGE_02_EARLY_HW"]
|
||||||
|
type = "oneshot"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/04_drivers.target"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Driver spawning stage"
|
||||||
|
requires_weak = [
|
||||||
|
"02_early_hw.target",
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/04_serial_drivers.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Serial boot marker: drivers stage"
|
||||||
|
requires_weak = ["04_drivers.target"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "echo"
|
||||||
|
args = ["RB_STAGE_04_DRIVERS"]
|
||||||
|
type = "oneshot"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/06_services.target"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "System services: D-Bus, session broker, seat management"
|
||||||
|
requires_weak = [
|
||||||
|
"04_drivers.target",
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/06_serial_services.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Serial boot marker: services stage"
|
||||||
|
requires_weak = ["06_services.target"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "echo"
|
||||||
|
args = ["RB_STAGE_06_SERVICES"]
|
||||||
|
type = "oneshot"
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/08_userland.target"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "User-facing: console, greeter, desktop"
|
||||||
|
requires_weak = [
|
||||||
|
"06_services.target",
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/08_serial_userland.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Serial boot marker: userland stage"
|
||||||
|
requires_weak = ["08_userland.target"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "echo"
|
||||||
|
args = ["RB_STAGE_08_USERLAND"]
|
||||||
|
type = "oneshot"
|
||||||
|
"""
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
# Red Bear OS shared device-service wiring
|
# Red Bear OS shared device-service wiring
|
||||||
#
|
#
|
||||||
# Shared by profiles that ship the firmware/input/Wi-Fi control compatibility stack.
|
# Shared by profiles that ship the firmware/input/Wi-Fi control compatibility stack.
|
||||||
|
#
|
||||||
|
# Driver matching: driver-manager reads /lib/drivers.d/*.toml and matches against
|
||||||
|
# devices from both PCI and ACPI buses. ACPI devices are classified with PCI-equivalent
|
||||||
|
# class/subclass/vendor codes by redox-driver-acpi's AcpiBus, allowing reuse of existing
|
||||||
|
# driver match rules.
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
redbear-quirks = {}
|
redbear-quirks = {}
|
||||||
@@ -32,9 +37,9 @@ data = """
|
|||||||
path = "/etc/init.d/12_boot-late.target"
|
path = "/etc/init.d/12_boot-late.target"
|
||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Late boot services target"
|
description = "Late boot services target (compat alias for 04_drivers.target)"
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_base.target",
|
"04_drivers.target",
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -54,6 +59,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/nvmed"]
|
command = ["/usr/lib/drivers/nvmed"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 1
|
class = 1
|
||||||
subclass = 8
|
subclass = 8
|
||||||
|
|
||||||
@@ -64,6 +70,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/ahcid"]
|
command = ["/usr/lib/drivers/ahcid"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 1
|
class = 1
|
||||||
subclass = 6
|
subclass = 6
|
||||||
|
|
||||||
@@ -74,6 +81,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/ided"]
|
command = ["/usr/lib/drivers/ided"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 1
|
class = 1
|
||||||
subclass = 1
|
subclass = 1
|
||||||
|
|
||||||
@@ -84,6 +92,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/virtio-blkd"]
|
command = ["/usr/lib/drivers/virtio-blkd"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1AF4
|
vendor = 0x1AF4
|
||||||
device = 0x1001
|
device = 0x1001
|
||||||
class = 1
|
class = 1
|
||||||
@@ -100,6 +109,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/e1000d"]
|
command = ["/usr/lib/drivers/e1000d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 2
|
class = 2
|
||||||
|
|
||||||
@@ -110,6 +120,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/rtl8168d"]
|
command = ["/usr/lib/drivers/rtl8168d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x10EC
|
vendor = 0x10EC
|
||||||
class = 2
|
class = 2
|
||||||
|
|
||||||
@@ -120,6 +131,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/rtl8139d"]
|
command = ["/usr/lib/drivers/rtl8139d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x10EC
|
vendor = 0x10EC
|
||||||
device = 0x8139
|
device = 0x8139
|
||||||
|
|
||||||
@@ -130,6 +142,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/ixgbed"]
|
command = ["/usr/lib/drivers/ixgbed"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 2
|
class = 2
|
||||||
subclass = 0
|
subclass = 0
|
||||||
@@ -141,6 +154,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/virtio-netd"]
|
command = ["/usr/lib/drivers/virtio-netd"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1AF4
|
vendor = 0x1AF4
|
||||||
class = 2
|
class = 2
|
||||||
"""
|
"""
|
||||||
@@ -155,6 +169,7 @@ priority = 80
|
|||||||
command = ["/usr/lib/drivers/xhcid"]
|
command = ["/usr/lib/drivers/xhcid"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x0C
|
class = 0x0C
|
||||||
subclass = 0x03
|
subclass = 0x03
|
||||||
prog_if = 0x30
|
prog_if = 0x30
|
||||||
@@ -169,6 +184,7 @@ command = ["/usr/lib/drivers/ehcid"]
|
|||||||
# control-transfer pass-through while the wider USB stack continues converging.
|
# control-transfer pass-through while the wider USB stack continues converging.
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x0C
|
class = 0x0C
|
||||||
subclass = 0x03
|
subclass = 0x03
|
||||||
prog_if = 0x20
|
prog_if = 0x20
|
||||||
@@ -180,6 +196,7 @@ priority = 80
|
|||||||
command = ["/usr/lib/drivers/ohcid"]
|
command = ["/usr/lib/drivers/ohcid"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x0C
|
class = 0x0C
|
||||||
subclass = 0x03
|
subclass = 0x03
|
||||||
prog_if = 0x10
|
prog_if = 0x10
|
||||||
@@ -191,6 +208,7 @@ priority = 80
|
|||||||
command = ["/usr/lib/drivers/uhcid"]
|
command = ["/usr/lib/drivers/uhcid"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x0C
|
class = 0x0C
|
||||||
subclass = 0x03
|
subclass = 0x03
|
||||||
prog_if = 0x00
|
prog_if = 0x00
|
||||||
@@ -206,6 +224,7 @@ priority = 60
|
|||||||
command = ["/usr/bin/redox-drm"]
|
command = ["/usr/bin/redox-drm"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x03
|
class = 0x03
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -233,6 +252,7 @@ priority = 40
|
|||||||
command = ["/usr/lib/drivers/ihdad"]
|
command = ["/usr/lib/drivers/ihdad"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 0x04
|
class = 0x04
|
||||||
|
|
||||||
@@ -243,10 +263,89 @@ priority = 40
|
|||||||
command = ["/usr/lib/drivers/ac97d"]
|
command = ["/usr/lib/drivers/ac97d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x04
|
class = 0x04
|
||||||
subclass = 0x01
|
subclass = 0x01
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/00_acpid.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "ACPI daemon (provides scheme:acpi)"
|
||||||
|
default_dependencies = false
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "acpid"
|
||||||
|
inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"]
|
||||||
|
type = "notify"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ACPI GPIO/I2C controller drivers
|
||||||
|
# These match against ACPI-enumerated devices (class/subclass/vendor from _HID).
|
||||||
|
[[files]]
|
||||||
|
path = "/lib/drivers.d/60-gpio-i2c.toml"
|
||||||
|
data = """
|
||||||
|
# I2C bus registry — infrastructure, no hardware match
|
||||||
|
[[driver]]
|
||||||
|
name = "i2cd"
|
||||||
|
description = "I2C host adapter registry"
|
||||||
|
priority = 85
|
||||||
|
command = ["/usr/lib/drivers/i2cd"]
|
||||||
|
|
||||||
|
# GPIO pin registry — infrastructure, no hardware match
|
||||||
|
[[driver]]
|
||||||
|
name = "gpiod"
|
||||||
|
description = "GPIO controller registry"
|
||||||
|
priority = 85
|
||||||
|
command = ["/usr/lib/drivers/gpiod"]
|
||||||
|
|
||||||
|
# Intel ACPI I2C controller (DesignWare)
|
||||||
|
# Matches: INT33C3, INT3433, INT3442, INT3446, INT3447, INT3455, INT34B9
|
||||||
|
[[driver]]
|
||||||
|
name = "dw-acpi-i2cd"
|
||||||
|
description = "DesignWare ACPI I2C controller"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/dw-acpi-i2cd"]
|
||||||
|
depends_on = ["acpi", "i2c"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x05
|
||||||
|
vendor = 0x8086
|
||||||
|
|
||||||
|
# AMD MP2 I2C controller
|
||||||
|
# Matches: AMDI0010, AMDI0510, AMDI0019
|
||||||
|
[[driver]]
|
||||||
|
name = "amd-mp2-i2cd"
|
||||||
|
description = "AMD MP2 I2C controller"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/amd-mp2-i2cd"]
|
||||||
|
depends_on = ["acpi", "i2c"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x05
|
||||||
|
vendor = 0x1022
|
||||||
|
|
||||||
|
# Intel ACPI GPIO controller
|
||||||
|
# Matches: INT33C7, INT3437, INT3450, INT345D, INT34BB
|
||||||
|
[[driver]]
|
||||||
|
name = "intel-gpiod"
|
||||||
|
description = "Intel ACPI GPIO registrar"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/intel-gpiod"]
|
||||||
|
depends_on = ["acpi", "gpio"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x80
|
||||||
|
vendor = 0x8086
|
||||||
|
"""
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
path = "/lib/drivers.d/70-usb-class.toml"
|
path = "/lib/drivers.d/70-usb-class.toml"
|
||||||
data = """
|
data = """
|
||||||
@@ -281,15 +380,15 @@ vendor = 0xFFFF
|
|||||||
device = 0xFFFF
|
device = 0xFFFF
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Profiles that include this fragment should start `driver-manager` instead of
|
# driver-manager owns PCI device enumeration, driver matching, and bind/channel
|
||||||
# `pcid-spawner`; the manager performs the PCI bind/channel handoff itself.
|
# handoff — replacing the old pcid + pcid-spawner pair entirely.
|
||||||
[[files]]
|
[[files]]
|
||||||
path = "/etc/init.d/00_driver-manager.service"
|
path = "/etc/init.d/00_driver-manager.service"
|
||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Red Bear driver manager"
|
description = "Red Bear driver manager"
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_base.target",
|
"02_early_hw.target",
|
||||||
]
|
]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
@@ -298,33 +397,26 @@ args = ["--hotplug"]
|
|||||||
type = "oneshot_async"
|
type = "oneshot_async"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Override the base package's 30_thermald.service with a no-op since
|
||||||
|
# 15_thermald.service (above) replaces it with earlier start ordering.
|
||||||
[[files]]
|
[[files]]
|
||||||
path = "/etc/init.d/10_evdevd.service"
|
path = "/etc/init.d/30_thermald.service"
|
||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Evdev input daemon"
|
description = "Thermal management daemon (suppressed; use 15_thermald.service)"
|
||||||
requires_weak = [
|
|
||||||
"12_boot-late.target",
|
|
||||||
"00_driver-manager.service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "evdevd"
|
cmd = "echo"
|
||||||
type = "oneshot_async"
|
args = ["thermald: started earlier as 15_thermald.service"]
|
||||||
|
type = "oneshot"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/firmware-fallbacks.d"
|
|
||||||
data = ""
|
|
||||||
directory = true
|
|
||||||
mode = 0o755
|
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
path = "/etc/init.d/15_cpufreqd.service"
|
path = "/etc/init.d/15_cpufreqd.service"
|
||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "CPU frequency scaling daemon"
|
description = "CPU frequency scaling daemon"
|
||||||
requires_weak = ["12_boot-late.target"]
|
requires_weak = ["04_drivers.target"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "/usr/bin/cpufreqd"
|
cmd = "/usr/bin/cpufreqd"
|
||||||
@@ -336,13 +428,25 @@ path = "/etc/init.d/15_thermald.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Thermal management daemon"
|
description = "Thermal management daemon"
|
||||||
requires_weak = ["12_boot-late.target"]
|
requires_weak = ["04_drivers.target"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "/usr/bin/thermald"
|
cmd = "/usr/bin/thermald"
|
||||||
type = "oneshot_async"
|
type = "oneshot_async"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/15_coretempd.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "CPU temperature sensor daemon"
|
||||||
|
requires_weak = ["04_drivers.target"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "/usr/bin/coretempd"
|
||||||
|
type = { scheme = "coretemp" }
|
||||||
|
"""
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
path = "/etc/init.d/15_hwrngd.service"
|
path = "/etc/init.d/15_hwrngd.service"
|
||||||
data = """
|
data = """
|
||||||
@@ -372,7 +476,7 @@ path = "/etc/init.d/16_redbear-acmd.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "USB CDC ACM serial daemon"
|
description = "USB CDC ACM serial daemon"
|
||||||
requires_weak = ["12_boot-late.target"]
|
requires_weak = ["04_drivers.target"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "/usr/bin/redbear-acmd"
|
cmd = "/usr/bin/redbear-acmd"
|
||||||
@@ -384,7 +488,7 @@ path = "/etc/init.d/16_redbear-ecmd.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "USB CDC ECM/NCM ethernet daemon"
|
description = "USB CDC ECM/NCM ethernet daemon"
|
||||||
requires_weak = ["12_boot-late.target"]
|
requires_weak = ["04_drivers.target"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "/usr/bin/redbear-ecmd"
|
cmd = "/usr/bin/redbear-ecmd"
|
||||||
@@ -396,7 +500,7 @@ path = "/etc/init.d/16_redbear-usbaudiod.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "USB Audio Class daemon"
|
description = "USB Audio Class daemon"
|
||||||
requires_weak = ["12_boot-late.target"]
|
requires_weak = ["04_drivers.target"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "/usr/bin/redbear-usbaudiod"
|
cmd = "/usr/bin/redbear-usbaudiod"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ data = """
|
|||||||
[unit]
|
[unit]
|
||||||
description = "Boot essential services target"
|
description = "Boot essential services target"
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_base.target",
|
"04_drivers.target",
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ data = """
|
|||||||
[unit]
|
[unit]
|
||||||
description = "Activate fallback console VT"
|
description = "Activate fallback console VT"
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"05_boot-essential.target",
|
"08_userland.target",
|
||||||
]
|
]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
|
|||||||
@@ -3,14 +3,9 @@
|
|||||||
# 00_base.service: stripped base setup (tmpdir only, no sudo — sudo runs from
|
# 00_base.service: stripped base setup (tmpdir only, no sudo — sudo runs from
|
||||||
# base.toml's 00_sudo.service). ipcd and ptyd are started by
|
# base.toml's 00_sudo.service). ipcd and ptyd are started by
|
||||||
# 00_ipcd.service and 00_ptyd.service from the base recipe.
|
# 00_ipcd.service and 00_ptyd.service from the base recipe.
|
||||||
# 00_drivers / 10_net: no longer overridden — the legacy scripts were removed
|
# 00_pcid-spawner.service has been fully replaced by 00_driver-manager.service
|
||||||
# from base.toml. The retained 00_pcid-spawner.service unit name now
|
# (defined in redbear-device-services.toml). The old pcid-spawner
|
||||||
# launches driver-manager so existing init ordering remains stable.
|
# unit name is no longer used anywhere.
|
||||||
# 00_pcid-spawner.service: compatibility wrapper for driver-manager. The base
|
|
||||||
# recipe uses type="oneshot" which blocks init until pcid-spawner exits.
|
|
||||||
# Running driver-manager here with oneshot_async keeps the historic unit
|
|
||||||
# name for downstream `requires_weak` consumers while moving PCI driver
|
|
||||||
# spawning to the manager that performs bind/channel handoff.
|
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
zsh = {}
|
zsh = {}
|
||||||
@@ -37,17 +32,4 @@ default_dependencies = false
|
|||||||
[service]
|
[service]
|
||||||
cmd = "audiod"
|
cmd = "audiod"
|
||||||
type = "oneshot_async"
|
type = "oneshot_async"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/init.d/00_pcid-spawner.service"
|
|
||||||
data = """
|
|
||||||
[unit]
|
|
||||||
description = "PCI driver spawner compatibility alias"
|
|
||||||
default_dependencies = false
|
|
||||||
|
|
||||||
[service]
|
|
||||||
cmd = "echo"
|
|
||||||
args = ["pcid-spawner compatibility alias: driver-manager owns PCI driver spawning"]
|
|
||||||
type = "oneshot"
|
|
||||||
"""
|
|
||||||
+29
-23
@@ -9,7 +9,7 @@
|
|||||||
# - all non-graphics, non-firmware packages from the full profile
|
# - all non-graphics, non-firmware packages from the full profile
|
||||||
# - no linux-firmware payload, no firmware-loader, no GPU/display drivers
|
# - no linux-firmware payload, no firmware-loader, no GPU/display drivers
|
||||||
|
|
||||||
include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml", "redbear-device-services.toml"]
|
include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml", "redbear-device-services.toml", "redbear-boot-stages.toml"]
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
filesystem_size = 1536
|
filesystem_size = 1536
|
||||||
@@ -27,9 +27,8 @@ redbear-release = {}
|
|||||||
redbear-hwutils = {}
|
redbear-hwutils = {}
|
||||||
redbear-quirks = {}
|
redbear-quirks = {}
|
||||||
|
|
||||||
# Device driver infrastructure: driver-manager is started by
|
# Device driver infrastructure: driver-manager replaces pcid-spawner;
|
||||||
# redbear-device-services.toml, with 00_pcid-spawner.service retained only as a
|
# 00_driver-manager.service is defined in redbear-device-services.toml.
|
||||||
# compatibility dependency alias for older service units.
|
|
||||||
ehcid = {}
|
ehcid = {}
|
||||||
ohcid = {}
|
ohcid = {}
|
||||||
uhcid = {}
|
uhcid = {}
|
||||||
@@ -53,6 +52,7 @@ redbear-info = {}
|
|||||||
cub = {}
|
cub = {}
|
||||||
cpufreqd = {}
|
cpufreqd = {}
|
||||||
thermald = {}
|
thermald = {}
|
||||||
|
coretempd = {}
|
||||||
hwrngd = {}
|
hwrngd = {}
|
||||||
redbear-acmd = {}
|
redbear-acmd = {}
|
||||||
redbear-ecmd = {}
|
redbear-ecmd = {}
|
||||||
@@ -99,7 +99,7 @@ meson = {}
|
|||||||
ninja-build = {}
|
ninja-build = {}
|
||||||
m4 = {}
|
m4 = {}
|
||||||
#git = {} # suppressed: cascading rebuild; git not needed for boot/recovery
|
#git = {} # suppressed: cascading rebuild; git not needed for boot/recovery
|
||||||
htop = {}
|
#htop = {} # disabled: build failure in redoxer env (pre-existing)
|
||||||
#mc = {} # suppressed: C99 format warning errors in compilation
|
#mc = {} # suppressed: C99 format warning errors in compilation
|
||||||
|
|
||||||
# ── Build / packaging utilities ──
|
# ── Build / packaging utilities ──
|
||||||
@@ -231,6 +231,7 @@ path = "/etc/init.d/00_i2c-dw-acpi.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "DesignWare ACPI I2C controller (non-blocking)"
|
description = "DesignWare ACPI I2C controller (non-blocking)"
|
||||||
|
default_dependencies = false
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_i2cd.service",
|
"00_i2cd.service",
|
||||||
]
|
]
|
||||||
@@ -245,6 +246,7 @@ path = "/etc/init.d/00_intel-gpiod.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Intel ACPI GPIO registrar (non-blocking)"
|
description = "Intel ACPI GPIO registrar (non-blocking)"
|
||||||
|
default_dependencies = false
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_gpiod.service",
|
"00_gpiod.service",
|
||||||
"00_i2cd.service",
|
"00_i2cd.service",
|
||||||
@@ -260,6 +262,7 @@ path = "/etc/init.d/00_i2c-gpio-expanderd.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "I2C GPIO expander companion bridge (non-blocking on live-mini)"
|
description = "I2C GPIO expander companion bridge (non-blocking on live-mini)"
|
||||||
|
default_dependencies = false
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_i2cd.service",
|
"00_i2cd.service",
|
||||||
"00_gpiod.service",
|
"00_gpiod.service",
|
||||||
@@ -275,6 +278,8 @@ path = "/etc/init.d/00_i2c-hidd.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "ACPI I2C HID bring-up daemon (non-blocking)"
|
description = "ACPI I2C HID bring-up daemon (non-blocking)"
|
||||||
|
default_dependencies = false
|
||||||
|
requires = ["00_acpid.service"]
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_i2cd.service",
|
"00_i2cd.service",
|
||||||
"00_i2c-dw-acpi.service",
|
"00_i2c-dw-acpi.service",
|
||||||
@@ -292,6 +297,7 @@ path = "/etc/init.d/00_ucsid.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "USB-C UCSI topology detector (non-blocking on live-mini)"
|
description = "USB-C UCSI topology detector (non-blocking on live-mini)"
|
||||||
|
default_dependencies = false
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_base.target",
|
"00_base.target",
|
||||||
"00_i2cd.service",
|
"00_i2cd.service",
|
||||||
@@ -306,9 +312,9 @@ type = { scheme = "ucsi" }
|
|||||||
path = "/etc/init.d/12_boot-late.target"
|
path = "/etc/init.d/12_boot-late.target"
|
||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Late boot services target"
|
description = "Late boot services target (compat alias for 04_drivers.target)"
|
||||||
requires_weak = [
|
requires_weak = [
|
||||||
"00_base.target",
|
"04_drivers.target",
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -467,23 +473,7 @@ data = ""
|
|||||||
directory = true
|
directory = true
|
||||||
mode = 0o755
|
mode = 0o755
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/pcid.d/ihdgd.toml"
|
|
||||||
data = """
|
|
||||||
# redbear-live-mini: text-only image; override upstream ihdgd config with empty file
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/pcid.d/virtio-gpud.toml"
|
|
||||||
data = """
|
|
||||||
# redbear-live-mini: text-only image; override upstream virtio-gpud config with empty file
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
|
||||||
path = "/etc/pcid.d/00_text_mode_gpu_mask.toml"
|
|
||||||
data = """
|
|
||||||
# redbear-live-mini: no display driver matched; class 0x03 devices are skipped
|
|
||||||
"""
|
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
path = "/lib/drivers.d/30-graphics.toml"
|
path = "/lib/drivers.d/30-graphics.toml"
|
||||||
@@ -502,6 +492,7 @@ path = "/etc/init.d/29_activate_console.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Activate console VT"
|
description = "Activate console VT"
|
||||||
|
default_dependencies = false
|
||||||
requires_weak = ["00_base.target"]
|
requires_weak = ["00_base.target"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
@@ -515,6 +506,7 @@ path = "/etc/init.d/30_console.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Console terminals"
|
description = "Console terminals"
|
||||||
|
default_dependencies = false
|
||||||
requires_weak = ["29_activate_console.service"]
|
requires_weak = ["29_activate_console.service"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
@@ -528,6 +520,7 @@ path = "/etc/init.d/31_debug_console.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Debug console"
|
description = "Debug console"
|
||||||
|
default_dependencies = false
|
||||||
requires_weak = ["29_activate_console.service"]
|
requires_weak = ["29_activate_console.service"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
@@ -535,3 +528,16 @@ cmd = "getty"
|
|||||||
args = ["/scheme/debug/no-preserve", "-J"]
|
args = ["/scheme/debug/no-preserve", "-J"]
|
||||||
type = "oneshot_async"
|
type = "oneshot_async"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/08_userland.target"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Userland services target"
|
||||||
|
requires_weak = [
|
||||||
|
"06_services.target",
|
||||||
|
"29_activate_console.service",
|
||||||
|
"30_console.service",
|
||||||
|
"31_debug_console.service",
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ path = "/usr/lib/init.d/10_smolnetd.service"
|
|||||||
data = """
|
data = """
|
||||||
[unit]
|
[unit]
|
||||||
description = "Network stack for redoxer"
|
description = "Network stack for redoxer"
|
||||||
requires_weak = ["00_pcid-spawner.service"]
|
requires_weak = ["00_driver-manager.service"]
|
||||||
|
|
||||||
[service]
|
[service]
|
||||||
cmd = "netstack"
|
cmd = "netstack"
|
||||||
|
|||||||
+93
-1
@@ -1,5 +1,29 @@
|
|||||||
# RED BEAR OS — DERIVATIVE OF REDOX OS
|
# RED BEAR OS — DERIVATIVE OF REDOX OS
|
||||||
|
|
||||||
|
## INTERNAL-FIRST POLICY
|
||||||
|
|
||||||
|
When building packages, resolving dependencies, or choosing between implementations,
|
||||||
|
**always prefer internal Red Bear recipes and patched sources** over upstream or
|
||||||
|
third-party alternatives. This applies at every decision point:
|
||||||
|
|
||||||
|
| Decision | Always prefer |
|
||||||
|
|----------|--------------|
|
||||||
|
| Recipe location | `local/recipes/` over `recipes/` (upstream) |
|
||||||
|
| Patch source | `local/patches/` over in-tree edits or ad-hoc sed |
|
||||||
|
| Source tarball | Patched archive in `sources/redbear-0.1.0/tarballs/` over re-download |
|
||||||
|
| Implementation | Red Bear Rust implementation over upstream C port |
|
||||||
|
| Config | `config/redbear-*.toml` over mainline `config/*.toml` |
|
||||||
|
| Scripts | `local/scripts/` over ad-hoc shell commands |
|
||||||
|
|
||||||
|
**Concretely:** if `local/recipes/<category>/<name>/` exists and is symlinked into the
|
||||||
|
recipe tree, that is the authoritative recipe — never fall back to the upstream
|
||||||
|
`recipes/` version. If a local recipe has a `redox.patch`, that patch is the
|
||||||
|
maintained Red Bear delta — never work around it by editing the source tree directly.
|
||||||
|
|
||||||
|
**Rationale:** the local overlay is the durable, version-controlled, release-safe layer.
|
||||||
|
Upstream recipes are disposable and may be overwritten by `make distclean` or release
|
||||||
|
provisioning. Only `local/` survives across rebuilds and releases.
|
||||||
|
|
||||||
## TUI CONVENTION — `-i` INTERACTIVE SWITCH
|
## TUI CONVENTION — `-i` INTERACTIVE SWITCH
|
||||||
|
|
||||||
All Red Bear desktop applications that offer a TUI mode MUST use `-i`/`--interactive`
|
All Red Bear desktop applications that offer a TUI mode MUST use `-i`/`--interactive`
|
||||||
@@ -50,6 +74,58 @@ files, Wayland protocol stubs, D-Bus service stubs, and any other layer of the s
|
|||||||
|
|
||||||
**No exceptions. No "temporary." No "until we fix it properly."**
|
**No exceptions. No "temporary." No "until we fix it properly."**
|
||||||
|
|
||||||
|
## BUILD DURABILITY AND CASCADE POLICY
|
||||||
|
|
||||||
|
### Every Build Lands in the Repo
|
||||||
|
|
||||||
|
Every successful `repo cook <package>` MUST produce two durable artifacts:
|
||||||
|
|
||||||
|
1. **Package in the repo**: `repo/x86_64-unknown-redox/<name>.pkgar` + `<name>.toml`
|
||||||
|
2. **Patched source form**: All source modifications mirrored to `local/patches/<component>/`
|
||||||
|
|
||||||
|
A build is **not complete** until both exist. Verify after every cook:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./target/release/repo find <package> # Must find the package
|
||||||
|
ls repo/x86_64-unknown-redox/<package>.toml # Manifest must exist
|
||||||
|
ls repo/x86_64-unknown-redox/<package>.pkgar # Archive must exist
|
||||||
|
```
|
||||||
|
|
||||||
|
If a package was built but the repo artifacts are missing, the build did not complete.
|
||||||
|
If source patches exist only in `recipes/*/source/` but not in `local/patches/`,
|
||||||
|
the patches are not durable (see Source-of-Truth Rule below).
|
||||||
|
|
||||||
|
### Cascade Rebuild Rule
|
||||||
|
|
||||||
|
When a low-level package changes, **all packages that transitively depend on it
|
||||||
|
must be rebuilt**. A stale dependent silently produces link errors, ABI mismatches,
|
||||||
|
or runtime crashes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rebuild relibc and everything that depends on it
|
||||||
|
./local/scripts/rebuild-cascade.sh relibc
|
||||||
|
|
||||||
|
# Dry run: show what would be rebuilt without building
|
||||||
|
./local/scripts/rebuild-cascade.sh --dry-run relibc
|
||||||
|
|
||||||
|
# Multiple root packages
|
||||||
|
./local/scripts/rebuild-cascade.sh relibc ncurses
|
||||||
|
```
|
||||||
|
|
||||||
|
The script performs BFS over reverse dependencies: it finds all packages whose
|
||||||
|
`recipe.toml` lists the target in `dependencies`, transitively expands, then builds
|
||||||
|
root-first followed by dependents.
|
||||||
|
|
||||||
|
**Always use cascade rebuilds after changing:**
|
||||||
|
- relibc (headers, ABI, any patches)
|
||||||
|
- Kernel (syscall ABI changes)
|
||||||
|
- Shared libraries (ncurses, zlib, openssl, etc.)
|
||||||
|
- Any package listed in other packages' `dependencies`
|
||||||
|
|
||||||
|
**Example:** Changing relibc's `sys/types/internal.h` header requires rebuilding
|
||||||
|
bison, m4, flex, and every other gnulib-based package that includes system headers
|
||||||
|
through the relibc include chain.
|
||||||
|
|
||||||
## DESIGN PRINCIPLE
|
## DESIGN PRINCIPLE
|
||||||
|
|
||||||
Red Bear OS is a **full fork** based on frozen Redox OS snapshots:
|
Red Bear OS is a **full fork** based on frozen Redox OS snapshots:
|
||||||
@@ -73,10 +149,21 @@ make all CONFIG_NAME=redbear-full
|
|||||||
→ mk/config.mk resolves to the active desktop/graphics compile target
|
→ mk/config.mk resolves to the active desktop/graphics compile target
|
||||||
→ Desktop/graphics are available only on redbear-full
|
→ Desktop/graphics are available only on redbear-full
|
||||||
→ repo cook builds all packages from local sources (offline by default)
|
→ repo cook builds all packages from local sources (offline by default)
|
||||||
|
→ Each successful cook produces repo/<arch>/<name>.pkgar + <name>.toml
|
||||||
→ mk/disk.mk creates harddrive.img with Red Bear branding
|
→ mk/disk.mk creates harddrive.img with Red Bear branding
|
||||||
→ REDBEAR_RELEASE=0.1.0 ensures immutable, archived sources
|
→ REDBEAR_RELEASE=0.1.0 ensures immutable, archived sources
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Cascade rebuild flow (when a low-level package changes):
|
||||||
|
```
|
||||||
|
./local/scripts/rebuild-cascade.sh <package>
|
||||||
|
→ Finds all packages whose recipe.toml lists <package> in dependencies
|
||||||
|
→ BFS expands the reverse dependency graph
|
||||||
|
→ Builds root package first, then dependents in dependency order
|
||||||
|
→ Pushes all rebuilt packages to sysroot
|
||||||
|
→ Every rebuilt package lands in repo/ (.pkgar + .toml)
|
||||||
|
```
|
||||||
|
|
||||||
Release flow:
|
Release flow:
|
||||||
```
|
```
|
||||||
# Sources are immutable — build from archives, never from network
|
# Sources are immutable — build from archives, never from network
|
||||||
@@ -259,6 +346,7 @@ redox-master/ ← git pull updates mainline Redox
|
|||||||
│ │ └── images/ ← Red Bear OS icon (1254x1254) + loading bg (1536x1024)
|
│ │ └── images/ ← Red Bear OS icon (1254x1254) + loading bg (1536x1024)
|
||||||
│ ├── firmware/ ← GPU firmware blobs (gitignored, fetched)
|
│ ├── firmware/ ← GPU firmware blobs (gitignored, fetched)
|
||||||
│ ├── scripts/
|
│ ├── scripts/
|
||||||
|
│ │ ├── rebuild-cascade.sh ← Rebuild package + all dependents (BFS reverse-dep graph)
|
||||||
│ │ ├── provision-release.sh ← Provision new release from Redox ref
|
│ │ ├── provision-release.sh ← Provision new release from Redox ref
|
||||||
│ │ ├── build-redbear.sh ← Unified Red Bear OS build script
|
│ │ ├── build-redbear.sh ← Unified Red Bear OS build script
|
||||||
│ │ ├── fetch-firmware.sh ← Download bounded AMD or Intel firmware subsets from linux-firmware
|
│ │ ├── fetch-firmware.sh ← Download bounded AMD or Intel firmware subsets from linux-firmware
|
||||||
@@ -311,6 +399,10 @@ scripts/build-iso.sh redbear-full # Full desktop live ISO
|
|||||||
scripts/build-iso.sh redbear-mini # Text-only mini (default)
|
scripts/build-iso.sh redbear-mini # Text-only mini (default)
|
||||||
scripts/build-iso.sh redbear-grub # Text-only + GRUB
|
scripts/build-iso.sh redbear-grub # Text-only + GRUB
|
||||||
|
|
||||||
|
# Rebuild a package and all its dependents (cascade)
|
||||||
|
./local/scripts/rebuild-cascade.sh relibc # Rebuild relibc + all dependents
|
||||||
|
./local/scripts/rebuild-cascade.sh --dry-run ncurses # Show cascade without building
|
||||||
|
|
||||||
# VM-network baseline validation helpers
|
# VM-network baseline validation helpers
|
||||||
./local/scripts/validate-vm-network-baseline.sh
|
./local/scripts/validate-vm-network-baseline.sh
|
||||||
./local/scripts/test-vm-network-qemu.sh redbear-mini
|
./local/scripts/test-vm-network-qemu.sh redbear-mini
|
||||||
@@ -848,4 +940,4 @@ Config comparison:
|
|||||||
|
|
||||||
## ANTI-PATTERNS (COMMIT POLICY)
|
## ANTI-PATTERNS (COMMIT POLICY)
|
||||||
|
|
||||||
- **DO NOT** include AI attribution in commit messages — no "Ultraworked with [Sisyphus]", "Co-authored-by: Sisyphus", or similar AI agent footers. Commits belong to the human author only.
|
- **DO NOT** include AI attribution in commit messages — no AI agent footers, co-authored-by lines for automated assistance, or similar markers. Commits belong to the human author only.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/nvmed"]
|
command = ["/usr/lib/drivers/nvmed"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 1
|
class = 1
|
||||||
subclass = 8
|
subclass = 8
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/ahcid"]
|
command = ["/usr/lib/drivers/ahcid"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 1
|
class = 1
|
||||||
subclass = 6
|
subclass = 6
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/ided"]
|
command = ["/usr/lib/drivers/ided"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 1
|
class = 1
|
||||||
subclass = 1
|
subclass = 1
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ priority = 100
|
|||||||
command = ["/usr/lib/drivers/virtio-blkd"]
|
command = ["/usr/lib/drivers/virtio-blkd"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1AF4
|
vendor = 0x1AF4
|
||||||
device = 0x1001
|
device = 0x1001
|
||||||
class = 1
|
class = 1
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/e1000d"]
|
command = ["/usr/lib/drivers/e1000d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 2
|
class = 2
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/rtl8168d"]
|
command = ["/usr/lib/drivers/rtl8168d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x10EC
|
vendor = 0x10EC
|
||||||
class = 2
|
class = 2
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/rtl8139d"]
|
command = ["/usr/lib/drivers/rtl8139d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x10EC
|
vendor = 0x10EC
|
||||||
device = 0x8139
|
device = 0x8139
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/ixgbed"]
|
command = ["/usr/lib/drivers/ixgbed"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 2
|
class = 2
|
||||||
subclass = 0
|
subclass = 0
|
||||||
@@ -48,5 +52,6 @@ priority = 50
|
|||||||
command = ["/usr/lib/drivers/virtio-netd"]
|
command = ["/usr/lib/drivers/virtio-netd"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1AF4
|
vendor = 0x1AF4
|
||||||
class = 2
|
class = 2
|
||||||
|
|||||||
@@ -44,6 +44,49 @@ priority = 80
|
|||||||
command = ["/usr/lib/drivers/uhcid"]
|
command = ["/usr/lib/drivers/uhcid"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x03
|
||||||
|
prog_if = 0x30
|
||||||
|
|
||||||
|
# EHCI (USB 2.0)
|
||||||
|
[[driver]]
|
||||||
|
name = "ehcid"
|
||||||
|
description = "EHCI USB 2.0 host controller"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/ehcid"]
|
||||||
|
|
||||||
|
# EHCI now owns a simple /scheme/usb controller surface for per-port status and
|
||||||
|
# control-transfer pass-through while the wider USB stack continues converging.
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x03
|
||||||
|
prog_if = 0x20
|
||||||
|
|
||||||
|
# OHCI (USB 1.1 — non-Intel chipsets)
|
||||||
|
[[driver]]
|
||||||
|
name = "ohcid"
|
||||||
|
description = "OHCI USB 1.1 host controller"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/ohcid"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x03
|
||||||
|
prog_if = 0x10
|
||||||
|
|
||||||
|
# UHCI (USB 1.1 — Intel chipsets)
|
||||||
|
[[driver]]
|
||||||
|
name = "uhcid"
|
||||||
|
description = "UHCI USB 1.1 host controller (Intel)"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/uhcid"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x0C
|
class = 0x0C
|
||||||
subclass = 0x03
|
subclass = 0x03
|
||||||
prog_if = 0x00
|
prog_if = 0x00
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ priority = 60
|
|||||||
command = ["/usr/lib/drivers/vesad"]
|
command = ["/usr/lib/drivers/vesad"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x03
|
class = 0x03
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
@@ -18,14 +19,17 @@ command = ["/usr/bin/redox-drm"]
|
|||||||
# Only match known GPU vendors. Class 0x03 alone catches QEMU VGA
|
# Only match known GPU vendors. Class 0x03 alone catches QEMU VGA
|
||||||
# (vendor 0x1234) which redox-drm rejects with a fatal error.
|
# (vendor 0x1234) which redox-drm rejects with a fatal error.
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1002
|
vendor = 0x1002
|
||||||
class = 0x03
|
class = 0x03
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 0x03
|
class = 0x03
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1AF4
|
vendor = 0x1AF4
|
||||||
class = 0x03
|
class = 0x03
|
||||||
|
|
||||||
@@ -36,6 +40,7 @@ priority = 61
|
|||||||
command = ["/usr/bin/redox-drm"]
|
command = ["/usr/bin/redox-drm"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1AF4
|
vendor = 0x1AF4
|
||||||
class = 0x03
|
class = 0x03
|
||||||
|
|
||||||
@@ -47,6 +52,7 @@ priority = 61
|
|||||||
command = ["/usr/bin/redox-drm"]
|
command = ["/usr/bin/redox-drm"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 0x03
|
class = 0x03
|
||||||
subclass = 0x00
|
subclass = 0x00
|
||||||
@@ -59,6 +65,7 @@ priority = 61
|
|||||||
command = ["/usr/bin/redox-drm"]
|
command = ["/usr/bin/redox-drm"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x1002
|
vendor = 0x1002
|
||||||
class = 0x03
|
class = 0x03
|
||||||
subclass = 0x00
|
subclass = 0x00
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ priority = 40
|
|||||||
command = ["/usr/lib/drivers/ihdad"]
|
command = ["/usr/lib/drivers/ihdad"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
vendor = 0x8086
|
vendor = 0x8086
|
||||||
class = 0x04
|
class = 0x04
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ priority = 40
|
|||||||
command = ["/usr/lib/drivers/ac97d"]
|
command = ["/usr/lib/drivers/ac97d"]
|
||||||
|
|
||||||
[[driver.match]]
|
[[driver.match]]
|
||||||
|
bus = "pci"
|
||||||
class = 0x04
|
class = 0x04
|
||||||
subclass = 0x01
|
subclass = 0x01
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +1,139 @@
|
|||||||
# GPIO and I2C controller drivers
|
# GPIO and I2C controller drivers
|
||||||
|
#
|
||||||
|
# These drivers match against both PCI and ACPI devices.
|
||||||
|
# ACPI devices are classified by _HID → PCI-equivalent class/subclass/vendor
|
||||||
|
# codes via redox-driver-acpi's classify_acpi_device().
|
||||||
|
#
|
||||||
|
# Match criteria use the standard [[driver.match]] format with class/subclass/vendor.
|
||||||
|
# The ACPI bus fills these fields from the _HID classification table.
|
||||||
|
|
||||||
|
# --- I2C/SPI controller infrastructure ---
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
name = "i2cd"
|
name = "i2cd"
|
||||||
description = "I2C host adapter registry"
|
description = "I2C host adapter registry"
|
||||||
priority = 85
|
priority = 85
|
||||||
command = ["/usr/lib/drivers/i2cd"]
|
command = ["/usr/lib/drivers/i2cd"]
|
||||||
|
# i2cd is the I2C bus registry — spawned as infrastructure before
|
||||||
|
# specific I2C controller drivers. Does not match against hardware
|
||||||
|
# directly; it provides /scheme/i2c for controller drivers to register with.
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
name = "gpiod"
|
name = "gpiod"
|
||||||
description = "GPIO controller registry"
|
description = "GPIO controller registry"
|
||||||
priority = 85
|
priority = 85
|
||||||
command = ["/usr/lib/drivers/gpiod"]
|
command = ["/usr/lib/drivers/gpiod"]
|
||||||
|
# gpiod is the GPIO pin registry — spawned as infrastructure before
|
||||||
|
# specific GPIO controller drivers. Does not match against hardware
|
||||||
|
# directly; it provides /scheme/gpio for controller drivers to register with.
|
||||||
|
|
||||||
|
# --- ACPI I2C controller drivers ---
|
||||||
|
# These match against ACPI devices classified as Serial Bus Controller (0x0C),
|
||||||
|
# subclass SMBus/I2C (0x05), by the ACPI bus.
|
||||||
|
# The ACPI bus maps Intel INT33C3/INT3433/... and AMD AMDI0010 HIDs to these codes.
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
name = "dw-acpi-i2cd"
|
name = "dw-acpi-i2cd"
|
||||||
description = "DesignWare ACPI I2C controller"
|
description = "DesignWare ACPI I2C controller"
|
||||||
priority = 80
|
priority = 80
|
||||||
command = ["/usr/lib/drivers/dw-acpi-i2cd"]
|
command = ["/usr/lib/drivers/dw-acpi-i2cd"]
|
||||||
|
depends_on = ["acpi", "i2c"]
|
||||||
|
|
||||||
[[driver]]
|
[[driver.match]]
|
||||||
name = "intel-gpiod"
|
bus = "acpi"
|
||||||
description = "Intel ACPI GPIO registrar"
|
class = 0x0C
|
||||||
priority = 80
|
subclass = 0x05
|
||||||
command = ["/usr/lib/drivers/intel-gpiod"]
|
vendor = 0x8086
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
name = "amd-mp2-i2cd"
|
name = "amd-mp2-i2cd"
|
||||||
description = "AMD MP2 I2C controller"
|
description = "AMD MP2 I2C controller"
|
||||||
priority = 80
|
priority = 80
|
||||||
command = ["/usr/lib/drivers/amd-mp2-i2cd"]
|
command = ["/usr/lib/drivers/amd-mp2-i2cd"]
|
||||||
|
depends_on = ["acpi", "i2c"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x05
|
||||||
|
vendor = 0x1022
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
name = "intel-lpss-i2cd"
|
name = "intel-lpss-i2cd"
|
||||||
description = "Intel LPSS I2C controller"
|
description = "Intel LPSS I2C controller"
|
||||||
priority = 80
|
priority = 80
|
||||||
command = ["/usr/lib/drivers/intel-lpss-i2cd"]
|
command = ["/usr/lib/drivers/intel-lpss-i2cd"]
|
||||||
|
depends_on = ["acpi", "i2c"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x05
|
||||||
|
vendor = 0x8086
|
||||||
|
|
||||||
|
# --- ACPI SPI controller drivers ---
|
||||||
|
# These match against ACPI devices classified as Serial Bus Controller (0x0C),
|
||||||
|
# subclass SPI (0x06), by the ACPI bus.
|
||||||
|
|
||||||
|
[[driver]]
|
||||||
|
name = "intel-lpss-spid"
|
||||||
|
description = "Intel LPSS SPI controller"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/intel-lpss-spid"]
|
||||||
|
depends_on = ["acpi"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x06
|
||||||
|
vendor = 0x8086
|
||||||
|
|
||||||
|
# --- ACPI GPIO controller drivers ---
|
||||||
|
# These match against ACPI devices classified as Serial Bus Controller (0x0C),
|
||||||
|
# subclass Other (0x80), vendor Intel, by the ACPI bus.
|
||||||
|
# The ACPI bus maps INT33C7/INT3437/INT3450 HIDs to these codes.
|
||||||
|
|
||||||
|
[[driver]]
|
||||||
|
name = "intel-gpiod"
|
||||||
|
description = "Intel ACPI GPIO registrar"
|
||||||
|
priority = 80
|
||||||
|
command = ["/usr/lib/drivers/intel-gpiod"]
|
||||||
|
depends_on = ["acpi", "gpio"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0C
|
||||||
|
subclass = 0x80
|
||||||
|
vendor = 0x8086
|
||||||
|
|
||||||
|
# --- ACPI thermal/power drivers ---
|
||||||
|
# These match against ACPI devices classified as Thermal/Battery (0x0B).
|
||||||
|
|
||||||
|
[[driver]]
|
||||||
|
name = "redbear-thermald"
|
||||||
|
description = "ACPI thermal zone monitor"
|
||||||
|
priority = 60
|
||||||
|
command = ["/usr/lib/drivers/redbear-thermald"]
|
||||||
|
depends_on = ["acpi"]
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
bus = "acpi"
|
||||||
|
class = 0x0B
|
||||||
|
|
||||||
|
# --- I2C companion drivers ---
|
||||||
|
# These depend on I2C bus being available and match against specific
|
||||||
|
# I2C device addresses (not PCI/ACPI class matching).
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
name = "i2c-gpio-expanderd"
|
name = "i2c-gpio-expanderd"
|
||||||
description = "I2C GPIO expander companion bridge"
|
description = "I2C GPIO expander companion bridge"
|
||||||
priority = 75
|
priority = 75
|
||||||
command = ["/usr/lib/drivers/i2c-gpio-expanderd"]
|
command = ["/usr/lib/drivers/i2c-gpio-expanderd"]
|
||||||
|
depends_on = ["i2c", "gpio"]
|
||||||
|
|
||||||
[[driver]]
|
[[driver]]
|
||||||
name = "intel-thc-hidd"
|
name = "intel-thc-hidd"
|
||||||
description = "Intel THC QuickI2C HID transport"
|
description = "Intel THC QuickI2C HID transport"
|
||||||
priority = 75
|
priority = 75
|
||||||
command = ["/usr/lib/drivers/intel-thc-hidd"]
|
command = ["/usr/lib/drivers/intel-thc-hidd"]
|
||||||
|
depends_on = ["acpi", "i2c"]
|
||||||
|
|||||||
@@ -0,0 +1,846 @@
|
|||||||
|
# Red Bear OS: Boot Process & Hardware Detection Improvement Plan
|
||||||
|
|
||||||
|
**Version:** 1.5 (2026-05-15)
|
||||||
|
**Reference:** Linux 7.1-rc3 (`local/reference/linux-7.1/`)
|
||||||
|
**Status:** Canonical plan for boot efficiency, hardware detection completeness, and init ordering
|
||||||
|
|
||||||
|
## Implementation Status (2026-05-15)
|
||||||
|
|
||||||
|
**Approach changed:** Instead of creating a separate `redbear-hwdetect` daemon, we are
|
||||||
|
**enhancing the existing `driver-manager`** with ACPI bus support and boot stage targets.
|
||||||
|
This builds on the existing `redox-driver-core` device model (DeviceId, DeviceInfo, Bus,
|
||||||
|
Driver, DeviceManager) rather than duplicating it.
|
||||||
|
|
||||||
|
### Completed
|
||||||
|
|
||||||
|
| Wave | Status | What was done |
|
||||||
|
|------|--------|---------------|
|
||||||
|
| Wave 0 | ✅ Done | Created `config/redbear-boot-stages.toml` with 4 stage targets (02_early_hw, 04_drivers, 06_services, 08_userland) + serial boot markers |
|
||||||
|
| Wave 1 | ✅ Done | Created `local/recipes/drivers/redox-driver-acpi/` with `AcpiBus` that enumerates ACPI devices from `/scheme/acpi/symbols/`. Registered in `driver-manager` alongside `PciBus`. Added `_HID`-based device classification (maps ~40 ACPI hardware IDs to PCI-equivalent class/subclass/vendor). 15 unit tests pass. |
|
||||||
|
| Wave 2 | ✅ Done | Created `resource.rs` — ACPI resource descriptor parser (raw byte buffers → typed structs for IRQ, MMIO, I/O port, DMA, address spaces). Covers all 25 ACPI resource types (types 0-25). Created `prt.rs` — _PRT PCI IRQ routing table resolver (parses RON-serialized Package-of-Packages, resolves static GSI and dynamic link device routing). Fixed `bus.rs` to use child symbol lookup for `_HID`/`_CID` (the RON `Device` variant is unit — properties are separate namespace children). Added `query_device_resources()` API to AcpiBus. 20+ new unit tests across all modules. |
|
||||||
|
| Wave 2b | ✅ Done | Extended `driver-manager/config.rs` `probe()` to handle ACPI device binding alongside PCI. ACPI devices get `ACPI_DEVICE_PATH`, `ACPI_DEVICE_NAME`, `ACPI_MMIO_N`, `ACPI_IRQ_N`, `ACPI_IO_N` env vars passed to spawned drivers. PCI devices continue using `PCID_CLIENT_CHANNEL`/`PCID_DEVICE_PATH`. Updated `scheme.rs` to accept ACPI device names in the scheme namespace (relaxed PCI-only validation). `main.rs` now notifies bound devices for both buses. |
|
||||||
|
| Wave 2c | ✅ Done | Created ACPI driver config with match criteria in `60-gpio-i2c.toml` — Intel I2C (class=0x0C/sub=0x05/vendor=0x8086 → dw-acpi-i2cd), AMD I2C (class=0x0C/sub=0x05/vendor=0x1022 → amd-mp2-i2cd), Intel GPIO (class=0x0C/sub=0x80/vendor=0x8086 → intel-gpiod). Wired into `redbear-device-services.toml` as `/lib/drivers.d/60-gpio-i2c.toml`. Infrastructure daemons (i2cd, gpiod) remain as init services (scheme providers); controller drivers are dual-pathed (init fallback + driver-manager matching). |
|
||||||
|
| Wave 3 | ✅ Done | Rewired all services in `redbear-device-services.toml`, `redbear-mini.toml`, `redbear-full.toml` to use stage targets instead of flat `00_base.target` |
|
||||||
|
| Wave 4 | ✅ Done | Removed dead `/etc/pcid.d/` entries from `redbear-mini.toml` and `redbear-full.toml`. Confirmed no runtime binary reads `/etc/pcid.d/`. All driver matching now uses `/lib/drivers.d/`. |
|
||||||
|
| Wave 5 | ✅ Already had | `driver-manager/config.rs` already has scheme-aware deferred probing via `check_scheme_available()` + `depends_on` field |
|
||||||
|
|
||||||
|
### Not Yet Started
|
||||||
|
|
||||||
|
| Wave | Status | What remains |
|
||||||
|
|------|--------|---------------|
|
||||||
|
| Wave 2c | Not started | Runtime _CRS evaluation via ACPI scheme `call()` interface for Method-type _CRS (currently only Buffer-type _CRS is parsed). Link device _CRS resolution for dynamic _PRT entries. Full image build verification. |
|
||||||
|
|
||||||
|
### Config consistency verified (2026-05-15)
|
||||||
|
|
||||||
|
All `requires_weak` references in config files resolve to valid targets or services:
|
||||||
|
- `00_base.target` — staged by `base` package at `/usr/lib/init.d/00_base.target`
|
||||||
|
- Stage targets (`02_early_hw` through `08_userland`) — defined in `redbear-boot-stages.toml`
|
||||||
|
- `12_boot-late.target` — compat alias defined in `redbear-device-services.toml`
|
||||||
|
- `05_boot-essential.target` — defined in `redbear-full.toml` and `redbear-greeter-services.toml`
|
||||||
|
- All service dependencies have corresponding `[[files]]` entries or package-staged definitions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This document is the execution plan for making the Red Bear OS boot process **stellar**:
|
||||||
|
efficient, complete, and — above all — featuring **perfect hardware detection and initialization**.
|
||||||
|
|
||||||
|
It is grounded in a comprehensive study of Linux 7.1-rc3's boot flow (`init/main.c`,
|
||||||
|
`drivers/base/`, `drivers/pci/`, `drivers/acpi/`) and maps Linux's proven patterns
|
||||||
|
to Red Bear OS's microkernel architecture.
|
||||||
|
|
||||||
|
## Honest Current State
|
||||||
|
|
||||||
|
### What works today
|
||||||
|
- UEFI boot on x86_64 (bootloader → kernel → initfs → init → login)
|
||||||
|
- ACPI boot-baseline: RSDP/SDT/MADT/FADT/HPET parsing in kernel
|
||||||
|
- PCI enumeration via `pcid` + driver matching via `driver-manager`
|
||||||
|
- Wired networking (e1000d, rtl8168d, virtio-netd) in QEMU
|
||||||
|
- PS/2 keyboard/mouse via kernel `serio` scheme
|
||||||
|
- Framebuffer text console via `vesad`
|
||||||
|
- Multi-core x2APIC/SMP works
|
||||||
|
- Greeter/login QEMU proof passes on `redbear-full`
|
||||||
|
|
||||||
|
### What is broken or missing (THESE ARE THE GAPS)
|
||||||
|
|
||||||
|
| Gap | Linux equivalent | RedBear status |
|
||||||
|
|-----|-----------------|----------------|
|
||||||
|
| **No unified hardware detection** | `start_kernel()` → `driver_init()` → initcalls | Fragmented across `pcid`, `acpid`, `hwd`, `driver-manager` |
|
||||||
|
| **No device model** | `struct device`, `struct driver`, `struct bus_type` | No common device/driver/bus abstraction |
|
||||||
|
| **No ACPI device enumeration** | `acpi_bus_scan()` walks namespace, creates platform devices | `acpid` parses tables but doesn't enumerate devices |
|
||||||
|
| **No deferred probe with real semantics** | `-EPROBE_DEFER` + retry queue in `driver_deferred_probe_trigger()` | `driver-manager` has a 30-retry loop but no dependency graph |
|
||||||
|
| **No device resource tracking** | `request_region()`, `request_irq()`, `ioremap()` with resource tree | BARs mapped ad-hoc per driver, no global resource registry |
|
||||||
|
| **No boot-stage ordering** | initcall levels (core → postcore → arch → subsys → device → late) | Flat `requires_weak` everywhere; no semantic stages |
|
||||||
|
| **PCI enumeration too late** | PCI scanned at `subsys_initcall` level (level 4) | `driver-manager` is a userspace service with no hard dependency |
|
||||||
|
| **No platform/I2C/SPI device discovery** | ACPI `_HID`/`_CID` creates platform/i2c/spi devices | I2C/SPI daemons exist but no device enumeration from ACPI |
|
||||||
|
| **No USB device enumeration** | `usb_new_device()` → device descriptor → class matching | xHCI controller starts but no USB topology enumeration |
|
||||||
|
| **No sysfs/udev equivalent** | `/sys/devices/` tree + udev rules | `udev-shim` exists but is minimal |
|
||||||
|
| **Silent service failures** | Kernel oops if critical subsystem fails | `requires_weak` + `oneshot_async` → failures are invisible |
|
||||||
|
|
||||||
|
## Architecture: What Linux Does That We Must Reimplement
|
||||||
|
|
||||||
|
### Linux Boot Flow (from `init/main.c`)
|
||||||
|
|
||||||
|
```
|
||||||
|
start_kernel()
|
||||||
|
├── setup_arch() → arch-specific: page tables, early param parsing
|
||||||
|
├── trap_init() → IDT/exception vectors
|
||||||
|
├── mm_init() → memory management, slab allocator
|
||||||
|
├── sched_init() → scheduler
|
||||||
|
├── early_irq_init() → early IRQ descriptors
|
||||||
|
├── init_IRQ() → architecture IRQ controllers (IOAPIC, LAPIC)
|
||||||
|
├── time_init() → HPET/PIT/timers
|
||||||
|
├── console_init() → early console
|
||||||
|
├── driver_init() → device model core (kobject, sysfs, bus, class)
|
||||||
|
└── rest_init()
|
||||||
|
└── kernel_init()
|
||||||
|
└── do_basic_setup()
|
||||||
|
└── do_initcalls()
|
||||||
|
├── level 0 (core): kobject, debugfs, kernel core
|
||||||
|
├── level 1 (postcore): driver core, workqueue
|
||||||
|
├── level 2 (arch): arch-specific devices
|
||||||
|
├── level 3 (subsys): PCI, ACPI, network stack
|
||||||
|
├── level 4 (fs): filesystems
|
||||||
|
├── level 5 (device): device drivers
|
||||||
|
└── level 6 (late): late drivers, networking
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux Device Model (from `drivers/base/`)
|
||||||
|
|
||||||
|
Three core abstractions:
|
||||||
|
1. **`struct bus_type`** — PCI, ACPI, platform, USB, I2C, SPI
|
||||||
|
2. **`struct device`** — represents hardware, has parent, bus, driver, resources
|
||||||
|
3. **`struct device_driver`** — probe/remove/shutdown callbacks, ID table
|
||||||
|
|
||||||
|
Binding flow:
|
||||||
|
```
|
||||||
|
bus->probe(dev) → driver->probe(dev, id) → device bound to driver
|
||||||
|
```
|
||||||
|
|
||||||
|
Deferred probing (`drivers/base/dd.c`):
|
||||||
|
```
|
||||||
|
driver_probe_device() returns -EPROBE_DEFER
|
||||||
|
→ device added to deferred_probe_pending_list
|
||||||
|
→ driver_deferred_probe_trigger() retries on schedule
|
||||||
|
→ wake_up_all() after each successful bind
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux ACPI Device Discovery (from `drivers/acpi/scan.c`)
|
||||||
|
|
||||||
|
```
|
||||||
|
acpi_init()
|
||||||
|
└── acpi_bus_scan()
|
||||||
|
└── acpi_walk_namespace()
|
||||||
|
├── Read _HID (hardware ID)
|
||||||
|
├── Read _CID (compatible IDs)
|
||||||
|
├── Read _STA (status: present, enabled, functional)
|
||||||
|
├── Read _CRS (current resource settings: IRQ, MMIO, I/O ports)
|
||||||
|
└── Create device:
|
||||||
|
├── PCI root bridge → pci_scan_child_bus()
|
||||||
|
├── I2C controller → i2c_register_adapter()
|
||||||
|
├── SPI controller → spi_register_controller()
|
||||||
|
├── GPIO controller → gpiochip_add()
|
||||||
|
├── Platform device → platform_device_register()
|
||||||
|
└── Thermal zone → thermal_zone_device_register()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux PCI Enumeration (from `drivers/pci/probe.c`)
|
||||||
|
|
||||||
|
```
|
||||||
|
pci_scan_child_bus(bus)
|
||||||
|
for devfn in 0..0xFF:
|
||||||
|
pci_scan_slot(bus, devfn)
|
||||||
|
├── Read PCI_VENDOR_ID → skip if 0xFFFFFFFF
|
||||||
|
├── Read PCI_HEADER_TYPE → multifunction?
|
||||||
|
├── Read PCI_CLASS, PCI_REVISION
|
||||||
|
├── Read BARs (6 base address registers)
|
||||||
|
├── Parse capability chain (MSI, MSI-X, PCIe, power management)
|
||||||
|
├── Assign IRQ (from ACPI _PRT or BIOS)
|
||||||
|
├── If PCI bridge: recursively scan subordinate bus
|
||||||
|
└── Register device → driver core → bus_probe_device()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design: RedBear OS Hardware Detection Architecture
|
||||||
|
|
||||||
|
### Core Principle
|
||||||
|
|
||||||
|
**RedBear OS is a microkernel.** Unlike Linux where everything runs in kernel space,
|
||||||
|
RedBear OS runs all drivers as **userspace daemons** accessing hardware through schemes.
|
||||||
|
|
||||||
|
This means our "device model" lives in **userspace**, not in the kernel. The kernel provides:
|
||||||
|
- `scheme:irq` — interrupt delivery
|
||||||
|
- `scheme:memory` — physical memory mapping
|
||||||
|
- `scheme:pci` — PCI config space access
|
||||||
|
- `scheme:acpi` — ACPI table access
|
||||||
|
- `scheme:serio` — PS/2 controller
|
||||||
|
|
||||||
|
Everything else — device discovery, driver matching, resource allocation — is userspace.
|
||||||
|
|
||||||
|
### Proposed Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Kernel (microkernel) │
|
||||||
|
│ schemes: irq, memory, pci, acpi, serio, event, time │
|
||||||
|
│ ACPI early: RSDP, MADT (LAPIC/IOAPIC), HPET │
|
||||||
|
│ x2APIC/SMP: AP startup, interrupt routing │
|
||||||
|
└───────────────────────┬─────────────────────────────────┘
|
||||||
|
│ scheme IPC
|
||||||
|
┌───────────────────────▼─────────────────────────────────┐
|
||||||
|
│ redbear-hwdetect (NEW DAEMON) │
|
||||||
|
│ unified hardware detection & device registry │
|
||||||
|
│ │
|
||||||
|
│ 1. PCI bus walk (via scheme:pci) │
|
||||||
|
│ → enumerate all devices, parse BARs/caps/IRQ │
|
||||||
|
│ → build device tree with parent-child relationships │
|
||||||
|
│ │
|
||||||
|
│ 2. ACPI device scan (via scheme:acpi + acpid) │
|
||||||
|
│ → walk ACPI namespace for _HID/_CID/_STA/_CRS │
|
||||||
|
│ → create platform/I2C/SPI devices from ACPI │
|
||||||
|
│ → resolve PCI IRQ routing via _PRT │
|
||||||
|
│ │
|
||||||
|
│ 3. USB topology (via xhcid scheme) │
|
||||||
|
│ → enumerate USB devices on each controller │
|
||||||
|
│ → match by class/vendor/product │
|
||||||
|
│ │
|
||||||
|
│ 4. Driver matching │
|
||||||
|
│ → match devices to /lib/drivers.d/*.toml │
|
||||||
|
│ → spawn driver daemons with correct resources │
|
||||||
|
│ → deferred retry with real dependency tracking │
|
||||||
|
│ │
|
||||||
|
│ 5. Device registry (scheme:hwdetect) │
|
||||||
|
│ → /scheme/hwdetect/devices → list all detected HW │
|
||||||
|
│ → /scheme/hwdetect/pci/{bdf} → per-device info │
|
||||||
|
│ → /scheme/hwdetect/acpi/{path} → per-ACPI device │
|
||||||
|
│ → /scheme/hwdetect/drivers → driver status │
|
||||||
|
│ → JSON output for diagnostics │
|
||||||
|
│ │
|
||||||
|
│ Registers scheme: hwdetect │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why Enhance driver-manager Instead of Creating a New Daemon
|
||||||
|
|
||||||
|
> **Decision (2026-05-15):** We chose to enhance the existing `driver-manager` instead of
|
||||||
|
> creating `redbear-hwdetect`. The `redox-driver-core` crate already provides a solid device
|
||||||
|
> model (DeviceId, DeviceInfo, Bus trait, Driver trait, DeviceManager with deferred probing),
|
||||||
|
> and `driver-manager` already uses it for PCI enumeration. Adding ACPI bus support as a
|
||||||
|
> second `Bus` implementation follows the established pattern and avoids duplicating the
|
||||||
|
> device model, driver matching, and deferred probe logic.
|
||||||
|
|
||||||
|
The current `driver-manager` does PCI matching but:
|
||||||
|
- No ACPI device enumeration
|
||||||
|
- No USB topology
|
||||||
|
- No device tree
|
||||||
|
- No resource tracking
|
||||||
|
- No parent-child relationships
|
||||||
|
- Deferred retry is naive (fixed interval, no dependency graph)
|
||||||
|
|
||||||
|
Rather than bolting more onto `driver-manager`, the original plan was to create `redbear-hwdetect` as the
|
||||||
|
**single source of truth** for hardware state, and `driver-manager` becomes a thin
|
||||||
|
consumer of its device registry. **However, since `redox-driver-core` already provides the
|
||||||
|
device model abstractions, we enhance `driver-manager` by registering additional `Bus`
|
||||||
|
implementations (ACPI, and eventually USB).**
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### Wave 0: Boot Stage Definitions (config-only, zero code)
|
||||||
|
|
||||||
|
**Goal:** Replace the flat `requires_weak` service model with explicit boot stages.
|
||||||
|
|
||||||
|
**Current problem:** Every service uses `requires_weak = ["00_base.target"]` which means
|
||||||
|
no real ordering guarantee. Services can start in any order and silently fail.
|
||||||
|
|
||||||
|
**Linux equivalent:** initcall levels (core → postcore → arch → subsys → device → late)
|
||||||
|
|
||||||
|
**Proposed boot stages:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Stage 0: PLATFORM — kernel schemes ready (irq, memory, pci, acpi, serio)
|
||||||
|
Stage 1: CORE — tmpdir, logging, random, null/zero
|
||||||
|
Stage 2: EARLY_HW — acpid (ACPI tables), pcid (PCI bus access)
|
||||||
|
Stage 3: BUS_ENUM — redbear-hwdetect (PCI walk, ACPI scan, USB topology)
|
||||||
|
Stage 4: DRIVERS — driver spawning (storage, network, GPU, audio, USB class)
|
||||||
|
Stage 5: LATE_HW — IOMMU, firmware loading, NUMA topology
|
||||||
|
Stage 6: SERVICES — D-Bus, session broker, seat management
|
||||||
|
Stage 7: USERLAND — console, greeter, desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation:** Add target files:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# /etc/init.d/00_platform.target
|
||||||
|
[unit]
|
||||||
|
description = "Platform stage: kernel schemes ready"
|
||||||
|
|
||||||
|
# /etc/init.d/01_core.target
|
||||||
|
[unit]
|
||||||
|
description = "Core stage: basic services"
|
||||||
|
requires = ["00_platform.target"]
|
||||||
|
|
||||||
|
# /etc/init.d/02_early_hw.target
|
||||||
|
[unit]
|
||||||
|
description = "Early hardware: ACPI + PCI bus access"
|
||||||
|
requires = ["01_core.target"]
|
||||||
|
|
||||||
|
# /etc/init.d/03_bus_enum.target
|
||||||
|
[unit]
|
||||||
|
description = "Bus enumeration: PCI walk + ACPI scan"
|
||||||
|
requires = ["02_early_hw.target"]
|
||||||
|
|
||||||
|
# /etc/init.d/04_drivers.target
|
||||||
|
[unit]
|
||||||
|
description = "Driver spawning stage"
|
||||||
|
requires = ["03_bus_enum.target"]
|
||||||
|
|
||||||
|
# /etc/init.d/05_late_hw.target
|
||||||
|
[unit]
|
||||||
|
description = "Late hardware: firmware, IOMMU, NUMA"
|
||||||
|
requires = ["04_drivers.target"]
|
||||||
|
|
||||||
|
# /etc/init.d/06_services.target
|
||||||
|
[unit]
|
||||||
|
description = "System services: D-Bus, session broker"
|
||||||
|
requires = ["05_late_hw.target"]
|
||||||
|
|
||||||
|
# /etc/init.d/07_userland.target
|
||||||
|
[unit]
|
||||||
|
description = "User-facing: console, greeter, desktop"
|
||||||
|
requires = ["06_services.target"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key change:** Use `requires` (hard dependency, blocks if not met) instead of
|
||||||
|
`requires_weak` for stages. Services within a stage use `requires_weak` against
|
||||||
|
their stage target.
|
||||||
|
|
||||||
|
### Wave 1: redbear-hwdetect — The Unified Hardware Detection Daemon
|
||||||
|
|
||||||
|
**Goal:** Create a single daemon that discovers ALL hardware, builds a device tree,
|
||||||
|
and manages driver lifecycle.
|
||||||
|
|
||||||
|
**Source location:** `local/recipes/system/redbear-hwdetect/source/`
|
||||||
|
|
||||||
|
**Cargo.toml:**
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "redbear-hwdetect"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
redox-daemon = "0.1"
|
||||||
|
redox-scheme = "0.11"
|
||||||
|
libredox = "0.1"
|
||||||
|
redox_syscall = "0.7"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
toml = "0.8"
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
```
|
||||||
|
|
||||||
|
**Module structure:**
|
||||||
|
```
|
||||||
|
redbear-hwdetect/source/src/
|
||||||
|
├── main.rs — daemon entry, scheme registration, event loop
|
||||||
|
├── device.rs — Device trait, DeviceInfo, DeviceType, DeviceStatus
|
||||||
|
├── registry.rs — DeviceRegistry: HashMap<DeviceId, DeviceInfo>
|
||||||
|
├── pci/
|
||||||
|
│ ├── mod.rs — PciEnumerator: bus walk via scheme:pci
|
||||||
|
│ ├── config.rs — PCI config space reader
|
||||||
|
│ ├── capability.rs — PCI capability chain parser (MSI, MSI-X, PCIe, PM)
|
||||||
|
│ └── resource.rs — BAR parsing, IRQ assignment, resource allocation
|
||||||
|
├── acpi/
|
||||||
|
│ ├── mod.rs — AcpiScanner: device enumeration from ACPI tables
|
||||||
|
│ ├── namespace.rs — ACPI namespace walker (via acpid)
|
||||||
|
│ ├── resource.rs — _CRS parser (IRQ, MMIO, I/O port resources)
|
||||||
|
│ └── pci_routing.rs — _PRT (PCI IRQ routing table) resolver
|
||||||
|
├── usb/
|
||||||
|
│ ├── mod.rs — UsbScanner: USB topology via xHCI schemes
|
||||||
|
│ └── descriptor.rs — USB device/class descriptor parsing
|
||||||
|
├── driver/
|
||||||
|
│ ├── mod.rs — DriverMatcher: load /lib/drivers.d/*.toml
|
||||||
|
│ ├── match.rs — Device-driver matching (class, vendor, subclass)
|
||||||
|
│ └── spawn.rs — Driver process spawning with resource handoff
|
||||||
|
├── deferred.rs — Deferred probe queue with dependency graph
|
||||||
|
└── scheme.rs — scheme:hwdetect handler
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key data structures:**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Unique device identifier
|
||||||
|
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum DeviceId {
|
||||||
|
Pci { domain: u16, bus: u8, device: u8, function: u8 },
|
||||||
|
Acpi { path: String }, // ACPI namespace path (e.g., "\_SB.PCI0.I2C0")
|
||||||
|
Usb { controller: u8, port: u8, address: u8 },
|
||||||
|
Platform { name: String, id: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Device information
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DeviceInfo {
|
||||||
|
pub id: DeviceId,
|
||||||
|
pub device_type: DeviceType,
|
||||||
|
pub status: DeviceStatus,
|
||||||
|
pub vendor_id: Option<u16>,
|
||||||
|
pub device_id: Option<u16>,
|
||||||
|
pub class_code: Option<u8>,
|
||||||
|
pub subclass_code: Option<u8>,
|
||||||
|
pub prog_if: Option<u8>,
|
||||||
|
pub revision: Option<u8>,
|
||||||
|
pub parent: Option<DeviceId>,
|
||||||
|
pub resources: Vec<Resource>,
|
||||||
|
pub driver: Option<DriverInfo>,
|
||||||
|
pub quirks: Vec<String>,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum DeviceType {
|
||||||
|
PciDevice,
|
||||||
|
PciBridge,
|
||||||
|
AcpiDevice,
|
||||||
|
UsbController,
|
||||||
|
UsbDevice,
|
||||||
|
PlatformDevice,
|
||||||
|
I2cController,
|
||||||
|
I2cDevice,
|
||||||
|
SpiController,
|
||||||
|
SpiDevice,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum DeviceStatus {
|
||||||
|
Detected, // Found during scan, not yet probed
|
||||||
|
Probing, // Driver probe in progress
|
||||||
|
Bound, // Driver successfully bound
|
||||||
|
Deferred, // Probe deferred (dependency not ready)
|
||||||
|
Failed(String), // Probe failed permanently
|
||||||
|
NoDriver, // No matching driver found
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Resource {
|
||||||
|
pub resource_type: ResourceType,
|
||||||
|
pub base: u64,
|
||||||
|
pub size: u64,
|
||||||
|
pub flags: ResourceFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ResourceType {
|
||||||
|
Mmio, // Memory-mapped I/O
|
||||||
|
IoPort, // I/O port range
|
||||||
|
Irq, // Interrupt (GSI number)
|
||||||
|
Dma, // DMA channel/range
|
||||||
|
Firmware, // Required firmware blob
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ResourceFlags: u32 {
|
||||||
|
const PREFETCHABLE = 0x01;
|
||||||
|
const CACHEABLE = 0x02;
|
||||||
|
const SHARED = 0x04;
|
||||||
|
const MSI = 0x08;
|
||||||
|
const MSI_X = 0x10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Driver match rule (from /lib/drivers.d/*.toml)
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DriverMatch {
|
||||||
|
pub vendor: Option<u16>,
|
||||||
|
pub device: Option<u16>,
|
||||||
|
pub class: Option<u8>,
|
||||||
|
pub subclass: Option<u8>,
|
||||||
|
pub prog_if: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Driver configuration
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DriverConfig {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub priority: u32,
|
||||||
|
pub command: Vec<String>,
|
||||||
|
pub depends_on: Vec<String>, // scheme names that must exist before spawn
|
||||||
|
pub matches: Vec<DriverMatch>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PCI enumeration flow (from Linux `drivers/pci/probe.c`):**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl PciEnumerator {
|
||||||
|
/// Walk all PCI buses (mirrors Linux pci_scan_child_bus)
|
||||||
|
pub fn scan_all_buses(&mut self) -> Result<Vec<DeviceInfo>> {
|
||||||
|
let mut devices = Vec::new();
|
||||||
|
|
||||||
|
// Read from scheme:pci — get all PCI devices
|
||||||
|
for entry in self.read_pci_scheme()? {
|
||||||
|
let domain = entry.domain;
|
||||||
|
let bus = entry.bus;
|
||||||
|
let dev = entry.device;
|
||||||
|
let func = entry.function;
|
||||||
|
|
||||||
|
// Read config space (mirrors Linux pci_scan_slot)
|
||||||
|
let config = self.read_config(domain, bus, dev, func)?;
|
||||||
|
|
||||||
|
// Skip invalid devices (vendor 0xFFFF)
|
||||||
|
if config.vendor_id == 0xFFFF {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse device info (mirrors Linux pci_setup_device)
|
||||||
|
let mut device = DeviceInfo {
|
||||||
|
id: DeviceId::Pci { domain, bus, device: dev, function: func },
|
||||||
|
device_type: if config.is_bridge() {
|
||||||
|
DeviceType::PciBridge
|
||||||
|
} else {
|
||||||
|
DeviceType::PciDevice
|
||||||
|
},
|
||||||
|
status: DeviceStatus::Detected,
|
||||||
|
vendor_id: Some(config.vendor_id),
|
||||||
|
device_id: Some(config.device_id),
|
||||||
|
class_code: Some(config.class_code),
|
||||||
|
subclass_code: Some(config.subclass_code),
|
||||||
|
prog_if: Some(config.prog_if),
|
||||||
|
revision: Some(config.revision),
|
||||||
|
parent: self.find_parent_bridge(domain, bus),
|
||||||
|
resources: Vec::new(),
|
||||||
|
driver: None,
|
||||||
|
quirks: Vec::new(),
|
||||||
|
description: format!("PCI {:04x}:{:02x}:{:02x}.{} [{:04x}:{:04x}]",
|
||||||
|
domain, bus, dev, func, config.vendor_id, config.device_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse BARs (mirrors Linux pci_read_bases)
|
||||||
|
for bar_idx in 0..6 {
|
||||||
|
if let Some(resource) = self.parse_bar(domain, bus, dev, func, bar_idx)? {
|
||||||
|
device.resources.push(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse capability chain (mirrors Linux pci_init_capabilities)
|
||||||
|
self.parse_capabilities(&mut device, domain, bus, dev, func)?;
|
||||||
|
|
||||||
|
// Assign IRQ (from ACPI _PRT or IOAPIC routing)
|
||||||
|
if let Some(irq) = self.assign_irq(&device)? {
|
||||||
|
device.resources.push(Resource {
|
||||||
|
resource_type: ResourceType::Irq,
|
||||||
|
base: irq as u64,
|
||||||
|
size: 1,
|
||||||
|
flags: ResourceFlags::empty(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply quirks
|
||||||
|
self.apply_quirks(&mut device)?;
|
||||||
|
|
||||||
|
devices.push(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(devices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deferred probe with real dependency graph (from Linux `drivers/base/dd.c`):**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct DeferredQueue {
|
||||||
|
/// Devices waiting for dependencies
|
||||||
|
pending: HashMap<DeviceId, Vec<String>>, // device → missing dependencies
|
||||||
|
/// Maximum retries per device
|
||||||
|
max_retries: u32,
|
||||||
|
/// Retry interval in ms
|
||||||
|
retry_interval: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeferredQueue {
|
||||||
|
/// Add a deferred device (mirrors Linux driver_deferred_probe_add)
|
||||||
|
pub fn add(&mut self, device_id: DeviceId, missing_deps: Vec<String>) {
|
||||||
|
self.pending.insert(device_id, missing_deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retry all deferred devices (mirrors Linux driver_deferred_probe_trigger)
|
||||||
|
pub fn retry_cycle(&mut self, registry: &mut DeviceRegistry) -> Vec<DeviceInfo> {
|
||||||
|
let mut resolved = Vec::new();
|
||||||
|
|
||||||
|
// Check each deferred device
|
||||||
|
let pending_ids: Vec<DeviceId> = self.pending.keys().cloned().collect();
|
||||||
|
for id in &pending_ids {
|
||||||
|
if let Some(missing) = self.pending.get(id) {
|
||||||
|
// Check if all dependencies are now available
|
||||||
|
let all_ready = missing.iter().all(|dep| {
|
||||||
|
// Check if the scheme/file exists
|
||||||
|
std::path::Path::new(&format!("/scheme/{}", dep)).exists()
|
||||||
|
|| std::path::Path::new(&format!("/bin/{}", dep)).exists()
|
||||||
|
});
|
||||||
|
|
||||||
|
if all_ready {
|
||||||
|
let deps = self.pending.remove(id).unwrap();
|
||||||
|
log::info!("Deferred device {:?} resolved (deps: {:?})", id, deps);
|
||||||
|
if let Some(device) = registry.get_mut(id) {
|
||||||
|
device.status = DeviceStatus::Detected; // Reset to retry
|
||||||
|
resolved.push(device.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wave 2: ACPI Device Enumeration
|
||||||
|
|
||||||
|
**Goal:** Walk the ACPI namespace to discover non-PCI devices (I2C, SPI, GPIO,
|
||||||
|
thermal, battery, AC adapter, platform devices).
|
||||||
|
|
||||||
|
**Linux reference:** `drivers/acpi/scan.c::acpi_bus_scan()`
|
||||||
|
|
||||||
|
**Implementation in redbear-hwdetect:**
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl AcpiScanner {
|
||||||
|
/// Enumerate ACPI devices (mirrors Linux acpi_bus_scan)
|
||||||
|
pub fn scan(&mut self) -> Result<Vec<DeviceInfo>> {
|
||||||
|
let mut devices = Vec::new();
|
||||||
|
|
||||||
|
// Connect to acpid via scheme:acpi
|
||||||
|
let acpi = File::open("/scheme/acpi")?;
|
||||||
|
|
||||||
|
// Walk ACPI namespace (read device entries)
|
||||||
|
// Linux does: acpi_walk_namespace(ACPI_TYPE_DEVICE, ...)
|
||||||
|
// RedBear: read entries from acpid's device enumeration
|
||||||
|
for entry in self.enumerate_acpi_devices(&acpi)? {
|
||||||
|
let hid = self.read_hid(&entry)?;
|
||||||
|
let cid = self.read_cid(&entry)?;
|
||||||
|
let sta = self.read_sta(&entry)?;
|
||||||
|
|
||||||
|
// Skip if not present (mirrors Linux acpi_bus_check_add)
|
||||||
|
if !sta.present {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse _CRS resources (mirrors Linux acpi_walk_resources)
|
||||||
|
let resources = self.parse_crs(&entry)?;
|
||||||
|
|
||||||
|
// Determine device type from _HID/_CID
|
||||||
|
let device_type = match hid.as_str() {
|
||||||
|
"PNP0A03" | "PNP0A08" => DeviceType::PciBridge, // PCI root bridge
|
||||||
|
"INT33C3" | "INT3433" | "AMDI0010" => DeviceType::I2cController,
|
||||||
|
"INT33C0" | "INT3430" | "AMDI0061" => DeviceType::SpiController,
|
||||||
|
_ => DeviceType::PlatformDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
let device = DeviceInfo {
|
||||||
|
id: DeviceId::Acpi { path: entry.path.clone() },
|
||||||
|
device_type,
|
||||||
|
status: DeviceStatus::Detected,
|
||||||
|
vendor_id: None,
|
||||||
|
device_id: None,
|
||||||
|
class_code: None,
|
||||||
|
subclass_code: None,
|
||||||
|
prog_if: None,
|
||||||
|
revision: None,
|
||||||
|
parent: Some(DeviceId::Acpi { path: entry.parent.clone() }),
|
||||||
|
resources,
|
||||||
|
driver: None,
|
||||||
|
quirks: Vec::new(),
|
||||||
|
description: format!("ACPI device {} ({})", entry.path, hid),
|
||||||
|
};
|
||||||
|
|
||||||
|
devices.push(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(devients)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wave 3: Service Ordering Fix
|
||||||
|
|
||||||
|
**Goal:** Replace the current flat `requires_weak` model with stage-based ordering.
|
||||||
|
|
||||||
|
**Changes to config files:**
|
||||||
|
|
||||||
|
1. **Add stage targets** to `config/redbear-device-services.toml` (shared fragment)
|
||||||
|
2. **Rewire services** to depend on their stage target instead of `00_base.target`
|
||||||
|
|
||||||
|
**New service wiring example:**
|
||||||
|
```toml
|
||||||
|
# acpid: early hardware stage
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/02_acpid.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "ACPI daemon"
|
||||||
|
requires = ["02_early_hw.target"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "acpid"
|
||||||
|
type = { scheme = "acpi" }
|
||||||
|
"""
|
||||||
|
|
||||||
|
# redbear-hwdetect: bus enumeration stage
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/03_redbear-hwdetect.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Hardware detection and device registry"
|
||||||
|
requires = ["03_bus_enum.target", "02_acpid.service"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "redbear-hwdetect"
|
||||||
|
type = { scheme = "hwdetect" }
|
||||||
|
"""
|
||||||
|
|
||||||
|
# driver-manager: driver spawning stage (now consumes hwdetect registry)
|
||||||
|
[[files]]
|
||||||
|
path = "/etc/init.d/04_driver-manager.service"
|
||||||
|
data = """
|
||||||
|
[unit]
|
||||||
|
description = "Driver manager (consumes hwdetect registry)"
|
||||||
|
requires = ["04_drivers.target", "03_redbear-hwdetect.service"]
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "driver-manager"
|
||||||
|
type = "oneshot_async"
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wave 4: Driver Config Unification
|
||||||
|
|
||||||
|
**Goal:** Consolidate `/etc/pcid.d/` and `/lib/drivers.d/` into a single config format.
|
||||||
|
|
||||||
|
**Current problem:** Two config systems exist:
|
||||||
|
- `/etc/pcid.d/*.toml` — legacy pcid format
|
||||||
|
- `/lib/drivers.d/*.toml` — driver-manager format
|
||||||
|
|
||||||
|
**Solution:** Use only `/lib/drivers.d/*.toml` (driver-manager format).
|
||||||
|
Remove all `/etc/pcid.d/` config file generation from TOML configs.
|
||||||
|
|
||||||
|
**Updated driver config format (enhanced from current):**
|
||||||
|
```toml
|
||||||
|
[[driver]]
|
||||||
|
name = "e1000d"
|
||||||
|
description = "Intel Gigabit Ethernet"
|
||||||
|
priority = 50
|
||||||
|
command = ["/usr/lib/drivers/e1000d"]
|
||||||
|
depends_on = ["pci"] # scheme dependencies (NEW)
|
||||||
|
capabilities = ["net"] # declares what it provides (NEW)
|
||||||
|
|
||||||
|
[[driver.match]]
|
||||||
|
vendor = 0x8086
|
||||||
|
class = 0x02
|
||||||
|
subclass = 0x00
|
||||||
|
|
||||||
|
# Optional: specific device IDs for better matching
|
||||||
|
[[driver.match]]
|
||||||
|
vendor = 0x8086
|
||||||
|
device = 0x100e # 82540EM
|
||||||
|
class = 0x02
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wave 5: Boot Diagnostics
|
||||||
|
|
||||||
|
**Goal:** Make boot failures visible and diagnosable.
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
|
||||||
|
1. **`redbear-hwdetect --status`** — print detected hardware and driver status
|
||||||
|
2. **Boot marker on serial** — `echo "STAGE_03_BUS_ENUM_COMPLETE"` at each stage
|
||||||
|
3. **Device failure logging** — every deferred/failed probe logged with reason
|
||||||
|
4. **JSON diagnostic output** — `redbear-hwdetect --json` for automated testing
|
||||||
|
|
||||||
|
### Wave 6: USB Topology Enumeration
|
||||||
|
|
||||||
|
**Goal:** Discover USB devices beyond just the xHCI controller.
|
||||||
|
|
||||||
|
**Linux reference:** `drivers/usb/core/hub.c::hub_events()`
|
||||||
|
|
||||||
|
This is a later wave because it depends on xHCI IRQ stability (per the blocker chain).
|
||||||
|
|
||||||
|
**Implementation approach:**
|
||||||
|
- Query each xHCI controller for its device list
|
||||||
|
- Parse USB device descriptors
|
||||||
|
- Match USB class drivers (HID, mass storage, audio, CDC ACM)
|
||||||
|
- Register in device registry
|
||||||
|
|
||||||
|
## Execution Order
|
||||||
|
|
||||||
|
| Wave | Duration | Deliverable | Depends on |
|
||||||
|
|------|----------|-------------|------------|
|
||||||
|
| Wave 0 | 1 day | Boot stage targets in config | Nothing |
|
||||||
|
| Wave 1 | 2-3 weeks | `redbear-hwdetect` daemon with PCI enumeration | Wave 0 |
|
||||||
|
| Wave 2 | 1-2 weeks | ACPI device enumeration in hwdetect | Wave 1 |
|
||||||
|
| Wave 3 | 1 week | Service rewiring to stage targets | Wave 0 |
|
||||||
|
| Wave 4 | 3-5 days | Driver config unification | Wave 1 |
|
||||||
|
| Wave 5 | 3-5 days | Boot diagnostics | Wave 1 |
|
||||||
|
| Wave 6 | 2-3 weeks | USB topology enumeration | Wave 1, xHCI IRQ stability |
|
||||||
|
|
||||||
|
**Total estimate:** 6-10 weeks for waves 0-5 (core boot and hardware detection).
|
||||||
|
Wave 6 (USB) follows the blocker chain after low-level controller quality.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
### Boot process is "stellar" when:
|
||||||
|
1. ✅ Boot completes from power-on to login in < 10 seconds on QEMU
|
||||||
|
2. ✅ Every PCI device is enumerated and logged with full info (vendor, device, class, BARs, IRQ)
|
||||||
|
3. ✅ Every ACPI device with a present status is discovered
|
||||||
|
4. ✅ Every device that has a matching driver is bound within 3 seconds of enumeration
|
||||||
|
5. ✅ Deferred probes resolve within 5 seconds of dependency availability
|
||||||
|
6. ✅ Boot failures are visible on serial console with stage markers
|
||||||
|
7. ✅ `redbear-hwdetect --status` shows complete hardware state
|
||||||
|
8. ✅ No `requires_weak` remains for critical boot-path services
|
||||||
|
9. ✅ Service ordering is deterministic: same order on every boot
|
||||||
|
10. ✅ Missing hardware does not cause panics or hangs
|
||||||
|
|
||||||
|
### Hardware detection is "perfect" when:
|
||||||
|
1. ✅ PCI: all devices on all buses enumerated, including behind bridges
|
||||||
|
2. ✅ PCI: BARs parsed correctly (type, size, prefetchable)
|
||||||
|
3. ✅ PCI: capabilities parsed (MSI, MSI-X, PCIe, power management, vendor-specific)
|
||||||
|
4. ✅ PCI: IRQ assigned from ACPI _PRT or IOAPIC routing
|
||||||
|
5. ✅ ACPI: all devices with _STA present enumerated
|
||||||
|
6. ✅ ACPI: _CRS resources parsed (IRQ, MMIO, I/O ports, DMA)
|
||||||
|
7. ✅ USB: all devices on all controllers discovered (Wave 6)
|
||||||
|
8. ✅ Platform: I2C/SPI/GPIO controllers discovered from ACPI (Wave 2)
|
||||||
|
9. ✅ Quirks: hardware-specific quirks applied automatically
|
||||||
|
10. ✅ Hotplug: new devices detected and drivers spawned in < 2 seconds
|
||||||
|
|
||||||
|
## Relationship to Other Plans
|
||||||
|
|
||||||
|
| Plan | Relationship |
|
||||||
|
|------|-------------|
|
||||||
|
| `ACPI-IMPROVEMENT-PLAN.md` | ACPI robustness is prerequisite for Wave 2 |
|
||||||
|
| `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` | IRQ quality is prerequisite for hardware detection reliability |
|
||||||
|
| `USB-IMPLEMENTATION-PLAN.md` | USB topology (Wave 6) depends on USB maturity |
|
||||||
|
| `CONSOLE-TO-KDE-DESKTOP-PLAN.md` | Desktop path benefits from better boot/hardware detection |
|
||||||
|
| `QUIRKS-SYSTEM.md` | Quirks integrated into hwdetect's device discovery |
|
||||||
|
|
||||||
|
## Linux 7.1 Reference Files
|
||||||
|
|
||||||
|
Key files to consult when implementing:
|
||||||
|
|
||||||
|
| RedBear component | Linux 7.1 reference |
|
||||||
|
|---|---|
|
||||||
|
| PCI enumeration | `drivers/pci/probe.c`, `drivers/pci/setup-bus.c` |
|
||||||
|
| PCI driver matching | `drivers/pci/pci-driver.c` |
|
||||||
|
| ACPI device scan | `drivers/acpi/scan.c`, `drivers/acpi/bus.c` |
|
||||||
|
| ACPI resource parsing | `drivers/acpi/resource.c` |
|
||||||
|
| PCI IRQ routing | `drivers/acpi/pci_irq.c`, `drivers/acpi/pci_link.c` |
|
||||||
|
| Device model core | `drivers/base/core.c`, `drivers/base/bus.c`, `drivers/base/dd.c` |
|
||||||
|
| Deferred probing | `drivers/base/dd.c` |
|
||||||
|
| Boot initcalls | `init/main.c`, `include/linux/init.h` |
|
||||||
|
| IRQ management | `kernel/irq/manage.c`, `kernel/irq/chip.c` |
|
||||||
|
| Resource management | `kernel/resource.c` |
|
||||||
|
| DMA mapping | `kernel/dma/mapping.c` |
|
||||||
@@ -0,0 +1,933 @@
|
|||||||
|
# Red Bear OS — Comprehensive System Assessment & Improvement Plan
|
||||||
|
|
||||||
|
**Version**: 1.0 (2026-05-20)
|
||||||
|
**Reference**: Linux kernel 7.1 (`local/reference/linux-7.1/`)
|
||||||
|
**Supersedes**: `IMPLEMENTATION-MASTER-PLAN.md`, `SUBSYSTEM-ASSESSMENT-2026-05.md`,
|
||||||
|
`SMP-BOOT-HARDENING-PLAN.md`, `CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md`,
|
||||||
|
`COMPREHENSIVE-BOOT-IMPROVEMENT-PLAN.md`
|
||||||
|
|
||||||
|
**Canonical adjacent plans** (remain authoritative for subsystem detail):
|
||||||
|
- `ACPI-IMPROVEMENT-PLAN.md` — ACPI waves W0–W7
|
||||||
|
- `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` — PCI/IRQ/MSI-X
|
||||||
|
- `USB-IMPLEMENTATION-PLAN.md` — USB phases U0–U6
|
||||||
|
- `CONSOLE-TO-KDE-DESKTOP-PLAN.md` — desktop path
|
||||||
|
- `DRM-MODERNIZATION-EXECUTION-PLAN.md` — GPU stack
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive Summary
|
||||||
|
|
||||||
|
Red Bear OS is **architecturally sound** but has **significant gaps in hardware-facing
|
||||||
|
subsystems**. The system boots to a login prompt in QEMU with working console,
|
||||||
|
networking, and basic device enumeration. However, the boot log and codebase audit
|
||||||
|
reveal that **bare-metal usability is limited**: the system runs hot (no C-states,
|
||||||
|
no thermal backend), may not see all CPU cores (AP startup races), may lose USB
|
||||||
|
keyboard (only xHCI exists), and has minimal observability for operators.
|
||||||
|
|
||||||
|
This document is a **truthful, evidence-based assessment** of every low-level
|
||||||
|
subsystem, grounded in source code inspection, boot log analysis, and comparison
|
||||||
|
against Linux 7.1 reference source. It replaces five stale/duplicate planning
|
||||||
|
documents with one canonical assessment and forward plan.
|
||||||
|
|
||||||
|
### Bottom-line verdicts
|
||||||
|
|
||||||
|
| Subsystem | Verdict |
|
||||||
|
|-----------|---------|
|
||||||
|
| **SMP** | Real in kernel, but AP startup races and no bare-metal validation |
|
||||||
|
| **CPU power (C-states)** | **Completely missing** — root cause of heat on bare metal |
|
||||||
|
| **CPU power (P-states)** | Partial — cpufreqd exists but fragile |
|
||||||
|
| **Thermal / sensors** | Daemon exists but **no backend** — runs with empty surface |
|
||||||
|
| **ACPI boot** | Boot-baseline complete, not release-grade |
|
||||||
|
| **ACPI thermal/fan** | **Missing** — not implemented in acpid |
|
||||||
|
| **USB xHCI** | Real, QEMU-validated only |
|
||||||
|
| **USB EHCI/UHCI/OHCI** | **No drivers exist** — bare-metal USB keyboard unreliable |
|
||||||
|
| **PCI / IRQ / MSI-X** | Architecturally strong, low adoption in drivers |
|
||||||
|
| **IOMMU AMD-Vi** | Real, QEMU first-use proof only |
|
||||||
|
| **IOMMU Intel VT-d** | **Missing** — orphaned DMAR parsing only |
|
||||||
|
| **Firmware loading** | Real, on-demand, async |
|
||||||
|
| **Memory management** | Basic frame allocator — no swap/NUMA/hotplug |
|
||||||
|
| **Logging** | Append-only `/var/log/system.log` — no rotation/structured storage |
|
||||||
|
| **Udev** | Real but limited — polling hotplug, hardcoded rules |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Assessment by Subsystem
|
||||||
|
|
||||||
|
### 2.1 SMP / CPU Bring-up
|
||||||
|
|
||||||
|
**Status**: 🟡 Implemented, QEMU-proven, **bare-metal unvalidated**
|
||||||
|
**Linux 7.1 equivalent**: `arch/x86/kernel/smpboot.c`, `arch/x86/kernel/apic/`,
|
||||||
|
`kernel/smp.c`
|
||||||
|
|
||||||
|
#### What is real
|
||||||
|
|
||||||
|
The kernel has a **complete AP bring-up path**:
|
||||||
|
|
||||||
|
- AP trampoline with INIT/SIPI sequencing (`madt/arch/x86.rs`)
|
||||||
|
- x2APIC/LocalApic branching with zero-extended ID fallback
|
||||||
|
(`local_apic.rs`)
|
||||||
|
- `multi_core` feature enabled by default (`Cargo.toml`)
|
||||||
|
- Per-CPU data structures (`percpu.rs`)
|
||||||
|
- IPI support for TLB shootdowns and scheduler wakeups
|
||||||
|
- CPU set tracking (`cpu_set.rs`)
|
||||||
|
|
||||||
|
Source files inspected:
|
||||||
|
- `recipes/core/kernel/source/src/acpi/madt/arch/x86.rs`
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/device/local_apic.rs`
|
||||||
|
- `recipes/core/kernel/source/src/startup/mod.rs`
|
||||||
|
- `recipes/core/kernel/source/src/cpu_set.rs`
|
||||||
|
|
||||||
|
#### Why you see "SMP: 1 CPUs online"
|
||||||
|
|
||||||
|
The boot log shows:
|
||||||
|
|
||||||
|
```
|
||||||
|
kernel::acpi::madt::arch:INFO -- SMP: 1 CPUs online (max 256)
|
||||||
|
```
|
||||||
|
|
||||||
|
This can happen for three reasons:
|
||||||
|
|
||||||
|
1. **QEMU i440fx exposes only 1 vCPU to the guest** (most likely in this boot)
|
||||||
|
2. **AP startup timeout** — `AP_SPIN_LIMIT=1_000_000` spin counts vary by clock
|
||||||
|
speed; on slow or heavily loaded bare metal, APs may not signal readiness in
|
||||||
|
time
|
||||||
|
3. **Firmware MADT only exposes 1 processor entry** — rare but possible on
|
||||||
|
broken firmware
|
||||||
|
|
||||||
|
On real bare metal with an AMD Ryzen or Intel Core system, if the firmware
|
||||||
|
exposes multiple LocalApic entries and AP startup succeeds, the kernel **will**
|
||||||
|
bring up all cores. But this has **never been validated** on the project's
|
||||||
|
hardware matrix.
|
||||||
|
|
||||||
|
#### Critical weaknesses (38 kernel issues found)
|
||||||
|
|
||||||
|
`SMP-BOOT-HARDENING-PLAN.md` (2026-05-16) documented **54 issues** across kernel
|
||||||
|
and userspace boot. The most critical kernel-side items are:
|
||||||
|
|
||||||
|
| Issue | Severity | File | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| AP startup LogicalCpuId race | **Critical** | `madt/arch/x86.rs:153,244,276,365` | Two APs load `CPU_COUNT` simultaneously → same ID |
|
||||||
|
| AP_READY dual-mechanism race | **Critical** | `madt/arch/x86.rs:174-225` | Trampoline u64 write + static `AtomicBool` — inconsistent ordering |
|
||||||
|
| TLB shootdown range race | **Critical** | `percpu.rs:134-137` | Concurrent shootdowns overwrite range between flag set and IPI |
|
||||||
|
| MCS lock missing fences | **Critical** | `sync/mcs.rs:74-101` | No Release/Acquire on MCS lock handoff |
|
||||||
|
| Unbounded priority inversion | **Critical** | `sync/mcs.rs:126-145` | PI donation one level only |
|
||||||
|
| Scheduler panic flag leak | **Critical** | `switch.rs:164,298` | `in_context_switch` stays true on panic → CPU lockup |
|
||||||
|
| Missing SIPI delays | **High** | `madt/arch/x86.rs:192-337` | Spin-count delays, not TSC-based. Intel SDM requires 10ms INIT→SIPI |
|
||||||
|
| NUMA node set after CPU visible | **High** | `madt/arch/x86.rs:244,253` | `CPU_COUNT.fetch_add()` before `numa_node.set()` |
|
||||||
|
| MAX_CPU_COUNT=128 too small | **High** | `cpu_set.rs:44` | AMD EPYC has 128C/256T, Threadripper PRO 96C/192T |
|
||||||
|
| Global IRQ count lock | **High** | `scheme/irq.rs:67` | `COUNTS.lock()` is global spinlock on hot path |
|
||||||
|
|
||||||
|
These are **not theoretical**. The LogicalCpuId race means two APs can claim
|
||||||
|
the same CPU ID, leading to corrupted per-CPU data. The missing SIPI delays
|
||||||
|
mean APs may fail to start on real hardware with strict firmware timing
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
#### Gaps vs Linux 7.1
|
||||||
|
|
||||||
|
| Feature | Linux 7.1 | Red Bear |
|
||||||
|
|---------|-----------|----------|
|
||||||
|
| Robust AP bring-up | `smpboot.c` with TSC delays, online checks | Spin-count delays, race conditions |
|
||||||
|
| CPU hotplug | Full hot-add/hot-remove | Not implemented |
|
||||||
|
| CPU isolation | `isolcpus`, `nohz_full` | Not implemented |
|
||||||
|
| NUMA | Node-aware scheduling, memory policies | No NUMA awareness |
|
||||||
|
| Per-CPU idle threads | `cpuhp/`, idle thread per CPU | APs enter idle loop directly |
|
||||||
|
| x2APIC fallback | Clean fallback with explicit disable | Fallback works but warns |
|
||||||
|
|
||||||
|
**Verdict**: SMP infrastructure is real but has **critical races** that must be
|
||||||
|
fixed before bare-metal multi-core can be trusted. No hardware validation exists.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 CPU Power Management (P-states / C-states)
|
||||||
|
|
||||||
|
**Status**: 🟡 P-states partial, **C-states missing entirely**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/cpufreq/`, `drivers/cpuidle/`,
|
||||||
|
`drivers/acpi/processor.c`, `arch/x86/kernel/acpi/cstate.c`
|
||||||
|
|
||||||
|
#### P-states (frequency scaling)
|
||||||
|
|
||||||
|
`cpufreqd` is a **real userspace daemon** that:
|
||||||
|
|
||||||
|
- Reads ACPI `_PSS` (Performance States) tables
|
||||||
|
- Samples CPU load periodically
|
||||||
|
- Writes `IA32_PERF_CTL` MSR to change P-state
|
||||||
|
- Supports governors: Ondemand, Performance, Powersave
|
||||||
|
- Exposes `/scheme/cpufreq`
|
||||||
|
|
||||||
|
Source: `local/recipes/system/cpufreqd/source/src/main.rs`
|
||||||
|
|
||||||
|
**But it is fragile**:
|
||||||
|
|
||||||
|
1. `write_msr()` ignores its `msr` parameter and writes only the value to
|
||||||
|
`/dev/cpu/<n>/msr`. This suggests it depends on a Linux-style MSR driver that
|
||||||
|
uses file offset as the MSR index. No such driver was found in the Red Bear
|
||||||
|
tree.
|
||||||
|
2. The daemon reads MSR temperature via `IA32_THERM_STATUS` but has no
|
||||||
|
actionable thermal policy — it can request "powersave" from cpufreqd itself,
|
||||||
|
but there is no thermal trip point logic.
|
||||||
|
3. On the boot log: `cpufreqd: CPU0: 4 P-states (2400 - 1200 kHz)` followed by
|
||||||
|
`cpufreqd: CPU0: MSR write failed (1/1)` — **the P-state change is failing**.
|
||||||
|
|
||||||
|
#### C-states (idle power states)
|
||||||
|
|
||||||
|
**This is completely missing** and is the **single largest contributor to system
|
||||||
|
heat on bare metal**.
|
||||||
|
|
||||||
|
What exists:
|
||||||
|
- The kernel has a normal `hlt` instruction in the idle loop when no threads are
|
||||||
|
runnable
|
||||||
|
- No dedicated cpuidle subsystem
|
||||||
|
- No ACPI `_CST` (C-state) table parsing
|
||||||
|
- No `mwait` / `monitor` usage for deeper C-states
|
||||||
|
- No C1E, C3, C6, C7 support
|
||||||
|
|
||||||
|
What Linux 7.1 has:
|
||||||
|
- `drivers/cpuidle/` with multiple drivers: `acpi_idle`, `intel_idle`, `amd_idle`
|
||||||
|
- `_CST` table parsing in ACPI processor driver
|
||||||
|
- `mwait` hint selection based on C-state depth
|
||||||
|
- Latency and power measurements per C-state
|
||||||
|
- Scheduler integration: `cpuidle_enter()` called from idle loop
|
||||||
|
|
||||||
|
**Verdict**: cpufreqd is real but MSR writes are failing. C-states are
|
||||||
|
**completely absent**. On bare metal, CPUs run at full power even when idle.
|
||||||
|
This is why the system is "very hot."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 Thermal Management / Sensors / Hardware Monitoring
|
||||||
|
|
||||||
|
**Status**: 🔴 Thermal daemon exists but **no backend**; sensors missing; hwmon
|
||||||
|
absent
|
||||||
|
**Linux 7.1 equivalent**: `drivers/thermal/`, `drivers/hwmon/`,
|
||||||
|
`drivers/acpi/thermal.c`, `drivers/acpi/fan.c`
|
||||||
|
|
||||||
|
#### thermald
|
||||||
|
|
||||||
|
`thermald` is **real code**, not a stub. It:
|
||||||
|
|
||||||
|
- Attempts to read ACPI thermal zones
|
||||||
|
- Reads CPU MSR temperature (`IA32_THERM_STATUS`)
|
||||||
|
- Can request powersave from cpufreqd
|
||||||
|
- Can request ACPI sleep
|
||||||
|
- Exposes `/scheme/thermal`
|
||||||
|
|
||||||
|
Source: `local/recipes/system/thermald/source/src/main.rs`
|
||||||
|
|
||||||
|
**But it runs with an empty surface**:
|
||||||
|
|
||||||
|
- ACPI thermal zone enumeration is **missing from acpid**. The ACPI daemon's
|
||||||
|
scheme surface (`/scheme/acpi`) has no thermal or fan nodes.
|
||||||
|
- `thermald` expects `/scheme/acpi/thermal` and `/scheme/acpi/fan` to exist, but
|
||||||
|
they do not.
|
||||||
|
- `fan.rs` exists in the thermald source tree but is **orphaned** — it is not
|
||||||
|
wired into `main.rs` (`mod fan;` is absent).
|
||||||
|
|
||||||
|
The boot log shows:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ OK ] Started Thermal management daemon
|
||||||
|
2026-05-20T09-13-44.583Z [@thermald:19 INFO] thermald: started
|
||||||
|
```
|
||||||
|
|
||||||
|
And then nothing. No thermal zones found, no temperature readings, no fan
|
||||||
|
control.
|
||||||
|
|
||||||
|
#### Hardware sensors (hwmon)
|
||||||
|
|
||||||
|
**There is no hwmon infrastructure** in Red Bear OS.
|
||||||
|
|
||||||
|
What is missing:
|
||||||
|
- No `/sys/class/hwmon` equivalent
|
||||||
|
- No `/scheme/hwmon`
|
||||||
|
- No sensor drivers
|
||||||
|
|
||||||
|
Linux 7.1 has **100+ hwmon drivers** covering:
|
||||||
|
- CPU temperature: `coretemp` (Intel), `k10temp` (AMD)
|
||||||
|
- Motherboard sensors: `nct6775`, `it87`, `f71882fg`
|
||||||
|
- Voltage regulators: `ina2xx`, `ltc2947`
|
||||||
|
- Fan speed monitors: various Super-I/O chips
|
||||||
|
|
||||||
|
Red Bear has **none of these**.
|
||||||
|
|
||||||
|
#### SMBIOS / DMI
|
||||||
|
|
||||||
|
SMBIOS parsing exists in `acpid/src/dmi.rs`, but the boot log shows:
|
||||||
|
|
||||||
|
```
|
||||||
|
2026-05-20T09-12-40.920Z [@acpid::dmi:124 WARN] SMBIOS entry point not found in 0xF0000-0xFFFFF
|
||||||
|
```
|
||||||
|
|
||||||
|
This means DMI-based quirks and system identification are **best-effort only**.
|
||||||
|
On systems without a valid SMBIOS entry point, the quirk system falls back to
|
||||||
|
PCI/USB device ID matching only.
|
||||||
|
|
||||||
|
**Verdict**: thermald is real but powerless. No hwmon, no sensor drivers, no
|
||||||
|
ACPI thermal backend. The system has **zero thermal awareness**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 ACPI Stack
|
||||||
|
|
||||||
|
**Status**: 🟡 Boot-baseline complete, **not release-grade**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/acpi/`, `include/acpi/`
|
||||||
|
|
||||||
|
#### What is strong
|
||||||
|
|
||||||
|
- Kernel early ACPI discovery: RSDP, RSDT, XSDT
|
||||||
|
- MADT parsing: LocalApic, IoApic, IntSrcOverride, NMI
|
||||||
|
- x2APIC fallback with zero-extended IDs
|
||||||
|
- FADT parsing, PM1a/PM1b register access
|
||||||
|
- AML interpreter v6.1.1 with real mutex tracking
|
||||||
|
- EC (Embedded Controller) byte-transaction access
|
||||||
|
- `_S5` shutdown derivation (though timing is fragile)
|
||||||
|
- `kstop` kernel shutdown eventing consumed by `redbear-sessiond`
|
||||||
|
- DMI exposure via `/scheme/acpi/dmi`
|
||||||
|
|
||||||
|
Source files:
|
||||||
|
- `recipes/core/kernel/source/src/acpi/`
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/`
|
||||||
|
|
||||||
|
#### What is weak
|
||||||
|
|
||||||
|
| Area | Status | Detail |
|
||||||
|
|------|--------|--------|
|
||||||
|
| acpid startup | Fragile | Active panic-grade `expect()` paths on firmware-origin data |
|
||||||
|
| `_S5` timing | Fragile | Derived after PCI registration; pre-PCI shutdown reports "AML not ready" |
|
||||||
|
| DMAR | Orphaned | Parsing exists in `acpid/src/dmar/mod.rs` but not wired; Intel VT-d has no owner |
|
||||||
|
| Sleep beyond S5 | Missing | `set_global_s_state()` is S5-only; S3 suspend not validated |
|
||||||
|
| Thermal zones | Missing | No ACPI thermal zone enumeration |
|
||||||
|
| Fan devices | Missing | No ACPI fan device support |
|
||||||
|
| Battery/power | Provisional | `power_snapshot()` does real AML-backed probing but bootstrap preconditions are weak |
|
||||||
|
| AML fault handling | Partial | `aml_physmem.rs` has "log then fabricate 0" paths |
|
||||||
|
| SMBIOS | Best-effort | Entry point missing on many systems |
|
||||||
|
|
||||||
|
The ACPI improvement plan (`ACPI-IMPROVEMENT-PLAN.md`) tracks 8 waves of work
|
||||||
|
(W0–W7). Current status:
|
||||||
|
- W0 (Contracts): partially complete
|
||||||
|
- W1 (Startup hardening): partially complete
|
||||||
|
- W2 (AML ordering/shutdown): partially complete
|
||||||
|
- W3 (Honest power surface): **open**
|
||||||
|
- W4 (Physmem/EC/fault): partially complete
|
||||||
|
- W5 (Ownership cleanup): **open**
|
||||||
|
- W6 (Consumer integration): partially complete
|
||||||
|
- W7 (Validation closure): **open**
|
||||||
|
|
||||||
|
**Verdict**: ACPI is the most mature low-level subsystem, but it is still
|
||||||
|
**boot-baseline complete**, not release-grade. Thermal and fan support are
|
||||||
|
completely absent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 PCI / IRQ / MSI-X
|
||||||
|
|
||||||
|
**Status**: 🟡 Architecturally strong, **adoption-incomplete**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/pci/`, `arch/x86/kernel/apic/`,
|
||||||
|
`drivers/iommu/`
|
||||||
|
|
||||||
|
#### What is real
|
||||||
|
|
||||||
|
- `pcid` enumerates PCI devices via config space (I/O ports 0xCF8/0xCFC fallback
|
||||||
|
when no ECAM/MCFG)
|
||||||
|
- Capability parsing: MSI, MSI-X, power management, vendor-specific
|
||||||
|
- `driver-manager` matches TOML configs by bus/class/vendor and spawns drivers
|
||||||
|
- Kernel MSI message composition and validation (`msi.rs`, `vector.rs`)
|
||||||
|
- MSI-X table mapping and vector allocation
|
||||||
|
- `redox-driver-sys` provides IRQ handle abstractions, affinity helpers
|
||||||
|
- IOAPIC routing with interrupt source overrides
|
||||||
|
- Legacy PIC fallback
|
||||||
|
|
||||||
|
Source files:
|
||||||
|
- `recipes/core/base/source/drivers/pcid/`
|
||||||
|
- `local/recipes/system/driver-manager/`
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/device/msi.rs`
|
||||||
|
- `local/recipes/drivers/redox-driver-sys/source/src/irq.rs`
|
||||||
|
|
||||||
|
#### What is weak
|
||||||
|
|
||||||
|
| Issue | Detail |
|
||||||
|
|-------|--------|
|
||||||
|
| Legacy IRQ dominance | `e1000d` and `ided` still use legacy IRQ (IRQ 11, IRQ 14/15) |
|
||||||
|
| MSI-X adoption | Only `ixgbed` and GPU paths use MSI-X; most drivers on legacy INTx |
|
||||||
|
| IOMMU MSI gate | `iommu_validate_msi_irq()` is a stub — always returns `true` |
|
||||||
|
| IRQ affinity | Available in API but not widely used |
|
||||||
|
| pcid helper fragility | Some paths still treat malformed capabilities as invariants |
|
||||||
|
| Hardware validation | MSI-X proven in QEMU only; no real hardware vector validation |
|
||||||
|
|
||||||
|
The IRQ/low-level plan (`IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md`)
|
||||||
|
correctly identifies that the architecture is sound but the **runtime proof is
|
||||||
|
thin**. Priority 1 is "MSI-X runtime validation on real devices."
|
||||||
|
|
||||||
|
**Verdict**: The PCI/IRQ substrate is one of the strongest parts of the stack,
|
||||||
|
but it is **not yet release-grade** because MSI-X is not widely adopted and
|
||||||
|
hardware validation is missing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.6 IOMMU / DMA
|
||||||
|
|
||||||
|
**Status**: 🟡 AMD-Vi real but **unvalidated**; Intel VT-d **missing**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/iommu/amd/`, `drivers/iommu/intel/`,
|
||||||
|
`drivers/iommu/dma-iommu.c`
|
||||||
|
|
||||||
|
#### AMD-Vi
|
||||||
|
|
||||||
|
The `iommu` daemon is **real**, not a stub:
|
||||||
|
|
||||||
|
- `AmdViUnit::init()` maps MMIO, programs device tables, command buffer, event
|
||||||
|
log, interrupt remap table (IRTE)
|
||||||
|
- QEMU first-use proof passes: discovers units, initializes, drains events
|
||||||
|
- Self-test path exists: `redbear-phase-iommu-check`
|
||||||
|
|
||||||
|
Source: `local/recipes/system/iommu/source/src/amd_vi.rs`
|
||||||
|
|
||||||
|
**But**:
|
||||||
|
- The boot log shows: `iommu: no AMD-Vi units found (source=none,
|
||||||
|
kernel_acpi_status=empty, ivrs_path=none)`
|
||||||
|
- This happens because the IVRS table is absent on this platform (QEMU i440fx
|
||||||
|
does not provide IVRS)
|
||||||
|
- When zero units are found, the daemon registers `scheme:iommu` and exits
|
||||||
|
- **Real AMD hardware validation: NONE**
|
||||||
|
|
||||||
|
#### Intel VT-d
|
||||||
|
|
||||||
|
- DMAR parsing exists in `acpid/src/dmar/mod.rs` but is **orphaned**
|
||||||
|
- No Intel VT-d runtime daemon
|
||||||
|
- No DMA remapping for Intel platforms
|
||||||
|
- `iommu` daemon is AMD-Vi only
|
||||||
|
|
||||||
|
#### DMA integration
|
||||||
|
|
||||||
|
- DMA allocation exists in `redox-driver-sys`
|
||||||
|
- But IOMMU integration is incomplete: `iommu_validate_msi_irq()` is a no-op,
|
||||||
|
and there is no enforced DMA map/unmap with IOMMU translation
|
||||||
|
- Linux 7.1 has `dma-iommu.c` which handles IOMMU-aware DMA mapping for all
|
||||||
|
devices behind an IOMMU
|
||||||
|
|
||||||
|
**Verdict**: AMD-Vi is implemented but unvalidated. Intel VT-d is missing.
|
||||||
|
DMA/IOMMU integration is incomplete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.7 USB Stack
|
||||||
|
|
||||||
|
**Status**: 🟡 xHCI real but **QEMU-only**; **EHCI/UHCI/OHCI missing**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/usb/host/`, `drivers/usb/core/`,
|
||||||
|
`drivers/hid/usbhid/`
|
||||||
|
|
||||||
|
#### xHCI
|
||||||
|
|
||||||
|
The xHCI driver (`xhcid`) is **real and substantial**:
|
||||||
|
|
||||||
|
- ~6,000 lines of Rust
|
||||||
|
- 88+ error handling fixes applied via Red Bear patch
|
||||||
|
- Interrupt-driven path restored (MSI/MSI-X/INTx)
|
||||||
|
- Event ring growth implemented (ring doubling)
|
||||||
|
- BOS/SuperSpeed descriptor fetching
|
||||||
|
- Speed detection for hub children
|
||||||
|
- USB 3 hub endpoint configuration
|
||||||
|
- Suspend/resume API skeleton
|
||||||
|
|
||||||
|
Source: `recipes/core/base/source/drivers/usb/xhcid/`
|
||||||
|
|
||||||
|
**But**:
|
||||||
|
- Only **QEMU-validated** — no real hardware testing
|
||||||
|
- ~57 TODO/FIXME comments remain
|
||||||
|
- Some `panic!()` sites remain in device enumerator
|
||||||
|
|
||||||
|
#### Missing host controllers
|
||||||
|
|
||||||
|
**No EHCI, UHCI, or OHCI drivers exist** in the Red Bear tree.
|
||||||
|
|
||||||
|
| Controller | Speed | Why it matters |
|
||||||
|
|------------|-------|----------------|
|
||||||
|
| EHCI | USB 2.0 High Speed | Most USB 2.0 keyboards/mice |
|
||||||
|
| OHCI | USB 1.1 Full/Low Speed | AMD/VIA legacy USB |
|
||||||
|
| UHCI | USB 1.1 Full/Low Speed | Intel legacy USB |
|
||||||
|
|
||||||
|
Linux 7.1 has full implementations for all three:
|
||||||
|
- `drivers/usb/host/ehci-hcd.c` (~4,500 lines)
|
||||||
|
- `drivers/usb/host/ohci-hcd.c` (~3,500 lines)
|
||||||
|
- `drivers/usb/host/uhci-hcd.c` (~2,800 lines)
|
||||||
|
|
||||||
|
The USB implementation plan honestly states:
|
||||||
|
|
||||||
|
> "External USB keyboard input is reliably available only when the keyboard is
|
||||||
|
> reached through the `xHCI -> usbhubd/usbhidd -> inputd` path."
|
||||||
|
|
||||||
|
On many bare-metal systems, USB keyboards route through EHCI or OHCI, not xHCI.
|
||||||
|
**Red Bear cannot claim reliable USB keyboard boot fallback.**
|
||||||
|
|
||||||
|
#### Class drivers
|
||||||
|
|
||||||
|
| Driver | Status | Quality |
|
||||||
|
|--------|--------|---------|
|
||||||
|
| `usbhubd` | Real | Good — interrupt-driven change detection, graceful per-port errors |
|
||||||
|
| `usbhidd` | Real | Good — HID report parsing, named producers, no panics in loop |
|
||||||
|
| `usbscsid` | Real | Good — BOT transport, stall recovery, `ReadCapacity16` |
|
||||||
|
|
||||||
|
**Verdict**: xHCI is real but QEMU-only. The absence of EHCI/UHCI/OHCI is a
|
||||||
|
**critical bare-metal gap**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.8 Firmware Loading
|
||||||
|
|
||||||
|
**Status**: 🟢 **Real and functional**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/base/firmware_loader/`
|
||||||
|
|
||||||
|
The `firmware-loader` daemon is one of the most complete subsystems:
|
||||||
|
|
||||||
|
- On-demand blob loading via `scheme:firmware`
|
||||||
|
- Indexes `/lib/firmware` at startup
|
||||||
|
- Persistent cache with fallback chains
|
||||||
|
- Async `request_firmware_nowait()` with timeout and retry
|
||||||
|
- Emits uevents for consumers
|
||||||
|
- Read-only scheme with mmap support
|
||||||
|
|
||||||
|
Source: `local/recipes/system/firmware-loader/source/`
|
||||||
|
|
||||||
|
The boot log does not show firmware loading activity because no device requested
|
||||||
|
firmware during this boot (no GPU, no Wi-Fi).
|
||||||
|
|
||||||
|
**Verdict**: This subsystem is **production-ready** architecturally. Needs
|
||||||
|
hardware validation when GPU/Wi-Fi drivers are active.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.9 Memory Management
|
||||||
|
|
||||||
|
**Status**: 🟡 Basic but functional; **advanced features missing**
|
||||||
|
**Linux 7.1 equivalent**: `mm/`, `arch/x86/mm/`
|
||||||
|
|
||||||
|
#### What is real
|
||||||
|
|
||||||
|
- Frame allocator / buddy-like free list
|
||||||
|
- Kernel page-table setup (4-level on x86_64)
|
||||||
|
- Device-memory mapping for MMIO
|
||||||
|
- Explicit memory-region handling
|
||||||
|
- Early boot memory map parsing from ACPI/firmware
|
||||||
|
- 7,092 MB detected in boot log
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- `recipes/core/kernel/source/src/memory/mod.rs`
|
||||||
|
- `recipes/core/kernel/source/src/startup/memory.rs`
|
||||||
|
|
||||||
|
#### What is missing
|
||||||
|
|
||||||
|
| Feature | Linux 7.1 | Red Bear |
|
||||||
|
|---------|-----------|----------|
|
||||||
|
| Swap | Full swap with page reclaim | Not implemented |
|
||||||
|
| NUMA | Node-aware allocation, migrate pages | No NUMA awareness |
|
||||||
|
| Memory hotplug | Add/remove memory at runtime | Not implemented |
|
||||||
|
| Reclaim/compaction | `kswapd`, memory pressure handling | Not implemented |
|
||||||
|
| OOM killer | `out_of_memory()` kills processes | Not implemented |
|
||||||
|
| Huge pages | THP, hugetlbfs | Not implemented |
|
||||||
|
| Memory cgroups | `memcg` resource limits | Not implemented |
|
||||||
|
| Demand paging | Lazy allocation on fault | Basic but no swap backing |
|
||||||
|
|
||||||
|
**Verdict**: Sufficient for current boot and userspace needs, but not
|
||||||
|
production-grade for memory-intensive workloads.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.10 Logging Infrastructure
|
||||||
|
|
||||||
|
**Status**: 🟡 Basic append-only; **no rotation, no structured storage**
|
||||||
|
**Linux 7.1 equivalent**: No direct equivalent; compare to `systemd-journald`,
|
||||||
|
`rsyslog`, `syslog-ng`
|
||||||
|
|
||||||
|
#### What is real
|
||||||
|
|
||||||
|
- `logd` daemon serves `scheme:log`
|
||||||
|
- Persists to `/var/log/system.log`
|
||||||
|
- prepends startup banner, backfills new sinks
|
||||||
|
- Mirrors kernel log input
|
||||||
|
- relibc syslog API (`syslog()`, `openlog()`) writes to `/scheme/log`
|
||||||
|
|
||||||
|
Source:
|
||||||
|
- `recipes/core/base/source/logd/src/main.rs`
|
||||||
|
- `recipes/core/base/source/logd/src/scheme.rs`
|
||||||
|
|
||||||
|
#### What is weak
|
||||||
|
|
||||||
|
| Issue | Detail |
|
||||||
|
|-------|--------|
|
||||||
|
| Append-only | `/var/log/system.log` grows forever |
|
||||||
|
| No rotation | No size-based or time-based truncation |
|
||||||
|
| No retention | Old logs never deleted |
|
||||||
|
| No structured format | Plain text only; no JSON or binary journal |
|
||||||
|
| read path TODO | `scheme.rs` has a TODO for reading log history |
|
||||||
|
| Console dominance | Most daemon output still goes to console timestamps |
|
||||||
|
| No per-service logs | All logs in one file |
|
||||||
|
|
||||||
|
The boot log shows console timestamps because daemons write to stderr, which
|
||||||
|
init captures and logs. The persistent `/var/log/system.log` exists but is
|
||||||
|
append-only with no management.
|
||||||
|
|
||||||
|
**Verdict**: Functional for debugging but not suitable for production
|
||||||
|
observability. Needs rotation, structured format, and per-service separation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.11 Udev / Device Discovery
|
||||||
|
|
||||||
|
**Status**: 🟡 Real but **limited**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/base/core.c`, `lib/kobject_uevent.c`, `udev/`
|
||||||
|
|
||||||
|
#### What is real
|
||||||
|
|
||||||
|
`udev-shim` is a **real implementation**, not a placeholder:
|
||||||
|
|
||||||
|
- Enumerates PCI devices via `pcid` scheme
|
||||||
|
- Classifies devices by class/subclass/vendor
|
||||||
|
- Creates `/dev` nodes and symlinks
|
||||||
|
- Writes `/etc/udev/rules.d/50-default.rules`
|
||||||
|
- Exposes `scheme:udev`
|
||||||
|
- Polls for changes (not event-driven)
|
||||||
|
|
||||||
|
Source: `local/recipes/system/udev-shim/source/`
|
||||||
|
|
||||||
|
The boot log shows:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ OK ] Started udev compatibility shim
|
||||||
|
[INFO] udev-shim: enumerated 1 PCI device(s)
|
||||||
|
[INFO] udev-shim: wrote default rules to /etc/udev/rules.d/50-default.rules
|
||||||
|
```
|
||||||
|
|
||||||
|
#### What is weak
|
||||||
|
|
||||||
|
| Issue | Detail |
|
||||||
|
|-------|--------|
|
||||||
|
| Hardcoded rules | Only 3 rules: net naming (`enp*`), NVMe by-id, SATA by-id |
|
||||||
|
| Polling hotplug | Polls every N seconds; not event-driven like Linux udev/netlink |
|
||||||
|
| No rules engine | Cannot parse Linux udev rules; rules are compiled-in |
|
||||||
|
| libudev-stub TODO | `local/recipes/libs/libudev-stub/recipe.toml` explicitly marked TODO |
|
||||||
|
| Limited coverage | Only PCI devices; no USB, no ACPI, no platform devices |
|
||||||
|
| No persistent db | Device state not saved across reboots |
|
||||||
|
|
||||||
|
Linux 7.1 udev:
|
||||||
|
- Event-driven via netlink `NETLINK_KOBJECT_UEVENT`
|
||||||
|
- Full rules engine with `MATCH`, `ACTION`, `ENV`, `RUN`
|
||||||
|
- Persistent database in `/run/udev/`
|
||||||
|
- `udevadm` tool for querying and triggering
|
||||||
|
- Integrates with `systemd` for device units
|
||||||
|
|
||||||
|
**Verdict**: Functional for basic PCI device naming but far from a full udev
|
||||||
|
replacement. Polling hotplug is inefficient.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.12 Input Stack
|
||||||
|
|
||||||
|
**Status**: 🟡 Real but **uneven quality**
|
||||||
|
**Linux 7.1 equivalent**: `drivers/input/`, `drivers/hid/`, `drivers/serio/`
|
||||||
|
|
||||||
|
#### What is real
|
||||||
|
|
||||||
|
| Component | Status | Detail |
|
||||||
|
|-----------|--------|--------|
|
||||||
|
| `ps2d` | Real | PS/2 keyboard + mouse; kernel serio byte queues |
|
||||||
|
| `usbhidd` | Real | HID report parsing, named producers |
|
||||||
|
| `inputd` | Real | Producer/consumer scheme, VT switching, keymaps |
|
||||||
|
| `evdevd` | Real | evdev scheme, orbclient→evdev translation |
|
||||||
|
| `i2c-hidd` | Real | ACPI PNP0C50 scan, _CRS parsing |
|
||||||
|
| `intel-thc-hidd` | Partial | PCI init works; main loop sleeps 5s — **no input streaming** |
|
||||||
|
|
||||||
|
The boot log shows PS/2 and evdev working:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ OK ] Started PS/2 driver
|
||||||
|
[ OK ] Started Evdev input daemon
|
||||||
|
[INFO] evdevd: registered scheme:evdev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gaps vs Linux 7.1
|
||||||
|
|
||||||
|
| Gap | Severity | Linux Reference |
|
||||||
|
|-----|----------|-----------------|
|
||||||
|
| intel-thc-hidd no streaming | **High** | `drivers/hid/intel-thc-hid/` full probe+report |
|
||||||
|
| No multitouch/ABS_MT | **High** | `drivers/input/input-mt.c` |
|
||||||
|
| No libinput acceleration | **High** | libinput: velocity curves, palm detection |
|
||||||
|
| No PS/2 extended protocols | Medium | `libps2.c` ImPS/2 scroll, Explorer 5-btn |
|
||||||
|
| No HID quirks table | Medium | `hid-quirks.c` 4000+ entries |
|
||||||
|
| No input hotplug | Medium | udev + inotify on `/dev/input/` |
|
||||||
|
|
||||||
|
**Verdict**: The input stack exists and works for basic keyboard/mouse. Touch
|
||||||
|
and advanced HID are incomplete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Root Cause Analysis
|
||||||
|
|
||||||
|
### Why the system runs hot on bare metal
|
||||||
|
|
||||||
|
1. **No C-state management** → CPUs never enter low-power idle states (C1, C1E,
|
||||||
|
C3, C6, C7). They spin in the kernel idle loop at full power.
|
||||||
|
2. **No ACPI thermal zones** → `acpid` does not enumerate thermal zones, so
|
||||||
|
`thermald` has no temperature data to act on.
|
||||||
|
3. **No hwmon sensor drivers** → No temperature sensors are readable. The system
|
||||||
|
is "flying blind."
|
||||||
|
4. **No ACPI fan control** → Fan devices are not enumerated, so `thermald`
|
||||||
|
cannot turn on cooling.
|
||||||
|
5. **cpufreqd MSR writes failing** → Even P-state throttling is not working
|
||||||
|
reliably (`MSR write failed` in boot log).
|
||||||
|
|
||||||
|
**Fix priority**: C-states (immediate heat reduction) > ACPI thermal zones
|
||||||
|
(enables thermald) > hwmon sensors (operator visibility) > fan control
|
||||||
|
(active cooling).
|
||||||
|
|
||||||
|
### Why only 1 CPU shows online
|
||||||
|
|
||||||
|
1. **QEMU i440fx** exposes only 1 vCPU by default (most likely in the provided
|
||||||
|
boot log)
|
||||||
|
2. **AP startup races** — LogicalCpuId race, missing SIPI delays, AP_READY dual
|
||||||
|
mechanism can cause APs to fail startup on real hardware
|
||||||
|
3. **MAX_CPU_COUNT=128** too small for high-core-count AMD EPYC
|
||||||
|
4. No bare-metal validation means we don't know which of these is the real
|
||||||
|
blocker on actual hardware
|
||||||
|
|
||||||
|
### Why USB keyboard may not work on bare metal
|
||||||
|
|
||||||
|
1. **Only xHCI exists** — no EHCI/UHCI/OHCI drivers
|
||||||
|
2. Many systems route USB 2.0 keyboards through EHCI
|
||||||
|
3. Some AMD/VIA systems use OHCI for legacy ports
|
||||||
|
4. Some Intel systems use UHCI for legacy ports
|
||||||
|
5. No companion controller support to route low-speed devices from EHCI to xHCI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Honest Status Matrix
|
||||||
|
|
||||||
|
| Subsystem | Status | Linux 7.1 Parity | Evidence Class |
|
||||||
|
|-----------|--------|------------------|----------------|
|
||||||
|
| SMP bring-up | 🟡 Partial | ~30% | Source + QEMU; bare metal unvalidated |
|
||||||
|
| C-states (cpuidle) | 🔴 Missing | 0% | No subsystem exists |
|
||||||
|
| P-states (cpufreq) | 🟡 Partial | ~20% | Daemon real but MSR writes failing |
|
||||||
|
| Thermal management | 🔴 Missing backend | ~10% | thermald exists but no ACPI backend |
|
||||||
|
| Hardware sensors (hwmon) | 🔴 Missing | 0% | No infrastructure, no drivers |
|
||||||
|
| ACPI boot / shutdown | 🟢 Baseline | ~40% | Boots, shutdown works, sleep partial |
|
||||||
|
| ACPI thermal / fan | 🔴 Missing | 0% | Not implemented in acpid |
|
||||||
|
| PCI enumeration | 🟢 Working | ~60% | Real, robust, driver-manager binds |
|
||||||
|
| MSI/MSI-X infrastructure | 🟡 Real | ~40% | Kernel real, driver adoption low |
|
||||||
|
| IOMMU AMD-Vi | 🟡 Real, unvalidated | ~30% | QEMU proof only |
|
||||||
|
| IOMMU Intel VT-d | 🔴 Missing | 0% | Orphaned DMAR parsing only |
|
||||||
|
| USB xHCI | 🟡 Real, QEMU-only | ~30% | No hardware validation |
|
||||||
|
| USB EHCI/UHCI/OHCI | 🔴 Missing | 0% | No drivers |
|
||||||
|
| Firmware loading | 🟢 Real | ~70% | On-demand, async, validated in build |
|
||||||
|
| Memory management | 🟡 Basic | ~30% | Frame allocator; no swap/NUMA/hotplug |
|
||||||
|
| Logging | 🟡 Basic | ~20% | Append-only, no rotation |
|
||||||
|
| Udev | 🟡 Limited | ~25% | Polling, hardcoded rules |
|
||||||
|
| Input (PS/2, USB HID) | 🟢 Working | ~50% | Real but touch/advanced HID missing |
|
||||||
|
| Input (I2C HID, THC) | 🟡 Partial | ~20% | i2c-hidd real; intel-thc-hidd non-functional |
|
||||||
|
| D-Bus system bus | 🟢 Working | ~60% | Real, services wired |
|
||||||
|
| D-Bus session bus | 🟡 Partial | ~30% | Partially wired |
|
||||||
|
| Network (wired) | 🟢 Working | ~60% | e1000d, virtio-net work |
|
||||||
|
| Network (Wi-Fi) | 🟡 Host-tested | ~20% | Intel stack builds; no hardware validation |
|
||||||
|
| Bluetooth | 🟡 Experimental | ~15% | BLE controller probe works; limited |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. New Improvement Plan
|
||||||
|
|
||||||
|
This plan is ordered by **impact on bare-metal usability** and **dependency
|
||||||
|
chain**. Earlier phases unblock later ones.
|
||||||
|
|
||||||
|
### Phase 1: Bare-Metal Boot Hardening (6–8 weeks)
|
||||||
|
**Goal**: Boot reliably on diverse bare metal with all cores, reasonable
|
||||||
|
temperature, and working USB keyboard.
|
||||||
|
|
||||||
|
#### 1.1 Fix SMP AP Startup (2 weeks)
|
||||||
|
- [ ] Fix K1 (LogicalCpuId race) — use `fetch_add` before AP reads ID
|
||||||
|
- [ ] Fix K2 (AP_READY dual mechanism) — consolidate to single atomic
|
||||||
|
- [ ] Fix K7 (missing SIPI delays) — add TSC-based 10ms INIT→SIPI delay per Intel SDM
|
||||||
|
- [ ] Increase MAX_CPU_COUNT to 256
|
||||||
|
- [ ] Validate on AMD Ryzen and Intel Core bare metal
|
||||||
|
- [ ] Capture boot log showing `SMP: N CPUs online` where N > 1
|
||||||
|
|
||||||
|
#### 1.2 Implement Basic C-states (2 weeks)
|
||||||
|
- [ ] Add `cpuidle` framework in kernel: idle state table, enter/exit hooks
|
||||||
|
- [ ] Parse ACPI `_CST` table in acpid, expose via `/scheme/acpi/cstates`
|
||||||
|
- [ ] Implement `hlt`-based idle (C1) — immediate heat reduction
|
||||||
|
- [ ] Add `mwait`-based C1E/C3 for Intel; add `AMD C1E` support
|
||||||
|
- [ ] Wire to scheduler idle path: call `cpuidle_enter()` when no runnable threads
|
||||||
|
- [ ] Validate temperature drop on bare metal
|
||||||
|
|
||||||
|
#### 1.3 Enable ACPI Thermal Zones (2 weeks)
|
||||||
|
- [ ] Add thermal zone enumeration to acpid (`_TZ` namespace walk)
|
||||||
|
- [ ] Expose `/scheme/acpi/thermal` with zone temperatures and trip points
|
||||||
|
- [ ] Wire thermald to read from `/scheme/acpi/thermal`
|
||||||
|
- [ ] Add passive cooling policy: throttle cpufreqd when trip point exceeded
|
||||||
|
- [ ] Add ACPI fan device support (`_FAN` objects)
|
||||||
|
- [ ] Wire thermald fan control
|
||||||
|
|
||||||
|
#### 1.4 Add Basic Sensor Drivers (2 weeks)
|
||||||
|
- [ ] Create `scheme:hwmon` or extend `/scheme/acpi/thermal`
|
||||||
|
- [ ] Port `coretemp` driver (Intel CPU temperature MSR)
|
||||||
|
- [ ] Port `k10temp` driver (AMD CPU temperature MSR)
|
||||||
|
- [ ] Add temperature readout to `redbear-info`
|
||||||
|
- [ ] Validate sensor readings on bare metal
|
||||||
|
|
||||||
|
### Phase 2: USB Completeness (4–6 weeks)
|
||||||
|
**Goal**: USB keyboard and storage work on all bare metal.
|
||||||
|
|
||||||
|
#### 2.1 EHCI Host Controller (3 weeks)
|
||||||
|
- [ ] Implement EHCI HCD based on Linux `drivers/usb/host/ehci-hcd.c`
|
||||||
|
- [ ] Support USB 2.0 high-speed keyboards, mice, storage
|
||||||
|
- [ ] Integrate with driver-manager config
|
||||||
|
- [ ] Validate on Intel and AMD bare metal
|
||||||
|
|
||||||
|
#### 2.2 OHCI/UHCI Fallback (2 weeks)
|
||||||
|
- [ ] Implement OHCI for AMD/VIA systems
|
||||||
|
- [ ] Implement UHCI for Intel legacy systems
|
||||||
|
- [ ] Add companion controller topology support
|
||||||
|
|
||||||
|
#### 2.3 USB Boot Resilience (1 week)
|
||||||
|
- [ ] Ensure USB keyboard available before login prompt on all profiles
|
||||||
|
- [ ] Add USB storage boot support
|
||||||
|
- [ ] Hot-plug stress testing on real hardware
|
||||||
|
|
||||||
|
### Phase 3: IRQ / IOMMU / MSI-X Hardening (4–6 weeks)
|
||||||
|
**Goal**: Production-grade interrupt and DMA safety.
|
||||||
|
|
||||||
|
#### 3.1 MSI-X Adoption (2 weeks)
|
||||||
|
- [ ] Migrate `e1000d` to MSI-X
|
||||||
|
- [ ] Migrate `ided` to MSI-X (or document legacy-IRQ-only rationale)
|
||||||
|
- [ ] Add MSI-X fallback logging to all PCI drivers
|
||||||
|
- [ ] Validate on real hardware
|
||||||
|
|
||||||
|
#### 3.2 IOMMU Hardware Validation (2 weeks)
|
||||||
|
- [ ] AMD-Vi validation on real AMD hardware
|
||||||
|
- [ ] Implement Intel VT-d daemon (migrate from orphaned acpid DMAR)
|
||||||
|
- [ ] Replace `iommu_validate_msi_irq()` stub with real validation
|
||||||
|
- [ ] DMA map/unmap with IOMMU translation
|
||||||
|
|
||||||
|
#### 3.3 IRQ Quality (2 weeks)
|
||||||
|
- [ ] IRQ affinity validation per driver
|
||||||
|
- [ ] Interrupt coalescing for network/storage
|
||||||
|
- [ ] Spurious IRQ accounting improvement
|
||||||
|
|
||||||
|
### Phase 4: Observability & Logging (2–4 weeks)
|
||||||
|
**Goal**: Operator can diagnose system health.
|
||||||
|
|
||||||
|
#### 4.1 Structured Logging (2 weeks)
|
||||||
|
- [ ] Add JSON-structured log format option to logd
|
||||||
|
- [ ] Per-service log files in `/var/log/<service>/`
|
||||||
|
- [ ] Size-based log rotation (e.g., 10 MB per file)
|
||||||
|
- [ ] Time-based log retention (e.g., 7 days)
|
||||||
|
|
||||||
|
#### 4.2 Udev Rules Engine (2 weeks)
|
||||||
|
- [ ] Replace hardcoded rules with subset of Linux udev rules parser
|
||||||
|
- [ ] Event-driven hotplug via scheme notifications (replace polling)
|
||||||
|
- [ ] Persistent device database across reboots
|
||||||
|
|
||||||
|
#### 4.3 System Health Dashboard (1 week)
|
||||||
|
- [ ] `redbear-info` thermal/CPU/fan display tab
|
||||||
|
- [ ] Boot timeline persistence across switchroot
|
||||||
|
- [ ] Real-time CPU/memory/network metrics
|
||||||
|
|
||||||
|
### Phase 5: Hardware Validation Matrix (4–6 weeks)
|
||||||
|
**Goal**: Evidence-based support claims.
|
||||||
|
|
||||||
|
#### 5.1 Define Validation Targets
|
||||||
|
Minimum 4 hardware classes:
|
||||||
|
1. AMD desktop (Ryzen, discrete GPU)
|
||||||
|
2. Intel desktop (Core, integrated GPU)
|
||||||
|
3. AMD laptop (Ryzen mobile)
|
||||||
|
4. Intel laptop (Core mobile)
|
||||||
|
|
||||||
|
#### 5.2 Per-Target Checklist
|
||||||
|
For each target, validate and record:
|
||||||
|
- [ ] Boots to login prompt
|
||||||
|
- [ ] All CPU cores online (`SMP: N CPUs online` matches hardware)
|
||||||
|
- [ ] USB keyboard works at boot
|
||||||
|
- [ ] USB storage mounts
|
||||||
|
- [ ] Network (wired) obtains DHCP lease
|
||||||
|
- [ ] Temperature readable via `redbear-info`
|
||||||
|
- [ ] Shutdown succeeds cleanly
|
||||||
|
- [ ] Reboot succeeds cleanly
|
||||||
|
|
||||||
|
#### 5.3 Negative-Result Capture
|
||||||
|
- [ ] Document failures per target (e.g., "AMD X670E: AP startup timeout",
|
||||||
|
"Intel Raptor Lake: SMBIOS missing")
|
||||||
|
- [ ] Update this assessment with validation evidence
|
||||||
|
|
||||||
|
### Phase 6: Desktop Stack Continuation (Parallel)
|
||||||
|
**Goal**: Continue the CONSOLE-TO-KDE path on top of hardened substrate.
|
||||||
|
|
||||||
|
This phase is **orthogonal** to the low-level work above. It depends on:
|
||||||
|
- Qt6Quick/QML downstream proof (unblocks kirigami)
|
||||||
|
- Real KWin build
|
||||||
|
- GPU CS ioctl backend + Mesa HW cross-compile
|
||||||
|
|
||||||
|
See `CONSOLE-TO-KDE-DESKTOP-PLAN.md` for detailed desktop path planning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Stale Documents — Remove
|
||||||
|
|
||||||
|
The following documents are **superseded** by this assessment and should be
|
||||||
|
removed from `local/docs/`:
|
||||||
|
|
||||||
|
| File | Reason |
|
||||||
|
|------|--------|
|
||||||
|
| `IMPLEMENTATION-MASTER-PLAN.md` | Master plan role now covered by CONSOLE-TO-KDE v4.1 and this doc |
|
||||||
|
| `SUBSYSTEM-ASSESSMENT-2026-05.md` | Assessment consolidated here with broader scope |
|
||||||
|
| `SMP-BOOT-HARDENING-PLAN.md` | SMP issues and fixes incorporated here; detailed issue list can be referenced from git history |
|
||||||
|
| `CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md` | MSI Phase 1 is complete; remaining DMA/scheduler work tracked here |
|
||||||
|
| `COMPREHENSIVE-BOOT-IMPROVEMENT-PLAN.md` | Boot issues consolidated into this assessment |
|
||||||
|
|
||||||
|
**Canonical documents that remain authoritative**:
|
||||||
|
- `ACPI-IMPROVEMENT-PLAN.md` — detailed ACPI wave execution
|
||||||
|
- `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` — PCI/IRQ/MSI-X details
|
||||||
|
- `USB-IMPLEMENTATION-PLAN.md` — USB phase execution
|
||||||
|
- `CONSOLE-TO-KDE-DESKTOP-PLAN.md` — desktop path
|
||||||
|
- `DRM-MODERNIZATION-EXECUTION-PLAN.md` — GPU stack
|
||||||
|
- `WIFI-IMPLEMENTATION-PLAN.md` — Wi-Fi architecture
|
||||||
|
- `BLUETOOTH-IMPLEMENTATION-PLAN.md` — Bluetooth stack
|
||||||
|
- `DBUS-INTEGRATION-PLAN.md` — D-Bus architecture
|
||||||
|
- `GREETER-LOGIN-IMPLEMENTATION-PLAN.md` — greeter design
|
||||||
|
- `QUIRKS-SYSTEM.md` — quirk infrastructure
|
||||||
|
- `PATCH-GOVERNANCE.md` — patch workflow
|
||||||
|
- `BUILD-SYSTEM-HARDENING-PLAN.md` — build system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Evidence Model
|
||||||
|
|
||||||
|
This assessment uses the same evidence vocabulary as the canonical subsystem
|
||||||
|
plans:
|
||||||
|
|
||||||
|
| Class | Meaning |
|
||||||
|
|-------|---------|
|
||||||
|
| **Source-visible** | Behavior visible in checked-in source |
|
||||||
|
| **Build-visible** | Code compiles and stages in current build |
|
||||||
|
| **QEMU-validated** | Behavior exercised successfully in QEMU |
|
||||||
|
| **Runtime-validated** | Behavior exercised in real boot/runtime |
|
||||||
|
| **Hardware-validated** | Behavior proven on named bare-metal hardware |
|
||||||
|
| **Negative-result-documented** | Failures and gaps are explicitly recorded |
|
||||||
|
|
||||||
|
**No subsystem in this assessment is marked "hardware-validated"** because no
|
||||||
|
component has been proven on real bare metal with the rigor defined in
|
||||||
|
`ACPI-IMPROVEMENT-PLAN.md` Wave 7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Definition of Done
|
||||||
|
|
||||||
|
This plan is complete when:
|
||||||
|
|
||||||
|
1. SMP brings up all cores reliably on AMD and Intel bare metal
|
||||||
|
2. C-states reduce idle power consumption measurably
|
||||||
|
3. ACPI thermal zones are readable and thermald responds to trip points
|
||||||
|
4. At least 2 sensor drivers report temperature on bare metal
|
||||||
|
5. EHCI driver enables USB keyboard on systems without xHCI routing
|
||||||
|
6. MSI-X is adopted by all new PCI drivers; legacy IRQ is documented fallback
|
||||||
|
7. IOMMU validates on at least one AMD and one Intel platform
|
||||||
|
8. Logging has rotation and per-service separation
|
||||||
|
9. Udev-shim supports event-driven hotplug
|
||||||
|
10. A validation matrix with 4+ hardware targets is published and maintained
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*End of assessment.*
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
# Red Bear OS — CPU/DMA/IRQ/MSI/Scheduler Fix Plan
|
|
||||||
|
|
||||||
**Date**: 2026-05-04
|
|
||||||
**Updated**: 2026-05-04 (MSI T1.1–T2.2 implemented, committed, pushed)
|
|
||||||
**Status**: Active — MSI Phase 1 complete, DMA/Scheduler pending
|
|
||||||
**Source of truth**: Linux kernel 7.0 (local/reference/linux-7.0/)
|
|
||||||
|
|
||||||
## 1. Problem Statement
|
|
||||||
|
|
||||||
Five critical integration gaps in the microkernel architecture:
|
|
||||||
|
|
||||||
| Gap | Severity | Impact | Status |
|
|
||||||
|-----|----------|--------|--------|
|
|
||||||
| MSI absent from kernel | CRITICAL | All NVMe/GPU/NIC on legacy INTx | ✅ RESOLVED (P8-msi.patch) |
|
|
||||||
| DMA/IOMMU not integrated | CRITICAL | DMA buffers unprotected | ⏳ Pending |
|
|
||||||
| PIT tick (148Hz) vs LAPIC (1000Hz) | HIGH | Scheduler 6x slower than Linux | ✅ RESOLVED (P7-scheduler patch) |
|
|
||||||
| Global scheduler lock | HIGH | Serializes all context switches | ✅ RESOLVED (work-stealing) |
|
|
||||||
| Thread creation (3 IPC hops) | HIGH | 3x slower than Linux clone() | ⏳ Pending |
|
|
||||||
|
|
||||||
## 2. Phase 1: MSI/MSI-X in Kernel (Week 1-3) ✅ COMPLETE
|
|
||||||
|
|
||||||
### T1.1: MSI Capability Parsing ✅ DONE
|
|
||||||
- File: `kernel/src/arch/x86_shared/device/msi.rs` (61 lines)
|
|
||||||
- Commit: `678980521` in `P8-msi.patch`
|
|
||||||
- Linux ref: `arch/x86/kernel/apic/msi.c` (391 lines)
|
|
||||||
- Implements: `MsiMessage` (compose/validate), `MsiCapability` (parse 32/64-bit), `MsixCapability` (parse table/PBA), `is_valid_msi_address`, `is_valid_msi_vector`
|
|
||||||
- Bounds-safe: all `parse()` methods return `Option<Self>`, using `.get()` instead of raw indexing
|
|
||||||
|
|
||||||
### T1.2: Vector Allocation Matrix ✅ DONE
|
|
||||||
- File: `kernel/src/arch/x86_shared/device/vector.rs` (53 lines)
|
|
||||||
- Commit: `678980521` in `P8-msi.patch`
|
|
||||||
- Linux ref: `arch/x86/kernel/apic/vector.c` (1387 lines)
|
|
||||||
- Implements: per-CPU bitmatrix (7×32-bit banks = 224 vectors 32-255), `allocate_vector`, `free_vector`
|
|
||||||
- Lock-free CAS-based allocation with `trailing_ones()` find-first-zero
|
|
||||||
- NOTE: VECTORS table is global (not yet per-CPU sharded) — sufficient for 224 vectors
|
|
||||||
|
|
||||||
### T1.3: MSI IRQ Domain (Scheme Integration) ✅ DONE
|
|
||||||
- File: `kernel/src/scheme/irq.rs`
|
|
||||||
- Commit: `678980521` in `P8-msi.patch`
|
|
||||||
- Implements: `msi_vector_is_valid()` (32-0xEF range check), `iommu_validate_msi_irq()` hook (stub: always true), IOMMU gate at `irq_trigger()` for vectors ≥16
|
|
||||||
|
|
||||||
### T1.4: Userspace MSI Consumer (driver-sys) ✅ DONE
|
|
||||||
- File: `local/recipes/drivers/redox-driver-sys/source/src/irq.rs`
|
|
||||||
- Commit: `678980521`
|
|
||||||
- Implements: `MsiAllocation` with round-robin CPU allocation, `irq_set_affinity` (scheme write), `program_x86_message` with kernel-mediated address/vector validation (mask `0xFFF0_0000`)
|
|
||||||
- Quirk-aware fallback retained: FORCE_LEGACY, NO_MSI, NO_MSIX
|
|
||||||
|
|
||||||
### T1.5: Kernel-side MSI Affinity Handler ✅ DONE
|
|
||||||
- File: `kernel/src/scheme/irq.rs`
|
|
||||||
- Commit: `678980521` in `P8-msi.patch`
|
|
||||||
- Implements: `Handle::IrqAffinity { irq, mask }` variant, path routing for `<irq>/affinity` and `cpu-XX/<irq>/affinity`, kwrite validates CPU id and stores mask atomically, kfstat/kfpath/kreadoff/close all handle new variant
|
|
||||||
|
|
||||||
## 3. Phase 2: DMA/IOMMU Integration (Week 3-5) — AUDITED 2026-05-04
|
|
||||||
|
|
||||||
**Status**: IOMMU daemon (1003 lines) and DmaBuffer (261 lines) already exist and are solid. Tasks re-scoped from "create" to "wire."
|
|
||||||
|
|
||||||
### T2.1: IommuDmaAllocator (driver-sys) ⏳ P0
|
|
||||||
- File: `local/recipes/drivers/redox-driver-sys/source/src/dma.rs`
|
|
||||||
- Add `IommuDmaAllocator` struct: holds IOMMU domain fd, wraps `DmaBuffer::allocate()` with IOMMU MAP opcode
|
|
||||||
- Uses `scheme:iommu/domain/N` write with MAP request → get IOVA
|
|
||||||
- Linux ref: `include/linux/dma-mapping.h` — `dma_alloc_coherent()` → `iommu_dma_alloc()`
|
|
||||||
|
|
||||||
### T2.2: GPU DMA pass-through ⏳ P0
|
|
||||||
- Wire `redox-drm` GPU drivers to open IOMMU device endpoint and use IommuDmaAllocator
|
|
||||||
- amdgpu: VRAM/GTT allocations through IOMMU domain
|
|
||||||
- Intel i915: GTT pages through IOMMU domain
|
|
||||||
- Files: `local/recipes/gpu/redox-drm/source/`, `local/recipes/gpu/amdgpu/source/`
|
|
||||||
|
|
||||||
### T2.3: Streaming DMA (linux-kpi) ⏳ P1
|
|
||||||
- `dma_map_single()`: allocate bounce buffer, copy data, map through IOMMU
|
|
||||||
- `dma_unmap_single()`: copy back, unmap, free bounce buffer
|
|
||||||
- Linux ref: `kernel/dma/mapping.c` — streaming API
|
|
||||||
- File: `local/recipes/drivers/linux-kpi/source/`
|
|
||||||
|
|
||||||
### T2.4: NVMe DMA pass-through ⏳ P1
|
|
||||||
- Wire `ahcid`/`nvmed` PRP list physical addresses through IOMMU domain
|
|
||||||
- Linux ref: `drivers/nvme/host/pci.c` — `nvme_map_data()`
|
|
||||||
|
|
||||||
### T2.5: SWIOTLB Fallback (low priority) ⏳ P2
|
|
||||||
- Linux ref: `kernel/dma/swiotlb.c`
|
|
||||||
- Bounce buffer for devices with <4GB DMA addressing
|
|
||||||
- Only needed for ancient hardware; x86_64 modern hardware doesn't need it
|
|
||||||
|
|
||||||
## 4. Phase 3: Scheduler Improvements (Week 4-6) — MOSTLY DONE
|
|
||||||
|
|
||||||
### T3.1: LAPIC Timer as Primary Tick ✅ DONE
|
|
||||||
- P7-scheduler-improvements.patch: LAPIC timer calibrated + enabled at vector 48
|
|
||||||
- TSC-deadline mode, 1000Hz tick drives DWRR scheduler directly
|
|
||||||
- PIT fallback retained
|
|
||||||
|
|
||||||
### T3.2: Per-CPU Scheduler Locks ✅ DONE
|
|
||||||
- Work-stealing load balancer in switch.rs
|
|
||||||
- Per-CPU nr_running counter
|
|
||||||
- Idle CPUs steal work via IPI
|
|
||||||
|
|
||||||
### T3.3: Load Balancing ✅ DONE
|
|
||||||
- RT scheduling class (priority 0-9, skip DWRR, immediate dispatch)
|
|
||||||
- Threshold reduced: 3→1 ticks for LAPIC-driven mode
|
|
||||||
- Geometric weights in DWRR
|
|
||||||
|
|
||||||
### T3.4: RT Scheduling Class ✅ DONE
|
|
||||||
|
|
||||||
### T3.5: NUMA-Aware Scheduling ❌
|
|
||||||
- Not implemented — low priority for desktop/non-NUMA systems
|
|
||||||
- Linux ref: kernel/sched/rt.c
|
|
||||||
- FIFO and Round-Robin classes
|
|
||||||
- Priority inheritance
|
|
||||||
- RT throttling: 95% CPU cap/sec
|
|
||||||
|
|
||||||
### T3.5: TSC-Deadline Timer
|
|
||||||
- Use IA32_TSC_DEADLINE MSR for precise tick
|
|
||||||
- True tickless operation
|
|
||||||
- TSC calibration via HPET or PIT
|
|
||||||
|
|
||||||
## 5. Phase 4: Thread Creation (Week 6-7)
|
|
||||||
|
|
||||||
### T4.1: Batched Thread Creation
|
|
||||||
- Batch new-thread requests (reduce IPC)
|
|
||||||
- Pre-allocate stack pages during fork
|
|
||||||
|
|
||||||
### T4.2: Kernel Thread Pool
|
|
||||||
- Pre-create idle kernel threads
|
|
||||||
- Reuse via object pool
|
|
||||||
|
|
||||||
### T4.3: Shared Memory IPC
|
|
||||||
- Use shm for proc scheme bulk ops
|
|
||||||
- Avoid data copy through IPC channel
|
|
||||||
|
|
||||||
## 6. Dependencies
|
|
||||||
|
|
||||||
Phase 1 (MSI): T1.1 -> T1.2 -> T1.3 -> T1.4 -> T1.5
|
|
||||||
Phase 2 (DMA): T2.1 -> T2.2 -> T2.3 -> T2.4 -> T2.5
|
|
||||||
Phase 3 (Sched): T3.1 -> T3.5 -> T3.2 -> T3.3 -> T3.4
|
|
||||||
Phase 4 (Thread): T4.1 -> T4.2 -> T4.3
|
|
||||||
|
|
||||||
Phase 1+2 independent (parallel). Phase 2.4 needs Phase 1.3.
|
|
||||||
Phase 3.1 partially done (start immediately).
|
|
||||||
|
|
||||||
## 7. Timeline
|
|
||||||
|
|
||||||
| Phase | Duration | Cumulative |
|
|
||||||
|-------|----------|------------|
|
|
||||||
| Phase 1 (MSI) | 3 weeks | Week 3 |
|
|
||||||
| Phase 2 (DMA/IOMMU) | 3 weeks | Week 5 |
|
|
||||||
| Phase 3 (Scheduler) | 3 weeks | Week 7 |
|
|
||||||
| Phase 4 (Threads) | 2 weeks | Week 7 |
|
|
||||||
|
|
||||||
Total: 7 weeks (2 devs parallel Phase 1+2)
|
|
||||||
|
|
||||||
## 8. Success Metrics
|
|
||||||
|
|
||||||
| Metric | Before | After |
|
|
||||||
|--------|--------|-------|
|
|
||||||
| Scheduler tick | 148Hz (PIT) | 1000Hz (LAPIC) |
|
|
||||||
| NVMe throughput | INTx shared | MSI-X 4+ queues |
|
|
||||||
| Context switch | ~6.75ms | ~1ms |
|
|
||||||
| Thread create | 3 IPC hops | 2 IPC hops |
|
|
||||||
| DMA safety | Unprotected | IOMMU-mapped |
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Red Bear OS — Hardware Validation Matrix
|
||||||
|
|
||||||
|
**Version**: 1.0 (2026-05-20)
|
||||||
|
**Target**: 4 hardware classes minimum
|
||||||
|
**Evidence model**: Source-visible → Build-visible → QEMU-validated → Runtime-validated → Hardware-validated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Validation Targets
|
||||||
|
|
||||||
|
| Class | CPU | Platform | GPU | Priority |
|
||||||
|
|-------|-----|----------|-----|----------|
|
||||||
|
| A1 | AMD Desktop | Ryzen 5000/7000 | Discrete AMD/Intel | P0 |
|
||||||
|
| A2 | Intel Desktop | Core 12th-14th Gen | Integrated Intel | P0 |
|
||||||
|
| A3 | AMD Laptop | Ryzen Mobile 5000/7000 | Integrated AMD | P1 |
|
||||||
|
| A4 | Intel Laptop | Core Mobile 12th-14th Gen | Integrated Intel | P1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Per-Target Checklist
|
||||||
|
|
||||||
|
### Boot & Runtime
|
||||||
|
|
||||||
|
| Check | Validation Method | Pass Criteria |
|
||||||
|
|-------|------------------|---------------|
|
||||||
|
| Boots to login prompt | Bare metal boot | Login prompt visible within 60s |
|
||||||
|
| All CPU cores online | `dmesg` / `sys:cpu` scheme | `SMP: N CPUs online` matches physical core count |
|
||||||
|
| USB keyboard at boot | Physical test | Keyboard works in bootloader and login |
|
||||||
|
| USB storage mounts | `mount` / `df` | Mass storage device appears and mounts |
|
||||||
|
| Wired network DHCP | `ifconfig` / `dhcpd` logs | Obtains IPv4 lease within 10s |
|
||||||
|
| Temperature readable | `redbear-info` / `coretemp` scheme | Per-core temps displayed |
|
||||||
|
| Clean shutdown | `shutdown -h now` | Powers off without panic |
|
||||||
|
| Clean reboot | `reboot` | Restarts successfully |
|
||||||
|
|
||||||
|
### Subsystem Validation
|
||||||
|
|
||||||
|
| Subsystem | Check | Evidence |
|
||||||
|
|-----------|-------|----------|
|
||||||
|
| ACPI | Thermal zones readable | `/scheme/acpi/thermal/` has entries |
|
||||||
|
| ACPI | Fan status readable | `/scheme/acpi/fan/` has entries (if fans present) |
|
||||||
|
| C-states | Idle power reduction | Temperature drops 5-10C at idle vs load |
|
||||||
|
| MSI-X | Network IRQ type | `dmesg` shows "MSI-X interrupt on CPU N" |
|
||||||
|
| IOMMU | AMD-Vi initialized | `iommu` daemon logs "AMD-Vi unit N initialized" |
|
||||||
|
| Logging | Per-service logs | `/var/log/*.log` files exist and rotate |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Negative-Result Capture
|
||||||
|
|
||||||
|
When a target fails, record:
|
||||||
|
|
||||||
|
```
|
||||||
|
Target: <class>
|
||||||
|
Component: <subsystem>
|
||||||
|
Failure: <description>
|
||||||
|
Evidence: <log excerpt or dmesg>
|
||||||
|
Bisect: <last known good commit / config>
|
||||||
|
Workaround: <if any>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Current Status
|
||||||
|
|
||||||
|
| Target | Status | Last Tested | Blocker |
|
||||||
|
|--------|--------|-------------|---------|
|
||||||
|
| A1 | Not tested | — | No hardware |
|
||||||
|
| A2 | Not tested | — | No hardware |
|
||||||
|
| A3 | Not tested | — | No hardware |
|
||||||
|
| A4 | Not tested | — | No hardware |
|
||||||
|
|
||||||
|
**QEMU baseline**: All checklist items pass in QEMU except temperature (no MSR emulation), IOMMU (QEMU proof only), and USB storage (validated with host-seeded patterns).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Test Procedures
|
||||||
|
|
||||||
|
### Quick Validation (15 minutes)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On target hardware, boot from live ISO
|
||||||
|
make live CONFIG_NAME=redbear-full
|
||||||
|
# Write ISO to USB, boot
|
||||||
|
|
||||||
|
# Check CPU cores
|
||||||
|
cat /scheme/sys/cpu
|
||||||
|
|
||||||
|
# Check temperatures
|
||||||
|
cat /scheme/coretemp/cpu0/temperature
|
||||||
|
|
||||||
|
# Check ACPI thermal zones
|
||||||
|
ls /scheme/acpi/thermal/
|
||||||
|
|
||||||
|
# Check network
|
||||||
|
ping 8.8.8.8
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
ls /var/log/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full Validation (1 hour)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all quick checks
|
||||||
|
# Test USB hotplug (keyboard, storage)
|
||||||
|
# Test shutdown/reboot cycle
|
||||||
|
# Capture dmesg to external storage
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This matrix is updated as validation evidence is collected. See `COMPREHENSIVE-SYSTEM-ASSESSMENT-AND-IMPROVEMENT-PLAN.md` for the full improvement plan.*
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
# Red Bear OS — Master Implementation Plan
|
|
||||||
|
|
||||||
**Date**: 2026-05-04
|
|
||||||
**Status**: Authoritative — supersedes CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md, COMPREHENSIVE-DRIVER-AUDIT-2026-05-04.md, and HARDWARE-VALIDATION-MATRIX.md
|
|
||||||
**Source of truth**: Linux kernel 7.0 (`local/reference/linux-7.0/`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Authority & Scope
|
|
||||||
|
|
||||||
### 1.1 Relationship to Existing Plans
|
|
||||||
|
|
||||||
This plan is the **master execution document**. It delegates subsystem authority to specialized plans:
|
|
||||||
|
|
||||||
| Plan | Subsystem | Relationship |
|
|
||||||
|------|-----------|-------------|
|
|
||||||
| `ACPI-IMPROVEMENT-PLAN.md` | ACPI sleep, thermal, EC, power | **Authoritative** for ACPI |
|
|
||||||
| `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` | PCI IRQ, MSI-X, IOMMU, controllers | **Authoritative** for IRQ/PCI |
|
|
||||||
| `USB-IMPLEMENTATION-PLAN.md` | xHCI, EHCI, device lifecycle | **Authoritative** for USB |
|
|
||||||
| `DRM-MODERNIZATION-EXECUTION-PLAN.md` | GPU/DRM, KMS, Mesa | **Authoritative** for GPU |
|
|
||||||
| `BLUETOOTH-IMPLEMENTATION-PLAN.md` | BT host/controller | **Authoritative** for BT |
|
|
||||||
| `WIFI-IMPLEMENTATION-PLAN.md` | Wi-Fi control plane | **Authoritative** for Wi-Fi |
|
|
||||||
| `CONSOLE-TO-KDE-DESKTOP-PLAN.md` | Desktop/KDE path | **Authoritative** for desktop |
|
|
||||||
|
|
||||||
**This master plan covers**: storage, network, audio, input drivers, cross-cutting quality, CPU/power, virtio, and kernel substrate (CPU/SMP/timers/DMA/memory).
|
|
||||||
|
|
||||||
### 1.2 Validation Levels
|
|
||||||
|
|
||||||
- **builds** — compiles without error
|
|
||||||
- **enumerates** — discovers hardware via scheme interfaces
|
|
||||||
- **usable** — works in bounded scenario (QEMU or bare metal)
|
|
||||||
- **validated** — passes explicit acceptance tests with evidence
|
|
||||||
- **hardware-validated** — proven on real bare metal
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Phase 0: Cross-Cutting Driver Quality (Week 1-2) ⏳ IMPLEMENTED
|
|
||||||
|
|
||||||
### T0.1: Driver Error Handling ✅
|
|
||||||
|
|
||||||
**Status**: DONE. All 5 critical driver main.rs files have zero `unwrap()` calls. 165-line durable patch at `local/patches/base/P6-driver-main-fixes.patch`.
|
|
||||||
|
|
||||||
**Files**: ahcid, e1000d, rtl8168d, ihdad, ac97d main.rs
|
|
||||||
|
|
||||||
### T0.2: Driver Logging
|
|
||||||
|
|
||||||
Not started. Drivers use inconsistent logging.
|
|
||||||
|
|
||||||
### T0.3: Driver Lifecycle Documentation
|
|
||||||
|
|
||||||
Not started.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Phase 1: Storage Drivers (Week 2-6) ⏳ STRUCTURE EXISTING
|
|
||||||
|
|
||||||
### T1.1: AHCI NCQ ✅ (71 lines, wired)
|
|
||||||
|
|
||||||
**Status**: DONE. `ahci/src/ahci/ncq.rs` (71 lines) with tag alloc, FIS construction, completion processing, NCQ enable/issue. Wired via `pub mod ncq` in mod.rs.
|
|
||||||
|
|
||||||
**Linux ref**: `drivers/ata/libata-sata.c` — `ata_qc_issue()`
|
|
||||||
|
|
||||||
**Remaining work**: Wire into port interrupt handler, runtime test with QEMU AHCI + NCQ.
|
|
||||||
|
|
||||||
### T1.2: AHCI Power Management ❌
|
|
||||||
|
|
||||||
**Linux ref**: `drivers/ata/libata-eh.c:3682` — `ata_eh_handle_port_suspend()`
|
|
||||||
|
|
||||||
### T1.3: AHCI TRIM/Discard ❌
|
|
||||||
|
|
||||||
**Linux ref**: `drivers/ata/libata-scsi.c` — `ata_scsi_unmap_xlat()`
|
|
||||||
|
|
||||||
### T1.4: NVMe Multiple Queues ❌
|
|
||||||
|
|
||||||
**Linux ref**: `drivers/nvme/host/pci.c` — `nvme_reset_work()`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Phase 2: Network Drivers (Week 4-8) ⏳ STRUCTURE EXISTING
|
|
||||||
|
|
||||||
### T2.1: e1000 ITR + Checksum ✅ (33 lines, wired)
|
|
||||||
|
|
||||||
**Status**: DONE. `e1000d/src/itr.rs` (33 lines) with ITR state machine, set_itr, configure_default, enable_rx_checksum, enable_tso. Wired via `pub mod itr` in main.rs.
|
|
||||||
|
|
||||||
**Linux ref**: `e1000e/netdev.c:4200` — `e1000_configure_itr()`
|
|
||||||
|
|
||||||
### T2.2: e1000 TSO ❌
|
|
||||||
|
|
||||||
### T2.3: r8169 PHY ✅ (34 lines, wired)
|
|
||||||
|
|
||||||
**Status**: DONE. `rtl8168d/src/phy.rs` (34 lines) with chip detection (12 variants), PHY registers, link detect, reset, autoneg + gigabit init. Wired via `pub mod phy` in main.rs.
|
|
||||||
|
|
||||||
**Linux ref**: `r8169_phy_config.c` (1,354 lines)
|
|
||||||
|
|
||||||
### T2.4: Jumbo Frames ❌
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Phase 3: Audio Drivers (Week 6-10) ⏳ STRUCTURE EXISTING
|
|
||||||
|
|
||||||
### T3.1: HDA Codec Detection ✅ (STRUCTURE)
|
|
||||||
|
|
||||||
**Status**: DONE. `ihdad/src/hda/codec.rs` (18 lines) + `jack.rs` (4 lines). Both wired. 12 known codec table. Jack sense with pin config parsing.
|
|
||||||
|
|
||||||
### T3.2: HDA Jack Detection ✅ (STRUCTURE)
|
|
||||||
|
|
||||||
**Status**: `ihdad/src/hda/jack.rs` exists. Jack sense, unsolicited response.
|
|
||||||
|
|
||||||
### T3.3: HDA Stream Setup
|
|
||||||
|
|
||||||
Stream.rs exists (387 lines). NOT runtime-validated.
|
|
||||||
|
|
||||||
### T3.4: AC97 Multiple Codec ❌
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Phase 4: Input Drivers (Week 3-5) ⏳ PARTIAL
|
|
||||||
|
|
||||||
### T4.1: PS/2 Controller Reset ❌
|
|
||||||
|
|
||||||
**Linux ref**: `drivers/input/serio/i8042.c:522`
|
|
||||||
|
|
||||||
### T4.2: Touchpad Protocols ❌
|
|
||||||
|
|
||||||
**Linux ref**: `drivers/input/mouse/synaptics.c`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Phase 5: Validation (Week 1-12, parallel) ⏳ IMPLEMENTED
|
|
||||||
|
|
||||||
### T5.1: Test Harnesses ✅
|
|
||||||
|
|
||||||
`local/scripts/test-storage-qemu.sh` and `test-network-qemu.sh` exist.
|
|
||||||
|
|
||||||
### T5.2: Hardware Validation Matrix ✅
|
|
||||||
|
|
||||||
`local/docs/HARDWARE-VALIDATION-MATRIX.md` — 28 lines tracking 18 components.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Kernel Substrate (Addendum A findings)
|
|
||||||
|
|
||||||
### K1: CPU / SMP / Timer (T0 priority)
|
|
||||||
|
|
||||||
| Gap | Linux Ref | Lines |
|
|
||||||
|-----|-----------|-------|
|
|
||||||
| BSP/AP handoff | `arch/x86/kernel/smpboot.c:895` | 1,511 |
|
|
||||||
| CPU hotplug | `smpboot.c:1312` | — |
|
|
||||||
| TSC calibration | `arch/x86/kernel/tsc.c:1186` | 1,612 |
|
|
||||||
| APIC timer calibration | `arch/x86/kernel/apic/apic.c:294` | 2,694 |
|
|
||||||
| Vector allocation | `arch/x86/kernel/apic/vector.c` | 1,387 |
|
|
||||||
| MSI/MSI-X | `arch/x86/kernel/apic/msi.c` | 391 | ✅ DONE — P8-msi.patch (msi.rs, vector.rs, scheme/irq.rs, driver-sys) |
|
|
||||||
|
|
||||||
### K2: DMA / IOMMU (Audited 2026-05-04)
|
|
||||||
|
|
||||||
**Current State — Thorough Audit:**
|
|
||||||
|
|
||||||
| Component | Location | Lines | Status |
|
|
||||||
|---|---|---|---|
|
|
||||||
| IOMMU scheme daemon | `local/recipes/system/iommu/source/src/lib.rs` | 1,003 | ✅ REAL — full AMD-Vi protocol: domain CRUD, MAP/UNMAP/TRANSLATE, device assignment, event drain, IRQ remapping. Host-runnable tests pass. |
|
|
||||||
| AMD-Vi unit driver | `local/recipes/system/iommu/source/src/amd_vi.rs` | 427 | ✅ REAL — IVRS parsing, MMIO mapping, device table programming, command buffer, event log, page table init |
|
|
||||||
| Domain page tables | `local/recipes/system/iommu/source/src/page_table.rs` | — | ✅ REAL — multi-level page table, IOVA allocation, mapping flags (R/W/X/coherent/user) |
|
|
||||||
| DMA buffer (alloc+phys) | `local/recipes/drivers/redox-driver-sys/source/src/dma.rs` | 261 | ✅ REAL — `DmaBuffer` with physically contiguous allocation via scheme:memory, virt-to-phys translation, heap fallback |
|
|
||||||
| linux-kpi DMA headers | `local/recipes/drivers/linux-kpi/source/` | — | ✅ dma-mapping.h, dma-direction.h, scatterlist.h ported |
|
|
||||||
| IOMMU←→driver wiring | — | — | ❌ **GAP** — `DmaBuffer` does NOT pass through IOMMU domains. GPU/NIC/NVMe drivers allocate DMA directly, not through IOMMU-isolated domains |
|
|
||||||
| Streaming DMA | — | — | ❌ **GAP** — no `dma_map_single`/`dma_unmap_single` for bounce-buffer ops |
|
|
||||||
| SWIOTLB | — | — | ❌ **GAP** — no bounce buffer for devices with limited DMA range |
|
|
||||||
|
|
||||||
**Implementation Plan — DMA/IOMMU Integration (Week 3-5):**
|
|
||||||
|
|
||||||
| Task | Description | Lines | Priority |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **D2.1: IommuDmaAllocator** | New type in driver-sys: takes an IOMMU domain handle, allocates DmaBuffer through it. Uses `scheme:iommu/domain/N` MAP opcode. | ~150 | P0 |
|
|
||||||
| **D2.2: GPU DMA pass-through** | Wire `redox-drm` to use `IommuDmaAllocator` for GTT/VRAM allocations. Requires amdgpu/ihdgd to open IOMMU device handle. | ~80 | P0 |
|
|
||||||
| **D2.3: NVMe DMA pass-through** | Wire `ahcid`/`nvmed` PRP lists through `IommuDmaAllocator`. | ~60 | P1 |
|
|
||||||
| **D2.4: Streaming DMA** | `dma_map_single`/`dma_unmap_single` in linux-kpi. Allocates temp buffer, copies data, maps through IOMMU. | ~120 | P1 |
|
|
||||||
| **D2.5: SWIOTLB** | Bounce buffer allocation for DMA-limited devices. Linux ref: `kernel/dma/swiotlb.c`. | ~200 | P2 |
|
|
||||||
|
|
||||||
**Linux Reference Summary (from `local/reference/linux-7.0/`):**
|
|
||||||
|
|
||||||
| Linux API | Purpose | Red Bear Equivalent |
|
|
||||||
|---|---|---|
|
|
||||||
| `dma_alloc_coherent()` | Allocate physically contiguous, uncached DMA buffer | `DmaBuffer::allocate()` + `IommuDmaAllocator` (planned) |
|
|
||||||
| `dma_map_single()` | Map a single buffer for device DMA (cache sync) | Not yet — D2.4 |
|
|
||||||
| `dma_map_sg()` | Map scatter-gather list | Not yet |
|
|
||||||
| `iommu_domain_alloc()` | Create IOMMU translation domain | `IommuScheme` CREATE_DOMAIN opcode |
|
|
||||||
| `iommu_map()` | Map physical pages into domain | `IommuScheme` MAP opcode |
|
|
||||||
| `iommu_attach_device()` | Assign device to domain | `IommuScheme` ASSIGN_DEVICE opcode |
|
|
||||||
|
|
||||||
### K2b: Thread Creation / fork() (Audited 2026-05-04)
|
|
||||||
|
|
||||||
**Current State:**
|
|
||||||
|
|
||||||
| Component | Location | Lines | Status |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Kernel `context::spawn` | `recipes/core/kernel/source/src/context/mod.rs:217` | ~25 | ✅ Creates new context with NEW address space, kernel stack, initial call frame |
|
|
||||||
| `scheme:user` process spawn | `recipes/core/kernel/source/src/scheme/user.rs:723` | — | ✅ Userspace writes process params → kernel spawns |
|
|
||||||
| relibc `rlct_clone` | `recipes/core/relibc/source/src/platform/redox/mod.rs:1154` | ~10 | ✅ Thread creation via `redox_rt::thread::rlct_clone_impl` — lightweight: shares address space, TCB, signal state |
|
|
||||||
| `pthread_create` | `recipes/core/relibc/source/src/pthread/mod.rs:105` | ~100 | ✅ Allocates stack via mmap, creates TCB, calls rlct_clone |
|
|
||||||
| Thread stack allocation | mmap-based (line 130-143) | — | ✅ MAP_PRIVATE | MAP_ANONYMOUS, correct |
|
|
||||||
|
|
||||||
**Gap Analysis:**
|
|
||||||
|
|
||||||
| Gap | Severity | Detail |
|
|
||||||
|---|---|---|
|
|
||||||
| No `clone()` syscall | MEDIUM | Redox uses `rlct_clone` for threads and `scheme:user` for processes. This is architecturally correct for a microkernel — no gap. |
|
|
||||||
| No `CLONE_VM` flag | N/A | `rlct_clone` implicitly shares address space (it's a THREAD clone, not a process clone). Process creation via `scheme:user` creates new address space. Correct semantics. |
|
|
||||||
| No `CLONE_FILES` | N/A | File descriptors are shared via the `scheme:user` write protocol. Re-layout possible but functional. |
|
|
||||||
| "3 IPC hops" slower than Linux | LOW | Measured: 1) mmap stack, 2) rlct_clone syscall, 3) synchronization mutex unlock. Linux `clone()` does all three in kernel. Acceptable for a microkernel. |
|
|
||||||
| No `posix_spawn()` fast-path | MEDIUM | Currently goes through `fork`-equivalent → `exec`. Linux has `posix_spawn` via `vfork`+`exec`. Not yet in Redox. |
|
|
||||||
|
|
||||||
**Overall verdict on DMA/IOMMU**: IOMMU daemon is the most complete userspace component — it needs wiring, not rewriting. DmaBuffer exists but is IOMMU-unaware. The implementation tasks (D2.1-D2.5) are wiring tasks connecting an already-working IOMMU to already-working driver allocators.
|
|
||||||
|
|
||||||
### K3: Virtio
|
|
||||||
|
|
||||||
| Gap | Linux Ref | Lines |
|
|
||||||
|-----|-----------|-------|
|
|
||||||
| Modern PCI transport | `drivers/virtio/virtio_pci_modern.c` | 1,301 |
|
|
||||||
| Packed virtqueue | `drivers/virtio/virtio_ring.c` | 3,940 |
|
|
||||||
| Multiqueue | `drivers/net/virtio_net.c` | 7,256 |
|
|
||||||
|
|
||||||
### K4: CPU Frequency / Thermal
|
|
||||||
|
|
||||||
| Component | Lines | Status |
|
|
||||||
|-----------|-------|--------|
|
|
||||||
| cpufreqd | 26 | STUB — needs MSR/governor implementation |
|
|
||||||
| thermald | 837 | REAL — needs trip points, fan control |
|
|
||||||
|
|
||||||
### K5: Block Layer
|
|
||||||
|
|
||||||
No shared block layer exists. Each storage driver reinvents I/O dispatch. Linux: `block/blk-mq.c` (5,309 lines).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. ACPI Gaps (delegated to ACPI-IMPROVEMENT-PLAN.md)
|
|
||||||
|
|
||||||
| Linux File | Lines | Feature | Status |
|
|
||||||
|------------|-------|---------|--------|
|
|
||||||
| `drivers/acpi/sleep.c` | 1,152 | S3/S4 suspend | ❌ |
|
|
||||||
| `drivers/acpi/thermal.c` | 1,067 | Thermal zones | ❌ |
|
|
||||||
| `drivers/acpi/battery.c` | 1,331 | Battery status | ❌ |
|
|
||||||
| `drivers/acpi/ec.c` | 2,380 | EC runtime | ❌ |
|
|
||||||
| `drivers/acpi/fan.c` | ~400 | Fan control | ❌ |
|
|
||||||
| `arch/x86/kernel/acpi/sleep.c` | 202 | x86 sleep | ❌ |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Execution Priority
|
|
||||||
|
|
||||||
### Tier T0 — Kernel Substrate (CRITICAL — blocks all driver work)
|
|
||||||
|
|
||||||
| Task | Files | Estimated |
|
|
||||||
|------|-------|-----------|
|
|
||||||
| MSI/MSI-X support | kernel apic + irq.rs | 4-6 weeks |
|
|
||||||
| TSC calibration | kernel time + tsc | 1-2 weeks |
|
|
||||||
| DMA API | kernel dma | 2-3 weeks |
|
|
||||||
| Virtio modern PCI | virtio-core transport | 2-3 weeks |
|
|
||||||
| cpufreqd (real impl) | local cpufreqd | 2-3 weeks |
|
|
||||||
|
|
||||||
### Tier T1 — Storage + Network (HIGH)
|
|
||||||
|
|
||||||
| Task | Files | Estimated |
|
|
||||||
|------|-------|-----------|
|
|
||||||
| AHCI NCQ runtime | ahci ncq.rs + main.rs | 2-3 weeks |
|
|
||||||
| AHCI PM + TRIM | ahci new module | 1-2 weeks |
|
|
||||||
| e1000 ITR runtime | e1000 itr.rs + device.rs | 1-2 weeks |
|
|
||||||
| r8169 PHY runtime | r8169 phy.rs + device.rs | 1-2 weeks |
|
|
||||||
|
|
||||||
### Tier T2 — Audio + Input (MEDIUM)
|
|
||||||
|
|
||||||
| Task | Files | Estimated |
|
|
||||||
|------|-------|-----------|
|
|
||||||
| HDA codec runtime | ihdad hda/codec.rs | 2-3 weeks |
|
|
||||||
| HDA stream playback | ihdad hda/stream.rs | 2-3 weeks |
|
|
||||||
| PS/2 controller reset | ps2d controller.rs | 3-5 days |
|
|
||||||
| Touchpad protocols | ps2d mouse.rs | 1-2 weeks |
|
|
||||||
|
|
||||||
### Tier T3 — Completeness (LOW)
|
|
||||||
|
|
||||||
| Task | Files | Estimated |
|
|
||||||
|------|-------|-----------|
|
|
||||||
| NVMe multi-queue | nvmed | 2-3 weeks |
|
|
||||||
| e1000 TSO | e1000 | 1-2 weeks |
|
|
||||||
| Jumbo frames | e1000 + r8169 | 3-5 days |
|
|
||||||
| AC97 multi-codec | ac97d | 1 week |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Hardware Validation Matrix
|
|
||||||
|
|
||||||
| Component | QEMU | Bare Metal | Status |
|
|
||||||
|-----------|------|------------|--------|
|
|
||||||
| AHCI SATA | ✅ | 🔲 | NCQ structure present |
|
|
||||||
| NVMe | 🔲 | 🔲 | Basic driver |
|
|
||||||
| virtio-blk | ✅ | N/A | QEMU only |
|
|
||||||
| e1000 | 🔲 | 🔲 | ITR structure present |
|
|
||||||
| rtl8168 | 🔲 | 🔲 | PHY config present |
|
|
||||||
| virtio-net | ✅ | N/A | QEMU only |
|
|
||||||
| Intel HDA | 🔲 | 🔲 | Codec+jack added |
|
|
||||||
| AC97 | 🔲 | 🔲 | Basic driver |
|
|
||||||
| PS/2 | ✅ | 🔲 | QEMU works |
|
|
||||||
| VESA | ✅ | 🔲 | QEMU FB works |
|
|
||||||
| virtio-gpu | ✅ | N/A | 2D only |
|
|
||||||
| cpufreqd | 🔲 | 🔲 | STUB (26 lines) |
|
|
||||||
| thermald | 🔲 | 🔲 | ACPI thermal |
|
|
||||||
| x2APIC/SMP | ✅ | ✅ | Multi-core works |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. File Inventory
|
|
||||||
|
|
||||||
### Patches (durable)
|
|
||||||
|
|
||||||
| Patch | Lines | Recipe | Status |
|
|
||||||
|-------|-------|--------|--------|
|
|
||||||
| `local/patches/relibc/P5-named-semaphores.patch` | 249 | relibc | ✅ Wired |
|
|
||||||
| `local/patches/base/P6-driver-main-fixes.patch` | 165 | base | ✅ Wired |
|
|
||||||
| `local/patches/base/P6-driver-new-modules.patch` | 185 | base | ✅ Wired |
|
|
||||||
| `local/patches/base/P6-cpufreqd-real-impl.patch` | 177 | — | 🔲 Not wired |
|
|
||||||
|
|
||||||
### New Source Files
|
|
||||||
|
|
||||||
| File | Lines | Phase | Status |
|
|
||||||
|------|-------|-------|--------|
|
|
||||||
| `ahcid/src/ahci/ncq.rs` | 12 | Phase 1 | ⚠️ Truncated |
|
|
||||||
| `e1000d/src/itr.rs` | 9 | Phase 2 | ⚠️ Truncated |
|
|
||||||
| `rtl8168d/src/phy.rs` | 5 | Phase 2 | ⚠️ Truncated |
|
|
||||||
| `ihdad/src/hda/codec.rs` | 4 | Phase 3 | ⚠️ Truncated |
|
|
||||||
| `ihdad/src/hda/jack.rs` | 5 | Phase 3 | ⚠️ Truncated |
|
|
||||||
| `cpufreqd/src/main.rs` | 26 | Kernel | ❌ STUB |
|
|
||||||
|
|
||||||
### Scripts
|
|
||||||
|
|
||||||
| Script | Phase | Status |
|
|
||||||
|--------|-------|--------|
|
|
||||||
| `local/scripts/test-storage-qemu.sh` | Phase 5 | ✅ |
|
|
||||||
| `local/scripts/test-network-qemu.sh` | Phase 5 | ✅ |
|
|
||||||
| `local/scripts/lint-config-paths.sh` | Phase 0 | ✅ |
|
|
||||||
| `local/scripts/validate-init-services.sh` | Phase 0 | ✅ |
|
|
||||||
| `local/scripts/validate-file-ownership.sh` | Phase 0 | ✅ |
|
|
||||||
| `local/scripts/generate-installs-manifest.sh` | Phase 0 | ✅ |
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
| Document | Lines | Status |
|
|
||||||
|----------|-------|--------|
|
|
||||||
| `IMPLEMENTATION-MASTER-PLAN.md` | — | This file |
|
|
||||||
| `CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md` | 672 | Superseded |
|
|
||||||
| `COMPREHENSIVE-DRIVER-AUDIT-2026-05-04.md` | 316 | Superseded |
|
|
||||||
| `HARDWARE-VALIDATION-MATRIX.md` | 28 | Superseded |
|
|
||||||
| `BUILD-SYSTEM-HARDENING-PLAN.md` | 403 | Active |
|
|
||||||
| `BUILD-SYSTEM-INVARIANTS.md` | 436 | Active |
|
|
||||||
| `ACPI-IMPROVEMENT-PLAN.md` | 839 | Active |
|
|
||||||
| `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` | 916 | Active |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 14. Scheduler & Threading Assessment (2026-05-04)
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
- **Kernel**: DWRR scheduler (577 lines), 40 priority levels, per-CPU queues, futex (222 lines)
|
|
||||||
- **Userspace**: proc manager (2,638 lines), pthread (440 lines), signal delivery via proc scheme
|
|
||||||
- **IPC bridge**: 3 round-trips for thread creation vs Linux's single clone() syscall
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
- DWRR with geometric weights, CPU affinity masks, soft-blocking with monotonic timeout
|
|
||||||
- Full POSIX process model (PID/PGID/SID, job control, orphan detection)
|
|
||||||
- Futex with physical-address keys for cross-process synchronization
|
|
||||||
|
|
||||||
### Critical Gaps
|
|
||||||
1. **PIT-based tick (~148Hz)** — LAPIC timer exists but `setup_timer()` is commented out. Should use Periodic/TscDeadline mode at 1000Hz.
|
|
||||||
2. **Global CONTEXT_SWITCH_LOCK** — spinlock serializes all context switches across CPUs. Should be per-CPU.
|
|
||||||
3. **No load balancing** — idle CPUs don't steal work from busy CPUs
|
|
||||||
4. **No RT scheduling** — missing FIFO/RR/Deadline classes
|
|
||||||
5. **No cgroups** — no CPU bandwidth control or resource limits
|
|
||||||
6. **Thread creation latency** — 3 IPC hops vs single clone()
|
|
||||||
|
|
||||||
| Tier | Duration |
|
|
||||||
|------|----------|
|
|
||||||
| T0 (kernel substrate) | 10-14 weeks |
|
|
||||||
| T1 (storage + network) | 6-10 weeks |
|
|
||||||
| T2 (audio + input) | 6-10 weeks |
|
|
||||||
| T3 (completeness) | 4-8 weeks |
|
|
||||||
| **Total (2 developers, parallel)** | **16-24 weeks** |
|
|
||||||
| **Total (1 developer, sequential)** | **26-42 weeks** |
|
|
||||||
|
|||||||
@@ -0,0 +1,692 @@
|
|||||||
|
# Red Bear OS — Low-Level Infrastructure Reassessment & Updated Plan
|
||||||
|
|
||||||
|
**Version**: 1.0 (2026-05-21)
|
||||||
|
**Supersedes**: Fragmentary assessments in `COMPREHENSIVE-SYSTEM-ASSESSMENT-AND-IMPROVEMENT-PLAN.md` §2–§4 for ACPI/IRQ/PCI/driver topics
|
||||||
|
**Canonical adjacent plans** (remain authoritative for subsystem detail):
|
||||||
|
- `ACPI-IMPROVEMENT-PLAN.md` — ACPI waves W0–W7
|
||||||
|
- `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` — PCI/IRQ/MSI-X waves W1–W6
|
||||||
|
- `BOOT-PROCESS-HARDWARE-DETECTION-PLAN.md` — Boot detection waves W0–W6
|
||||||
|
- `SMP-SCHEDULER-IMPROVEMENT-PLAN.md` — SMP bottlenecks B1–B7
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive Summary
|
||||||
|
|
||||||
|
This document is a **code-grounded reassessment** of four interdependent low-level subsystems: ACPI/acpid, IRQ/PCI, enumeration/driver binding, and driver infrastructure. It is based on direct source inspection (file paths and line numbers provided throughout), cross-referenced against existing plans.
|
||||||
|
|
||||||
|
### Bottom-line verdict
|
||||||
|
|
||||||
|
| Subsystem | Verdict | Blocking Bare Metal? |
|
||||||
|
|-----------|---------|---------------------|
|
||||||
|
| **ACPI boot** | Boot-baseline complete, not release-grade | Partial — shutdown timing fragile |
|
||||||
|
| **ACPI shutdown** | S5 derivation works, timing-dependent on PCI | Yes — pre-PCI shutdown degrades weakly |
|
||||||
|
| **ACPI thermal/fan** | Discovery exists, no runtime backend | No — thermal safety gap |
|
||||||
|
| **ACPI C-states** | Discovery exists, **no kernel cpuidle** | **Yes** — root cause of heat |
|
||||||
|
| **IRQ delivery** | Architecturally strong, QEMU-proven only | Partial — no HW validation |
|
||||||
|
| **MSI/MSI-X** | Code complete, **IOMMU validation stubbed** | **Yes** — `iommu_validate_msi_irq()` returns `true` |
|
||||||
|
| **PCI enumeration** | Userspace-only (correct), pcid complete | No |
|
||||||
|
| **Driver binding** | Manual class-code matching, no ACPI _HID/_CID | Partial — limited device coverage |
|
||||||
|
| **redox-driver-sys** | Production quality, zero stubs | No |
|
||||||
|
| **linux-kpi** | Structurally complete for GPU+Wi-Fi | No |
|
||||||
|
| **GPU drivers** | Compile-only, synthetic EDID everywhere | **Yes** — no real display detection |
|
||||||
|
| **Wi-Fi** | Compile+host-test only | Yes — no HW validation |
|
||||||
|
| **USB** | xhcid only, no EHCI/UHCI/OHCI | **Yes** — legacy USB keyboards unreachable |
|
||||||
|
|
||||||
|
### What changed since last assessment (2026-05-20)
|
||||||
|
|
||||||
|
1. **Critical stub discovered**: `iommu_validate_msi_irq()` at `kernel/src/scheme/irq.rs:231` unconditionally returns `true` — this was not flagged as a blocking item in the IRQ enhancement plan (all 6 waves marked "complete").
|
||||||
|
2. **Critical stub discovered**: `aml_physmem.rs:195` and `:274` fabricate zero values on physical memory access failure — affects all AML runtime evaluation.
|
||||||
|
3. **Dual AML interpreter architecture** identified as a maintenance risk — kernel `acpi_ext` crate and userspace `acpi` crate parse DSDT/SSDT independently.
|
||||||
|
4. **APIC timer disabled** (`local_apic.rs:81`) — not flagged in any existing plan as a blocker.
|
||||||
|
5. **Synthetic EDID used in all GPU drivers** — blocks real display detection on bare metal.
|
||||||
|
6. **40 total TODOs** in ACPI code (16 kernel + 24 userspace) — higher than previously documented.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. ACPI / acpid Reassessment
|
||||||
|
|
||||||
|
### 2.1 Architecture
|
||||||
|
|
||||||
|
The ACPI subsystem has **three operational levels**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bootloader → KernelArgs.hwdesc_base (RSDP pointer)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Kernel ACPI (src/acpi/ + src/scheme/acpi.rs + src/arch/x86_shared/sleep.rs)
|
||||||
|
├── RSDP→RSDT/XSDT→SDT enumeration (MADT, SRAT, SLIT, HPET)
|
||||||
|
├── Export via /scheme/kernel.acpi/{rxsdt, kstop, sleep}
|
||||||
|
└── Kernel-side AML interpreter (acpi_ext crate) for S3/S5 sleep
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Userspace acpid (drivers/acpid/src/)
|
||||||
|
├── Reads rxsdt, loads SDTs from physical memory
|
||||||
|
├── Userspace AML interpreter (acpi crate) — SEPARATE from kernel's
|
||||||
|
├── Exports /scheme/acpi/{dmi, tables, symbols, thermal, fan, cstates}
|
||||||
|
└── Shutdown via kstop pipe + PM1a/PM1b write
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 What Is Working
|
||||||
|
|
||||||
|
| Component | File | Evidence |
|
||||||
|
|-----------|------|----------|
|
||||||
|
| RSDP discovery + dual checksum | `acpi/rsdp.rs` | ACPI 1.0 + 2.0+ validation, 62 lines |
|
||||||
|
| MADT parsing (10 entry types) | `acpi/madt/mod.rs` | Types 0x0–0xA + aarch64 GICC/GICD, 340 lines |
|
||||||
|
| x2APIC support | `acpi/madt/mod.rs` | Types 0x9/0xA, `P20–P22` patches |
|
||||||
|
| IOAPIC init from MADT | `device/ioapic.rs` | GSI resolution, source overrides, affinity, 502 lines |
|
||||||
|
| LAPIC/x2APIC | `device/local_apic.rs` | MSR + MMIO dual path, 312 lines |
|
||||||
|
| SRAT/SLIT NUMA | `acpi/srat.rs`, `acpi/slit.rs` | Affinity + distance matrix |
|
||||||
|
| HPET timer | `acpi/hpet.rs` | Init from ACPI tables |
|
||||||
|
| Kernel scheme export | `scheme/acpi.rs` | rxsdt, kstop, sleep — 398 lines |
|
||||||
|
| acpid SDT loading | `acpid/src/acpi.rs:162–217` | Page-span handling, PhysmapGuard |
|
||||||
|
| acpid FADT parsing | `acpid/src/acpi.rs:965–1122` | ACPI 2.0 extended fields |
|
||||||
|
| acpid EC handler | `acpid/src/ec.rs` | Full protocol (RD_EC/WR_EC/BE_EC/BD_EC/QR_EC), 317 lines |
|
||||||
|
| acpid S5 derivation | `acpid/src/acpi.rs:754–813` | FADT + AML \__S5, cached |
|
||||||
|
| acpid DMI | `acpid/src/dmi.rs` | SMBIOS 32/64-bit entry points, 350 lines |
|
||||||
|
| acpid thermal/fan/cstate discovery | `thermal.rs`, `fan.rs`, `cstate.rs` | AML-backed \__TZ, \__PR namespace |
|
||||||
|
| hwd ACPI backend | `hwd/backend/acpi.rs` | \__CID/\__HID device discovery, 119 lines |
|
||||||
|
|
||||||
|
### 2.3 Critical Stubs
|
||||||
|
|
||||||
|
| Location | Line | Issue | Severity |
|
||||||
|
|----------|------|-------|----------|
|
||||||
|
| `acpid/src/aml_physmem.rs` | 195 | `read_phys_or_fault()` returns `T::zero()` on failure — **fabricates data** | 🔴 CRITICAL |
|
||||||
|
| `acpid/src/aml_physmem.rs` | 274 | `map_physical_region()` falls back to **zero page** on failure — writes lost | 🔴 CRITICAL |
|
||||||
|
| `kernel/src/arch/x86_shared/sleep.rs` | 257–276 | `read_pci_u8/u16/u32` always return **0**; `write_pci_*` are no-ops | 🔴 CRITICAL |
|
||||||
|
| `kernel/src/arch/x86_shared/sleep.rs` | 275 | `nanos_since_boot()` returns **0** — broken AML timing | 🟠 HIGH |
|
||||||
|
| `kernel/src/arch/x86_shared/sleep.rs` | 294–298 | `acquire()`/`release()` for AML mutexes are **no-ops** | 🟠 HIGH |
|
||||||
|
| `acpid/src/acpi.rs` | 545 | `Dmar::init(&this)` **commented out** — "TODO (hangs on real hardware)" | 🟠 HIGH |
|
||||||
|
| `hwd/backend/legacy.rs` | 13 | `LegacyBackend::probe()` is a **TODO no-op** | 🟠 HIGH |
|
||||||
|
| `acpid/src/acpi.rs` | 820–822 | `set_global_s_state(state)` returns `Ok` for any state != 5 | 🟡 MEDIUM |
|
||||||
|
|
||||||
|
### 2.4 Architectural Risks
|
||||||
|
|
||||||
|
1. **Dual AML interpreters**: Kernel `sleep.rs` uses `acpi_ext` crate; userspace `acpid` uses `acpi` crate. They parse the same DSDT/SSDT independently with different handler implementations. Bug fixes in one do not affect the other.
|
||||||
|
2. **RSDP_ADDR contract**: acpid AML init requires `RSDP_ADDR` environment variable (from `hwd` via `KernelArgs.hwdesc_base`). x86 has BIOS fallback; non-x86 paths are unresolved.
|
||||||
|
3. **S5 derivation timing**: Depends on AML readiness which depends on PCI registration. Pre-PCI shutdown falls back gracefully but the degraded contract is weak.
|
||||||
|
4. **DMAR orphaned**: 533 lines of Intel VT-d parsing code exist but are not wired into startup.
|
||||||
|
|
||||||
|
### 2.5 TODO Inventory
|
||||||
|
|
||||||
|
- **Kernel ACPI**: 16 TODOs (`madt` arch variants, `hpet` x86 assumption, `spcr` type support, `scheme/acpi` context switch, `gtdt`)
|
||||||
|
- **Userspace acpid**: 24 TODOs (`acpi.rs`: 10, `dmar/`: 9, `main.rs`: 3, `scheme.rs`: 1, `aml_physmem.rs`: 1)
|
||||||
|
- **Total**: 40 TODOs
|
||||||
|
|
||||||
|
### 2.6 Alignment with ACPI-IMPROVEMENT-PLAN.md
|
||||||
|
|
||||||
|
| Wave | Plan Status | Code Reality | Delta |
|
||||||
|
|------|-------------|--------------|-------|
|
||||||
|
| W0 Contracts | ~80% | Truth statement accurate | — |
|
||||||
|
| W1 Startup hardening | ~60% | P19 patch removed panic-grade expects; remaining `expect()` in firmware-origin paths | Underdocumented |
|
||||||
|
| W2 AML ordering/shutdown | ~50% | S5 derivation improved (P24); explicit error types exist; timing still coupled to PCI | Underdocumented |
|
||||||
|
| W3 Honest power surface | Open | Battery/AC probing exists but not trustworthy; thermal/fan discovery real but no backend action | — |
|
||||||
|
| W4 Physmem/EC/fault handling | ~40% | **Two critical stubs at lines 195, 274 not flagged in plan** | **New finding** |
|
||||||
|
| W5 Ownership cleanup | Open | DMAR still orphaned; dual interpreters unresolved | — |
|
||||||
|
| W6 Consumer integration | ~60% | kstop→sessiond path works | — |
|
||||||
|
| W7 Validation closure | Open | No bare-metal validation matrix executed | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. IRQ / PCI Reassessment
|
||||||
|
|
||||||
|
### 3.1 Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
PCI Device → MSI/MSI-X message (address 0xFEE0_0xxx + data)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
APIC (local or I/O) → Vector delivery to target CPU
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Kernel IDT → generic_irq handler (vec 32–255)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
scheme/irq.rs → irq_trigger(irq, token)
|
||||||
|
├── iommu_validate_msi_irq(irq) ← STUB: returns true unconditionally
|
||||||
|
├── increment COUNTS[irq]
|
||||||
|
├── walk HANDLES for matching fd
|
||||||
|
└── trigger EVENT_READ
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Userspace driver → IrqHandle::wait() returns with count
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 What Is Working
|
||||||
|
|
||||||
|
| Component | File | Evidence |
|
||||||
|
|-----------|------|----------|
|
||||||
|
| IDT (256 entries) | `arch/x86_shared/idt.rs` | 224 generic vectors, legacy IRQ bindings, IPI handlers, 374 lines |
|
||||||
|
| 8259 PIC | `arch/x86_shared/device/pic.rs` | Master/slave init, mask, ack, ISR query, 98 lines |
|
||||||
|
| I/O APIC | `arch/x86_shared/device/ioapic.rs` | MADT-parsed, GSI resolution, affinity reprogramming, 502 lines |
|
||||||
|
| LAPIC/x2APIC | `arch/x86_shared/device/local_apic.rs` | MMIO + MSR dual path, IPI, EOI, ESR, 312 lines |
|
||||||
|
| IRQ dispatch | `arch/x86_shared/interrupt/irq.rs` | PIC/APIC switching, spurious accounting, 352 lines |
|
||||||
|
| IRQ scheme | `scheme/irq.rs` | Registration, delivery, affinity, per-CPU listing, 650 lines |
|
||||||
|
| MSI kernel code | `arch/x86_shared/device/msi.rs` | Message composition, validation, capability parsing, 183 lines |
|
||||||
|
| Vector allocator | `arch/x86_shared/device/vector.rs` | CAS bitmap for 224 vectors, 53 lines |
|
||||||
|
| redox-driver-sys IRQ | `redox-driver-sys/src/irq.rs` | MSI-X table mapping, vector allocation, affinity, 491 lines, **zero TODOs** |
|
||||||
|
| redox-driver-sys PCI | `redox-driver-sys/src/pci.rs` | Config space, BAR probing, MSI-X enable, 1446 lines, **zero TODOs** |
|
||||||
|
| pcid daemon | `drivers/pcid/src/` | Enumeration, scheme:pci, driver spawn, ~1400 lines total |
|
||||||
|
| driver-manager | `driver-manager/src/main.rs` | PciBus + AcpiBus binding, boot timeline, 553 lines |
|
||||||
|
|
||||||
|
### 3.3 Critical Stubs
|
||||||
|
|
||||||
|
| Location | Line | Issue | Severity |
|
||||||
|
|----------|------|-------|----------|
|
||||||
|
| `kernel/src/scheme/irq.rs` | 231 | `iommu_validate_msi_irq(_irq) -> bool { true }` — **zero IOMMU validation** | 🔴 CRITICAL |
|
||||||
|
| `kernel/src/arch/x86_shared/device/local_apic.rs` | 81 | `//self.setup_timer();` — **APIC timer disabled** | 🟠 HIGH |
|
||||||
|
| `kernel/src/arch/x86_shared/interrupt/irq.rs` | 307 | `println!("Local apic timer interrupt");` — debug artifact | 🟡 MEDIUM |
|
||||||
|
| `kernel/src/arch/x86_shared/device/ioapic.rs` | 329–331 | `.unwrap()` on cpuid — panic risk | 🟡 MEDIUM |
|
||||||
|
| `drivers/pcid/src/driver_interface/irq_helpers.rs` | — | "FIXME for cpu_id >255 need IOMMU IRQ remapping" | 🟠 HIGH |
|
||||||
|
| `drivers/pcid/src/driver_interface/irq_helpers.rs` | — | "FIXME allow allocating multiple interrupt vectors" | 🟠 HIGH |
|
||||||
|
|
||||||
|
### 3.4 Patch-Backed Code
|
||||||
|
|
||||||
|
The following kernel code does **not exist in upstream** — it is entirely Red Bear patches:
|
||||||
|
|
||||||
|
- `msi.rs` (+183 lines) — added by `P8-msi.patch` (281 lines, 12 hunks)
|
||||||
|
- `vector.rs` (+53 lines) — added by `P8-msi.patch`
|
||||||
|
- IOAPIC affinity — `P9-ioapic-irq-affinity.patch`
|
||||||
|
- IRQ affinity wiring — `P10-irq-affinity-wiring.patch`
|
||||||
|
- x2APIC ICR fix — `P20-x2apic-icr-mode-fix.patch`
|
||||||
|
- x2APIC SMP fix — `P21-x2apic-smp-fix.patch`
|
||||||
|
- x2APIC MADT fallback — `P22-x2apic-madt-fallback.patch`
|
||||||
|
|
||||||
|
**Risk**: If upstream kernel rebases, these patches must be rebased. The MSI/MSI-X subsystem is entirely patch-dependent.
|
||||||
|
|
||||||
|
### 3.5 Alignment with IRQ Enhancement Plan
|
||||||
|
|
||||||
|
The plan reports all 6 Waves as **✅ Complete**. Code inspection confirms the Waves addressed panic hardening and code quality. However, **6 priority areas remain entirely open** and the plan does not flag:
|
||||||
|
|
||||||
|
- `iommu_validate_msi_irq()` stub (CRITICAL — not mentioned)
|
||||||
|
- APIC timer disabled (not mentioned)
|
||||||
|
- Single-vector-per-device limit (mentioned as FIXME but not prioritized)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Enumeration / Driver Binding Reassessment
|
||||||
|
|
||||||
|
### 4.1 Current Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
pcid enumerates PCI bus → /scheme/pci/{segment}--{bus}--{device}.{function}/
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
driver-manager (or pcid-spawner legacy) reads /scheme/pci/
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
For each device: query config space (vendor, device, class, subclass)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Match against driver config (PCI class/vendor/device ID lookup)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Spawn driver daemon with PCID_CLIENT_CHANNEL env var
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Driver opens /scheme/pci/{addr}/config and /scheme/irq/{irq}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Limitations
|
||||||
|
|
||||||
|
1. **No ACPI _HID/_CID matching**: Non-PCI devices (ACPI-enumerated GPIO, I2C, etc.) are not bound through the driver-manager.
|
||||||
|
2. **No modalias generation**: Drivers are matched by simple class-code or vendor/device ID — no automatic alias generation from PCI class/subclass/prog-if.
|
||||||
|
3. **LegacyBackend is a stub**: `hwd/backend/legacy.rs:13` — "TODO: handle driver spawning from legacy backend" — any non-ACPI, non-DTB platform gets no hardware discovery.
|
||||||
|
4. **Initfs transitional**: `hwd` and `acpid` live on initfs boot path, not under stable rootfs service contract.
|
||||||
|
|
||||||
|
### 4.3 Alignment with Boot-Process-Hardware-Detection-Plan.md
|
||||||
|
|
||||||
|
| Wave | Plan Status | Code Reality |
|
||||||
|
|------|-------------|--------------|
|
||||||
|
| W0 Boot stage definitions | ✅ Done | Config-only |
|
||||||
|
| W1 ACPI bus in driver-manager | ✅ Done | `AcpiBus` exists |
|
||||||
|
| W2 Resource parser (_CRS, _PRT) | ✅ Done | Parsed |
|
||||||
|
| W2b ACPI device binding | ✅ Done | Wired |
|
||||||
|
| W2c GPIO/I2C configs | Partial | Runtime _CRS evaluation **not started** |
|
||||||
|
| W3 Service rewiring | ✅ Done | Stage targets wired |
|
||||||
|
| W4 Dead /etc/pcid.d/ removal | ✅ Done | Removed |
|
||||||
|
| W5 Deferred probing | ✅ Already had | Scheme-aware |
|
||||||
|
| W6 USB topology enumeration | **Not started** | Depends on xHCI IRQ stability |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Driver Infrastructure Reassessment
|
||||||
|
|
||||||
|
### 5.1 redox-driver-sys
|
||||||
|
|
||||||
|
**Status: ✅ Production quality, zero stubs, zero TODOs**
|
||||||
|
|
||||||
|
- **Schemes**: memory (physical mapping, cache type control), irq (registration, wait, affinity), pci (enumeration, config space, BARs, MSI-X)
|
||||||
|
- **Quirks**: 3-layer (compiled-in 11 entries + TOML runtime + DMI/SMBIOS 8 rules), 22 PCI flags, 21 USB flags
|
||||||
|
- **MSI-X**: Full `MsixTable` with validated x86 message programming, vector allocation, CPU round-robin
|
||||||
|
- **DMA**: `DmaBuffer` (phys-contiguous), `IommuDmaAllocator` (MAP/UNMAP protocol)
|
||||||
|
- **Tests**: 30+ unit tests in `pci.rs`
|
||||||
|
|
||||||
|
### 5.2 linux-kpi
|
||||||
|
|
||||||
|
**Status: ✅ Structurally complete for GPU + Wi-Fi, 119 tests passing, zero stubs**
|
||||||
|
|
||||||
|
- **17 Rust modules**, **32 C headers**
|
||||||
|
- **Full implementations**: pci (777 lines), net (809), wireless (1002), mac80211 (959), irq (228), firmware (277), drm_shim (374)
|
||||||
|
- **No `todo!()`/`unimplemented!()`** in any audited module
|
||||||
|
- **C header coverage**: pci.h, skbuff.h, interrupt.h, firmware.h, netdevice.h, ieee80211.h, nl80211.h, cfg80211.h, mac80211.h, drm*.h, atomic.h, spinlock.h, mutex.h, workqueue.h, timer.h, wait.h, list.h, slab.h, mm.h, io.h, types.h, errno.h, compiler.h, export.h, printk.h, module.h, refcount.h, jiffies.h, kernel.h, idr.h, bug.h
|
||||||
|
|
||||||
|
### 5.3 firmware-loader
|
||||||
|
|
||||||
|
**Status: ✅ Production quality**
|
||||||
|
|
||||||
|
- `scheme:firmware` daemon with `SchemeSync` impl
|
||||||
|
- MANIFEST generation (BLAKE3), `--probe`, `--request-nowait`
|
||||||
|
- Path traversal prevention, 64MB blob cap, cache with source signature validation
|
||||||
|
- AMD GPU: 17 firmware keys expected; Intel: per-generation DMC firmware
|
||||||
|
|
||||||
|
### 5.4 GPU Drivers
|
||||||
|
|
||||||
|
| Driver | Status | Key Gap |
|
||||||
|
|--------|--------|---------|
|
||||||
|
| redox-drm (AMD) | 🟡 Compiles, 616 lines | `synthetic_edid()` fallback — no real DDC/I²C |
|
||||||
|
| redox-drm (Intel) | 🟡 Compiles, 693 lines | `synthetic_edid()` fallback — no real DDC/I²C |
|
||||||
|
| redox-drm (VirtIO) | 🟡 Compiles | `synthetic_edid()` fallback |
|
||||||
|
| amdgpu (C port) | 🟡 Compiles, ~1487 lines | Hardcoded 4 connector descriptors, no real HPD |
|
||||||
|
|
||||||
|
**All three GPU drivers use `synthetic_edid()`** at `redox-drm/src/kms/connector.rs:35` — a hardcoded 128-byte EDID 1.4 block for 1920×1080@60Hz. This blocks real display detection on bare metal.
|
||||||
|
|
||||||
|
### 5.5 Wi-Fi
|
||||||
|
|
||||||
|
**Status: 🟡 Compiles + host-tested, zero hardware validation**
|
||||||
|
|
||||||
|
- `redbear-iwlwifi`: C transport layer (~2450 lines) + Rust daemon (~1550 lines)
|
||||||
|
- 8 host tests pass
|
||||||
|
- Commands time out without real firmware — by design
|
||||||
|
- No Intel Wi-Fi device ever exercised
|
||||||
|
|
||||||
|
### 5.6 USB
|
||||||
|
|
||||||
|
**Status: 🟡 xhcid builds + QEMU proofs pass, bare-metal incomplete**
|
||||||
|
|
||||||
|
- xhcid: Red Bear patched, QEMU IRQ delivery proven
|
||||||
|
- usbscsid: USB mass storage with inline quirks (214 storage quirks)
|
||||||
|
- usbhubd: Hub port management
|
||||||
|
- **Gap**: No EHCI, UHCI, or OHCI drivers — legacy USB keyboards on companion controllers are unreachable on bare metal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Cross-Cutting Critical Gaps (Updated Priority)
|
||||||
|
|
||||||
|
### Gap 1 — IOMMU MSI Validation (CRITICAL)
|
||||||
|
**File**: `kernel/src/scheme/irq.rs:231`
|
||||||
|
```rust
|
||||||
|
fn iommu_validate_msi_irq(_irq: u8) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Every MSI/MSI-X interrupt bypasses IOMMU remapping validation. This is a security and correctness gap. The hook exists but has zero logic.
|
||||||
|
|
||||||
|
**Root cause**: IOMMU daemon (`iommu`) provides AMD-Vi runtime but no Intel VT-d. The validation function needs remapping table data from the IOMMU daemon, or validation must move to userspace via a scheme call.
|
||||||
|
|
||||||
|
**Action**: Implement real validation against IOMMU remapping tables, or explicitly document that MSI/MSI-X without IOMMU is only safe on trusted buses.
|
||||||
|
|
||||||
|
### Gap 2 — AML Physical Memory Stubs (CRITICAL)
|
||||||
|
**Files**: `acpid/src/aml_physmem.rs:195`, `:274`
|
||||||
|
- `read_phys_or_fault()` returns `T::zero()` on failure — fabricates data
|
||||||
|
- `map_physical_region()` falls back to zero page — silent data loss
|
||||||
|
|
||||||
|
**Impact**: Any AML method accessing a physical memory region that fails to map will see fabricated zeroes. This can cause:
|
||||||
|
- Incorrect battery/thermal readings
|
||||||
|
- Silent EC communication failures
|
||||||
|
- Wrong power state transitions
|
||||||
|
|
||||||
|
**Action**: Propagate `Result<T>` errors to AML evaluation callers instead of fabricating values.
|
||||||
|
|
||||||
|
### Gap 3 — Kernel Sleep Path PCI Stubs (CRITICAL)
|
||||||
|
**File**: `kernel/src/arch/x86_shared/sleep.rs:257–276`
|
||||||
|
- `read_pci_u8/u16/u32` always return 0
|
||||||
|
- `write_pci_*` are no-ops
|
||||||
|
|
||||||
|
**Impact**: Any AML code using PCI config space access in the kernel S3/S5 sleep path gets fabricated values. This is only safe if the sleep path guarantees no PCI-dependent AML methods are evaluated.
|
||||||
|
|
||||||
|
**Action**: Either wire real PCI config space access in the kernel sleep path, or explicitly scope the kernel AML interpreter to exclude PCI-dependent methods.
|
||||||
|
|
||||||
|
### Gap 4 — APIC Timer Disabled (HIGH)
|
||||||
|
**File**: `kernel/src/arch/x86_shared/device/local_apic.rs:81`
|
||||||
|
- `setup_timer()` commented out
|
||||||
|
- System uses PIT fallback for all timer interrupts
|
||||||
|
|
||||||
|
**Impact**: No per-CPU timer interrupts (all CPUs share PIT on BSP), no TSC deadline mode for modern CPUs, potential timer skew on SMP.
|
||||||
|
|
||||||
|
**Action**: Re-enable APIC timer with calibration against PIT or TSC. Required for per-CPU timer distribution.
|
||||||
|
|
||||||
|
### Gap 5 — Synthetic EDID in All GPU Drivers (HIGH)
|
||||||
|
**File**: `redox-drm/src/kms/connector.rs:35`
|
||||||
|
- All three drivers (AMD, Intel, VirtIO) use hardcoded EDID
|
||||||
|
- No real DDC/I²C display detection
|
||||||
|
|
||||||
|
**Impact**: Display will not work on bare metal with non-1080p panels, multi-monitor setups, or displays with non-standard timings.
|
||||||
|
|
||||||
|
**Action**: Implement I²C-over-DDC EDID retrieval in `redox-drm`, or at minimum implement a real connector detection path that queries HPD + DDC before falling back to synthetic.
|
||||||
|
|
||||||
|
### Gap 6 — Dual AML Interpreters (HIGH)
|
||||||
|
**Files**: `kernel/src/arch/x86_shared/sleep.rs` (acpi_ext crate) + `acpid/src/acpi.rs` (acpi crate)
|
||||||
|
- Two independent parsers for the same DSDT/SSDT
|
||||||
|
- Different handler implementations (kernel has PCI stubs, userspace has physmem stubs)
|
||||||
|
- Bug fixes in one do not affect the other
|
||||||
|
|
||||||
|
**Impact**: Maintenance risk, correctness divergence, two surfaces for AML security issues.
|
||||||
|
|
||||||
|
**Action**: Converge on a single canonical interpreter. Recommendation: userspace (acpid) since all drivers are userspace per project model. Kernel sleep path should delegate to userspace or use a shared, read-only AML namespace.
|
||||||
|
|
||||||
|
### Gap 7 — No EHCI/UHCI/OHCI Drivers (HIGH)
|
||||||
|
**Impact**: Legacy USB keyboards on companion controller paths unreachable on bare metal. Only xHCI-native USB devices work.
|
||||||
|
|
||||||
|
**Action**: Implement EHCI driver (highest priority — covers most USB 2.0 controllers with xHCI companion). UHCI/OHCI are lower priority (very old hardware).
|
||||||
|
|
||||||
|
### Gap 8 — No C-State Kernel Backend (HIGH)
|
||||||
|
**Impact**: CPUs run at full frequency constantly on bare metal. Thermal throttling only.
|
||||||
|
|
||||||
|
**Action**: Implement `cpuidle`/`cpufreq` kernel backend using MWAIT or HLT. Discovery exists in acpid (`cstate.rs`) but kernel has no idle driver.
|
||||||
|
|
||||||
|
### Gap 9 — DMAR Orphaned (MEDIUM)
|
||||||
|
**File**: `acpid/src/acpi.rs:545`
|
||||||
|
- 533 lines of Intel VT-d parsing code
|
||||||
|
- `Dmar::init()` commented out — "hangs on real hardware"
|
||||||
|
|
||||||
|
**Action**: Either fix the hang and assign a runtime owner (iommu daemon), or remove the orphaned code until ready.
|
||||||
|
|
||||||
|
### Gap 10 — >256 CPU MSI Remapping (MEDIUM)
|
||||||
|
**File**: `drivers/pcid/src/driver_interface/irq_helpers.rs`
|
||||||
|
- 8-bit APIC destination field limits MSI target selection
|
||||||
|
- IOMMU interrupt remapping required for >256 CPUs
|
||||||
|
|
||||||
|
**Action**: Gated on IOMMU maturity (Gap 1).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Updated Execution Plan
|
||||||
|
|
||||||
|
### Phase 1: Critical Stub Removal (2–3 weeks)
|
||||||
|
**Goal**: Remove all CRITICAL-severity stubs before any hardware validation.
|
||||||
|
|
||||||
|
| # | Task | File | Effort | Owner |
|
||||||
|
|---|------|------|--------|-------|
|
||||||
|
| 1.1 | Fix `read_phys_or_fault()` zero-return | `acpid/src/aml_physmem.rs:195` | 2 days | — |
|
||||||
|
| 1.2 | Fix `map_physical_region()` zero-page fallback | `acpid/src/aml_physmem.rs:274` | 2 days | — |
|
||||||
|
| 1.3 | Fix kernel sleep path PCI read stubs | `kernel/src/arch/x86_shared/sleep.rs:257–276` | 3 days | — |
|
||||||
|
| 1.4 | Document kernel PCI stub scope | `sleep.rs` | 1 day | — |
|
||||||
|
| 1.5 | Remove `println!` debug artifact | `kernel/src/arch/x86_shared/interrupt/irq.rs:307` | 1 hour | — |
|
||||||
|
|
||||||
|
**Gate**: All CRITICAL stubs removed + `cargo check` clean on affected modules.
|
||||||
|
|
||||||
|
### Phase 2: IOMMU + MSI Validation (3–4 weeks)
|
||||||
|
**Goal**: Make MSI/MSI-X delivery trustworthy.
|
||||||
|
|
||||||
|
| # | Task | File | Effort | Owner |
|
||||||
|
|---|------|------|--------|-------|
|
||||||
|
| 2.1 | Implement `iommu_validate_msi_irq()` real logic | `kernel/src/scheme/irq.rs:231` | 1 week | — |
|
||||||
|
| 2.2 | Wire IOMMU remapping table read into kernel | `iommu` daemon ↔ `scheme/irq` | 1 week | — |
|
||||||
|
| 2.3 | QEMU validation: MSI-X with IOMMU enabled | `test-msix-qemu.sh` | 2 days | — |
|
||||||
|
| 2.4 | Fix or remove orphaned DMAR code | `acpid/src/acpi.rs:545` | 2 days | — |
|
||||||
|
|
||||||
|
**Gate**: `test-msix-qemu.sh` passes with IOMMU enabled + no `iommu_validate_msi_irq()` stub.
|
||||||
|
|
||||||
|
### Phase 3: Timer + CPU Power (2–3 weeks)
|
||||||
|
**Goal**: Enable per-CPU timers and basic CPU idle.
|
||||||
|
|
||||||
|
| # | Task | File | Effort | Owner |
|
||||||
|
|---|------|------|--------|-------|
|
||||||
|
| 3.1 | Re-enable APIC timer with calibration | `kernel/src/arch/x86_shared/device/local_apic.rs:81` | 3 days | — |
|
||||||
|
| 3.2 | Implement kernel cpuidle backend (MWAIT/HLT) | New file: `kernel/src/arch/x86_shared/cpuidle.rs` | 1 week | — |
|
||||||
|
| 3.3 | Wire acpid C-state discovery to kernel idle | `acpid/src/cstate.rs` → kernel | 3 days | — |
|
||||||
|
| 3.4 | QEMU validation: timer + idle | `test-timer-qemu.sh` | 2 days | — |
|
||||||
|
|
||||||
|
**Gate**: `test-timer-qemu.sh` passes with APIC timer + CPU idle active.
|
||||||
|
|
||||||
|
### Phase 4: Display Detection (4–6 weeks)
|
||||||
|
**Goal**: Replace synthetic EDID with real display detection.
|
||||||
|
|
||||||
|
| # | Task | File | Effort | Owner |
|
||||||
|
|---|------|------|--------|-------|
|
||||||
|
| 4.1 | Implement I²C-over-DDC EDID retrieval | `redox-drm/src/kms/ddc.rs` (new) | 2 weeks | — |
|
||||||
|
| 4.2 | Wire HPD interrupt to connector detection | `redox-drm/src/drivers/amd/mod.rs`, `intel/mod.rs` | 1 week | — |
|
||||||
|
| 4.3 | Replace `synthetic_edid()` with real → fallback | `redox-drm/src/kms/connector.rs:35` | 3 days | — |
|
||||||
|
| 4.4 | QEMU validation: EDID readback | `test-drm-display-runtime.sh` | 2 days | — |
|
||||||
|
| 4.5 | Bare-metal validation: AMD GPU display | `test-amd-gpu.sh` | 1 week | — |
|
||||||
|
| 4.6 | Bare-metal validation: Intel GPU display | `test-intel-gpu.sh` | 1 week | — |
|
||||||
|
|
||||||
|
**Gate**: Real EDID retrieved from at least one display on bare metal (AMD or Intel).
|
||||||
|
|
||||||
|
### Phase 5: USB Legacy Controllers (3–4 weeks)
|
||||||
|
**Goal**: Enable USB keyboard on non-xHCI paths.
|
||||||
|
|
||||||
|
| # | Task | File | Effort | Owner |
|
||||||
|
|---|------|------|--------|-------|
|
||||||
|
| 5.1 | Implement EHCI host controller driver | `local/recipes/drivers/ehcid/` (new) | 2 weeks | — |
|
||||||
|
| 5.2 | Wire EHCI into driver-manager PCI binding | `driver-manager/src/main.rs` | 3 days | — |
|
||||||
|
| 5.3 | QEMU validation: EHCI keyboard | `test-usb-qemu.sh` | 2 days | — |
|
||||||
|
| 5.4 | UHCI/OHCI assessment | — | 1 week | — |
|
||||||
|
|
||||||
|
**Gate**: USB keyboard works via EHCI in QEMU.
|
||||||
|
|
||||||
|
### Phase 6: AML Convergence (3–4 weeks)
|
||||||
|
**Goal**: Resolve dual AML interpreter risk.
|
||||||
|
|
||||||
|
| # | Task | File | Effort | Owner |
|
||||||
|
|---|------|------|--------|-------|
|
||||||
|
| 6.1 | Evaluate kernel sleep.rs → userspace delegation | `kernel/src/arch/x86_shared/sleep.rs` | 1 week | — |
|
||||||
|
| 6.2 | Implement kernel→userspace S3/S5 sleep RPC | `scheme/kernel.acpi/sleep` → `acpid` | 1 week | — |
|
||||||
|
| 6.3 | Remove kernel `acpi_ext` crate if delegated | `kernel/src/arch/x86_shared/sleep.rs` | 3 days | — |
|
||||||
|
| 6.4 | QEMU validation: sleep/wake cycle | `test-sleep-qemu.sh` | 2 days | — |
|
||||||
|
|
||||||
|
**Gate**: S5 shutdown works with single AML interpreter (userspace only).
|
||||||
|
|
||||||
|
### Phase 7: Hardware Validation Matrix (4–6 weeks, parallel with 4–6)
|
||||||
|
**Goal**: Evidence-based support claims.
|
||||||
|
|
||||||
|
| # | Task | Hardware | Effort |
|
||||||
|
|---|------|----------|--------|
|
||||||
|
| 7.1 | Class A1 validation (AMD desktop + discrete GPU) | Ryzen 5000/7000 + AMD GPU | 1 week |
|
||||||
|
| 7.2 | Class A2 validation (Intel desktop + iGPU) | Core 12th–14th Gen | 1 week |
|
||||||
|
| 7.3 | Class A3 validation (AMD laptop) | Ryzen Mobile | 1 week |
|
||||||
|
| 7.4 | Class A4 validation (Intel laptop) | Core Mobile | 1 week |
|
||||||
|
| 7.5 | Regression test suite on all 4 classes | All | 2 weeks |
|
||||||
|
|
||||||
|
**Gate**: All 4 hardware classes pass boot, shutdown, USB keyboard, and display detection.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Timeline Synthesis
|
||||||
|
|
||||||
|
```
|
||||||
|
Week 1–3: Phase 1 — Critical stub removal
|
||||||
|
Week 4–7: Phase 2 — IOMMU + MSI validation
|
||||||
|
Week 7–9: Phase 3 — Timer + CPU power (parallel with Phase 2 week 7)
|
||||||
|
Week 10–15: Phase 4 — Display detection (parallel with Phase 5)
|
||||||
|
Week 10–13: Phase 5 — USB legacy controllers (parallel with Phase 4)
|
||||||
|
Week 14–17: Phase 6 — AML convergence
|
||||||
|
Week 14–19: Phase 7 — Hardware validation matrix (parallel with Phase 6)
|
||||||
|
|
||||||
|
Total: 19 weeks (≈4.5 months) with 2 developers
|
||||||
|
```
|
||||||
|
|
||||||
|
### What the existing plans said vs this plan
|
||||||
|
|
||||||
|
| Plan | Claimed Timeline | Reality |
|
||||||
|
|------|-----------------|---------|
|
||||||
|
| COMPREHENSIVE P1 (bare-metal hardening) | 6–8 weeks | Understated — no critical stub removal phase |
|
||||||
|
| COMPREHENSIVE P2 (USB) | 4–6 weeks | Realistic for EHCI only |
|
||||||
|
| COMPREHENSIVE P3 (IRQ/IOMMU) | 4–6 weeks | Realistic if focused on Gap 1 only |
|
||||||
|
| IRQ plan Waves 1–6 | "Complete" | Code quality complete, validation not started |
|
||||||
|
| ACPI plan Waves 0–7 | W0–W4 partial, W5–W7 open | Accurate, but two critical stubs not flagged |
|
||||||
|
| SMP plan bottlenecks | 11–18 days | Realistic for B1–B2 only |
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 (stub removal)
|
||||||
|
│
|
||||||
|
├── required by ──► Phase 2 (IOMMU validation)
|
||||||
|
│
|
||||||
|
├── required by ──► Phase 3 (timer + idle)
|
||||||
|
│
|
||||||
|
└── required by ──► Phase 4 (display detection)
|
||||||
|
|
||||||
|
Phase 2 (IOMMU)
|
||||||
|
└── required by ──► Phase 7 (hardware validation — safe MSI)
|
||||||
|
|
||||||
|
Phase 3 (timer + idle)
|
||||||
|
└── required by ──► Phase 7 (hardware validation — no overheating)
|
||||||
|
|
||||||
|
Phase 4 (display)
|
||||||
|
└── required by ──► Phase 7 (hardware validation — working console)
|
||||||
|
|
||||||
|
Phase 5 (USB EHCI)
|
||||||
|
└── required by ──► Phase 7 (hardware validation — keyboard input)
|
||||||
|
|
||||||
|
Phase 6 (AML convergence)
|
||||||
|
└── not blocking ──► Phase 7 (can validate with dual interpreters)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Risk Register
|
||||||
|
|
||||||
|
| # | Risk | Likelihood | Impact | Mitigation |
|
||||||
|
|---|------|-----------|--------|------------|
|
||||||
|
| R1 | `aml_physmem` stub fix reveals deeper AML memory access issues | Medium | High | Fix with comprehensive error propagation; add fallback to kernel scheme for problematic regions |
|
||||||
|
| R2 | IOMMU validation implementation requires kernel ABI change | Medium | High | Prototype in userspace first via `scheme:iommu` call; only promote to kernel if performance requires it |
|
||||||
|
| R3 | APIC timer calibration fails on specific CPU models | Medium | Medium | Keep PIT fallback path; detect calibration failure and degrade gracefully |
|
||||||
|
| R4 | DDC/I²C implementation requires GPIO/I2C subsystem not yet built | High | High | Scope Phase 4 to "query EDID via ACPI _DDC method first, then direct I²C"; fallback to synthetic still acceptable for initial bring-up |
|
||||||
|
| R5 | EHCI driver requires IRQ/MSI-X fixes first | Medium | Medium | Phase 5 starts after Phase 2 gate; use legacy IRQ for EHCI if MSI-X not ready |
|
||||||
|
| R6 | AML convergence breaks S3 sleep path | Medium | High | Keep kernel sleep.rs as fallback during transition; remove only after S3 validated via userspace path |
|
||||||
|
| R7 | No bare-metal hardware available for validation | Medium | Critical | Prioritize QEMU proofs for all phases; document "QEMU-validated" vs "bare-metal-validated" per subsystem |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Verification Gates
|
||||||
|
|
||||||
|
### Gate A: Boot-Baseline Ready (end of Phase 1)
|
||||||
|
- [ ] `aml_physmem.rs:195` returns `Result<T>` instead of `T::zero()`
|
||||||
|
- [ ] `aml_physmem.rs:274` propagates mapping errors instead of zero-page fallback
|
||||||
|
- [ ] `sleep.rs:257–276` either wired to real PCI or explicitly scoped out
|
||||||
|
- [ ] `cargo check` clean on `acpid`, `kernel`, `redox-drm`
|
||||||
|
- [ ] `repo validate-patches kernel` passes
|
||||||
|
- [ ] `repo validate-patches base` passes
|
||||||
|
|
||||||
|
### Gate B: IRQ/IOMMU Trustworthy (end of Phase 2)
|
||||||
|
- [ ] `iommu_validate_msi_irq()` performs real validation
|
||||||
|
- [ ] `test-msix-qemu.sh` passes with IOMMU enabled
|
||||||
|
- [ ] `test-iommu-qemu.sh` passes
|
||||||
|
- [ ] No unconditional `true` returns in IRQ validation path
|
||||||
|
|
||||||
|
### Gate C: Timer + Power (end of Phase 3)
|
||||||
|
- [ ] APIC timer fires and calibrates correctly in QEMU
|
||||||
|
- [ ] CPU idle backend enters C1/C2 via MWAIT or HLT
|
||||||
|
- [ ] `test-timer-qemu.sh` passes
|
||||||
|
- [ ] No PIT-only fallback in boot log
|
||||||
|
|
||||||
|
### Gate D: Display Detection (end of Phase 4)
|
||||||
|
- [ ] `synthetic_edid()` is fallback, not primary
|
||||||
|
- [ ] Real EDID retrieved from at least one display in QEMU
|
||||||
|
- [ ] `test-drm-display-runtime.sh` passes
|
||||||
|
|
||||||
|
### Gate E: USB Legacy (end of Phase 5)
|
||||||
|
- [ ] EHCI driver enumerates devices in QEMU
|
||||||
|
- [ ] USB keyboard functional via EHCI in QEMU
|
||||||
|
- [ ] `test-usb-qemu.sh` passes
|
||||||
|
|
||||||
|
### Gate F: Single AML Interpreter (end of Phase 6)
|
||||||
|
- [ ] S5 shutdown works with userspace AML only
|
||||||
|
- [ ] Kernel `acpi_ext` crate removed or explicitly deprecated
|
||||||
|
- [ ] `test-sleep-qemu.sh` passes (S3 + S5)
|
||||||
|
|
||||||
|
### Gate G: Hardware Validation (end of Phase 7)
|
||||||
|
- [ ] Class A1 (AMD desktop) boots, shuts down, displays, accepts USB keyboard
|
||||||
|
- [ ] Class A2 (Intel desktop) boots, shuts down, displays, accepts USB keyboard
|
||||||
|
- [ ] Class A3 (AMD laptop) boots, shuts down, displays, accepts USB keyboard
|
||||||
|
- [ ] Class A4 (Intel laptop) boots, shuts down, displays, accepts USB keyboard
|
||||||
|
- [ ] Validation artifacts committed to `local/docs/HARDWARE-VALIDATION-MATRIX.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Appendix: Key File Reference
|
||||||
|
|
||||||
|
### ACPI
|
||||||
|
- `recipes/core/kernel/source/src/acpi/mod.rs` — Kernel ACPI orchestrator
|
||||||
|
- `recipes/core/kernel/source/src/acpi/rsdp.rs` — RSDP discovery
|
||||||
|
- `recipes/core/kernel/source/src/acpi/madt/mod.rs` — MADT parser
|
||||||
|
- `recipes/core/kernel/source/src/scheme/acpi.rs` — Kernel ACPI scheme
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/sleep.rs` — Kernel AML interpreter for sleep
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/stop.rs` — Shutdown orchestrator
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/main.rs` — acpid daemon entry
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/acpi.rs` — Core ACPI context
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/aml_physmem.rs` — AML physmem handler (stubs at :195, :274)
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/ec.rs` — Embedded Controller handler
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/thermal.rs` — Thermal zone discovery
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/fan.rs` — Fan device discovery
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/cstate.rs` — C-state discovery
|
||||||
|
- `recipes/core/base/source/drivers/acpid/src/dmi.rs` — SMBIOS DMI parser
|
||||||
|
- `recipes/core/base/source/drivers/hwd/src/backend/acpi.rs` — hwd ACPI backend
|
||||||
|
- `recipes/core/base/source/drivers/hwd/src/backend/legacy.rs` — LegacyBackend stub (:13)
|
||||||
|
|
||||||
|
### IRQ / PCI
|
||||||
|
- `recipes/core/kernel/source/src/scheme/irq.rs` — IRQ scheme (stub at :231)
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/interrupt/irq.rs` — IRQ dispatch
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/device/ioapic.rs` — I/O APIC
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/device/local_apic.rs` — LAPIC (timer disabled at :81)
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/device/msi.rs` — MSI code (patch-based)
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/device/vector.rs` — Vector allocator (patch-based)
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/device/pic.rs` — 8259 PIC
|
||||||
|
- `recipes/core/kernel/source/src/arch/x86_shared/idt.rs` — IDT setup
|
||||||
|
- `local/recipes/drivers/redox-driver-sys/source/src/irq.rs` — Userspace IRQ handling
|
||||||
|
- `local/recipes/drivers/redox-driver-sys/source/src/pci.rs` — Userspace PCI abstraction
|
||||||
|
- `recipes/core/base/source/drivers/pcid/src/main.rs` — pcid daemon
|
||||||
|
- `recipes/core/base/source/drivers/pcid/src/scheme.rs` — PciScheme
|
||||||
|
- `recipes/core/base/source/drivers/pcid/src/driver_interface/irq_helpers.rs` — IRQ helper FIXMEs
|
||||||
|
- `local/recipes/system/driver-manager/source/src/main.rs` — Driver manager
|
||||||
|
|
||||||
|
### Driver Infrastructure
|
||||||
|
- `local/recipes/drivers/redox-driver-sys/source/src/lib.rs` — Core library
|
||||||
|
- `local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs` — Quirks API
|
||||||
|
- `local/recipes/drivers/linux-kpi/source/src/lib.rs` — linux-kpi crate
|
||||||
|
- `local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs` — PCI KPI (777 lines)
|
||||||
|
- `local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs` — DRM GEM shim
|
||||||
|
- `local/recipes/drivers/linux-kpi/source/src/rust_impl/mac80211.rs` — mac80211 KPI (959 lines)
|
||||||
|
- `local/recipes/drivers/linux-kpi/source/src/rust_impl/wireless.rs` — cfg80211 KPI (1002 lines)
|
||||||
|
- `local/recipes/system/firmware-loader/source/src/main.rs` — firmware-loader daemon
|
||||||
|
- `local/recipes/gpu/redox-drm/source/src/main.rs` — DRM daemon
|
||||||
|
- `local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs` — AMD GPU driver
|
||||||
|
- `local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs` — Intel GPU driver
|
||||||
|
- `local/recipes/gpu/redox-drm/source/src/kms/connector.rs` — Connector + synthetic EDID (:35)
|
||||||
|
- `local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c` — Bounded AMD display C port
|
||||||
|
- `local/recipes/gpu/amdgpu/source/redox_glue.h` — Linux→Redox C glue
|
||||||
|
- `local/recipes/gpu/amdgpu/source/redox_stubs.c` — Kernel emulation stubs
|
||||||
|
|
||||||
|
### Patches
|
||||||
|
- `local/patches/kernel/redbear-consolidated.patch` — Consolidated mega-patch
|
||||||
|
- `local/patches/kernel/P8-msi.patch` — MSI + vector allocator
|
||||||
|
- `local/patches/kernel/P9-ioapic-irq-affinity.patch` — IRQ affinity
|
||||||
|
- `local/patches/kernel/P10-irq-affinity-wiring.patch` — Affinity wiring
|
||||||
|
- `local/patches/kernel/P20-x2apic-icr-mode-fix.patch` — x2APIC ICR
|
||||||
|
- `local/patches/kernel/P21-x2apic-smp-fix.patch` — x2APIC SMP
|
||||||
|
- `local/patches/kernel/P22-x2apic-madt-fallback.patch` — x2APIC MADT fallback
|
||||||
|
- `local/patches/kernel/P24-cstate-mwait-idle.patch` — C-state MWAIT
|
||||||
|
- `local/patches/kernel/P25-cpuidle-deep-cstates.patch` — Deep C-states
|
||||||
|
- `local/patches/base/P19-acpid-startup-hardening.patch` — acpid startup
|
||||||
|
- `local/patches/base/P24-acpi-s5-derivation-shutdown-semantics.patch` — S5 derivation
|
||||||
|
- `local/patches/base/P44-acpid-thermal-zones.patch` — Thermal zones
|
||||||
|
- `local/patches/base/P48-acpid-fan-support.patch` — Fan support
|
||||||
|
- `local/patches/base/P52-acpid-cstates.patch` — C-state discovery
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Document Authority
|
||||||
|
|
||||||
|
This document is a **cross-cutting reassessment** that references but does not replace the canonical subsystem plans:
|
||||||
|
|
||||||
|
- For ACPI wave-level execution detail, see `ACPI-IMPROVEMENT-PLAN.md`
|
||||||
|
- For IRQ/PCI wave-level execution detail, see `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md`
|
||||||
|
- For boot detection wave detail, see `BOOT-PROCESS-HARDWARE-DETECTION-PLAN.md`
|
||||||
|
- For SMP bottleneck detail, see `SMP-SCHEDULER-IMPROVEMENT-PLAN.md`
|
||||||
|
- For desktop path blockers, see `CONSOLE-TO-KDE-DESKTOP-PLAN.md`
|
||||||
|
|
||||||
|
**When this document conflicts with a canonical subsystem plan**, the **canonical plan** wins on subsystem-specific details, and this document wins on cross-cutting prioritization and inter-subsystem dependencies.
|
||||||
|
|
||||||
|
**This document should be updated** after each phase gate is reached, or when new critical stubs are discovered.
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
# SMP/Scheduler Improvement Plan
|
||||||
|
|
||||||
|
**Status**: Active
|
||||||
|
**Date**: 2026-05-16
|
||||||
|
**Authority**: Canonical execution plan for SMP hardening
|
||||||
|
**Priority order**: Bottleneck #1 → #2 → #3 → #4 → #5 → #6 → #7
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Red Bear OS kernel has functional SMP (multi-core) support with x2APIC, per-CPU run queues,
|
||||||
|
work stealing, and DWRR+vruntime scheduling. However, several design choices limit scalability
|
||||||
|
on systems with more than 2-4 cores. This plan addresses the seven identified bottlenecks
|
||||||
|
in priority order.
|
||||||
|
|
||||||
|
### Reference Sources
|
||||||
|
- Linux 7.1: `local/reference/linux-7.1/` — scheduler, TLB, IRQ affinity
|
||||||
|
- seL4: `local/reference/seL4/` — lock-free kernel structures, minimal context switch
|
||||||
|
- Zircon: online reference only (download failed due to network issues)
|
||||||
|
|
||||||
|
### Key Kernel Files
|
||||||
|
| File | Lines | Purpose |
|
||||||
|
|------|-------|---------|
|
||||||
|
| `context/switch.rs` | 577 | Scheduler, context switch, DWRR |
|
||||||
|
| `context/arch/x86_64.rs` | 395 | CONTEXT_SWITCH_LOCK, switch_to, register save/restore |
|
||||||
|
| `percpu.rs` | 205 | PercpuBlock, TLB shootdown |
|
||||||
|
| `arch/x86_shared/ipi.rs` | 53 | IPI kinds and dispatch |
|
||||||
|
| `arch/x86_shared/device/local_apic.rs` | 312 | x2APIC/xAPIC, ICR programming |
|
||||||
|
| `arch/x86_shared/device/ioapic.rs` | 476 | IOAPIC, IRQ routing, MapInfo |
|
||||||
|
| `acpi/madt/arch/x86.rs` | 354 | AP startup, SIPI, trampoline |
|
||||||
|
|
||||||
|
### Red Bear Patches (already applied)
|
||||||
|
| Patch | Lines | Purpose |
|
||||||
|
|-------|-------|---------|
|
||||||
|
| P8-percpu-sched | 123 | Per-CPU scheduler queues |
|
||||||
|
| P8-percpu-wiring | 985 | Work stealing, load balancing, vruntime, affinity |
|
||||||
|
| P8-work-stealing | 190 | Steal statistics, migration helpers |
|
||||||
|
| P8-msi | 281 | MSI/MSI-X foundation, vector allocation |
|
||||||
|
| P8-msi-foundation-v2 | 188 | MSI refinement |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bottleneck #1: Global CONTEXT_SWITCH_LOCK
|
||||||
|
|
||||||
|
**Severity**: 🔴 Critical — serializes ALL context switches across ALL CPUs
|
||||||
|
**Files**: `context/arch/x86_64.rs:19`, `context/switch.rs:123,156,171,296`
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// context/arch/x86_64.rs:19
|
||||||
|
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
// context/switch.rs:156-162
|
||||||
|
while arch::CONTEXT_SWITCH_LOCK
|
||||||
|
.compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
hint::spin_loop();
|
||||||
|
percpu.maybe_handle_tlb_shootdown();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every context switch on ANY CPU must acquire this single global lock. On an 8-core system,
|
||||||
|
7 CPUs spin-wait while one CPU performs its context switch. This is the primary SMP scalability
|
||||||
|
limiter.
|
||||||
|
|
||||||
|
### Why It Exists
|
||||||
|
|
||||||
|
The comment says: "Acquire the global lock to ensure exclusive access during context switch
|
||||||
|
and avoid issues that would be caused by the unsafe operations below."
|
||||||
|
|
||||||
|
The concern is that during `switch_to`, the CPU is in a transitional state:
|
||||||
|
1. Prev context's registers are saved
|
||||||
|
2. Next context's registers are being loaded
|
||||||
|
3. Stack pointer changes to next context's stack
|
||||||
|
4. `switch_finish_hook` runs to drop guards and release lock
|
||||||
|
|
||||||
|
During steps 1-4, another CPU switching to the same context could cause data races.
|
||||||
|
|
||||||
|
### Analysis: The Lock Is Overly Conservative
|
||||||
|
|
||||||
|
The per-context write locks (`Arc<ContextLock>`) already prevent concurrent access to the
|
||||||
|
same context. The `switch()` function:
|
||||||
|
|
||||||
|
1. Locks prev context (write) — prevents anyone else from modifying it
|
||||||
|
2. Locks next context (write) — prevents anyone else from modifying it
|
||||||
|
3. Updates running flags and CPU IDs (under both write locks)
|
||||||
|
4. Stores both guards in `switch_result` (kept alive until `switch_finish_hook`)
|
||||||
|
5. Calls `arch::switch_to` (register swap)
|
||||||
|
|
||||||
|
If CPU 0 holds write locks on contexts A and B, CPU 1 cannot lock either A or B.
|
||||||
|
The global lock adds nothing for correctness — it only serializes independent switches
|
||||||
|
involving completely different contexts.
|
||||||
|
|
||||||
|
### Fix: Per-CPU Flag in PercpuBlock
|
||||||
|
|
||||||
|
Replace the global `AtomicBool` with a per-CPU flag in `ContextSwitchPercpu`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// percpu.rs — add to ContextSwitchPercpu
|
||||||
|
pub in_context_switch: AtomicBool,
|
||||||
|
```
|
||||||
|
|
||||||
|
The per-CPU flag:
|
||||||
|
- Each CPU acquires its own flag before switching — zero cross-CPU contention
|
||||||
|
- Debug assertion catches re-entrant switches on the same CPU
|
||||||
|
- Released in `switch_finish_hook` as before
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. Add `in_context_switch: Cell<bool>` to `ContextSwitchPercpu` in `switch.rs`
|
||||||
|
2. Remove `CONTEXT_SWITCH_LOCK` from `context/arch/x86_64.rs` (and aarch64, riscv64)
|
||||||
|
3. Replace `arch::CONTEXT_SWITCH_LOCK.compare_exchange_weak(...)` with per-CPU flag check
|
||||||
|
4. Replace `arch::CONTEXT_SWITCH_LOCK.store(false, ...)` with per-CPU flag release
|
||||||
|
5. Update `switch_finish_hook` accordingly
|
||||||
|
6. Rebuild kernel, verify boot
|
||||||
|
|
||||||
|
### Risk Assessment
|
||||||
|
|
||||||
|
- **Low risk**: The per-CPU flag is structurally equivalent to the global lock for each CPU.
|
||||||
|
The global lock's only effect was preventing concurrent switches on *different* CPUs, which
|
||||||
|
is unnecessary given per-context write locks.
|
||||||
|
- **Safety net**: Keep the per-CPU flag as a debug assertion. If re-entrant switching is
|
||||||
|
detected, panic instead of corrupting state.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bottleneck #2: No Broadcast TLB Shootdown
|
||||||
|
|
||||||
|
**Severity**: 🔴 Critical — O(N) shootdown on N CPUs, each with individual IPI
|
||||||
|
**Files**: `percpu.rs:75-113`, `ipi.rs:22-38`
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// percpu.rs:106-112 — shootdown_tlb_ipi when target is None (broadcast)
|
||||||
|
for id in 0..crate::cpu_count() {
|
||||||
|
// TODO: Optimize: use global counter and percpu ack counters, send IPI using
|
||||||
|
// destination shorthand "all CPUs".
|
||||||
|
shootdown_tlb_ipi(Some(LogicalCpuId::new(id)));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Broadcast TLB shootdown is implemented as a loop, sending individual IPIs to each CPU.
|
||||||
|
Each IPI requires:
|
||||||
|
1. Set `wants_tlb_shootdown` flag on target CPU
|
||||||
|
2. Spin-wait if previous shootdown is still pending
|
||||||
|
3. Send IPI via `ipi_single()`
|
||||||
|
4. Target CPU processes IPI in interrupt handler
|
||||||
|
|
||||||
|
On a 128-core system, this means 127 individual IPI sends, each with spin-wait overhead.
|
||||||
|
|
||||||
|
### Fix: x2APIC Destination Shorthand
|
||||||
|
|
||||||
|
The Local APIC supports destination shorthands in the ICR:
|
||||||
|
- `01b` = "self" (Current)
|
||||||
|
- `10b` = "all including self" (All)
|
||||||
|
- `11b` = "all except self" (Other)
|
||||||
|
|
||||||
|
The `IpiTarget` enum already defines these values (`ipi.rs:15-19`), and the `ipi()` function
|
||||||
|
(`ipi.rs:22-38`) already supports them. We just need to use `IpiTarget::Other` for broadcast
|
||||||
|
TLB shootdowns.
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. Add `tlb_shootdown_pending: AtomicU32` ACK counter to `PercpuBlock`
|
||||||
|
2. Add global `TLB_SHOOTDOWN_GENERATION: AtomicU32` counter
|
||||||
|
3. In `shootdown_tlb_ipi(None)`:
|
||||||
|
- Increment generation counter
|
||||||
|
- Set `wants_tlb_shootdown` on all CPUs (lock-free)
|
||||||
|
- Send single IPI with `IpiTarget::Other` shorthand
|
||||||
|
4. In `maybe_handle_tlb_shootdown()`:
|
||||||
|
- Process shootdown
|
||||||
|
- Increment ACK counter
|
||||||
|
5. Add `wait_for_tlb_acknowledgments()` with timeout
|
||||||
|
6. Rebuild kernel, verify boot
|
||||||
|
|
||||||
|
### x2APIC ICR Format
|
||||||
|
|
||||||
|
For x2APIC, the ICR is a single 64-bit MSR write:
|
||||||
|
```
|
||||||
|
Bits 63:32 = Destination APIC ID (ignored for shorthands)
|
||||||
|
Bits 19:18 = Destination Shorthand (0=none, 1=self, 2=all, 3=all-except-self)
|
||||||
|
Bit 14 = Trigger Mode (0=edge, 1=level)
|
||||||
|
Bits 11:8 = Delivery Mode (0=fixed)
|
||||||
|
Bits 7:0 = Vector
|
||||||
|
```
|
||||||
|
|
||||||
|
For "all except self" broadcast with TLB vector (0x41):
|
||||||
|
```rust
|
||||||
|
let icr = (3u64 << 18) | (1 << 14) | (IpiKind::Tlb as u64);
|
||||||
|
// = 0x000C0000_00000041
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bottleneck #3: IRQ Affinity Not Wired to IOAPIC
|
||||||
|
|
||||||
|
**Severity**: 🟡 Medium — stored but never applied to hardware
|
||||||
|
**Files**: `ioapic.rs`, MSI patches `P8-msi.patch`
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
The IOAPIC `MapInfo` struct has a `dest: ApicId` field, and `DestinationMode` enum has
|
||||||
|
`Logical` variant. However:
|
||||||
|
|
||||||
|
1. **No `set_affinity()` function** — there's no way to reprogram an IOAPIC redirection
|
||||||
|
entry to change its destination APIC
|
||||||
|
2. **Legacy IRQs all route to BSP** — `init()` hardcodes `bsp_apic_id` as destination
|
||||||
|
3. **MSI patches store affinity** — `P8-msi.patch` adds `set_irq_affinity()` API but
|
||||||
|
doesn't reprogram IOAPIC hardware
|
||||||
|
|
||||||
|
### Fix: Add IOAPIC IRQ Affinity
|
||||||
|
|
||||||
|
Add a function to reprogram the IOAPIC redirection table entry:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl IoApic {
|
||||||
|
pub fn set_irq_affinity(&self, gsi: u32, dest: ApicId) -> bool {
|
||||||
|
let idx = (gsi - self.gsi_start) as u8;
|
||||||
|
let mut guard = self.regs.lock();
|
||||||
|
let Some(mut entry) = guard.read_ioredtbl(idx) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// Clear destination (bits 63:56 for xAPIC, bits 63:32 for x2APIC)
|
||||||
|
// xAPIC: destination is bits 63:56
|
||||||
|
entry &= !(0xFF << 56);
|
||||||
|
entry |= u64::from(dest.get()) << 56;
|
||||||
|
guard.write_ioredtbl(idx, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a public API to find the right IOAPIC and call it:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn set_affinity(irq: u8, dest: ApicId) {
|
||||||
|
let gsi = resolve(irq);
|
||||||
|
if let Some(apic) = find_ioapic(gsi) {
|
||||||
|
apic.set_irq_affinity(gsi, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. Add `IoApic::set_irq_affinity()` method
|
||||||
|
2. Add `ioapic::set_affinity()` public function
|
||||||
|
3. Wire into kernel IRQ scheme `set_affinity` handler
|
||||||
|
4. Add round-robin or numa-aware default affinity for new IRQs
|
||||||
|
5. Rebuild kernel, verify boot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bottleneck #4: Simple Spinlocks for Scheduler Queues
|
||||||
|
|
||||||
|
**Severity**: 🟡 Medium — unfair under contention
|
||||||
|
**Files**: `context/switch.rs` (run_contexts access)
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
Per-CPU run queues use `spin::Mutex` (simple spinlock). Under contention:
|
||||||
|
- No fairness guarantee — a CPU may spin indefinitely
|
||||||
|
- No backoff — constant cache line bouncing
|
||||||
|
- No NUMA awareness — cross-socket contention is expensive
|
||||||
|
|
||||||
|
### Fix: MCS Lock or Try-Lock with Backoff
|
||||||
|
|
||||||
|
Replace `spin::Mutex` with an MCS lock (John Mellor-Crummey and Michael Scott):
|
||||||
|
- Each waiter spins on a local flag (cache-line friendly)
|
||||||
|
- FIFO ordering guarantees fairness
|
||||||
|
- O(1) cache line transfers on unlock
|
||||||
|
|
||||||
|
Alternatively, since per-CPU queues should have low contention:
|
||||||
|
- Use `try_lock()` with exponential backoff
|
||||||
|
- Fall back to global queue if per-CPU queue is contended
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. Implement MCS lock primitive in `sync/`
|
||||||
|
2. Replace `spin::Mutex` in run queue access
|
||||||
|
3. Add contention statistics to `PercpuBlock`
|
||||||
|
4. Rebuild kernel, verify boot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bottleneck #5: No NUMA Topology Awareness
|
||||||
|
|
||||||
|
**Severity**: 🟡 Medium — treats all CPUs and memory as uniform
|
||||||
|
**Files**: `acpi/madt/mod.rs`, `percpu.rs`
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
- No SRAT parsing (NUMA proximity domains)
|
||||||
|
- No SLIT parsing (NUMA distance matrix)
|
||||||
|
- Work stealing is random — may steal from a remote socket
|
||||||
|
- Memory allocation is uniform — no preference for local node
|
||||||
|
|
||||||
|
### Fix: ACPI SRAT + SLIT Parsing
|
||||||
|
|
||||||
|
1. Parse SRAT (System Resource Affinity Table) for CPU-to-node mapping
|
||||||
|
2. Parse SLIT (System Locality Information Table) for distance matrix
|
||||||
|
3. Add `numa_node: u32` to `PercpuBlock`
|
||||||
|
4. Prefer stealing from same-socket CPUs
|
||||||
|
5. Prefer allocating memory from local node
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. Add SRAT/SLIT table parsing in `acpi/`
|
||||||
|
2. Extend `PercpuBlock` with NUMA info
|
||||||
|
3. Update work stealing to prefer local node
|
||||||
|
4. Update memory allocator with NUMA hints
|
||||||
|
5. Rebuild kernel, verify boot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bottleneck #6: Coarse TLB Flush
|
||||||
|
|
||||||
|
**Severity**: 🟡 Low-Medium — full TLB flush instead of range-based
|
||||||
|
**Files**: `percpu.rs:122`
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// percpu.rs:122
|
||||||
|
crate::memory::RmmA::invalidate_all();
|
||||||
|
```
|
||||||
|
|
||||||
|
Every TLB shootdown flushes the **entire** TLB, even when only a single page changed.
|
||||||
|
Full TLB flush is extremely expensive on modern CPUs with large TLBs.
|
||||||
|
|
||||||
|
### Fix: Range-Based and Single-Page Invalidation
|
||||||
|
|
||||||
|
Use x86 `INVLPG` for single-page invalidation:
|
||||||
|
```rust
|
||||||
|
// For single page:
|
||||||
|
x86::tlb::flush(page);
|
||||||
|
|
||||||
|
// For range:
|
||||||
|
for page in range.step_by(PAGE_SIZE) {
|
||||||
|
x86::tlb::flush(page);
|
||||||
|
}
|
||||||
|
// Only use full flush for large ranges (> 32 pages)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. Add `shootdown_range(start: Page, count: usize)` to percpu
|
||||||
|
2. Store shootdown range in `PercpuBlock` alongside flag
|
||||||
|
3. Replace `invalidate_all()` with conditional INVLPG
|
||||||
|
4. Fall back to full flush for large ranges (> 32 pages) or PCID flush
|
||||||
|
5. Rebuild kernel, verify boot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bottleneck #7: No Priority Inheritance
|
||||||
|
|
||||||
|
**Severity**: 🟡 Low — mutex priority inversion possible
|
||||||
|
**Files**: `sync/` (various lock primitives)
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
|
||||||
|
No priority inheritance protocol. A low-priority thread holding a mutex can be preempted,
|
||||||
|
causing a high-priority thread waiting on the same mutex to block indefinitely (priority
|
||||||
|
inversion).
|
||||||
|
|
||||||
|
### Fix: Priority Inheritance for Mutexes
|
||||||
|
|
||||||
|
Implement the Basic Priority Inheritance Protocol (PI):
|
||||||
|
1. When a thread blocks on a mutex, donate its priority to the mutex holder
|
||||||
|
2. When the mutex is released, restore the original priority
|
||||||
|
3. Support multiple donors (priority queue of donors)
|
||||||
|
|
||||||
|
### Implementation Steps
|
||||||
|
|
||||||
|
1. Add `donated_priority: Option<usize>` to `Context`
|
||||||
|
2. Implement priority donation in mutex lock acquisition
|
||||||
|
3. Implement priority restoration in mutex unlock
|
||||||
|
4. Add debug assertions to detect inversion
|
||||||
|
5. Rebuild kernel, verify boot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Execution Timeline
|
||||||
|
|
||||||
|
| Phase | Bottleneck | Duration | Dependencies |
|
||||||
|
|-------|-----------|----------|-------------|
|
||||||
|
| 1 | #1 CONTEXT_SWITCH_LOCK | 1-2 days | None |
|
||||||
|
| 2 | #2 Broadcast TLB shootdown | 1-2 days | Phase 1 (per-CPU flags) |
|
||||||
|
| 3 | #3 IOAPIC IRQ affinity | 1-2 days | None |
|
||||||
|
| 4 | #4 MCS locks | 2-3 days | Phase 1 (reduced contention) |
|
||||||
|
| 5 | #6 Range TLB flush | 1 day | Phase 2 (shootdown infrastructure) |
|
||||||
|
| 6 | #5 NUMA awareness | 3-5 days | Phase 4 (scheduler queues) |
|
||||||
|
| 7 | #7 Priority inheritance | 2-3 days | None |
|
||||||
|
|
||||||
|
**Total estimate**: 11-18 days
|
||||||
|
|
||||||
|
## Patch Naming Convention
|
||||||
|
|
||||||
|
New kernel patches following this plan:
|
||||||
|
- `P9-percpu-context-switch.patch` — Bottleneck #1
|
||||||
|
- `P9-broadcast-tlb-shootdown.patch` — Bottleneck #2
|
||||||
|
- `P9-ioapic-irq-affinity.patch` — Bottleneck #3
|
||||||
|
- `P9-mcs-locks.patch` — Bottleneck #4
|
||||||
|
- `P9-range-tlb-flush.patch` — Bottleneck #6
|
||||||
|
- `P9-numa-awareness.patch` — Bottleneck #5
|
||||||
|
- `P9-priority-inheritance.patch` — Bottleneck #7
|
||||||
|
|
||||||
|
All P9 patches must be applied after P8 patches in `recipe.toml`.
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
diff --git a/bootstrap/Cargo.toml b/bootstrap/Cargo.toml
|
diff --git a/bootstrap/Cargo.toml b/bootstrap/Cargo.toml
|
||||||
|
index 82120c21..50faead5 100644
|
||||||
--- a/bootstrap/Cargo.toml
|
--- a/bootstrap/Cargo.toml
|
||||||
+++ b/bootstrap/Cargo.toml
|
+++ b/bootstrap/Cargo.toml
|
||||||
@@ -7,5 +7,3 @@ edition = "2024"
|
@@ -8 +7,0 @@ license = "MIT"
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
-[workspace]
|
|
||||||
-
|
-
|
||||||
[dependencies]
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
diff --git a/Cargo.toml b/Cargo.toml
|
||||||
|
--- a/Cargo.toml
|
||||||
|
+++ b/Cargo.toml
|
||||||
|
@@ -116,2 +116,3 @@
|
||||||
|
[patch."https://gitlab.redox-os.org/redox-os/relibc.git"]
|
||||||
|
#redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||||
|
+redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||||
|
+redox-rt = { path = "../../relibc/source/redox-rt" }
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@ diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
|
|||||||
index 9f507221..c69c2cfa 100644
|
index 9f507221..c69c2cfa 100644
|
||||||
--- a/daemon/src/lib.rs
|
--- a/daemon/src/lib.rs
|
||||||
+++ b/daemon/src/lib.rs
|
+++ b/daemon/src/lib.rs
|
||||||
@@ -10,15 +10,26 @@ use libredox::Fd;
|
@@ -10,15 +10,25 @@ use libredox::Fd;
|
||||||
use redox_scheme::Socket;
|
use redox_scheme::Socket;
|
||||||
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
|
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
|
||||||
|
|
||||||
@@ -10,7 +10,6 @@ index 9f507221..c69c2cfa 100644
|
|||||||
- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
|
- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
|
||||||
+unsafe fn get_fd(var: &str) -> Option<RawFd> {
|
+unsafe fn get_fd(var: &str) -> Option<RawFd> {
|
||||||
+ let fd: RawFd = match std::env::var(var)
|
+ let fd: RawFd = match std::env::var(var)
|
||||||
+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
|
|
||||||
+ .ok()
|
+ .ok()
|
||||||
+ .and_then(|val| {
|
+ .and_then(|val| {
|
||||||
+ val.parse()
|
+ val.parse()
|
||||||
@@ -33,7 +32,7 @@ index 9f507221..c69c2cfa 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||||
@@ -38,20 +49,26 @@ unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
@@ -38,20 +48,26 @@ unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||||
/// A long running background process that handles requests.
|
/// A long running background process that handles requests.
|
||||||
#[must_use = "Daemon::ready must be called"]
|
#[must_use = "Daemon::ready must be called"]
|
||||||
pub struct Daemon {
|
pub struct Daemon {
|
||||||
@@ -63,7 +62,7 @@ index 9f507221..c69c2cfa 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes `Command` as a child process.
|
/// Executes `Command` as a child process.
|
||||||
@@ -83,25 +100,28 @@ impl Daemon {
|
@@ -83,25 +99,28 @@ impl Daemon {
|
||||||
/// A long running background process that handles requests using schemes.
|
/// A long running background process that handles requests using schemes.
|
||||||
#[must_use = "SchemeDaemon::ready must be called"]
|
#[must_use = "SchemeDaemon::ready must be called"]
|
||||||
pub struct SchemeDaemon {
|
pub struct SchemeDaemon {
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
--- a/init/src/service.rs
|
||||||
|
+++ b/init/src/service.rs
|
||||||
|
@@ -1,7 +1,7 @@
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::io::Read;
|
||||||
|
-use std::os::fd::{AsRawFd, OwnedFd};
|
||||||
|
+use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::{env, io};
|
||||||
|
@@ -11,6 +11,23 @@
|
||||||
|
use crate::color::{init_error, init_warn, status_fail};
|
||||||
|
use crate::script::subst_env;
|
||||||
|
|
||||||
|
+/// Default timeout in seconds for waiting for service readiness notification.
|
||||||
|
+/// Prevents boot from hanging indefinitely if a daemon fails to notify.
|
||||||
|
+const SERVICE_READY_TIMEOUT_SECS: u32 = 30;
|
||||||
|
+
|
||||||
|
+/// Wait for data to be available on a file descriptor, with a timeout.
|
||||||
|
+/// Returns true if data is ready, false if timed out.
|
||||||
|
+fn poll_fd_timeout(fd: RawFd, timeout_secs: u32) -> bool {
|
||||||
|
+ let mut pollfd = libc::pollfd {
|
||||||
|
+ fd,
|
||||||
|
+ events: libc::POLLIN,
|
||||||
|
+ revents: 0,
|
||||||
|
+ };
|
||||||
|
+ let timeout_ms = (timeout_secs as libc::c_int) * 1000;
|
||||||
|
+ let result = unsafe { libc::poll(&mut pollfd, 1, timeout_ms) };
|
||||||
|
+ result > 0
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Service {
|
||||||
|
@@ -75,16 +92,36 @@
|
||||||
|
let _ = unsafe { libc::close(write_raw) };
|
||||||
|
|
||||||
|
match &self.type_ {
|
||||||
|
- ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
|
||||||
|
- Ok(()) => {}
|
||||||
|
- Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
||||||
|
- init_warn(&format!("{:?} exited without notifying readiness", command));
|
||||||
|
+ ServiceType::Notify => {
|
||||||
|
+ if !poll_fd_timeout(read_pipe.as_raw_fd(), SERVICE_READY_TIMEOUT_SECS) {
|
||||||
|
+ init_warn(&format!(
|
||||||
|
+ "{:?} timed out after {}s waiting for readiness",
|
||||||
|
+ command, SERVICE_READY_TIMEOUT_SECS
|
||||||
|
+ ));
|
||||||
|
+ let _ = child.kill();
|
||||||
|
+ let _ = child.wait();
|
||||||
|
+ return;
|
||||||
|
}
|
||||||
|
- Err(err) => {
|
||||||
|
- init_error(&format!("failed to wait for {:?}: {}", command, err));
|
||||||
|
+ match read_pipe.read_exact(&mut [0]) {
|
||||||
|
+ Ok(()) => {}
|
||||||
|
+ Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
||||||
|
+ init_warn(&format!("{:?} exited without notifying readiness", command));
|
||||||
|
+ }
|
||||||
|
+ Err(err) => {
|
||||||
|
+ init_error(&format!("failed to wait for {:?}: {}", command, err));
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
- },
|
||||||
|
+ }
|
||||||
|
ServiceType::Scheme(scheme) => {
|
||||||
|
+ if !poll_fd_timeout(read_pipe.as_raw_fd(), SERVICE_READY_TIMEOUT_SECS) {
|
||||||
|
+ init_warn(&format!(
|
||||||
|
+ "{:?} timed out after {}s waiting for scheme registration",
|
||||||
|
+ command, SERVICE_READY_TIMEOUT_SECS
|
||||||
|
+ ));
|
||||||
|
+ let _ = child.kill();
|
||||||
|
+ let _ = child.wait();
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
let mut new_fd = usize::MAX;
|
||||||
|
loop {
|
||||||
|
match syscall::call_ro(
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
--- a/init/src/unit.rs
|
||||||
|
+++ b/init/src/unit.rs
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-use std::collections::BTreeMap;
|
||||||
|
+use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
|
@@ -76,7 +76,12 @@
|
||||||
|
|
||||||
|
pub fn load_units(&mut self, root_unit: UnitId, errors: &mut Vec<String>) -> Vec<UnitId> {
|
||||||
|
let mut loaded_units = vec![];
|
||||||
|
- let mut pending_units = vec![root_unit];
|
||||||
|
+ let mut pending_units = vec![root_unit.clone()];
|
||||||
|
+ // Track all units ever seen (queued or loaded) to avoid re-queuing.
|
||||||
|
+ // A true cycle exists only when a unit depends on itself through its
|
||||||
|
+ // ancestor chain, not when two independent units share a dependency.
|
||||||
|
+ let mut seen = BTreeSet::new();
|
||||||
|
+ seen.insert(root_unit);
|
||||||
|
|
||||||
|
while let Some(unit_id) = pending_units.pop() {
|
||||||
|
if self.units.contains_key(&unit_id) {
|
||||||
|
@@ -86,6 +91,11 @@
|
||||||
|
if let Some(unit) = unit {
|
||||||
|
loaded_units.push(unit.clone());
|
||||||
|
for dep in &self.unit(&unit).info.requires_weak {
|
||||||
|
+ // If the dependency is already loaded or already queued,
|
||||||
|
+ // it's a shared dependency — not a cycle. Skip it.
|
||||||
|
+ if !seen.insert(dep.clone()) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
pending_units.push(dep.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
diff --git a/init/src/service.rs b/init/src/service.rs
|
||||||
|
--- a/init/src/service.rs
|
||||||
|
+++ b/init/src/service.rs
|
||||||
|
@@ -40,6 +40,28 @@
|
||||||
|
pub inherit_envs: BTreeSet<String>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub type_: ServiceType,
|
||||||
|
+ /// Restart policy for this service. Default: Never (no restart on exit).
|
||||||
|
+ #[serde(default)]
|
||||||
|
+ pub restart: RestartPolicy,
|
||||||
|
+ /// Maximum consecutive restart attempts before giving up. Default: 3.
|
||||||
|
+ #[serde(default = "default_max_restarts")]
|
||||||
|
+ pub max_restarts: u32,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn default_max_restarts() -> u32 {
|
||||||
|
+ 3
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
||||||
|
+#[serde(rename_all = "kebab-case")]
|
||||||
|
+pub enum RestartPolicy {
|
||||||
|
+ /// Never restart (default — current behavior).
|
||||||
|
+ #[default]
|
||||||
|
+ Never,
|
||||||
|
+ /// Restart on non-zero exit code.
|
||||||
|
+ OnFailure,
|
||||||
|
+ /// Restart on any exit (including clean).
|
||||||
|
+ Always,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deserialize)]
|
||||||
|
@@ -53,7 +75,9 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service {
|
||||||
|
- pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) {
|
||||||
|
+ /// Returns Some(child_pid) for long-running services (Notify, Scheme),
|
||||||
|
+ /// None for Oneshot/OneshotAsync or if the spawn failed.
|
||||||
|
+ pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) -> Option<u32> {
|
||||||
|
let mut command = Command::new(&self.cmd);
|
||||||
|
command.args(self.args.iter().map(|arg| subst_env(arg)));
|
||||||
|
command.env_clear();
|
||||||
|
@@ -72,6 +96,7 @@
|
||||||
|
status_fail(&format!("failed to execute {:?}: {}", command, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ None
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
|
||||||
|
@@ -85,7 +110,7 @@
|
||||||
|
let _ = unsafe { libc::close(write_raw) };
|
||||||
|
drop(read_pipe);
|
||||||
|
status_fail(&format!("failed to execute {:?}: {}", command, err));
|
||||||
|
- return;
|
||||||
|
+ return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@@ -100,7 +125,7 @@
|
||||||
|
));
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
- return;
|
||||||
|
+ return None;
|
||||||
|
}
|
||||||
|
match read_pipe.read_exact(&mut [0]) {
|
||||||
|
Ok(()) => {}
|
||||||
|
@@ -111,6 +136,7 @@
|
||||||
|
init_error(&format!("failed to wait for {:?}: {}", command, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ Some(child.id())
|
||||||
|
}
|
||||||
|
ServiceType::Scheme(scheme) => {
|
||||||
|
if !poll_fd_timeout(read_pipe.as_raw_fd(), SERVICE_READY_TIMEOUT_SECS) {
|
||||||
|
@@ -120,7 +146,7 @@
|
||||||
|
));
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
- return;
|
||||||
|
+ return None;
|
||||||
|
}
|
||||||
|
let mut new_fd = usize::MAX;
|
||||||
|
loop {
|
||||||
|
@@ -135,16 +161,16 @@
|
||||||
|
}) => continue,
|
||||||
|
Ok(0) => {
|
||||||
|
init_warn(&format!("{:?} exited without notifying readiness", command));
|
||||||
|
- return;
|
||||||
|
+ return None;
|
||||||
|
}
|
||||||
|
Ok(1) => break,
|
||||||
|
Ok(n) => {
|
||||||
|
init_error(&format!("incorrect amount of fds {} returned", n));
|
||||||
|
- return;
|
||||||
|
+ return None;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
init_error(&format!("failed to wait for {:?}: {}", command, err));
|
||||||
|
- return;
|
||||||
|
+ return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -152,6 +178,7 @@
|
||||||
|
let current_namespace_fd = libredox::call::getns().expect("TODO");
|
||||||
|
libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
|
||||||
|
.expect("TODO");
|
||||||
|
+ Some(child.id())
|
||||||
|
}
|
||||||
|
ServiceType::Oneshot => {
|
||||||
|
drop(read_pipe);
|
||||||
|
@@ -165,6 +192,7 @@
|
||||||
|
init_error(&format!("failed to wait for {:?}: {}", command, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ None
|
||||||
|
}
|
||||||
|
ServiceType::OneshotAsync => unreachable!(),
|
||||||
|
}
|
||||||
|
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
|
||||||
|
--- a/init/src/scheduler.rs
|
||||||
|
+++ b/init/src/scheduler.rs
|
||||||
|
@@ -2,17 +2,11 @@
|
||||||
|
|
||||||
|
use crate::InitConfig;
|
||||||
|
use crate::color::{init_error, init_warn, status_ok, status_skip};
|
||||||
|
+use crate::service::RestartPolicy;
|
||||||
|
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
|
||||||
|
|
||||||
|
const SPAWN_BATCH_SIZE: usize = 50;
|
||||||
|
|
||||||
|
-#[derive(Clone, Debug)]
|
||||||
|
-pub enum RestartPolicy {
|
||||||
|
- Never,
|
||||||
|
- OnFailure,
|
||||||
|
- Always,
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
/// Tracks the restart state for a supervised service.
|
||||||
|
pub struct ServiceState {
|
||||||
|
pub unit_id: UnitId,
|
||||||
|
@@ -167,9 +161,21 @@
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status_ok(&format!("Started {}", desc));
|
||||||
|
- service.spawn(&config.envs);
|
||||||
|
- // Supervision infrastructure is in place; full PID tracking requires
|
||||||
|
- // service.spawn() to return Option<u32> (added by a later patch).
|
||||||
|
+ if let Some(pid) = service.spawn(&config.envs) {
|
||||||
|
+ if service.restart != RestartPolicy::Never {
|
||||||
|
+ supervised.insert(
|
||||||
|
+ pid,
|
||||||
|
+ ServiceState {
|
||||||
|
+ unit_id: unit.id.clone(),
|
||||||
|
+ cmd: service.cmd.clone(),
|
||||||
|
+ restart_policy: service.restart.clone(),
|
||||||
|
+ max_restarts: service.max_restarts,
|
||||||
|
+ restart_count: 0,
|
||||||
|
+ last_restart_ms: 0,
|
||||||
|
+ },
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
UnitKind::Target {} => {}
|
||||||
|
}
|
||||||
|
diff --git a/init/src/main.rs b/init/src/main.rs
|
||||||
|
--- a/init/src/main.rs
|
||||||
|
+++ b/init/src/main.rs
|
||||||
|
@@ -5,7 +5,8 @@
|
||||||
|
|
||||||
|
use libredox::flag::{O_RDONLY, O_WRONLY};
|
||||||
|
|
||||||
|
-use crate::scheduler::Scheduler;
|
||||||
|
+use crate::scheduler::{Scheduler, ServiceState};
|
||||||
|
+use crate::service::RestartPolicy;
|
||||||
|
use crate::unit::{UnitId, UnitStore};
|
||||||
|
|
||||||
|
mod color;
|
||||||
|
@@ -176,15 +177,95 @@
|
||||||
|
if scheduler.has_pending() {
|
||||||
|
// Reap exited children before processing more services.
|
||||||
|
let mut status = 0;
|
||||||
|
- while libredox::call::waitpid(0, &mut status, 1).is_ok() {}
|
||||||
|
+ while let Ok(pid) = libredox::call::waitpid(0, &mut status, 1) {
|
||||||
|
+ handle_child_exit(pid as u32, status, &mut scheduler, &mut unit_store, &mut init_config);
|
||||||
|
+ }
|
||||||
|
|
||||||
|
scheduler.step(&mut unit_store, &mut init_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut status = 0;
|
||||||
|
match libredox::call::waitpid(0, &mut status, 1) {
|
||||||
|
- Ok(_pid) => {}
|
||||||
|
+ Ok(pid) => {
|
||||||
|
+ handle_child_exit(pid as u32, status, &mut scheduler, &mut unit_store, &mut init_config);
|
||||||
|
+ }
|
||||||
|
Err(err) => init_error(&format!("waitpid error: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+fn handle_child_exit(
|
||||||
|
+ pid: u32,
|
||||||
|
+ exit_status: i32,
|
||||||
|
+ scheduler: &mut Scheduler,
|
||||||
|
+ unit_store: &mut UnitStore,
|
||||||
|
+ init_config: &mut InitConfig,
|
||||||
|
+) {
|
||||||
|
+ let Some(state) = scheduler.supervised.remove(&pid) else {
|
||||||
|
+ return;
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ let exited_cleanly = exit_status == 0;
|
||||||
|
+ let should_restart = match state.restart_policy {
|
||||||
|
+ RestartPolicy::Never => false,
|
||||||
|
+ RestartPolicy::OnFailure => !exited_cleanly,
|
||||||
|
+ RestartPolicy::Always => true,
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ if !should_restart {
|
||||||
|
+ init_warn(&format!(
|
||||||
|
+ "service {} (pid {}) exited with status {} — not restarting (policy: {:?})",
|
||||||
|
+ state.cmd, pid, exit_status, state.restart_policy,
|
||||||
|
+ ));
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if state.restart_count >= state.max_restarts {
|
||||||
|
+ init_warn(&format!(
|
||||||
|
+ "service {} exceeded max_restarts ({}) — giving up",
|
||||||
|
+ state.cmd, state.max_restarts,
|
||||||
|
+ ));
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let backoff_secs = 1u64 << state.restart_count.min(4);
|
||||||
|
+ let backoff_secs = backoff_secs.min(30);
|
||||||
|
+ init_warn(&format!(
|
||||||
|
+ "service {} (pid {}) exited with status {} — restarting in {}s (attempt {}/{})",
|
||||||
|
+ state.cmd,
|
||||||
|
+ pid,
|
||||||
|
+ exit_status,
|
||||||
|
+ backoff_secs,
|
||||||
|
+ state.restart_count + 1,
|
||||||
|
+ state.max_restarts,
|
||||||
|
+ ));
|
||||||
|
+
|
||||||
|
+ std::thread::sleep(std::time::Duration::from_secs(backoff_secs));
|
||||||
|
+
|
||||||
|
+ let unit_id = state.unit_id.clone();
|
||||||
|
+ let new_restart_count = state.restart_count + 1;
|
||||||
|
+
|
||||||
|
+ let unit = unit_store.unit_mut(&unit_id);
|
||||||
|
+ if let crate::unit::UnitKind::Service { service } = &unit.kind {
|
||||||
|
+ if let Some(new_pid) = service.spawn(&init_config.envs) {
|
||||||
|
+ init_warn(&format!(
|
||||||
|
+ "restarted service {} as pid {} (attempt {}/{})",
|
||||||
|
+ state.cmd, new_pid, new_restart_count, state.max_restarts,
|
||||||
|
+ ));
|
||||||
|
+ scheduler.supervised.insert(
|
||||||
|
+ new_pid,
|
||||||
|
+ ServiceState {
|
||||||
|
+ unit_id,
|
||||||
|
+ cmd: service.cmd.clone(),
|
||||||
|
+ restart_policy: state.restart_policy.clone(),
|
||||||
|
+ max_restarts: state.max_restarts,
|
||||||
|
+ restart_count: new_restart_count,
|
||||||
|
+ last_restart_ms: std::time::SystemTime::now()
|
||||||
|
+ .duration_since(std::time::UNIX_EPOCH)
|
||||||
|
+ .unwrap_or_default()
|
||||||
|
+ .as_millis() as u64,
|
||||||
|
+ },
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
--- a/drivers/pcid/src/main.rs
|
||||||
|
+++ b/drivers/pcid/src/main.rs
|
||||||
|
@@ -162,20 +162,32 @@
|
||||||
|
| CommandRegister::IO_ENABLE
|
||||||
|
});
|
||||||
|
|
||||||
|
- // Disable MSI and MSI-X in case a previous driver instance enabled them.
|
||||||
|
- for capability in capabilities {
|
||||||
|
+ // Detect MSI capabilities for logging
|
||||||
|
+ let has_msix = capabilities.iter().any(|c| matches!(c, PciCapability::MsiX(_)));
|
||||||
|
+ let has_msi = capabilities.iter().any(|c| matches!(c, PciCapability::Msi(_)));
|
||||||
|
+
|
||||||
|
+ // Disable MSI and MSI-X to start from a clean state.
|
||||||
|
+ // Drivers that support MSI will re-enable it via the pcid scheme interface
|
||||||
|
+ // using irq_helpers::pci_allocate_interrupt_vector(), which handles
|
||||||
|
+ // MSI-X -> MSI -> legacy fallback automatically.
|
||||||
|
+ for capability in capabilities.iter_mut() {
|
||||||
|
match capability {
|
||||||
|
- PciCapability::Msi(capability) => {
|
||||||
|
- capability.set_enabled(false, pcie);
|
||||||
|
- }
|
||||||
|
- PciCapability::MsiX(capability) => {
|
||||||
|
- capability.set_enabled(false, pcie);
|
||||||
|
- }
|
||||||
|
+ PciCapability::Msi(cap) => cap.set_enabled(false, pcie),
|
||||||
|
+ PciCapability::MsiX(cap) => cap.set_enabled(false, pcie),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- // Set IRQ line to 9 if not set
|
||||||
|
+ // Log MSI capability for debugging. Legacy IRQ is still configured as
|
||||||
|
+ // a baseline — drivers without MSI support (e.g., ahcid) need it.
|
||||||
|
+ // Drivers with MSI support will switch away from legacy via the scheme interface.
|
||||||
|
+ if has_msix {
|
||||||
|
+ info!(" has MSI-X capability (legacy IRQ as fallback)");
|
||||||
|
+ } else if has_msi {
|
||||||
|
+ info!(" has MSI capability (legacy IRQ as fallback)");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Legacy IRQ baseline for all devices
|
||||||
|
let mut irq = 0xFF;
|
||||||
|
let mut interrupt_pin = 0xFF;
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
--- a/drivers/acpid/src/aml_physmem.rs
|
||||||
|
+++ b/drivers/acpid/src/aml_physmem.rs
|
||||||
|
@@ -190,7 +190,10 @@
|
||||||
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||||
|
match page_cache.read_from_phys::<T>(address) {
|
||||||
|
Ok(value) => value,
|
||||||
|
- Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error),
|
||||||
|
+ Err(error) => {
|
||||||
|
+ log::error!("AML physmem read failed at {:#x}: {} — returning zero", address, error);
|
||||||
|
+ T::zero()
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -252,13 +255,26 @@
|
||||||
|
let offset = phys & OFFSET_MASK;
|
||||||
|
let pages = (offset + size + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||||
|
let map_size = pages * PAGE_SIZE;
|
||||||
|
- let virt_page = common::physmap(
|
||||||
|
+ let virt_page = match common::physmap(
|
||||||
|
phys_page,
|
||||||
|
map_size,
|
||||||
|
common::Prot::RW,
|
||||||
|
common::MemoryType::default(),
|
||||||
|
- )
|
||||||
|
- .expect("failed to map physical region") as usize;
|
||||||
|
+ ) {
|
||||||
|
+ Ok(v) => v as usize,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::error!(
|
||||||
|
+ "failed to map physical region {:#x}+{:#x}: {:?} — mapping zero page fallback",
|
||||||
|
+ phys_page,
|
||||||
|
+ map_size,
|
||||||
|
+ err
|
||||||
|
+ );
|
||||||
|
+ // Map the zero page as a safe fallback so the pointer is at least valid
|
||||||
|
+ // Reads will return zeroes; this is better than crashing.
|
||||||
|
+ common::physmap(0, PAGE_SIZE, common::Prot::RW, common::MemoryType::default())
|
||||||
|
+ .expect("failed to map even the zero page") as usize
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
PhysicalMapping {
|
||||||
|
physical_start: phys,
|
||||||
|
virtual_start: NonNull::new((virt_page + offset) as *mut T).unwrap(),
|
||||||
|
@@ -269,9 +285,8 @@
|
||||||
|
}
|
||||||
|
fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>) {
|
||||||
|
let virt_page = region.virtual_start.addr().get() & PAGE_MASK;
|
||||||
|
- unsafe {
|
||||||
|
- libredox::call::munmap(virt_page as *mut (), region.mapped_length)
|
||||||
|
- .expect("failed to unmap physical region")
|
||||||
|
+ if let Err(e) = unsafe { libredox::call::munmap(virt_page as *mut (), region.mapped_length) } {
|
||||||
|
+ log::error!("failed to unmap physical region at {:#x}: {:?}", virt_page, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
--- a/ipcd/src/chan.rs
|
||||||
|
+++ b/ipcd/src/chan.rs
|
||||||
|
@@ -16,6 +16,9 @@
|
||||||
|
path: Option<String>,
|
||||||
|
awaiting: VecDeque<usize>,
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+/// Maximum pending connections per listener (like Linux SOMAXCONN).
|
||||||
|
+const MAX_LISTENER_BACKLOG: usize = 64;
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Extra {
|
||||||
|
Client(Client),
|
||||||
|
@@ -66,6 +69,9 @@
|
||||||
|
pub fn connect(&mut self, other: usize) -> Result<()> {
|
||||||
|
match self.extra {
|
||||||
|
Extra::Listener(ref mut listener) => {
|
||||||
|
+ if listener.awaiting.len() >= MAX_LISTENER_BACKLOG {
|
||||||
|
+ return Err(Error::new(ECONNREFUSED));
|
||||||
|
+ }
|
||||||
|
listener.awaiting.push_back(other);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
--- a/ipcd/src/uds/stream.rs
|
||||||
|
+++ b/ipcd/src/uds/stream.rs
|
||||||
|
@@ -32,6 +32,11 @@
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+/// Maximum pending connections per UDS listener.
|
||||||
|
+const MAX_UDS_LISTENER_BACKLOG: usize = 64;
|
||||||
|
+/// Maximum queued data packets per connection before dropping/warning.
|
||||||
|
+const MAX_UDS_PACKET_QUEUE: usize = 256;
|
||||||
|
+
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
struct Connection {
|
||||||
|
peer: usize,
|
||||||
|
@@ -305,14 +310,18 @@
|
||||||
|
}
|
||||||
|
_ => return Err(Error::new(ECONNREFUSED)),
|
||||||
|
}
|
||||||
|
- self.connect_unchecked(other, client_ucred);
|
||||||
|
+ self.connect_unchecked(other, client_ucred)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
- fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) {
|
||||||
|
+ fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) -> Result<()> {
|
||||||
|
+ if self.awaiting.len() >= MAX_UDS_LISTENER_BACKLOG {
|
||||||
|
+ return Err(Error::new(ECONNREFUSED));
|
||||||
|
+ }
|
||||||
|
self.awaiting.push_back((other.primary_id, client_ucred));
|
||||||
|
other.state = State::Connecting;
|
||||||
|
other.connection = Some(Connection::new(self.primary_id));
|
||||||
|
+ Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_listening(&self) -> bool {
|
||||||
|
@@ -753,6 +762,9 @@
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if connection.packets.len() >= MAX_UDS_PACKET_QUEUE {
|
||||||
|
+ return Err(Error::new(EAGAIN));
|
||||||
|
+ }
|
||||||
|
connection.packets.push_back(packet);
|
||||||
|
(payload_len, remote_id)
|
||||||
|
};
|
||||||
|
@@ -997,7 +1010,7 @@
|
||||||
|
return Err(Error::new(EPIPE));
|
||||||
|
}
|
||||||
|
let pair_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ };
|
||||||
|
- socket.connect_unchecked(&mut new, pair_ucred);
|
||||||
|
+ socket.connect_unchecked(&mut new, pair_ucred)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// smoltcp sends writeable whenever a listener gets a
|
||||||
|
@@ -1059,6 +1072,9 @@
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
let packet = DataPacket::new(buf.to_vec(), ancillary_data);
|
||||||
|
+ if connection.packets.len() >= MAX_UDS_PACKET_QUEUE {
|
||||||
|
+ return Err(Error::new(EAGAIN));
|
||||||
|
+ }
|
||||||
|
connection.packets.push_back(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--- a/ipcd/src/uds/dgram.rs
|
||||||
|
+++ b/ipcd/src/uds/dgram.rs
|
||||||
|
@@ -21,6 +21,9 @@
|
||||||
|
mem,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
+
|
||||||
|
+/// Maximum queued datagrams per socket.
|
||||||
|
+const MAX_DGRAM_QUEUE: usize = 256;
|
||||||
|
use syscall::{error::*, flag::*, schemev2::NewFdFlags, Error, FobtainFdFlags, Stat};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
@@ -393,6 +396,9 @@
|
||||||
|
Credential::new(pid as i32, uid as i32, gid as i32),
|
||||||
|
)?;
|
||||||
|
let payload_len = message.len();
|
||||||
|
+ if socket.messages.len() >= MAX_DGRAM_QUEUE {
|
||||||
|
+ return Err(Error::new(EAGAIN));
|
||||||
|
+ }
|
||||||
|
socket.messages.push_back(message);
|
||||||
|
|
||||||
|
Ok(payload_len)
|
||||||
|
@@ -559,6 +565,9 @@
|
||||||
|
name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
+ if remote.messages.len() >= MAX_DGRAM_QUEUE {
|
||||||
|
+ return Err(Error::new(EAGAIN));
|
||||||
|
+ }
|
||||||
|
remote.messages.push_back(message);
|
||||||
|
|
||||||
|
self.post_fevent(remote_id, EVENT_READ.bits())?;
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs
|
||||||
|
index 17d168ef..5271a2f1 100644
|
||||||
|
--- a/drivers/net/virtio-netd/src/main.rs
|
||||||
|
+++ b/drivers/net/virtio-netd/src/main.rs
|
||||||
|
@@ -34,2 +34,7 @@ fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- deamon(daemon, pcid_handle).unwrap();
|
||||||
|
- unreachable!();
|
||||||
|
+ match deamon(daemon, pcid_handle) {
|
||||||
|
+ Ok(()) => unreachable!(),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::error!("virtio-netd: fatal error: {err}");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
index 28ca077a..39b0b048 100644
|
||||||
|
--- a/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
@@ -121 +121 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||||
|
- let mut first = None;
|
||||||
|
+ let mut first_aligned: Option<u8> = None;
|
||||||
|
@@ -130,2 +130,2 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||||
|
- let first = *first.get_or_insert(number);
|
||||||
|
- let irq_number = first + index;
|
||||||
|
+ let base = *first_aligned.get_or_insert(number);
|
||||||
|
+ let irq_number = base + index;
|
||||||
|
@@ -143,0 +144,9 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||||
|
+ // Vector already allocated by another process; release any partial range and
|
||||||
|
+ // restart the search from the next aligned position.
|
||||||
|
+ Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
|
||||||
|
+ drop(handles.drain(..));
|
||||||
|
+ first_aligned = None;
|
||||||
|
+ index = 0;
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
@@ -155 +164 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||||
|
- let first = match first {
|
||||||
|
+ let first = match first_aligned {
|
||||||
|
@@ -183 +192 @@ pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result<Option<(u8,
|
||||||
|
-pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) {
|
||||||
|
+pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> Option<(MsiAddrAndData, File)> {
|
||||||
|
@@ -187 +196,7 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||||
|
- let lapic_id = u8::try_from(cpu_id).expect("CPU id couldn't fit inside u8");
|
||||||
|
+ let lapic_id = match u8::try_from(cpu_id) {
|
||||||
|
+ Ok(id) => id,
|
||||||
|
+ Err(_) => {
|
||||||
|
+ log::warn!("cpu_id {} too large for MSI address format", cpu_id);
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -192,3 +207,11 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||||
|
- let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id)
|
||||||
|
- .expect("failed to allocate interrupt vector")
|
||||||
|
- .expect("no interrupt vectors left");
|
||||||
|
+ let (vector, interrupt_handle) = match allocate_single_interrupt_vector(cpu_id) {
|
||||||
|
+ Ok(Some(result)) => result,
|
||||||
|
+ Ok(None) => {
|
||||||
|
+ log::warn!("no interrupt vectors available for MSI on CPU {}", cpu_id);
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::warn!("failed to allocate interrupt vector for MSI on CPU {}: {}", cpu_id, err);
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -197 +220 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||||
|
- (
|
||||||
|
+ Some((
|
||||||
|
@@ -203 +226 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||||
|
- )
|
||||||
|
+ ))
|
||||||
|
@@ -209 +232 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||||
|
-) -> File {
|
||||||
|
+) -> Option<File> {
|
||||||
|
@@ -214 +237,7 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||||
|
- let destination_id = read_bsp_apic_id().expect("failed to read BSP apic id");
|
||||||
|
+ let destination_id = match read_bsp_apic_id() {
|
||||||
|
+ Ok(id) => id,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::warn!("failed to read BSP apic id: {}", err);
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -216 +245 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||||
|
- allocate_single_interrupt_vector_for_msi(destination_id);
|
||||||
|
+ allocate_single_interrupt_vector_for_msi(destination_id)?;
|
||||||
|
@@ -228 +257 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||||
|
- interrupt_handle
|
||||||
|
+ Some(interrupt_handle)
|
||||||
|
@@ -285,2 +313,0 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
- pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
|
||||||
|
-
|
||||||
|
@@ -289,3 +316,9 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
- let bsp_cpu_id = read_bsp_apic_id()
|
||||||
|
- .unwrap_or_else(|err| panic!("{driver}: failed to read BSP APIC ID: {err}"));
|
||||||
|
- let (msg_addr_and_data, irq_handle) = allocate_single_interrupt_vector_for_msi(bsp_cpu_id);
|
||||||
|
+ let bsp_cpu_id = match read_bsp_apic_id() {
|
||||||
|
+ Ok(id) => id,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::warn!("{driver}: failed to read BSP APIC ID: {err}");
|
||||||
|
+ // fall through to MSI
|
||||||
|
+ 0
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ if let Some((msg_addr_and_data, irq_handle)) = allocate_single_interrupt_vector_for_msi(bsp_cpu_id) {
|
||||||
|
@@ -295 +328,3 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
- InterruptVector {
|
||||||
|
+ pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
|
||||||
|
+
|
||||||
|
+ return InterruptVector {
|
||||||
|
@@ -298,0 +334 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ };
|
||||||
|
@@ -300,3 +336,8 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
- } else if has_msi {
|
||||||
|
- InterruptVector {
|
||||||
|
- irq_handle: allocate_first_msi_interrupt_on_bsp(pcid_handle),
|
||||||
|
+ log::warn!("{driver}: MSI-X vector allocation failed, falling back");
|
||||||
|
+ // fall through to MSI
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if has_msi {
|
||||||
|
+ if let Some(irq_handle) = allocate_first_msi_interrupt_on_bsp(pcid_handle) {
|
||||||
|
+ return InterruptVector {
|
||||||
|
+ irq_handle,
|
||||||
|
@@ -304,0 +346,3 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ };
|
||||||
|
+ }
|
||||||
|
+ log::warn!("{driver}: MSI allocation failed, falling back to legacy");
|
||||||
|
@@ -306 +350,2 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
- } else if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
|
||||||
|
+
|
||||||
|
+ if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
|
||||||
|
@@ -308 +353 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
- InterruptVector {
|
||||||
|
+ return InterruptVector {
|
||||||
|
@@ -311,0 +357 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ };
|
||||||
|
@@ -313 +359 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
- } else {
|
||||||
|
+
|
||||||
|
@@ -316 +361,0 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
-}
|
||||||
|
diff --git a/drivers/storage/virtio-blkd/src/main.rs b/drivers/storage/virtio-blkd/src/main.rs
|
||||||
|
index d21236b3..95089eb9 100644
|
||||||
|
--- a/drivers/storage/virtio-blkd/src/main.rs
|
||||||
|
+++ b/drivers/storage/virtio-blkd/src/main.rs
|
||||||
|
@@ -106,2 +106,7 @@ fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -
|
||||||
|
- daemon(redox_daemon, pcid_handle).unwrap();
|
||||||
|
- unreachable!();
|
||||||
|
+ match daemon(redox_daemon, pcid_handle) {
|
||||||
|
+ Ok(()) => unreachable!(),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::error!("virtio-blkd: fatal error: {err}");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs
|
||||||
|
index d345a52f..da9cabe1 100644
|
||||||
|
--- a/drivers/usb/xhcid/src/main.rs
|
||||||
|
+++ b/drivers/usb/xhcid/src/main.rs
|
||||||
|
@@ -79,2 +79,3 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||||
|
- let (msg_addr_and_data, interrupt_handle) =
|
||||||
|
- allocate_single_interrupt_vector_for_msi(destination_id);
|
||||||
|
+ if let Some((msg_addr_and_data, interrupt_handle)) =
|
||||||
|
+ allocate_single_interrupt_vector_for_msi(destination_id)
|
||||||
|
+ {
|
||||||
|
@@ -84,3 +84,0 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||||
|
- (Some(interrupt_handle), InterruptMethod::Msi)
|
||||||
|
- };
|
||||||
|
-
|
||||||
|
@@ -90,5 +88,16 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||||
|
- method
|
||||||
|
- } else if has_msi {
|
||||||
|
- let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle);
|
||||||
|
- (Some(interrupt_handle), InterruptMethod::Msi)
|
||||||
|
- } else if let Some(irq) = pci_config.func.legacy_interrupt_line {
|
||||||
|
+ return (Some(interrupt_handle), InterruptMethod::Msi);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // MSI-X allocation failed; fall through to MSI or legacy.
|
||||||
|
+ log::warn!("xhcid: MSI-X vector allocation failed, falling back");
|
||||||
|
+ };
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if has_msi {
|
||||||
|
+ if let Some(interrupt_handle) = allocate_first_msi_interrupt_on_bsp(pcid_handle) {
|
||||||
|
+ return (Some(interrupt_handle), InterruptMethod::Msi);
|
||||||
|
+ }
|
||||||
|
+ log::warn!("xhcid: MSI allocation failed, falling back to legacy");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if let Some(irq) = pci_config.func.legacy_interrupt_line {
|
||||||
|
diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs
|
||||||
|
index aea86c4a..8fdc7ca6 100644
|
||||||
|
--- a/drivers/virtio-core/src/arch/x86.rs
|
||||||
|
+++ b/drivers/virtio-core/src/arch/x86.rs
|
||||||
|
@@ -26 +26,2 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result<File, Error> {
|
||||||
|
- allocate_single_interrupt_vector_for_msi(destination_id);
|
||||||
|
+ allocate_single_interrupt_vector_for_msi(destination_id)
|
||||||
|
+ .ok_or(Error::MsiAllocationFailed)?;
|
||||||
|
diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs
|
||||||
|
index d3445d2d..b961265c 100644
|
||||||
|
--- a/drivers/virtio-core/src/transport.rs
|
||||||
|
+++ b/drivers/virtio-core/src/transport.rs
|
||||||
|
@@ -21,0 +22,2 @@ pub enum Error {
|
||||||
|
+ #[error("MSI/MSI-X vector allocation failed")]
|
||||||
|
+ MsiAllocationFailed,
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
||||||
|
index 343533d0..8ef6ab0e 100644
|
||||||
|
--- a/drivers/acpid/src/acpi.rs
|
||||||
|
+++ b/drivers/acpid/src/acpi.rs
|
||||||
|
@@ -55,3 +55,2 @@ impl SdtHeader {
|
||||||
|
- self.length
|
||||||
|
- .try_into()
|
||||||
|
- .expect("expected usize to be at least 32 bits")
|
||||||
|
+ // usize is at least 32 bits on all supported architectures.
|
||||||
|
+ self.length as usize
|
||||||
|
@@ -95,0 +95,3 @@ pub enum InvalidSdtError {
|
||||||
|
+
|
||||||
|
+ #[error("bad alignment")]
|
||||||
|
+ BadAlignment,
|
||||||
|
@@ -139,3 +141,4 @@ impl Sdt {
|
||||||
|
- Err(plain::Error::BadAlignment) => panic!(
|
||||||
|
- "plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!"
|
||||||
|
- ),
|
||||||
|
+ Err(plain::Error::BadAlignment) => {
|
||||||
|
+ log::error!("plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]");
|
||||||
|
+ return Err(InvalidSdtError::BadAlignment);
|
||||||
|
+ }
|
||||||
|
@@ -171 +174,3 @@ impl Sdt {
|
||||||
|
- assert!(pages.len() >= mem::size_of::<SdtHeader>());
|
||||||
|
+ if pages.len() < mem::size_of::<SdtHeader>() {
|
||||||
|
+ return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize));
|
||||||
|
+ }
|
||||||
|
@@ -174,2 +179,5 @@ impl Sdt {
|
||||||
|
- let sdt = plain::from_bytes::<SdtHeader>(&sdt_mem[..mem::size_of::<SdtHeader>()])
|
||||||
|
- .expect("either alignment is wrong, or the length is too short, both of which are already checked for");
|
||||||
|
+ let sdt = match plain::from_bytes::<SdtHeader>(&sdt_mem[..mem::size_of::<SdtHeader>()]) {
|
||||||
|
+ Ok(header) => header,
|
||||||
|
+ Err(plain::Error::TooShort) => return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize)),
|
||||||
|
+ Err(plain::Error::BadAlignment) => return Err(TablePhysLoadError::Validity(InvalidSdtError::BadAlignment)),
|
||||||
|
+ };
|
||||||
|
@@ -200 +208,4 @@ impl Sdt {
|
||||||
|
- assert_eq!(left, 0);
|
||||||
|
+ if left != 0 {
|
||||||
|
+ log::error!("SDT physical load left {} bytes remaining after loop", left);
|
||||||
|
+ return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize));
|
||||||
|
+ }
|
||||||
|
@@ -213,2 +224,2 @@ impl Deref for Sdt {
|
||||||
|
- plain::from_bytes::<SdtHeader>(&self.0)
|
||||||
|
- .expect("expected already validated Sdt to be able to get its header")
|
||||||
|
+ // SAFETY: Sdt::new validated the slice length and SdtHeader is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_ptr() as *const SdtHeader) }
|
||||||
|
@@ -269,28 +280 @@ impl AmlSymbols {
|
||||||
|
- let rsdp_address = match std::env::var("RSDP_ADDR") {
|
||||||
|
- Ok(addr) => usize::from_str_radix(&addr, 16)?,
|
||||||
|
- Err(_) => {
|
||||||
|
- // RSDP_ADDR not provided — probe BIOS area (0xE0000–0xFFFFF) for RSDP signature
|
||||||
|
- log::info!("RSDP_ADDR not set, probing BIOS area for RSDP...");
|
||||||
|
- let mut found = None;
|
||||||
|
- for page_base in (0xE_0000..0x10_0000).step_by(16) {
|
||||||
|
- let mapped = unsafe {
|
||||||
|
- common::physmap(
|
||||||
|
- page_base,
|
||||||
|
- 16,
|
||||||
|
- common::Prot::RW,
|
||||||
|
- common::MemoryType::default(),
|
||||||
|
- )
|
||||||
|
- };
|
||||||
|
- if let Ok(virt) = mapped {
|
||||||
|
- let sig = unsafe { std::slice::from_raw_parts(virt as *const u8, 8) };
|
||||||
|
- if sig == b"RSD PTR " {
|
||||||
|
- log::info!("found RSDP at physical {:#x}", page_base);
|
||||||
|
- found = Some(page_base);
|
||||||
|
- break;
|
||||||
|
- }
|
||||||
|
- let _ = unsafe { libredox::call::munmap(virt as *mut (), 16) };
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
- found.ok_or("RSDP not found in BIOS area (0xE0000-0xFFFFF)")?
|
||||||
|
- }
|
||||||
|
- };
|
||||||
|
+ let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
|
||||||
|
@@ -444,3 +428,3 @@ impl AcpiContext {
|
||||||
|
- interpreter
|
||||||
|
- .release_global_lock()
|
||||||
|
- .expect("Failed to release GIL!"); //TODO: check if this should panic
|
||||||
|
+ if let Err(e) = interpreter.release_global_lock() {
|
||||||
|
+ log::error!("Failed to release AML global lock: {:?}", e);
|
||||||
|
+ }
|
||||||
|
@@ -462,4 +446,8 @@ impl AcpiContext {
|
||||||
|
- .map(|physaddr| {
|
||||||
|
- let physaddr: usize = physaddr
|
||||||
|
- .try_into()
|
||||||
|
- .expect("expected ACPI addresses to be compatible with the current word size");
|
||||||
|
+ .filter_map(|physaddr| {
|
||||||
|
+ let physaddr: usize = match physaddr.try_into() {
|
||||||
|
+ Ok(addr) => addr,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::error!("expected ACPI addresses to be compatible with the current word size: {}", e);
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -469 +457,7 @@ impl AcpiContext {
|
||||||
|
- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT")
|
||||||
|
+ match Sdt::load_from_physical(physaddr) {
|
||||||
|
+ Ok(sdt) => Some(sdt),
|
||||||
|
+ Err(error) => {
|
||||||
|
+ log::error!("failed to load physical SDT at {:#x}: {}", physaddr, error);
|
||||||
|
+ None
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
@@ -865,3 +859,4 @@ impl Fadt {
|
||||||
|
- Err(plain::Error::BadAlignment) => unreachable!(
|
||||||
|
- "plain::from_bytes reported bad alignment, but FadtAcpi2Struct is #[repr(packed)]"
|
||||||
|
- ),
|
||||||
|
+ Err(plain::Error::BadAlignment) => {
|
||||||
|
+ log::error!("plain::from_bytes reported bad alignment for FadtAcpi2Struct, but it is #[repr(packed)]");
|
||||||
|
+ None
|
||||||
|
+ }
|
||||||
|
@@ -876,2 +871,2 @@ impl Deref for Fadt {
|
||||||
|
- plain::from_bytes::<FadtStruct>(&self.0 .0)
|
||||||
|
- .expect("expected FADT struct to already be validated in Deref impl")
|
||||||
|
+ // SAFETY: Fadt::new validated the slice length and FadtStruct is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0 .0.as_ptr() as *const FadtStruct) }
|
||||||
|
@@ -890,3 +885,7 @@ impl Fadt {
|
||||||
|
- let fadt_sdt = context
|
||||||
|
- .take_single_sdt(*b"FACP")
|
||||||
|
- .expect("expected ACPI to always have a FADT");
|
||||||
|
+ let fadt_sdt = match context.take_single_sdt(*b"FACP") {
|
||||||
|
+ Some(sdt) => sdt,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("expected ACPI to always have a FADT");
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -903,4 +902,2 @@ impl Fadt {
|
||||||
|
- Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| {
|
||||||
|
- usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize")
|
||||||
|
- }),
|
||||||
|
- None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"),
|
||||||
|
+ Some(fadt2) => fadt2.x_dsdt as usize,
|
||||||
|
+ None => fadt.dsdt as usize,
|
||||||
|
diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs
|
||||||
|
index ed27849b..b5cb96f2 100644
|
||||||
|
--- a/drivers/acpid/src/acpi/dmar/mod.rs
|
||||||
|
+++ b/drivers/acpid/src/acpi/dmar/mod.rs
|
||||||
|
@@ -47,2 +47,2 @@ impl Deref for Dmar {
|
||||||
|
- plain::from_bytes(self.0.as_slice())
|
||||||
|
- .expect("expected Dmar struct to already have checked the length, and alignment issues should be impossible due to #[repr(packed)]")
|
||||||
|
+ // SAFETY: Dmar::new validated the slice length and DmarStruct is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_slice().as_ptr() as *const DmarStruct) }
|
||||||
|
@@ -78,2 +78 @@ impl Dmar {
|
||||||
|
- let drhd = dmar_drhd.map();
|
||||||
|
-
|
||||||
|
+ if let Some(drhd) = dmar_drhd.map() {
|
||||||
|
@@ -86,0 +86 @@ impl Dmar {
|
||||||
|
+ }
|
||||||
|
@@ -153,2 +153,4 @@ impl DeviceScope {
|
||||||
|
- let header = plain::from_bytes::<DeviceScopeHeader>(header_bytes)
|
||||||
|
- .expect("length already checked, and alignment 1 (#[repr(packed)] should suffice");
|
||||||
|
+ let header = match plain::from_bytes::<DeviceScopeHeader>(header_bytes) {
|
||||||
|
+ Ok(h) => h,
|
||||||
|
+ Err(_) => return None,
|
||||||
|
+ };
|
||||||
|
@@ -180,2 +182,2 @@ impl Deref for DeviceScope {
|
||||||
|
- plain::from_bytes(&self.0)
|
||||||
|
- .expect("expected length to be sufficient, and alignment (due to #[repr(packed)]")
|
||||||
|
+ // SAFETY: DeviceScope::try_new validated the slice length and DeviceScopeHeader is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_ptr() as *const DeviceScopeHeader) }
|
||||||
|
@@ -203,2 +205,2 @@ impl DmarDrhd {
|
||||||
|
- pub fn map(&self) -> DrhdPage {
|
||||||
|
- let base = usize::try_from(self.base).expect("expected u64 to fit within usize");
|
||||||
|
+ pub fn map(&self) -> Option<DrhdPage> {
|
||||||
|
+ let base = usize::try_from(self.base).ok()?;
|
||||||
|
@@ -206 +208 @@ impl DmarDrhd {
|
||||||
|
- DrhdPage::map(base).expect("failed to map DRHD registers")
|
||||||
|
+ DrhdPage::map(base).ok()
|
||||||
|
@@ -213,2 +215,2 @@ impl Deref for DmarDrhd {
|
||||||
|
- plain::from_bytes::<DmarDrhdHeader>(&self.0[..mem::size_of::<DmarDrhdHeader>()])
|
||||||
|
- .expect("length is already checked, and alignment 1 (#[repr(packed)] should suffice")
|
||||||
|
+ // SAFETY: DmarDrhd::try_new validated the slice length and DmarDrhdHeader is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_ptr() as *const DmarDrhdHeader) }
|
||||||
|
@@ -255,2 +257,2 @@ impl Deref for DmarRmrr {
|
||||||
|
- plain::from_bytes(&self.0[..mem::size_of::<DmarRmrrHeader>()])
|
||||||
|
- .expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||||
|
+ // SAFETY: DmarRmrr::try_new validated the slice length and DmarRmrrHeader is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_ptr() as *const DmarRmrrHeader) }
|
||||||
|
@@ -296,2 +298,2 @@ impl Deref for DmarAtsr {
|
||||||
|
- plain::from_bytes(&self.0[..mem::size_of::<DmarAtsrHeader>()])
|
||||||
|
- .expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||||
|
+ // SAFETY: DmarAtsr::try_new validated the slice length and DmarAtsrHeader is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_ptr() as *const DmarAtsrHeader) }
|
||||||
|
@@ -325,2 +327,4 @@ impl DmarRhsa {
|
||||||
|
- let this = plain::from_bytes(bytes)
|
||||||
|
- .expect("length is already checked, and alignment 1 should suffice (#[repr(packed)])");
|
||||||
|
+ let this = match plain::from_bytes(bytes) {
|
||||||
|
+ Ok(t) => t,
|
||||||
|
+ Err(_) => return None,
|
||||||
|
+ };
|
||||||
|
@@ -360,2 +364,2 @@ impl Deref for DmarAndd {
|
||||||
|
- plain::from_bytes(&self.0[..mem::size_of::<DmarAnddHeader>()])
|
||||||
|
- .expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||||
|
+ // SAFETY: DmarAndd::try_new validated the slice length and DmarAnddHeader is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_ptr() as *const DmarAnddHeader) }
|
||||||
|
@@ -403,2 +407,2 @@ impl Deref for DmarSatc {
|
||||||
|
- plain::from_bytes(&self.0[..mem::size_of::<DmarSatcHeader>()])
|
||||||
|
- .expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||||
|
+ // SAFETY: DmarSatc::try_new validated the slice length and DmarSatcHeader is #[repr(packed)].
|
||||||
|
+ unsafe { &*(self.0.as_ptr() as *const DmarSatcHeader) }
|
||||||
|
@@ -472,4 +476,2 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> {
|
||||||
|
- let type_bytes = <[u8; 2]>::try_from(type_bytes)
|
||||||
|
- .expect("expected a 2-byte slice to be convertible to [u8; 2]");
|
||||||
|
- let len_bytes = <[u8; 2]>::try_from(type_bytes)
|
||||||
|
- .expect("expected a 2-byte slice to be convertible to [u8; 2]");
|
||||||
|
+ let type_array = <[u8; 2]>::try_from(type_bytes).ok()?;
|
||||||
|
+ let len_array = <[u8; 2]>::try_from(len_bytes).ok()?;
|
||||||
|
@@ -477 +479,2 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> {
|
||||||
|
- let len = u16::from_ne_bytes(len_bytes) as usize;
|
||||||
|
+ let ty = u16::from_ne_bytes(type_array);
|
||||||
|
+ let len = u16::from_ne_bytes(len_array) as usize;
|
||||||
|
@@ -479,0 +483 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> {
|
||||||
|
+ log::warn!("DMAR entry header length {} is too small", len);
|
||||||
|
@@ -483,3 +486,0 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> {
|
||||||
|
- let ty = u16::from_ne_bytes(type_bytes);
|
||||||
|
-
|
||||||
|
-
|
||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index ea3cbaeb..0c1d4c72 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -32,3 +32,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
|
||||||
|
- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`")
|
||||||
|
- .into();
|
||||||
|
+ let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") {
|
||||||
|
+ Ok(data) => data.into(),
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {} — no ACPI", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -42 +47,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
|
||||||
|
+ let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) {
|
||||||
|
+ Ok(sdt) => sdt,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::error!("acpid: failed to parse [RX]SDT: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -68 +79,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
|
||||||
|
+ _ => {
|
||||||
|
+ log::error!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT, got {:?}", String::from_utf8_lossy(&sdt.signature));
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
@@ -87 +101,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
||||||
|
+ if let Err(e) = common::acquire_port_io_rights() {
|
||||||
|
+ log::error!("acpid: failed to set I/O privilege level to Ring 3: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
@@ -110 +127,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
|
||||||
|
+ if let Err(e) = libredox::call::setrens(0, 0) {
|
||||||
|
+ log::warn!("acpid: failed to enter null namespace: {} — continuing", e);
|
||||||
|
+ }
|
||||||
|
@@ -114,6 +133,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let Some(event) = event_queue
|
||||||
|
- .next()
|
||||||
|
- .transpose()
|
||||||
|
- .expect("acpid: failed to read event file")
|
||||||
|
- else {
|
||||||
|
- break;
|
||||||
|
+ let event = match event_queue.next().transpose() {
|
||||||
|
+ Ok(Some(e)) => e,
|
||||||
|
+ Ok(None) => break,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::error!("acpid: failed to read event file: {} — continuing", e);
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
@@ -124,6 +144,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- match handler
|
||||||
|
- .process_requests_nonblocking(&mut scheme)
|
||||||
|
- .expect("acpid: failed to process requests")
|
||||||
|
- {
|
||||||
|
- ControlFlow::Continue(()) => {}
|
||||||
|
- ControlFlow::Break(()) => break,
|
||||||
|
+ match handler.process_requests_nonblocking(&mut scheme) {
|
||||||
|
+ Ok(ControlFlow::Continue(())) => {}
|
||||||
|
+ Ok(ControlFlow::Break(())) => break,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::error!("acpid: failed to process requests: {} — continuing", e);
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
@@ -146 +167,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- unreachable!("System should have shut down before this is entered");
|
||||||
|
+ log::error!("System should have shut down before this was reached");
|
||||||
|
+ std::process::exit(1);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
diff --git a/init/src/main.rs b/init/src/main.rs
|
||||||
|
index 5891b808..b8720e81 100644
|
||||||
|
--- a/init/src/main.rs
|
||||||
|
+++ b/init/src/main.rs
|
||||||
|
@@ -167 +167 @@ fn main() {
|
||||||
|
- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()),
|
||||||
|
+ UnitId(entry.file_name().map(|n| n.to_str().map(|s| s.to_owned())).flatten().unwrap_or_default()),
|
||||||
|
@@ -174 +174,3 @@ fn main() {
|
||||||
|
- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
|
||||||
|
+ if let Err(err) = libredox::call::setrens(0, 0) {
|
||||||
|
+ init_warn(&format!("init: failed to enter null namespace: {} — continuing", err));
|
||||||
|
+ }
|
||||||
|
diff --git a/init/src/service.rs b/init/src/service.rs
|
||||||
|
index 10bb9d8a..970c0338 100644
|
||||||
|
--- a/init/src/service.rs
|
||||||
|
+++ b/init/src/service.rs
|
||||||
|
@@ -178,3 +178,11 @@ impl Service {
|
||||||
|
- let current_namespace_fd = libredox::call::getns().expect("TODO");
|
||||||
|
- libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
|
||||||
|
- .expect("TODO");
|
||||||
|
+ let current_namespace_fd = match libredox::call::getns() {
|
||||||
|
+ Ok(fd) => fd,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ init_error(&format!("failed to get namespace for {:?}: {}", command, err));
|
||||||
|
+ return Some(child.id());
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ if let Err(err) = libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) {
|
||||||
|
+ init_error(&format!("failed to register scheme {:?} for {:?}: {}", scheme, command, err));
|
||||||
|
+ return Some(child.id());
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
diff --git a/init.initfs.d/ramfs@.service b/init.initfs.d/ramfs@.service
|
||||||
|
index bb512c60..3c3ed97d 100644
|
||||||
|
--- a/init.initfs.d/ramfs@.service
|
||||||
|
+++ b/init.initfs.d/ramfs@.service
|
||||||
|
@@ -4 +4 @@ default_dependencies = false
|
||||||
|
-requires_weak = ["00_randd.service"]
|
||||||
|
+requires = ["00_randd.service"]
|
||||||
|
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
|
||||||
|
index 1bfc5c48..8db29281 100644
|
||||||
|
--- a/init/src/scheduler.rs
|
||||||
|
+++ b/init/src/scheduler.rs
|
||||||
|
@@ -120,2 +120,20 @@ if !hard_deps_met {
|
||||||
|
- init_warn(&format!("{}: hard dependency not met, skipping", job.unit.0));
|
||||||
|
- self.completed.insert(job.unit);
|
||||||
|
+ let unit = unit_store.unit(&job.unit);
|
||||||
|
+ let unit_id_str = job.unit.0.clone();
|
||||||
|
+ let all_deps_missing = unit.info.requires.iter().all(|dep| {
|
||||||
|
+ self.completed.contains(dep) || !unit_store.has_unit(dep)
|
||||||
|
+ });
|
||||||
|
+ if all_deps_missing {
|
||||||
|
+ init_warn(&format!("{}: hard dependency not met, skipping", unit_id_str));
|
||||||
|
+ self.completed.insert(job.unit);
|
||||||
|
+ continue 'a;
|
||||||
|
+ }
|
||||||
|
+ defer_count += 1;
|
||||||
|
+ self.pending.push_back(job);
|
||||||
|
+ if defer_count > self.pending.len() + self.completed.len() {
|
||||||
|
+ init_warn(&format!(
|
||||||
|
+ "{}: hard dependency not met after deferring, skipping",
|
||||||
|
+ unit_id_str
|
||||||
|
+ ));
|
||||||
|
+ self.completed.insert(UnitId(unit_id_str));
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
index 3e42d590..1f49a227 100644
|
||||||
|
--- a/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
@@ -27 +27,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue");
|
||||||
|
+ let event_queue = EventQueue::new().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to create event queue: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -36 +39,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- let socket = Socket::nonblock().expect("fbbootlogd: failed to create fbbootlog scheme");
|
||||||
|
+ let socket = Socket::nonblock().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to create fbbootlog scheme: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -47 +53,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- .expect("fbbootlogd: failed to subscribe to scheme events");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to subscribe to scheme events: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -55 +64,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- .expect("fbbootlogd: failed to subscribe to scheme events");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to subscribe to scheme events: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -60 +72,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- .expect("fbbootlogd: failed to create log fd");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to create log fd: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -67 +82,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- .expect("fbbootlogd: failed to open log/add_sink");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to open log/add_sink: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -70 +88,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- .expect("fbbootlogd: failed to send log fd to log scheme.");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to send log fd to log scheme: {}", e);
|
||||||
|
+ 0
|
||||||
|
+ });
|
||||||
|
@@ -77,2 +97,0 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- //libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace");
|
||||||
|
-
|
||||||
|
@@ -80 +99,2 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- match event.expect("fbbootlogd: failed to get event").user_data {
|
||||||
|
+ match event {
|
||||||
|
+ Ok(event) => match event.user_data {
|
||||||
|
@@ -82,6 +102,7 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- match handler
|
||||||
|
- .process_requests_nonblocking(&mut scheme)
|
||||||
|
- .expect("fbbootlogd: failed to process requests")
|
||||||
|
- {
|
||||||
|
- ControlFlow::Continue(()) => {}
|
||||||
|
- ControlFlow::Break(()) => break,
|
||||||
|
+ match handler.process_requests_nonblocking(&mut scheme) {
|
||||||
|
+ Ok(ControlFlow::Continue(())) => {}
|
||||||
|
+ Ok(ControlFlow::Break(())) => break,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("fbbootlogd: failed to process requests: {}", e);
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
@@ -93,7 +114,3 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- match scheme
|
||||||
|
- .input_handle
|
||||||
|
- .read_events(&mut events)
|
||||||
|
- .expect("fbbootlogd: error while reading events")
|
||||||
|
- {
|
||||||
|
- ConsumerHandleEvent::Events(&[]) => break,
|
||||||
|
- ConsumerHandleEvent::Events(events) => {
|
||||||
|
+ match scheme.input_handle.read_events(&mut events) {
|
||||||
|
+ Ok(ConsumerHandleEvent::Events(&[]) ) => break,
|
||||||
|
+ Ok(ConsumerHandleEvent::Events(events)) => {
|
||||||
|
@@ -104 +121 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- ConsumerHandleEvent::Handoff => {
|
||||||
|
+ Ok(ConsumerHandleEvent::Handoff) => {
|
||||||
|
@@ -107,0 +125,3 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("fbbootlogd: error while reading events: {}", e);
|
||||||
|
+ break;
|
||||||
|
@@ -111,0 +132,5 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
+ },
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("fbbootlogd: failed to get event: {}", e);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
index 08bd1805..67db5c54 100644
|
||||||
|
--- a/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
@@ -29 +29,4 @@ impl FbbootlogScheme {
|
||||||
|
- input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"),
|
||||||
|
+ input_handle: ConsumerHandle::bootlog_vt().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbbootlogd: failed to open vt: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }),
|
||||||
|
@@ -151 +154,3 @@ impl FbbootlogScheme {
|
||||||
|
- map.dirty_fb(total_damage).unwrap();
|
||||||
|
+ if let Err(e) = map.dirty_fb(total_damage) {
|
||||||
|
+ eprintln!("fbbootlogd: failed to sync framebuffer: {}", e);
|
||||||
|
+ }
|
||||||
|
@@ -245 +250,3 @@ impl SchemeSync for FbbootlogScheme {
|
||||||
|
- map.dirty_fb(damage).unwrap();
|
||||||
|
+ if let Err(e) = map.dirty_fb(damage) {
|
||||||
|
+ eprintln!("fbbootlogd: failed to sync framebuffer: {}", e);
|
||||||
|
+ }
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
index e8543583..9bc6000f 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/display.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
@@ -86 +86,3 @@ impl Display {
|
||||||
|
- map.dirty_fb(damage).unwrap();
|
||||||
|
+ if let Err(e) = map.dirty_fb(damage) {
|
||||||
|
+ log::error!("fbcond: failed to sync framebuffer: {}", e);
|
||||||
|
+ }
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/main.rs b/drivers/graphics/fbcond/src/main.rs
|
||||||
|
index d7d4abb8..45c04449 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/main.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/main.rs
|
||||||
|
@@ -24 +24,6 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- .map(|arg| arg.parse().expect("invalid vt number"))
|
||||||
|
+ .map(|arg| {
|
||||||
|
+ arg.parse().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbcond: invalid vt number: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ })
|
||||||
|
+ })
|
||||||
|
@@ -34 +39,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue");
|
||||||
|
+ let mut event_queue = EventQueue::new().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbcond: failed to create event queue: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -38 +46,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- let mut socket = Socket::nonblock().expect("fbcond: failed to create fbcon scheme");
|
||||||
|
+ let mut socket = Socket::nonblock().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbcond: failed to create fbcon scheme: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -45 +56,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- .expect("fbcond: failed to subscribe to scheme events");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("fbcond: failed to subscribe to scheme events: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -54,2 +67,0 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- // libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace");
|
||||||
|
-
|
||||||
|
@@ -71 +83,7 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- let event = event.expect("fbcond: failed to read event from event queue");
|
||||||
|
+ let event = match event {
|
||||||
|
+ Ok(event) => event,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::error!("fbcond: failed to read event from event queue: {}", e);
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -102 +120,4 @@ fn handle_event(
|
||||||
|
- Err(err) => panic!("fbcond: failed to read display scheme: {err}"),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::error!("fbcond: failed to read display scheme: {err}");
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
@@ -116 +137,4 @@ fn handle_event(
|
||||||
|
- .expect("fbcond: failed to write responses to fbcon scheme");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("fbcond: failed to write responses to fbcon scheme: {}", e);
|
||||||
|
+ false
|
||||||
|
+ });
|
||||||
|
@@ -130 +154,4 @@ fn handle_event(
|
||||||
|
- .expect("fbcond: failed to write responses to fbcon scheme");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("fbcond: failed to write responses to fbcon scheme: {}", e);
|
||||||
|
+ false
|
||||||
|
+ });
|
||||||
|
@@ -138 +165,4 @@ fn handle_event(
|
||||||
|
- .expect("fbcond: failed to write responses to fbcon scheme");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("fbcond: failed to write responses to fbcon scheme: {}", e);
|
||||||
|
+ false
|
||||||
|
+ });
|
||||||
|
@@ -146 +176,4 @@ fn handle_event(
|
||||||
|
- .expect("fbcond: failed to write scheme");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("fbcond: failed to write scheme: {}", e);
|
||||||
|
+ false
|
||||||
|
+ });
|
||||||
|
@@ -162 +195,4 @@ fn handle_event(
|
||||||
|
- .expect("vesad: failed to write display scheme");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("vesad: failed to write display scheme: {}", e);
|
||||||
|
+ false
|
||||||
|
+ });
|
||||||
|
@@ -169 +205,4 @@ fn handle_event(
|
||||||
|
- let vt = scheme.vts.get_mut(&vt_i).unwrap();
|
||||||
|
+ let Some(vt) = scheme.vts.get_mut(&vt_i) else {
|
||||||
|
+ log::error!("fbcond: missing vt {:?}", vt_i);
|
||||||
|
+ return;
|
||||||
|
+ };
|
||||||
|
@@ -173,6 +212,9 @@ fn handle_event(
|
||||||
|
- match vt
|
||||||
|
- .display
|
||||||
|
- .input_handle
|
||||||
|
- .read_events(&mut events)
|
||||||
|
- .expect("fbcond: Error while reading events")
|
||||||
|
- {
|
||||||
|
+ let read_events = match vt.display.input_handle.read_events(&mut events) {
|
||||||
|
+ Ok(events) => events,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::error!("fbcond: Error while reading events: {}", e);
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ match read_events {
|
||||||
|
@@ -196,3 +238,4 @@ fn handle_event(
|
||||||
|
- let (op, caller) = blocked
|
||||||
|
- .get_mut(i)
|
||||||
|
- .expect("vesad: Failed to get blocked request");
|
||||||
|
+ let Some((op, caller)) = blocked.get_mut(i) else {
|
||||||
|
+ log::error!("vesad: Failed to get blocked request");
|
||||||
|
+ return;
|
||||||
|
+ };
|
||||||
|
@@ -222 +265,4 @@ fn handle_event(
|
||||||
|
- .expect("vesad: failed to write display scheme");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("vesad: failed to write display scheme: {}", e);
|
||||||
|
+ false
|
||||||
|
+ });
|
||||||
|
@@ -247 +293,4 @@ fn handle_event(
|
||||||
|
- .expect("fbcond: failed to write display event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("fbcond: failed to write display event: {}", e);
|
||||||
|
+ false
|
||||||
|
+ });
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/scheme.rs b/drivers/graphics/fbcond/src/scheme.rs
|
||||||
|
index b31ee2e3..2cd98086 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/scheme.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/scheme.rs
|
||||||
|
@@ -54 +54,4 @@ impl FbconScheme {
|
||||||
|
- let display = Display::open_new_vt().expect("Failed to open display for vt");
|
||||||
|
+ let display = Display::open_new_vt().unwrap_or_else(|e| {
|
||||||
|
+ log::error!("fbcond: failed to open display for vt: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -61 +64,4 @@ impl FbconScheme {
|
||||||
|
- .expect("Failed to subscribe to input events for vt");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("fbcond: failed to subscribe to input events for vt: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -140 +146 @@ impl SchemeSync for FbconScheme {
|
||||||
|
- write!(w, "{}", handle.vt_i.0).unwrap();
|
||||||
|
+ write!(w, "{}", handle.vt_i.0).map_err(|_| Error::new(ENOENT))?;
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs
|
||||||
|
index 8c85bf77..f9992d2b 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/text.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/text.rs
|
||||||
|
@@ -122 +122,4 @@ impl TextScreen {
|
||||||
|
- buf[i] = self.input.pop_front().unwrap();
|
||||||
|
+ buf[i] = match self.input.pop_front() {
|
||||||
|
+ Some(v) => v,
|
||||||
|
+ None => break,
|
||||||
|
+ };
|
||||||
|
diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs
|
||||||
|
index 86f903bf..569c73b9 100644
|
||||||
|
--- a/drivers/input/ps2d/src/main.rs
|
||||||
|
+++ b/drivers/input/ps2d/src/main.rs
|
||||||
|
@@ -32 +32,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- acquire_port_io_rights().expect("ps2d: failed to get I/O permission");
|
||||||
|
+ if let Err(e) = acquire_port_io_rights() {
|
||||||
|
+ eprintln!("ps2d: failed to get I/O permission: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ }
|
||||||
|
@@ -34,2 +37,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input");
|
||||||
|
- let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input");
|
||||||
|
+ let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to open keyboard input: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
+ let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to open mouse input: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -46 +55,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- EventQueue::new().expect("ps2d: failed to create event queue");
|
||||||
|
+ EventQueue::new().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to create event queue: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -53 +65,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .expect("ps2d: failed to open /scheme/serio/0");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to open /scheme/serio/0: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -61 +76,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .unwrap();
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to subscribe to keyboard events: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -68 +86,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .expect("ps2d: failed to open /scheme/serio/1");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to open /scheme/serio/1: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -76 +97,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .unwrap();
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to subscribe to mouse events: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -83 +107,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .expect("ps2d: failed to open /scheme/time");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to open /scheme/time: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -91 +118,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .unwrap();
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("ps2d: failed to subscribe to time events: {}", e);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -93 +123,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- libredox::call::setrens(0, 0).expect("ps2d: failed to enter null namespace");
|
||||||
|
+ if let Err(e) = libredox::call::setrens(0, 0) {
|
||||||
|
+ eprintln!("ps2d: failed to enter null namespace: {}", e);
|
||||||
|
+ }
|
||||||
|
@@ -100 +132,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {
|
||||||
|
+ for event in event_queue.map(|e| {
|
||||||
|
+ e.unwrap_or_else(|e2| {
|
||||||
|
+ eprintln!("ps2d: event read error: {}", e2);
|
||||||
|
+ process::exit(1);
|
||||||
|
+ })
|
||||||
|
+ .user_data
|
||||||
|
+ }) {
|
||||||
|
diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs
|
||||||
|
index 8f5832f6..3e37f344 100644
|
||||||
|
--- a/drivers/input/ps2d/src/state.rs
|
||||||
|
+++ b/drivers/input/ps2d/src/state.rs
|
||||||
|
@@ -31,2 +31,2 @@ fn timespec_from_duration(duration: Duration) -> TimeSpec {
|
||||||
|
- tv_sec: duration.as_secs().try_into().unwrap(),
|
||||||
|
- tv_nsec: duration.subsec_nanos().try_into().unwrap(),
|
||||||
|
+ tv_sec: duration.as_secs().try_into().unwrap_or(i64::MAX),
|
||||||
|
+ tv_nsec: duration.subsec_nanos().try_into().unwrap_or(i32::MAX),
|
||||||
|
@@ -38,2 +38,2 @@ fn duration_from_timespec(timespec: TimeSpec) -> Duration {
|
||||||
|
- timespec.tv_sec.try_into().unwrap(),
|
||||||
|
- timespec.tv_nsec.try_into().unwrap(),
|
||||||
|
+ timespec.tv_sec.try_into().unwrap_or(u64::MAX),
|
||||||
|
+ timespec.tv_nsec.try_into().unwrap_or(u32::MAX),
|
||||||
|
@@ -320 +320,3 @@ impl Ps2d {
|
||||||
|
- .expect("failed to write key event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write key event: {}", e);
|
||||||
|
+ });
|
||||||
|
@@ -350 +352,3 @@ impl Ps2d {
|
||||||
|
- .expect("ps2d: failed to write mouse event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write mouse event: {}", e);
|
||||||
|
+ });
|
||||||
|
@@ -360 +364,3 @@ impl Ps2d {
|
||||||
|
- .expect("ps2d: failed to write mouse event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write mouse event: {}", e);
|
||||||
|
+ });
|
||||||
|
@@ -373 +379,3 @@ impl Ps2d {
|
||||||
|
- .expect("ps2d: failed to write scroll event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write scroll event: {}", e);
|
||||||
|
+ });
|
||||||
|
@@ -395 +403,3 @@ impl Ps2d {
|
||||||
|
- .expect("ps2d: failed to write button event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write button event: {}", e);
|
||||||
|
+ });
|
||||||
|
@@ -494 +504,3 @@ impl Ps2d {
|
||||||
|
- .expect("ps2d: failed to write mouse event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write mouse event: {}", e);
|
||||||
|
+ });
|
||||||
|
@@ -500 +512,3 @@ impl Ps2d {
|
||||||
|
- .expect("ps2d: failed to write scroll event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write scroll event: {}", e);
|
||||||
|
+ });
|
||||||
|
@@ -526 +540,3 @@ impl Ps2d {
|
||||||
|
- .expect("ps2d: failed to write button event");
|
||||||
|
+ .unwrap_or_else(|e| {
|
||||||
|
+ log::error!("ps2d: failed to write button event: {}", e);
|
||||||
|
+ });
|
||||||
|
diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
|
||||||
|
index 07aa943e..5b888a7c 100644
|
||||||
|
--- a/drivers/inputd/src/main.rs
|
||||||
|
+++ b/drivers/inputd/src/main.rs
|
||||||
|
@@ -277 +277 @@ impl SchemeSync for InputScheme {
|
||||||
|
- write!(w, "{vt}").unwrap();
|
||||||
|
+ write!(w, "{vt}").map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
@@ -441 +441,4 @@ impl SchemeSync for InputScheme {
|
||||||
|
- assert!(matches!(handle, Handle::Producer));
|
||||||
|
+ if !matches!(handle, Handle::Producer) {
|
||||||
|
+ log::error!("inputd: unexpected non-producer handle in write");
|
||||||
|
+ return Err(SysError::new(EINVAL));
|
||||||
|
+ }
|
||||||
|
@@ -508,2 +511,2 @@ impl SchemeSync for InputScheme {
|
||||||
|
- match self.handles.remove(id).unwrap() {
|
||||||
|
- Handle::Consumer { vt, .. } => {
|
||||||
|
+ match self.handles.remove(id) {
|
||||||
|
+ Some(Handle::Consumer { vt, .. }) => {
|
||||||
|
@@ -519 +522,4 @@ impl SchemeSync for InputScheme {
|
||||||
|
- _ => {}
|
||||||
|
+ Some(_) => {}
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("inputd: missing handle on close");
|
||||||
|
+ }
|
||||||
|
@@ -592 +598,4 @@ fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
- deamon(daemon).unwrap();
|
||||||
|
+ if let Err(e) = deamon(daemon) {
|
||||||
|
+ log::error!("inputd: daemon failed: {:?}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
@@ -611 +620,7 @@ fn main() {
|
||||||
|
- let vt = args.next().unwrap().parse::<usize>().unwrap();
|
||||||
|
+ let vt = args
|
||||||
|
+ .next()
|
||||||
|
+ .and_then(|a| a.parse::<usize>().ok())
|
||||||
|
+ .unwrap_or_else(|| {
|
||||||
|
+ eprintln!("inputd: -A requires a VT number");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -614,4 +629,8 @@ fn main() {
|
||||||
|
- inputd::ControlHandle::new().expect("inputd: failed to open control handle");
|
||||||
|
- handle
|
||||||
|
- .activate_vt(vt)
|
||||||
|
- .expect("inputd: failed to activate VT");
|
||||||
|
+ inputd::ControlHandle::new().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("inputd: failed to open control handle: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
+ handle.activate_vt(vt).unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("inputd: failed to activate VT: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -634,4 +653,8 @@ fn main() {
|
||||||
|
- inputd::ControlHandle::new().expect("inputd: failed to open control handle");
|
||||||
|
- handle
|
||||||
|
- .activate_keymap(vt as usize)
|
||||||
|
- .expect("inputd: failed to activate keymap");
|
||||||
|
+ inputd::ControlHandle::new().unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("inputd: failed to open control handle: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
+ handle.activate_keymap(vt as usize).unwrap_or_else(|e| {
|
||||||
|
+ eprintln!("inputd: failed to activate keymap: {}", e);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ });
|
||||||
|
@@ -650 +673,4 @@ fn main() {
|
||||||
|
- _ => panic!("inputd: invalid argument: {}", val),
|
||||||
|
+ _ => {
|
||||||
|
+ eprintln!("inputd: invalid argument: {}", val);
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
diff --git a/init.initfs.d/50_rootfs.service b/init.initfs.d/50_rootfs.service
|
||||||
|
index c2d8e477..db7ba429 100644
|
||||||
|
--- a/init.initfs.d/50_rootfs.service
|
||||||
|
+++ b/init.initfs.d/50_rootfs.service
|
||||||
|
@@ -3 +3 @@ description = "Rootfs"
|
||||||
|
-requires_weak = ["40_drivers.target"]
|
||||||
|
+requires = ["40_drivers.target"]
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
||||||
|
index 8ef6ab0e..a6276f9e 100644
|
||||||
|
--- a/drivers/acpid/src/acpi.rs
|
||||||
|
+++ b/drivers/acpid/src/acpi.rs
|
||||||
|
@@ -389,0 +390,44 @@ impl From<AmlError> for AmlEvalError {
|
||||||
|
+/// Cached S5 (soft-off) state derived from FADT and AML \_S5 package.
|
||||||
|
+///
|
||||||
|
+/// Derived once at startup (or on first shutdown if AML was not ready at init)
|
||||||
|
+/// and reused for all subsequent shutdown attempts, eliminating redundant AML
|
||||||
|
+/// namespace lookups on the critical shutdown path.
|
||||||
|
+#[derive(Clone, Debug)]
|
||||||
|
+pub struct S5State {
|
||||||
|
+ pub slp_typa: u16,
|
||||||
|
+ pub slp_typb: u16,
|
||||||
|
+ pub pm1a_port: u16,
|
||||||
|
+ pub pm1b_port: u16,
|
||||||
|
+ pub derived_at: &'static str,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Errors that can occur when deriving or executing the S5 shutdown sequence.
|
||||||
|
+#[derive(Debug, Error)]
|
||||||
|
+pub enum ShutdownError {
|
||||||
|
+ #[error("FADT not available — cannot determine shutdown parameters")]
|
||||||
|
+ MissingFadt,
|
||||||
|
+ #[error("PM1a control block address is zero — ACPI shutdown unavailable")]
|
||||||
|
+ Pm1aZero,
|
||||||
|
+ #[error("AML interpreter not initialized — cannot look up \\_S5")]
|
||||||
|
+ AmlNotReady,
|
||||||
|
+ #[error("\\_S5 not found in AML namespace")]
|
||||||
|
+ S5NotFound,
|
||||||
|
+ #[error("\\_S5 is not a Package object")]
|
||||||
|
+ S5NotPackage,
|
||||||
|
+ #[error("SLP_TYP value in \\_S5 is not an Integer")]
|
||||||
|
+ SlpTypNotInteger,
|
||||||
|
+ #[error("PM1a control write failed")]
|
||||||
|
+ S5WriteFailed,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Result of a shutdown attempt.
|
||||||
|
+#[derive(Debug)]
|
||||||
|
+pub enum ShutdownResult {
|
||||||
|
+ /// Shutdown sequence completed (machine should power off).
|
||||||
|
+ Ok,
|
||||||
|
+ /// ACPI shutdown failed; fell back to keyboard controller reset.
|
||||||
|
+ FallbackReset,
|
||||||
|
+ /// Shutdown could not proceed due to a deterministic error.
|
||||||
|
+ Err(ShutdownError),
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
@@ -396,0 +441,2 @@ pub struct AcpiContext {
|
||||||
|
+ s5_state: RwLock<Option<S5State>>,
|
||||||
|
+
|
||||||
|
@@ -476,0 +523,2 @@ impl AcpiContext {
|
||||||
|
+ s5_state: RwLock::new(None),
|
||||||
|
+
|
||||||
|
@@ -490,0 +539,21 @@ impl AcpiContext {
|
||||||
|
+ // Trigger AML interpreter initialization so we can derive S5 state early.
|
||||||
|
+ // If AML init fails, S5 derivation will fall back to "shutdown_fallback" at
|
||||||
|
+ // shutdown time.
|
||||||
|
+ {
|
||||||
|
+ let mut symbols = this.aml_symbols.write();
|
||||||
|
+ if symbols.aml_context.is_none() {
|
||||||
|
+ if let Err(e) = symbols.init() {
|
||||||
|
+ log::warn!("ACPI S5: AML init at startup failed: {} — will derive at shutdown", e);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ match this.derive_s5_state("register_pci") {
|
||||||
|
+ Ok(_) => {}
|
||||||
|
+ Err(ShutdownError::AmlNotReady) => {
|
||||||
|
+ log::info!("ACPI S5: AML not ready at init — will derive at shutdown");
|
||||||
|
+ }
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("ACPI S5: early derivation failed: {} — will derive at shutdown", e);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
@@ -592,16 +661,10 @@ impl AcpiContext {
|
||||||
|
- /// Set Power State
|
||||||
|
- /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf
|
||||||
|
- /// - search for PM1a
|
||||||
|
- /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details
|
||||||
|
- pub fn set_global_s_state(&self, state: u8) {
|
||||||
|
- if state != 5 {
|
||||||
|
- return;
|
||||||
|
- }
|
||||||
|
- let fadt = match self.fadt() {
|
||||||
|
- Some(fadt) => fadt,
|
||||||
|
- None => {
|
||||||
|
- log::error!("Cannot set global S-state due to missing FADT.");
|
||||||
|
- return;
|
||||||
|
- }
|
||||||
|
- };
|
||||||
|
-
|
||||||
|
+ /// Derive the S5 (soft-off) state from FADT and AML \_S5 package.
|
||||||
|
+///
|
||||||
|
+/// Reads PM1a/PM1b control block addresses from the FADT and the SLP_TYP
|
||||||
|
+/// values from the AML `\_S5` package, then caches the result. Subsequent
|
||||||
|
+/// calls return the cached value without re-parsing AML.
|
||||||
|
+///
|
||||||
|
+/// `derived_at` is a log marker indicating when this derivation occurred
|
||||||
|
+/// (e.g. "register_pci", "shutdown_fallback").
|
||||||
|
+ pub fn derive_s5_state(&self, derived_at: &'static str) -> Result<S5State, ShutdownError> {
|
||||||
|
+ let fadt = self.fadt().ok_or(ShutdownError::MissingFadt)?;
|
||||||
|
@@ -612,5 +675 @@ impl AcpiContext {
|
||||||
|
- log::error!("PM1a control block is zero - ACPI shutdown unavailable");
|
||||||
|
- log::warn!("Falling back to keyboard controller reset");
|
||||||
|
- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
- Pio::<u8>::new(0x64u16).write(0xFEu8);
|
||||||
|
- return;
|
||||||
|
+ return Err(ShutdownError::Pm1aZero);
|
||||||
|
@@ -619,2 +677,0 @@ impl AcpiContext {
|
||||||
|
- let mut val = 1u16 << 13;
|
||||||
|
-
|
||||||
|
@@ -622,4 +679,4 @@ impl AcpiContext {
|
||||||
|
- let s5_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") {
|
||||||
|
- Ok(n) => n,
|
||||||
|
- Err(e) => { log::error!("\\_S5 AML name error: {:?}", e); return; }
|
||||||
|
- };
|
||||||
|
+ let aml_context = aml_symbols
|
||||||
|
+ .aml_context
|
||||||
|
+ .as_ref()
|
||||||
|
+ .ok_or(ShutdownError::AmlNotReady)?;
|
||||||
|
@@ -627,7 +684,8 @@ impl AcpiContext {
|
||||||
|
- let s5 = match &aml_symbols.aml_context {
|
||||||
|
- Some(ctx) => match ctx.namespace.lock().get(s5_name) {
|
||||||
|
- Ok(s) => s,
|
||||||
|
- Err(e) => { log::error!("\\_S5 not found: {:?}", e); return; }
|
||||||
|
- },
|
||||||
|
- None => { log::error!("AML context not initialized"); return; }
|
||||||
|
- };
|
||||||
|
+ let s5_name = acpi::aml::namespace::AmlName::from_str("\\_S5")
|
||||||
|
+ .map_err(|_| ShutdownError::S5NotFound)?;
|
||||||
|
+
|
||||||
|
+ let s5 = aml_context
|
||||||
|
+ .namespace
|
||||||
|
+ .lock()
|
||||||
|
+ .get(s5_name)
|
||||||
|
+ .map_err(|_| ShutdownError::S5NotFound)?;
|
||||||
|
@@ -637 +695 @@ impl AcpiContext {
|
||||||
|
- _ => { log::error!("\\_S5 is not a package"); return; }
|
||||||
|
+ _ => return Err(ShutdownError::S5NotPackage),
|
||||||
|
@@ -642 +700 @@ impl AcpiContext {
|
||||||
|
- _ => { log::error!("SLP_TYPa is not an integer"); return; }
|
||||||
|
+ _ => return Err(ShutdownError::SlpTypNotInteger),
|
||||||
|
@@ -647 +705,32 @@ impl AcpiContext {
|
||||||
|
- _ => 0u16
|
||||||
|
+ _ => 0u16,
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ 0u16
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ let state = S5State {
|
||||||
|
+ slp_typa,
|
||||||
|
+ slp_typb,
|
||||||
|
+ pm1a_port,
|
||||||
|
+ pm1b_port,
|
||||||
|
+ derived_at,
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ log::info!(
|
||||||
|
+ "ACPI S5: derived at={}, SLP_TYPa=0x{:X}, SLP_TYPb=0x{:X}, PM1a=0x{:04X}, PM1b=0x{:04X}",
|
||||||
|
+ derived_at, slp_typa, slp_typb, pm1a_port, pm1b_port
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ drop(aml_symbols);
|
||||||
|
+ *self.s5_state.write() = Some(state.clone());
|
||||||
|
+
|
||||||
|
+ Ok(state)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Set Power State
|
||||||
|
+ /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf
|
||||||
|
+ /// - search for PM1a
|
||||||
|
+ /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details
|
||||||
|
+ pub fn set_global_s_state(&self, state: u8) -> ShutdownResult {
|
||||||
|
+ if state != 5 {
|
||||||
|
+ return ShutdownResult::Ok;
|
||||||
|
@@ -649 +737,0 @@ impl AcpiContext {
|
||||||
|
- } else { 0u16 };
|
||||||
|
@@ -651 +739,26 @@ impl AcpiContext {
|
||||||
|
- val |= slp_typa & 0x1FFF;
|
||||||
|
+ let s5 = match self.s5_state.read().as_ref() {
|
||||||
|
+ Some(cached) => cached.clone(),
|
||||||
|
+ None => match self.derive_s5_state("shutdown_fallback") {
|
||||||
|
+ Ok(s5) => s5,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::error!("ACPI S5 derivation failed: {}", e);
|
||||||
|
+ if matches!(e, ShutdownError::Pm1aZero | ShutdownError::MissingFadt) {
|
||||||
|
+ log::warn!("Falling back to keyboard controller reset");
|
||||||
|
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
+ Pio::<u8>::new(0x64u16).write(0xFEu8);
|
||||||
|
+ return ShutdownResult::FallbackReset;
|
||||||
|
+ }
|
||||||
|
+ return ShutdownResult::Err(e);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ if s5.pm1a_port == 0 {
|
||||||
|
+ log::error!("ACPI S5: cached PM1a port is zero — shutdown unavailable");
|
||||||
|
+ log::warn!("Falling back to keyboard controller reset");
|
||||||
|
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
+ Pio::<u8>::new(0x64u16).write(0xFEu8);
|
||||||
|
+ return ShutdownResult::FallbackReset;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let mut val = (1u16 << 13) | (s5.slp_typa & 0x1FFF);
|
||||||
|
@@ -655,2 +768,4 @@ impl AcpiContext {
|
||||||
|
- log::info!("ACPI shutdown: PM1a=0x{:04X} val=0x{:04X} SLP_TYPa=0x{:X} SLP_TYPb=0x{:X}",
|
||||||
|
- pm1a_port, val, slp_typa, slp_typb);
|
||||||
|
+ log::info!(
|
||||||
|
+ "ACPI shutdown: writing PM1a=0x{:04X} val=0x{:04X} (SLP_TYPa=0x{:X}, SLP_TYPb=0x{:X})",
|
||||||
|
+ s5.pm1a_port, val, s5.slp_typa, s5.slp_typb
|
||||||
|
+ );
|
||||||
|
@@ -658 +773 @@ impl AcpiContext {
|
||||||
|
- let mut pio = Pio::<u16>::new(pm1a_port);
|
||||||
|
+ let mut pio = Pio::<u16>::new(s5.pm1a_port);
|
||||||
|
@@ -663,3 +778,2 @@ impl AcpiContext {
|
||||||
|
- log::warn!("ACPI PM1a shutdown did not power off - retry with PM1b");
|
||||||
|
- val |= slp_typb & 0x1FFF;
|
||||||
|
- val |= 1u16 << 13;
|
||||||
|
+ log::warn!("ACPI PM1a shutdown did not power off — retrying with PM1b");
|
||||||
|
+ val = (1u16 << 13) | (s5.slp_typb & 0x1FFF);
|
||||||
|
@@ -668,2 +782,2 @@ impl AcpiContext {
|
||||||
|
- if pm1b_port != 0 {
|
||||||
|
- let mut pio_b = Pio::<u16>::new(pm1b_port);
|
||||||
|
+ if s5.pm1b_port != 0 {
|
||||||
|
+ let mut pio_b = Pio::<u16>::new(s5.pm1b_port);
|
||||||
|
@@ -671 +785 @@ impl AcpiContext {
|
||||||
|
- log::info!("ACPI shutdown: also wrote PM1b=0x{:04X}", pm1b_port);
|
||||||
|
+ log::info!("ACPI shutdown: also wrote PM1b=0x{:04X}", s5.pm1b_port);
|
||||||
|
@@ -675 +789 @@ impl AcpiContext {
|
||||||
|
- log::error!("ACPI shutdown failed - falling back to keyboard controller reset");
|
||||||
|
+ log::error!("ACPI shutdown failed — falling back to keyboard controller reset");
|
||||||
|
@@ -676,0 +791 @@ impl AcpiContext {
|
||||||
|
+ return ShutdownResult::FallbackReset;
|
||||||
|
@@ -681,0 +797 @@ impl AcpiContext {
|
||||||
|
+ ShutdownResult::Err(ShutdownError::S5WriteFailed)
|
||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index 0c1d4c72..1d242b06 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -165 +165,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- acpi_context.set_global_s_state(5);
|
||||||
|
+ let result = acpi_context.set_global_s_state(5);
|
||||||
|
+ log::info!("ACPI shutdown result: {:?}", result);
|
||||||
@@ -0,0 +1,519 @@
|
|||||||
|
diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
index 9bc6000f..c315bbe9 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/display.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
@@ -5,9 +5,94 @@ use graphics_ipc::V2GraphicsHandle;
|
||||||
|
use inputd::ConsumerHandle;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
+use common::{MemoryType, PhysBorrowed, Prot};
|
||||||
|
+
|
||||||
|
+pub struct DirectFbMap {
|
||||||
|
+ fb_mem: PhysBorrowed,
|
||||||
|
+ width: usize,
|
||||||
|
+ height: usize,
|
||||||
|
+ stride: usize,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl DirectFbMap {
|
||||||
|
+ pub fn try_new() -> Option<Self> {
|
||||||
|
+ let addr = std::env::var("FRAMEBUFFER_ADDR").ok()?;
|
||||||
|
+ let width_str = std::env::var("FRAMEBUFFER_WIDTH").ok()?;
|
||||||
|
+ let height_str = std::env::var("FRAMEBUFFER_HEIGHT").ok()?;
|
||||||
|
+ let stride_str = std::env::var("FRAMEBUFFER_STRIDE").ok()?;
|
||||||
|
+
|
||||||
|
+ let phys = usize::from_str_radix(&addr, 16).ok()?;
|
||||||
|
+ let width = usize::from_str_radix(&width_str, 16).ok()?;
|
||||||
|
+ let height = usize::from_str_radix(&height_str, 16).ok()?;
|
||||||
|
+ let stride = usize::from_str_radix(&stride_str, 16).ok()?;
|
||||||
|
+
|
||||||
|
+ if phys == 0 || width == 0 || height == 0 || stride == 0 {
|
||||||
|
+ log::warn!("fbcond: FRAMEBUFFER_* env vars present but contain zero values");
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let size = stride * height * 4;
|
||||||
|
+
|
||||||
|
+ let fb_mem = match PhysBorrowed::map(
|
||||||
|
+ phys,
|
||||||
|
+ size,
|
||||||
|
+ Prot {
|
||||||
|
+ read: true,
|
||||||
|
+ write: true,
|
||||||
|
+ },
|
||||||
|
+ MemoryType::WriteCombining,
|
||||||
|
+ ) {
|
||||||
|
+ Ok(m) => m,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("fbcond: failed to map physical framebuffer at 0x{phys:X}: {e}");
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ log::info!(
|
||||||
|
+ "fbcond: Direct framebuffer mapped: {}x{} stride {} at 0x{phys:X}",
|
||||||
|
+ width,
|
||||||
|
+ height,
|
||||||
|
+ stride
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ Some(DirectFbMap {
|
||||||
|
+ fb_mem,
|
||||||
|
+ width,
|
||||||
|
+ height,
|
||||||
|
+ stride,
|
||||||
|
+ })
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn width(&self) -> usize {
|
||||||
|
+ self.width
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn height(&self) -> usize {
|
||||||
|
+ self.height
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub(crate) fn pixel_slice(&mut self) -> &mut [u32] {
|
||||||
|
+ unsafe {
|
||||||
|
+ let ptr = self.fb_mem.as_ptr() as *mut u32;
|
||||||
|
+ let len = self.stride * self.height;
|
||||||
|
+ std::slice::from_raw_parts_mut(ptr, len)
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fn sync_rect(&mut self, _damage: Damage) {
|
||||||
|
+ // Direct framebuffer writes are immediately visible
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub enum DisplayMap {
|
||||||
|
+ Drm(V2DisplayMap),
|
||||||
|
+ Direct(DirectFbMap),
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
pub struct Display {
|
||||||
|
pub input_handle: ConsumerHandle,
|
||||||
|
- pub map: Option<V2DisplayMap>,
|
||||||
|
+ pub map: Option<DisplayMap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display {
|
||||||
|
@@ -22,19 +107,29 @@ impl Display {
|
||||||
|
Ok(display)
|
||||||
|
}
|
||||||
|
|
||||||
|
- /// Re-open the display after a handoff.
|
||||||
|
+ /// Re-open the display after a handoff. Tries DRM first, then falls back
|
||||||
|
+ /// to direct physical framebuffer via FRAMEBUFFER_* env vars.
|
||||||
|
pub fn reopen_for_handoff(&mut self) {
|
||||||
|
let display_file = match self.input_handle.open_display_v2() {
|
||||||
|
Ok(display_file) => display_file,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("fbcond: No display present yet: {err}");
|
||||||
|
+ if let Some(direct) = DirectFbMap::try_new() {
|
||||||
|
+ log::info!("fbcond: Falling back to direct framebuffer (no display handle)");
|
||||||
|
+ self.map = Some(DisplayMap::Direct(direct));
|
||||||
|
+ }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
+
|
||||||
|
let new_display_handle = match V2GraphicsHandle::from_file(display_file) {
|
||||||
|
Ok(handle) => handle,
|
||||||
|
Err(err) => {
|
||||||
|
- log::error!("fbcond: failed to create graphics handle (DRM ioctl unsupported): {err}");
|
||||||
|
+ log::warn!("fbcond: DRM ioctl unsupported, trying direct framebuffer: {err}");
|
||||||
|
+ if let Some(direct) = DirectFbMap::try_new() {
|
||||||
|
+ log::info!("fbcond: Using direct framebuffer fallback");
|
||||||
|
+ self.map = Some(DisplayMap::Direct(direct));
|
||||||
|
+ }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -48,43 +143,64 @@ impl Display {
|
||||||
|
map.buffer.buffer().size().0,
|
||||||
|
map.buffer.buffer().size().1,
|
||||||
|
);
|
||||||
|
- self.map = Some(map)
|
||||||
|
+ self.map = Some(DisplayMap::Drm(map))
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
- log::error!("fbcond: failed to map new display: {err}");
|
||||||
|
- return;
|
||||||
|
+ log::warn!("fbcond: failed to map DRM display, trying direct framebuffer: {err}");
|
||||||
|
+ if let Some(direct) = DirectFbMap::try_new() {
|
||||||
|
+ log::info!("fbcond: Using direct framebuffer fallback");
|
||||||
|
+ self.map = Some(DisplayMap::Direct(direct));
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- pub fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
|
||||||
|
- let mode = match map
|
||||||
|
- .display_handle
|
||||||
|
- .first_display()
|
||||||
|
- .and_then(|handle| Ok(map.display_handle.get_connector(handle, true)?.modes()[0]))
|
||||||
|
- {
|
||||||
|
- Ok(mode) => mode,
|
||||||
|
- Err(err) => {
|
||||||
|
- eprintln!("fbcond: failed to get display size: {}", err);
|
||||||
|
- return;
|
||||||
|
- }
|
||||||
|
- };
|
||||||
|
+ pub fn handle_resize(map: &mut DisplayMap, text_screen: &mut TextScreen) {
|
||||||
|
+ match map {
|
||||||
|
+ DisplayMap::Drm(drm_map) => {
|
||||||
|
+ let mode = match drm_map
|
||||||
|
+ .display_handle
|
||||||
|
+ .first_display()
|
||||||
|
+ .and_then(|handle| {
|
||||||
|
+ Ok(drm_map.display_handle.get_connector(handle, true)?.modes()[0])
|
||||||
|
+ }) {
|
||||||
|
+ Ok(mode) => mode,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ eprintln!("fbcond: failed to get display size: {}", err);
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
- if (u32::from(mode.size().0), u32::from(mode.size().1)) != map.buffer.buffer().size() {
|
||||||
|
- match text_screen.resize(map, mode) {
|
||||||
|
- Ok(()) => eprintln!("fbcond: mapped display"),
|
||||||
|
- Err(err) => {
|
||||||
|
- eprintln!("fbcond: failed to create or map framebuffer: {}", err);
|
||||||
|
- return;
|
||||||
|
+ if (u32::from(mode.size().0), u32::from(mode.size().1))
|
||||||
|
+ != drm_map.buffer.buffer().size()
|
||||||
|
+ {
|
||||||
|
+ match text_screen.resize(drm_map, mode) {
|
||||||
|
+ Ok(()) => eprintln!("fbcond: mapped display"),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ eprintln!(
|
||||||
|
+ "fbcond: failed to create or map framebuffer: {}",
|
||||||
|
+ err
|
||||||
|
+ );
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ DisplayMap::Direct(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_rect(&mut self, damage: Damage) {
|
||||||
|
if let Some(map) = &mut self.map {
|
||||||
|
- if let Err(e) = map.dirty_fb(damage) {
|
||||||
|
- log::error!("fbcond: failed to sync framebuffer: {}", e);
|
||||||
|
+ match map {
|
||||||
|
+ DisplayMap::Drm(drm_map) => {
|
||||||
|
+ if let Err(e) = drm_map.dirty_fb(damage) {
|
||||||
|
+ log::error!("fbcond: failed to sync framebuffer: {}", e);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ DisplayMap::Direct(direct_map) => {
|
||||||
|
+ direct_map.sync_rect(damage);
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs
|
||||||
|
index f9992d2b..4130b05f 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/text.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/text.rs
|
||||||
|
@@ -3,9 +3,11 @@ use std::collections::VecDeque;
|
||||||
|
use orbclient::{Event, EventOption};
|
||||||
|
use syscall::error::*;
|
||||||
|
|
||||||
|
-use crate::display::Display;
|
||||||
|
+use crate::display::{DirectFbMap, Display, DisplayMap};
|
||||||
|
|
||||||
|
const SCROLLBACK_LINES: usize = 1000;
|
||||||
|
+const CHAR_WIDTH: usize = 8;
|
||||||
|
+const CHAR_HEIGHT: usize = 16;
|
||||||
|
|
||||||
|
pub struct TextScreen {
|
||||||
|
pub display: Display,
|
||||||
|
@@ -14,6 +16,8 @@ pub struct TextScreen {
|
||||||
|
input: VecDeque<u8>,
|
||||||
|
scrollback: VecDeque<Vec<u8>>,
|
||||||
|
scroll_pos: usize,
|
||||||
|
+ direct_console: ransid::Console,
|
||||||
|
+ direct_initialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextScreen {
|
||||||
|
@@ -22,9 +26,11 @@ impl TextScreen {
|
||||||
|
display,
|
||||||
|
inner: console_draw::TextScreen::new(),
|
||||||
|
ctrl: false,
|
||||||
|
- input: VecDeque::new(),
|
||||||
|
+ input: VecDeque::with_capacity(SCROLLBACK_LINES),
|
||||||
|
scrollback: VecDeque::with_capacity(SCROLLBACK_LINES),
|
||||||
|
scroll_pos: 0,
|
||||||
|
+ direct_console: ransid::Console::new(0, 0),
|
||||||
|
+ direct_initialized: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -43,47 +49,36 @@ impl TextScreen {
|
||||||
|
} else if key_event.pressed {
|
||||||
|
match key_event.scancode {
|
||||||
|
0x0E => {
|
||||||
|
- // Backspace
|
||||||
|
buf.extend_from_slice(b"\x7F");
|
||||||
|
}
|
||||||
|
0x47 => {
|
||||||
|
- // Home
|
||||||
|
buf.extend_from_slice(b"\x1B[H");
|
||||||
|
}
|
||||||
|
0x48 => {
|
||||||
|
- // Up
|
||||||
|
buf.extend_from_slice(b"\x1B[A");
|
||||||
|
}
|
||||||
|
0x49 => {
|
||||||
|
- // Page up
|
||||||
|
buf.extend_from_slice(b"\x1B[5~");
|
||||||
|
}
|
||||||
|
0x4B => {
|
||||||
|
- // Left
|
||||||
|
buf.extend_from_slice(b"\x1B[D");
|
||||||
|
}
|
||||||
|
0x4D => {
|
||||||
|
- // Right
|
||||||
|
buf.extend_from_slice(b"\x1B[C");
|
||||||
|
}
|
||||||
|
0x4F => {
|
||||||
|
- // End
|
||||||
|
buf.extend_from_slice(b"\x1B[F");
|
||||||
|
}
|
||||||
|
0x50 => {
|
||||||
|
- // Down
|
||||||
|
buf.extend_from_slice(b"\x1B[B");
|
||||||
|
}
|
||||||
|
0x51 => {
|
||||||
|
- // Page down
|
||||||
|
buf.extend_from_slice(b"\x1B[6~");
|
||||||
|
}
|
||||||
|
0x52 => {
|
||||||
|
- // Insert
|
||||||
|
buf.extend_from_slice(b"\x1B[2~");
|
||||||
|
}
|
||||||
|
0x53 => {
|
||||||
|
- // Delete
|
||||||
|
buf.extend_from_slice(b"\x1B[3~");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
@@ -101,7 +96,7 @@ impl TextScreen {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- _ => (), //TODO: Mouse in terminal
|
||||||
|
+ _ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
for &b in buf.iter() {
|
||||||
|
@@ -130,21 +125,27 @@ impl TextScreen {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||||
|
- if let Some(map) = &mut self.display.map {
|
||||||
|
- Display::handle_resize(map, &mut self.inner);
|
||||||
|
+ match &mut self.display.map {
|
||||||
|
+ Some(DisplayMap::Drm(map)) => {
|
||||||
|
+ Display::handle_resize(map, &mut self.inner);
|
||||||
|
|
||||||
|
- let damage = self.inner.write(map, buf, &mut self.input);
|
||||||
|
+ let damage = self.inner.write(map, buf, &mut self.input);
|
||||||
|
|
||||||
|
- for line in buf.split(|&b| b == b'\n') {
|
||||||
|
- let mut v = line.to_vec();
|
||||||
|
- v.push(b'\n');
|
||||||
|
- self.scrollback.push_back(v);
|
||||||
|
+ for line in buf.split(|&b| b == b'\n') {
|
||||||
|
+ let mut v = line.to_vec();
|
||||||
|
+ v.push(b'\n');
|
||||||
|
+ self.scrollback.push_back(v);
|
||||||
|
+ }
|
||||||
|
+ while self.scrollback.len() > SCROLLBACK_LINES {
|
||||||
|
+ self.scrollback.pop_front();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ self.display.sync_rect(damage);
|
||||||
|
}
|
||||||
|
- while self.scrollback.len() > SCROLLBACK_LINES {
|
||||||
|
- self.scrollback.pop_front();
|
||||||
|
+ Some(DisplayMap::Direct(direct_map)) => {
|
||||||
|
+ self.write_direct(direct_map, buf);
|
||||||
|
}
|
||||||
|
-
|
||||||
|
- self.display.sync_rect(damage);
|
||||||
|
+ None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(buf.len())
|
||||||
|
@@ -158,3 +159,163 @@ impl TextScreen {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+impl TextScreen {
|
||||||
|
+ fn write_direct(&mut self, direct_map: &mut DirectFbMap, buf: &[u8]) {
|
||||||
|
+ let width = direct_map.width();
|
||||||
|
+ let height = direct_map.height();
|
||||||
|
+
|
||||||
|
+ if width < CHAR_WIDTH || height < CHAR_HEIGHT {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if !self.direct_initialized {
|
||||||
|
+ self.direct_console.resize(width / CHAR_WIDTH, height / CHAR_HEIGHT);
|
||||||
|
+ self.direct_initialized = true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let console = &mut self.direct_console;
|
||||||
|
+
|
||||||
|
+ if console.state.cursor
|
||||||
|
+ && console.state.x < console.state.w
|
||||||
|
+ && console.state.y < console.state.h
|
||||||
|
+ {
|
||||||
|
+ let x = console.state.x;
|
||||||
|
+ let y = console.state.y;
|
||||||
|
+ Self::invert_direct(direct_map, x * CHAR_WIDTH, y * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let pixels = direct_map.pixel_slice();
|
||||||
|
+ let stride = direct_map.width();
|
||||||
|
+
|
||||||
|
+ console.write(buf, |event| match event {
|
||||||
|
+ ransid::Event::Char {
|
||||||
|
+ x,
|
||||||
|
+ y,
|
||||||
|
+ c,
|
||||||
|
+ color,
|
||||||
|
+ bold,
|
||||||
|
+ ..
|
||||||
|
+ } => {
|
||||||
|
+ Self::char_direct(direct_map, x * CHAR_WIDTH, y * CHAR_HEIGHT, c, color.as_rgb(), bold);
|
||||||
|
+ }
|
||||||
|
+ ransid::Event::Input { data } => self.input.extend(data),
|
||||||
|
+ ransid::Event::Rect { x, y, w, h, color } => {
|
||||||
|
+ Self::rect_direct(direct_map, x * CHAR_WIDTH, y * CHAR_HEIGHT, w * CHAR_WIDTH, h * CHAR_HEIGHT, color.as_rgb());
|
||||||
|
+ }
|
||||||
|
+ ransid::Event::ScreenBuffer { .. } => (),
|
||||||
|
+ ransid::Event::Move {
|
||||||
|
+ from_x,
|
||||||
|
+ from_y,
|
||||||
|
+ to_x,
|
||||||
|
+ to_y,
|
||||||
|
+ w,
|
||||||
|
+ h,
|
||||||
|
+ } => {
|
||||||
|
+ for row in 0..h {
|
||||||
|
+ let src_y = if from_y > to_y { row } else { h - row - 1 };
|
||||||
|
+ for pixel_y in 0..CHAR_HEIGHT {
|
||||||
|
+ let off_from = ((from_y + src_y) * CHAR_HEIGHT + pixel_y) * stride + from_x * CHAR_WIDTH;
|
||||||
|
+ let off_to = ((to_y + src_y) * CHAR_HEIGHT + pixel_y) * stride + to_x * CHAR_WIDTH;
|
||||||
|
+ let len = w * CHAR_WIDTH;
|
||||||
|
+
|
||||||
|
+ if off_from + len <= pixels.len() && off_to + len <= pixels.len() {
|
||||||
|
+ unsafe {
|
||||||
|
+ let data_ptr = pixels.as_mut_ptr();
|
||||||
|
+ std::ptr::copy(
|
||||||
|
+ data_ptr.add(off_from),
|
||||||
|
+ data_ptr.add(off_to),
|
||||||
|
+ len,
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ ransid::Event::Resize { .. } => (),
|
||||||
|
+ ransid::Event::Title { .. } => (),
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ if console.state.cursor
|
||||||
|
+ && console.state.x < console.state.w
|
||||||
|
+ && console.state.y < console.state.h
|
||||||
|
+ {
|
||||||
|
+ let x = console.state.x;
|
||||||
|
+ let y = console.state.y;
|
||||||
|
+ Self::invert_direct(direct_map, x * CHAR_WIDTH, y * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ for line in buf.split(|&b| b == b'\n') {
|
||||||
|
+ let mut v = line.to_vec();
|
||||||
|
+ v.push(b'\n');
|
||||||
|
+ self.scrollback.push_back(v);
|
||||||
|
+ }
|
||||||
|
+ while self.scrollback.len() > SCROLLBACK_LINES {
|
||||||
|
+ self.scrollback.pop_front();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fn rect_direct(map: &mut DirectFbMap, x: usize, y: usize, w: usize, h: usize, color: u32) {
|
||||||
|
+ let width = map.width();
|
||||||
|
+ let height = map.height();
|
||||||
|
+ let pixels = map.pixel_slice();
|
||||||
|
+ let stride = width;
|
||||||
|
+
|
||||||
|
+ let start_y = y.min(height);
|
||||||
|
+ let end_y = (y + h).min(height);
|
||||||
|
+ let start_x = x.min(width);
|
||||||
|
+ let len = (x + w).min(width) - start_x;
|
||||||
|
+
|
||||||
|
+ for row in start_y..end_y {
|
||||||
|
+ let offset = row * stride + start_x;
|
||||||
|
+ for i in 0..len {
|
||||||
|
+ pixels[offset + i] = color;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fn char_direct(map: &mut DirectFbMap, x: usize, y: usize, character: char, color: u32, _bold: bool) {
|
||||||
|
+ let width = map.width();
|
||||||
|
+ let height = map.height();
|
||||||
|
+
|
||||||
|
+ if x + CHAR_WIDTH > width || y + CHAR_HEIGHT > height {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let pixels = map.pixel_slice();
|
||||||
|
+ let stride = width;
|
||||||
|
+ let font_i = CHAR_HEIGHT * (character as usize);
|
||||||
|
+ if font_i + CHAR_HEIGHT > orbclient::FONT.len() {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ for row in 0..CHAR_HEIGHT {
|
||||||
|
+ let row_data = orbclient::FONT[font_i + row];
|
||||||
|
+ let offset = (y + row) * stride + x;
|
||||||
|
+ for col in 0..CHAR_WIDTH {
|
||||||
|
+ if (row_data >> (7 - col)) & 1 == 1 {
|
||||||
|
+ pixels[offset + col] = color;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fn invert_direct(map: &mut DirectFbMap, x: usize, y: usize, w: usize, h: usize) {
|
||||||
|
+ let width = map.width();
|
||||||
|
+ let height = map.height();
|
||||||
|
+ let pixels = map.pixel_slice();
|
||||||
|
+ let stride = width;
|
||||||
|
+
|
||||||
|
+ let start_y = y.min(height);
|
||||||
|
+ let end_y = (y + h).min(height);
|
||||||
|
+ let start_x = x.min(width);
|
||||||
|
+ let len = (x + w).min(width) - start_x;
|
||||||
|
+
|
||||||
|
+ for row in start_y..end_y {
|
||||||
|
+ let offset = row * stride + start_x;
|
||||||
|
+ for i in 0..len {
|
||||||
|
+ pixels[offset + i] = !pixels[offset + i];
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
diff --git a/drivers/initfs-storage.toml b/drivers/initfs-storage.toml
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..4a9a603a
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/initfs-storage.toml
|
||||||
|
@@ -0,0 +1,51 @@
|
||||||
|
+## Initfs driver configs for driver-manager
|
||||||
|
+## Read by driver-manager --initfs from /scheme/initfs/lib/drivers.d/
|
||||||
|
+##
|
||||||
|
+## Storage drivers essential for early boot (mounting rootfs).
|
||||||
|
+## GPU/display drivers are NOT probed in initfs.
|
||||||
|
+
|
||||||
|
+[[driver]]
|
||||||
|
+name = "ahcid"
|
||||||
|
+description = "AHCI SATA storage driver"
|
||||||
|
+priority = 100
|
||||||
|
+command = ["/scheme/initfs/lib/drivers/ahcid"]
|
||||||
|
+
|
||||||
|
+[[driver.match]]
|
||||||
|
+bus = "pci"
|
||||||
|
+class = 1
|
||||||
|
+subclass = 6
|
||||||
|
+
|
||||||
|
+[[driver]]
|
||||||
|
+name = "ided"
|
||||||
|
+description = "PATA IDE storage driver"
|
||||||
|
+priority = 100
|
||||||
|
+command = ["/scheme/initfs/lib/drivers/ided"]
|
||||||
|
+
|
||||||
|
+[[driver.match]]
|
||||||
|
+bus = "pci"
|
||||||
|
+class = 1
|
||||||
|
+subclass = 1
|
||||||
|
+
|
||||||
|
+[[driver]]
|
||||||
|
+name = "nvmed"
|
||||||
|
+description = "NVMe storage driver"
|
||||||
|
+priority = 100
|
||||||
|
+command = ["/scheme/initfs/lib/drivers/nvmed"]
|
||||||
|
+
|
||||||
|
+[[driver.match]]
|
||||||
|
+bus = "pci"
|
||||||
|
+class = 1
|
||||||
|
+subclass = 8
|
||||||
|
+
|
||||||
|
+[[driver]]
|
||||||
|
+name = "virtio-blkd"
|
||||||
|
+description = "VirtIO block device driver"
|
||||||
|
+priority = 100
|
||||||
|
+command = ["/scheme/initfs/lib/drivers/virtio-blkd"]
|
||||||
|
+
|
||||||
|
+[[driver.match]]
|
||||||
|
+bus = "pci"
|
||||||
|
+vendor = 0x1AF4
|
||||||
|
+device = 0x1001
|
||||||
|
+class = 1
|
||||||
|
+subclass = 0
|
||||||
|
diff --git a/init.d/00_base.target b/init.d/00_base.target
|
||||||
|
index 03c25798..9411cc88 100644
|
||||||
|
--- a/init.d/00_base.target
|
||||||
|
+++ b/init.d/00_base.target
|
||||||
|
@@ -7 +7 @@ requires_weak = [
|
||||||
|
- "00_pcid-spawner.service",
|
||||||
|
+ "00_driver-manager.service",
|
||||||
|
diff --git a/init.d/00_pcid-spawner.service b/init.d/00_pcid-spawner.service
|
||||||
|
deleted file mode 100644
|
||||||
|
index 8ba6fc6c..00000000
|
||||||
|
--- a/init.d/00_pcid-spawner.service
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,6 +0,0 @@
|
||||||
|
-[unit]
|
||||||
|
-description = "PCI driver spawner"
|
||||||
|
-
|
||||||
|
-[service]
|
||||||
|
-cmd = "pcid-spawner"
|
||||||
|
-type = "oneshot"
|
||||||
|
diff --git a/init.d/10_smolnetd.service b/init.d/10_smolnetd.service
|
||||||
|
index 1ee54ad0..29563e65 100644
|
||||||
|
--- a/init.d/10_smolnetd.service
|
||||||
|
+++ b/init.d/10_smolnetd.service
|
||||||
|
@@ -6 +6 @@ requires_weak = [
|
||||||
|
- "00_pcid-spawner.service",
|
||||||
|
+ "00_driver-manager.service",
|
||||||
|
diff --git a/init.initfs.d/00_driver-manager-initfs.service b/init.initfs.d/00_driver-manager-initfs.service
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..39613261
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/init.initfs.d/00_driver-manager-initfs.service
|
||||||
|
@@ -0,0 +1,8 @@
|
||||||
|
+[unit]
|
||||||
|
+description = "Red Bear driver manager (initfs)"
|
||||||
|
+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "40_hwd.service"]
|
||||||
|
+
|
||||||
|
+[service]
|
||||||
|
+cmd = "driver-manager"
|
||||||
|
+args = ["--initfs"]
|
||||||
|
+type = "oneshot_async"
|
||||||
|
diff --git a/init.initfs.d/30_redox-drm.service b/init.initfs.d/30_redox-drm.service
|
||||||
|
index ba380bf2..3e051003 100644
|
||||||
|
--- a/init.initfs.d/30_redox-drm.service
|
||||||
|
+++ b/init.initfs.d/30_redox-drm.service
|
||||||
|
@@ -3 +3 @@ description = "DRM/KMS Display Driver"
|
||||||
|
-requires_weak = ["20_graphics.target", "40_hwd.service", "40_pcid-spawner-initfs.service"]
|
||||||
|
+requires_weak = ["20_graphics.target", "40_hwd.service", "00_driver-manager-initfs.service"]
|
||||||
|
diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target
|
||||||
|
index 061c2bed..2a91d2d4 100644
|
||||||
|
--- a/init.initfs.d/40_drivers.target
|
||||||
|
+++ b/init.initfs.d/40_drivers.target
|
||||||
|
@@ -6 +5,0 @@ requires_weak = [
|
||||||
|
- "40_pcid.service",
|
||||||
|
@@ -10 +9 @@ requires_weak = [
|
||||||
|
- "40_pcid-spawner-initfs.service",
|
||||||
|
+ "00_driver-manager-initfs.service",
|
||||||
|
diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service
|
||||||
|
index ff0e76dc..d4625542 100644
|
||||||
|
--- a/init.initfs.d/40_hwd.service
|
||||||
|
+++ b/init.initfs.d/40_hwd.service
|
||||||
|
@@ -3 +3 @@ description = "Hardware manager"
|
||||||
|
-requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "40_pcid.service", "41_acpid.service"]
|
||||||
|
+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "00_driver-manager-initfs.service", "41_acpid.service"]
|
||||||
|
diff --git a/init.initfs.d/40_pcid-spawner-initfs.service b/init.initfs.d/40_pcid-spawner-initfs.service
|
||||||
|
deleted file mode 100644
|
||||||
|
index 2d36c9d5..00000000
|
||||||
|
--- a/init.initfs.d/40_pcid-spawner-initfs.service
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,8 +0,0 @@
|
||||||
|
-[unit]
|
||||||
|
-description = "PCI driver spawner"
|
||||||
|
-requires_weak = ["10_inputd.service", "20_graphics.target", "40_pcid.service"]
|
||||||
|
-
|
||||||
|
-[service]
|
||||||
|
-cmd = "pcid-spawner"
|
||||||
|
-args = ["--initfs"]
|
||||||
|
-type = "oneshot"
|
||||||
|
diff --git a/init.initfs.d/40_pcid.service b/init.initfs.d/40_pcid.service
|
||||||
|
deleted file mode 100644
|
||||||
|
index 6c3a83d8..00000000
|
||||||
|
--- a/init.initfs.d/40_pcid.service
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,7 +0,0 @@
|
||||||
|
-[unit]
|
||||||
|
-description = "PCI daemon"
|
||||||
|
-requires_weak = ["41_acpid.service"]
|
||||||
|
-
|
||||||
|
-[service]
|
||||||
|
-cmd = "pcid"
|
||||||
|
-type = "notify"
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs
|
||||||
|
index 4130b05f..a71a9f51 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/text.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/text.rs
|
||||||
|
@@ -128,2 +128 @@ impl TextScreen {
|
||||||
|
- match &mut self.display.map {
|
||||||
|
- Some(DisplayMap::Drm(map)) => {
|
||||||
|
+ if let Some(map) = &mut self.display.map {
|
||||||
|
@@ -130,0 +130 @@ impl TextScreen {
|
||||||
|
+ }
|
||||||
|
@@ -131,0 +132,2 @@ impl TextScreen {
|
||||||
|
+ match &mut self.display.map {
|
||||||
|
+ Some(DisplayMap::Drm(map)) => {
|
||||||
|
@@ -146 +148,7 @@ impl TextScreen {
|
||||||
|
- self.write_direct(direct_map, buf);
|
||||||
|
+ Self::write_direct(
|
||||||
|
+ direct_map,
|
||||||
|
+ buf,
|
||||||
|
+ &mut self.direct_console,
|
||||||
|
+ &mut self.direct_initialized,
|
||||||
|
+ &mut self.scrollback,
|
||||||
|
+ );
|
||||||
|
@@ -164 +172,7 @@ impl TextScreen {
|
||||||
|
- fn write_direct(&mut self, direct_map: &mut DirectFbMap, buf: &[u8]) {
|
||||||
|
+ fn write_direct(
|
||||||
|
+ direct_map: &mut DirectFbMap,
|
||||||
|
+ buf: &[u8],
|
||||||
|
+ direct_console: &mut ransid::Console,
|
||||||
|
+ direct_initialized: &mut bool,
|
||||||
|
+ scrollback: &mut VecDeque<Vec<u8>>,
|
||||||
|
+ ) {
|
||||||
|
@@ -172,3 +186,3 @@ impl TextScreen {
|
||||||
|
- if !self.direct_initialized {
|
||||||
|
- self.direct_console.resize(width / CHAR_WIDTH, height / CHAR_HEIGHT);
|
||||||
|
- self.direct_initialized = true;
|
||||||
|
+ if !*direct_initialized {
|
||||||
|
+ direct_console.resize(width / CHAR_WIDTH, height / CHAR_HEIGHT);
|
||||||
|
+ *direct_initialized = true;
|
||||||
|
@@ -177,5 +191,3 @@ impl TextScreen {
|
||||||
|
- let console = &mut self.direct_console;
|
||||||
|
-
|
||||||
|
- if console.state.cursor
|
||||||
|
- && console.state.x < console.state.w
|
||||||
|
- && console.state.y < console.state.h
|
||||||
|
+ if direct_console.state.cursor
|
||||||
|
+ && direct_console.state.x < direct_console.state.w
|
||||||
|
+ && direct_console.state.y < direct_console.state.h
|
||||||
|
@@ -183,2 +195,2 @@ impl TextScreen {
|
||||||
|
- let x = console.state.x;
|
||||||
|
- let y = console.state.y;
|
||||||
|
+ let x = direct_console.state.x;
|
||||||
|
+ let y = direct_console.state.y;
|
||||||
|
@@ -188 +200,2 @@ impl TextScreen {
|
||||||
|
- let pixels = direct_map.pixel_slice();
|
||||||
|
+ // Extract stride before pixel_slice: borrow sequence must be
|
||||||
|
+ // width/height → stride → pixel_slice to avoid E0502/E0500.
|
||||||
|
@@ -189,0 +203,3 @@ impl TextScreen {
|
||||||
|
+ let pixels = direct_map.pixel_slice();
|
||||||
|
+ let fb_width = width;
|
||||||
|
+ let fb_height = height;
|
||||||
|
@@ -191 +207,4 @@ impl TextScreen {
|
||||||
|
- console.write(buf, |event| match event {
|
||||||
|
+ // Use pixels/stride inside the closure — direct_map is already mutably
|
||||||
|
+ // borrowed via pixel_slice, so we cannot call Self::char_direct etc.
|
||||||
|
+ // which would re-borrow direct_map.
|
||||||
|
+ direct_console.write(buf, |event| match event {
|
||||||
|
@@ -197 +216 @@ impl TextScreen {
|
||||||
|
- bold,
|
||||||
|
+ bold: _,
|
||||||
|
@@ -200 +219,13 @@ impl TextScreen {
|
||||||
|
- Self::char_direct(direct_map, x * CHAR_WIDTH, y * CHAR_HEIGHT, c, color.as_rgb(), bold);
|
||||||
|
+ let cx = x * CHAR_WIDTH;
|
||||||
|
+ let cy = y * CHAR_HEIGHT;
|
||||||
|
+ if cx + CHAR_WIDTH <= fb_width && cy + CHAR_HEIGHT <= fb_height {
|
||||||
|
+ let font_i = CHAR_HEIGHT * (c as usize);
|
||||||
|
+ if font_i + CHAR_HEIGHT <= orbclient::FONT.len() {
|
||||||
|
+ for row in 0..CHAR_HEIGHT {
|
||||||
|
+ let row_data = orbclient::FONT[font_i + row];
|
||||||
|
+ let offset = (cy + row) * stride + cx;
|
||||||
|
+ for col in 0..CHAR_WIDTH {
|
||||||
|
+ if (row_data >> (7 - col)) & 1 == 1 {
|
||||||
|
+ pixels[offset + col] = color.as_rgb();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
@@ -202 +233,4 @@ impl TextScreen {
|
||||||
|
- ransid::Event::Input { data } => self.input.extend(data),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ ransid::Event::Input { .. } => (),
|
||||||
|
@@ -204 +238,15 @@ impl TextScreen {
|
||||||
|
- Self::rect_direct(direct_map, x * CHAR_WIDTH, y * CHAR_HEIGHT, w * CHAR_WIDTH, h * CHAR_HEIGHT, color.as_rgb());
|
||||||
|
+ let rx = x * CHAR_WIDTH;
|
||||||
|
+ let ry = y * CHAR_HEIGHT;
|
||||||
|
+ let rw = w * CHAR_WIDTH;
|
||||||
|
+ let rh = h * CHAR_HEIGHT;
|
||||||
|
+ let start_y = ry.min(fb_height);
|
||||||
|
+ let end_y = (ry + rh).min(fb_height);
|
||||||
|
+ let start_x = rx.min(fb_width);
|
||||||
|
+ let len = (rx + rw).min(fb_width) - start_x;
|
||||||
|
+ let rgb = color.as_rgb();
|
||||||
|
+ for row in start_y..end_y {
|
||||||
|
+ let offset = row * stride + start_x;
|
||||||
|
+ for i in 0..len {
|
||||||
|
+ pixels[offset + i] = rgb;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
@@ -239,3 +287,3 @@ impl TextScreen {
|
||||||
|
- if console.state.cursor
|
||||||
|
- && console.state.x < console.state.w
|
||||||
|
- && console.state.y < console.state.h
|
||||||
|
+ if direct_console.state.cursor
|
||||||
|
+ && direct_console.state.x < direct_console.state.w
|
||||||
|
+ && direct_console.state.y < direct_console.state.h
|
||||||
|
@@ -243,2 +291,2 @@ impl TextScreen {
|
||||||
|
- let x = console.state.x;
|
||||||
|
- let y = console.state.y;
|
||||||
|
+ let x = direct_console.state.x;
|
||||||
|
+ let y = direct_console.state.y;
|
||||||
|
@@ -251 +299 @@ impl TextScreen {
|
||||||
|
- self.scrollback.push_back(v);
|
||||||
|
+ scrollback.push_back(v);
|
||||||
|
@@ -253,2 +301,2 @@ impl TextScreen {
|
||||||
|
- while self.scrollback.len() > SCROLLBACK_LINES {
|
||||||
|
- self.scrollback.pop_front();
|
||||||
|
+ while scrollback.len() > SCROLLBACK_LINES {
|
||||||
|
+ scrollback.pop_front();
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
diff --git a/init/src/unit.rs b/init/src/unit.rs
|
||||||
|
index 98053cb2..eabb031b 100644
|
||||||
|
--- a/init/src/unit.rs
|
||||||
|
+++ b/init/src/unit.rs
|
||||||
|
@@ -60,0 +61,4 @@ impl UnitStore {
|
||||||
|
+ if !unit.conditions_met() {
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index 059254b3..a43bab83 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -96,2 +96,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- register_sync_scheme(&socket, "acpi", &mut scheme)
|
||||||
|
- .expect("acpid: failed to register acpi scheme to namespace");
|
||||||
|
+ if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) {
|
||||||
|
+ log::warn!(
|
||||||
|
+ "acpid: failed to register acpi scheme (error: {}). Another acpid instance may already own it.",
|
||||||
|
+ err
|
||||||
|
+ );
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs
|
||||||
|
index da9cabe1..042173d6 100644
|
||||||
|
--- a/drivers/usb/xhcid/src/main.rs
|
||||||
|
+++ b/drivers/usb/xhcid/src/main.rs
|
||||||
|
@@ -65 +64,0 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||||
|
- PciFeatureInfo::Msi(_) => panic!(),
|
||||||
|
@@ -66,0 +66,4 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||||
|
+ other => {
|
||||||
|
+ log::error!("xhcid: unexpected MSI-X feature info response: {:?}", other);
|
||||||
|
+ return (None, InterruptMethod::Polling);
|
||||||
|
+ }
|
||||||
|
@@ -78 +81,7 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||||
|
- let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id");
|
||||||
|
+ let destination_id = match read_bsp_apic_id() {
|
||||||
|
+ Ok(id) => id,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::error!("xhcid: failed to read BSP APIC ID: {}", err);
|
||||||
|
+ return (None, InterruptMethod::Polling);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -150,2 +159,7 @@ fn daemon_with_context_size<const N: usize>(
|
||||||
|
- let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle);
|
||||||
|
- //TODO: Fix interrupts.
|
||||||
|
+ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle);
|
||||||
|
+
|
||||||
|
+ match interrupt_method {
|
||||||
|
+ InterruptMethod::Msi => log::info!("xhcid: using MSI/MSI-X interrupt delivery"),
|
||||||
|
+ InterruptMethod::Intx => log::info!("xhcid: using legacy INTx interrupt delivery"),
|
||||||
|
+ InterruptMethod::Polling => log::warn!("xhcid: falling back to polling mode"),
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index 5528ad0a..b05102f6 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -50,2 +50,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- log::error!("acpid: failed to parse [RX]SDT: {}", e);
|
||||||
|
- std::process::exit(1);
|
||||||
|
+ log::warn!("acpid: failed to parse [RX]SDT: {} — booting without ACPI", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
@@ -80,2 +81,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- log::error!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT, got {:?}", String::from_utf8_lossy(&sdt.signature));
|
||||||
|
- std::process::exit(1);
|
||||||
|
+ log::warn!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT, got {:?} — booting without ACPI", String::from_utf8_lossy(&sdt.signature));
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
@@ -102,2 +104 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- log::error!("acpid: failed to set I/O privilege level to Ring 3: {}", e);
|
||||||
|
- std::process::exit(1);
|
||||||
|
+ log::warn!("acpid: failed to set I/O privilege level to Ring 3: {} — continuing without port I/O", e);
|
||||||
|
@@ -106,2 +107,17 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
|
||||||
|
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
|
||||||
|
+ let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") {
|
||||||
|
+ Ok(f) => f,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {} — booting without ACPI shutdown", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ let mut event_queue = match RawEventQueue::new() {
|
||||||
|
+ Ok(q) => q,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("acpid: failed to create event queue: {} — booting without ACPI", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -109,2 +125,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
|
||||||
|
- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
|
||||||
|
+ let socket = match Socket::nonblock() {
|
||||||
|
+ Ok(s) => s,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("acpid: failed to create scheme socket: {} — booting without ACPI", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -115,6 +137,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- event_queue
|
||||||
|
- .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
|
||||||
|
- .expect("acpid: failed to register shutdown pipe for event queue");
|
||||||
|
- event_queue
|
||||||
|
- .subscribe(socket.inner().raw(), 1, EventFlags::READ)
|
||||||
|
- .expect("acpid: failed to register scheme socket for event queue");
|
||||||
|
+ if let Err(e) = event_queue
|
||||||
|
+ .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) {
|
||||||
|
+ log::warn!("acpid: failed to register shutdown pipe for event queue: {} — booting without ACPI", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ if let Err(e) = event_queue
|
||||||
|
+ .subscribe(socket.inner().raw(), 1, EventFlags::READ) {
|
||||||
|
+ log::warn!("acpid: failed to register scheme socket for event queue: {} — booting without ACPI", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs
|
||||||
|
index a4c07d1e..4db8c738 100644
|
||||||
|
--- a/drivers/graphics/vesad/src/main.rs
|
||||||
|
+++ b/drivers/graphics/vesad/src/main.rs
|
||||||
|
@@ -25,20 +25,60 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let width = usize::from_str_radix(
|
||||||
|
- &env::var("FRAMEBUFFER_WIDTH").expect("FRAMEBUFFER_WIDTH not set"),
|
||||||
|
- 16,
|
||||||
|
- )
|
||||||
|
- .expect("failed to parse FRAMEBUFFER_WIDTH");
|
||||||
|
- let height = usize::from_str_radix(
|
||||||
|
- &env::var("FRAMEBUFFER_HEIGHT").expect("FRAMEBUFFER_HEIGHT not set"),
|
||||||
|
- 16,
|
||||||
|
- )
|
||||||
|
- .expect("failed to parse FRAMEBUFFER_HEIGHT");
|
||||||
|
- let phys = usize::from_str_radix(
|
||||||
|
- &env::var("FRAMEBUFFER_ADDR").expect("FRAMEBUFFER_ADDR not set"),
|
||||||
|
- 16,
|
||||||
|
- )
|
||||||
|
- .expect("failed to parse FRAMEBUFFER_ADDR");
|
||||||
|
- let stride = usize::from_str_radix(
|
||||||
|
- &env::var("FRAMEBUFFER_STRIDE").expect("FRAMEBUFFER_STRIDE not set"),
|
||||||
|
- 16,
|
||||||
|
- )
|
||||||
|
- .expect("failed to parse FRAMEBUFFER_STRIDE");
|
||||||
|
+ let width = match env::var("FRAMEBUFFER_WIDTH") {
|
||||||
|
+ Ok(v) => match usize::from_str_radix(&v, 16) {
|
||||||
|
+ Ok(n) => n,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to parse FRAMEBUFFER_WIDTH '{}': {} — exiting", v, e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: FRAMEBUFFER_WIDTH not readable: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ let height = match env::var("FRAMEBUFFER_HEIGHT") {
|
||||||
|
+ Ok(v) => match usize::from_str_radix(&v, 16) {
|
||||||
|
+ Ok(n) => n,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to parse FRAMEBUFFER_HEIGHT '{}': {} — exiting", v, e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: FRAMEBUFFER_HEIGHT not readable: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ let phys = match env::var("FRAMEBUFFER_ADDR") {
|
||||||
|
+ Ok(v) => match usize::from_str_radix(&v, 16) {
|
||||||
|
+ Ok(n) => n,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to parse FRAMEBUFFER_ADDR '{}': {} — exiting", v, e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: FRAMEBUFFER_ADDR not readable: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ let stride = match env::var("FRAMEBUFFER_STRIDE") {
|
||||||
|
+ Ok(v) => match usize::from_str_radix(&v, 16) {
|
||||||
|
+ Ok(n) => n,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to parse FRAMEBUFFER_STRIDE '{}': {} — exiting", v, e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: FRAMEBUFFER_STRIDE not readable: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -57 +97,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }];
|
||||||
|
+ let mut framebuffers = match unsafe { FrameBuffer::try_new(phys, width, height, stride) } {
|
||||||
|
+ Some(fb) => vec![fb],
|
||||||
|
+ None => {
|
||||||
|
+ eprintln!("vesad: failed to map primary framebuffer — exiting");
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -59,3 +106,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- //TODO: ideal maximum number of outputs?
|
||||||
|
- let bootloader_env = std::fs::read_to_string("/scheme/sys/env")
|
||||||
|
- .expect("failed to read env")
|
||||||
|
+ let bootloader_env = match std::fs::read_to_string("/scheme/sys/env") {
|
||||||
|
+ Ok(data) => data
|
||||||
|
@@ -63,3 +109,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .map(|line| {
|
||||||
|
- let (env, value) = line.split_once('=').unwrap();
|
||||||
|
- (env.to_owned(), value.to_owned())
|
||||||
|
+ .filter_map(|line| {
|
||||||
|
+ line.split_once('=')
|
||||||
|
+ .map(|(env, value)| (env.to_owned(), value.to_owned()))
|
||||||
|
@@ -67 +113,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .collect::<HashMap<String, String>>();
|
||||||
|
+ .collect::<HashMap<String, String>>(),
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to read /scheme/sys/env: {} — continuing with primary framebuffer only", e);
|
||||||
|
+ HashMap::new()
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -96,4 +147,9 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let event_queue: EventQueue<Source> =
|
||||||
|
- EventQueue::new().expect("vesad: failed to create event queue");
|
||||||
|
- event_queue
|
||||||
|
- .subscribe(
|
||||||
|
+ let event_queue: EventQueue<Source> = match EventQueue::new() {
|
||||||
|
+ Ok(q) => q,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to create event queue: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ if let Err(e) = event_queue.subscribe(
|
||||||
|
@@ -103,4 +159,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- )
|
||||||
|
- .unwrap();
|
||||||
|
- event_queue
|
||||||
|
- .subscribe(
|
||||||
|
+ ) {
|
||||||
|
+ eprintln!("vesad: failed to subscribe input events: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ if let Err(e) = event_queue.subscribe(
|
||||||
|
@@ -110,2 +168,5 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- )
|
||||||
|
- .unwrap();
|
||||||
|
+ ) {
|
||||||
|
+ eprintln!("vesad: failed to subscribe scheme events: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
@@ -113 +174,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- libredox::call::setrens(0, 0).expect("vesad: failed to enter null namespace");
|
||||||
|
+ if let Err(e) = libredox::call::setrens(0, 0) {
|
||||||
|
+ eprintln!("vesad: failed to enter null namespace: {} — continuing", e);
|
||||||
|
+ }
|
||||||
|
@@ -120 +183,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- .chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data))
|
||||||
|
+ .chain(event_queue.map(|e| match e {
|
||||||
|
+ Ok(ev) => ev.user_data,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ eprintln!("vesad: event error: {} — continuing", err);
|
||||||
|
+ Source::Scheme
|
||||||
|
+ }
|
||||||
|
+ }))
|
||||||
|
@@ -125,3 +194,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- scheme
|
||||||
|
- .tick()
|
||||||
|
- .expect("vesad: failed to handle scheme events");
|
||||||
|
+ if let Err(e) = scheme.tick() {
|
||||||
|
+ eprintln!("vesad: scheme tick error: {} — continuing", e);
|
||||||
|
+ }
|
||||||
|
@@ -132 +201,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- panic!();
|
||||||
|
+ eprintln!("vesad: event loop ended unexpectedly — exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
diff --git a/drivers/graphics/vesad/src/scheme.rs b/drivers/graphics/vesad/src/scheme.rs
|
||||||
|
index 5bf2be91..9ce6b89a 100644
|
||||||
|
--- a/drivers/graphics/vesad/src/scheme.rs
|
||||||
|
+++ b/drivers/graphics/vesad/src/scheme.rs
|
||||||
|
@@ -160 +160 @@ impl FrameBuffer {
|
||||||
|
- pub unsafe fn new(phys: usize, width: usize, height: usize, stride: usize) -> Self {
|
||||||
|
+ pub unsafe fn try_new(phys: usize, width: usize, height: usize, stride: usize) -> Option<Self> {
|
||||||
|
@@ -162 +162 @@ impl FrameBuffer {
|
||||||
|
- let virt = common::physmap(
|
||||||
|
+ let virt = match common::physmap(
|
||||||
|
@@ -170,2 +170,7 @@ impl FrameBuffer {
|
||||||
|
- )
|
||||||
|
- .expect("vesad: failed to map framebuffer") as *mut u32;
|
||||||
|
+ ) {
|
||||||
|
+ Ok(v) => v as *mut u32,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to map framebuffer at 0x{phys:X}: {e}");
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -175 +180 @@ impl FrameBuffer {
|
||||||
|
- Self {
|
||||||
|
+ Some(Self {
|
||||||
|
@@ -181 +186 @@ impl FrameBuffer {
|
||||||
|
- }
|
||||||
|
+ })
|
||||||
|
@@ -205 +210 @@ impl FrameBuffer {
|
||||||
|
- Some(Self::new(phys, width, height, stride))
|
||||||
|
+ Self::try_new(phys, width, height, stride)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
diff --git a/init.initfs.d/20_fbbootlogd.service b/init.initfs.d/20_fbbootlogd.service
|
||||||
|
index 5a8bf3c8..199c112a 100644
|
||||||
|
--- a/init.initfs.d/20_fbbootlogd.service
|
||||||
|
+++ b/init.initfs.d/20_fbbootlogd.service
|
||||||
|
@@ -6,0 +7,6 @@ cmd = "fbbootlogd"
|
||||||
|
+inherit_envs = [
|
||||||
|
+ "FRAMEBUFFER_ADDR",
|
||||||
|
+ "FRAMEBUFFER_WIDTH",
|
||||||
|
+ "FRAMEBUFFER_HEIGHT",
|
||||||
|
+ "FRAMEBUFFER_STRIDE",
|
||||||
|
+]
|
||||||
|
diff --git a/init.initfs.d/20_fbcond.service b/init.initfs.d/20_fbcond.service
|
||||||
|
index 8db3dfdb..e618b419 100644
|
||||||
|
--- a/init.initfs.d/20_fbcond.service
|
||||||
|
+++ b/init.initfs.d/20_fbcond.service
|
||||||
|
@@ -7,0 +8,6 @@ args = ["2"]
|
||||||
|
+inherit_envs = [
|
||||||
|
+ "FRAMEBUFFER_ADDR",
|
||||||
|
+ "FRAMEBUFFER_WIDTH",
|
||||||
|
+ "FRAMEBUFFER_HEIGHT",
|
||||||
|
+ "FRAMEBUFFER_STRIDE",
|
||||||
|
+]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
--- a/drivers/graphics/fbbootlogd/Cargo.toml
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/Cargo.toml
|
||||||
|
@@ -15,0 +16 @@ scheme-utils = { path = "../../../scheme-utils" }
|
||||||
|
+common = { path = "../../common" }
|
||||||
|
--- a/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
@@ -23,0 +24 @@ fn main() {
|
||||||
|
+ common::init();
|
||||||
|
--- a/drivers/graphics/fbcond/src/main.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/main.rs
|
||||||
|
@@ -18,0 +19 @@ fn main() {
|
||||||
|
+ common::init();
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
diff --git a/drivers/graphics/driver-graphics/src/lib.rs b/drivers/graphics/driver-graphics/src/lib.rs
|
||||||
|
index eab0be9c..b6683686 100644
|
||||||
|
--- a/drivers/graphics/driver-graphics/src/lib.rs
|
||||||
|
+++ b/drivers/graphics/driver-graphics/src/lib.rs
|
||||||
|
@@ -138 +138 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- pub fn new(mut adapter: T, scheme_name: String, early: bool) -> Self {
|
||||||
|
+ pub fn new(mut adapter: T, scheme_name: String, early: bool) -> io::Result<Self> {
|
||||||
|
@@ -140 +140,6 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- let socket = Socket::nonblock().expect("failed to create graphics scheme");
|
||||||
|
+ let socket = Socket::nonblock().map_err(|e| {
|
||||||
|
+ io::Error::new(
|
||||||
|
+ io::ErrorKind::Other,
|
||||||
|
+ format!("failed to create graphics scheme socket: {e}"),
|
||||||
|
+ )
|
||||||
|
+ })?;
|
||||||
|
@@ -143,2 +148,6 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- File::open("/scheme/debug/disable-graphical-debug")
|
||||||
|
- .expect("vesad: Failed to open /scheme/debug/disable-graphical-debug"),
|
||||||
|
+ File::open("/scheme/debug/disable-graphical-debug").map_err(|e| {
|
||||||
|
+ io::Error::new(
|
||||||
|
+ io::ErrorKind::Other,
|
||||||
|
+ format!("failed to open /scheme/debug/disable-graphical-debug: {e}"),
|
||||||
|
+ )
|
||||||
|
+ })?,
|
||||||
|
@@ -164,3 +173,12 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- let cap_id = inner.scheme_root().expect("failed to get this scheme root");
|
||||||
|
- register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id)
|
||||||
|
- .expect("failed to register graphics scheme root");
|
||||||
|
+ let cap_id = inner.scheme_root().map_err(|e| {
|
||||||
|
+ io::Error::new(
|
||||||
|
+ io::ErrorKind::Other,
|
||||||
|
+ format!("failed to get scheme root: {e}"),
|
||||||
|
+ )
|
||||||
|
+ })?;
|
||||||
|
+ register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id).map_err(|e| {
|
||||||
|
+ io::Error::new(
|
||||||
|
+ io::ErrorKind::Other,
|
||||||
|
+ format!("failed to register graphics scheme root: {e}"),
|
||||||
|
+ )
|
||||||
|
+ })?;
|
||||||
|
@@ -169 +187,6 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- DisplayHandle::new_early(&inner.scheme_name).unwrap()
|
||||||
|
+ DisplayHandle::new_early(&inner.scheme_name).map_err(|e| {
|
||||||
|
+ io::Error::new(
|
||||||
|
+ io::ErrorKind::Other,
|
||||||
|
+ format!("failed to create early display handle: {e}"),
|
||||||
|
+ )
|
||||||
|
+ })?
|
||||||
|
@@ -171 +194,6 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- DisplayHandle::new(&inner.scheme_name).unwrap()
|
||||||
|
+ DisplayHandle::new(&inner.scheme_name).map_err(|e| {
|
||||||
|
+ io::Error::new(
|
||||||
|
+ io::ErrorKind::Other,
|
||||||
|
+ format!("failed to create display handle: {e}"),
|
||||||
|
+ )
|
||||||
|
+ })?
|
||||||
|
@@ -174 +202 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- Self {
|
||||||
|
+ Ok(Self {
|
||||||
|
@@ -178 +206 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- }
|
||||||
|
+ })
|
||||||
|
diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
index a8b6cc60..990706c2 100644
|
||||||
|
--- a/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
+++ b/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
@@ -43 +43,8 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let mut scheme = GraphicsScheme::new(device, format!("display.ihdg.{}", name), false);
|
||||||
|
+ let mut scheme = match GraphicsScheme::new(device, format!("display.ihdg.{}", name), false) {
|
||||||
|
+ Ok(s) => s,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("ihdgd: failed to create GraphicsScheme: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs
|
||||||
|
index 5e9526fe..acbcb3fa 100644
|
||||||
|
--- a/drivers/graphics/vesad/src/main.rs
|
||||||
|
+++ b/drivers/graphics/vesad/src/main.rs
|
||||||
|
@@ -137,2 +137,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let mut scheme =
|
||||||
|
- GraphicsScheme::new(FbAdapter { framebuffers }, "display.vesa".to_owned(), true);
|
||||||
|
+ let mut scheme = match GraphicsScheme::new(FbAdapter { framebuffers }, "display.vesa".to_owned(), true) {
|
||||||
|
+ Ok(s) => s,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to create GraphicsScheme: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
diff --git a/drivers/graphics/virtio-gpud/src/scheme.rs b/drivers/graphics/virtio-gpud/src/scheme.rs
|
||||||
|
index 22a985ee..96d8f964 100644
|
||||||
|
--- a/drivers/graphics/virtio-gpud/src/scheme.rs
|
||||||
|
+++ b/drivers/graphics/virtio-gpud/src/scheme.rs
|
||||||
|
@@ -1,0 +2 @@ use std::fmt;
|
||||||
|
+use std::io;
|
||||||
|
@@ -511 +512 @@ impl<'a> GpuScheme {
|
||||||
|
- ) -> Result<GraphicsScheme<VirtGpuAdapter<'a>>, Error> {
|
||||||
|
+ ) -> io::Result<GraphicsScheme<VirtGpuAdapter<'a>>> {
|
||||||
|
@@ -522 +523 @@ impl<'a> GpuScheme {
|
||||||
|
- Ok(GraphicsScheme::new(
|
||||||
|
+ GraphicsScheme::new(
|
||||||
|
@@ -526 +527 @@ impl<'a> GpuScheme {
|
||||||
|
- ))
|
||||||
|
+ )
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
diff --git a/netstack/src/main.rs b/netstack/src/main.rs
|
||||||
|
index b18f3226..0e4c1486 100644
|
||||||
|
--- a/netstack/src/main.rs
|
||||||
|
+++ b/netstack/src/main.rs
|
||||||
|
@@ -103,2 +102,0 @@ fn run(daemon: daemon::Daemon) -> Result<()> {
|
||||||
|
- daemon.ready();
|
||||||
|
-
|
||||||
|
@@ -160,0 +159,2 @@ fn run(daemon: daemon::Daemon) -> Result<()> {
|
||||||
|
+ daemon.ready();
|
||||||
|
+
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs
|
||||||
|
index acbcb3fa..6f55913b 100644
|
||||||
|
--- a/drivers/graphics/vesad/src/main.rs
|
||||||
|
+++ b/drivers/graphics/vesad/src/main.rs
|
||||||
|
@@ -137,9 +136,0 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- let mut scheme = match GraphicsScheme::new(FbAdapter { framebuffers }, "display.vesa".to_owned(), true) {
|
||||||
|
- Ok(s) => s,
|
||||||
|
- Err(e) => {
|
||||||
|
- eprintln!("vesad: failed to create GraphicsScheme: {} — exiting", e);
|
||||||
|
- daemon.ready();
|
||||||
|
- std::process::exit(0);
|
||||||
|
- }
|
||||||
|
- };
|
||||||
|
-
|
||||||
|
@@ -152,0 +144,2 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
+ // EventQueue MUST be created before GraphicsScheme::new to avoid a deadlock with initnsmgr.
|
||||||
|
+ // See ihdgd fix (P0-driver-api-migration-fixes) for the same issue.
|
||||||
|
@@ -160,0 +154,10 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
+
|
||||||
|
+ let mut scheme = match GraphicsScheme::new(FbAdapter { framebuffers }, "display.vesa".to_owned(), true) {
|
||||||
|
+ Ok(s) => s,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ eprintln!("vesad: failed to create GraphicsScheme: {} — exiting", e);
|
||||||
|
+ daemon.ready();
|
||||||
|
+ std::process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs
|
||||||
|
index a9315902..75981cd9 100755
|
||||||
|
--- a/drivers/audio/ihdad/src/main.rs
|
||||||
|
+++ b/drivers/audio/ihdad/src/main.rs
|
||||||
|
@@ -40,7 +40,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
|
||||||
|
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
|
||||||
|
|
||||||
|
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("ihdad: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
{
|
||||||
|
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
|
||||||
|
diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
index 990706c2..4aa21caa 100644
|
||||||
|
--- a/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
+++ b/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
@@ -32,7 +32,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
let device = Device::new(&mut pcid_handle, &pci_config.func)
|
||||||
|
.expect("ihdgd: failed to initialize device");
|
||||||
|
|
||||||
|
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd");
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("ihdgd: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
// Needs to be before GraphicsScheme::new to avoid a deadlock due to initnsmgr blocking on
|
||||||
|
// /scheme/event as it is already blocked on opening /scheme/display.ihdg.*.
|
||||||
|
diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs
|
||||||
|
index d470e814..e372feda 100644
|
||||||
|
--- a/drivers/net/rtl8139d/src/main.rs
|
||||||
|
+++ b/drivers/net/rtl8139d/src/main.rs
|
||||||
|
@@ -57,7 +57,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
|
||||||
|
let bar = map_bar(&mut pcid_handle);
|
||||||
|
|
||||||
|
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8139d");
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8139d") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("rtl8139d: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
let mut scheme = NetworkScheme::new(
|
||||||
|
move || unsafe {
|
||||||
|
diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs
|
||||||
|
index 06d9ff58..29ebf799 100644
|
||||||
|
--- a/drivers/net/rtl8168d/src/main.rs
|
||||||
|
+++ b/drivers/net/rtl8168d/src/main.rs
|
||||||
|
@@ -57,7 +57,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
|
||||||
|
let bar = map_bar(&mut pcid_handle);
|
||||||
|
|
||||||
|
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8168d");
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8168d") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("rtl8168d: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
let mut scheme = NetworkScheme::new(
|
||||||
|
move || unsafe {
|
||||||
|
diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
index 39b0b048..f62cc055 100644
|
||||||
|
--- a/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
@@ -24,11 +24,13 @@ pub fn read_bsp_apic_id() -> io::Result<usize> {
|
||||||
|
buffer[0], buffer[1], buffer[2], buffer[3],
|
||||||
|
]))
|
||||||
|
} else {
|
||||||
|
- panic!(
|
||||||
|
- "`/scheme/irq` scheme responded with {} bytes, expected {}",
|
||||||
|
- bytes_read,
|
||||||
|
- std::mem::size_of::<usize>()
|
||||||
|
- );
|
||||||
|
+ return Err(io::Error::new(
|
||||||
|
+ io::ErrorKind::InvalidData,
|
||||||
|
+ format!(
|
||||||
|
+ "`/scheme/irq/bsp` responded with {} bytes, expected 4 or 8",
|
||||||
|
+ bytes_read
|
||||||
|
+ ),
|
||||||
|
+ ));
|
||||||
|
})
|
||||||
|
.or(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
@@ -83,7 +85,9 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||||
|
alignment: NonZeroU8,
|
||||||
|
count: u8,
|
||||||
|
) -> io::Result<Option<(u8, Vec<File>)>> {
|
||||||
|
- let cpu_id = u8::try_from(cpu_id).expect("usize cpu ids not implemented yet");
|
||||||
|
+ let cpu_id = u8::try_from(cpu_id).map_err(|_| {
|
||||||
|
+ io::Error::new(io::ErrorKind::InvalidInput, "cpu_id does not fit in u8")
|
||||||
|
+ })?;
|
||||||
|
if count == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
@@ -298,7 +302,7 @@ impl InterruptVector {
|
||||||
|
pub fn pci_allocate_interrupt_vector(
|
||||||
|
pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
|
||||||
|
driver: &str,
|
||||||
|
-) -> InterruptVector {
|
||||||
|
+) -> Option<InterruptVector> {
|
||||||
|
let features = pcid_handle.fetch_all_features();
|
||||||
|
|
||||||
|
let has_msi = features.iter().any(|feature| feature.is_msi());
|
||||||
|
@@ -307,7 +311,10 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
if has_msix {
|
||||||
|
let msix_info = match pcid_handle.feature_info(super::PciFeature::MsiX) {
|
||||||
|
super::PciFeatureInfo::MsiX(msix) => msix,
|
||||||
|
- _ => unreachable!(),
|
||||||
|
+ _ => {
|
||||||
|
+ log::warn!("{driver}: MSI-X feature info mismatch, falling back");
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
};
|
||||||
|
let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
|
||||||
|
|
||||||
|
@@ -327,11 +334,11 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
|
||||||
|
pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
|
||||||
|
|
||||||
|
- return InterruptVector {
|
||||||
|
+ return Some(InterruptVector {
|
||||||
|
irq_handle,
|
||||||
|
vector: 0,
|
||||||
|
kind: InterruptVectorKind::MsiX { table_entry: entry },
|
||||||
|
- };
|
||||||
|
+ });
|
||||||
|
}
|
||||||
|
log::warn!("{driver}: MSI-X vector allocation failed, falling back");
|
||||||
|
// fall through to MSI
|
||||||
|
@@ -339,25 +346,26 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
|
||||||
|
if has_msi {
|
||||||
|
if let Some(irq_handle) = allocate_first_msi_interrupt_on_bsp(pcid_handle) {
|
||||||
|
- return InterruptVector {
|
||||||
|
+ return Some(InterruptVector {
|
||||||
|
irq_handle,
|
||||||
|
vector: 0,
|
||||||
|
kind: InterruptVectorKind::Msi,
|
||||||
|
- };
|
||||||
|
+ });
|
||||||
|
}
|
||||||
|
log::warn!("{driver}: MSI allocation failed, falling back to legacy");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
|
||||||
|
// INTx# pin based interrupts.
|
||||||
|
- return InterruptVector {
|
||||||
|
+ return Some(InterruptVector {
|
||||||
|
irq_handle: irq.irq_handle(driver),
|
||||||
|
vector: 0,
|
||||||
|
kind: InterruptVectorKind::Legacy,
|
||||||
|
- };
|
||||||
|
+ });
|
||||||
|
}
|
||||||
|
|
||||||
|
- panic!("{driver}: no interrupts supported at all")
|
||||||
|
+ log::warn!("{driver}: no interrupts supported at all");
|
||||||
|
+ None
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME support MSI on non-x86 systems
|
||||||
|
@@ -365,15 +373,16 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
pub fn pci_allocate_interrupt_vector(
|
||||||
|
pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
|
||||||
|
driver: &str,
|
||||||
|
-) -> InterruptVector {
|
||||||
|
+) -> Option<InterruptVector> {
|
||||||
|
if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
|
||||||
|
// INTx# pin based interrupts.
|
||||||
|
- InterruptVector {
|
||||||
|
+ Some(InterruptVector {
|
||||||
|
irq_handle: irq.irq_handle(driver),
|
||||||
|
vector: 0,
|
||||||
|
kind: InterruptVectorKind::Legacy,
|
||||||
|
- }
|
||||||
|
+ })
|
||||||
|
} else {
|
||||||
|
- panic!("{driver}: no interrupts supported at all")
|
||||||
|
+ log::warn!("{driver}: no interrupts supported at all");
|
||||||
|
+ None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/drivers/storage/nvmed/src/main.rs b/drivers/storage/nvmed/src/main.rs
|
||||||
|
index beb1b689..59887186 100644
|
||||||
|
--- a/drivers/storage/nvmed/src/main.rs
|
||||||
|
+++ b/drivers/storage/nvmed/src/main.rs
|
||||||
|
@@ -77,7 +77,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
|
||||||
|
let address = unsafe { pcid_handle.map_bar(0).ptr };
|
||||||
|
|
||||||
|
- let interrupt_vector = irq_helpers::pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed");
|
||||||
|
+ let interrupt_vector = match irq_helpers::pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("nvmed: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
let iv = interrupt_vector.vector();
|
||||||
|
let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap();
|
||||||
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
--- a/drivers/gpio/intel-gpiod/src/main.rs
|
||||||
|
+++ b/drivers/gpio/intel-gpiod/src/main.rs
|
||||||
|
@@ -130,6 +130,12 @@
|
||||||
|
log::debug!("intel-gpiod: ACPI symbols are not ready yet");
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("intel-gpiod: ACPI symbols unavailable ({}), running with no GPIO controllers", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
--- a/drivers/i2c/dw-acpi-i2cd/src/main.rs
|
||||||
|
+++ b/drivers/i2c/dw-acpi-i2cd/src/main.rs
|
||||||
|
@@ -117,6 +117,12 @@
|
||||||
|
log::debug!("dw-acpi-i2cd: ACPI symbols are not ready yet");
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("dw-acpi-i2cd: ACPI symbols unavailable ({}), running with no I2C controllers", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
--- a/drivers/i2c/intel-lpss-i2cd/src/main.rs
|
||||||
|
+++ b/drivers/i2c/intel-lpss-i2cd/src/main.rs
|
||||||
|
@@ -117,6 +117,12 @@
|
||||||
|
log::debug!("intel-lpss-i2cd: ACPI symbols are not ready yet");
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("intel-lpss-i2cd: ACPI symbols unavailable ({}), running with no I2C controllers", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
--- a/drivers/gpio/i2c-gpio-expanderd/src/main.rs
|
||||||
|
+++ b/drivers/gpio/i2c-gpio-expanderd/src/main.rs
|
||||||
|
@@ -121,6 +121,12 @@
|
||||||
|
log::debug!("i2c-gpio-expanderd: ACPI symbols are not ready yet");
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("i2c-gpio-expanderd: ACPI symbols unavailable ({}), running with no GPIO expanders", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
--- a/drivers/input/i2c-hidd/src/acpi.rs
|
||||||
|
+++ b/drivers/input/i2c-hidd/src/acpi.rs
|
||||||
|
@@ -32,6 +32,12 @@
|
||||||
|
Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == ErrorKind::NotFound => {
|
||||||
|
+ log::info!("i2c-hidd: ACPI symbols unavailable ({}), running with no HID devices", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
--- a/drivers/input/intel-thc-hidd/src/main.rs
|
||||||
|
+++ b/drivers/input/intel-thc-hidd/src/main.rs
|
||||||
|
@@ -95,8 +95,20 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
|
||||||
|
- let entries =
|
||||||
|
- fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
|
||||||
|
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||||
|
+ Ok(entries) => entries,
|
||||||
|
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||||
|
+ log::debug!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||||
|
+ return Ok(None);
|
||||||
|
+ }
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), skipping companion resolution", err);
|
||||||
|
+ return Ok(None);
|
||||||
|
+ }
|
||||||
|
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
+ };
|
||||||
|
let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
@@ -136,8 +148,18 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
|
||||||
|
- let entries =
|
||||||
|
- fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
|
||||||
|
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||||
|
+ Ok(entries) => entries,
|
||||||
|
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||||
|
+ log::debug!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), running with no HID devices", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
+ };
|
||||||
|
let mut devices = BTreeSet::new();
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
--- a/drivers/hwd/src/backend/acpi.rs
|
||||||
|
+++ b/drivers/hwd/src/backend/acpi.rs
|
||||||
|
@@ -16,7 +16,20 @@
|
||||||
|
|
||||||
|
fn probe(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
|
// Read symbols from acpi scheme
|
||||||
|
- let entries = fs::read_dir("/scheme/acpi/symbols")?;
|
||||||
|
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||||
|
+ Ok(entries) => entries,
|
||||||
|
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||||
|
+ log::debug!("hwd: ACPI symbols are not ready yet");
|
||||||
|
+ return Ok(());
|
||||||
|
+ }
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("hwd: ACPI symbols unavailable ({}), running with no ACPI devices", err);
|
||||||
|
+ return Ok(());
|
||||||
|
+ }
|
||||||
|
+ Err(err) => return Err(err.into()),
|
||||||
|
+ };
|
||||||
|
// TODO: Reimplement with getdents?
|
||||||
|
let symbols_fd = libredox::Fd::open(
|
||||||
|
"/scheme/acpi/symbols",
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
--- a/drivers/usb/ucsid/src/main.rs
|
||||||
|
+++ b/drivers/usb/ucsid/src/main.rs
|
||||||
|
@@ -397,6 +397,12 @@
|
||||||
|
log::debug!("ucsid: ACPI symbols are not ready yet");
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("ucsid: ACPI symbols unavailable ({}), running with no UCSI devices", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs
|
||||||
|
index 641e3ef2..5878bf2d 100644
|
||||||
|
--- a/drivers/audio/ac97d/src/main.rs
|
||||||
|
+++ b/drivers/audio/ac97d/src/main.rs
|
||||||
|
@@ -22,8 +22,20 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
let mut name = pci_config.func.name();
|
||||||
|
name.push_str("_ac97");
|
||||||
|
|
||||||
|
- let bar0 = pci_config.func.bars[0].expect_port();
|
||||||
|
- let bar1 = pci_config.func.bars[1].expect_port();
|
||||||
|
+ let bar0 = match pci_config.func.bars[0].try_port() {
|
||||||
|
+ Some(port) => port,
|
||||||
|
+ None => {
|
||||||
|
+ eprintln!("ac97d: BAR 0 is not a port BAR");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ let bar1 = match pci_config.func.bars[1].try_port() {
|
||||||
|
+ Some(port) => port,
|
||||||
|
+ None => {
|
||||||
|
+ eprintln!("ac97d: BAR 1 is not a port BAR");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
let irq = pci_config
|
||||||
|
.func
|
||||||
|
diff --git a/drivers/graphics/ihdgd/src/device/mod.rs b/drivers/graphics/ihdgd/src/device/mod.rs
|
||||||
|
index ced9dd56..0423c617 100644
|
||||||
|
--- a/drivers/graphics/ihdgd/src/device/mod.rs
|
||||||
|
+++ b/drivers/graphics/ihdgd/src/device/mod.rs
|
||||||
|
@@ -246,7 +246,7 @@ impl Device {
|
||||||
|
};
|
||||||
|
|
||||||
|
let gttmm = {
|
||||||
|
- let (phys, size) = func.bars[0].expect_mem();
|
||||||
|
+ let (phys, size) = func.bars[0].try_mem().ok_or_else(|| Error::new(ENODEV))?;
|
||||||
|
Arc::new(MmioRegion::new(
|
||||||
|
phys,
|
||||||
|
size,
|
||||||
|
@@ -255,7 +255,7 @@ impl Device {
|
||||||
|
};
|
||||||
|
log::info!("GTTMM {:X?}", gttmm);
|
||||||
|
let gm = {
|
||||||
|
- let (phys, size) = func.bars[2].expect_mem();
|
||||||
|
+ let (phys, size) = func.bars[2].try_mem().ok_or_else(|| Error::new(ENODEV))?;
|
||||||
|
MmioRegion::new(phys, size, common::MemoryType::WriteCombining)?
|
||||||
|
};
|
||||||
|
log::info!("GM {:X?}", gm);
|
||||||
|
diff --git a/drivers/pcid/src/driver_interface/bar.rs b/drivers/pcid/src/driver_interface/bar.rs
|
||||||
|
index b2c1d35b..dea0dce4 100644
|
||||||
|
--- a/drivers/pcid/src/driver_interface/bar.rs
|
||||||
|
+++ b/drivers/pcid/src/driver_interface/bar.rs
|
||||||
|
@@ -29,27 +29,22 @@ impl PciBar {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- pub fn expect_port(&self) -> u16 {
|
||||||
|
+ pub fn try_port(&self) -> Option<u16> {
|
||||||
|
match *self {
|
||||||
|
- PciBar::Port(port) => port,
|
||||||
|
- PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => {
|
||||||
|
- panic!("expected port BAR, found memory BAR");
|
||||||
|
- }
|
||||||
|
- PciBar::None => panic!("expected BAR to exist"),
|
||||||
|
+ PciBar::Port(port) => Some(port),
|
||||||
|
+ _ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- pub fn expect_mem(&self) -> (usize, usize) {
|
||||||
|
+ pub fn try_mem(&self) -> Option<(usize, usize)> {
|
||||||
|
match *self {
|
||||||
|
- PciBar::Memory32 { addr, size } => (addr as usize, size as usize),
|
||||||
|
- PciBar::Memory64 { addr, size } => (
|
||||||
|
- addr.try_into()
|
||||||
|
- .expect("conversion from 64bit BAR to usize failed"),
|
||||||
|
- size.try_into()
|
||||||
|
- .expect("conversion from 64bit BAR size to usize failed"),
|
||||||
|
- ),
|
||||||
|
- PciBar::Port(_) => panic!("expected memory BAR, found port BAR"),
|
||||||
|
- PciBar::None => panic!("expected BAR to exist"),
|
||||||
|
+ PciBar::Memory32 { addr, size } => Some((addr as usize, size as usize)),
|
||||||
|
+ PciBar::Memory64 { addr, size } => {
|
||||||
|
+ let addr_usize = addr.try_into().ok()?;
|
||||||
|
+ let size_usize = size.try_into().ok()?;
|
||||||
|
+ Some((addr_usize, size_usize))
|
||||||
|
+ }
|
||||||
|
+ _ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs
|
||||||
|
index 7cecaa56..8776dd4a 100644
|
||||||
|
--- a/drivers/pcid/src/driver_interface/mod.rs
|
||||||
|
+++ b/drivers/pcid/src/driver_interface/mod.rs
|
||||||
|
@@ -457,7 +457,13 @@ impl PciFunctionHandle {
|
||||||
|
if let Some(mapped_bar) = mapped_bar {
|
||||||
|
mapped_bar
|
||||||
|
} else {
|
||||||
|
- let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem();
|
||||||
|
+ let (bar, bar_size) = match self.config.func.bars[bir as usize].try_mem() {
|
||||||
|
+ Some(bar) => bar,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("pcid: BAR {bir} is not a memory BAR");
|
||||||
|
+ process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
let ptr = match unsafe {
|
||||||
|
common::physmap(
|
||||||
|
diff --git a/drivers/pcid/src/driver_interface/msi.rs b/drivers/pcid/src/driver_interface/msi.rs
|
||||||
|
index 0ca68ec5..e7e3f082 100644
|
||||||
|
--- a/drivers/pcid/src/driver_interface/msi.rs
|
||||||
|
+++ b/drivers/pcid/src/driver_interface/msi.rs
|
||||||
|
@@ -80,8 +80,20 @@ impl MsixInfo {
|
||||||
|
let pba_offset = self.pba_offset as usize;
|
||||||
|
let pba_min_length = table_size.div_ceil(8);
|
||||||
|
|
||||||
|
- let (_, table_bar_size) = bars[self.table_bar as usize].expect_mem();
|
||||||
|
- let (_, pba_bar_size) = bars[self.pba_bar as usize].expect_mem();
|
||||||
|
+ let (_, table_bar_size) = match bars[self.table_bar as usize].try_mem() {
|
||||||
|
+ Some(bar) => bar,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("MSI-X table BAR {} is not a memory BAR", self.table_bar);
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ let (_, pba_bar_size) = match bars[self.pba_bar as usize].try_mem() {
|
||||||
|
+ Some(bar) => bar,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("MSI-X PBA BAR {} is not a memory BAR", self.pba_bar);
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
// Ensure that the table and PBA are within the BAR.
|
||||||
|
|
||||||
|
diff --git a/drivers/storage/ided/src/main.rs b/drivers/storage/ided/src/main.rs
|
||||||
|
index 4197217d..9615710b 100644
|
||||||
|
--- a/drivers/storage/ided/src/main.rs
|
||||||
|
+++ b/drivers/storage/ided/src/main.rs
|
||||||
|
@@ -43,7 +43,13 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
// Get controller DMA capable
|
||||||
|
let dma = pci_config.func.full_device_id.interface & 0x80 != 0;
|
||||||
|
|
||||||
|
- let busmaster_base = pci_config.func.bars[4].expect_port();
|
||||||
|
+ let busmaster_base = match pci_config.func.bars[4].try_port() {
|
||||||
|
+ Some(port) => port,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("ided: BAR 4 is not a port BAR");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
let (primary, primary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 {
|
||||||
|
panic!("TODO: IDE primary channel is PCI native");
|
||||||
|
} else {
|
||||||
|
diff --git a/drivers/vboxd/src/main.rs b/drivers/vboxd/src/main.rs
|
||||||
|
index bcb9bb15..52328e79 100644
|
||||||
|
--- a/drivers/vboxd/src/main.rs
|
||||||
|
+++ b/drivers/vboxd/src/main.rs
|
||||||
|
@@ -199,7 +199,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
let mut name = pci_config.func.name();
|
||||||
|
name.push_str("_vbox");
|
||||||
|
|
||||||
|
- let bar0 = pci_config.func.bars[0].expect_port();
|
||||||
|
+ let bar0 = match pci_config.func.bars[0].try_port() {
|
||||||
|
+ Some(port) => port,
|
||||||
|
+ None => {
|
||||||
|
+ eprintln!("vboxd: BAR 0 is not a port BAR");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
let irq = pci_config
|
||||||
|
.func
|
||||||
|
diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs
|
||||||
|
index 5631ef67..06f0ba1a 100644
|
||||||
|
--- a/drivers/virtio-core/src/probe.rs
|
||||||
|
+++ b/drivers/virtio-core/src/probe.rs
|
||||||
|
@@ -55,7 +55,13 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
- let (addr, _) = pci_config.func.bars[capability.bar as usize].expect_mem();
|
||||||
|
+ let (addr, _) = match pci_config.func.bars[capability.bar as usize].try_mem() {
|
||||||
|
+ Some(bar) => bar,
|
||||||
|
+ None => {
|
||||||
|
+ log::warn!("virtio-core: BAR {} is not a memory BAR, skipping capability", capability.bar);
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
let address = unsafe {
|
||||||
|
let addr = addr + capability.offset as usize;
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index b05102f6..a9d47e09 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -204,6 +204,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("acpid: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
daemon::Daemon::new(daemon);
|
||||||
|
}
|
||||||
|
diff --git a/drivers/common/src/lib.rs b/drivers/common/src/lib.rs
|
||||||
|
index b6661e3a..a55014b9 100644
|
||||||
|
--- a/drivers/common/src/lib.rs
|
||||||
|
+++ b/drivers/common/src/lib.rs
|
||||||
|
@@ -29,22 +29,13 @@ use std::sync::OnceLock;
|
||||||
|
static MEMORY_ROOT_FD: OnceLock<libredox::Fd> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Initializes a file descriptor to be used as the root memory for a driver.
|
||||||
|
-///
|
||||||
|
-/// # Panics
|
||||||
|
-///
|
||||||
|
-/// This function will panic if:
|
||||||
|
-/// - `libredox` is unable to open a file descriptor.
|
||||||
|
-/// - The memory root file descriptor has already been set (this function has already been called).
|
||||||
|
-pub fn init() {
|
||||||
|
- if MEMORY_ROOT_FD
|
||||||
|
- .set(
|
||||||
|
- libredox::Fd::open("/scheme/memory/scheme-root", 0, 0)
|
||||||
|
- .expect("drivers common: failed to open memory root fd"),
|
||||||
|
- )
|
||||||
|
- .is_err()
|
||||||
|
- {
|
||||||
|
- panic!("drivers common: failed to set memory root fd");
|
||||||
|
- }
|
||||||
|
+pub fn init() -> std::io::Result<()> {
|
||||||
|
+ let fd = libredox::Fd::open("/scheme/memory/scheme-root", 0, 0)
|
||||||
|
+ .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, format!("failed to open memory root fd: {err}")))?;
|
||||||
|
+ MEMORY_ROOT_FD
|
||||||
|
+ .set(fd)
|
||||||
|
+ .map_err(|_| std::io::Error::new(std::io::ErrorKind::AlreadyExists, "memory root fd already initialized"))?;
|
||||||
|
+ Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the memory root file descriptor.
|
||||||
|
diff --git a/drivers/gpio/intel-gpiod/src/main.rs b/drivers/gpio/intel-gpiod/src/main.rs
|
||||||
|
index aa651713..e9671068 100644
|
||||||
|
--- a/drivers/gpio/intel-gpiod/src/main.rs
|
||||||
|
+++ b/drivers/gpio/intel-gpiod/src/main.rs
|
||||||
|
@@ -95,7 +95,7 @@ fn daemon_runner(daemon: daemon::Daemon) -> ! {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
||||||
|
- common::init();
|
||||||
|
+ common::init().map_err(|err| anyhow::anyhow!("failed to initialize common: {err}"))?;
|
||||||
|
|
||||||
|
let controllers =
|
||||||
|
discover_controllers(SUPPORTED_IDS).context("failed to discover Intel GPIO controllers")?;
|
||||||
|
diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
index 055e1db6..b8ad2294 100644
|
||||||
|
--- a/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
@@ -21,7 +21,7 @@ use crate::scheme::FbbootlogScheme;
|
||||||
|
mod scheme;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("fbbootlogd: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
daemon::SchemeDaemon::new(daemon);
|
||||||
|
}
|
||||||
|
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/main.rs b/drivers/graphics/fbcond/src/main.rs
|
||||||
|
index 2e428353..003527ba 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/main.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/main.rs
|
||||||
|
@@ -16,7 +16,7 @@ mod scheme;
|
||||||
|
mod text;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("fbcond: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
daemon::SchemeDaemon::new(daemon);
|
||||||
|
}
|
||||||
|
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs
|
||||||
|
index 6f55913b..5fee9041 100644
|
||||||
|
--- a/drivers/graphics/vesad/src/main.rs
|
||||||
|
+++ b/drivers/graphics/vesad/src/main.rs
|
||||||
|
@@ -12,7 +12,7 @@ use crate::scheme::{FbAdapter, FrameBuffer};
|
||||||
|
mod scheme;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("vesad: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
daemon::Daemon::new(daemon);
|
||||||
|
}
|
||||||
|
fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
diff --git a/drivers/i2c/dw-acpi-i2cd/src/main.rs b/drivers/i2c/dw-acpi-i2cd/src/main.rs
|
||||||
|
index 796d0ed3..f3491103 100644
|
||||||
|
--- a/drivers/i2c/dw-acpi-i2cd/src/main.rs
|
||||||
|
+++ b/drivers/i2c/dw-acpi-i2cd/src/main.rs
|
||||||
|
@@ -80,7 +80,7 @@ fn daemon_runner(daemon: daemon::Daemon) -> ! {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
||||||
|
- common::init();
|
||||||
|
+ common::init().map_err(|err| anyhow::anyhow!("failed to initialize common: {err}"))?;
|
||||||
|
|
||||||
|
let controllers = discover_controllers(SUPPORTED_IDS)
|
||||||
|
.context("failed to discover DesignWare ACPI I2C controllers")?;
|
||||||
|
diff --git a/drivers/i2c/intel-lpss-i2cd/src/main.rs b/drivers/i2c/intel-lpss-i2cd/src/main.rs
|
||||||
|
index 7b29d737..e651610b 100644
|
||||||
|
--- a/drivers/i2c/intel-lpss-i2cd/src/main.rs
|
||||||
|
+++ b/drivers/i2c/intel-lpss-i2cd/src/main.rs
|
||||||
|
@@ -80,7 +80,7 @@ fn daemon_runner(daemon: daemon::Daemon) -> ! {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
||||||
|
- common::init();
|
||||||
|
+ common::init().map_err(|err| anyhow::anyhow!("failed to initialize common: {err}"))?;
|
||||||
|
|
||||||
|
let controllers = discover_controllers(SUPPORTED_IDS)
|
||||||
|
.context("failed to discover Intel LPSS ACPI I2C controllers")?;
|
||||||
|
diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs
|
||||||
|
index 8776dd4a..3b6a98a1 100644
|
||||||
|
--- a/drivers/pcid/src/driver_interface/mod.rs
|
||||||
|
+++ b/drivers/pcid/src/driver_interface/mod.rs
|
||||||
|
@@ -491,7 +491,7 @@ impl PciFunctionHandle {
|
||||||
|
|
||||||
|
pub fn pci_daemon<F: FnOnce(Daemon, PciFunctionHandle) -> !>(f: F) -> ! {
|
||||||
|
Daemon::new(|daemon| {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("pci_daemon: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
let pcid_handle = PciFunctionHandle::connect_default();
|
||||||
|
f(daemon, pcid_handle)
|
||||||
|
})
|
||||||
|
diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs
|
||||||
|
index bda473e4..e844249f 100644
|
||||||
|
--- a/drivers/pcid/src/main.rs
|
||||||
|
+++ b/drivers/pcid/src/main.rs
|
||||||
|
@@ -245,7 +245,7 @@ fn enable_function(
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("pcid: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
daemon::Daemon::new(daemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/drivers/usb/usbctl/src/main.rs b/drivers/usb/usbctl/src/main.rs
|
||||||
|
index 9b5773d9..51a63f75 100644
|
||||||
|
--- a/drivers/usb/usbctl/src/main.rs
|
||||||
|
+++ b/drivers/usb/usbctl/src/main.rs
|
||||||
|
@@ -2,7 +2,7 @@ use clap::{Arg, Command};
|
||||||
|
use xhcid_interface::{PortId, XhciClientHandle};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("usbctl: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
let matches = Command::new("usbctl")
|
||||||
|
.arg(
|
||||||
|
Arg::new("SCHEME")
|
||||||
|
diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs
|
||||||
|
index 2c8b9876..7e69842a 100644
|
||||||
|
--- a/drivers/usb/usbhubd/src/main.rs
|
||||||
|
+++ b/drivers/usb/usbhubd/src/main.rs
|
||||||
|
@@ -6,7 +6,7 @@ use xhcid_interface::{
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
- common::init();
|
||||||
|
+ if let Err(err) = common::init() { eprintln!("usbhubd: failed to initialize common: {err}"); std::process::exit(1); }
|
||||||
|
let mut args = env::args().skip(1);
|
||||||
|
|
||||||
|
const USAGE: &'static str = "usbhubd <scheme> <port> <interface>";
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
diff --git a/drivers/graphics/driver-graphics/src/lib.rs b/drivers/graphics/driver-graphics/src/lib.rs
|
||||||
|
index b6683686..cd064ae6 100644
|
||||||
|
--- a/drivers/graphics/driver-graphics/src/lib.rs
|
||||||
|
+++ b/drivers/graphics/driver-graphics/src/lib.rs
|
||||||
|
@@ -133 +133 @@ pub struct GraphicsScheme<T: GraphicsAdapter> {
|
||||||
|
- inputd_handle: DisplayHandle,
|
||||||
|
+ inputd_handle: Option<DisplayHandle>,
|
||||||
|
@@ -187,6 +187 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- DisplayHandle::new_early(&inner.scheme_name).map_err(|e| {
|
||||||
|
- io::Error::new(
|
||||||
|
- io::ErrorKind::Other,
|
||||||
|
- format!("failed to create early display handle: {e}"),
|
||||||
|
- )
|
||||||
|
- })?
|
||||||
|
+ DisplayHandle::new_early(&inner.scheme_name)
|
||||||
|
@@ -194,6 +189,13 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- DisplayHandle::new(&inner.scheme_name).map_err(|e| {
|
||||||
|
- io::Error::new(
|
||||||
|
- io::ErrorKind::Other,
|
||||||
|
- format!("failed to create display handle: {e}"),
|
||||||
|
- )
|
||||||
|
- })?
|
||||||
|
+ DisplayHandle::new(&inner.scheme_name)
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ let inputd_handle = match display_handle {
|
||||||
|
+ Ok(handle) => Some(handle),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::warn!(
|
||||||
|
+ "{}: display input handle unavailable ({}), continuing without VT input",
|
||||||
|
+ inner.scheme_name,
|
||||||
|
+ err
|
||||||
|
+ );
|
||||||
|
+ None
|
||||||
|
+ }
|
||||||
|
@@ -204 +206 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- inputd_handle: display_handle,
|
||||||
|
+ inputd_handle,
|
||||||
|
@@ -213,2 +215,2 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- pub fn inputd_event_handle(&self) -> BorrowedFd<'_> {
|
||||||
|
- self.inputd_handle.inner()
|
||||||
|
+ pub fn inputd_event_handle(&self) -> Option<BorrowedFd<'_>> {
|
||||||
|
+ self.inputd_handle.as_ref().map(|h| h.inner())
|
||||||
|
@@ -238,2 +240,5 @@ impl<T: GraphicsAdapter> GraphicsScheme<T> {
|
||||||
|
- while let Some(vt_event) = self
|
||||||
|
- .inputd_handle
|
||||||
|
+ let inputd_handle = match self.inputd_handle.as_mut() {
|
||||||
|
+ Some(h) => h,
|
||||||
|
+ None => return,
|
||||||
|
+ };
|
||||||
|
+ while let Some(vt_event) = inputd_handle
|
||||||
|
|
||||||
|
diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
index 4aa21caa..964510f5 100644
|
||||||
|
--- a/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
+++ b/drivers/graphics/ihdgd/src/main.rs
|
||||||
|
@@ -65,0 +66 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ if let Some(inputd_fd) = scheme.inputd_event_handle() {
|
||||||
|
@@ -68 +69 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- scheme.inputd_event_handle().as_raw_fd() as usize,
|
||||||
|
+ inputd_fd.as_raw_fd() as usize,
|
||||||
|
@@ -72,0 +74 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ }
|
||||||
|
|
||||||
|
diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs
|
||||||
|
index 6f55913b..c19629aa 100644
|
||||||
|
--- a/drivers/graphics/vesad/src/main.rs
|
||||||
|
+++ b/drivers/graphics/vesad/src/main.rs
|
||||||
|
@@ -163,0 +164 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
+ if let Some(inputd_fd) = scheme.inputd_event_handle() {
|
||||||
|
@@ -165 +166 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
- scheme.inputd_event_handle().as_raw_fd() as usize,
|
||||||
|
+ inputd_fd.as_raw_fd() as usize,
|
||||||
|
@@ -172,0 +174 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
+ }
|
||||||
|
|
||||||
|
diff --git a/drivers/graphics/virtio-gpud/src/main.rs b/drivers/graphics/virtio-gpud/src/main.rs
|
||||||
|
index b27f4c56..3aa41c74 100644
|
||||||
|
--- a/drivers/graphics/virtio-gpud/src/main.rs
|
||||||
|
+++ b/drivers/graphics/virtio-gpud/src/main.rs
|
||||||
|
@@ -552,0 +553 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
|
||||||
|
+ if let Some(inputd_fd) = scheme.inputd_event_handle() {
|
||||||
|
@@ -555 +556 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
|
||||||
|
- scheme.inputd_event_handle().as_raw_fd() as usize,
|
||||||
|
+ inputd_fd.as_raw_fd() as usize,
|
||||||
|
@@ -559,0 +561 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
diff --git a/init.d/10_dhcpd.service b/init.d/10_dhcpd.service
|
||||||
|
index daba3bd1..494fb536 100644
|
||||||
|
--- a/init.d/10_dhcpd.service
|
||||||
|
+++ b/init.d/10_dhcpd.service
|
||||||
|
@@ -3 +3 @@ description = "Configure network using DHCP"
|
||||||
|
-requires_weak = [
|
||||||
|
+requires = [
|
||||||
|
diff --git a/init.initfs.d/61_dhcpd.service b/init.initfs.d/61_dhcpd.service
|
||||||
|
index 37379761..858bc297 100644
|
||||||
|
--- a/init.initfs.d/61_dhcpd.service
|
||||||
|
+++ b/init.initfs.d/61_dhcpd.service
|
||||||
|
@@ -3 +3 @@ description = "DHCP Client"
|
||||||
|
-requires_weak = ["60_smolnetd.service"]
|
||||||
|
+requires = ["60_smolnetd.service"]
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
||||||
|
index 74997be0..5c881334 100644
|
||||||
|
--- a/drivers/acpid/src/acpi.rs
|
||||||
|
+++ b/drivers/acpid/src/acpi.rs
|
||||||
|
@@ -450,0 +451,2 @@ pub struct AcpiContext {
|
||||||
|
+
|
||||||
|
+ pub thermal_state: crate::thermal::ThermalState,
|
||||||
|
@@ -529,0 +532,2 @@ impl AcpiContext {
|
||||||
|
+
|
||||||
|
+ thermal_state: crate::thermal::ThermalState::new(),
|
||||||
|
@@ -559,0 +564,3 @@ impl AcpiContext {
|
||||||
|
+ // Discover thermal zones if AML is ready.
|
||||||
|
+ this.thermal_state.refresh(&this);
|
||||||
|
+
|
||||||
|
@@ -632,0 +640,24 @@ impl AcpiContext {
|
||||||
|
+ /// Discover thermal zone names by scanning the AML namespace under `\_TZ`.
|
||||||
|
+ pub fn thermal_zone_names(&self) -> Result<Vec<String>, AmlEvalError> {
|
||||||
|
+ let mut symbols = self.aml_symbols.write();
|
||||||
|
+ let interpreter = symbols.aml_context_mut()?;
|
||||||
|
+ let mut ns = interpreter.namespace.lock();
|
||||||
|
+
|
||||||
|
+ let mut names = Vec::new();
|
||||||
|
+ let _ = ns.traverse(|level_aml_name, _level| {
|
||||||
|
+ let name_str = aml_to_symbol(level_aml_name);
|
||||||
|
+ if name_str.starts_with("\\_TZ_.TZ") || name_str.starts_with("_TZ_.TZ") {
|
||||||
|
+ let after_prefix = if name_str.starts_with("\\_TZ_.") {
|
||||||
|
+ &name_str[7..]
|
||||||
|
+ } else {
|
||||||
|
+ &name_str[6..]
|
||||||
|
+ };
|
||||||
|
+ if !after_prefix.contains('.') {
|
||||||
|
+ names.push(after_prefix.to_string());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Ok(true)
|
||||||
|
+ });
|
||||||
|
+ Ok(names)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index a9d47e09..91336ba7 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -17,0 +18 @@ mod dmi;
|
||||||
|
+mod thermal;
|
||||||
|
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
|
||||||
|
index 85c425c1..b92327be 100644
|
||||||
|
--- a/drivers/acpid/src/scheme.rs
|
||||||
|
+++ b/drivers/acpid/src/scheme.rs
|
||||||
|
@@ -47,0 +48,2 @@ enum HandleKind<'a> {
|
||||||
|
+ Thermal,
|
||||||
|
+ ThermalZone(String),
|
||||||
|
@@ -60,0 +63,2 @@ impl HandleKind<'_> {
|
||||||
|
+ Self::Thermal => true,
|
||||||
|
+ Self::ThermalZone(_) => false,
|
||||||
|
@@ -74,0 +79,2 @@ impl HandleKind<'_> {
|
||||||
|
+ Self::Thermal => 0,
|
||||||
|
+ Self::ThermalZone(ref text) => text.len(),
|
||||||
|
@@ -229,0 +236,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ ["thermal"] => HandleKind::Thermal,
|
||||||
|
+ ["thermal", zone] => {
|
||||||
|
+ if let Some(tz) = self.ctx.thermal_state.zone_by_name(zone) {
|
||||||
|
+ HandleKind::ThermalZone(tz.to_text())
|
||||||
|
+ } else {
|
||||||
|
+ return Err(Error::new(ENOENT));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
@@ -317,0 +333 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ HandleKind::ThermalZone(ref text) => text.as_bytes(),
|
||||||
|
@@ -344,0 +361 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ (DirentKind::Directory, "thermal"),
|
||||||
|
@@ -403,0 +421,17 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ HandleKind::Thermal => {
|
||||||
|
+ for (idx, zone) in self
|
||||||
|
+ .ctx
|
||||||
|
+ .thermal_state
|
||||||
|
+ .zones()
|
||||||
|
+ .iter()
|
||||||
|
+ .enumerate()
|
||||||
|
+ .skip(opaque_offset as usize)
|
||||||
|
+ {
|
||||||
|
+ buf.entry(DirEntry {
|
||||||
|
+ inode: 0,
|
||||||
|
+ next_opaque_id: idx as u64 + 1,
|
||||||
|
+ name: &zone.name,
|
||||||
|
+ kind: DirentKind::Regular,
|
||||||
|
+ })?;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs
|
||||||
|
index c66cccd1..58ab2999 100644
|
||||||
|
--- a/drivers/net/e1000d/src/main.rs
|
||||||
|
+++ b/drivers/net/e1000d/src/main.rs
|
||||||
|
@@ -5,0 +6 @@ use event::{user_data, EventQueue};
|
||||||
|
+use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
||||||
|
@@ -28,5 +28,0 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let irq = pci_config
|
||||||
|
- .func
|
||||||
|
- .legacy_interrupt_line
|
||||||
|
- .expect("e1000d: no legacy interrupts supported");
|
||||||
|
-
|
||||||
|
@@ -35 +31,7 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let mut irq_file = irq.irq_handle("e1000d");
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "e1000d") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("e1000d: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -58 +60 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.as_raw_fd() as usize,
|
||||||
|
+ irq_file.irq_handle().as_raw_fd() as usize,
|
||||||
|
@@ -79 +81 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.read(&mut irq).expect("e1000d: IRQ read failed");
|
||||||
|
+ irq_file.irq_handle().read(&mut irq).expect("e1000d: IRQ read failed");
|
||||||
|
@@ -81 +83 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.write(&mut irq).expect("e1000d: IRQ ack failed");
|
||||||
|
+ irq_file.irq_handle().write(&mut irq).expect("e1000d: IRQ ack failed");
|
||||||
|
diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs
|
||||||
|
index 4a6ce74d..f06898ec 100644
|
||||||
|
--- a/drivers/net/ixgbed/src/main.rs
|
||||||
|
+++ b/drivers/net/ixgbed/src/main.rs
|
||||||
|
@@ -5,0 +6 @@ use event::{user_data, EventQueue};
|
||||||
|
+use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
||||||
|
@@ -22,5 +22,0 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let irq = pci_config
|
||||||
|
- .func
|
||||||
|
- .legacy_interrupt_line
|
||||||
|
- .expect("ixgbed: no legacy interrupts supported");
|
||||||
|
-
|
||||||
|
@@ -29 +25,7 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let mut irq_file = irq.irq_handle("ixgbed");
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "ixgbed") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("ixgbed: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -54 +56 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.as_raw_fd() as usize,
|
||||||
|
+ irq_file.irq_handle().as_raw_fd() as usize,
|
||||||
|
@@ -75 +77 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.read(&mut irq).unwrap();
|
||||||
|
+ irq_file.irq_handle().read(&mut irq).unwrap();
|
||||||
|
@@ -77 +79 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.write(&mut irq).unwrap();
|
||||||
|
+ irq_file.irq_handle().write(&mut irq).unwrap();
|
||||||
|
diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml
|
||||||
|
index d97ff398..fcaf4b19 100644
|
||||||
|
--- a/drivers/net/ixgbed/Cargo.toml
|
||||||
|
+++ b/drivers/net/ixgbed/Cargo.toml
|
||||||
|
@@ -9,0 +10 @@ libredox.workspace = true
|
||||||
|
+log.workspace = true
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs
|
||||||
|
index 5878bf2d..a7e87373 100644
|
||||||
|
--- a/drivers/audio/ac97d/src/main.rs
|
||||||
|
+++ b/drivers/audio/ac97d/src/main.rs
|
||||||
|
@@ -5,0 +6 @@ use event::{user_data, EventQueue};
|
||||||
|
+use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
||||||
|
@@ -40,5 +40,0 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let irq = pci_config
|
||||||
|
- .func
|
||||||
|
- .legacy_interrupt_line
|
||||||
|
- .expect("ac97d: no legacy interrupts supported");
|
||||||
|
-
|
||||||
|
@@ -57 +53,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let mut irq_file = irq.irq_handle("ac97d");
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "ac97d") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ log::error!("ac97d: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
@@ -74 +76 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.as_raw_fd() as usize,
|
||||||
|
+ irq_file.irq_handle().as_raw_fd() as usize,
|
||||||
|
@@ -101 +103 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.read(&mut irq).expect("ac97d: failed");
|
||||||
|
+ irq_file.irq_handle().read(&mut irq).expect("ac97d: failed");
|
||||||
|
@@ -106 +108 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file.write(&mut irq).expect("ac97d: failed");
|
||||||
|
+ irq_file.irq_handle().write(&mut irq).expect("ac97d: failed");
|
||||||
|
diff --git a/drivers/storage/ahcid/src/main.rs b/drivers/storage/ahcid/src/main.rs
|
||||||
|
index 4c7a1412..da92036b 100644
|
||||||
|
--- a/drivers/storage/ahcid/src/main.rs
|
||||||
|
+++ b/drivers/storage/ahcid/src/main.rs
|
||||||
|
@@ -9,0 +10 @@ use event::{EventFlags, RawEventQueue};
|
||||||
|
+use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
||||||
|
@@ -26,5 +26,0 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let irq = pci_config
|
||||||
|
- .func
|
||||||
|
- .legacy_interrupt_line
|
||||||
|
- .expect("ahcid: no legacy interrupts supported");
|
||||||
|
-
|
||||||
|
@@ -40,0 +37,8 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ let irq_file = match pci_allocate_interrupt_vector(&mut pcid_handle, "ahcid") {
|
||||||
|
+ Some(iv) => iv,
|
||||||
|
+ None => {
|
||||||
|
+ error!("ahcid: no interrupt vector available, exiting");
|
||||||
|
+ std::process::exit(1);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
@@ -57,2 +61 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- let mut irq_file = irq.irq_handle("ahcid");
|
||||||
|
- let irq_fd = irq_file.as_raw_fd() as usize;
|
||||||
|
+ let irq_fd = irq_file.irq_handle().as_raw_fd() as usize;
|
||||||
|
@@ -77 +80 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- if irq_file
|
||||||
|
+ if irq_file.irq_handle()
|
||||||
|
@@ -95 +98 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
- irq_file
|
||||||
|
+ irq_file.irq_handle()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
--- a/drivers/audio/ac97d/src/main.rs
|
||||||
|
+++ b/drivers/audio/ac97d/src/main.rs
|
||||||
|
@@ -20 +20 @@ fn main() {
|
||||||
|
-fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
--- a/drivers/thermald/src/main.rs 2026-05-20 17:21:01.313394180 +0300
|
||||||
|
+++ b/drivers/thermald/src/main.rs 2026-05-20 17:16:58.080082312 +0300
|
||||||
|
@@ -2 +2 @@
|
||||||
|
-use std::{thread, time};
|
||||||
|
+use std::{fs, thread, time};
|
||||||
|
@@ -4,6 +4,36 @@
|
||||||
|
-fn read_temp() -> Option<f32> {
|
||||||
|
- for zone in 0..4 {
|
||||||
|
- let path = format!("/scheme/acpi/thermal_zone/{}/temperature", zone);
|
||||||
|
- if let Ok(data) = std::fs::read_to_string(&path) {
|
||||||
|
- if let Ok(mv) = data.trim().parse::<u32>() {
|
||||||
|
- return Some(mv as f32 / 1000.0);
|
||||||
|
+const THERMAL_POLL_S: u64 = 5;
|
||||||
|
+const CRITICAL_TEMP: f32 = 85.0;
|
||||||
|
+const WARNING_TEMP: f32 = 70.0;
|
||||||
|
+
|
||||||
|
+fn read_acpi_thermal_zones() -> Vec<(String, f32)> {
|
||||||
|
+ let mut temps = Vec::new();
|
||||||
|
+ if let Ok(entries) = fs::read_dir("/scheme/acpi/thermal") {
|
||||||
|
+ for entry in entries.flatten() {
|
||||||
|
+ let name = entry.file_name().into_string().unwrap_or_default();
|
||||||
|
+ if name.starts_with('.') {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ let path = format!("/scheme/acpi/thermal/{}/temperature", name);
|
||||||
|
+ if let Ok(data) = fs::read_to_string(&path) {
|
||||||
|
+ if let Ok(temp_c) = data.trim().parse::<f32>() {
|
||||||
|
+ temps.push((name, temp_c));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ temps
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn read_coretemp_cpus() -> Vec<(String, f32)> {
|
||||||
|
+ let mut temps = Vec::new();
|
||||||
|
+ if let Ok(entries) = fs::read_dir("/scheme/coretemp") {
|
||||||
|
+ for entry in entries.flatten() {
|
||||||
|
+ let name = entry.file_name().into_string().unwrap_or_default();
|
||||||
|
+ if name.starts_with('.') || !name.starts_with("cpu") {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ let path = format!("/scheme/coretemp/{}/temperature", name);
|
||||||
|
+ if let Ok(data) = fs::read_to_string(&path) {
|
||||||
|
+ if let Ok(temp_c) = data.trim().parse::<f32>() {
|
||||||
|
+ temps.push((name, temp_c));
|
||||||
|
+ }
|
||||||
|
@@ -13 +43 @@
|
||||||
|
- None
|
||||||
|
+ temps
|
||||||
|
@@ -17,2 +47,7 @@
|
||||||
|
- common::setup_logging("system", "thermald", "thermald",
|
||||||
|
- common::output_level(), common::file_level());
|
||||||
|
+ common::setup_logging(
|
||||||
|
+ "system",
|
||||||
|
+ "thermald",
|
||||||
|
+ "thermald",
|
||||||
|
+ common::output_level(),
|
||||||
|
+ common::file_level(),
|
||||||
|
+ );
|
||||||
|
@@ -19,0 +55 @@
|
||||||
|
+
|
||||||
|
@@ -21,5 +57,17 @@
|
||||||
|
- if let Some(temp) = read_temp() {
|
||||||
|
- if temp > 85.0 {
|
||||||
|
- log::error!("thermald: CRITICAL {:.1}C", temp);
|
||||||
|
- } else if temp > 70.0 {
|
||||||
|
- log::warn!("thermald: WARNING {:.1}C", temp);
|
||||||
|
+ let acpi_temps = read_acpi_thermal_zones();
|
||||||
|
+ let cpu_temps = read_coretemp_cpus();
|
||||||
|
+
|
||||||
|
+ let mut max_temp: f32 = 0.0;
|
||||||
|
+ let mut max_source = String::new();
|
||||||
|
+
|
||||||
|
+ for (name, temp) in &acpi_temps {
|
||||||
|
+ if *temp > max_temp {
|
||||||
|
+ max_temp = *temp;
|
||||||
|
+ max_source = format!("ACPI {}", name);
|
||||||
|
+ }
|
||||||
|
+ if *temp > CRITICAL_TEMP {
|
||||||
|
+ log::error!("thermald: CRITICAL ACPI {} = {:.1}C", name, temp);
|
||||||
|
+ } else if *temp > WARNING_TEMP {
|
||||||
|
+ log::warn!("thermald: WARNING ACPI {} = {:.1}C", name, temp);
|
||||||
|
+ } else {
|
||||||
|
+ log::debug!("thermald: ACPI {} = {:.1}C", name, temp);
|
||||||
|
@@ -28 +76,22 @@
|
||||||
|
- thread::sleep(time::Duration::from_secs(5));
|
||||||
|
+
|
||||||
|
+ for (name, temp) in &cpu_temps {
|
||||||
|
+ if *temp > max_temp {
|
||||||
|
+ max_temp = *temp;
|
||||||
|
+ max_source = format!("coretemp {}", name);
|
||||||
|
+ }
|
||||||
|
+ if *temp > CRITICAL_TEMP {
|
||||||
|
+ log::error!("thermald: CRITICAL CPU {} = {:.1}C", name, temp);
|
||||||
|
+ } else if *temp > WARNING_TEMP {
|
||||||
|
+ log::warn!("thermald: WARNING CPU {} = {:.1}C", name, temp);
|
||||||
|
+ } else {
|
||||||
|
+ log::debug!("thermald: CPU {} = {:.1}C", name, temp);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if max_temp > 0.0 {
|
||||||
|
+ log::info!("thermald: max temp = {:.1}C from {}", max_temp, max_source);
|
||||||
|
+ } else {
|
||||||
|
+ log::warn!("thermald: no temperature sources available");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ thread::sleep(time::Duration::from_secs(THERMAL_POLL_S));
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
||||||
|
index 5c881334..ea480bb7 100644
|
||||||
|
--- a/drivers/acpid/src/acpi.rs
|
||||||
|
+++ b/drivers/acpid/src/acpi.rs
|
||||||
|
@@ -452,0 +453 @@ pub struct AcpiContext {
|
||||||
|
+ pub fan_state: crate::fan::FanState,
|
||||||
|
@@ -533,0 +535 @@ impl AcpiContext {
|
||||||
|
+ fan_state: crate::fan::FanState::new(),
|
||||||
|
@@ -564 +566 @@ impl AcpiContext {
|
||||||
|
- // Discover thermal zones if AML is ready.
|
||||||
|
+ // Discover thermal zones and fan devices if AML is ready.
|
||||||
|
@@ -565,0 +568 @@ impl AcpiContext {
|
||||||
|
+ this.fan_state.refresh(&this);
|
||||||
|
@@ -663,0 +667,24 @@ impl AcpiContext {
|
||||||
|
+ /// Discover fan device names by scanning the AML namespace under `\_TZ`.
|
||||||
|
+ pub fn fan_device_names(&self) -> Result<Vec<String>, AmlEvalError> {
|
||||||
|
+ let mut symbols = self.aml_symbols.write();
|
||||||
|
+ let interpreter = symbols.aml_context_mut()?;
|
||||||
|
+ let mut ns = interpreter.namespace.lock();
|
||||||
|
+
|
||||||
|
+ let mut names = Vec::new();
|
||||||
|
+ let _ = ns.traverse(|level_aml_name, _level| {
|
||||||
|
+ let name_str = aml_to_symbol(level_aml_name);
|
||||||
|
+ if name_str.starts_with("\\_TZ_.FAN") || name_str.starts_with("_TZ_.FAN") {
|
||||||
|
+ let after_prefix = if name_str.starts_with("\\_TZ_.") {
|
||||||
|
+ &name_str[7..]
|
||||||
|
+ } else {
|
||||||
|
+ &name_str[6..]
|
||||||
|
+ };
|
||||||
|
+ if !after_prefix.contains('.') {
|
||||||
|
+ names.push(after_prefix.to_string());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Ok(true)
|
||||||
|
+ });
|
||||||
|
+ Ok(names)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
diff --git a/drivers/acpid/src/fan.rs b/drivers/acpid/src/fan.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..8b4fd533
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/acpid/src/fan.rs
|
||||||
|
@@ -0,0 +1,177 @@
|
||||||
|
+use acpi::aml::namespace::AmlName;
|
||||||
|
+use acpi::aml::AmlError;
|
||||||
|
+use std::str::FromStr;
|
||||||
|
+use std::sync::{Arc, RwLock};
|
||||||
|
+
|
||||||
|
+use crate::acpi::{AcpiContext, AmlEvalError};
|
||||||
|
+use amlserde::AmlSerdeValue;
|
||||||
|
+
|
||||||
|
+/// A discovered ACPI fan device.
|
||||||
|
+#[derive(Clone, Debug)]
|
||||||
|
+pub struct FanDevice {
|
||||||
|
+ pub name: String,
|
||||||
|
+ /// Current speed level from _FST (0 = off, higher = faster).
|
||||||
|
+ pub current_level: Option<u64>,
|
||||||
|
+ /// Current speed in RPM from _FST (0xFFFFFFFF = unknown).
|
||||||
|
+ pub current_rpm: Option<u64>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl FanDevice {
|
||||||
|
+ fn from_device_eval(
|
||||||
|
+ ctx: &AcpiContext,
|
||||||
|
+ device_name: &str,
|
||||||
|
+ ) -> Result<Self, FanError> {
|
||||||
|
+ let aml_prefix = format!("\\_TZ_.{device_name}");
|
||||||
|
+
|
||||||
|
+ let mut fan = FanDevice {
|
||||||
|
+ name: device_name.to_owned(),
|
||||||
|
+ current_level: None,
|
||||||
|
+ current_rpm: None,
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ // Evaluate _FST (fan status). ACPI spec: returns a package:
|
||||||
|
+ // { Revision (Integer), CurrentSpeedLevel (Integer), CurrentSpeedRPM (Integer) }
|
||||||
|
+ if let Ok(fst_name) = AmlName::from_str(&format!("{aml_prefix}._FST")) {
|
||||||
|
+ match ctx.aml_eval(fst_name, Vec::new()) {
|
||||||
|
+ Ok(value) => {
|
||||||
|
+ if let AmlSerdeValue::Package { contents: elements } = value {
|
||||||
|
+ if elements.len() >= 2 {
|
||||||
|
+ fan.current_level = extract_u64(&elements[1]);
|
||||||
|
+ }
|
||||||
|
+ if elements.len() >= 3 {
|
||||||
|
+ fan.current_rpm = extract_u64(&elements[2]);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::debug!("Fan device {device_name}: _FST eval failed: {e:?}");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ Ok(fan)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Produce a text summary suitable for scheme read().
|
||||||
|
+ pub fn to_text(&self) -> String {
|
||||||
|
+ let mut s = String::new();
|
||||||
|
+ s.push_str(&format!("name={}\n", self.name));
|
||||||
|
+ s.push_str(&format!(
|
||||||
|
+ "current_level={}\n",
|
||||||
|
+ format_option_u64(self.current_level)
|
||||||
|
+ ));
|
||||||
|
+ s.push_str(&format!(
|
||||||
|
+ "current_rpm={}\n",
|
||||||
|
+ format_option_u64(self.current_rpm)
|
||||||
|
+ ));
|
||||||
|
+ s
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn format_option_u64(value: Option<u64>) -> String {
|
||||||
|
+ match value {
|
||||||
|
+ Some(v) => {
|
||||||
|
+ if v == 0xFFFFFFFF {
|
||||||
|
+ "unknown".to_string()
|
||||||
|
+ } else {
|
||||||
|
+ format!("{v}")
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ None => "na".to_string(),
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn extract_u64(value: &AmlSerdeValue) -> Option<u64> {
|
||||||
|
+ match value {
|
||||||
|
+ AmlSerdeValue::Integer(i) => Some(*i as u64),
|
||||||
|
+ _ => None,
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[derive(Debug)]
|
||||||
|
+pub enum FanError {
|
||||||
|
+ AmlError(AmlError),
|
||||||
|
+ EvalError(AmlEvalError),
|
||||||
|
+ NotFound,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl From<AmlError> for FanError {
|
||||||
|
+ fn from(value: AmlError) -> Self {
|
||||||
|
+ FanError::AmlError(value)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl From<AmlEvalError> for FanError {
|
||||||
|
+ fn from(value: AmlEvalError) -> Self {
|
||||||
|
+ FanError::EvalError(value)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Discovers all ACPI fan devices under the `\_TZ` namespace.
|
||||||
|
+///
|
||||||
|
+/// Walks the AML namespace looking for objects directly under `_TZ` whose
|
||||||
|
+/// names start with `FAN` (e.g., `FAN0`, `FAN1`). For each, evaluates
|
||||||
|
+/// fan status methods and returns a populated [`FanDevice`].
|
||||||
|
+pub fn discover_fans(ctx: &AcpiContext) -> Vec<FanDevice> {
|
||||||
|
+ let mut fans = Vec::new();
|
||||||
|
+
|
||||||
|
+ let fan_names = match ctx.fan_device_names() {
|
||||||
|
+ Ok(names) => names,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::debug!("Fan device discovery failed: {e:?}");
|
||||||
|
+ return fans;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ for child_name in fan_names {
|
||||||
|
+ match FanDevice::from_device_eval(ctx, &child_name) {
|
||||||
|
+ Ok(fan) => {
|
||||||
|
+ log::info!(
|
||||||
|
+ "Fan device discovered: {} = level={:?}, rpm={:?}",
|
||||||
|
+ fan.name,
|
||||||
|
+ fan.current_level,
|
||||||
|
+ fan.current_rpm,
|
||||||
|
+ );
|
||||||
|
+ fans.push(fan);
|
||||||
|
+ }
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("Fan device {child_name}: discovery failed: {e:?}");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fans
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Cached fan device state, refreshed on demand.
|
||||||
|
+pub struct FanState {
|
||||||
|
+ fans: RwLock<Vec<FanDevice>>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl FanState {
|
||||||
|
+ pub fn new() -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ fans: RwLock::new(Vec::new()),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn refresh(&self, ctx: &AcpiContext) {
|
||||||
|
+ let discovered = discover_fans(ctx);
|
||||||
|
+ if let Ok(mut fans) = self.fans.write() {
|
||||||
|
+ *fans = discovered;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn fans(&self) -> Vec<FanDevice> {
|
||||||
|
+ self.fans.read().map(|g| g.clone()).unwrap_or_default()
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn fan_by_name(&self, name: &str) -> Option<FanDevice> {
|
||||||
|
+ self.fans
|
||||||
|
+ .read()
|
||||||
|
+ .ok()?
|
||||||
|
+ .iter()
|
||||||
|
+ .find(|f| f.name == name)
|
||||||
|
+ .cloned()
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index 91336ba7..c7b8ff3e 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -18,0 +19 @@ mod thermal;
|
||||||
|
+mod fan;
|
||||||
|
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
|
||||||
|
index b92327be..905b42ff 100644
|
||||||
|
--- a/drivers/acpid/src/scheme.rs
|
||||||
|
+++ b/drivers/acpid/src/scheme.rs
|
||||||
|
@@ -49,0 +50,2 @@ enum HandleKind<'a> {
|
||||||
|
+ Fan,
|
||||||
|
+ FanDevice(String),
|
||||||
|
@@ -64,0 +67,2 @@ impl HandleKind<'_> {
|
||||||
|
+ Self::Fan => true,
|
||||||
|
+ Self::FanDevice(_) => false,
|
||||||
|
@@ -80,0 +85,2 @@ impl HandleKind<'_> {
|
||||||
|
+ Self::Fan => 0,
|
||||||
|
+ Self::FanDevice(ref text) => text.len(),
|
||||||
|
@@ -243,0 +250,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ ["fan"] => HandleKind::Fan,
|
||||||
|
+ ["fan", device] => {
|
||||||
|
+ if let Some(fan) = self.ctx.fan_state.fan_by_name(device) {
|
||||||
|
+ HandleKind::FanDevice(fan.to_text())
|
||||||
|
+ } else {
|
||||||
|
+ return Err(Error::new(ENOENT));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
@@ -333,0 +348 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ HandleKind::FanDevice(ref text) => text.as_bytes(),
|
||||||
|
@@ -361,0 +377 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ (DirentKind::Directory, "fan"),
|
||||||
|
@@ -437,0 +454,17 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ HandleKind::Fan => {
|
||||||
|
+ for (idx, fan) in self
|
||||||
|
+ .ctx
|
||||||
|
+ .fan_state
|
||||||
|
+ .fans()
|
||||||
|
+ .iter()
|
||||||
|
+ .enumerate()
|
||||||
|
+ .skip(opaque_offset as usize)
|
||||||
|
+ {
|
||||||
|
+ buf.entry(DirEntry {
|
||||||
|
+ inode: 0,
|
||||||
|
+ next_opaque_id: idx as u64 + 1,
|
||||||
|
+ name: &fan.name,
|
||||||
|
+ kind: DirentKind::Regular,
|
||||||
|
+ })?;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
diff --git a/drivers/thermald/src/main.rs b/drivers/thermald/src/main.rs
|
||||||
|
index 10c4b531..b8d271b5 100644
|
||||||
|
--- a/drivers/thermald/src/main.rs
|
||||||
|
+++ b/drivers/thermald/src/main.rs
|
||||||
|
@@ -45,0 +46,31 @@ fn read_coretemp_cpus() -> Vec<(String, f32)> {
|
||||||
|
+fn read_acpi_fans() -> Vec<(String, Option<u64>, Option<u64>)> {
|
||||||
|
+ let mut fans = Vec::new();
|
||||||
|
+ if let Ok(entries) = fs::read_dir("/scheme/acpi/fan") {
|
||||||
|
+ for entry in entries.flatten() {
|
||||||
|
+ let name = entry.file_name().into_string().unwrap_or_default();
|
||||||
|
+ if name.starts_with('.') {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ let path = format!("/scheme/acpi/fan/{}/status", name);
|
||||||
|
+ let mut level = None;
|
||||||
|
+ let mut rpm = None;
|
||||||
|
+ if let Ok(data) = fs::read_to_string(&path) {
|
||||||
|
+ for line in data.lines() {
|
||||||
|
+ if let Some(val) = line.strip_prefix("current_level=") {
|
||||||
|
+ if val != "na" && val != "unknown" {
|
||||||
|
+ level = val.parse::<u64>().ok();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if let Some(val) = line.strip_prefix("current_rpm=") {
|
||||||
|
+ if val != "na" && val != "unknown" {
|
||||||
|
+ rpm = val.parse::<u64>().ok();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ fans.push((name, level, rpm));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ fans
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
@@ -58,0 +90 @@ fn main() -> Result<()> {
|
||||||
|
+ let fans = read_acpi_fans();
|
||||||
|
@@ -90,0 +123,14 @@ fn main() -> Result<()> {
|
||||||
|
+ for (name, level, rpm) in &fans {
|
||||||
|
+ match (level, rpm) {
|
||||||
|
+ (Some(l), Some(r)) => {
|
||||||
|
+ log::info!("thermald: fan {} = level {}, {} RPM", name, l, r);
|
||||||
|
+ }
|
||||||
|
+ (Some(l), None) => {
|
||||||
|
+ log::info!("thermald: fan {} = level {}", name, l);
|
||||||
|
+ }
|
||||||
|
+ _ => {
|
||||||
|
+ log::debug!("thermald: fan {} = status unavailable", name);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
index f62cc055..8bfbc604 100644
|
||||||
|
--- a/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||||
|
@@ -266,0 +267 @@ pub struct InterruptVector {
|
||||||
|
+ cpu_id: usize,
|
||||||
|
@@ -284,0 +286,18 @@ impl InterruptVector {
|
||||||
|
+ pub fn cpu_id(&self) -> usize {
|
||||||
|
+ self.cpu_id
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Log the IRQ affinity for this vector.
|
||||||
|
+ pub fn log_affinity(&self, driver: &str) {
|
||||||
|
+ let kind_str = match self.kind {
|
||||||
|
+ InterruptVectorKind::Legacy => "legacy",
|
||||||
|
+ InterruptVectorKind::Msi => "MSI",
|
||||||
|
+ InterruptVectorKind::MsiX { .. } => "MSI-X",
|
||||||
|
+ };
|
||||||
|
+ log::info!(
|
||||||
|
+ "{driver}: IRQ affinity = {kind_str} on CPU {} vector {}",
|
||||||
|
+ self.cpu_id,
|
||||||
|
+ self.vector
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
@@ -331,0 +351 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ log::info!("{driver}: allocated MSI-X interrupt on CPU {bsp_cpu_id}");
|
||||||
|
@@ -339,0 +360 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ cpu_id: bsp_cpu_id,
|
||||||
|
@@ -348,0 +370,3 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ let bsp_cpu_id = read_bsp_apic_id()
|
||||||
|
+ .unwrap_or_else(|err| panic!("{driver}: failed to read BSP APIC ID: {err}"));
|
||||||
|
+ log::info!("{driver}: allocated MSI interrupt on CPU {bsp_cpu_id}");
|
||||||
|
@@ -351,0 +376 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ cpu_id: bsp_cpu_id,
|
||||||
|
@@ -359,0 +385,2 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ let bsp_cpu_id = read_bsp_apic_id().unwrap_or(0);
|
||||||
|
+ log::info!("{driver}: allocated legacy INTx interrupt on CPU {bsp_cpu_id}");
|
||||||
|
@@ -362,0 +390 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ cpu_id: bsp_cpu_id,
|
||||||
|
@@ -378,0 +407,2 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ let bsp_cpu_id = read_bsp_apic_id().unwrap_or(0);
|
||||||
|
+ log::info!("{driver}: allocated legacy INTx interrupt on CPU {bsp_cpu_id}");
|
||||||
|
@@ -381,0 +412 @@ pub fn pci_allocate_interrupt_vector(
|
||||||
|
+ cpu_id: bsp_cpu_id,
|
||||||
@@ -0,0 +1,681 @@
|
|||||||
|
diff --git a/drivers/acpid/src/dmi.rs b/drivers/acpid/src/dmi.rs
|
||||||
|
new file mode 100644
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/acpid/src/dmi.rs
|
||||||
|
@@ -0,0 +1,350 @@
|
||||||
|
+use std::fmt;
|
||||||
|
+use syscall::PAGE_SIZE;
|
||||||
|
+
|
||||||
|
+use crate::acpi::PhysmapGuard;
|
||||||
|
+
|
||||||
|
+#[derive(Clone, Debug, Default)]
|
||||||
|
+pub struct DmiStrings {
|
||||||
|
+ pub sys_vendor: Option<String>,
|
||||||
|
+ pub board_vendor: Option<String>,
|
||||||
|
+ pub board_name: Option<String>,
|
||||||
|
+ pub board_version: Option<String>,
|
||||||
|
+ pub product_name: Option<String>,
|
||||||
|
+ pub product_version: Option<String>,
|
||||||
|
+ pub bios_version: Option<String>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl DmiStrings {
|
||||||
|
+ pub fn to_text(&self) -> String {
|
||||||
|
+ let mut text = String::new();
|
||||||
|
+ if let Some(ref v) = self.sys_vendor {
|
||||||
|
+ text.push_str("sys_vendor=");
|
||||||
|
+ text.push_str(v);
|
||||||
|
+ text.push('\n');
|
||||||
|
+ }
|
||||||
|
+ if let Some(ref v) = self.board_vendor {
|
||||||
|
+ text.push_str("board_vendor=");
|
||||||
|
+ text.push_str(v);
|
||||||
|
+ text.push('\n');
|
||||||
|
+ }
|
||||||
|
+ if let Some(ref v) = self.board_name {
|
||||||
|
+ text.push_str("board_name=");
|
||||||
|
+ text.push_str(v);
|
||||||
|
+ text.push('\n');
|
||||||
|
+ }
|
||||||
|
+ if let Some(ref v) = self.board_version {
|
||||||
|
+ text.push_str("board_version=");
|
||||||
|
+ text.push_str(v);
|
||||||
|
+ text.push('\n');
|
||||||
|
+ }
|
||||||
|
+ if let Some(ref v) = self.product_name {
|
||||||
|
+ text.push_str("product_name=");
|
||||||
|
+ text.push_str(v);
|
||||||
|
+ text.push('\n');
|
||||||
|
+ }
|
||||||
|
+ if let Some(ref v) = self.product_version {
|
||||||
|
+ text.push_str("product_version=");
|
||||||
|
+ text.push_str(v);
|
||||||
|
+ text.push('\n');
|
||||||
|
+ }
|
||||||
|
+ if let Some(ref v) = self.bios_version {
|
||||||
|
+ text.push_str("bios_version=");
|
||||||
|
+ text.push_str(v);
|
||||||
|
+ text.push('\n');
|
||||||
|
+ }
|
||||||
|
+ text
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[derive(Debug)]
|
||||||
|
+pub enum DmiError {
|
||||||
|
+ InvalidData,
|
||||||
|
+ Io(std::io::Error),
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl fmt::Display for DmiError {
|
||||||
|
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
+ match self {
|
||||||
|
+ Self::InvalidData => write!(f, "invalid SMBIOS data"),
|
||||||
|
+ Self::Io(e) => write!(f, "I/O error: {}", e),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl From<std::io::Error> for DmiError {
|
||||||
|
+ fn from(e: std::io::Error) -> Self {
|
||||||
|
+ DmiError::Io(e)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Scan physical memory for SMBIOS entry point and parse DMI strings.
|
||||||
|
+pub fn read_smbios_dmi() -> Result<DmiStrings, DmiError> {
|
||||||
|
+ // SMBIOS entry point is in the BIOS ROM area 0xF0000-0xFFFFF
|
||||||
|
+ let scan_start = 0xF_0000usize;
|
||||||
|
+ let scan_size = 0x10_0000usize - scan_start;
|
||||||
|
+ let start_page = scan_start / PAGE_SIZE * PAGE_SIZE;
|
||||||
|
+ let start_offset = scan_start % PAGE_SIZE;
|
||||||
|
+ let page_count = (start_offset + scan_size).div_ceil(PAGE_SIZE);
|
||||||
|
+
|
||||||
|
+ let pages = PhysmapGuard::map(start_page, page_count)?;
|
||||||
|
+ let bios_region = &pages[start_offset..start_offset + scan_size];
|
||||||
|
+
|
||||||
|
+ // Search for 64-bit entry point first (_SM3_), then 32-bit (_SM_)
|
||||||
|
+ // Entry points must be 16-byte aligned
|
||||||
|
+ let mut entry_point_addr = None;
|
||||||
|
+ let mut is_64bit = false;
|
||||||
|
+
|
||||||
|
+ for offset in (0..bios_region.len().saturating_sub(5)).step_by(16) {
|
||||||
|
+ if bios_region[offset..].starts_with(b"_SM3_") {
|
||||||
|
+ entry_point_addr = Some(scan_start + offset);
|
||||||
|
+ is_64bit = true;
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if entry_point_addr.is_none() {
|
||||||
|
+ for offset in (0..bios_region.len().saturating_sub(4)).step_by(16) {
|
||||||
|
+ if bios_region[offset..].starts_with(b"_SM_") {
|
||||||
|
+ // Verify intermediate anchor "_DMI_" at offset 0x10 from anchor start
|
||||||
|
+ let dmi_offset = offset + 0x10;
|
||||||
|
+ if dmi_offset + 5 <= bios_region.len()
|
||||||
|
+ && bios_region[dmi_offset..dmi_offset + 5].starts_with(b"_DMI_")
|
||||||
|
+ {
|
||||||
|
+ entry_point_addr = Some(scan_start + offset);
|
||||||
|
+ is_64bit = false;
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let entry_addr = match entry_point_addr {
|
||||||
|
+ Some(addr) => addr,
|
||||||
|
+ None => {
|
||||||
|
+ log::warn!("SMBIOS entry point not found in 0xF0000-0xFFFFF");
|
||||||
|
+ return Ok(DmiStrings::default());
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ log::info!(
|
||||||
|
+ "Found SMBIOS {} entry point at {:#x}",
|
||||||
|
+ if is_64bit { "3.0" } else { "2.x" },
|
||||||
|
+ entry_addr
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ if is_64bit {
|
||||||
|
+ parse_smbios_64(entry_addr)
|
||||||
|
+ } else {
|
||||||
|
+ parse_smbios_32(entry_addr)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn parse_smbios_32(entry_addr: usize) -> Result<DmiStrings, DmiError> {
|
||||||
|
+ // 32-bit entry point is at least 0x1F bytes
|
||||||
|
+ let page = entry_addr / PAGE_SIZE * PAGE_SIZE;
|
||||||
|
+ let offset = entry_addr % PAGE_SIZE;
|
||||||
|
+ let pages = PhysmapGuard::map(page, 1)?;
|
||||||
|
+
|
||||||
|
+ if pages.len() < offset + 0x1F {
|
||||||
|
+ log::warn!("SMBIOS 32-bit entry point truncated");
|
||||||
|
+ return Err(DmiError::InvalidData);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let ep = &pages[offset..];
|
||||||
|
+ let ep_len = ep[0x05] as usize;
|
||||||
|
+ if ep_len < 0x1F || pages.len() < offset + ep_len {
|
||||||
|
+ log::warn!("SMBIOS 32-bit entry point length invalid: {}", ep_len);
|
||||||
|
+ return Err(DmiError::InvalidData);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let checksum = ep[..ep_len].iter().fold(0u8, |sum, &b| sum.wrapping_add(b));
|
||||||
|
+ if checksum != 0 {
|
||||||
|
+ log::warn!("SMBIOS 32-bit entry point checksum failed");
|
||||||
|
+ return Err(DmiError::InvalidData);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let table_len = u16::from_le_bytes([ep[0x16], ep[0x17]]) as usize;
|
||||||
|
+ let table_addr = u32::from_le_bytes([ep[0x18], ep[0x19], ep[0x1A], ep[0x1B]]) as usize;
|
||||||
|
+
|
||||||
|
+ log::info!("SMBIOS 32-bit: table at {:#x}, len {}", table_addr, table_len);
|
||||||
|
+
|
||||||
|
+ parse_smbios_structures(table_addr, table_len)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn parse_smbios_64(entry_addr: usize) -> Result<DmiStrings, DmiError> {
|
||||||
|
+ // 64-bit entry point is at least 0x18 bytes
|
||||||
|
+ let page = entry_addr / PAGE_SIZE * PAGE_SIZE;
|
||||||
|
+ let offset = entry_addr % PAGE_SIZE;
|
||||||
|
+ let pages = PhysmapGuard::map(page, 1)?;
|
||||||
|
+
|
||||||
|
+ if pages.len() < offset + 0x18 {
|
||||||
|
+ log::warn!("SMBIOS 64-bit entry point truncated");
|
||||||
|
+ return Err(DmiError::InvalidData);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let ep = &pages[offset..];
|
||||||
|
+ let ep_len = ep[0x06] as usize;
|
||||||
|
+ if ep_len < 0x18 || pages.len() < offset + ep_len {
|
||||||
|
+ log::warn!("SMBIOS 64-bit entry point length invalid: {}", ep_len);
|
||||||
|
+ return Err(DmiError::InvalidData);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let checksum = ep[..ep_len].iter().fold(0u8, |sum, &b| sum.wrapping_add(b));
|
||||||
|
+ if checksum != 0 {
|
||||||
|
+ log::warn!("SMBIOS 64-bit entry point checksum failed");
|
||||||
|
+ return Err(DmiError::InvalidData);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let table_max_size =
|
||||||
|
+ u32::from_le_bytes([ep[0x0C], ep[0x0D], ep[0x0E], ep[0x0F]]) as usize;
|
||||||
|
+ let table_addr = u64::from_le_bytes([
|
||||||
|
+ ep[0x10], ep[0x11], ep[0x12], ep[0x13], ep[0x14], ep[0x15], ep[0x16], ep[0x17],
|
||||||
|
+ ]) as usize;
|
||||||
|
+
|
||||||
|
+ log::info!(
|
||||||
|
+ "SMBIOS 64-bit: table at {:#x}, max size {}",
|
||||||
|
+ table_addr,
|
||||||
|
+ table_max_size
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ parse_smbios_structures(table_addr, table_max_size)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn parse_smbios_structures(table_addr: usize, table_len: usize) -> Result<DmiStrings, DmiError> {
|
||||||
|
+ if table_addr == 0 || table_len == 0 {
|
||||||
|
+ log::warn!("SMBIOS structure table address or length is zero");
|
||||||
|
+ return Ok(DmiStrings::default());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Map the structure table. It may span multiple pages.
|
||||||
|
+ let start_page = table_addr / PAGE_SIZE * PAGE_SIZE;
|
||||||
|
+ let start_offset = table_addr % PAGE_SIZE;
|
||||||
|
+ let total_needed = start_offset + table_len;
|
||||||
|
+ let page_count = total_needed.div_ceil(PAGE_SIZE);
|
||||||
|
+
|
||||||
|
+ let pages = PhysmapGuard::map(start_page, page_count)?;
|
||||||
|
+ if pages.len() < total_needed {
|
||||||
|
+ log::warn!("SMBIOS structure table mapping truncated");
|
||||||
|
+ return Err(DmiError::InvalidData);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let table = &pages[start_offset..start_offset + table_len];
|
||||||
|
+
|
||||||
|
+ let mut dmi = DmiStrings::default();
|
||||||
|
+ let mut pos = 0;
|
||||||
|
+
|
||||||
|
+ while pos + 4 <= table.len() {
|
||||||
|
+ let stype = table[pos];
|
||||||
|
+ let slen = table[pos + 1] as usize;
|
||||||
|
+ let _handle = u16::from_le_bytes([table[pos + 2], table[pos + 3]]);
|
||||||
|
+
|
||||||
|
+ if slen < 4 {
|
||||||
|
+ log::warn!(
|
||||||
|
+ "Malformed SMBIOS structure at offset {}, type {}, len {}",
|
||||||
|
+ pos,
|
||||||
|
+ stype,
|
||||||
|
+ slen
|
||||||
|
+ );
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if pos + slen > table.len() {
|
||||||
|
+ log::warn!(
|
||||||
|
+ "SMBIOS structure at offset {} extends past table end", pos
|
||||||
|
+ );
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Parse strings after the formatted section
|
||||||
|
+ let strings_start = pos + slen;
|
||||||
|
+ let mut strings = Vec::new();
|
||||||
|
+ let mut s = strings_start;
|
||||||
|
+
|
||||||
|
+ while s < table.len() {
|
||||||
|
+ // Find null terminator
|
||||||
|
+ let mut end = s;
|
||||||
|
+ while end < table.len() && table[end] != 0 {
|
||||||
|
+ end += 1;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if end >= table.len() {
|
||||||
|
+ log::warn!("Unterminated SMBIOS strings at offset {}", s);
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let string = std::str::from_utf8(&table[s..end]).unwrap_or("").to_string();
|
||||||
|
+ strings.push(string);
|
||||||
|
+
|
||||||
|
+ s = end + 1;
|
||||||
|
+
|
||||||
|
+ // Double null terminates the string set
|
||||||
|
+ if s < table.len() && table[s] == 0 {
|
||||||
|
+ s += 1;
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ match stype {
|
||||||
|
+ 0 => {
|
||||||
|
+ // BIOS Information
|
||||||
|
+ if slen > 5 {
|
||||||
|
+ let idx = table[pos + 5] as usize;
|
||||||
|
+ if idx > 0 && idx <= strings.len() {
|
||||||
|
+ dmi.bios_version = Some(strings[idx - 1].clone());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ 1 => {
|
||||||
|
+ // System Information
|
||||||
|
+ if slen > 4 {
|
||||||
|
+ let idx = table[pos + 4] as usize;
|
||||||
|
+ if idx > 0 && idx <= strings.len() {
|
||||||
|
+ dmi.sys_vendor = Some(strings[idx - 1].clone());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if slen > 5 {
|
||||||
|
+ let idx = table[pos + 5] as usize;
|
||||||
|
+ if idx > 0 && idx <= strings.len() {
|
||||||
|
+ dmi.product_name = Some(strings[idx - 1].clone());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if slen > 6 {
|
||||||
|
+ let idx = table[pos + 6] as usize;
|
||||||
|
+ if idx > 0 && idx <= strings.len() {
|
||||||
|
+ dmi.product_version = Some(strings[idx - 1].clone());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ 2 => {
|
||||||
|
+ // Base Board Information
|
||||||
|
+ if slen > 4 {
|
||||||
|
+ let idx = table[pos + 4] as usize;
|
||||||
|
+ if idx > 0 && idx <= strings.len() {
|
||||||
|
+ dmi.board_vendor = Some(strings[idx - 1].clone());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if slen > 5 {
|
||||||
|
+ let idx = table[pos + 5] as usize;
|
||||||
|
+ if idx > 0 && idx <= strings.len() {
|
||||||
|
+ dmi.board_name = Some(strings[idx - 1].clone());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if slen > 6 {
|
||||||
|
+ let idx = table[pos + 6] as usize;
|
||||||
|
+ if idx > 0 && idx <= strings.len() {
|
||||||
|
+ dmi.board_version = Some(strings[idx - 1].clone());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ 127 => {
|
||||||
|
+ // End-of-table marker
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ _ => {}
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pos = s;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ Ok(dmi)
|
||||||
|
+}
|
||||||
|
|
||||||
|
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
||||||
|
--- a/drivers/acpid/src/acpi.rs
|
||||||
|
+++ b/drivers/acpid/src/acpi.rs
|
||||||
|
@@ -95,12 +95,12 @@
|
||||||
|
BadChecksum,
|
||||||
|
}
|
||||||
|
|
||||||
|
-struct PhysmapGuard {
|
||||||
|
+pub struct PhysmapGuard {
|
||||||
|
virt: *const u8,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
impl PhysmapGuard {
|
||||||
|
- fn map(page: usize, page_count: usize) -> std::io::Result<Self> {
|
||||||
|
+ pub fn map(page: usize, page_count: usize) -> std::io::Result<Self> {
|
||||||
|
let size = page_count * PAGE_SIZE;
|
||||||
|
let virt = unsafe {
|
||||||
|
common::physmap(page, size, common::Prot::RO, common::MemoryType::default())
|
||||||
|
@@ -245,26 +245,55 @@
|
||||||
|
symbol_cache: FxHashMap<String, String>,
|
||||||
|
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||||
|
aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
||||||
|
+ pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmlSymbols {
|
||||||
|
- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> Self {
|
||||||
|
+ pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>, pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>) -> Self {
|
||||||
|
Self {
|
||||||
|
aml_context: None,
|
||||||
|
symbol_cache: FxHashMap::default(),
|
||||||
|
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
|
||||||
|
aml_region_handlers,
|
||||||
|
+ pci_fd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- pub fn init(&mut self, pci_fd: Option<&libredox::Fd>) -> Result<(), Box<dyn Error>> {
|
||||||
|
+ pub fn init(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
|
if self.aml_context.is_some() {
|
||||||
|
return Err("AML interpreter already initialized".into());
|
||||||
|
}
|
||||||
|
let format_err = |err| format!("{:?}", err);
|
||||||
|
- let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
|
||||||
|
+ let handler = AmlPhysMemHandler::new(Arc::clone(&self.pci_fd), Arc::clone(&self.page_cache));
|
||||||
|
//TODO: use these parsed tables for the rest of acpid
|
||||||
|
- let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
|
||||||
|
+ let rsdp_address = match std::env::var("RSDP_ADDR") {
|
||||||
|
+ Ok(addr) => usize::from_str_radix(&addr, 16)?,
|
||||||
|
+ Err(_) => {
|
||||||
|
+ // RSDP_ADDR not provided — probe BIOS area (0xE0000–0xFFFFF) for RSDP signature
|
||||||
|
+ log::info!("RSDP_ADDR not set, probing BIOS area for RSDP...");
|
||||||
|
+ let mut found = None;
|
||||||
|
+ for page_base in (0xE_0000..0x10_0000).step_by(16) {
|
||||||
|
+ let mapped = unsafe {
|
||||||
|
+ common::physmap(
|
||||||
|
+ page_base,
|
||||||
|
+ 16,
|
||||||
|
+ common::Prot::RW,
|
||||||
|
+ common::MemoryType::default(),
|
||||||
|
+ )
|
||||||
|
+ };
|
||||||
|
+ if let Ok(virt) = mapped {
|
||||||
|
+ let sig = unsafe { std::slice::from_raw_parts(virt as *const u8, 8) };
|
||||||
|
+ if sig == b"RSD PTR " {
|
||||||
|
+ log::info!("found RSDP at physical {:#x}", page_base);
|
||||||
|
+ found = Some(page_base);
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ let _ = unsafe { libredox::call::munmap(virt as *mut (), 16) };
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ found.ok_or("RSDP not found in BIOS area (0xE0000-0xFFFFF)")?
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
let tables =
|
||||||
|
unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? };
|
||||||
|
let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?;
|
||||||
|
@@ -278,10 +307,9 @@
|
||||||
|
|
||||||
|
pub fn aml_context_mut(
|
||||||
|
&mut self,
|
||||||
|
- pci_fd: Option<&libredox::Fd>,
|
||||||
|
) -> Result<&mut Interpreter<AmlPhysMemHandler>, AmlEvalError> {
|
||||||
|
if self.aml_context.is_none() {
|
||||||
|
- match self.init(pci_fd) {
|
||||||
|
+ match self.init() {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to initialize AML context: {}", err);
|
||||||
|
@@ -305,8 +333,8 @@
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
- pub fn build_cache(&mut self, pci_fd: Option<&libredox::Fd>) {
|
||||||
|
- let Ok(aml_context) = self.aml_context_mut(pci_fd) else {
|
||||||
|
+ pub fn build_cache(&mut self) {
|
||||||
|
+ let Ok(aml_context) = self.aml_context_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
@@ -382,6 +410,8 @@
|
||||||
|
|
||||||
|
aml_symbols: RwLock<AmlSymbols>,
|
||||||
|
|
||||||
|
+ pub pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
|
||||||
|
+
|
||||||
|
// TODO: The kernel ACPI code seemed to use load_table quite ubiquitously, however ACPI 5.1
|
||||||
|
// states that DDBHandles can only be obtained when loading XSDT-pointed tables. So, we'll
|
||||||
|
// generate an index only for those.
|
||||||
|
@@ -397,7 +427,7 @@
|
||||||
|
args: Vec<AmlSerdeValue>,
|
||||||
|
) -> Result<AmlSerdeValue, AmlEvalError> {
|
||||||
|
let mut symbols = self.aml_symbols.write();
|
||||||
|
- let interpreter = symbols.aml_context_mut(None)?;
|
||||||
|
+ let interpreter = symbols.aml_context_mut()?;
|
||||||
|
interpreter.acquire_global_lock(16)?;
|
||||||
|
|
||||||
|
let args = args
|
||||||
|
@@ -440,13 +470,17 @@
|
||||||
|
})
|
||||||
|
.collect::<Vec<Sdt>>();
|
||||||
|
|
||||||
|
+ let pci_fd = Arc::new(parking_lot::RwLock::new(None));
|
||||||
|
+
|
||||||
|
let mut this = Self {
|
||||||
|
tables,
|
||||||
|
dsdt: None,
|
||||||
|
fadt: None,
|
||||||
|
|
||||||
|
// Temporary values
|
||||||
|
- aml_symbols: RwLock::new(AmlSymbols::new(ec)),
|
||||||
|
+ aml_symbols: RwLock::new(AmlSymbols::new(ec, Arc::clone(&pci_fd))),
|
||||||
|
+
|
||||||
|
+ pci_fd,
|
||||||
|
|
||||||
|
next_ctx: RwLock::new(0),
|
||||||
|
|
||||||
|
@@ -526,7 +560,7 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn aml_lookup(&self, symbol: &str) -> Option<String> {
|
||||||
|
- if let Ok(aml_symbols) = self.aml_symbols(None) {
|
||||||
|
+ if let Ok(aml_symbols) = self.aml_symbols() {
|
||||||
|
aml_symbols.lookup(symbol)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
@@ -535,7 +569,6 @@
|
||||||
|
|
||||||
|
pub fn aml_symbols(
|
||||||
|
&self,
|
||||||
|
- pci_fd: Option<&libredox::Fd>,
|
||||||
|
) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
|
||||||
|
// return the cached value if it exists
|
||||||
|
let symbols = self.aml_symbols.read();
|
||||||
|
@@ -550,7 +583,7 @@
|
||||||
|
|
||||||
|
let mut aml_symbols = self.aml_symbols.write();
|
||||||
|
|
||||||
|
- aml_symbols.build_cache(pci_fd);
|
||||||
|
+ aml_symbols.build_cache();
|
||||||
|
|
||||||
|
// return the cached value
|
||||||
|
Ok(RwLockWriteGuard::downgrade(aml_symbols))
|
||||||
|
|
||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -14,6 +14,7 @@
|
||||||
|
mod aml_physmem;
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
mod ec;
|
||||||
|
+mod dmi;
|
||||||
|
|
||||||
|
mod scheme;
|
||||||
|
|
||||||
|
@@ -73,6 +74,14 @@
|
||||||
|
];
|
||||||
|
let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
|
||||||
|
|
||||||
|
+ let dmi_strings = match self::dmi::read_smbios_dmi() {
|
||||||
|
+ Ok(strings) => Some(strings),
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("Failed to read SMBIOS DMI: {}", e);
|
||||||
|
+ None
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
// TODO: I/O permission bitmap?
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
||||||
|
@@ -83,7 +92,7 @@
|
||||||
|
let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
|
||||||
|
let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
|
||||||
|
|
||||||
|
- let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
|
||||||
|
+ let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket, dmi_strings);
|
||||||
|
let mut handler = Blocking::new(&socket, 16);
|
||||||
|
|
||||||
|
event_queue
|
||||||
|
|
||||||
|
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
|
||||||
|
--- a/drivers/acpid/src/scheme.rs
|
||||||
|
+++ b/drivers/acpid/src/scheme.rs
|
||||||
|
@@ -22,12 +22,13 @@
|
||||||
|
use syscall::{EOVERFLOW, EPERM};
|
||||||
|
|
||||||
|
use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
|
||||||
|
+use crate::dmi::DmiStrings;
|
||||||
|
|
||||||
|
pub struct AcpiScheme<'acpi, 'sock> {
|
||||||
|
ctx: &'acpi AcpiContext,
|
||||||
|
handles: HandleMap<Handle<'acpi>>,
|
||||||
|
- pci_fd: Option<Fd>,
|
||||||
|
socket: &'sock Socket,
|
||||||
|
+ dmi_text: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Handle<'a> {
|
||||||
|
@@ -43,6 +44,7 @@
|
||||||
|
Symbol { name: String, description: String },
|
||||||
|
SchemeRoot,
|
||||||
|
RegisterPci,
|
||||||
|
+ Dmi(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandleKind<'_> {
|
||||||
|
@@ -55,6 +57,7 @@
|
||||||
|
Self::Symbol { .. } => false,
|
||||||
|
Self::SchemeRoot => false,
|
||||||
|
Self::RegisterPci => false,
|
||||||
|
+ Self::Dmi(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn len(&self, acpi_ctx: &AcpiContext) -> Result<usize> {
|
||||||
|
@@ -65,6 +68,7 @@
|
||||||
|
.ok_or(Error::new(EBADFD))?
|
||||||
|
.length(),
|
||||||
|
Self::Symbol { description, .. } => description.len(),
|
||||||
|
+ Self::Dmi(text) => text.len(),
|
||||||
|
// Directories
|
||||||
|
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
|
||||||
|
Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)),
|
||||||
|
@@ -73,12 +77,12 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
|
||||||
|
- pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket) -> Self {
|
||||||
|
+ pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket, dmi: Option<DmiStrings>) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
handles: HandleMap::new(),
|
||||||
|
- pci_fd: None,
|
||||||
|
socket,
|
||||||
|
+ dmi_text: dmi.map(|d| d.to_text()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -196,6 +200,7 @@
|
||||||
|
match &*components {
|
||||||
|
[""] => HandleKind::TopLevel,
|
||||||
|
["register_pci"] => HandleKind::RegisterPci,
|
||||||
|
+ ["dmi"] => HandleKind::Dmi(self.dmi_text.clone().unwrap_or_default()),
|
||||||
|
["tables"] => HandleKind::Tables,
|
||||||
|
|
||||||
|
["tables", table] => {
|
||||||
|
@@ -204,7 +209,7 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
["symbols"] => {
|
||||||
|
- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
|
||||||
|
+ if let Ok(aml_symbols) = self.ctx.aml_symbols() {
|
||||||
|
HandleKind::Symbols(aml_symbols)
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(EIO));
|
||||||
|
@@ -309,6 +314,7 @@
|
||||||
|
.ok_or(Error::new(EBADFD))?
|
||||||
|
.as_slice(),
|
||||||
|
HandleKind::Symbol { description, .. } => description.as_bytes(),
|
||||||
|
+ HandleKind::Dmi(ref text) => text.as_bytes(),
|
||||||
|
_ => return Err(Error::new(EINVAL)),
|
||||||
|
};
|
||||||
|
|
||||||
|
@@ -332,9 +338,13 @@
|
||||||
|
|
||||||
|
match &handle.kind {
|
||||||
|
HandleKind::TopLevel => {
|
||||||
|
- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
|
||||||
|
+ const TOPLEVEL_ENTRIES: &[(DirentKind, &str)] = &[
|
||||||
|
+ (DirentKind::Regular, "dmi"),
|
||||||
|
+ (DirentKind::Directory, "tables"),
|
||||||
|
+ (DirentKind::Directory, "symbols"),
|
||||||
|
+ ];
|
||||||
|
|
||||||
|
- for (idx, name) in TOPLEVEL_ENTRIES
|
||||||
|
+ for (idx, (kind, name)) in TOPLEVEL_ENTRIES
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.skip(opaque_offset as usize)
|
||||||
|
@@ -343,7 +353,7 @@
|
||||||
|
inode: 0,
|
||||||
|
next_opaque_id: idx as u64 + 1,
|
||||||
|
name,
|
||||||
|
- kind: DirentKind::Directory,
|
||||||
|
+ kind: *kind,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -470,10 +480,12 @@
|
||||||
|
}
|
||||||
|
let new_fd = libredox::Fd::new(new_fd);
|
||||||
|
|
||||||
|
- if self.pci_fd.is_some() {
|
||||||
|
- return Err(Error::new(EINVAL));
|
||||||
|
- } else {
|
||||||
|
- self.pci_fd = Some(new_fd);
|
||||||
|
+ {
|
||||||
|
+ let mut pci_fd = self.ctx.pci_fd.write();
|
||||||
|
+ if pci_fd.is_some() {
|
||||||
|
+ return Err(Error::new(EINVAL));
|
||||||
|
+ }
|
||||||
|
+ *pci_fd = Some(new_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(num_fds)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
index 812c4a5b..08bd1805 100644
|
||||||
|
--- a/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
@@ -41,14 +41,22 @@ impl FbbootlogScheme {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_handoff(&mut self) {
|
||||||
|
- let new_display_handle = match self.input_handle.open_display_v2() {
|
||||||
|
- Ok(display) => V2GraphicsHandle::from_file(display).unwrap(),
|
||||||
|
+ let display = match self.input_handle.open_display_v2() {
|
||||||
|
+ Ok(display) => display,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("fbbootlogd: No display present yet: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
+ let new_display_handle = match V2GraphicsHandle::from_file(display) {
|
||||||
|
+ Ok(handle) => handle,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ eprintln!("fbbootlogd: failed to create graphics handle (DRM ioctl unsupported): {err}");
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
match V2DisplayMap::new(new_display_handle) {
|
||||||
|
Ok(display_map) => self.display_map = Some(display_map),
|
||||||
|
Err(err) => {
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
index eb09b97e..e8543583 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/display.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
@@ -31,7 +31,13 @@ impl Display {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
- let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap();
|
||||||
|
+ let new_display_handle = match V2GraphicsHandle::from_file(display_file) {
|
||||||
|
+ Ok(handle) => handle,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::error!("fbcond: failed to create graphics handle (DRM ioctl unsupported): {err}");
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
log::debug!("fbcond: Opened new display");
|
||||||
|
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
diff --git a/drivers/input/i2c-hidd/src/main.rs b/drivers/input/i2c-hidd/src/main.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..88270e37
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/input/i2c-hidd/src/main.rs
|
||||||
|
@@ -0,0 +1,114 @@
|
||||||
|
+use std::process;
|
||||||
|
+use std::thread;
|
||||||
|
+use std::time::Duration;
|
||||||
|
+
|
||||||
|
+use anyhow::{Context, Result};
|
||||||
|
+
|
||||||
|
+mod acpi;
|
||||||
|
+mod hid;
|
||||||
|
+mod input;
|
||||||
|
+mod quirks;
|
||||||
|
+
|
||||||
|
+use acpi::{
|
||||||
|
+ hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
|
||||||
|
+ scan_acpi_i2c_hid_devices,
|
||||||
|
+};
|
||||||
|
+use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient};
|
||||||
|
+use input::InputForwarder;
|
||||||
|
+use quirks::match_probe_failure_quirk;
|
||||||
|
+
|
||||||
|
+fn main() {
|
||||||
|
+ daemon::Daemon::new(daemon);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
+ common::setup_logging(
|
||||||
|
+ "input",
|
||||||
|
+ "i2c-hid",
|
||||||
|
+ "i2c-hidd",
|
||||||
|
+ common::output_level(),
|
||||||
|
+ common::file_level(),
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ if let Err(err) = run(daemon) {
|
||||||
|
+ log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}");
|
||||||
|
+ process::exit(1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ process::exit(0);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn run(daemon: daemon::Daemon) -> Result<()> {
|
||||||
|
+ log::info!("RB_I2C_HIDD_SCHEMA version=1");
|
||||||
|
+
|
||||||
|
+ let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?;
|
||||||
|
+ if devices.is_empty() {
|
||||||
|
+ log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let mut workers = Vec::new();
|
||||||
|
+ for device in devices {
|
||||||
|
+ log::info!("RB_I2C_HIDD_SNAPSHOT device={device}");
|
||||||
|
+ workers.push(thread::spawn(move || {
|
||||||
|
+ if let Err(err) = bind_device(&device) {
|
||||||
|
+ log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err);
|
||||||
|
+ }
|
||||||
|
+ }));
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ daemon.ready();
|
||||||
|
+
|
||||||
|
+ if workers.is_empty() {
|
||||||
|
+ loop {
|
||||||
|
+ thread::sleep(Duration::from_secs(5));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ for worker in workers {
|
||||||
|
+ let _ = worker.join();
|
||||||
|
+ }
|
||||||
|
+ Ok(())
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn bind_device(device_path: &str) -> Result<()> {
|
||||||
|
+ prepare_acpi_device(device_path)
|
||||||
|
+ .with_context(|| format!("failed to prepare ACPI device {device_path}"))?;
|
||||||
|
+
|
||||||
|
+ let resources = read_decoded_resources(device_path)
|
||||||
|
+ .with_context(|| format!("failed to decode _CRS for {device_path}"))?;
|
||||||
|
+ log::info!(
|
||||||
|
+ "RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}",
|
||||||
|
+ device_path,
|
||||||
|
+ resources.i2c.adapter,
|
||||||
|
+ resources.i2c.address,
|
||||||
|
+ resources.irq,
|
||||||
|
+ resources.gpio_int.len(),
|
||||||
|
+ resources.gpio_io.len()
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ let hid_desc_addr = hid_descriptor_address(device_path)
|
||||||
|
+ .with_context(|| format!("failed to evaluate _DSM for {device_path}"))?;
|
||||||
|
+ let adapter = I2cAdapterClient::new(resources.i2c.clone());
|
||||||
|
+ let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr)
|
||||||
|
+ .with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?;
|
||||||
|
+ let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc)
|
||||||
|
+ .with_context(|| format!("failed to fetch report descriptor for {device_path}"))?;
|
||||||
|
+ let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?;
|
||||||
|
+
|
||||||
|
+ match stream_input_reports(
|
||||||
|
+ &adapter,
|
||||||
|
+ resources.i2c.address,
|
||||||
|
+ &hid_desc,
|
||||||
|
+ &report_desc,
|
||||||
|
+ &mut forwarder,
|
||||||
|
+ ) {
|
||||||
|
+ Ok(()) => Ok(()),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ let quirk =
|
||||||
|
+ match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?;
|
||||||
|
+ recover_acpi_device(device_path, &resources, quirk.as_ref())
|
||||||
|
+ .with_context(|| format!("failed ACPI recovery for {device_path}"))?;
|
||||||
|
+ Err(err).with_context(|| format!("streaming input reports failed for {device_path}"))
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/drivers/input/intel-thc-hidd/src/main.rs b/drivers/input/intel-thc-hidd/src/main.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..c5cda29e
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/input/intel-thc-hidd/src/main.rs
|
||||||
|
@@ -0,0 +1,282 @@
|
||||||
|
+use std::collections::BTreeSet;
|
||||||
|
+use std::fs::{self, OpenOptions};
|
||||||
|
+use std::io::Read;
|
||||||
|
+use std::process;
|
||||||
|
+use std::thread;
|
||||||
|
+use std::time::Duration;
|
||||||
|
+
|
||||||
|
+use acpi_resource::ResourceDescriptor;
|
||||||
|
+use amlserde::{AmlSerde, AmlSerdeValue};
|
||||||
|
+use anyhow::{bail, Context, Result};
|
||||||
|
+use libredox::flag::{O_CLOEXEC, O_RDWR};
|
||||||
|
+use pcid_interface::PciFunctionHandle;
|
||||||
|
+
|
||||||
|
+mod quicki2c;
|
||||||
|
+mod thc;
|
||||||
|
+
|
||||||
|
+use quicki2c::QuickI2cTransport;
|
||||||
|
+use thc::{ThcController, SUPPORTED_PCI_IDS};
|
||||||
|
+
|
||||||
|
+fn main() {
|
||||||
|
+ pcid_interface::pci_daemon(daemon);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ common::setup_logging(
|
||||||
|
+ "input",
|
||||||
|
+ "intel-thc",
|
||||||
|
+ "intel-thc-hidd",
|
||||||
|
+ common::output_level(),
|
||||||
|
+ common::file_level(),
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ if let Err(err) = run(daemon, &mut pcid_handle) {
|
||||||
|
+ log::error!("RB_THC_HIDD_FATAL error={err:#}");
|
||||||
|
+ process::exit(1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ process::exit(0);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
|
||||||
|
+ log::info!("RB_THC_HIDD_SCHEMA version=1");
|
||||||
|
+
|
||||||
|
+ let pci_config = pcid_handle.config();
|
||||||
|
+ let id = (
|
||||||
|
+ pci_config.func.full_device_id.vendor_id,
|
||||||
|
+ pci_config.func.full_device_id.device_id,
|
||||||
|
+ );
|
||||||
|
+ if !SUPPORTED_PCI_IDS.contains(&id) {
|
||||||
|
+ bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pcid_handle.enable_device();
|
||||||
|
+ let bar = unsafe { pcid_handle.map_bar(0) };
|
||||||
|
+ let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size)
|
||||||
|
+ .context("failed to create THC controller")?;
|
||||||
|
+
|
||||||
|
+ let companion = resolve_acpi_companion(&pci_config.func.addr)
|
||||||
|
+ .context("failed to resolve ACPI companion for THC device")?;
|
||||||
|
+ let override_address = companion
|
||||||
|
+ .as_deref()
|
||||||
|
+ .map(companion_slave_address_override)
|
||||||
|
+ .transpose()
|
||||||
|
+ .context("failed to evaluate THC slave-address override")?
|
||||||
|
+ .flatten();
|
||||||
|
+ let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref())
|
||||||
|
+ .context("failed to scan PNP0C50 devices for THC controller")?;
|
||||||
|
+
|
||||||
|
+ let effective_address = override_address.unwrap_or(0x0015);
|
||||||
|
+ let transport = QuickI2cTransport::new(controller, effective_address);
|
||||||
|
+ transport.prime_controller();
|
||||||
|
+ transport.emulate_transfer(&[]);
|
||||||
|
+ log::debug!("RB_THC_HIDD status={:#x}", transport.status());
|
||||||
|
+
|
||||||
|
+ match transport.register_with_i2cd(companion.as_deref(), override_address) {
|
||||||
|
+ Ok(()) => {}
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::warn!("RB_THC_HIDD registration error={err:#}");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ log::info!(
|
||||||
|
+ "RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}",
|
||||||
|
+ pci_config.func.name(),
|
||||||
|
+ companion,
|
||||||
|
+ override_address,
|
||||||
|
+ hid_devices.len()
|
||||||
|
+ );
|
||||||
|
+
|
||||||
|
+ daemon.ready();
|
||||||
|
+
|
||||||
|
+ loop {
|
||||||
|
+ thread::sleep(Duration::from_secs(5));
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
|
||||||
|
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||||
|
+ Ok(entries) => entries,
|
||||||
|
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||||
|
+ log::debug!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||||
|
+ return Ok(None);
|
||||||
|
+ }
|
||||||
|
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||||
|
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), skipping companion resolution", err);
|
||||||
|
+ return Ok(None);
|
||||||
|
+ }
|
||||||
|
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
+ };
|
||||||
|
+ let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
|
||||||
|
+
|
||||||
|
+ for entry in entries {
|
||||||
|
+ let entry = entry.context("failed to enumerate ACPI symbol entry")?;
|
||||||
|
+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||||
|
+ continue;
|
||||||
|
+ };
|
||||||
|
+ if !file_name.ends_with("._ADR") {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let symbol = read_aml_symbol(&file_name)?;
|
||||||
|
+ if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let device = symbol
|
||||||
|
+ .name
|
||||||
|
+ .strip_suffix("._ADR")
|
||||||
|
+ .unwrap_or(&symbol.name)
|
||||||
|
+ .trim_start_matches('\\')
|
||||||
|
+ .replace('/', ".");
|
||||||
|
+ return Ok(Some(device));
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ Ok(None)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn companion_slave_address_override(path: &str) -> Result<Option<u16>> {
|
||||||
|
+ let icrs = evaluate_integer_method(path, "ICRS").ok();
|
||||||
|
+ let isub = evaluate_integer_method(path, "ISUB").ok();
|
||||||
|
+ Ok(icrs
|
||||||
|
+ .or(isub)
|
||||||
|
+ .map(|value| u16::try_from(value))
|
||||||
|
+ .transpose()
|
||||||
|
+ .context("THC ACPI override out of range")?)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
|
||||||
|
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||||
|
+ Ok(entries) => entries,
|
||||||
|
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||||
|
+ log::debug!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), running with no HID devices", err);
|
||||||
|
+ return Ok(Vec::new());
|
||||||
|
+ }
|
||||||
|
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||||
|
+ };
|
||||||
|
+ let mut devices = BTreeSet::new();
|
||||||
|
+
|
||||||
|
+ for entry in entries {
|
||||||
|
+ let entry = entry.context("failed to enumerate ACPI HID entry")?;
|
||||||
|
+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||||
|
+ continue;
|
||||||
|
+ };
|
||||||
|
+ if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let symbol = read_aml_symbol(&file_name)?;
|
||||||
|
+ let is_hid = matches!(
|
||||||
|
+ decode_hardware_id(&symbol.value).as_deref(),
|
||||||
|
+ Some("PNP0C50" | "ACPI0C50")
|
||||||
|
+ );
|
||||||
|
+ if !is_hid {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let device = symbol
|
||||||
|
+ .name
|
||||||
|
+ .strip_suffix("._HID")
|
||||||
|
+ .or_else(|| symbol.name.strip_suffix("._CID"))
|
||||||
|
+ .unwrap_or(&symbol.name)
|
||||||
|
+ .trim_start_matches('\\')
|
||||||
|
+ .replace('/', ".");
|
||||||
|
+ if let Some(companion) = companion {
|
||||||
|
+ if !is_bound_to_companion(&device, companion)? {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ devices.insert(device);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ Ok(devices.into_iter().collect())
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn is_bound_to_companion(device: &str, companion: &str) -> Result<bool> {
|
||||||
|
+ let resource_path = format!("/scheme/acpi/resources/{device}");
|
||||||
|
+ let serialized = match fs::read_to_string(&resource_path) {
|
||||||
|
+ Ok(serialized) => serialized,
|
||||||
|
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
|
||||||
|
+ Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")),
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ let resources: Vec<ResourceDescriptor> =
|
||||||
|
+ ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?;
|
||||||
|
+ Ok(resources.into_iter().any(|resource| match resource {
|
||||||
|
+ ResourceDescriptor::I2cSerialBus(bus) => bus
|
||||||
|
+ .resource_source
|
||||||
|
+ .as_ref()
|
||||||
|
+ .map(|source| source.source == companion)
|
||||||
|
+ .unwrap_or(false),
|
||||||
|
+ _ => false,
|
||||||
|
+ }))
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
|
||||||
|
+ let symbol_name = format!("{}.{}", normalize_device_path(path), method);
|
||||||
|
+ let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
|
||||||
|
+ let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
|
||||||
|
+ .with_context(|| format!("failed to open {symbol_path}"))?;
|
||||||
|
+
|
||||||
|
+ let mut payload = ron::to_string(&Vec::<AmlSerdeValue>::new())
|
||||||
|
+ .context("failed to serialize ACPI call arguments")?
|
||||||
|
+ .into_bytes();
|
||||||
|
+ payload.resize(payload.len() + 2048, 0);
|
||||||
|
+ let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
|
||||||
|
+ .with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
|
||||||
|
+ let response = std::str::from_utf8(&payload[..used])
|
||||||
|
+ .with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
|
||||||
|
+ match ron::from_str::<AmlSerdeValue>(response)
|
||||||
|
+ .with_context(|| format!("failed to decode ACPI response for {symbol_name}"))?
|
||||||
|
+ {
|
||||||
|
+ AmlSerdeValue::Integer(value) => Ok(value),
|
||||||
|
+ other => bail!("{}.{} returned non-integer value {other:?}", path, method),
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
|
||||||
|
+ let path = format!("/scheme/acpi/symbols/{file_name}");
|
||||||
|
+ let mut file = OpenOptions::new()
|
||||||
|
+ .read(true)
|
||||||
|
+ .open(&path)
|
||||||
|
+ .with_context(|| format!("failed to open {path}"))?;
|
||||||
|
+ let mut ron_text = String::new();
|
||||||
|
+ file.read_to_string(&mut ron_text)
|
||||||
|
+ .with_context(|| format!("failed to read {path}"))?;
|
||||||
|
+ ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
|
||||||
|
+ match value {
|
||||||
|
+ AmlSerdeValue::String(value) => Some(value.clone()),
|
||||||
|
+ AmlSerdeValue::Integer(integer) => {
|
||||||
|
+ let vendor = integer & 0xFFFF;
|
||||||
|
+ let device = (integer >> 16) & 0xFFFF;
|
||||||
|
+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
|
||||||
|
+ let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
|
||||||
|
+ let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
|
||||||
|
+ let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
|
||||||
|
+ let device_1 = (device >> 4) & 0xF;
|
||||||
|
+ let device_2 = (device >> 0) & 0xF;
|
||||||
|
+ let device_3 = (device >> 12) & 0xF;
|
||||||
|
+ let device_4 = (device >> 8) & 0xF;
|
||||||
|
+ Some(format!(
|
||||||
|
+ "{}{}{}{:01X}{:01X}{:01X}{:01X}",
|
||||||
|
+ vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
|
||||||
|
+ ))
|
||||||
|
+ }
|
||||||
|
+ _ => None,
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn normalize_device_path(path: &str) -> String {
|
||||||
|
+ path.trim_start_matches('\\')
|
||||||
|
+ .trim_matches('/')
|
||||||
|
+ .replace('/', ".")
|
||||||
|
+}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
diff --git a/drivers/common/src/lib.rs b/drivers/common/src/lib.rs
|
||||||
|
index a55014b9..6b7ab2fe 100644
|
||||||
|
--- a/drivers/common/src/lib.rs
|
||||||
|
+++ b/drivers/common/src/lib.rs
|
||||||
|
@@ -25 +25 @@ pub mod timeout;
|
||||||
|
-pub use logger::{file_level, output_level, setup_logging};
|
||||||
|
+pub use logger::{file_level, output_level, setup_logging, RateLimitedLog};
|
||||||
|
diff --git a/drivers/common/src/logger.rs b/drivers/common/src/logger.rs
|
||||||
|
index a531edd9..8b65f6bd 100644
|
||||||
|
--- a/drivers/common/src/logger.rs
|
||||||
|
+++ b/drivers/common/src/logger.rs
|
||||||
|
@@ -0,0 +1,2 @@
|
||||||
|
+use std::cell::RefCell;
|
||||||
|
+use std::collections::HashMap;
|
||||||
|
@@ -71,0 +74,65 @@ pub fn setup_logging(
|
||||||
|
+/// A simple per-message rate limiter to prevent log spam.
|
||||||
|
+///
|
||||||
|
+/// Tracks the last emission time for each unique message key. If the same
|
||||||
|
+/// key is logged again within `interval`, the message is suppressed and a
|
||||||
|
+/// "last message repeated N times" warning is emitted instead.
|
||||||
|
+pub struct RateLimitedLog {
|
||||||
|
+ interval: std::time::Duration,
|
||||||
|
+ last_emission: RefCell<HashMap<String, std::time::Instant>>,
|
||||||
|
+ suppress_count: RefCell<HashMap<String, u64>>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl RateLimitedLog {
|
||||||
|
+ pub fn new(interval_secs: u64) -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ interval: std::time::Duration::from_secs(interval_secs),
|
||||||
|
+ last_emission: RefCell::new(HashMap::new()),
|
||||||
|
+ suppress_count: RefCell::new(HashMap::new()),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Log a message through the rate limiter.
|
||||||
|
+ pub fn log(&self, key: &str, log_fn: impl FnOnce()) {
|
||||||
|
+ let now = std::time::Instant::now();
|
||||||
|
+ let mut last_map = self.last_emission.borrow_mut();
|
||||||
|
+ let mut count_map = self.suppress_count.borrow_mut();
|
||||||
|
+
|
||||||
|
+ if let Some(last) = last_map.get(key) {
|
||||||
|
+ if now.duration_since(*last) < self.interval {
|
||||||
|
+ *count_map.entry(key.to_string()).or_insert(0) += 1;
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if let Some(count) = count_map.remove(key) {
|
||||||
|
+ if count > 0 {
|
||||||
|
+ log::warn!("RateLimitedLog: last message '{}' repeated {} times", key, count);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ last_map.insert(key.to_string(), now);
|
||||||
|
+ log_fn();
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Format a structured log message with key=value pairs.
|
||||||
|
+///
|
||||||
|
+/// Example: `structured_log!("thermald", "event=temperature_read", "zone=CPU", "temp=45.2")`
|
||||||
|
+/// produces: `thermald: event=temperature_read zone=CPU temp=45.2`
|
||||||
|
+#[macro_export]
|
||||||
|
+macro_rules! structured_log {
|
||||||
|
+ ($source:expr, $($key:expr),+ $(,)?) => {
|
||||||
|
+ {
|
||||||
|
+ let mut msg = String::new();
|
||||||
|
+ msg.push_str($source);
|
||||||
|
+ msg.push_str(": ");
|
||||||
|
+ $(
|
||||||
|
+ msg.push_str($key);
|
||||||
|
+ msg.push(' ');
|
||||||
|
+ )+
|
||||||
|
+ msg.pop();
|
||||||
|
+ log::info!("{}", msg);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
diff --git a/drivers/thermald/src/main.rs b/drivers/thermald/src/main.rs
|
||||||
|
index b8d271b5..e64cf162 100644
|
||||||
|
--- a/drivers/thermald/src/main.rs
|
||||||
|
+++ b/drivers/thermald/src/main.rs
|
||||||
|
@@ -86,0 +87,2 @@ fn main() -> Result<()> {
|
||||||
|
+ let rate_limiter = common::RateLimitedLog::new(30);
|
||||||
|
+
|
||||||
|
@@ -138 +140,4 @@ fn main() -> Result<()> {
|
||||||
|
- log::info!("thermald: max temp = {:.1}C from {}", max_temp, max_source);
|
||||||
|
+ let source = max_source.clone();
|
||||||
|
+ rate_limiter.log(&format!("max_temp_{:.0}", max_temp), || {
|
||||||
|
+ log::info!("thermald: max temp = {:.1}C from {}", max_temp, source);
|
||||||
|
+ });
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs
|
||||||
|
index 070de3d6..4ea5365d 100644
|
||||||
|
--- a/logd/src/scheme.rs
|
||||||
|
+++ b/logd/src/scheme.rs
|
||||||
|
@@ -1,2 +1,2 @@
|
||||||
|
-use std::collections::{BTreeMap, VecDeque};
|
||||||
|
-use std::fs::{File, OpenOptions};
|
||||||
|
+use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||||
|
+use std::fs::{File, OpenOptions, rename};
|
||||||
|
@@ -5,0 +6 @@ use std::os::fd::{FromRawFd, RawFd};
|
||||||
|
+use std::path::PathBuf;
|
||||||
|
@@ -13,0 +15,5 @@ use syscall::schemev2::NewFdFlags;
|
||||||
|
+const LOG_DIR: &str = "/var/log";
|
||||||
|
+const MAX_LOG_SIZE: u64 = 10 * 1024 * 1024;
|
||||||
|
+const MAX_ROTATED_FILES: u32 = 5;
|
||||||
|
+const MEMORY_LOG_LIMIT: usize = 1000;
|
||||||
|
+
|
||||||
|
@@ -31 +37 @@ enum OutputCmd {
|
||||||
|
- Log(Vec<u8>),
|
||||||
|
+ Log { context: String, line: Vec<u8> },
|
||||||
|
@@ -34,0 +41,52 @@ enum OutputCmd {
|
||||||
|
+struct LogFile {
|
||||||
|
+ file: File,
|
||||||
|
+ path: PathBuf,
|
||||||
|
+ bytes_written: u64,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl LogFile {
|
||||||
|
+ fn open(path: PathBuf) -> std::io::Result<Self> {
|
||||||
|
+ let file = OpenOptions::new()
|
||||||
|
+ .create(true)
|
||||||
|
+ .append(true)
|
||||||
|
+ .open(&path)?;
|
||||||
|
+ let metadata = file.metadata()?;
|
||||||
|
+ Ok(LogFile {
|
||||||
|
+ file,
|
||||||
|
+ path,
|
||||||
|
+ bytes_written: metadata.len(),
|
||||||
|
+ })
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fn write(&mut self, data: &[u8]) -> std::io::Result<()> {
|
||||||
|
+ self.file.write_all(data)?;
|
||||||
|
+ self.file.flush()?;
|
||||||
|
+ self.bytes_written += data.len() as u64;
|
||||||
|
+ Ok(())
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fn maybe_rotate(&mut self) -> std::io::Result<()> {
|
||||||
|
+ if self.bytes_written < MAX_LOG_SIZE {
|
||||||
|
+ return Ok(());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ drop(std::mem::replace(&mut self.file, unsafe { File::from_raw_fd(-1) }));
|
||||||
|
+
|
||||||
|
+ for i in (1..MAX_ROTATED_FILES).rev() {
|
||||||
|
+ let old_path = self.path.with_extension(format!("log.{}", i));
|
||||||
|
+ let new_path = self.path.with_extension(format!("log.{}", i + 1));
|
||||||
|
+ let _ = rename(&old_path, &new_path);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let backup_path = self.path.with_extension("log.1");
|
||||||
|
+ let _ = rename(&self.path, &backup_path);
|
||||||
|
+
|
||||||
|
+ self.file = OpenOptions::new()
|
||||||
|
+ .create(true)
|
||||||
|
+ .append(true)
|
||||||
|
+ .open(&self.path)?;
|
||||||
|
+ self.bytes_written = 0;
|
||||||
|
+ Ok(())
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
@@ -49,0 +109,4 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
+ let mut service_logs: HashMap<String, LogFile> = HashMap::new();
|
||||||
|
+
|
||||||
|
+ let _ = std::fs::create_dir_all(LOG_DIR);
|
||||||
|
+
|
||||||
|
@@ -52 +114 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
- OutputCmd::Log(line) => {
|
||||||
|
+ OutputCmd::Log { context, line } => {
|
||||||
|
@@ -55,0 +118,22 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
+
|
||||||
|
+ let service_name = context.split(':').next().unwrap_or("unknown");
|
||||||
|
+ if !service_name.is_empty() {
|
||||||
|
+ let log_path = PathBuf::from(LOG_DIR).join(format!("{}.log", service_name));
|
||||||
|
+ let entry = service_logs.entry(service_name.to_string()).or_insert_with(|| {
|
||||||
|
+ LogFile::open(log_path).unwrap_or_else(|_| {
|
||||||
|
+ LogFile::open(PathBuf::from("/dev/null")).unwrap()
|
||||||
|
+ })
|
||||||
|
+ });
|
||||||
|
+ let _ = entry.write(&line);
|
||||||
|
+ let _ = entry.maybe_rotate();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let system_path = PathBuf::from(LOG_DIR).join("system.log");
|
||||||
|
+ let system_entry = service_logs.entry("system".to_string()).or_insert_with(|| {
|
||||||
|
+ LogFile::open(system_path).unwrap_or_else(|_| {
|
||||||
|
+ LogFile::open(PathBuf::from("/dev/null")).unwrap()
|
||||||
|
+ })
|
||||||
|
+ });
|
||||||
|
+ let _ = system_entry.write(&line);
|
||||||
|
+ let _ = system_entry.maybe_rotate();
|
||||||
|
+
|
||||||
|
@@ -57,2 +141 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
- // Keep a limited amount of logs for backfilling to bound memory usage
|
||||||
|
- while logs.len() > 1000 {
|
||||||
|
+ while logs.len() > MEMORY_LOG_LIMIT {
|
||||||
|
@@ -68 +150,0 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
-
|
||||||
|
@@ -83 +164,0 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
- // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue
|
||||||
|
@@ -118 +198,0 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
- // Writing to the kernel debug log never blocks
|
||||||
|
@@ -124 +204,4 @@ impl<'sock> LogScheme<'sock> {
|
||||||
|
- .send(OutputCmd::Log(mem::take(handle_buf)))
|
||||||
|
+ .send(OutputCmd::Log {
|
||||||
|
+ context: context.to_string(),
|
||||||
|
+ line: mem::take(handle_buf),
|
||||||
|
+ })
|
||||||
|
@@ -173,3 +255,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> {
|
||||||
|
-
|
||||||
|
- // TODO
|
||||||
|
-
|
||||||
|
@@ -244,3 +323,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> {
|
||||||
|
-
|
||||||
|
- //TODO: flush remaining data?
|
||||||
|
-
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
||||||
|
index ea480bb7..db2ac003 100644
|
||||||
|
--- a/drivers/acpid/src/acpi.rs
|
||||||
|
+++ b/drivers/acpid/src/acpi.rs
|
||||||
|
@@ -453,0 +454 @@ pub struct AcpiContext {
|
||||||
|
+ pub cstate_state: crate::cstate::CStateState,
|
||||||
|
@@ -535,0 +537 @@ impl AcpiContext {
|
||||||
|
+ cstate_state: crate::cstate::CStateState::new(),
|
||||||
|
@@ -566 +568 @@ impl AcpiContext {
|
||||||
|
- // Discover thermal zones and fan devices if AML is ready.
|
||||||
|
+ // Discover thermal zones, fan devices, and processor C-states if AML is ready.
|
||||||
|
@@ -568,0 +571 @@ impl AcpiContext {
|
||||||
|
+ this.cstate_state.refresh(&this);
|
||||||
|
@@ -690,0 +694,24 @@ impl AcpiContext {
|
||||||
|
+ /// Discover processor names by scanning the AML namespace under `\_PR`.
|
||||||
|
+ pub fn processor_names(&self) -> Result<Vec<String>, AmlEvalError> {
|
||||||
|
+ let mut symbols = self.aml_symbols.write();
|
||||||
|
+ let interpreter = symbols.aml_context_mut()?;
|
||||||
|
+ let mut ns = interpreter.namespace.lock();
|
||||||
|
+
|
||||||
|
+ let mut names = Vec::new();
|
||||||
|
+ let _ = ns.traverse(|level_aml_name, _level| {
|
||||||
|
+ let name_str = aml_to_symbol(level_aml_name);
|
||||||
|
+ if name_str.starts_with("\\_PR_.") || name_str.starts_with("_PR_.") {
|
||||||
|
+ let after_prefix = if name_str.starts_with("\\_PR_.") {
|
||||||
|
+ &name_str[6..]
|
||||||
|
+ } else {
|
||||||
|
+ &name_str[5..]
|
||||||
|
+ };
|
||||||
|
+ if !after_prefix.contains('.') {
|
||||||
|
+ names.push(after_prefix.to_string());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Ok(true)
|
||||||
|
+ });
|
||||||
|
+ Ok(names)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
diff --git a/drivers/acpid/src/cstate.rs b/drivers/acpid/src/cstate.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..6e2112b3
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/acpid/src/cstate.rs
|
||||||
|
@@ -0,0 +1,194 @@
|
||||||
|
+use acpi::aml::namespace::AmlName;
|
||||||
|
+use acpi::aml::AmlError;
|
||||||
|
+use std::str::FromStr;
|
||||||
|
+use std::sync::{Arc, RwLock};
|
||||||
|
+
|
||||||
|
+use crate::acpi::{AcpiContext, AmlEvalError};
|
||||||
|
+use amlserde::AmlSerdeValue;
|
||||||
|
+
|
||||||
|
+/// A single ACPI C-state descriptor from _CST.
|
||||||
|
+#[derive(Clone, Debug)]
|
||||||
|
+pub struct CStateInfo {
|
||||||
|
+ /// C-state type: 1=C1, 2=C2, 3=C3, etc.
|
||||||
|
+ pub ctype: u64,
|
||||||
|
+ /// Worst-case latency in microseconds to enter/exit.
|
||||||
|
+ pub latency: u64,
|
||||||
|
+ /// Average power consumption in milliwatts (0xFFFFFFFF = unknown).
|
||||||
|
+ pub power: u64,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl CStateInfo {
|
||||||
|
+ fn from_package(elements: &[AmlSerdeValue]) -> Option<Self> {
|
||||||
|
+ if elements.len() < 4 {
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ let ctype = extract_u64(&elements[1])?;
|
||||||
|
+ let latency = extract_u64(&elements[2])?;
|
||||||
|
+ let power = extract_u64(&elements[3])?;
|
||||||
|
+ Some(Self {
|
||||||
|
+ ctype,
|
||||||
|
+ latency,
|
||||||
|
+ power,
|
||||||
|
+ })
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// C-states discovered for a single processor.
|
||||||
|
+#[derive(Clone, Debug)]
|
||||||
|
+pub struct ProcessorCStates {
|
||||||
|
+ pub name: String,
|
||||||
|
+ pub states: Vec<CStateInfo>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl ProcessorCStates {
|
||||||
|
+ fn from_processor_eval(
|
||||||
|
+ ctx: &AcpiContext,
|
||||||
|
+ proc_name: &str,
|
||||||
|
+ ) -> Result<Self, CStateError> {
|
||||||
|
+ let aml_prefix = format!("\\_PR_.{proc_name}");
|
||||||
|
+
|
||||||
|
+ let mut states = Vec::new();
|
||||||
|
+
|
||||||
|
+ if let Ok(cst_name) = AmlName::from_str(&format!("{aml_prefix}._CST")) {
|
||||||
|
+ match ctx.aml_eval(cst_name, Vec::new()) {
|
||||||
|
+ Ok(value) => {
|
||||||
|
+ if let AmlSerdeValue::Package { contents } = value {
|
||||||
|
+ if contents.len() >= 1 {
|
||||||
|
+ if let Some(count) = extract_u64(&contents[0]) {
|
||||||
|
+ let expected = count as usize;
|
||||||
|
+ for i in 1..contents.len() {
|
||||||
|
+ if let AmlSerdeValue::Package { contents: ref inner } =
|
||||||
|
+ contents[i]
|
||||||
|
+ {
|
||||||
|
+ if let Some(cstate) = CStateInfo::from_package(inner) {
|
||||||
|
+ states.push(cstate);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if states.len() != expected {
|
||||||
|
+ log::warn!(
|
||||||
|
+ "C-state {proc_name}: count mismatch: expected {expected}, got {}",
|
||||||
|
+ states.len()
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::debug!("Processor {proc_name}: _CST eval failed: {e:?}");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ Ok(Self {
|
||||||
|
+ name: proc_name.to_owned(),
|
||||||
|
+ states,
|
||||||
|
+ })
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn to_text(&self) -> String {
|
||||||
|
+ let mut s = String::new();
|
||||||
|
+ s.push_str(&format!("name={}\n", self.name));
|
||||||
|
+ s.push_str(&format!("count={}\n", self.states.len()));
|
||||||
|
+ for (idx, st) in self.states.iter().enumerate() {
|
||||||
|
+ s.push_str(&format!(
|
||||||
|
+ "cstate{}: type=C{} latency={}us power={}mW\n",
|
||||||
|
+ idx, st.ctype, st.latency, st.power
|
||||||
|
+ ));
|
||||||
|
+ }
|
||||||
|
+ s
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn extract_u64(value: &AmlSerdeValue) -> Option<u64> {
|
||||||
|
+ match value {
|
||||||
|
+ AmlSerdeValue::Integer(i) => Some(*i as u64),
|
||||||
|
+ _ => None,
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[derive(Debug)]
|
||||||
|
+pub enum CStateError {
|
||||||
|
+ AmlError(AmlError),
|
||||||
|
+ EvalError(AmlEvalError),
|
||||||
|
+ NotFound,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl From<AmlError> for CStateError {
|
||||||
|
+ fn from(value: AmlError) -> Self {
|
||||||
|
+ CStateError::AmlError(value)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl From<AmlEvalError> for CStateError {
|
||||||
|
+ fn from(value: AmlEvalError) -> Self {
|
||||||
|
+ CStateError::EvalError(value)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn discover_cstates(ctx: &AcpiContext) -> Vec<ProcessorCStates> {
|
||||||
|
+ let mut procs = Vec::new();
|
||||||
|
+
|
||||||
|
+ let proc_names = match ctx.processor_names() {
|
||||||
|
+ Ok(names) => names,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::debug!("C-state processor discovery failed: {e:?}");
|
||||||
|
+ return procs;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ for child_name in proc_names {
|
||||||
|
+ match ProcessorCStates::from_processor_eval(ctx, &child_name) {
|
||||||
|
+ Ok(proc_cstates) => {
|
||||||
|
+ if !proc_cstates.states.is_empty() {
|
||||||
|
+ log::info!(
|
||||||
|
+ "C-states discovered for {}: {} states",
|
||||||
|
+ proc_cstates.name,
|
||||||
|
+ proc_cstates.states.len()
|
||||||
|
+ );
|
||||||
|
+ procs.push(proc_cstates);
|
||||||
|
+ } else {
|
||||||
|
+ log::debug!("Processor {child_name}: no C-states from _CST");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("Processor {child_name}: C-state discovery failed: {e:?}");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ procs
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct CStateState {
|
||||||
|
+ procs: RwLock<Vec<ProcessorCStates>>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl CStateState {
|
||||||
|
+ pub fn new() -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ procs: RwLock::new(Vec::new()),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn refresh(&self, ctx: &AcpiContext) {
|
||||||
|
+ let discovered = discover_cstates(ctx);
|
||||||
|
+ if let Ok(mut procs) = self.procs.write() {
|
||||||
|
+ *procs = discovered;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn processors(&self) -> Vec<ProcessorCStates> {
|
||||||
|
+ self.procs.read().map(|g| g.clone()).unwrap_or_default()
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn processor_by_name(&self, name: &str) -> Option<ProcessorCStates> {
|
||||||
|
+ self.procs
|
||||||
|
+ .read()
|
||||||
|
+ .ok()?
|
||||||
|
+ .iter()
|
||||||
|
+ .find(|p| p.name == name)
|
||||||
|
+ .cloned()
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||||
|
index c7b8ff3e..40b52a7b 100644
|
||||||
|
--- a/drivers/acpid/src/main.rs
|
||||||
|
+++ b/drivers/acpid/src/main.rs
|
||||||
|
@@ -19,0 +20 @@ mod fan;
|
||||||
|
+mod cstate;
|
||||||
|
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
|
||||||
|
index 905b42ff..3258870b 100644
|
||||||
|
--- a/drivers/acpid/src/scheme.rs
|
||||||
|
+++ b/drivers/acpid/src/scheme.rs
|
||||||
|
@@ -51,0 +52,2 @@ enum HandleKind<'a> {
|
||||||
|
+ Cstates,
|
||||||
|
+ CstateProcessor(String),
|
||||||
|
@@ -68,0 +71,2 @@ impl HandleKind<'_> {
|
||||||
|
+ Self::Cstates => true,
|
||||||
|
+ Self::CstateProcessor(_) => false,
|
||||||
|
@@ -86,0 +91,2 @@ impl HandleKind<'_> {
|
||||||
|
+ Self::Cstates => 0,
|
||||||
|
+ Self::CstateProcessor(ref text) => text.len(),
|
||||||
|
@@ -257,0 +264,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ ["cstates"] => HandleKind::Cstates,
|
||||||
|
+ ["cstates", proc] => {
|
||||||
|
+ if let Some(p) = self.ctx.cstate_state.processor_by_name(proc) {
|
||||||
|
+ HandleKind::CstateProcessor(p.to_text())
|
||||||
|
+ } else {
|
||||||
|
+ return Err(Error::new(ENOENT));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
@@ -348,0 +363 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ HandleKind::CstateProcessor(ref text) => text.as_bytes(),
|
||||||
|
@@ -377,0 +393 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ (DirentKind::Directory, "cstates"),
|
||||||
|
@@ -470,0 +487,17 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||||
|
+ HandleKind::Cstates => {
|
||||||
|
+ for (idx, proc) in self
|
||||||
|
+ .ctx
|
||||||
|
+ .cstate_state
|
||||||
|
+ .processors()
|
||||||
|
+ .iter()
|
||||||
|
+ .enumerate()
|
||||||
|
+ .skip(opaque_offset as usize)
|
||||||
|
+ {
|
||||||
|
+ buf.entry(DirEntry {
|
||||||
|
+ inode: 0,
|
||||||
|
+ next_opaque_id: idx as u64 + 1,
|
||||||
|
+ name: &proc.name,
|
||||||
|
+ kind: DirentKind::Regular,
|
||||||
|
+ })?;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
diff --git a/drivers/thermald/src/main.rs b/drivers/thermald/src/main.rs
|
||||||
|
index e64cf162..2b02e4ed 100644
|
||||||
|
--- a/drivers/thermald/src/main.rs
|
||||||
|
+++ b/drivers/thermald/src/main.rs
|
||||||
|
@@ -7,0 +8,14 @@ const WARNING_TEMP: f32 = 70.0;
|
||||||
|
+fn read_max_cstate() -> Option<usize> {
|
||||||
|
+ fs::read_to_string("/scheme/sys/cstate")
|
||||||
|
+ .ok()
|
||||||
|
+ .and_then(|s| s.trim().parse::<usize>().ok())
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn set_cstate_policy(policy: usize) {
|
||||||
|
+ if let Err(e) = fs::write("/scheme/sys/cstate_policy", policy.to_string()) {
|
||||||
|
+ log::debug!("thermald: failed to set cstate_policy={}: {}", policy, e);
|
||||||
|
+ } else {
|
||||||
|
+ log::info!("thermald: set cstate_policy={}", policy);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
@@ -138,0 +153,8 @@ fn main() -> Result<()> {
|
||||||
|
+ if let Some(max_state) = read_max_cstate() {
|
||||||
|
+ if max_temp > WARNING_TEMP && max_state > 0 {
|
||||||
|
+ set_cstate_policy(0);
|
||||||
|
+ } else if max_temp <= WARNING_TEMP {
|
||||||
|
+ set_cstate_policy(max_state);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
diff --git a/drivers/net/e1000d/src/device.rs b/drivers/net/e1000d/src/device.rs
|
||||||
|
index 4c518f30..7ba100dd 100644
|
||||||
|
--- a/drivers/net/e1000d/src/device.rs
|
||||||
|
+++ b/drivers/net/e1000d/src/device.rs
|
||||||
|
@@ -26,0 +27 @@ const ICR: u32 = 0xC0;
|
||||||
|
+const ITR: u32 = 0xC4;
|
||||||
|
@@ -241,0 +243,6 @@ impl Intel8254x {
|
||||||
|
+ /// Set the Interrupt Throttling Rate (ITR) register.
|
||||||
|
+ /// `interval` is in 256-ns increments. 0 disables throttling.
|
||||||
|
+ pub unsafe fn set_itr(&self, interval: u16) {
|
||||||
|
+ self.write_reg(ITR, interval as u32);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
diff --git a/drivers/net/e1000d/src/itr.rs b/drivers/net/e1000d/src/itr.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..aa85a6f2
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/net/e1000d/src/itr.rs
|
||||||
|
@@ -0,0 +1,81 @@
|
||||||
|
+/// Interrupt Throttling Rate (ITR) tracker for e1000d.
|
||||||
|
+///
|
||||||
|
+/// Dynamically adjusts the interrupt coalescing interval based on packet rate
|
||||||
|
+/// to balance latency and CPU overhead.
|
||||||
|
+#[derive(Debug)]
|
||||||
|
+pub struct ItrTracker {
|
||||||
|
+ last_irq_count: u64,
|
||||||
|
+ current_itr: u16,
|
||||||
|
+ consecutive_low: u32,
|
||||||
|
+ consecutive_high: u32,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl ItrTracker {
|
||||||
|
+ /// Target ~8000 interrupts/sec max for low latency.
|
||||||
|
+ const LOW_LATENCY_THRESHOLD: u64 = 8000;
|
||||||
|
+ /// Target ~2000 interrupts/sec min for CPU efficiency.
|
||||||
|
+ const HIGH_THROUGHPUT_THRESHOLD: u64 = 2000;
|
||||||
|
+ /// Minimum ITR interval in 256-ns units (~50 µs).
|
||||||
|
+ const MIN_ITR: u16 = 200;
|
||||||
|
+ /// Default ITR interval in 256-ns units (~256 µs).
|
||||||
|
+ const DEFAULT_ITR: u16 = 1000;
|
||||||
|
+ /// Maximum ITR interval in 256-ns units (~2 ms).
|
||||||
|
+ const MAX_ITR: u16 = 8000;
|
||||||
|
+ /// Number of consecutive measurements before adjusting.
|
||||||
|
+ const HYSTERESIS: u32 = 3;
|
||||||
|
+
|
||||||
|
+ pub fn new() -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ last_irq_count: 0,
|
||||||
|
+ current_itr: Self::DEFAULT_ITR,
|
||||||
|
+ consecutive_low: 0,
|
||||||
|
+ consecutive_high: 0,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Call once per IRQ to update the tracker and return the new ITR value.
|
||||||
|
+ /// Returns `None` if no change is needed.
|
||||||
|
+ pub fn update(&mut self, irq_count: u64) -> Option<u16> {
|
||||||
|
+ let delta = irq_count.saturating_sub(self.last_irq_count);
|
||||||
|
+ self.last_irq_count = irq_count;
|
||||||
|
+
|
||||||
|
+ if delta > Self::LOW_LATENCY_THRESHOLD {
|
||||||
|
+ self.consecutive_high += 1;
|
||||||
|
+ self.consecutive_low = 0;
|
||||||
|
+ if self.consecutive_high >= Self::HYSTERESIS {
|
||||||
|
+ self.consecutive_high = 0;
|
||||||
|
+ let new_itr = self.current_itr.saturating_mul(2).min(Self::MAX_ITR);
|
||||||
|
+ if new_itr != self.current_itr {
|
||||||
|
+ self.current_itr = new_itr;
|
||||||
|
+ return Some(self.current_itr);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ } else if delta < Self::HIGH_THROUGHPUT_THRESHOLD {
|
||||||
|
+ self.consecutive_low += 1;
|
||||||
|
+ self.consecutive_high = 0;
|
||||||
|
+ if self.consecutive_low >= Self::HYSTERESIS {
|
||||||
|
+ self.consecutive_low = 0;
|
||||||
|
+ let new_itr = self.current_itr.saturating_div(2).max(Self::MIN_ITR);
|
||||||
|
+ if new_itr != self.current_itr {
|
||||||
|
+ self.current_itr = new_itr;
|
||||||
|
+ return Some(self.current_itr);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ self.consecutive_low = 0;
|
||||||
|
+ self.consecutive_high = 0;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ None
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn current_itr(&self) -> u16 {
|
||||||
|
+ self.current_itr
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl Default for ItrTracker {
|
||||||
|
+ fn default() -> Self {
|
||||||
|
+ Self::new()
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs
|
||||||
|
index 373ea9b3..1a4b0667 100644
|
||||||
|
--- a/drivers/net/e1000d/src/main.rs
|
||||||
|
+++ b/drivers/net/e1000d/src/main.rs
|
||||||
|
@@ -8,0 +9 @@ pub mod device;
|
||||||
|
+pub mod itr;
|
||||||
|
@@ -47,0 +49,2 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ let mut itr_tracker = itr::ItrTracker::new();
|
||||||
|
+
|
||||||
|
@@ -74,0 +78 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ let mut irq_count: u64 = 0;
|
||||||
|
@@ -77,0 +82 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ irq_count += 1;
|
||||||
|
@@ -82,0 +88,4 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||||
|
+ if let Some(new_itr) = itr_tracker.update(irq_count) {
|
||||||
|
+ unsafe { scheme.adapter().set_itr(new_itr) };
|
||||||
|
+ }
|
||||||
|
+
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
diff --git a/drivers/acpid/src/thermal.rs b/drivers/acpid/src/thermal.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..4614e481
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/acpid/src/thermal.rs
|
||||||
|
@@ -0,0 +1,135 @@
|
||||||
|
+use acpi::aml::namespace::AmlName;
|
||||||
|
+use std::str::FromStr;
|
||||||
|
+use std::sync::RwLock;
|
||||||
|
+
|
||||||
|
+use crate::acpi::{AcpiContext, AmlEvalError};
|
||||||
|
+use amlserde::AmlSerdeValue;
|
||||||
|
+
|
||||||
|
+#[derive(Clone, Debug)]
|
||||||
|
+pub struct ThermalZone {
|
||||||
|
+ pub name: String,
|
||||||
|
+ pub temperature_raw: Option<u64>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl ThermalZone {
|
||||||
|
+ fn from_zone_eval(ctx: &AcpiContext, zone_name: &str) -> Result<Self, ThermalError> {
|
||||||
|
+ let aml_prefix = format!("\\_TZ_.{zone_name}");
|
||||||
|
+
|
||||||
|
+ let mut temp_raw = None;
|
||||||
|
+
|
||||||
|
+ if let Ok(tmp_name) = AmlName::from_str(&format!("{aml_prefix}._TMP")) {
|
||||||
|
+ match ctx.aml_eval(tmp_name, Vec::new()) {
|
||||||
|
+ Ok(value) => {
|
||||||
|
+ if let AmlSerdeValue::Integer(t) = value {
|
||||||
|
+ temp_raw = Some(t as u64);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::debug!("Thermal zone {zone_name}: _TMP eval failed: {e:?}");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ Ok(Self {
|
||||||
|
+ name: zone_name.to_owned(),
|
||||||
|
+ temperature_raw: temp_raw,
|
||||||
|
+ })
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn temperature_celsius(&self) -> Option<f64> {
|
||||||
|
+ self.temperature_raw.map(|t| (t as f64 - 273.15) / 10.0)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn to_text(&self) -> String {
|
||||||
|
+ let mut s = String::new();
|
||||||
|
+ s.push_str(&format!("name={}\n", self.name));
|
||||||
|
+ if let Some(c) = self.temperature_celsius() {
|
||||||
|
+ s.push_str(&format!("temperature={:.1}°C\n", c));
|
||||||
|
+ } else {
|
||||||
|
+ s.push_str("temperature=na\n");
|
||||||
|
+ }
|
||||||
|
+ s
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[derive(Debug)]
|
||||||
|
+pub enum ThermalError {
|
||||||
|
+ EvalError(AmlEvalError),
|
||||||
|
+ NotFound,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl From<AmlEvalError> for ThermalError {
|
||||||
|
+ fn from(value: AmlEvalError) -> Self {
|
||||||
|
+ ThermalError::EvalError(value)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn discover_thermal_zones(ctx: &AcpiContext) -> Vec<ThermalZone> {
|
||||||
|
+ let mut zones = Vec::new();
|
||||||
|
+
|
||||||
|
+ let zone_names = match ctx.thermal_zone_names() {
|
||||||
|
+ Ok(names) => names,
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::debug!("Thermal zone discovery failed: {e:?}");
|
||||||
|
+ return zones;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ for zone_name in zone_names {
|
||||||
|
+ match ThermalZone::from_zone_eval(ctx, &zone_name) {
|
||||||
|
+ Ok(zone) => {
|
||||||
|
+ if zone.temperature_raw.is_some() {
|
||||||
|
+ log::info!(
|
||||||
|
+ "Thermal zone discovered: {} = {:.1}°C",
|
||||||
|
+ zone.name,
|
||||||
|
+ zone.temperature_celsius().unwrap_or(0.0)
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ zones.push(zone);
|
||||||
|
+ }
|
||||||
|
+ Err(e) => {
|
||||||
|
+ log::warn!("Thermal zone {zone_name}: discovery failed: {e:?}");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ zones
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct ThermalState {
|
||||||
|
+ zones: RwLock<Vec<ThermalZone>>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl ThermalState {
|
||||||
|
+ pub fn new() -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ zones: RwLock::new(Vec::new()),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn refresh(&self, ctx: &AcpiContext) {
|
||||||
|
+ let discovered = discover_thermal_zones(ctx);
|
||||||
|
+ if let Ok(mut zones) = self.zones.write() {
|
||||||
|
+ *zones = discovered;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn zones(&self) -> Vec<ThermalZone> {
|
||||||
|
+ self.zones.read().map(|g| g.clone()).unwrap_or_default()
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn zone_by_name(&self, name: &str) -> Option<ThermalZone> {
|
||||||
|
+ self.zones
|
||||||
|
+ .read()
|
||||||
|
+ .ok()?
|
||||||
|
+ .iter()
|
||||||
|
+ .find(|z| z.name == name)
|
||||||
|
+ .cloned()
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl Default for ThermalState {
|
||||||
|
+ fn default() -> Self {
|
||||||
|
+ Self::new()
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
--- a/logd/src/scheme.rs
|
||||||
|
+++ b/logd/src/scheme.rs
|
||||||
|
@@ -5,6 +5,7 @@
|
||||||
|
use std::os::fd::{FromRawFd, RawFd};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::mpsc::{self, Sender};
|
||||||
|
+use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use redox_scheme::scheme::SchemeSync;
|
||||||
|
use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket};
|
||||||
|
@@ -38,6 +39,50 @@
|
||||||
|
AddSink(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
+fn json_escape(s: &str) -> String {
|
||||||
|
+ let mut out = String::with_capacity(s.len());
|
||||||
|
+ for c in s.chars() {
|
||||||
|
+ match c {
|
||||||
|
+ '\\' => out.push_str("\\\\"),
|
||||||
|
+ '"' => out.push_str("\\\""),
|
||||||
|
+ '\n' => out.push_str("\\n"),
|
||||||
|
+ '\r' => out.push_str("\\r"),
|
||||||
|
+ '\t' => out.push_str("\\t"),
|
||||||
|
+ c if c.is_control() => out.push_str(&format!("\\u{:04x}", c as u32)),
|
||||||
|
+ c => out.push(c),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ out
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn format_json_line(context: &str, line: &[u8]) -> Vec<u8> {
|
||||||
|
+ let now = SystemTime::now()
|
||||||
|
+ .duration_since(UNIX_EPOCH)
|
||||||
|
+ .unwrap_or_default();
|
||||||
|
+ let secs = now.as_secs();
|
||||||
|
+ let timestamp = format!(
|
||||||
|
+ "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
|
||||||
|
+ 1970 + secs / 31_557_600,
|
||||||
|
+ (secs % 31_557_600) / 2_592_000 + 1,
|
||||||
|
+ (secs % 2_592_000) / 86_400 + 1,
|
||||||
|
+ (secs % 86_400) / 3600,
|
||||||
|
+ (secs % 3600) / 60,
|
||||||
|
+ secs % 60
|
||||||
|
+ );
|
||||||
|
+ let text = String::from_utf8_lossy(line).trim_end_matches('\n').to_string();
|
||||||
|
+ let (source, message) = match text.split_once(": ") {
|
||||||
|
+ Some((s, m)) => (s, m),
|
||||||
|
+ None => (context, text.as_str()),
|
||||||
|
+ };
|
||||||
|
+ let json = format!(
|
||||||
|
+ "{{\"timestamp\":\"{}\",\"source\":\"{}\",\"message\":\"{}\"}}\n",
|
||||||
|
+ json_escape(×tamp),
|
||||||
|
+ json_escape(source),
|
||||||
|
+ json_escape(message)
|
||||||
|
+ );
|
||||||
|
+ json.into_bytes()
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
struct LogFile {
|
||||||
|
file: File,
|
||||||
|
path: PathBuf,
|
||||||
|
@@ -110,6 +155,8 @@
|
||||||
|
let _ = std::fs::create_dir_all(LOG_DIR);
|
||||||
|
|
||||||
|
|
||||||
|
+ let json_format = std::env::var("LOGD_JSON").map_or(false, |v| v == "1");
|
||||||
|
+
|
||||||
|
let (output_tx, output_rx) = mpsc::channel::<OutputCmd>();
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
@@ -123,9 +170,15 @@
|
||||||
|
for cmd in output_rx {
|
||||||
|
match cmd {
|
||||||
|
OutputCmd::Log { context, line } => {
|
||||||
|
+ let out_line = if json_format {
|
||||||
|
+ format_json_line(&context, &line)
|
||||||
|
+ } else {
|
||||||
|
+ line.clone()
|
||||||
|
+ };
|
||||||
|
if let Some(ref mut f) = persistent {
|
||||||
|
- let _ = f.write(&line);
|
||||||
|
+ let _ = f.write(&out_line);
|
||||||
|
let _ = f.flush();
|
||||||
|
+ }
|
||||||
|
|
||||||
|
let service_name = context.split(':').next().unwrap_or("unknown");
|
||||||
|
if !service_name.is_empty() {
|
||||||
|
@@ -135,7 +188,7 @@
|
||||||
|
LogFile::open(PathBuf::from("/dev/null")).unwrap()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
- let _ = entry.write(&line);
|
||||||
|
+ let _ = entry.write(&out_line);
|
||||||
|
let _ = entry.maybe_rotate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -145,15 +198,14 @@
|
||||||
|
LogFile::open(PathBuf::from("/dev/null")).unwrap()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
- let _ = system_entry.write(&line);
|
||||||
|
+ let _ = system_entry.write(&out_line);
|
||||||
|
let _ = system_entry.maybe_rotate();
|
||||||
|
|
||||||
|
- }
|
||||||
|
for file in &mut files {
|
||||||
|
- let _ = file.write(&line);
|
||||||
|
+ let _ = file.write(&out_line);
|
||||||
|
let _ = file.flush();
|
||||||
|
}
|
||||||
|
- logs.push_back(line);
|
||||||
|
+ logs.push_back(out_line);
|
||||||
|
while logs.len() > MEMORY_LOG_LIMIT {
|
||||||
|
logs.pop_front();
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
index 3e42d590..79c2119f 100644
|
||||||
|
--- a/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/src/main.rs
|
||||||
|
@@ -46,13 +46,17 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
)
|
||||||
|
.expect("fbbootlogd: failed to subscribe to scheme events");
|
||||||
|
|
||||||
|
- event_queue
|
||||||
|
- .subscribe(
|
||||||
|
- scheme.input_handle.event_handle().as_raw_fd() as usize,
|
||||||
|
- Source::Input,
|
||||||
|
- event::EventFlags::READ,
|
||||||
|
- )
|
||||||
|
- .expect("fbbootlogd: failed to subscribe to scheme events");
|
||||||
|
+ if let Some(ref input_handle) = scheme.input_handle {
|
||||||
|
+ event_queue
|
||||||
|
+ .subscribe(
|
||||||
|
+ input_handle.event_handle().as_raw_fd() as usize,
|
||||||
|
+ Source::Input,
|
||||||
|
+ event::EventFlags::READ,
|
||||||
|
+ )
|
||||||
|
+ .expect("fbbootlogd: failed to subscribe to input events");
|
||||||
|
+ } else {
|
||||||
|
+ eprintln!("fbbootlogd: running without input handle (log-only mode)");
|
||||||
|
+ }
|
||||||
|
|
||||||
|
{
|
||||||
|
let log_fd = socket
|
||||||
|
@@ -76,6 +80,11 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
// driver handoff. In the future inputd may directly pass a handle to the display instead.
|
||||||
|
//libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace");
|
||||||
|
|
||||||
|
+ enum Action {
|
||||||
|
+ Input(Event),
|
||||||
|
+ Handoff,
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
for event in event_queue {
|
||||||
|
match event.expect("fbbootlogd: failed to get event").user_data {
|
||||||
|
Source::Scheme => loop {
|
||||||
|
@@ -88,20 +97,31 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Source::Input => {
|
||||||
|
- let mut events = [Event::new(); 16];
|
||||||
|
- loop {
|
||||||
|
- match scheme
|
||||||
|
- .input_handle
|
||||||
|
- .read_events(&mut events)
|
||||||
|
- .expect("fbbootlogd: error while reading events")
|
||||||
|
- {
|
||||||
|
- ConsumerHandleEvent::Events(&[]) => break,
|
||||||
|
- ConsumerHandleEvent::Events(events) => {
|
||||||
|
- for event in events {
|
||||||
|
- scheme.handle_input(&event);
|
||||||
|
+ let mut actions: Vec<Action> = Vec::new();
|
||||||
|
+ if let Some(ref mut input_handle) = scheme.input_handle {
|
||||||
|
+ let mut events = [Event::new(); 16];
|
||||||
|
+ loop {
|
||||||
|
+ match input_handle
|
||||||
|
+ .read_events(&mut events)
|
||||||
|
+ .expect("fbbootlogd: error while reading events")
|
||||||
|
+ {
|
||||||
|
+ ConsumerHandleEvent::Events(&[]) => break,
|
||||||
|
+ ConsumerHandleEvent::Events(events) => {
|
||||||
|
+ for event in events {
|
||||||
|
+ actions.push(Action::Input(*event));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ ConsumerHandleEvent::Handoff => {
|
||||||
|
+ actions.push(Action::Handoff);
|
||||||
|
+ break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- ConsumerHandleEvent::Handoff => {
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ for action in actions {
|
||||||
|
+ match action {
|
||||||
|
+ Action::Input(event) => scheme.handle_input(&event),
|
||||||
|
+ Action::Handoff => {
|
||||||
|
eprintln!("fbbootlogd: handoff requested");
|
||||||
|
scheme.handle_handoff();
|
||||||
|
}
|
||||||
|
diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
index 812c4a5b..53e4bc75 100644
|
||||||
|
--- a/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
+++ b/drivers/graphics/fbbootlogd/src/scheme.rs
|
||||||
|
@@ -14,7 +14,7 @@ use syscall::schemev2::NewFdFlags;
|
||||||
|
use syscall::{Error, Result, EACCES, EBADF, EINVAL, ENOENT};
|
||||||
|
|
||||||
|
pub struct FbbootlogScheme {
|
||||||
|
- pub input_handle: ConsumerHandle,
|
||||||
|
+ pub input_handle: Option<ConsumerHandle>,
|
||||||
|
display_map: Option<V2DisplayMap>,
|
||||||
|
text_screen: console_draw::TextScreen,
|
||||||
|
text_buffer: console_draw::TextBuffer,
|
||||||
|
@@ -25,8 +25,16 @@ pub struct FbbootlogScheme {
|
||||||
|
|
||||||
|
impl FbbootlogScheme {
|
||||||
|
pub fn new() -> FbbootlogScheme {
|
||||||
|
+ let input_handle = match ConsumerHandle::bootlog_vt() {
|
||||||
|
+ Ok(handle) => Some(handle),
|
||||||
|
+ Err(err) => {
|
||||||
|
+ eprintln!("fbbootlogd: Failed to open vt (non-fatal): {err}");
|
||||||
|
+ None
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
let mut scheme = FbbootlogScheme {
|
||||||
|
- input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"),
|
||||||
|
+ input_handle,
|
||||||
|
display_map: None,
|
||||||
|
text_screen: console_draw::TextScreen::new(),
|
||||||
|
text_buffer: console_draw::TextBuffer::new(1000),
|
||||||
|
@@ -41,8 +49,19 @@ impl FbbootlogScheme {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_handoff(&mut self) {
|
||||||
|
- let new_display_handle = match self.input_handle.open_display_v2() {
|
||||||
|
- Ok(display) => V2GraphicsHandle::from_file(display).unwrap(),
|
||||||
|
+ let Some(ref input_handle) = self.input_handle else {
|
||||||
|
+ eprintln!("fbbootlogd: No input handle, skipping display handoff");
|
||||||
|
+ return;
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ let new_display_handle = match input_handle.open_display_v2() {
|
||||||
|
+ Ok(display) => match V2GraphicsHandle::from_file(display) {
|
||||||
|
+ Ok(handle) => handle,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ eprintln!("fbbootlogd: Display v2 protocol not supported: {err}");
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("fbbootlogd: No display present yet: {err}");
|
||||||
|
return;
|
||||||
|
diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
index eb09b97e..4e347475 100644
|
||||||
|
--- a/drivers/graphics/fbcond/src/display.rs
|
||||||
|
+++ b/drivers/graphics/fbcond/src/display.rs
|
||||||
|
@@ -31,7 +31,13 @@ impl Display {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
- let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap();
|
||||||
|
+ let new_display_handle = match V2GraphicsHandle::from_file(display_file) {
|
||||||
|
+ Ok(handle) => handle,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::error!("fbcond: Display v2 protocol not supported: {err}");
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
|
||||||
|
log::debug!("fbcond: Opened new display");
|
||||||
|
|
||||||
|
diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs
|
||||||
|
index b68e8211..b3e8354c 100644
|
||||||
|
--- a/drivers/inputd/src/lib.rs
|
||||||
|
+++ b/drivers/inputd/src/lib.rs
|
||||||
|
@@ -77,14 +77,14 @@ impl ConsumerHandle {
|
||||||
|
));
|
||||||
|
let display_path = display_path.to_str().unwrap();
|
||||||
|
|
||||||
|
- let display_file =
|
||||||
|
- libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0)
|
||||||
|
- .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) })
|
||||||
|
- .unwrap_or_else(|err| {
|
||||||
|
- panic!("failed to open display {}: {}", display_path, err);
|
||||||
|
- });
|
||||||
|
-
|
||||||
|
- Ok(display_file)
|
||||||
|
+ libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0)
|
||||||
|
+ .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) })
|
||||||
|
+ .map_err(|err| {
|
||||||
|
+ io::Error::new(
|
||||||
|
+ io::ErrorKind::Other,
|
||||||
|
+ format!("failed to open display {}: {}", display_path, err),
|
||||||
|
+ )
|
||||||
|
+ })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_events<'a>(&self, events: &'a mut [Event]) -> io::Result<ConsumerHandleEvent<'a>> {
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
diff --git a/init.initfs.d/00_logd.service b/init.initfs.d/00_logd.service
|
||||||
|
index b2293176..87dd7a45 100644
|
||||||
|
--- a/init.initfs.d/00_logd.service
|
||||||
|
+++ b/init.initfs.d/00_logd.service
|
||||||
|
@@ -3,0 +4 @@ default_dependencies = false
|
||||||
|
+requires = ["00_randd.service"]
|
||||||
|
diff --git a/init.initfs.d/20_fbbootlogd.service b/init.initfs.d/20_fbbootlogd.service
|
||||||
|
index 199c112a..5f1178a5 100644
|
||||||
|
--- a/init.initfs.d/20_fbbootlogd.service
|
||||||
|
+++ b/init.initfs.d/20_fbbootlogd.service
|
||||||
|
@@ -2,0 +3 @@ description = "Graphical bootlog"
|
||||||
|
+requires = ["00_logd.service"]
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
|
||||||
|
--- a/init/src/scheduler.rs
|
||||||
|
+++ b/init/src/scheduler.rs
|
||||||
|
@@ -1,14 +1,34 @@
|
||||||
|
-use std::collections::{BTreeSet, VecDeque};
|
||||||
|
+use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||||
|
|
||||||
|
use crate::InitConfig;
|
||||||
|
-use crate::color::{init_error, status_ok, status_skip};
|
||||||
|
+use crate::color::{init_error, init_warn, status_ok, status_skip};
|
||||||
|
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
|
||||||
|
|
||||||
|
const SPAWN_BATCH_SIZE: usize = 50;
|
||||||
|
|
||||||
|
+#[derive(Clone, Debug)]
|
||||||
|
+pub enum RestartPolicy {
|
||||||
|
+ Never,
|
||||||
|
+ OnFailure,
|
||||||
|
+ Always,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Tracks the restart state for a supervised service.
|
||||||
|
+pub struct ServiceState {
|
||||||
|
+ pub unit_id: UnitId,
|
||||||
|
+ pub cmd: String,
|
||||||
|
+ pub restart_policy: RestartPolicy,
|
||||||
|
+ pub max_restarts: u32,
|
||||||
|
+ pub restart_count: u32,
|
||||||
|
+ /// Monotonic time of last restart (for backoff calculation).
|
||||||
|
+ pub last_restart_ms: u64,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
pub struct Scheduler {
|
||||||
|
pending: VecDeque<Job>,
|
||||||
|
completed: BTreeSet<UnitId>,
|
||||||
|
+ /// Maps child PID → service state for supervised services.
|
||||||
|
+ pub supervised: BTreeMap<u32, ServiceState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Job {
|
||||||
|
@@ -25,6 +45,7 @@
|
||||||
|
Scheduler {
|
||||||
|
pending: VecDeque::new(),
|
||||||
|
completed: BTreeSet::new(),
|
||||||
|
+ supervised: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -75,25 +96,38 @@
|
||||||
|
|
||||||
|
match job.kind {
|
||||||
|
JobKind::Start => {
|
||||||
|
- let deps_ok = {
|
||||||
|
+ let (deps_ok, hard_deps_met) = {
|
||||||
|
let unit = unit_store.unit(&job.unit);
|
||||||
|
- let mut ok = true;
|
||||||
|
- for dep in &unit.info.requires_weak {
|
||||||
|
+ let mut hard_deps_met = true;
|
||||||
|
+ for dep in &unit.info.requires {
|
||||||
|
if self.completed.contains(dep) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !unit_store.has_unit(dep) {
|
||||||
|
- continue;
|
||||||
|
- }
|
||||||
|
- let in_pending = self.pending.iter().any(|pj| &pj.unit == dep);
|
||||||
|
- if in_pending {
|
||||||
|
- ok = false;
|
||||||
|
+ init_error(&format!(
|
||||||
|
+ "{}: hard dependency '{}' not found, skipping",
|
||||||
|
+ job.unit.0, dep.0
|
||||||
|
+ ));
|
||||||
|
+ hard_deps_met = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
+ hard_deps_met = false;
|
||||||
|
+ break;
|
||||||
|
}
|
||||||
|
- ok
|
||||||
|
+ let weak_ok = unit.info.requires_weak.iter().all(|dep| {
|
||||||
|
+ self.completed.contains(dep)
|
||||||
|
+ || !unit_store.has_unit(dep)
|
||||||
|
+ || self.pending.iter().any(|pj| &pj.unit == dep)
|
||||||
|
+ });
|
||||||
|
+ (weak_ok, hard_deps_met)
|
||||||
|
};
|
||||||
|
|
||||||
|
+ if !hard_deps_met {
|
||||||
|
+ init_warn(&format!("{}: hard dependency not met, skipping", job.unit.0));
|
||||||
|
+ self.completed.insert(job.unit);
|
||||||
|
+ continue 'a;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if !deps_ok {
|
||||||
|
defer_count += 1;
|
||||||
|
self.pending.push_back(job);
|
||||||
|
@@ -106,7 +140,7 @@
|
||||||
|
defer_count = 0;
|
||||||
|
|
||||||
|
let unit = unit_store.unit_mut(&job.unit);
|
||||||
|
- run(unit, init_config);
|
||||||
|
+ run(unit, init_config, &mut self.supervised);
|
||||||
|
self.completed.insert(job.unit);
|
||||||
|
spawned_this_step += 1;
|
||||||
|
|
||||||
|
@@ -119,7 +153,7 @@
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-fn run(unit: &mut Unit, config: &mut InitConfig) {
|
||||||
|
+fn run(unit: &mut Unit, config: &mut InitConfig, supervised: &mut BTreeMap<u32, ServiceState>) {
|
||||||
|
match &unit.kind {
|
||||||
|
UnitKind::LegacyScript { script } => {
|
||||||
|
for cmd in script.clone() {
|
||||||
|
@@ -127,13 +161,15 @@
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UnitKind::Service { service } => {
|
||||||
|
- let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0);
|
||||||
|
+ let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0).clone();
|
||||||
|
if config.skip_cmd.contains(&service.cmd) {
|
||||||
|
status_skip(&format!("Skipping {} ({})", desc, service.cmd));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status_ok(&format!("Started {}", desc));
|
||||||
|
service.spawn(&config.envs);
|
||||||
|
+ // Supervision infrastructure is in place; full PID tracking requires
|
||||||
|
+ // service.spawn() to return Option<u32> (added by a later patch).
|
||||||
|
}
|
||||||
|
UnitKind::Target {} => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/init/src/script.rs b/init/src/script.rs
|
||||||
|
--- a/init/src/script.rs
|
||||||
|
+++ b/init/src/script.rs
|
||||||
|
@@ -12,12 +12,13 @@
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-pub struct Script(pub Vec<Command>, pub Vec<UnitId>);
|
||||||
|
+pub struct Script(pub Vec<Command>, pub Vec<UnitId>, pub Vec<UnitId>);
|
||||||
|
|
||||||
|
impl Script {
|
||||||
|
pub fn from_str(config: &str, errors: &mut Vec<String>) -> io::Result<Script> {
|
||||||
|
let mut cmds = vec![];
|
||||||
|
let mut requires_weak = vec![];
|
||||||
|
+ let mut requires = vec![];
|
||||||
|
|
||||||
|
for line_raw in config.lines() {
|
||||||
|
let line = line_raw.trim();
|
||||||
|
@@ -27,14 +28,14 @@
|
||||||
|
|
||||||
|
let args = line.split(' ').map(subst_env);
|
||||||
|
|
||||||
|
- match Command::parse(args, &mut requires_weak) {
|
||||||
|
+ match Command::parse(args, &mut requires_weak, &mut requires) {
|
||||||
|
Ok(None) => {}
|
||||||
|
Ok(Some(cmd)) => cmds.push(cmd),
|
||||||
|
Err(err) => errors.push(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- Ok(Script(cmds, requires_weak))
|
||||||
|
+ Ok(Script(cmds, requires_weak, requires))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -54,12 +55,17 @@
|
||||||
|
fn parse(
|
||||||
|
mut args: impl Iterator<Item = String>,
|
||||||
|
requires_weak: &mut Vec<UnitId>,
|
||||||
|
+ requires: &mut Vec<UnitId>,
|
||||||
|
) -> Result<Option<Command>, String> {
|
||||||
|
let Some(cmd) = args.next() else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
match cmd.as_str() {
|
||||||
|
+ "requires" => {
|
||||||
|
+ requires.extend(args.map(UnitId));
|
||||||
|
+ Ok(None)
|
||||||
|
+ }
|
||||||
|
"requires_weak" => {
|
||||||
|
requires_weak.extend(args.map(UnitId));
|
||||||
|
Ok(None)
|
||||||
|
|
||||||
|
diff --git a/init/src/unit.rs b/init/src/unit.rs
|
||||||
|
--- a/init/src/unit.rs
|
||||||
|
+++ b/init/src/unit.rs
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-use std::collections::BTreeMap;
|
||||||
|
+use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
|
@@ -76,7 +76,9 @@
|
||||||
|
|
||||||
|
pub fn load_units(&mut self, root_unit: UnitId, errors: &mut Vec<String>) -> Vec<UnitId> {
|
||||||
|
let mut loaded_units = vec![];
|
||||||
|
- let mut pending_units = vec![root_unit];
|
||||||
|
+ let mut pending_units = vec![root_unit.clone()];
|
||||||
|
+ let mut seen = BTreeSet::new();
|
||||||
|
+ seen.insert(root_unit);
|
||||||
|
|
||||||
|
while let Some(unit_id) = pending_units.pop() {
|
||||||
|
if self.units.contains_key(&unit_id) {
|
||||||
|
@@ -85,7 +87,16 @@
|
||||||
|
let unit = self.load_single_unit(unit_id, errors);
|
||||||
|
if let Some(unit) = unit {
|
||||||
|
loaded_units.push(unit.clone());
|
||||||
|
+ for dep in &self.unit(&unit).info.requires {
|
||||||
|
+ if !seen.insert(dep.clone()) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ pending_units.push(dep.clone());
|
||||||
|
+ }
|
||||||
|
for dep in &self.unit(&unit).info.requires_weak {
|
||||||
|
+ if !seen.insert(dep.clone()) {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
pending_units.push(dep.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -125,6 +136,8 @@
|
||||||
|
#[serde(default = "true_bool")]
|
||||||
|
pub default_dependencies: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
+ pub requires: Vec<UnitId>,
|
||||||
|
+ #[serde(default)]
|
||||||
|
pub requires_weak: Vec<UnitId>,
|
||||||
|
pub condition_architecture: Option<Vec<String>>,
|
||||||
|
// FIXME replace this with hwd reading from the devicetree
|
||||||
|
@@ -191,6 +204,7 @@
|
||||||
|
info: UnitInfo {
|
||||||
|
description: None,
|
||||||
|
default_dependencies: true,
|
||||||
|
+ requires: script.2,
|
||||||
|
requires_weak: script.1,
|
||||||
|
condition_architecture: None,
|
||||||
|
condition_board: None,
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
diff --git a/drivers/storage/lived/src/main.rs b/drivers/storage/lived/src/main.rs
|
||||||
|
index 2ca1ff27..cd92fa85 100644
|
||||||
|
--- a/drivers/storage/lived/src/main.rs
|
||||||
|
+++ b/drivers/storage/lived/src/main.rs
|
||||||
|
@@ -55,8 +55,10 @@ impl LiveDisk {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Disk for LiveDisk {
|
||||||
|
+ // Must be 512 (redoxfs BLOCK_SIZE), not PAGE_SIZE: DiskWrapper::read rejects
|
||||||
|
+ // buffers not aligned to block_size, and redoxfs reads in 512-byte chunks.
|
||||||
|
fn block_size(&self) -> u32 {
|
||||||
|
- PAGE_SIZE as u32
|
||||||
|
+ 512
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> u64 {
|
||||||
|
@@ -64,11 +66,12 @@ impl Disk for LiveDisk {
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read(&mut self, mut block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
||||||
|
- let mut offset = (block as usize) * PAGE_SIZE;
|
||||||
|
+ let bs = self.block_size() as usize;
|
||||||
|
+ let mut offset = (block as usize) * bs;
|
||||||
|
if offset + buffer.len() > self.original.len() {
|
||||||
|
return Err(syscall::Error::new(EINVAL));
|
||||||
|
}
|
||||||
|
- for chunk in buffer.chunks_mut(PAGE_SIZE) {
|
||||||
|
+ for chunk in buffer.chunks_mut(bs) {
|
||||||
|
match self.overlay.get(&block) {
|
||||||
|
Some(overlay) => {
|
||||||
|
chunk.copy_from_slice(&overlay[..chunk.len()]);
|
||||||
|
@@ -78,26 +81,27 @@ impl Disk for LiveDisk {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block += 1;
|
||||||
|
- offset += PAGE_SIZE;
|
||||||
|
+ offset += bs;
|
||||||
|
}
|
||||||
|
Ok(buffer.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write(&mut self, mut block: u64, buffer: &[u8]) -> syscall::Result<usize> {
|
||||||
|
- let mut offset = (block as usize) * PAGE_SIZE;
|
||||||
|
+ let bs = self.block_size() as usize;
|
||||||
|
+ let mut offset = (block as usize) * bs;
|
||||||
|
if offset + buffer.len() > self.original.len() {
|
||||||
|
return Err(syscall::Error::new(EINVAL));
|
||||||
|
}
|
||||||
|
- for chunk in buffer.chunks(PAGE_SIZE) {
|
||||||
|
+ for chunk in buffer.chunks(bs) {
|
||||||
|
self.overlay.entry(block).or_insert_with(|| {
|
||||||
|
- let offset = (block as usize) * PAGE_SIZE;
|
||||||
|
- self.original[offset..offset + PAGE_SIZE]
|
||||||
|
+ let offset = (block as usize) * bs;
|
||||||
|
+ self.original[offset..offset + bs]
|
||||||
|
.to_vec()
|
||||||
|
.into_boxed_slice()
|
||||||
|
})[..chunk.len()]
|
||||||
|
.copy_from_slice(chunk);
|
||||||
|
block += 1;
|
||||||
|
- offset += PAGE_SIZE;
|
||||||
|
+ offset += bs;
|
||||||
|
}
|
||||||
|
Ok(buffer.len())
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
--- a/drivers/pcid/src/main.rs
|
||||||
|
+++ b/drivers/pcid/src/main.rs
|
||||||
|
@@ -262,11 +262,10 @@
|
||||||
|
let access_fd = socket
|
||||||
|
.create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0)
|
||||||
|
.expect("failed to issue this resource");
|
||||||
|
- let access_bytes = access_fd.to_ne_bytes();
|
||||||
|
if let Err(err) = register_pci.call_wo(
|
||||||
|
- &access_bytes,
|
||||||
|
- syscall::CallFlags::WRITE | syscall::CallFlags::FD,
|
||||||
|
&[],
|
||||||
|
+ syscall::CallFlags::WRITE | syscall::CallFlags::FD,
|
||||||
|
+ &[access_fd as u64],
|
||||||
|
) {
|
||||||
|
warn!("pcid: failed to send pci_fd to acpid (error: {}). Running without ACPI integration.", err);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
diff --git a/drivers/rtcd/src/main.rs b/drivers/rtcd/src/main.rs
|
||||||
|
index 3e913780..41383ca3 100644
|
||||||
|
--- a/drivers/rtcd/src/main.rs
|
||||||
|
+++ b/drivers/rtcd/src/main.rs
|
||||||
|
@@ -1,4 +1,5 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
+use std::io::Write;
|
||||||
|
|
||||||
|
// TODO: Do not use target architecture to distinguish these.
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
@@ -17,7 +18,14 @@ fn main() -> Result<()> {
|
||||||
|
let time_s = self::x86::get_time();
|
||||||
|
let time_ns = u128::from(time_s) * 1_000_000_000;
|
||||||
|
|
||||||
|
- std::fs::write("/scheme/sys/update_time_offset", &time_ns.to_ne_bytes())
|
||||||
|
+ // Open the sys scheme resource directly without O_CREAT, since update_time_offset
|
||||||
|
+ // is a pre-existing kernel resource. Using std::fs::write (which sets O_CREAT) can
|
||||||
|
+ // trigger EEXIST from the kernel's named pipe subsystem.
|
||||||
|
+ let mut file = std::fs::OpenOptions::new()
|
||||||
|
+ .write(true)
|
||||||
|
+ .open("/scheme/sys/update_time_offset")
|
||||||
|
+ .context("failed to open time offset")?;
|
||||||
|
+ file.write_all(&time_ns.to_ne_bytes())
|
||||||
|
.context("failed to write to time offset")?;
|
||||||
|
}
|
||||||
|
// TODO: aarch64 is currently handled in the kernel
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs
|
||||||
|
--- a/drivers/acpid/src/aml_physmem.rs
|
||||||
|
+++ b/drivers/acpid/src/aml_physmem.rs
|
||||||
|
@@ -143,7 +143,7 @@
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AmlPhysMemHandler {
|
||||||
|
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||||
|
- pci_fd: Arc<Option<libredox::Fd>>,
|
||||||
|
+ pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
|
||||||
|
aml_mutexes: Arc<Mutex<FxHashMap<u32, Arc<AmlMutex>>>>,
|
||||||
|
next_mutex_handle: Arc<AtomicU32>,
|
||||||
|
}
|
||||||
|
@@ -163,16 +163,10 @@
|
||||||
|
/// Read from a physical address.
|
||||||
|
/// Generic parameter must be u8, u16, u32 or u64.
|
||||||
|
impl AmlPhysMemHandler {
|
||||||
|
- pub fn new(pci_fd_opt: Option<&libredox::Fd>, page_cache: Arc<Mutex<AmlPageCache>>) -> Self {
|
||||||
|
- let pci_fd = if let Some(pci_fd) = pci_fd_opt {
|
||||||
|
- Some(libredox::Fd::new(pci_fd.raw()))
|
||||||
|
- } else {
|
||||||
|
- log::error!("pci_fd is not registered");
|
||||||
|
- None
|
||||||
|
- };
|
||||||
|
+ pub fn new(pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>, page_cache: Arc<Mutex<AmlPageCache>>) -> Self {
|
||||||
|
Self {
|
||||||
|
page_cache,
|
||||||
|
- pci_fd: Arc::new(pci_fd),
|
||||||
|
+ pci_fd,
|
||||||
|
aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())),
|
||||||
|
next_mutex_handle: Arc::new(AtomicU32::new(1)),
|
||||||
|
}
|
||||||
|
@@ -218,7 +212,8 @@
|
||||||
|
|
||||||
|
fn read_pci(&self, addr: PciAddress, off: u16, value: &mut [u8]) {
|
||||||
|
let metadata = Self::pci_call_metadata(1, addr, off);
|
||||||
|
- match &*self.pci_fd {
|
||||||
|
+ let guard = self.pci_fd.read();
|
||||||
|
+ match guard.as_ref() {
|
||||||
|
Some(pci_fd) => match pci_fd.call_ro(value, syscall::CallFlags::empty(), &metadata) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
@@ -236,7 +231,8 @@
|
||||||
|
|
||||||
|
fn write_pci(&self, addr: PciAddress, off: u16, value: &[u8]) {
|
||||||
|
let metadata = Self::pci_call_metadata(2, addr, off);
|
||||||
|
- match &*self.pci_fd {
|
||||||
|
+ let guard = self.pci_fd.read();
|
||||||
|
+ match guard.as_ref() {
|
||||||
|
Some(pci_fd) => match pci_fd.call_wo(value, syscall::CallFlags::empty(), &metadata) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
@@ -0,0 +1,757 @@
|
|||||||
|
diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs
|
||||||
|
index d7af4cba..061ef2cf 100644
|
||||||
|
--- a/drivers/input/ps2d/src/controller.rs
|
||||||
|
+++ b/drivers/input/ps2d/src/controller.rs
|
||||||
|
@@ -97,6 +97,14 @@ enum KeyboardCommandData {
|
||||||
|
const DEFAULT_TIMEOUT: u64 = 50_000;
|
||||||
|
// Reset timeout in microseconds
|
||||||
|
const RESET_TIMEOUT: u64 = 1_000_000;
|
||||||
|
+// Maximum bytes to drain during flush (Linux: I8042_BUFFER_SIZE)
|
||||||
|
+const FLUSH_LIMIT: usize = 4096;
|
||||||
|
+// Controller self-test pass value (Linux: I8042_RET_CTL_TEST)
|
||||||
|
+const SELFTEST_PASS: u8 = 0x55;
|
||||||
|
+// Controller self-test retries (Linux: 5 attempts)
|
||||||
|
+const SELFTEST_RETRIES: usize = 5;
|
||||||
|
+// AUX port test pass value (Linux returns 0x00 on success)
|
||||||
|
+const AUX_TEST_PASS: u8 = 0x00;
|
||||||
|
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
pub struct Ps2 {
|
||||||
|
@@ -129,7 +137,15 @@ impl Ps2 {
|
||||||
|
|
||||||
|
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
- unimplemented!()
|
||||||
|
+ // PS/2 controller is x86-only hardware. On other architectures, construct
|
||||||
|
+ // a zeroed struct; init() will fail at the controller self-test and the
|
||||||
|
+ // daemon will log an error and stop attempting keyboard/mouse operations.
|
||||||
|
+ Ps2 {
|
||||||
|
+ data: Mmio::new(0),
|
||||||
|
+ status: ReadOnly::new(Mmio::new(0)),
|
||||||
|
+ command: WriteOnly::new(Mmio::new(0)),
|
||||||
|
+ mouse_resets: 0,
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&mut self) -> StatusFlags {
|
||||||
|
@@ -261,6 +277,30 @@ impl Ps2 {
|
||||||
|
self.write(command as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
+ pub fn set_leds(&mut self, caps: bool, num: bool, scroll: bool) {
|
||||||
|
+ let mut led_byte = 0u8;
|
||||||
|
+ if scroll { led_byte |= 1; }
|
||||||
|
+ if num { led_byte |= 2; }
|
||||||
|
+ if caps { led_byte |= 4; }
|
||||||
|
+ if let Err(err) = self.keyboard_command_inner(0xED) {
|
||||||
|
+ log::debug!("ps2d: LED command 0xED not supported: {:?}", err);
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ match self.read_timeout(DEFAULT_TIMEOUT) {
|
||||||
|
+ Ok(0xFA) => {
|
||||||
|
+ if let Err(err) = self.write(led_byte) {
|
||||||
|
+ log::debug!("ps2d: failed to send LED byte {:02X}: {:?}", led_byte, err);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Ok(val) => {
|
||||||
|
+ log::debug!("ps2d: LED command ACK expected 0xFA, got {:02X}", val);
|
||||||
|
+ }
|
||||||
|
+ Err(err) => {
|
||||||
|
+ log::debug!("ps2d: LED command ACK timeout: {:?}", err);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
pub fn next(&mut self) -> Option<(bool, u8)> {
|
||||||
|
let status = self.status();
|
||||||
|
if status.contains(StatusFlags::OUTPUT_FULL) {
|
||||||
|
@@ -271,6 +311,50 @@ impl Ps2 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ /// Drain all pending bytes from the controller output buffer.
|
||||||
|
+ /// Borrowed from Linux i8042_flush(): stale firmware/BIOS bytes can be
|
||||||
|
+ /// misinterpreted as device responses during initialization.
|
||||||
|
+ fn flush(&mut self) -> usize {
|
||||||
|
+ let mut count = 0;
|
||||||
|
+ while self.status().contains(StatusFlags::OUTPUT_FULL) {
|
||||||
|
+ if count >= FLUSH_LIMIT {
|
||||||
|
+ warn!("flush: exceeded limit, controller may be stuck");
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ let data = self.data.read();
|
||||||
|
+ trace!("flush: discarded {:02X}", data);
|
||||||
|
+ count += 1;
|
||||||
|
+ }
|
||||||
|
+ if count > 0 {
|
||||||
|
+ debug!("flushed {} stale bytes from controller", count);
|
||||||
|
+ }
|
||||||
|
+ count
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Test the AUX (mouse) port via controller command 0xA9.
|
||||||
|
+ /// Borrowed from Linux: verifies electrical connectivity before
|
||||||
|
+ /// attempting to talk to the mouse. Returns true if the port passed.
|
||||||
|
+ fn test_aux_port(&mut self) -> bool {
|
||||||
|
+ if let Err(err) = self.command(Command::TestSecond) {
|
||||||
|
+ warn!("aux port test command failed: {:?}", err);
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+ match self.read() {
|
||||||
|
+ Ok(AUX_TEST_PASS) => {
|
||||||
|
+ debug!("aux port test passed");
|
||||||
|
+ true
|
||||||
|
+ }
|
||||||
|
+ Ok(val) => {
|
||||||
|
+ warn!("aux port test failed: {:02X}", val);
|
||||||
|
+ false
|
||||||
|
+ }
|
||||||
|
+ Err(err) => {
|
||||||
|
+ warn!("aux port test read timeout: {:?}", err);
|
||||||
|
+ false
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
pub fn init_keyboard(&mut self) -> Result<(), Error> {
|
||||||
|
let mut b;
|
||||||
|
|
||||||
|
@@ -308,66 +392,125 @@ impl Ps2 {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self) -> Result<(), Error> {
|
||||||
|
+ // Linux i8042_controller_check(): verify controller is present by
|
||||||
|
+ // flushing any stale data. A stuck output buffer means no controller.
|
||||||
|
+ self.flush();
|
||||||
|
+
|
||||||
|
+ // Bare-metal controllers may be slow after firmware handoff.
|
||||||
|
+ // Give the controller a moment to finish POST before sending commands.
|
||||||
|
+ std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
+
|
||||||
|
{
|
||||||
|
- // Disable devices
|
||||||
|
- self.command(Command::DisableFirst)?;
|
||||||
|
- self.command(Command::DisableSecond)?;
|
||||||
|
+ // Disable both ports first — use retry because the controller
|
||||||
|
+ // may still be settling or temporarily unresponsive.
|
||||||
|
+ // Failure here is non-fatal: we continue and attempt the rest
|
||||||
|
+ // of initialization. A truly absent controller will fail later
|
||||||
|
+ // at self-test or keyboard reset.
|
||||||
|
+ if let Err(err) = self.retry(
|
||||||
|
+ format_args!("disable first port"),
|
||||||
|
+ 3,
|
||||||
|
+ |x| x.command(Command::DisableFirst),
|
||||||
|
+ ) {
|
||||||
|
+ warn!("disable first port failed: {:?}", err);
|
||||||
|
+ }
|
||||||
|
+ if let Err(err) = self.retry(
|
||||||
|
+ format_args!("disable second port"),
|
||||||
|
+ 3,
|
||||||
|
+ |x| x.command(Command::DisableSecond),
|
||||||
|
+ ) {
|
||||||
|
+ warn!("disable second port failed: {:?}", err);
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
- // Disable clocks, disable interrupts, and disable translate
|
||||||
|
+ // Flush again after disabling — firmware may have queued more bytes
|
||||||
|
+ self.flush();
|
||||||
|
+
|
||||||
|
+ // Linux i8042_controller_init() step 1: write a known-safe config
|
||||||
|
+ // (interrupts off, both ports disabled) so stale config can't cause
|
||||||
|
+ // spurious interrupts during the rest of init.
|
||||||
|
{
|
||||||
|
- // Since the default config may have interrupts enabled, and the kernel may eat up
|
||||||
|
- // our data in that case, we will write a config without reading the current one
|
||||||
|
let config = ConfigFlags::POST_PASSED
|
||||||
|
| ConfigFlags::FIRST_DISABLED
|
||||||
|
| ConfigFlags::SECOND_DISABLED;
|
||||||
|
self.set_config(config)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
- // The keyboard seems to still collect bytes even when we disable
|
||||||
|
- // the port, so we must disable the keyboard too
|
||||||
|
+ // Linux i8042_controller_selftest(): retry up to 5 times with delay.
|
||||||
|
+ // "On some really fragile systems this does not take the first time."
|
||||||
|
+ {
|
||||||
|
+ let mut passed = false;
|
||||||
|
+ for attempt in 0..SELFTEST_RETRIES {
|
||||||
|
+ if let Err(err) = self.command(Command::TestController) {
|
||||||
|
+ warn!("self-test command failed (attempt {}): {:?}", attempt + 1, err);
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ match self.read() {
|
||||||
|
+ Ok(SELFTEST_PASS) => {
|
||||||
|
+ passed = true;
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ Ok(val) => {
|
||||||
|
+ warn!(
|
||||||
|
+ "self-test unexpected value {:02X} (attempt {}/{})",
|
||||||
|
+ val,
|
||||||
|
+ attempt + 1,
|
||||||
|
+ SELFTEST_RETRIES
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ Err(err) => {
|
||||||
|
+ warn!("self-test read timeout (attempt {}): {:?}", attempt + 1, err);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ // Linux: msleep(50) between retries
|
||||||
|
+ std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
+ }
|
||||||
|
+ if !passed {
|
||||||
|
+ // Linux on x86: "giving up on controller selftest, continuing anyway"
|
||||||
|
+ warn!("controller self-test did not pass after {} attempts, continuing", SELFTEST_RETRIES);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Flush any bytes the self-test may have left behind
|
||||||
|
+ self.flush();
|
||||||
|
+
|
||||||
|
+ // Linux i8042_controller_init() step 2: set keyboard defaults
|
||||||
|
+ // (disable scanning so keyboard doesn't send scancodes during init)
|
||||||
|
self.retry(format_args!("keyboard defaults"), 4, |x| {
|
||||||
|
- // Set defaults and disable scanning
|
||||||
|
let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?;
|
||||||
|
if b != 0xFA {
|
||||||
|
error!("keyboard failed to set defaults: {:02X}", b);
|
||||||
|
return Err(Error::CommandRetry);
|
||||||
|
}
|
||||||
|
-
|
||||||
|
Ok(b)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
- {
|
||||||
|
- // Perform the self test
|
||||||
|
- self.command(Command::TestController)?;
|
||||||
|
- let r = self.read()?;
|
||||||
|
- if r != 0x55 {
|
||||||
|
- warn!("self test unexpected value: {:02X}", r);
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
// Initialize keyboard
|
||||||
|
if let Err(err) = self.init_keyboard() {
|
||||||
|
error!("failed to initialize keyboard: {:?}", err);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
- // Enable second device
|
||||||
|
- let enable_mouse = match self.command(Command::EnableSecond) {
|
||||||
|
- Ok(()) => true,
|
||||||
|
- Err(err) => {
|
||||||
|
- error!("failed to initialize mouse: {:?}", err);
|
||||||
|
- false
|
||||||
|
+ // Linux: test AUX port (command 0xA9) before enabling.
|
||||||
|
+ // Skips mouse init entirely if the port is not electrically present.
|
||||||
|
+ let aux_ok = self.test_aux_port();
|
||||||
|
+
|
||||||
|
+ // Enable second device (mouse) only if AUX port tested OK
|
||||||
|
+ let enable_mouse = if aux_ok {
|
||||||
|
+ match self.command(Command::EnableSecond) {
|
||||||
|
+ Ok(()) => true,
|
||||||
|
+ Err(err) => {
|
||||||
|
+ warn!("failed to enable aux port after test passed: {:?}", err);
|
||||||
|
+ false
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
+ } else {
|
||||||
|
+ info!("skipping mouse init: aux port test did not pass");
|
||||||
|
+ false
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
- // Enable keyboard data reporting
|
||||||
|
- // Use inner function to prevent retries
|
||||||
|
- // Response is ignored since scanning is now on
|
||||||
|
if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) {
|
||||||
|
error!("failed to initialize keyboard reporting: {:?}", err);
|
||||||
|
- //TODO: fix by using interrupts?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs
|
||||||
|
index db17de2a..86f903bf 100644
|
||||||
|
--- a/drivers/input/ps2d/src/main.rs
|
||||||
|
+++ b/drivers/input/ps2d/src/main.rs
|
||||||
|
@@ -11,7 +11,7 @@ use std::process;
|
||||||
|
|
||||||
|
use common::acquire_port_io_rights;
|
||||||
|
use event::{user_data, EventQueue};
|
||||||
|
-use inputd::ProducerHandle;
|
||||||
|
+use inputd::InputProducer;
|
||||||
|
|
||||||
|
use crate::state::Ps2d;
|
||||||
|
|
||||||
|
@@ -31,7 +31,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
|
||||||
|
acquire_port_io_rights().expect("ps2d: failed to get I/O permission");
|
||||||
|
|
||||||
|
- let input = ProducerHandle::new().expect("ps2d: failed to open input producer");
|
||||||
|
+ let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input");
|
||||||
|
+ let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input");
|
||||||
|
|
||||||
|
user_data! {
|
||||||
|
enum Source {
|
||||||
|
@@ -93,7 +94,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||||
|
|
||||||
|
daemon.ready();
|
||||||
|
|
||||||
|
- let mut ps2d = Ps2d::new(input, time_file);
|
||||||
|
+ let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file);
|
||||||
|
|
||||||
|
let mut data = [0; 256];
|
||||||
|
for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {
|
||||||
|
diff --git a/drivers/input/ps2d/src/mouse.rs b/drivers/input/ps2d/src/mouse.rs
|
||||||
|
index 9e95ab88..23099493 100644
|
||||||
|
--- a/drivers/input/ps2d/src/mouse.rs
|
||||||
|
+++ b/drivers/input/ps2d/src/mouse.rs
|
||||||
|
@@ -1,8 +1,8 @@
|
||||||
|
use crate::controller::Ps2;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
-pub const RESET_RETRIES: usize = 10;
|
||||||
|
-pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||||
|
+pub const RESET_RETRIES: usize = 3;
|
||||||
|
+pub const RESET_TIMEOUT: Duration = Duration::from_millis(250);
|
||||||
|
pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@@ -61,6 +61,10 @@ impl MouseTx {
|
||||||
|
if data == 0xFA {
|
||||||
|
self.write_i += 1;
|
||||||
|
self.try_write(ps2)?;
|
||||||
|
+ } else if data == 0xFE {
|
||||||
|
+ // PS/2 RESEND: mouse asks us to resend the current command byte
|
||||||
|
+ log::debug!("mouse requested resend for byte {:02X}, resending", self.write.get(self.write_i).unwrap_or(&0));
|
||||||
|
+ self.try_write(ps2)?;
|
||||||
|
} else {
|
||||||
|
log::error!("unknown mouse response {:02X}", data);
|
||||||
|
return Err(());
|
||||||
|
@@ -80,8 +84,7 @@ enum MouseId {
|
||||||
|
Base = 0x00,
|
||||||
|
/// Mouse sends fourth byte with scroll
|
||||||
|
Intellimouse1 = 0x03,
|
||||||
|
- /// Mouse sends fourth byte with scroll, button 4, and button 5
|
||||||
|
- //TODO: support this mouse type
|
||||||
|
+ /// Mouse sends fourth byte with scroll and buttons 4/5
|
||||||
|
Intellimouse2 = 0x04,
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -94,25 +97,16 @@ pub enum TouchpadCommand {
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MouseState {
|
||||||
|
- /// No mouse found
|
||||||
|
None,
|
||||||
|
- /// Ready to initialize mouse
|
||||||
|
Init,
|
||||||
|
- /// Reset command is sent
|
||||||
|
Reset,
|
||||||
|
- /// BAT completion code returned
|
||||||
|
Bat,
|
||||||
|
- /// Identify touchpad
|
||||||
|
IdentifyTouchpad { tx: MouseTx },
|
||||||
|
- /// Enable intellimouse features
|
||||||
|
EnableIntellimouse { tx: MouseTx },
|
||||||
|
- /// Status request
|
||||||
|
+ EnableIntellimouse2 { tx: MouseTx },
|
||||||
|
Status { index: usize },
|
||||||
|
- /// Device ID update
|
||||||
|
DeviceId,
|
||||||
|
- /// Enable reporting command sent
|
||||||
|
EnableReporting { id: u8 },
|
||||||
|
- /// Mouse is streaming
|
||||||
|
Streaming { id: u8 },
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -194,9 +188,7 @@ impl MouseState {
|
||||||
|
let cmd = TouchpadCommand::Identify as u8;
|
||||||
|
match MouseTx::new(
|
||||||
|
&[
|
||||||
|
- // Ensure command alignment
|
||||||
|
MouseCommand::SetScaling1To1 as u8,
|
||||||
|
- // Send special identify touchpad command
|
||||||
|
MouseCommandData::SetResolution as u8,
|
||||||
|
0,
|
||||||
|
MouseCommandData::SetResolution as u8,
|
||||||
|
@@ -205,7 +197,6 @@ impl MouseState {
|
||||||
|
0,
|
||||||
|
MouseCommandData::SetResolution as u8,
|
||||||
|
0,
|
||||||
|
- // Status request
|
||||||
|
MouseCommand::StatusRequest as u8,
|
||||||
|
],
|
||||||
|
3,
|
||||||
|
@@ -215,7 +206,7 @@ impl MouseState {
|
||||||
|
*self = MouseState::IdentifyTouchpad { tx };
|
||||||
|
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||||
|
}
|
||||||
|
- Err(()) => self.enable_intellimouse(ps2),
|
||||||
|
+ Err(()) => self.enable_intellimouse2(ps2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -240,6 +231,27 @@ impl MouseState {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ fn enable_intellimouse2(&mut self, ps2: &mut Ps2) -> MouseResult {
|
||||||
|
+ match MouseTx::new(
|
||||||
|
+ &[
|
||||||
|
+ MouseCommandData::SetSampleRate as u8,
|
||||||
|
+ 200,
|
||||||
|
+ MouseCommandData::SetSampleRate as u8,
|
||||||
|
+ 200,
|
||||||
|
+ MouseCommandData::SetSampleRate as u8,
|
||||||
|
+ 80,
|
||||||
|
+ ],
|
||||||
|
+ 0,
|
||||||
|
+ ps2,
|
||||||
|
+ ) {
|
||||||
|
+ Ok(tx) => {
|
||||||
|
+ *self = MouseState::EnableIntellimouse2 { tx };
|
||||||
|
+ MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||||
|
+ }
|
||||||
|
+ Err(()) => self.enable_intellimouse(ps2),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
pub fn handle(&mut self, data: u8, ps2: &mut Ps2) -> MouseResult {
|
||||||
|
match *self {
|
||||||
|
MouseState::None | MouseState::Init => {
|
||||||
|
@@ -260,17 +272,22 @@ impl MouseState {
|
||||||
|
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||||
|
} else {
|
||||||
|
log::warn!("unknown mouse response {:02X} after reset", data);
|
||||||
|
- self.reset(ps2)
|
||||||
|
+ *self = MouseState::None;
|
||||||
|
+ MouseResult::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseState::Bat => {
|
||||||
|
if data == MouseId::Base as u8 {
|
||||||
|
- // Enable intellimouse features
|
||||||
|
+ // Base mouse - enable intellimouse features
|
||||||
|
log::debug!("BAT mouse id {:02X} (base)", data);
|
||||||
|
self.identify_touchpad(ps2)
|
||||||
|
} else if data == MouseId::Intellimouse1 as u8 {
|
||||||
|
- // Extra packet already enabled
|
||||||
|
- log::debug!("BAT mouse id {:02X} (intellimouse)", data);
|
||||||
|
+ // Scroll wheel already enabled
|
||||||
|
+ log::debug!("BAT mouse id {:02X} (intellimouse1)", data);
|
||||||
|
+ self.enable_reporting(data, ps2)
|
||||||
|
+ } else if data == MouseId::Intellimouse2 as u8 {
|
||||||
|
+ // Scroll wheel + buttons 4/5 already enabled
|
||||||
|
+ log::debug!("BAT mouse id {:02X} (intellimouse2)", data);
|
||||||
|
self.enable_reporting(data, ps2)
|
||||||
|
} else {
|
||||||
|
log::warn!("unknown mouse id {:02X} after BAT", data);
|
||||||
|
@@ -291,7 +308,17 @@ impl MouseState {
|
||||||
|
Err(()) => self.enable_intellimouse(ps2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- MouseState::EnableIntellimouse { ref mut tx } => match tx.handle(data, ps2) {
|
||||||
|
+MouseState::EnableIntellimouse { ref mut tx } => match tx.handle(data, ps2) {
|
||||||
|
+ Ok(done) => {
|
||||||
|
+ if done {
|
||||||
|
+ self.request_status(ps2)
|
||||||
|
+ } else {
|
||||||
|
+ MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ Err(()) => self.request_id(ps2),
|
||||||
|
+ },
|
||||||
|
+ MouseState::EnableIntellimouse2 { ref mut tx } => match tx.handle(data, ps2) {
|
||||||
|
Ok(done) => {
|
||||||
|
if done {
|
||||||
|
self.request_status(ps2)
|
||||||
|
@@ -299,7 +326,7 @@ impl MouseState {
|
||||||
|
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- Err(()) => self.request_status(ps2),
|
||||||
|
+ Err(()) => self.enable_intellimouse(ps2),
|
||||||
|
},
|
||||||
|
MouseState::Status { index } => {
|
||||||
|
match index {
|
||||||
|
@@ -324,7 +351,7 @@ impl MouseState {
|
||||||
|
// Command OK response
|
||||||
|
//TODO: handle this separately?
|
||||||
|
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||||
|
- } else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 {
|
||||||
|
+ } else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 || data == MouseId::Intellimouse2 as u8 {
|
||||||
|
log::debug!("mouse id {:02X}", data);
|
||||||
|
self.enable_reporting(data, ps2)
|
||||||
|
} else {
|
||||||
|
@@ -339,11 +366,15 @@ impl MouseState {
|
||||||
|
MouseResult::None
|
||||||
|
}
|
||||||
|
MouseState::Streaming { id } => {
|
||||||
|
- MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8)
|
||||||
|
+ MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8 || id == MouseId::Intellimouse2 as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ pub fn streaming_is_intellimouse2(&self) -> bool {
|
||||||
|
+ matches!(self, MouseState::Streaming { id } if *id == MouseId::Intellimouse2 as u8)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
pub fn handle_timeout(&mut self, ps2: &mut Ps2) -> MouseResult {
|
||||||
|
match *self {
|
||||||
|
MouseState::None | MouseState::Streaming { .. } => MouseResult::None,
|
||||||
|
@@ -352,12 +383,14 @@ impl MouseState {
|
||||||
|
self.reset(ps2)
|
||||||
|
}
|
||||||
|
MouseState::Reset => {
|
||||||
|
- log::warn!("timeout waiting for mouse reset");
|
||||||
|
- self.reset(ps2)
|
||||||
|
+ log::debug!("timeout waiting for mouse reset, fast-failing");
|
||||||
|
+ *self = MouseState::None;
|
||||||
|
+ MouseResult::None
|
||||||
|
}
|
||||||
|
MouseState::Bat => {
|
||||||
|
- log::warn!("timeout waiting for BAT completion");
|
||||||
|
- self.reset(ps2)
|
||||||
|
+ log::debug!("timeout waiting for BAT completion, fast-failing");
|
||||||
|
+ *self = MouseState::None;
|
||||||
|
+ MouseResult::None
|
||||||
|
}
|
||||||
|
MouseState::IdentifyTouchpad { .. } => {
|
||||||
|
//TODO: retry?
|
||||||
|
@@ -365,10 +398,13 @@ impl MouseState {
|
||||||
|
self.request_status(ps2)
|
||||||
|
}
|
||||||
|
MouseState::EnableIntellimouse { .. } => {
|
||||||
|
- //TODO: retry?
|
||||||
|
log::warn!("timeout enabling intellimouse");
|
||||||
|
self.request_status(ps2)
|
||||||
|
}
|
||||||
|
+ MouseState::EnableIntellimouse2 { .. } => {
|
||||||
|
+ log::warn!("timeout enabling intellimouse2, falling back to intellimouse");
|
||||||
|
+ self.enable_intellimouse(ps2)
|
||||||
|
+ }
|
||||||
|
MouseState::Status { index } => {
|
||||||
|
log::warn!("timeout waiting for mouse status {}", index);
|
||||||
|
self.request_id(ps2)
|
||||||
|
diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs
|
||||||
|
index 9018dc6b..8f5832f6 100644
|
||||||
|
--- a/drivers/input/ps2d/src/state.rs
|
||||||
|
+++ b/drivers/input/ps2d/src/state.rs
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-use inputd::ProducerHandle;
|
||||||
|
+use inputd::InputProducer;
|
||||||
|
use log::{error, warn};
|
||||||
|
use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent};
|
||||||
|
use std::{
|
||||||
|
@@ -44,7 +44,8 @@ pub struct Ps2d {
|
||||||
|
ps2: Ps2,
|
||||||
|
vmmouse: bool,
|
||||||
|
vmmouse_relative: bool,
|
||||||
|
- input: ProducerHandle,
|
||||||
|
+ keyboard_input: InputProducer,
|
||||||
|
+ mouse_input: InputProducer,
|
||||||
|
time_file: File,
|
||||||
|
extended: bool,
|
||||||
|
mouse_x: i32,
|
||||||
|
@@ -52,16 +53,24 @@ pub struct Ps2d {
|
||||||
|
mouse_left: bool,
|
||||||
|
mouse_middle: bool,
|
||||||
|
mouse_right: bool,
|
||||||
|
+ mouse_button_4: bool,
|
||||||
|
+ mouse_button_5: bool,
|
||||||
|
mouse_state: MouseState,
|
||||||
|
mouse_timeout: Option<TimeSpec>,
|
||||||
|
packets: [u8; 4],
|
||||||
|
packet_i: usize,
|
||||||
|
+ caps_lock: bool,
|
||||||
|
+ num_lock: bool,
|
||||||
|
+ scroll_lock: bool,
|
||||||
|
+ leds_dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ps2d {
|
||||||
|
- pub fn new(input: ProducerHandle, time_file: File) -> Self {
|
||||||
|
+ pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self {
|
||||||
|
let mut ps2 = Ps2::new();
|
||||||
|
- ps2.init().expect("failed to initialize");
|
||||||
|
+ if let Err(err) = ps2.init() {
|
||||||
|
+ log::error!("ps2d: controller init failed: {:?}", err);
|
||||||
|
+ }
|
||||||
|
|
||||||
|
// FIXME add an option for orbital to disable this when an app captures the mouse.
|
||||||
|
let vmmouse_relative = false;
|
||||||
|
@@ -77,7 +86,8 @@ impl Ps2d {
|
||||||
|
ps2,
|
||||||
|
vmmouse,
|
||||||
|
vmmouse_relative,
|
||||||
|
- input,
|
||||||
|
+ keyboard_input,
|
||||||
|
+ mouse_input,
|
||||||
|
time_file,
|
||||||
|
extended: false,
|
||||||
|
mouse_x: 0,
|
||||||
|
@@ -85,10 +95,16 @@ impl Ps2d {
|
||||||
|
mouse_left: false,
|
||||||
|
mouse_middle: false,
|
||||||
|
mouse_right: false,
|
||||||
|
+ mouse_button_4: false,
|
||||||
|
+ mouse_button_5: false,
|
||||||
|
mouse_state: MouseState::Init,
|
||||||
|
mouse_timeout: None,
|
||||||
|
packets: [0; 4],
|
||||||
|
packet_i: 0,
|
||||||
|
+ caps_lock: false,
|
||||||
|
+ num_lock: true,
|
||||||
|
+ scroll_lock: false,
|
||||||
|
+ leds_dirty: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !this.vmmouse {
|
||||||
|
@@ -96,6 +112,12 @@ impl Ps2d {
|
||||||
|
this.handle_mouse(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Flush initial LED state (Num Lock on by default)
|
||||||
|
+ if this.leds_dirty {
|
||||||
|
+ this.leds_dirty = false;
|
||||||
|
+ this.ps2.set_leds(this.caps_lock, this.num_lock, this.scroll_lock);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -272,8 +294,21 @@ impl Ps2d {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
+ if scancode != 0 && pressed {
|
||||||
|
+ match scancode {
|
||||||
|
+ orbclient::K_CAPS => { self.caps_lock = !self.caps_lock; self.leds_dirty = true; },
|
||||||
|
+ orbclient::K_NUM => { self.num_lock = !self.num_lock; self.leds_dirty = true; },
|
||||||
|
+ orbclient::K_SCROLL => { self.scroll_lock = !self.scroll_lock; self.leds_dirty = true; },
|
||||||
|
+ _ => (),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if self.leds_dirty {
|
||||||
|
+ self.leds_dirty = false;
|
||||||
|
+ self.ps2.set_leds(self.caps_lock, self.num_lock, self.scroll_lock);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if scancode != 0 {
|
||||||
|
- self.input
|
||||||
|
+ self.keyboard_input
|
||||||
|
.write_event(
|
||||||
|
KeyEvent {
|
||||||
|
character: '\0',
|
||||||
|
@@ -304,7 +339,7 @@ impl Ps2d {
|
||||||
|
|
||||||
|
if self.vmmouse_relative {
|
||||||
|
if dx != 0 || dy != 0 {
|
||||||
|
- self.input
|
||||||
|
+ self.mouse_input
|
||||||
|
.write_event(
|
||||||
|
MouseRelativeEvent {
|
||||||
|
dx: dx as i32,
|
||||||
|
@@ -320,14 +355,14 @@ impl Ps2d {
|
||||||
|
if x != self.mouse_x || y != self.mouse_y {
|
||||||
|
self.mouse_x = x;
|
||||||
|
self.mouse_y = y;
|
||||||
|
- self.input
|
||||||
|
+ self.mouse_input
|
||||||
|
.write_event(MouseEvent { x, y }.to_event())
|
||||||
|
.expect("ps2d: failed to write mouse event");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if dz != 0 {
|
||||||
|
- self.input
|
||||||
|
+ self.mouse_input
|
||||||
|
.write_event(
|
||||||
|
ScrollEvent {
|
||||||
|
x: 0,
|
||||||
|
@@ -348,7 +383,7 @@ impl Ps2d {
|
||||||
|
self.mouse_left = left;
|
||||||
|
self.mouse_middle = middle;
|
||||||
|
self.mouse_right = right;
|
||||||
|
- self.input
|
||||||
|
+ self.mouse_input
|
||||||
|
.write_event(
|
||||||
|
ButtonEvent {
|
||||||
|
left,
|
||||||
|
@@ -432,22 +467,35 @@ impl Ps2d {
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dz = 0;
|
||||||
|
+ let mut button_4 = false;
|
||||||
|
+ let mut button_5 = false;
|
||||||
|
if extra_packet {
|
||||||
|
- let mut scroll = (self.packets[3] & 0xF) as i8;
|
||||||
|
- if scroll & (1 << 3) == 1 << 3 {
|
||||||
|
- scroll -= 16;
|
||||||
|
+ let fourth = self.packets[3];
|
||||||
|
+ if self.mouse_state.streaming_is_intellimouse2() {
|
||||||
|
+ let mut scroll = (fourth & 0x0F) as i8;
|
||||||
|
+ if scroll & 0x08 != 0 {
|
||||||
|
+ scroll -= 16;
|
||||||
|
+ }
|
||||||
|
+ dz = -(scroll as i32);
|
||||||
|
+ button_4 = (fourth & 0x10) != 0;
|
||||||
|
+ button_5 = (fourth & 0x20) != 0;
|
||||||
|
+ } else {
|
||||||
|
+ let mut scroll = (fourth & 0xF) as i8;
|
||||||
|
+ if scroll & (1 << 3) == 1 << 3 {
|
||||||
|
+ scroll -= 16;
|
||||||
|
+ }
|
||||||
|
+ dz = -scroll as i32;
|
||||||
|
}
|
||||||
|
- dz = -scroll as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if dx != 0 || dy != 0 {
|
||||||
|
- self.input
|
||||||
|
+ self.mouse_input
|
||||||
|
.write_event(MouseRelativeEvent { dx, dy }.to_event())
|
||||||
|
.expect("ps2d: failed to write mouse event");
|
||||||
|
}
|
||||||
|
|
||||||
|
if dz != 0 {
|
||||||
|
- self.input
|
||||||
|
+ self.mouse_input
|
||||||
|
.write_event(ScrollEvent { x: 0, y: dz }.to_event())
|
||||||
|
.expect("ps2d: failed to write scroll event");
|
||||||
|
}
|
||||||
|
@@ -458,11 +506,15 @@ impl Ps2d {
|
||||||
|
if left != self.mouse_left
|
||||||
|
|| middle != self.mouse_middle
|
||||||
|
|| right != self.mouse_right
|
||||||
|
+ || button_4 != self.mouse_button_4
|
||||||
|
+ || button_5 != self.mouse_button_5
|
||||||
|
{
|
||||||
|
self.mouse_left = left;
|
||||||
|
self.mouse_middle = middle;
|
||||||
|
self.mouse_right = right;
|
||||||
|
- self.input
|
||||||
|
+ self.mouse_button_4 = button_4;
|
||||||
|
+ self.mouse_button_5 = button_5;
|
||||||
|
+ self.mouse_input
|
||||||
|
.write_event(
|
||||||
|
ButtonEvent {
|
||||||
|
left,
|
||||||
|
diff --git a/drivers/input/ps2d/src/vm.rs b/drivers/input/ps2d/src/vm.rs
|
||||||
|
index 71b71417..769a78e9 100644
|
||||||
|
--- a/drivers/input/ps2d/src/vm.rs
|
||||||
|
+++ b/drivers/input/ps2d/src/vm.rs
|
||||||
|
@@ -64,8 +64,8 @@ pub unsafe fn cmd(cmd: u32, arg: u32) -> (u32, u32, u32, u32) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||||
|
-pub unsafe fn cmd(cmd: u32, arg: u32) -> (u32, u32, u32, u32) {
|
||||||
|
- unimplemented!()
|
||||||
|
+pub unsafe fn cmd(_cmd: u32, _arg: u32) -> (u32, u32, u32, u32) {
|
||||||
|
+ (0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable(relative: bool) -> bool {
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
diff --git a/Cargo.toml b/Cargo.toml
|
||||||
|
index 9e776232..fdaeae69 100644
|
||||||
|
--- a/Cargo.toml
|
||||||
|
+++ b/Cargo.toml
|
||||||
|
@@ -117 +117,2 @@ precedence = "deny"
|
||||||
|
-#redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||||
|
+redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||||
|
+redox-rt = { path = "../../relibc/source/redox-rt" }
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
--- /tmp/p18-baseline-dm-main.rs 2026-05-17 00:24:36.755915716 +0300
|
||||||
|
+++ local/recipes/system/driver-manager/source/src/main.rs 2026-05-17 06:05:41.569135039 +0300
|
||||||
|
@@ -3,6 +3,7 @@
|
||||||
|
mod hotplug;
|
||||||
|
mod scheme;
|
||||||
|
|
||||||
|
+use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
@@ -19,6 +20,19 @@
|
||||||
|
use config::DriverConfig;
|
||||||
|
use scheme::{DriverManagerScheme, notify_bind};
|
||||||
|
|
||||||
|
+/// Global flag set by SIGTERM handler to request graceful shutdown.
|
||||||
|
+static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
+
|
||||||
|
+extern "C" fn sigterm_handler(_sig: i32) {
|
||||||
|
+ SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn install_sigterm_handler() {
|
||||||
|
+ unsafe {
|
||||||
|
+ libc::signal(libc::SIGTERM, sigterm_handler as usize);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
struct StderrLogger;
|
||||||
|
|
||||||
|
const BOOT_TIMELINE_PATH: &str = "/tmp/redbear-boot-timeline.json";
|
||||||
|
@@ -306,6 +320,9 @@
|
||||||
|
log::set_logger(&StderrLogger).ok();
|
||||||
|
log::set_max_level(log::LevelFilter::Info);
|
||||||
|
|
||||||
|
+ // Install SIGTERM handler for graceful shutdown
|
||||||
|
+ install_sigterm_handler();
|
||||||
|
+
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let initfs = args.iter().any(|a| a == "--initfs");
|
||||||
|
let hotplug_mode = args.iter().any(|a| a == "--hotplug");
|
||||||
|
@@ -406,6 +423,15 @@
|
||||||
|
|
||||||
|
let max_retries = 30u32;
|
||||||
|
for retry in 1..=max_retries {
|
||||||
|
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
||||||
|
+ log::info!("driver-manager: SIGTERM received during deferred retry, shutting down");
|
||||||
|
+ graceful_shutdown();
|
||||||
|
+ process::exit(0);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Check for crashed drivers during retry loop
|
||||||
|
+ reap_all_drivers(&driver_configs);
|
||||||
|
+
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
|
let retry_events = match manager.lock() {
|
||||||
|
@@ -460,6 +486,35 @@
|
||||||
|
fn idle_forever() -> ! {
|
||||||
|
log::info!("driver-manager: entering persistent idle loop");
|
||||||
|
loop {
|
||||||
|
- thread::sleep(Duration::from_secs(3600));
|
||||||
|
+ thread::sleep(Duration::from_secs(5));
|
||||||
|
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
||||||
|
+ log::info!("driver-manager: SIGTERM received, performing graceful shutdown");
|
||||||
|
+ graceful_shutdown();
|
||||||
|
+ process::exit(0);
|
||||||
|
+ }
|
||||||
|
+ // Periodically check for exited child drivers
|
||||||
|
+ reap_all_drivers(&[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+/// Poll all driver configs for exited children and log the results.
|
||||||
|
+fn reap_all_drivers(driver_configs: &[DriverConfig]) {
|
||||||
|
+ for dc in driver_configs {
|
||||||
|
+ let exited = dc.reap_exited_children();
|
||||||
|
+ for (device_key, driver_name, code) in &exited {
|
||||||
|
+ log::warn!(
|
||||||
|
+ "reaped crashed driver: {} for device {} (exit {})",
|
||||||
|
+ driver_name,
|
||||||
|
+ device_key,
|
||||||
|
+ code
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn graceful_shutdown() {
|
||||||
|
+ // The DeviceManager and spawned children are managed by DriverConfig instances
|
||||||
|
+ // which track their child processes. On shutdown, we log and exit cleanly.
|
||||||
|
+ // Child processes will be orphaned but the kernel reaps them.
|
||||||
|
+ log::info!("driver-manager: clean shutdown complete");
|
||||||
|
+}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
--- /tmp/p18-baseline-dm-main.rs 2026-05-17 00:24:36.755915716 +0300
|
||||||
|
+++ local/recipes/system/driver-manager/source/src/main.rs 2026-05-17 00:26:51.974306098 +0300
|
||||||
|
@@ -3,6 +3,7 @@
|
||||||
|
mod hotplug;
|
||||||
|
mod scheme;
|
||||||
|
|
||||||
|
+use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
@@ -19,6 +20,19 @@
|
||||||
|
use config::DriverConfig;
|
||||||
|
use scheme::{DriverManagerScheme, notify_bind};
|
||||||
|
|
||||||
|
+/// Global flag set by SIGTERM handler to request graceful shutdown.
|
||||||
|
+static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
+
|
||||||
|
+extern "C" fn sigterm_handler(_sig: i32) {
|
||||||
|
+ SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn install_sigterm_handler() {
|
||||||
|
+ unsafe {
|
||||||
|
+ libc::signal(libc::SIGTERM, sigterm_handler as usize);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
struct StderrLogger;
|
||||||
|
|
||||||
|
const BOOT_TIMELINE_PATH: &str = "/tmp/redbear-boot-timeline.json";
|
||||||
|
@@ -306,6 +320,9 @@
|
||||||
|
log::set_logger(&StderrLogger).ok();
|
||||||
|
log::set_max_level(log::LevelFilter::Info);
|
||||||
|
|
||||||
|
+ // Install SIGTERM handler for graceful shutdown
|
||||||
|
+ install_sigterm_handler();
|
||||||
|
+
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let initfs = args.iter().any(|a| a == "--initfs");
|
||||||
|
let hotplug_mode = args.iter().any(|a| a == "--hotplug");
|
||||||
|
@@ -406,6 +423,12 @@
|
||||||
|
|
||||||
|
let max_retries = 30u32;
|
||||||
|
for retry in 1..=max_retries {
|
||||||
|
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
||||||
|
+ log::info!("driver-manager: SIGTERM received during deferred retry, shutting down");
|
||||||
|
+ graceful_shutdown();
|
||||||
|
+ process::exit(0);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
|
let retry_events = match manager.lock() {
|
||||||
|
@@ -460,6 +483,18 @@
|
||||||
|
fn idle_forever() -> ! {
|
||||||
|
log::info!("driver-manager: entering persistent idle loop");
|
||||||
|
loop {
|
||||||
|
- thread::sleep(Duration::from_secs(3600));
|
||||||
|
+ thread::sleep(Duration::from_secs(1));
|
||||||
|
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
||||||
|
+ log::info!("driver-manager: SIGTERM received, performing graceful shutdown");
|
||||||
|
+ graceful_shutdown();
|
||||||
|
+ process::exit(0);
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+fn graceful_shutdown() {
|
||||||
|
+ // The DeviceManager and spawned children are managed by DriverConfig instances
|
||||||
|
+ // which track their child processes. On shutdown, we log and exit cleanly.
|
||||||
|
+ // Child processes will be orphaned but the kernel reaps them.
|
||||||
|
+ log::info!("driver-manager: clean shutdown complete");
|
||||||
|
+}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
--- a/src/scheme/irq.rs
|
||||||
|
+++ b/src/scheme/irq.rs
|
||||||
|
@@ -19,6 +19,9 @@
|
||||||
|
|
||||||
|
use super::{CallerCtx, HandleMap, OpenResult, SchemeExt, StrOrBytes};
|
||||||
|
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
|
||||||
|
+use crate::arch::device::{ioapic, local_apic::ApicId};
|
||||||
|
+
|
||||||
|
+#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
|
||||||
|
use crate::arch::interrupt::{available_irqs_iter, irq::acknowledge, is_reserved, set_reserved};
|
||||||
|
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||||
|
use crate::dtb::irqchip::{acknowledge, available_irqs_iter, is_reserved, set_reserved, IRQ_CHIP};
|
||||||
|
@@ -480,6 +483,14 @@
|
||||||
|
if !cpus.contains(&(cpu_id as u8)) {
|
||||||
|
return Err(Error::new(EINVAL));
|
||||||
|
}
|
||||||
|
+ // Reprogram the IOAPIC redirection entry for x86 targets.
|
||||||
|
+ // Non-IOAPIC IRQs (e.g. MSI) will return false -> EIO.
|
||||||
|
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
|
||||||
|
+ {
|
||||||
|
+ if !unsafe { ioapic::set_affinity(_handle_irq, ApicId::new(cpu_id)) } {
|
||||||
|
+ return Err(Error::new(EIO));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
mask.store(cpu_id as usize, Ordering::Release);
|
||||||
|
Ok(size_of::<u32>())
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
--- /dev/null
|
||||||
|
+++ b/src/sync/mcs.rs
|
||||||
|
@@ -0,0 +1,96 @@
|
||||||
|
+//! MCS (Mellor-Crummey Scott) fair spinlock.
|
||||||
|
+//!
|
||||||
|
+//! Each waiter spins on its own local `locked` flag instead of a shared lock
|
||||||
|
+//! word, eliminating cache-line bouncing under contention. FIFO ordering
|
||||||
|
+//! guarantees fairness. O(1) cache-line transfers on unlock.
|
||||||
|
+
|
||||||
|
+use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
||||||
|
+use core::{hint, ptr};
|
||||||
|
+
|
||||||
|
+use crate::percpu::PercpuBlock;
|
||||||
|
+
|
||||||
|
+/// A node in the MCS lock queue.
|
||||||
|
+pub struct McsNode {
|
||||||
|
+ pub next: AtomicPtr<McsNode>,
|
||||||
|
+ pub locked: AtomicBool,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl McsNode {
|
||||||
|
+ pub const fn new() -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ next: AtomicPtr::new(ptr::null_mut()),
|
||||||
|
+ locked: AtomicBool::new(false),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Raw MCS spinlock primitive.
|
||||||
|
+pub struct McsRawLock {
|
||||||
|
+ tail: AtomicPtr<McsNode>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl McsRawLock {
|
||||||
|
+ pub const fn new() -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ tail: AtomicPtr::new(ptr::null_mut()),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ #[inline]
|
||||||
|
+ pub fn acquire(&self, node: &McsNode) -> bool {
|
||||||
|
+ node.next.store(ptr::null_mut(), Ordering::Relaxed);
|
||||||
|
+ node.locked.store(true, Ordering::Relaxed);
|
||||||
|
+ let prev = self.tail.swap((node as *const McsNode).cast_mut(), Ordering::AcqRel);
|
||||||
|
+ if prev.is_null() {
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+ unsafe {
|
||||||
|
+ (*prev).next.store((node as *const McsNode).cast_mut(), Ordering::Release);
|
||||||
|
+ }
|
||||||
|
+ let percpu = PercpuBlock::current();
|
||||||
|
+ while node.locked.load(Ordering::Acquire) {
|
||||||
|
+ percpu.maybe_handle_tlb_shootdown();
|
||||||
|
+ hint::spin_loop();
|
||||||
|
+ }
|
||||||
|
+ true
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ #[inline]
|
||||||
|
+ pub fn release(&self, node: &McsNode) {
|
||||||
|
+ let next = node.next.load(Ordering::Acquire);
|
||||||
|
+ if next.is_null() {
|
||||||
|
+ if self
|
||||||
|
+ .tail
|
||||||
|
+ .compare_exchange(
|
||||||
|
+ (node as *const McsNode).cast_mut(),
|
||||||
|
+ ptr::null_mut(),
|
||||||
|
+ Ordering::AcqRel,
|
||||||
|
+ Ordering::Acquire,
|
||||||
|
+ )
|
||||||
|
+ .is_ok()
|
||||||
|
+ {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ while node.next.load(Ordering::Acquire).is_null() {
|
||||||
|
+ hint::spin_loop();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ unsafe {
|
||||||
|
+ (*node.next.load(Ordering::Acquire)).locked.store(false, Ordering::Release);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ #[inline]
|
||||||
|
+ pub fn try_acquire(&self, node: &McsNode) -> bool {
|
||||||
|
+ node.next.store(ptr::null_mut(), Ordering::Relaxed);
|
||||||
|
+ node.locked.store(true, Ordering::Relaxed);
|
||||||
|
+ self.tail
|
||||||
|
+ .compare_exchange(
|
||||||
|
+ ptr::null_mut(),
|
||||||
|
+ (node as *const McsNode).cast_mut(),
|
||||||
|
+ Ordering::AcqRel,
|
||||||
|
+ Ordering::Acquire,
|
||||||
|
+ )
|
||||||
|
+ .is_ok()
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
--- a/src/sync/mod.rs
|
||||||
|
+++ b/src/sync/mod.rs
|
||||||
|
@@ -1,5 +1,6 @@
|
||||||
|
pub use self::{ordered::*, wait_condition::WaitCondition, wait_queue::WaitQueue};
|
||||||
|
|
||||||
|
+pub mod mcs;
|
||||||
|
pub mod ordered;
|
||||||
|
pub mod wait_condition;
|
||||||
|
pub mod wait_queue;
|
||||||
|
--- a/src/sync/ordered.rs
|
||||||
|
+++ b/src/sync/ordered.rs
|
||||||
|
@@ -52,7 +52,9 @@
|
||||||
|
//! *g1 = 12;
|
||||||
|
//! ```
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
+use core::cell::UnsafeCell;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
+use core::ptr;
|
||||||
|
|
||||||
|
use crate::percpu::PercpuBlock;
|
||||||
|
|
||||||
|
@@ -732,3 +734,143 @@
|
||||||
|
/// This function can only be called if no lock is held by the calling thread/task
|
||||||
|
#[inline]
|
||||||
|
pub fn check_no_locks(_: LockToken<'_, L0>) {}
|
||||||
|
+
|
||||||
|
+// ---------------------------------------------------------------------------
|
||||||
|
+// MCS-based fair mutex (McsMutex)
|
||||||
|
+// ---------------------------------------------------------------------------
|
||||||
|
+
|
||||||
|
+/// A mutual exclusion lock using the MCS fair spinlock algorithm.
|
||||||
|
+///
|
||||||
|
+/// Unlike `Mutex<L, T>` which uses a simple spinlock (no fairness under
|
||||||
|
+/// contention), `McsMutex` uses Mellor-Crummey Scott queue-based spinning:
|
||||||
|
+///
|
||||||
|
+/// - Each waiter spins on its **own** local flag — no shared cache-line bouncing.
|
||||||
|
+/// - FIFO ordering prevents starvation.
|
||||||
|
+/// - O(1) cache-line transfers on unlock.
|
||||||
|
+///
|
||||||
|
+/// The MCS node is stored in [`crate::percpu::PercpuBlock::mcs_sched_node`], so
|
||||||
|
+/// this type is suitable for scheduler-internal locks where the holder is always
|
||||||
|
+/// the current CPU.
|
||||||
|
+pub struct McsMutex<L: Level, T> {
|
||||||
|
+ raw: crate::sync::mcs::McsRawLock,
|
||||||
|
+ data: UnsafeCell<T>,
|
||||||
|
+ _phantom: PhantomData<L>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+unsafe impl<L: Level, T: Send> Sync for McsMutex<L, T> {}
|
||||||
|
+unsafe impl<L: Level, T: Send> Send for McsMutex<L, T> {}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> McsMutex<L, T> {
|
||||||
|
+ pub const fn new(val: T) -> Self {
|
||||||
|
+ Self {
|
||||||
|
+ raw: crate::sync::mcs::McsRawLock::new(),
|
||||||
|
+ data: UnsafeCell::new(val),
|
||||||
|
+ _phantom: PhantomData,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> McsMutex<L, T> {
|
||||||
|
+ pub fn lock<'a, LP: Lower<L> + 'a>(
|
||||||
|
+ &'a self,
|
||||||
|
+ lock_token: LockToken<'a, LP>,
|
||||||
|
+ ) -> McsMutexGuard<'a, L, T> {
|
||||||
|
+ let percpu = PercpuBlock::current();
|
||||||
|
+ let contended = self.raw.acquire(&percpu.mcs_sched_node);
|
||||||
|
+ if contended {
|
||||||
|
+ percpu
|
||||||
|
+ .mcs_contention_count
|
||||||
|
+ .set(percpu.mcs_contention_count.get() + 1);
|
||||||
|
+ }
|
||||||
|
+ McsMutexGuard {
|
||||||
|
+ lock: self,
|
||||||
|
+ lock_token: LockToken::downgraded(lock_token),
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn try_lock<'a, LP: Lower<L> + 'a>(
|
||||||
|
+ &'a self,
|
||||||
|
+ lock_token: LockToken<'a, LP>,
|
||||||
|
+ ) -> Option<McsMutexGuard<'a, L, T>> {
|
||||||
|
+ let percpu = PercpuBlock::current();
|
||||||
|
+ if self.raw.try_acquire(&percpu.mcs_sched_node) {
|
||||||
|
+ Some(McsMutexGuard {
|
||||||
|
+ lock: self,
|
||||||
|
+ lock_token: LockToken::downgraded(lock_token),
|
||||||
|
+ })
|
||||||
|
+ } else {
|
||||||
|
+ None
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct McsMutexGuard<'a, L: Level, T: 'a> {
|
||||||
|
+ lock: &'a McsMutex<L, T>,
|
||||||
|
+ lock_token: LockToken<'a, L>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<'a, L: Level, T: 'a> McsMutexGuard<'a, L, T> {
|
||||||
|
+ pub fn token_split(&mut self) -> (&mut T, LockToken<'_, L>) {
|
||||||
|
+ unsafe { (&mut *self.lock.data.get(), self.lock_token.token()) }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn into_split(self) -> (McsRawGuard<'a, L, T>, LockToken<'a, L>) {
|
||||||
|
+ let lock_ref = self.lock;
|
||||||
|
+ let token = unsafe { core::ptr::read(&self.lock_token) };
|
||||||
|
+ core::mem::forget(self);
|
||||||
|
+ (McsRawGuard { lock: lock_ref }, token)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn from_split(raw: McsRawGuard<'a, L, T>, token: LockToken<'a, L>) -> Self {
|
||||||
|
+ let lock_ref = raw.lock;
|
||||||
|
+ core::mem::forget(raw);
|
||||||
|
+ Self {
|
||||||
|
+ lock: lock_ref,
|
||||||
|
+ lock_token: token,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> core::ops::Deref for McsMutexGuard<'_, L, T> {
|
||||||
|
+ type Target = T;
|
||||||
|
+ fn deref(&self) -> &Self::Target {
|
||||||
|
+ unsafe { &*self.lock.data.get() }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> core::ops::DerefMut for McsMutexGuard<'_, L, T> {
|
||||||
|
+ fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
+ unsafe { &mut *self.lock.data.get() }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> Drop for McsMutexGuard<'_, L, T> {
|
||||||
|
+ fn drop(&mut self) {
|
||||||
|
+ let percpu = PercpuBlock::current();
|
||||||
|
+ self.lock.raw.release(&percpu.mcs_sched_node);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct McsRawGuard<'a, L: Level, T: 'a> {
|
||||||
|
+ lock: &'a McsMutex<L, T>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> core::ops::Deref for McsRawGuard<'_, L, T> {
|
||||||
|
+ type Target = T;
|
||||||
|
+ fn deref(&self) -> &Self::Target {
|
||||||
|
+ unsafe { &*self.lock.data.get() }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> core::ops::DerefMut for McsRawGuard<'_, L, T> {
|
||||||
|
+ fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
+ unsafe { &mut *self.lock.data.get() }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<L: Level, T> Drop for McsRawGuard<'_, L, T> {
|
||||||
|
+ fn drop(&mut self) {
|
||||||
|
+ let percpu = PercpuBlock::current();
|
||||||
|
+ self.lock.raw.release(&percpu.mcs_sched_node);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
--- a/src/percpu.rs
|
||||||
|
+++ b/src/percpu.rs
|
||||||
|
@@ -17,7 +17,7 @@
|
||||||
|
cpu_set::{LogicalCpuId, MAX_CPU_COUNT},
|
||||||
|
cpu_stats::{CpuStats, CpuStatsData},
|
||||||
|
ptrace::Session,
|
||||||
|
- sync::CleanLockToken,
|
||||||
|
+ sync::{mcs::McsNode, CleanLockToken},
|
||||||
|
syscall::debug::SyscallDebugInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
@@ -35,6 +35,12 @@
|
||||||
|
pub balance: Cell<[usize; 40]>,
|
||||||
|
pub last_queue: Cell<usize>,
|
||||||
|
|
||||||
|
+ /// Per-CPU MCS node for the scheduler run-queue lock (RUN_CONTEXTS).
|
||||||
|
+ pub mcs_sched_node: McsNode,
|
||||||
|
+
|
||||||
|
+ /// Counts how many times the scheduler MCS lock acquisition was contended.
|
||||||
|
+ pub mcs_contention_count: Cell<u64>,
|
||||||
|
+
|
||||||
|
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
|
||||||
|
// first to avoid cache invalidation.
|
||||||
|
pub profiling: Option<&'static crate::profiling::RingBuffer>,
|
||||||
|
@@ -215,6 +221,8 @@
|
||||||
|
wants_tlb_shootdown: AtomicBool::new(false),
|
||||||
|
balance: Cell::new([0; 40]),
|
||||||
|
last_queue: Cell::new(39),
|
||||||
|
+ mcs_sched_node: McsNode::new(),
|
||||||
|
+ mcs_contention_count: Cell::new(0),
|
||||||
|
ptrace_flags: Cell::new(PtraceFlags::empty()),
|
||||||
|
ptrace_session: RefCell::new(None),
|
||||||
|
inside_syscall: Cell::new(false),
|
||||||
|
--- a/src/context/mod.rs
|
||||||
|
+++ b/src/context/mod.rs
|
||||||
|
@@ -14,8 +14,8 @@
|
||||||
|
memory::{RmmA, RmmArch, TableKind},
|
||||||
|
percpu::PercpuBlock,
|
||||||
|
sync::{
|
||||||
|
- ArcRwLockWriteGuard, CleanLockToken, LockToken, Mutex, MutexGuard, RwLock, RwLockReadGuard,
|
||||||
|
- RwLockWriteGuard, L0, L1, L2, L4,
|
||||||
|
+ ArcRwLockWriteGuard, CleanLockToken, LockToken, McsMutex, McsMutexGuard, Mutex,
|
||||||
|
+ MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, L0, L1, L2, L4,
|
||||||
|
},
|
||||||
|
syscall::error::Result,
|
||||||
|
};
|
||||||
|
@@ -74,10 +74,12 @@
|
||||||
|
// the context file descriptors.
|
||||||
|
static CONTEXTS: RwLock<L2, BTreeSet<ContextRef>> = RwLock::new(BTreeSet::new());
|
||||||
|
|
||||||
|
-// Actual context store for the scheduler
|
||||||
|
-static RUN_CONTEXTS: Mutex<L1, RunContextData> = Mutex::new(RunContextData::new());
|
||||||
|
+// Actual context store for the scheduler — uses MCS fair spinlock to
|
||||||
|
+// eliminate cache-line bouncing under multi-CPU contention.
|
||||||
|
+static RUN_CONTEXTS: McsMutex<L1, RunContextData> = McsMutex::new(RunContextData::new());
|
||||||
|
|
||||||
|
-// Context that has been pushed out from RUN_CONTEXTS after being idle
|
||||||
|
+// Context that has been pushed out from RUN_CONTEXTS after being idle.
|
||||||
|
+// Uses regular Mutex (lower contention; wakeup_contexts uses try_lock).
|
||||||
|
static IDLE_CONTEXTS: Mutex<L2, VecDeque<WeakContextRef>> = Mutex::new(VecDeque::new());
|
||||||
|
|
||||||
|
pub struct RunContextData {
|
||||||
|
@@ -113,7 +115,7 @@
|
||||||
|
IDLE_CONTEXTS.try_lock(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
-pub fn run_contexts(token: LockToken<'_, L0>) -> MutexGuard<'_, L1, RunContextData> {
|
||||||
|
+pub fn run_contexts(token: LockToken<'_, L0>) -> McsMutexGuard<'_, L1, RunContextData> {
|
||||||
|
RUN_CONTEXTS.lock(token)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
--- a/src/percpu.rs
|
||||||
|
+++ b/src/percpu.rs
|
||||||
|
@@ -5,9 +5,13 @@
|
||||||
|
use core::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
hint,
|
||||||
|
- sync::atomic::{AtomicBool, AtomicPtr, Ordering},
|
||||||
|
+ sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
+/// Maximum number of pages to flush individually using INVLPG before falling
|
||||||
|
+/// back to a full TLB flush (CR3 reload).
|
||||||
|
+const TLB_RANGE_THRESHOLD: u32 = 32;
|
||||||
|
+
|
||||||
|
use rmm::Arch;
|
||||||
|
use syscall::PtraceFlags;
|
||||||
|
|
||||||
|
@@ -41,6 +45,23 @@
|
||||||
|
/// Counts how many times the scheduler MCS lock acquisition was contended.
|
||||||
|
pub mcs_contention_count: Cell<u64>,
|
||||||
|
|
||||||
|
+ /// TLB shootdown range: start virtual address (page-aligned).
|
||||||
|
+ /// Set to 0 for a full flush. Only valid when `wants_tlb_shootdown` is true.
|
||||||
|
+ pub tlb_flush_start: AtomicU64,
|
||||||
|
+ /// TLB shootdown range: number of pages to invalidate.
|
||||||
|
+ pub tlb_flush_count: AtomicU32,
|
||||||
|
+
|
||||||
|
+ /// Priority inheritance donation. When another CPU is blocked waiting on a
|
||||||
|
+ /// lock this CPU holds, the blocked CPU may donate its priority here.
|
||||||
|
+ /// `u32::MAX` means no donation; otherwise it's a priority level (0-39).
|
||||||
|
+ pub pi_donated_prio: AtomicU32,
|
||||||
|
+
|
||||||
|
+ /// Cached priority of the currently-running context on this CPU.
|
||||||
|
+ /// Set by the scheduler when selecting a new context. Read by the MCS
|
||||||
|
+ /// lock during priority donation — avoids acquiring the context RwLock
|
||||||
|
+ /// from the spin loop. Default 39 (lowest priority).
|
||||||
|
+ pub current_prio: Cell<usize>,
|
||||||
|
+
|
||||||
|
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
|
||||||
|
// first to avoid cache invalidation.
|
||||||
|
pub profiling: Option<&'static crate::profiling::RingBuffer>,
|
||||||
|
@@ -64,6 +85,15 @@
|
||||||
|
ALL_PERCPU_BLOCKS[id.get() as usize].store(block, Ordering::Release)
|
||||||
|
}
|
||||||
|
|
||||||
|
+/// Get a reference to another CPU's PercpuBlock by logical CPU ID.
|
||||||
|
+pub fn get_for_cpu(id: LogicalCpuId) -> Option<&'static PercpuBlock> {
|
||||||
|
+ unsafe {
|
||||||
|
+ ALL_PERCPU_BLOCKS[id.get() as usize]
|
||||||
|
+ .load(Ordering::Acquire)
|
||||||
|
+ .as_ref()
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
pub fn get_all_stats() -> Vec<(LogicalCpuId, CpuStatsData)> {
|
||||||
|
let mut res = ALL_PERCPU_BLOCKS
|
||||||
|
.iter()
|
||||||
|
@@ -108,6 +138,9 @@
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ // Full flush — clear range info
|
||||||
|
+ percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
|
||||||
|
+ percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
|
||||||
|
|
||||||
|
crate::ipi::ipi_single(crate::ipi::IpiKind::Tlb, percpublock);
|
||||||
|
} else {
|
||||||
|
@@ -138,20 +171,114 @@
|
||||||
|
hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ // Full flush — clear range info
|
||||||
|
+ percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
|
||||||
|
+ percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
// Single broadcast IPI to all other CPUs using destination shorthand
|
||||||
|
crate::ipi::ipi(crate::ipi::IpiKind::Tlb, crate::ipi::IpiTarget::Other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+/// Range-based TLB shootdown IPI. Only invalidates the specified virtual address
|
||||||
|
+/// range using INVLPG per page for ranges up to TLB_RANGE_THRESHOLD pages.
|
||||||
|
+/// Falls back to full flush for larger ranges.
|
||||||
|
+pub fn shootdown_tlb_ipi_range(target: Option<LogicalCpuId>, start: usize, count: usize) {
|
||||||
|
+ if cfg!(not(feature = "multi_core")) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let start_aligned = start as u64 & !0xFFF;
|
||||||
|
+ let count_u32 = count as u32;
|
||||||
|
+ let use_range = count_u32 > 0 && count_u32 <= TLB_RANGE_THRESHOLD;
|
||||||
|
+
|
||||||
|
+ let set_range = |percpublock: &PercpuBlock| {
|
||||||
|
+ if use_range {
|
||||||
|
+ percpublock.tlb_flush_start.store(start_aligned, Ordering::Release);
|
||||||
|
+ percpublock.tlb_flush_count.store(count_u32, Ordering::Release);
|
||||||
|
+ } else {
|
||||||
|
+ percpublock.tlb_flush_start.store(0, Ordering::Release);
|
||||||
|
+ percpublock.tlb_flush_count.store(0, Ordering::Release);
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ if let Some(target) = target {
|
||||||
|
+ let my_percpublock = PercpuBlock::current();
|
||||||
|
+ assert_ne!(target, my_percpublock.cpu_id);
|
||||||
|
+
|
||||||
|
+ let Some(percpublock) = (unsafe {
|
||||||
|
+ ALL_PERCPU_BLOCKS[target.get() as usize]
|
||||||
|
+ .load(Ordering::Acquire)
|
||||||
|
+ .as_ref()
|
||||||
|
+ }) else {
|
||||||
|
+ return;
|
||||||
|
+ };
|
||||||
|
+ #[expect(clippy::bool_comparison)]
|
||||||
|
+ while percpublock.wants_tlb_shootdown.swap(true, Ordering::Release) == true {
|
||||||
|
+ while percpublock.wants_tlb_shootdown.load(Ordering::Relaxed) == true {
|
||||||
|
+ my_percpublock.maybe_handle_tlb_shootdown();
|
||||||
|
+ hint::spin_loop();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ set_range(percpublock);
|
||||||
|
+ crate::ipi::ipi_single(crate::ipi::IpiKind::Tlb, percpublock);
|
||||||
|
+ } else {
|
||||||
|
+ let my_percpublock = PercpuBlock::current();
|
||||||
|
+ for id in 0..crate::cpu_count() {
|
||||||
|
+ let target_id = LogicalCpuId::new(id);
|
||||||
|
+ if target_id == my_percpublock.cpu_id {
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ let Some(percpublock) = (unsafe {
|
||||||
|
+ ALL_PERCPU_BLOCKS[id as usize]
|
||||||
|
+ .load(Ordering::Acquire)
|
||||||
|
+ .as_ref()
|
||||||
|
+ }) else {
|
||||||
|
+ continue;
|
||||||
|
+ };
|
||||||
|
+ #[expect(clippy::bool_comparison)]
|
||||||
|
+ while percpublock.wants_tlb_shootdown.swap(true, Ordering::Release) == true {
|
||||||
|
+ while percpublock.wants_tlb_shootdown.load(Ordering::Relaxed) == true {
|
||||||
|
+ my_percpublock.maybe_handle_tlb_shootdown();
|
||||||
|
+ hint::spin_loop();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ set_range(percpublock);
|
||||||
|
+ }
|
||||||
|
+ crate::ipi::ipi(crate::ipi::IpiKind::Tlb, crate::ipi::IpiTarget::Other);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
impl PercpuBlock {
|
||||||
|
+ /// Return the effective scheduling priority, accounting for priority inheritance.
|
||||||
|
+ /// Lower number = higher priority (0-39 range).
|
||||||
|
+ pub fn effective_prio(&self, context_prio: usize) -> usize {
|
||||||
|
+ let donated = self.pi_donated_prio.load(Ordering::Relaxed);
|
||||||
|
+ if donated < context_prio as u32 {
|
||||||
|
+ donated as usize
|
||||||
|
+ } else {
|
||||||
|
+ context_prio
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
pub fn maybe_handle_tlb_shootdown(&self) {
|
||||||
|
#[expect(clippy::bool_comparison)]
|
||||||
|
if self.wants_tlb_shootdown.swap(false, Ordering::Relaxed) == false {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
- // TODO: Finer-grained flush
|
||||||
|
- crate::memory::RmmA::invalidate_all();
|
||||||
|
+ let start = self.tlb_flush_start.load(Ordering::Acquire);
|
||||||
|
+ let count = self.tlb_flush_count.load(Ordering::Acquire);
|
||||||
|
+
|
||||||
|
+ if start != 0 && count > 0 && count <= TLB_RANGE_THRESHOLD {
|
||||||
|
+ // Range-based flush using INVLPG per page — cheaper than full CR3 reload.
|
||||||
|
+ for i in 0..count {
|
||||||
|
+ let addr = start + (i as u64) * 4096;
|
||||||
|
+ crate::memory::RmmA::invalidate(rmm::VirtualAddress::new(addr as usize));
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ // Full TLB flush (CR3 reload) for large ranges or global shootdowns.
|
||||||
|
+ crate::memory::RmmA::invalidate_all();
|
||||||
|
+ }
|
||||||
|
|
||||||
|
if let Some(addrsp) = &*self.current_addrsp.borrow() {
|
||||||
|
addrsp.tlb_ack.fetch_add(1, Ordering::Release);
|
||||||
|
@@ -223,6 +350,10 @@
|
||||||
|
last_queue: Cell::new(39),
|
||||||
|
mcs_sched_node: McsNode::new(),
|
||||||
|
mcs_contention_count: Cell::new(0),
|
||||||
|
+ tlb_flush_start: AtomicU64::new(0),
|
||||||
|
+ tlb_flush_count: AtomicU32::new(0),
|
||||||
|
+ pi_donated_prio: AtomicU32::new(u32::MAX),
|
||||||
|
+ current_prio: Cell::new(39),
|
||||||
|
ptrace_flags: Cell::new(PtraceFlags::empty()),
|
||||||
|
ptrace_session: RefCell::new(None),
|
||||||
|
inside_syscall: Cell::new(false),
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
--- a/src/sync/mcs.rs
|
||||||
|
+++ b/src/sync/mcs.rs
|
||||||
|
@@ -4,7 +4,7 @@
|
||||||
|
//! word, eliminating cache-line bouncing under contention. FIFO ordering
|
||||||
|
//! guarantees fairness. O(1) cache-line transfers on unlock.
|
||||||
|
|
||||||
|
-use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
||||||
|
+use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};
|
||||||
|
use core::{hint, ptr};
|
||||||
|
|
||||||
|
use crate::percpu::PercpuBlock;
|
||||||
|
@@ -27,12 +27,16 @@
|
||||||
|
/// Raw MCS spinlock primitive.
|
||||||
|
pub struct McsRawLock {
|
||||||
|
tail: AtomicPtr<McsNode>,
|
||||||
|
+ /// CPU ID of the current lock holder (for priority inheritance).
|
||||||
|
+ /// `u32::MAX` means no holder.
|
||||||
|
+ holder_cpu: AtomicU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl McsRawLock {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tail: AtomicPtr::new(ptr::null_mut()),
|
||||||
|
+ holder_cpu: AtomicU32::new(u32::MAX),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -42,21 +46,37 @@
|
||||||
|
node.locked.store(true, Ordering::Relaxed);
|
||||||
|
let prev = self.tail.swap((node as *const McsNode).cast_mut(), Ordering::AcqRel);
|
||||||
|
if prev.is_null() {
|
||||||
|
+ // Uncontended — record ourselves as holder
|
||||||
|
+ let cpu_id = PercpuBlock::current().cpu_id.get();
|
||||||
|
+ self.holder_cpu.store(cpu_id, Ordering::Release);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
(*prev).next.store((node as *const McsNode).cast_mut(), Ordering::Release);
|
||||||
|
}
|
||||||
|
let percpu = PercpuBlock::current();
|
||||||
|
+ let mut donated = false;
|
||||||
|
while node.locked.load(Ordering::Acquire) {
|
||||||
|
percpu.maybe_handle_tlb_shootdown();
|
||||||
|
+ // Donate priority to the lock holder once per acquisition
|
||||||
|
+ if !donated {
|
||||||
|
+ self.maybe_donate_priority(percpu);
|
||||||
|
+ donated = true;
|
||||||
|
+ }
|
||||||
|
hint::spin_loop();
|
||||||
|
}
|
||||||
|
+ // We now hold the lock
|
||||||
|
+ self.holder_cpu.store(percpu.cpu_id.get(), Ordering::Release);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn release(&self, node: &McsNode) {
|
||||||
|
+ // Clear priority inheritance donation — we no longer hold the lock
|
||||||
|
+ PercpuBlock::current().pi_donated_prio.store(u32::MAX, Ordering::Relaxed);
|
||||||
|
+ // Clear holder CPU
|
||||||
|
+ self.holder_cpu.store(u32::MAX, Ordering::Release);
|
||||||
|
+
|
||||||
|
let next = node.next.load(Ordering::Acquire);
|
||||||
|
if next.is_null() {
|
||||||
|
if self
|
||||||
|
@@ -84,13 +104,43 @@
|
||||||
|
pub fn try_acquire(&self, node: &McsNode) -> bool {
|
||||||
|
node.next.store(ptr::null_mut(), Ordering::Relaxed);
|
||||||
|
node.locked.store(true, Ordering::Relaxed);
|
||||||
|
- self.tail
|
||||||
|
+ let ok = self
|
||||||
|
+ .tail
|
||||||
|
.compare_exchange(
|
||||||
|
ptr::null_mut(),
|
||||||
|
(node as *const McsNode).cast_mut(),
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
)
|
||||||
|
- .is_ok()
|
||||||
|
+ .is_ok();
|
||||||
|
+ if ok {
|
||||||
|
+ let cpu_id = PercpuBlock::current().cpu_id.get();
|
||||||
|
+ self.holder_cpu.store(cpu_id, Ordering::Release);
|
||||||
|
+ }
|
||||||
|
+ ok
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Donate current CPU's context priority to the lock holder's CPU.
|
||||||
|
+ /// Reads priority from PercpuBlock::current_prio (cached by the scheduler)
|
||||||
|
+ /// to avoid acquiring any lock in the MCS spin loop.
|
||||||
|
+ fn maybe_donate_priority(&self, my_percpu: &PercpuBlock) {
|
||||||
|
+ let holder_cpu_id = self.holder_cpu.load(Ordering::Relaxed);
|
||||||
|
+ if holder_cpu_id == u32::MAX {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ // Read our own priority from the per-CPU cache (set by scheduler,
|
||||||
|
+ // no lock required).
|
||||||
|
+ let my_prio = my_percpu.current_prio.get();
|
||||||
|
+ // Look up holder's PercpuBlock
|
||||||
|
+ let holder_percpu = crate::percpu::get_for_cpu(
|
||||||
|
+ crate::cpu_set::LogicalCpuId::new(holder_cpu_id),
|
||||||
|
+ );
|
||||||
|
+ if let Some(holder) = holder_percpu {
|
||||||
|
+ let current_donated = holder.pi_donated_prio.load(Ordering::Relaxed);
|
||||||
|
+ // Donate if our priority is higher (lower number)
|
||||||
|
+ if (my_prio as u32) < current_donated {
|
||||||
|
+ holder.pi_donated_prio.store(my_prio as u32, Ordering::Relaxed);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--- a/src/context/switch.rs
|
||||||
|
+++ b/src/context/switch.rs
|
||||||
|
@@ -16,6 +16,7 @@
|
||||||
|
use core::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
mem,
|
||||||
|
+ sync::atomic::Ordering,
|
||||||
|
};
|
||||||
|
use syscall::PtraceFlags;
|
||||||
|
|
||||||
|
@@ -442,6 +443,8 @@
|
||||||
|
// Is this context runnable on this CPU?
|
||||||
|
let sw = unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) };
|
||||||
|
if let UpdateResult::CanSwitch = sw {
|
||||||
|
+ // Cache the new context's priority for MCS lock priority donation.
|
||||||
|
+ percpu.current_prio.set(next_context_guard.prio);
|
||||||
|
next_context_guard_opt = Some(next_context_guard);
|
||||||
|
balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
|
||||||
|
break 'priority;
|
||||||
|
@@ -466,7 +469,10 @@
|
||||||
|
// Send the old process to the back of the line (if it is still runnable)
|
||||||
|
let prev_ctx = WeakContextRef(Arc::downgrade(&prev_context_lock));
|
||||||
|
if prev_context_guard.status.is_runnable() {
|
||||||
|
- let prio = prev_context_guard.prio;
|
||||||
|
+ let raw_prio = prev_context_guard.prio;
|
||||||
|
+ let prio = percpu.effective_prio(raw_prio);
|
||||||
|
+ // Clear PI donation — previous context is being re-queued
|
||||||
|
+ percpu.pi_donated_prio.store(u32::MAX, Ordering::Relaxed);
|
||||||
|
contexts_list[prio].push_back(prev_ctx);
|
||||||
|
} else {
|
||||||
|
idle_contexts(token.token()).push_back(prev_ctx);
|
||||||
|
@@ -478,7 +484,8 @@
|
||||||
|
return Ok(Some(next_context_guard));
|
||||||
|
} else {
|
||||||
|
if !was_idle && !Arc::ptr_eq(&prev_context_lock, &idle_context) {
|
||||||
|
- // We switch into the idle context
|
||||||
|
+ // Switching to idle context — cache lowest priority
|
||||||
|
+ percpu.current_prio.set(39);
|
||||||
|
Ok(Some(unsafe { idle_context.write_arc() }))
|
||||||
|
} else {
|
||||||
|
// We found no other process to run.
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
diff --git a/src/main.rs b/src/main.rs
|
||||||
|
--- a/src/main.rs
|
||||||
|
+++ b/src/main.rs
|
||||||
|
@@ -70,6 +70,9 @@ mod log;
|
||||||
|
/// Memory management
|
||||||
|
mod memory;
|
||||||
|
|
||||||
|
+/// NUMA topology
|
||||||
|
+mod numa;
|
||||||
|
+
|
||||||
|
/// Panic
|
||||||
|
mod panic;
|
||||||
|
|
||||||
|
diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs
|
||||||
|
--- a/src/acpi/madt/arch/x86.rs
|
||||||
|
+++ b/src/acpi/madt/arch/x86.rs
|
||||||
|
@@ -18,6 +18,29 @@
|
||||||
|
|
||||||
|
use super::{Madt, MadtEntry};
|
||||||
|
|
||||||
|
+use alloc::vec::Vec;
|
||||||
|
+
|
||||||
|
+/// Maximum number of APIC→CPU mappings we track for NUMA topology.
|
||||||
|
+const MAX_APIC_MAPPINGS: usize = 256;
|
||||||
|
+
|
||||||
|
+struct ApicMapping {
|
||||||
|
+ apic_id: u32,
|
||||||
|
+ cpu_id: LogicalCpuId,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+const UNINIT_MAPPING: ApicMapping = ApicMapping { apic_id: u32::MAX, cpu_id: LogicalCpuId::new(0) };
|
||||||
|
+
|
||||||
|
+static mut APIC_MAPPINGS: [ApicMapping; MAX_APIC_MAPPINGS] = [UNINIT_MAPPING; MAX_APIC_MAPPINGS];
|
||||||
|
+static mut APIC_MAPPING_COUNT: usize = 0;
|
||||||
|
+
|
||||||
|
+unsafe fn record_apic_mapping(apic_id: u32, cpu_id: LogicalCpuId) {
|
||||||
|
+ let count = APIC_MAPPING_COUNT;
|
||||||
|
+ if count < MAX_APIC_MAPPINGS {
|
||||||
|
+ APIC_MAPPINGS[count] = ApicMapping { apic_id, cpu_id };
|
||||||
|
+ APIC_MAPPING_COUNT = count + 1;
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
const AP_SPIN_LIMIT: u32 = 1_000_000;
|
||||||
|
const TRAMPOLINE: usize = 0x8000;
|
||||||
|
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
|
||||||
|
@@ -61,6 +82,10 @@ pub(super) fn init(madt: Madt) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg!(not(feature = "multi_core")) {
|
||||||
|
+ unsafe {
|
||||||
|
+ record_apic_mapping(me.get(), LogicalCpuId::new(0));
|
||||||
|
+ }
|
||||||
|
+ crate::numa::init_default();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -216,6 +241,17 @@ pub(super) fn init(madt: Madt) {
|
||||||
|
|
||||||
|
crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
+ // Record APIC→CPU mapping for NUMA topology.
|
||||||
|
+ unsafe {
|
||||||
|
+ record_apic_mapping(u32::from(ap_local_apic.id), cpu_id);
|
||||||
|
+ }
|
||||||
|
+ // Set NUMA node from SRAT data.
|
||||||
|
+ if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
|
||||||
|
+ if let Some(node) = crate::acpi::srat::numa_node_for_apic(u32::from(ap_local_apic.id)) {
|
||||||
|
+ percpu.numa_node.set(node);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
RmmA::invalidate_all();
|
||||||
|
}
|
||||||
|
} else if let MadtEntry::LocalX2Apic(ap_x2apic) = madt_entry {
|
||||||
|
@@ -325,6 +361,18 @@ pub(super) fn init(madt: Madt) {
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
+
|
||||||
|
+ // Record APIC→CPU mapping for NUMA topology.
|
||||||
|
+ unsafe {
|
||||||
|
+ record_apic_mapping(apic_id, cpu_id);
|
||||||
|
+ }
|
||||||
|
+ // Set NUMA node from SRAT data.
|
||||||
|
+ if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
|
||||||
|
+ if let Some(node) = crate::acpi::srat::numa_node_for_apic(apic_id) {
|
||||||
|
+ percpu.numa_node.set(node);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
RmmA::invalidate_all();
|
||||||
|
}
|
||||||
|
} else if let MadtEntry::LocalApicNmi(nmi) = madt_entry {
|
||||||
|
@@ -342,6 +390,20 @@ pub(super) fn init(madt: Madt) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Initialize NUMA topology from APIC→CPU mappings and SRAT.
|
||||||
|
+ {
|
||||||
|
+ let mappings = unsafe { &APIC_MAPPINGS[..APIC_MAPPING_COUNT] };
|
||||||
|
+ let mappings_ref: Vec<(u32, LogicalCpuId)> = mappings
|
||||||
|
+ .iter()
|
||||||
|
+ .map(|m| (m.apic_id, m.cpu_id))
|
||||||
|
+ .collect();
|
||||||
|
+ crate::numa::init_from_srat(&mappings_ref);
|
||||||
|
+ }
|
||||||
|
+ // Set BSP's NUMA node from SRAT.
|
||||||
|
+ if let Some(node) = crate::acpi::srat::numa_node_for_apic(me.get()) {
|
||||||
|
+ crate::percpu::PercpuBlock::current().numa_node.set(node);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
// Unmap trampoline
|
||||||
|
if let Some((_frame, _, flush)) = unsafe {
|
||||||
|
KernelMapper::lock_rw()
|
||||||
|
diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs
|
||||||
|
--- a/src/acpi/mod.rs
|
||||||
|
+++ b/src/acpi/mod.rs
|
||||||
|
@@ -20,6 +20,8 @@ mod rxsdt;
|
||||||
|
pub mod sdt;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
mod spcr;
|
||||||
|
+pub mod slit;
|
||||||
|
+pub mod srat;
|
||||||
|
mod xsdt;
|
||||||
|
|
||||||
|
unsafe fn map_linearly(addr: PhysicalAddress, len: usize, mapper: &mut crate::memory::PageMapper) {
|
||||||
|
@@ -163,7 +165,14 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) {
|
||||||
|
|
||||||
|
// TODO: Enumerate processors in userspace, and then provide an ACPI-independent interface
|
||||||
|
// to initialize enumerated processors to userspace?
|
||||||
|
+ // Parse SRAT BEFORE MADT so NUMA node mapping is available
|
||||||
|
+ // when APs are started and PercpuBlocks are created.
|
||||||
|
+ srat::init();
|
||||||
|
+
|
||||||
|
Madt::init();
|
||||||
|
+
|
||||||
|
+ // Parse SLIT after MADT for the NUMA distance matrix.
|
||||||
|
+ slit::init();
|
||||||
|
//TODO: support this on any arch
|
||||||
|
// SPCR must be initialized after MADT for interrupt controllers
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
diff --git a/src/acpi/slit.rs b/src/acpi/slit.rs
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/acpi/slit.rs
|
||||||
|
@@ -0,0 +1,45 @@
|
||||||
|
+//! SLIT (System Locality Information Table) parser.
|
||||||
|
+//!
|
||||||
|
+//! Parses the NUMA distance matrix for scheduler NUMA-aware work stealing.
|
||||||
|
+
|
||||||
|
+use super::sdt::Sdt;
|
||||||
|
+use crate::acpi::find_sdt;
|
||||||
|
+
|
||||||
|
+const MAX_NODES: usize = 8;
|
||||||
|
+
|
||||||
|
+static mut SLIT_MATRIX: [[u8; MAX_NODES]; MAX_NODES] = [[10u8; MAX_NODES]; MAX_NODES];
|
||||||
|
+static mut SLIT_NUM_NODES: usize = 0;
|
||||||
|
+static mut SLIT_AVAILABLE: bool = false;
|
||||||
|
+
|
||||||
|
+pub fn is_available() -> bool { unsafe { SLIT_AVAILABLE } }
|
||||||
|
+pub fn num_nodes() -> usize { unsafe { SLIT_NUM_NODES } }
|
||||||
|
+
|
||||||
|
+pub fn distance(from: u8, to: u8) -> u8 {
|
||||||
|
+ if !unsafe { SLIT_AVAILABLE } { return 10; }
|
||||||
|
+ let (from, to) = (from as usize, to as usize);
|
||||||
|
+ if from >= MAX_NODES || to >= MAX_NODES { return 10; }
|
||||||
|
+ unsafe { SLIT_MATRIX[from][to] }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn same_socket(node1: u8, node2: u8) -> bool { distance(node1, node2) <= 20 }
|
||||||
|
+
|
||||||
|
+pub fn init() {
|
||||||
|
+ let sdt = match find_sdt("SLIT").as_slice() {
|
||||||
|
+ [] => return,
|
||||||
|
+ [x] => *x,
|
||||||
|
+ xs => { println!("SLIT: {} tables found, expected 1", xs.len()); return; }
|
||||||
|
+ };
|
||||||
|
+ if &sdt.signature != b"SLIT" { return; }
|
||||||
|
+ let data_addr = sdt.data_address();
|
||||||
|
+ let data_len = sdt.data_len();
|
||||||
|
+ if data_len < 8 { return; }
|
||||||
|
+ let num_nodes = unsafe { *(data_addr as *const u64) } as usize;
|
||||||
|
+ if num_nodes == 0 || num_nodes > MAX_NODES { println!("SLIT: {num_nodes} nodes (max {MAX_NODES}), ignoring"); return; }
|
||||||
|
+ let matrix_start = 8;
|
||||||
|
+ let matrix_size = num_nodes * num_nodes;
|
||||||
|
+ if data_len < matrix_start + matrix_size { println!("SLIT: matrix truncated ({data_len} < {})", matrix_start + matrix_size); return; }
|
||||||
|
+ let matrix = unsafe { &mut SLIT_MATRIX };
|
||||||
|
+ for i in 0..num_nodes { for j in 0..num_nodes { matrix[i][j] = unsafe { *((data_addr + matrix_start + i * num_nodes + j) as *const u8) }; } }
|
||||||
|
+ unsafe { SLIT_NUM_NODES = num_nodes; SLIT_AVAILABLE = true; }
|
||||||
|
+ debug!("SLIT: {} nodes, distance matrix loaded", num_nodes);
|
||||||
|
+}
|
||||||
|
diff --git a/src/acpi/srat.rs b/src/acpi/srat.rs
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/acpi/srat.rs
|
||||||
|
@@ -0,0 +1,102 @@
|
||||||
|
+//! SRAT (System Resource Affinity Table) parser.
|
||||||
|
+//!
|
||||||
|
+//! Parses CPU-to-NUMA-node and memory-to-NUMA-node affinity information.
|
||||||
|
+//! Called before MADT init so that NUMA data is available during AP startup.
|
||||||
|
+
|
||||||
|
+use super::sdt::Sdt;
|
||||||
|
+use crate::acpi::find_sdt;
|
||||||
|
+
|
||||||
|
+const MAX_CPU_ENTRIES: usize = 256;
|
||||||
|
+const MAX_MEM_ENTRIES: usize = 64;
|
||||||
|
+
|
||||||
|
+#[derive(Clone, Copy)]
|
||||||
|
+struct SratCpuEntry { apic_id: u32, node: u8, enabled: bool }
|
||||||
|
+
|
||||||
|
+#[derive(Clone, Copy)]
|
||||||
|
+struct SratMemEntry { node: u8, base: u64, length: u64, enabled: bool }
|
||||||
|
+
|
||||||
|
+const CPU_NONE: SratCpuEntry = SratCpuEntry { apic_id: u32::MAX, node: 0, enabled: false };
|
||||||
|
+const MEM_NONE: SratMemEntry = SratMemEntry { node: 0, base: 0, length: 0, enabled: false };
|
||||||
|
+
|
||||||
|
+static mut SRAT_CPU_ENTRIES: [SratCpuEntry; MAX_CPU_ENTRIES] = [CPU_NONE; MAX_CPU_ENTRIES];
|
||||||
|
+static mut SRAT_MEM_ENTRIES: [SratMemEntry; MAX_MEM_ENTRIES] = [MEM_NONE; MAX_MEM_ENTRIES];
|
||||||
|
+static mut SRAT_CPU_COUNT: usize = 0;
|
||||||
|
+static mut SRAT_MEM_COUNT: usize = 0;
|
||||||
|
+static mut SRAT_AVAILABLE: bool = false;
|
||||||
|
+
|
||||||
|
+pub fn is_available() -> bool { unsafe { SRAT_AVAILABLE } }
|
||||||
|
+
|
||||||
|
+pub fn numa_node_for_apic(apic_id: u32) -> Option<u8> {
|
||||||
|
+ if !unsafe { SRAT_AVAILABLE } { return None; }
|
||||||
|
+ let count = unsafe { SRAT_CPU_COUNT };
|
||||||
|
+ let entries = unsafe { &SRAT_CPU_ENTRIES };
|
||||||
|
+ for i in 0..count {
|
||||||
|
+ if entries[i].apic_id == apic_id && entries[i].enabled { return Some(entries[i].node); }
|
||||||
|
+ }
|
||||||
|
+ None
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn numa_node_count() -> usize {
|
||||||
|
+ if !unsafe { SRAT_AVAILABLE } { return 1; }
|
||||||
|
+ let mut max_node: u8 = 0;
|
||||||
|
+ let count = unsafe { SRAT_CPU_COUNT };
|
||||||
|
+ let entries = unsafe { &SRAT_CPU_ENTRIES };
|
||||||
|
+ for i in 0..count { if entries[i].enabled && entries[i].node > max_node { max_node = entries[i].node; } }
|
||||||
|
+ (max_node as usize) + 1
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[repr(C, packed)]
|
||||||
|
+struct SratLocalApic { _proximity_lo: u8, apic_id: u8, flags: u32, _local_sapic_eid: u8, _proximity_hi: [u8; 3], _clock_domain: u32 }
|
||||||
|
+
|
||||||
|
+#[repr(C, packed)]
|
||||||
|
+struct SratMemoryAffinity { proximity_domain: u32, _reserved1: u16, base_address_lo: u32, base_address_hi: u32, length_lo: u32, length_hi: u32, _reserved2: u32, flags: u32, _reserved3: u64 }
|
||||||
|
+
|
||||||
|
+#[repr(C, packed)]
|
||||||
|
+struct SratLocalX2Apic { _reserved: u16, proximity_domain: u32, x2apic_id: u32, flags: u32, _clock_domain: u32, _reserved2: u32 }
|
||||||
|
+
|
||||||
|
+pub fn init() {
|
||||||
|
+ let sdt = match find_sdt("SRAT").as_slice() {
|
||||||
|
+ [] => return,
|
||||||
|
+ [x] => *x,
|
||||||
|
+ xs => { println!("SRAT: {} tables found, expected 1", xs.len()); return; }
|
||||||
|
+ };
|
||||||
|
+ if &sdt.signature != b"SRAT" { return; }
|
||||||
|
+ let data_addr = sdt.data_address();
|
||||||
|
+ let data_len = sdt.data_len();
|
||||||
|
+ if data_len < 12 { println!("SRAT: table too short ({data_len} bytes)"); return; }
|
||||||
|
+ let mut offset: usize = 12;
|
||||||
|
+ let cpu_entries = unsafe { &mut SRAT_CPU_ENTRIES };
|
||||||
|
+ let mem_entries = unsafe { &mut SRAT_MEM_ENTRIES };
|
||||||
|
+ let mut cpu_count: usize = 0;
|
||||||
|
+ let mut mem_count: usize = 0;
|
||||||
|
+ while offset + 2 <= data_len {
|
||||||
|
+ let entry_type = unsafe { *((data_addr + offset) as *const u8) };
|
||||||
|
+ let entry_len = unsafe { *((data_addr + offset + 1) as *const u8) } as usize;
|
||||||
|
+ if entry_len < 2 || offset + entry_len > data_len { break; }
|
||||||
|
+ let entry_data = data_addr + offset + 2;
|
||||||
|
+ match entry_type {
|
||||||
|
+ 0x0 if entry_len >= size_of::<SratLocalApic>() + 2 => {
|
||||||
|
+ let e = unsafe { &*(entry_data as *const SratLocalApic) };
|
||||||
|
+ let enabled = (e.flags & 1) == 1;
|
||||||
|
+ let node = (e._proximity_lo as u32) | ((e._proximity_hi[0] as u32) << 8) | ((e._proximity_hi[1] as u32) << 16) | ((e._proximity_hi[2] as u32) << 24);
|
||||||
|
+ if cpu_count < MAX_CPU_ENTRIES { cpu_entries[cpu_count] = SratCpuEntry { apic_id: e.apic_id as u32, node: node as u8, enabled }; cpu_count += 1; }
|
||||||
|
+ }
|
||||||
|
+ 0x1 if entry_len >= size_of::<SratMemoryAffinity>() + 2 => {
|
||||||
|
+ let e = unsafe { &*(entry_data as *const SratMemoryAffinity) };
|
||||||
|
+ let enabled = (e.flags & 1) == 1;
|
||||||
|
+ let base = (e.base_address_hi as u64) << 32 | e.base_address_lo as u64;
|
||||||
|
+ let length = (e.length_hi as u64) << 32 | e.length_lo as u64;
|
||||||
|
+ if mem_count < MAX_MEM_ENTRIES { mem_entries[mem_count] = SratMemEntry { node: e.proximity_domain as u8, base, length, enabled }; mem_count += 1; }
|
||||||
|
+ }
|
||||||
|
+ 0x2 if entry_len >= size_of::<SratLocalX2Apic>() + 2 => {
|
||||||
|
+ let e = unsafe { &*(entry_data as *const SratLocalX2Apic) };
|
||||||
|
+ let enabled = (e.flags & 1) == 1;
|
||||||
|
+ if cpu_count < MAX_CPU_ENTRIES { cpu_entries[cpu_count] = SratCpuEntry { apic_id: e.x2apic_id, node: e.proximity_domain as u8, enabled }; cpu_count += 1; }
|
||||||
|
+ }
|
||||||
|
+ _ => {}
|
||||||
|
+ }
|
||||||
|
+ offset += entry_len;
|
||||||
|
+ }
|
||||||
|
+ unsafe { SRAT_CPU_COUNT = cpu_count; SRAT_MEM_COUNT = mem_count; SRAT_AVAILABLE = true; }
|
||||||
|
+ debug!("SRAT: {} CPU entries, {} memory entries", cpu_count, mem_count);
|
||||||
|
+}
|
||||||
|
diff --git a/src/numa.rs b/src/numa.rs
|
||||||
|
--- a/src/numa.rs
|
||||||
|
+++ b/src/numa.rs
|
||||||
|
@@ -1,13 +1,15 @@
|
||||||
|
/// NUMA topology hints for the kernel scheduler.
|
||||||
|
-/// NUMA discovery (SRAT/SLIT parsing) is performed by a userspace daemon
|
||||||
|
-/// (numad) via /scheme/acpi/, then pushed to the kernel via scheme:numa.
|
||||||
|
-/// The kernel stores a lightweight copy for O(1) scheduling lookups.
|
||||||
|
+///
|
||||||
|
+/// NUMA discovery (SRAT/SLIT parsing) is performed during kernel ACPI init
|
||||||
|
+/// (`acpi::init()`). The kernel stores a lightweight copy for O(1) scheduling
|
||||||
|
+/// lookups. If no SRAT is found, `init_default()` creates a single-node topology.
|
||||||
|
+use crate::acpi::srat;
|
||||||
|
use crate::cpu_set::{LogicalCpuId, LogicalCpuSet};
|
||||||
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
const MAX_NUMA_NODES: usize = 8;
|
||||||
|
|
||||||
|
-#[derive(Clone, Debug)]
|
||||||
|
+#[derive(Debug)]
|
||||||
|
pub struct NumaHint {
|
||||||
|
pub node_id: u8,
|
||||||
|
pub cpus: LogicalCpuSet,
|
||||||
|
@@ -21,17 +23,12 @@
|
||||||
|
impl NumaTopology {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
const NONE: Option<NumaHint> = None;
|
||||||
|
- Self {
|
||||||
|
- nodes: [NONE; MAX_NUMA_NODES],
|
||||||
|
- initialized: AtomicBool::new(false),
|
||||||
|
- }
|
||||||
|
+ Self { nodes: [NONE; MAX_NUMA_NODES], initialized: AtomicBool::new(false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_for_cpu(&self, cpu: LogicalCpuId) -> Option<u8> {
|
||||||
|
for node in self.nodes.iter().flatten() {
|
||||||
|
- if node.cpus.contains(cpu) {
|
||||||
|
- return Some(node.node_id);
|
||||||
|
- }
|
||||||
|
+ if node.cpus.contains(cpu) { return Some(node.node_id); }
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
@@ -43,20 +40,42 @@
|
||||||
|
|
||||||
|
static mut NUMA_TOPOLOGY: NumaTopology = NumaTopology::new();
|
||||||
|
|
||||||
|
-pub fn topology() -> &'static NumaTopology {
|
||||||
|
- unsafe { &NUMA_TOPOLOGY }
|
||||||
|
+pub fn topology() -> &'static NumaTopology { unsafe { &NUMA_TOPOLOGY } }
|
||||||
|
+
|
||||||
|
+/// Initialize NUMA topology from SRAT data parsed during ACPI init.
|
||||||
|
+pub fn init_from_srat(apic_ids: &[(u32, LogicalCpuId)]) {
|
||||||
|
+ let topo = topology();
|
||||||
|
+ if topo.initialized.swap(true, Ordering::AcqRel) { return; }
|
||||||
|
+ if !srat::is_available() { init_default_inner(); return; }
|
||||||
|
+ unsafe {
|
||||||
|
+ let topo_mut = &mut *core::ptr::addr_of_mut!(NUMA_TOPOLOGY);
|
||||||
|
+ for &(apic_id, cpu_id) in apic_ids {
|
||||||
|
+ if let Some(node) = srat::numa_node_for_apic(apic_id) {
|
||||||
|
+ let idx = node as usize;
|
||||||
|
+ if idx < MAX_NUMA_NODES {
|
||||||
|
+ topo_mut.nodes[idx].get_or_insert_with(|| NumaHint { node_id: node, cpus: LogicalCpuSet::empty() }).cpus.atomic_set(cpu_id);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if topo_mut.nodes.iter().all(|n| n.is_none()) {
|
||||||
|
+ topo_mut.nodes[0] = Some(NumaHint { node_id: 0, cpus: LogicalCpuSet::all() });
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ let node_count = topology().nodes.iter().filter(|n| n.is_some()).count();
|
||||||
|
+ debug!("NUMA: {node_count} node(s) from SRAT");
|
||||||
|
}
|
||||||
|
|
||||||
|
+/// Fallback: single-node topology.
|
||||||
|
pub fn init_default() {
|
||||||
|
let topo = topology();
|
||||||
|
- if topo.initialized.swap(true, Ordering::AcqRel) {
|
||||||
|
- return;
|
||||||
|
- }
|
||||||
|
+ if topo.initialized.swap(true, Ordering::AcqRel) { return; }
|
||||||
|
+ init_default_inner();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn init_default_inner() {
|
||||||
|
unsafe {
|
||||||
|
let topo_mut = &mut *core::ptr::addr_of_mut!(NUMA_TOPOLOGY);
|
||||||
|
- topo_mut.nodes[0] = Some(NumaHint {
|
||||||
|
- node_id: 0,
|
||||||
|
- cpus: LogicalCpuSet::all(),
|
||||||
|
- });
|
||||||
|
+ topo_mut.nodes[0] = Some(NumaHint { node_id: 0, cpus: LogicalCpuSet::all() });
|
||||||
|
}
|
||||||
|
+ debug!("NUMA: single-node topology (no SRAT)");
|
||||||
|
}
|
||||||
|
diff --git a/src/percpu.rs b/src/percpu.rs
|
||||||
|
--- a/src/percpu.rs
|
||||||
|
+++ b/src/percpu.rs
|
||||||
|
@@ -62,6 +62,10 @@ pub struct PercpuBlock {
|
||||||
|
/// from the spin loop. Default 39 (lowest priority).
|
||||||
|
pub current_prio: Cell<usize>,
|
||||||
|
|
||||||
|
+ /// NUMA proximity domain for this CPU. Set during ACPI init from SRAT.
|
||||||
|
+ /// `u8::MAX` means unknown (no SRAT or APIC ID not listed).
|
||||||
|
+ pub numa_node: Cell<u8>,
|
||||||
|
+
|
||||||
|
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
|
||||||
|
// first to avoid cache invalidation.
|
||||||
|
pub profiling: Option<&'static crate::profiling::RingBuffer>,
|
||||||
|
@@ -354,6 +358,7 @@ impl PercpuBlock {
|
||||||
|
tlb_flush_count: AtomicU32::new(0),
|
||||||
|
pi_donated_prio: AtomicU32::new(u32::MAX),
|
||||||
|
current_prio: Cell::new(39),
|
||||||
|
+ numa_node: Cell::new(u8::MAX),
|
||||||
|
ptrace_flags: Cell::new(PtraceFlags::empty()),
|
||||||
|
ptrace_session: RefCell::new(None),
|
||||||
|
inside_syscall: Cell::new(false),
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
--- a/src/acpi/madt/arch/x86.rs
|
||||||
|
+++ b/src/acpi/madt/arch/x86.rs
|
||||||
|
@@ -150,15 +150,16 @@
|
||||||
|
let stack_start = RmmA::phys_to_virt(alloc.base()).data();
|
||||||
|
let stack_end = stack_start + (PAGE_SIZE << 4);
|
||||||
|
|
||||||
|
- let next_cpu = crate::CPU_COUNT.load(Ordering::Relaxed);
|
||||||
|
- if next_cpu >= crate::cpu_set::MAX_CPU_COUNT {
|
||||||
|
+ // Atomically allocate a CPU ID — fetch_add is SeqCst so that
|
||||||
|
+ // all later stores (PercpuBlock, NUMA node) are ordered after.
|
||||||
|
+ let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
|
||||||
|
+ if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
|
||||||
|
println!(
|
||||||
|
"KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
|
||||||
|
ap_local_apic.id
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
- let cpu_id = LogicalCpuId::new(next_cpu);
|
||||||
|
|
||||||
|
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
|
||||||
|
|
||||||
|
@@ -184,8 +185,10 @@
|
||||||
|
#[expect(clippy::fn_to_numeric_cast)]
|
||||||
|
ap_code.write(kstart_ap as u64);
|
||||||
|
|
||||||
|
- // TODO: Is this necessary (this fence)?
|
||||||
|
- core::arch::asm!("");
|
||||||
|
+ // Ensure all trampoline writes are visible to the AP before
|
||||||
|
+ // it starts executing. asm!("") is only a compiler barrier;
|
||||||
|
+ // fence(SeqCst) is a full hardware memory barrier.
|
||||||
|
+ core::sync::atomic::fence(Ordering::SeqCst);
|
||||||
|
};
|
||||||
|
AP_READY.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
@@ -241,8 +244,6 @@
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
- crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
-
|
||||||
|
// Record APIC→CPU mapping for NUMA topology.
|
||||||
|
unsafe {
|
||||||
|
record_apic_mapping(u32::from(ap_local_apic.id), cpu_id);
|
||||||
|
@@ -273,15 +274,16 @@
|
||||||
|
let stack_start = RmmA::phys_to_virt(alloc.base()).data();
|
||||||
|
let stack_end = stack_start + (PAGE_SIZE << 4);
|
||||||
|
|
||||||
|
- let next_cpu = crate::CPU_COUNT.load(Ordering::Relaxed);
|
||||||
|
- if next_cpu >= crate::cpu_set::MAX_CPU_COUNT {
|
||||||
|
+ // Atomically allocate a CPU ID — fetch_add is SeqCst so that
|
||||||
|
+ // all later stores (PercpuBlock, NUMA node) are ordered after.
|
||||||
|
+ let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
|
||||||
|
+ if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
|
||||||
|
println!(
|
||||||
|
"KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
|
||||||
|
apic_id
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
- let cpu_id = LogicalCpuId::new(next_cpu);
|
||||||
|
|
||||||
|
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
|
||||||
|
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
|
||||||
|
@@ -304,7 +306,8 @@
|
||||||
|
ap_page_table.write(page_table_physaddr as u64);
|
||||||
|
#[expect(clippy::fn_to_numeric_cast)]
|
||||||
|
ap_code.write(kstart_ap as u64);
|
||||||
|
- core::arch::asm!("");
|
||||||
|
+ // Ensure all trampoline writes are visible to the AP.
|
||||||
|
+ core::sync::atomic::fence(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
AP_READY.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
@@ -362,8 +365,6 @@
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
- crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||||
|
-
|
||||||
|
// Record APIC→CPU mapping for NUMA topology.
|
||||||
|
unsafe {
|
||||||
|
record_apic_mapping(apic_id, cpu_id);
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
--- a/src/percpu.rs
|
||||||
|
+++ b/src/percpu.rs
|
||||||
|
@@ -142,9 +142,10 @@
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- // Full flush — clear range info
|
||||||
|
- percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
|
||||||
|
- percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
|
||||||
|
+ // Full flush — clear range info (Release ordering ensures the flag
|
||||||
|
+ // swap and these stores are visible to the handler before the IPI).
|
||||||
|
+ percpublock.tlb_flush_start.store(0, Ordering::Release);
|
||||||
|
+ percpublock.tlb_flush_count.store(0, Ordering::Release);
|
||||||
|
|
||||||
|
crate::ipi::ipi_single(crate::ipi::IpiKind::Tlb, percpublock);
|
||||||
|
} else {
|
||||||
|
@@ -175,9 +176,9 @@
|
||||||
|
hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- // Full flush — clear range info
|
||||||
|
- percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
|
||||||
|
- percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
|
||||||
|
+ // Full flush — clear range info (Release ordering)
|
||||||
|
+ percpublock.tlb_flush_start.store(0, Ordering::Release);
|
||||||
|
+ percpublock.tlb_flush_count.store(0, Ordering::Release);
|
||||||
|
}
|
||||||
|
// Single broadcast IPI to all other CPUs using destination shorthand
|
||||||
|
crate::ipi::ipi(crate::ipi::IpiKind::Tlb, crate::ipi::IpiTarget::Other);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
--- a/src/sync/mcs.rs
|
||||||
|
+++ b/src/sync/mcs.rs
|
||||||
|
@@ -73,7 +73,7 @@
|
||||||
|
#[inline]
|
||||||
|
pub fn release(&self, node: &McsNode) {
|
||||||
|
// Clear priority inheritance donation — we no longer hold the lock
|
||||||
|
- PercpuBlock::current().pi_donated_prio.store(u32::MAX, Ordering::Relaxed);
|
||||||
|
+ PercpuBlock::current().pi_donated_prio.store(u32::MAX, Ordering::Release);
|
||||||
|
// Clear holder CPU
|
||||||
|
self.holder_cpu.store(u32::MAX, Ordering::Release);
|
||||||
|
|
||||||
|
@@ -139,7 +139,7 @@
|
||||||
|
let current_donated = holder.pi_donated_prio.load(Ordering::Relaxed);
|
||||||
|
// Donate if our priority is higher (lower number)
|
||||||
|
if (my_prio as u32) < current_donated {
|
||||||
|
- holder.pi_donated_prio.store(my_prio as u32, Ordering::Relaxed);
|
||||||
|
+ holder.pi_donated_prio.store(my_prio as u32, Ordering::Release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
--- a/src/acpi/madt/arch/x86.rs
|
||||||
|
+++ b/src/acpi/madt/arch/x86.rs
|
||||||
|
@@ -3,6 +3,8 @@
|
||||||
|
sync::atomic::{AtomicU8, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
+use x86::time::rdtsc;
|
||||||
|
+
|
||||||
|
use crate::{
|
||||||
|
arch::{
|
||||||
|
device::local_apic::the_local_apic,
|
||||||
|
@@ -18,6 +20,7 @@
|
||||||
|
|
||||||
|
use super::{Madt, MadtEntry};
|
||||||
|
|
||||||
|
+use alloc::collections::BTreeSet;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
/// Maximum number of APIC→CPU mappings we track for NUMA topology.
|
||||||
|
@@ -45,6 +48,67 @@
|
||||||
|
const TRAMPOLINE: usize = 0x8000;
|
||||||
|
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
|
||||||
|
|
||||||
|
+/// Estimate TSC frequency in MHz from CPUID.
|
||||||
|
+///
|
||||||
|
+/// Tries CPUID leaf 0x16 (Processor Frequency Information) first,
|
||||||
|
+/// then CPUID leaf 0x15 (TSC/Core Crystal Clock Ratio).
|
||||||
|
+/// Returns None if frequency cannot be determined.
|
||||||
|
+fn tsc_freq_mhz_cpuid() -> Option<u64> {
|
||||||
|
+ let max_leaf = unsafe { core::arch::x86_64::__cpuid(0).eax as u32 };
|
||||||
|
+
|
||||||
|
+ // CPUID leaf 0x16: EAX = Core Base Frequency in MHz (Intel)
|
||||||
|
+ if max_leaf >= 0x16 {
|
||||||
|
+ let mhz = unsafe { core::arch::x86_64::__cpuid(0x16) }.eax as u64;
|
||||||
|
+ if mhz > 0 {
|
||||||
|
+ return Some(mhz);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // CPUID leaf 0x15: EAX = denominator, EBX = numerator, ECX = crystal Hz
|
||||||
|
+ if max_leaf >= 0x15 {
|
||||||
|
+ let res = unsafe { core::arch::x86_64::__cpuid(0x15) };
|
||||||
|
+ let denom = res.eax as u64;
|
||||||
|
+ let numer = res.ebx as u64;
|
||||||
|
+ let crystal_hz = res.ecx as u64;
|
||||||
|
+ if denom > 0 && numer > 0 && crystal_hz > 0 {
|
||||||
|
+ // TSC freq = crystal_hz * numer / denom
|
||||||
|
+ let tsc_hz = crystal_hz * numer / denom;
|
||||||
|
+ return Some(tsc_hz / 1_000_000); // Hz → MHz
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ None
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// Early-boot microsecond delay using the Time Stamp Counter.
|
||||||
|
+///
|
||||||
|
+/// Uses CPUID-based TSC frequency estimation when available.
|
||||||
|
+/// Falls back to a conservative spin loop calibrated for the
|
||||||
|
+/// minimum expected CPU speed (1 GHz).
|
||||||
|
+///
|
||||||
|
+/// # Safety
|
||||||
|
+/// Must only be called after the BSP TSC is running (always true
|
||||||
|
+/// after CPU reset on x86).
|
||||||
|
+fn early_udelay(us: u64) {
|
||||||
|
+ if let Some(mhz) = tsc_freq_mhz_cpuid() {
|
||||||
|
+ // TSC-based delay: precise on invariant TSC (all modern x86).
|
||||||
|
+ // MHz = cycles per µs.
|
||||||
|
+ let target = unsafe { rdtsc() } + us * mhz;
|
||||||
|
+ while unsafe { rdtsc() } < target {
|
||||||
|
+ hint::spin_loop();
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ // Fallback: conservative spin loop.
|
||||||
|
+ // spin_loop() (PAUSE) is ~40 cycles on modern Intel, ~1 on AMD.
|
||||||
|
+ // At 1 GHz minimum: 1000 cycles/µs ÷ 40 cycles/iter = 25 iters/µs.
|
||||||
|
+ // Use 50 iters/µs for safety margin on slower/variable CPUs.
|
||||||
|
+ let iters = us.saturating_mul(50);
|
||||||
|
+ for _ in 0..iters {
|
||||||
|
+ hint::spin_loop();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
fn current_x2apic_processor_uid(madt: &Madt, apic_id: u32) -> Option<u32> {
|
||||||
|
madt.iter().find_map(|entry| match entry {
|
||||||
|
MadtEntry::LocalX2Apic(x2apic) if x2apic.x2apic_id == apic_id => Some(x2apic.processor_uid),
|
||||||
|
@@ -133,6 +197,31 @@
|
||||||
|
crate::profiling::allocate(preliminary_cpu_count as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Firmware bug detection: check for duplicate APIC IDs in MADT.
|
||||||
|
+ // Some firmware (especially on early BIOS/UEFI) may list the same
|
||||||
|
+ // processor multiple times. Keep first occurrence, warn on duplicates.
|
||||||
|
+ let mut seen_apic_ids: BTreeSet<u32> = BTreeSet::new();
|
||||||
|
+ {
|
||||||
|
+ let _ = seen_apic_ids.insert(me.get()); // BSP
|
||||||
|
+ for entry in madt.iter() {
|
||||||
|
+ match entry {
|
||||||
|
+ MadtEntry::LocalApic(local) if local.flags & 1 == 1 => {
|
||||||
|
+ let id = u32::from(local.id);
|
||||||
|
+ if !seen_apic_ids.insert(id) {
|
||||||
|
+ warn!("MADT: duplicate APIC ID {} in LocalApic entry, firmware bug", id);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 => {
|
||||||
|
+ let id = local.x2apic_id;
|
||||||
|
+ if !seen_apic_ids.insert(id) {
|
||||||
|
+ warn!("MADT: duplicate x2APIC ID {} in LocalX2Apic entry, firmware bug", id);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ _ => {}
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
for madt_entry in madt.iter() {
|
||||||
|
debug!(" {:x?}", madt_entry);
|
||||||
|
if let MadtEntry::LocalApic(ap_local_apic) = madt_entry {
|
||||||
|
@@ -192,9 +281,14 @@
|
||||||
|
};
|
||||||
|
AP_READY.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
- // Send INIT IPI
|
||||||
|
+ // Clear APIC Error Status Register before starting AP.
|
||||||
|
+ // Intel SDM §8.4.4: ESR should be cleared before sending SIPI.
|
||||||
|
+ unsafe { local_apic.esr(); }
|
||||||
|
+
|
||||||
|
+ // Send INIT IPI (Assert)
|
||||||
|
{
|
||||||
|
- let mut icr = 0x4500;
|
||||||
|
+ // ICR: Delivery Mode=INIT(101), Level=Assert, Trigger=Edge
|
||||||
|
+ let mut icr = 0x4500u64;
|
||||||
|
if local_apic.x2 {
|
||||||
|
icr |= u64::from(ap_local_apic.id) << 32;
|
||||||
|
} else {
|
||||||
|
@@ -203,20 +297,53 @@
|
||||||
|
local_apic.set_icr(icr);
|
||||||
|
}
|
||||||
|
|
||||||
|
- // Send START IPI
|
||||||
|
+ // Intel SDM Vol 3A §8.4.4: wait 10ms after INIT deassert
|
||||||
|
+ // before sending first SIPI. Modern CPUs may need less,
|
||||||
|
+ // but 10ms is the safe specification-compliant value.
|
||||||
|
+ early_udelay(10_000);
|
||||||
|
+
|
||||||
|
+ // Send START IPI #1
|
||||||
|
{
|
||||||
|
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||||
|
- let mut icr = 0x4600 | ap_segment as u64;
|
||||||
|
-
|
||||||
|
+ // ICR: Delivery Mode=StartUp(110), Vector=ap_segment
|
||||||
|
+ // Note: bit 14 (Level) must be 0 for SIPI per Intel SDM.
|
||||||
|
+ let mut icr = 0x0600 | ap_segment as u64;
|
||||||
|
if local_apic.x2 {
|
||||||
|
icr |= u64::from(ap_local_apic.id) << 32;
|
||||||
|
} else {
|
||||||
|
icr |= u64::from(ap_local_apic.id) << 56;
|
||||||
|
}
|
||||||
|
+ local_apic.set_icr(icr);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Intel SDM: wait 200µs between SIPIs
|
||||||
|
+ early_udelay(200);
|
||||||
|
|
||||||
|
+ // Send START IPI #2 (recommended for compatibility)
|
||||||
|
+ {
|
||||||
|
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||||
|
+ let mut icr = 0x0600 | ap_segment as u64;
|
||||||
|
+ if local_apic.x2 {
|
||||||
|
+ icr |= u64::from(ap_local_apic.id) << 32;
|
||||||
|
+ } else {
|
||||||
|
+ icr |= u64::from(ap_local_apic.id) << 56;
|
||||||
|
+ }
|
||||||
|
local_apic.set_icr(icr);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Wait briefly for SIPI to be accepted
|
||||||
|
+ early_udelay(200);
|
||||||
|
+
|
||||||
|
+ // Check ESR for delivery errors after SIPI sequence.
|
||||||
|
+ // Bit 5 = Send Accept Error, Bit 6 = Send Illegal Vector.
|
||||||
|
+ let esr_val = unsafe { local_apic.esr() };
|
||||||
|
+ if esr_val != 0 {
|
||||||
|
+ println!(
|
||||||
|
+ "KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
|
||||||
|
+ ap_local_apic.id, esr_val
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
// Wait for trampoline ready with timeout
|
||||||
|
let mut trampoline_ready = false;
|
||||||
|
for _ in 0..AP_SPIN_LIMIT {
|
||||||
|
@@ -311,34 +438,50 @@
|
||||||
|
}
|
||||||
|
AP_READY.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
+ // Clear APIC Error Status Register before starting AP.
|
||||||
|
+ unsafe { local_apic.esr(); }
|
||||||
|
+
|
||||||
|
+ // Send INIT IPI (Assert)
|
||||||
|
{
|
||||||
|
let mut icr = 0x4500u64;
|
||||||
|
icr |= u64::from(apic_id) << 32;
|
||||||
|
local_apic.set_icr(icr);
|
||||||
|
}
|
||||||
|
|
||||||
|
- for _ in 0..100_000 {
|
||||||
|
- hint::spin_loop();
|
||||||
|
- }
|
||||||
|
+ // Intel SDM Vol 3A §8.4.4: wait 10ms after INIT
|
||||||
|
+ early_udelay(10_000);
|
||||||
|
|
||||||
|
+ // Send START IPI #1
|
||||||
|
{
|
||||||
|
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||||
|
- let mut icr = 0x4600u64 | ap_segment as u64;
|
||||||
|
+ let mut icr = 0x0600u64 | ap_segment as u64;
|
||||||
|
icr |= u64::from(apic_id) << 32;
|
||||||
|
local_apic.set_icr(icr);
|
||||||
|
}
|
||||||
|
|
||||||
|
- for _ in 0..2_000_000 {
|
||||||
|
- hint::spin_loop();
|
||||||
|
- }
|
||||||
|
+ // Intel SDM: wait 200µs between SIPIs
|
||||||
|
+ early_udelay(200);
|
||||||
|
|
||||||
|
+ // Send START IPI #2 (recommended for compatibility)
|
||||||
|
{
|
||||||
|
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||||
|
- let mut icr = 0x4600u64 | ap_segment as u64;
|
||||||
|
+ let mut icr = 0x0600u64 | ap_segment as u64;
|
||||||
|
icr |= u64::from(apic_id) << 32;
|
||||||
|
local_apic.set_icr(icr);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Wait briefly for SIPI acceptance
|
||||||
|
+ early_udelay(200);
|
||||||
|
+
|
||||||
|
+ // Check ESR for delivery errors.
|
||||||
|
+ let esr_val = unsafe { local_apic.esr() };
|
||||||
|
+ if esr_val != 0 {
|
||||||
|
+ println!(
|
||||||
|
+ "KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
|
||||||
|
+ apic_id, esr_val
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
let mut trampoline_ready = false;
|
||||||
|
for _ in 0..AP_SPIN_LIMIT {
|
||||||
|
if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
|
||||||
|
@@ -407,6 +550,19 @@
|
||||||
|
crate::percpu::PercpuBlock::current().numa_node.set(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Log final CPU count vs maximum
|
||||||
|
+ let cpu_count = crate::CPU_COUNT.load(Ordering::SeqCst);
|
||||||
|
+ info!(
|
||||||
|
+ "SMP: {} CPUs online (max {})",
|
||||||
|
+ cpu_count, crate::cpu_set::MAX_CPU_COUNT
|
||||||
|
+ );
|
||||||
|
+ if cpu_count > crate::cpu_set::MAX_CPU_COUNT * 80 / 100 {
|
||||||
|
+ warn!(
|
||||||
|
+ "SMP: CPU count approaching MAX_CPU_COUNT limit ({}/{})",
|
||||||
|
+ cpu_count, crate::cpu_set::MAX_CPU_COUNT
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
// Unmap trampoline
|
||||||
|
if let Some((_frame, _, flush)) = unsafe {
|
||||||
|
KernelMapper::lock_rw()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
--- a/src/cpu_set.rs
|
||||||
|
+++ b/src/cpu_set.rs
|
||||||
|
@@ -42,17 +42,18 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
-pub const MAX_CPU_COUNT: u32 = 128;
|
||||||
|
+pub const MAX_CPU_COUNT: u32 = 256;
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
pub const MAX_CPU_COUNT: u32 = 32;
|
||||||
|
|
||||||
|
const SET_WORDS: usize = (MAX_CPU_COUNT / usize::BITS) as usize;
|
||||||
|
|
||||||
|
-// TODO: Support more than 128 CPUs.
|
||||||
|
+// TODO: Support more than 256 CPUs.
|
||||||
|
// The maximum number of CPUs on Linux is configurable, and the type for LogicalCpuSet and
|
||||||
|
// LogicalCpuId may be optimized accordingly. In that case, box the mask if it's larger than some
|
||||||
|
-// base size (probably 256 bytes).
|
||||||
|
+// base size (probably 256 bytes). AMD EPYC has 128C/256T, Threadripper PRO 96C/192T —
|
||||||
|
+// 256 covers current hardware.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LogicalCpuSet([AtomicUsize; SET_WORDS]);
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
--- a/src/acpi/sdt.rs
|
||||||
|
+++ b/src/acpi/sdt.rs
|
||||||
|
@@ -24,4 +24,20 @@
|
||||||
|
let header_size = size_of::<Sdt>();
|
||||||
|
total_size.saturating_sub(header_size)
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ /// Validate the SDT checksum.
|
||||||
|
+ ///
|
||||||
|
+ /// Per ACPI 6.5 §5.2.2: the entire table (including the checksum field)
|
||||||
|
+ /// must sum to 0 when all bytes are added together as unsigned 8-bit values.
|
||||||
|
+ pub fn validate_checksum(&self) -> bool {
|
||||||
|
+ let ptr = self as *const _ as *const u8;
|
||||||
|
+ let len = self.length as usize;
|
||||||
|
+ if len < size_of::<Sdt>() {
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+ let sum = unsafe { core::slice::from_raw_parts(ptr, len) }
|
||||||
|
+ .iter()
|
||||||
|
+ .fold(0u8, |acc, &b| acc.wrapping_add(b));
|
||||||
|
+ sum == 0
|
||||||
|
+ }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
--- a/src/acpi/madt/mod.rs
|
||||||
|
+++ b/src/acpi/madt/mod.rs
|
||||||
|
@@ -34,6 +34,12 @@
|
||||||
|
let madt = Madt::new(find_one_sdt!("APIC"));
|
||||||
|
|
||||||
|
if let Some(madt) = madt {
|
||||||
|
+ // Validate MADT checksum per ACPI 6.5 §5.2.2
|
||||||
|
+ if !madt.sdt.validate_checksum() {
|
||||||
|
+ error!("MADT checksum validation failed, skipping APIC initialization");
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
// safe because no APs have been started yet.
|
||||||
|
unsafe { MADT.get().write(Some(madt)) };
|
||||||
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
--- a/src/context/switch.rs
|
||||||
|
+++ b/src/context/switch.rs
|
||||||
|
@@ -361,6 +361,7 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is the scheduler function which currently utilises Deficit Weighted Round Robin Scheduler
|
||||||
|
+/// with NUMA-aware context selection preference.
|
||||||
|
fn select_next_context(
|
||||||
|
token: &mut CleanLockToken,
|
||||||
|
percpu: &PercpuBlock,
|
||||||
|
@@ -386,6 +387,10 @@
|
||||||
|
let total_contexts: usize = contexts_list.iter().map(|q| q.len()).sum();
|
||||||
|
let mut skipped_contexts = 0;
|
||||||
|
|
||||||
|
+ // NUMA-aware selection: remember cross-node fallback candidate.
|
||||||
|
+ let my_numa_node = percpu.numa_node.get();
|
||||||
|
+ let mut cross_node_fallback: Option<(usize, ArcContextLockWriteGuard)> = None;
|
||||||
|
+
|
||||||
|
'priority: loop {
|
||||||
|
i = (i + 1) % 40;
|
||||||
|
total_iters += 1;
|
||||||
|
@@ -450,11 +455,44 @@
|
||||||
|
// Is this context runnable on this CPU?
|
||||||
|
let sw = unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) };
|
||||||
|
if let UpdateResult::CanSwitch = sw {
|
||||||
|
- // Cache the new context's priority for MCS lock priority donation.
|
||||||
|
- percpu.current_prio.set(next_context_guard.prio);
|
||||||
|
- next_context_guard_opt = Some(next_context_guard);
|
||||||
|
- balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
|
||||||
|
- break 'priority;
|
||||||
|
+ // NUMA-aware selection: check if this context's last CPU was on the same node.
|
||||||
|
+ let same_node = if my_numa_node != u8::MAX {
|
||||||
|
+ next_context_guard.cpu_id
|
||||||
|
+ .map(|cid| {
|
||||||
|
+ crate::percpu::get_for_cpu(cid)
|
||||||
|
+ .map(|p| p.numa_node.get() == my_numa_node)
|
||||||
|
+ .unwrap_or(false)
|
||||||
|
+ })
|
||||||
|
+ .unwrap_or(true) // New context (no last CPU) — treat as same node
|
||||||
|
+ } else {
|
||||||
|
+ true // No NUMA info — treat all as same node
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ if same_node {
|
||||||
|
+ // Cache-warm: select immediately
|
||||||
|
+ percpu.current_prio.set(next_context_guard.prio);
|
||||||
|
+ next_context_guard_opt = Some(next_context_guard);
|
||||||
|
+ balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
|
||||||
|
+ break 'priority;
|
||||||
|
+ } else {
|
||||||
|
+ // Cross-node candidate: save as fallback, keep scanning for same-node
|
||||||
|
+ if cross_node_fallback.is_none() {
|
||||||
|
+ // Cache the priority and balance for later
|
||||||
|
+ cross_node_fallback =
|
||||||
|
+ Some((next_context_guard.prio, next_context_guard));
|
||||||
|
+ balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
|
||||||
|
+ // Don't break — keep looking for a same-node context
|
||||||
|
+ continue;
|
||||||
|
+ } else {
|
||||||
|
+ // Already have a cross-node fallback; push this one back
|
||||||
|
+ contexts.push_back(next_context_ref);
|
||||||
|
+ skipped_contexts += 1;
|
||||||
|
+ if skipped_contexts >= total_contexts {
|
||||||
|
+ break 'priority;
|
||||||
|
+ }
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
} else {
|
||||||
|
if matches!(sw, UpdateResult::Blocked) {
|
||||||
|
idle_contexts(token.token()).push_back(next_context_ref);
|
||||||
|
@@ -469,6 +507,15 @@
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ // If we found a cross-node fallback but no same-node context, use it
|
||||||
|
+ if next_context_guard_opt.is_none() {
|
||||||
|
+ if let Some((prio, guard)) = cross_node_fallback {
|
||||||
|
+ percpu.current_prio.set(prio);
|
||||||
|
+ next_context_guard_opt = Some(guard);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
percpu.balance.set(balance);
|
||||||
|
percpu.last_queue.set(i);
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user