Compare commits
755 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9db9c3bdc9 | |||
| 845ae99f9d | |||
| 5c5f853192 | |||
| ece9837d15 | |||
| d26675708e | |||
| 2d11c98428 | |||
| 5c127bf6f4 | |||
| a0244075e7 | |||
| 2b11b20a2f | |||
| cb50169517 | |||
| 328d1abbcd | |||
| 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`).
|
||||
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
|
||||
from any upstream repository without explicit user instruction.**
|
||||
@@ -178,10 +284,24 @@ make all
|
||||
→ mk/fstools.mk (build cookbook repo binary + fstools)
|
||||
→ mk/repo.mk (repo cook --filesystem=config/*.toml)
|
||||
→ 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)
|
||||
→ 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
|
||||
|
||||
- **Rust edition 2024**, nightly channel
|
||||
@@ -337,6 +457,49 @@ See `local/docs/BUILD-SYSTEM-HARDENING-PLAN.md` for the full plan.
|
||||
- **DO NOT** skip warnings — investigate, diagnose, and fix the root cause; suppressing or ignoring warnings is not acceptable when a fix is feasible
|
||||
- **DO NOT** remove patches from `recipe.toml` to fix build failures — rebase the patch instead (see `local/docs/PATCH-GOVERNANCE.md`)
|
||||
- **DO NOT** remove BINS entries to fix build failures — fix the source or use EXISTING_BINS filtering
|
||||
- **DO NOT** use the VESA display driver (`vesad`) as the primary display surface after GPU detection. vesad is only for early-boot framebuffer handoff — after redox-drm loads, the display path is `/scheme/drm/card0`. See **NO VESA POLICY** below.
|
||||
|
||||
## NO VESA POLICY
|
||||
|
||||
Red Bear OS does not use the VESA display driver as the primary display surface. All display
|
||||
output goes through the DRM/KMS path via real GPU drivers:
|
||||
|
||||
| Environment | GPU Driver | 3D Support |
|
||||
|---|---|---|
|
||||
| QEMU | virtio-gpu (via redox-drm) | ✅ virgl |
|
||||
| Intel hardware | Intel i915-like (via redox-drm) | ✅ Mesa i965/iris |
|
||||
| AMD hardware | amdgpu (via redox-drm + linux-kpi) | ✅ Mesa radeonsi |
|
||||
| Future | nouveau reimplementation (Rust, via redox-drm) | ✅ Mesa nouveau |
|
||||
|
||||
**vesad is allowed ONLY as an early-boot framebuffer handoff.** The bootloader sets up a linear
|
||||
framebuffer before the kernel starts. vesad takes over this framebuffer so the initfs has console
|
||||
output (fbcond, fbbootlogd) before real GPU drivers are available. Once redox-drm initializes and
|
||||
registers `scheme:drm/card0`, vesad must hand off and NOT register `scheme:display.vesa` as the
|
||||
primary display surface.
|
||||
|
||||
The display path for redbear-full:
|
||||
|
||||
```
|
||||
Bootloader linear framebuffer
|
||||
→ vesad (initfs, service 20): temporary FB handoff for text console
|
||||
→ redox-drm (initfs, service 30): detects GPU hardware, takes over via DRM/KMS
|
||||
→ redox-drm (rootfs, service 14): full DRM driver with 3D (Mesa)
|
||||
→ KWin compositor: DRM/KMS master, composites desktop via /scheme/drm/card0
|
||||
```
|
||||
|
||||
For redbear-mini: vesad handles the bootloader framebuffer for the text-only console. No GPU
|
||||
driver loads — mini is text-only by design.
|
||||
|
||||
**After GPU detection, any code that opens `/scheme/display.vesa/` is incorrect.** The correct
|
||||
display path is `/scheme/drm/card0` via the DRM scheme.
|
||||
|
||||
Rationale: VESA is a legacy BIOS-era standard with no hardware acceleration, no mode setting
|
||||
beyond what the bootloader provides, no 3D, and no future. Red Bear OS targets real GPU
|
||||
hardware with full DRM/KMS and Mesa support. vesad serves only as a bridge between bootloader
|
||||
FB and the real GPU driver — it is never the final display path.
|
||||
|
||||
This policy also covers future GPU driver work: any new GPU support (nouveau Rust reimplementation,
|
||||
ARM Mali, etc.) must go through the redox-drm + DRM/KMS path, never through VESA fallback.
|
||||
|
||||
## ZERO TOLERANCE FOR STUBS
|
||||
|
||||
@@ -444,6 +607,65 @@ or any path that is already git-tracked and not inside a fetched source tree.
|
||||
|
||||
## 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
|
||||
|
||||
The cookbook tool (`src/cook/fetch.rs`) applies patches **atomically**:
|
||||
@@ -466,12 +688,78 @@ Patches may use either format:
|
||||
|
||||
Git-specific headers (`diff --git`, `diff -ruN`, `index`, `new file mode`, `rename from/to`,
|
||||
`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.
|
||||
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.
|
||||
|
||||
### 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
|
||||
|
||||
Core recipes (`base`, `kernel`, `relibc`, `bootloader`, etc.) and any recipe carrying
|
||||
|
||||
@@ -231,3 +231,5 @@ packages-sync: ; @bash local/scripts/sync-packages.sh
|
||||
packages-list: ; @ls -la Packages/*.pkgar 2>/dev/null | wc -l && echo "pkgar files in Packages/"
|
||||
validate-patches:
|
||||
@bash local/scripts/validate-patches.sh
|
||||
cascade.%: FORCE
|
||||
@bash local/scripts/rebuild-cascade.sh $(basename $(subst cascade,, $*))
|
||||
|
||||
Executable
BIN
Binary file not shown.
+1
-1
@@ -18,7 +18,7 @@ path = "/usr/lib/init.d/10_acid.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Acid test runner"
|
||||
requires_weak = ["00_pcid-spawner.service"]
|
||||
requires_weak = ["00_driver-manager.service"]
|
||||
|
||||
[service]
|
||||
cmd = "ion"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# Protected recipes — these recipes are NEVER re-fetched from upstream.
|
||||
# They use offline/archived sources from sources/redbear-<release>/.
|
||||
#
|
||||
# Protection reasons:
|
||||
# - patched: carries Red Bear patches (upstream changes could break patches)
|
||||
# - custom: Red Bear-specific recipe (no upstream equivalent)
|
||||
# - core: core system component (kernel, libc, bootloader, etc.)
|
||||
#
|
||||
# The Rust code in src/cook/fetch.rs reads this file at startup.
|
||||
# Recipes NOT listed here but carrying patches (patches = [...] in recipe.toml)
|
||||
# are automatically protected by the recipe_has_patches() check.
|
||||
|
||||
# Core patched recipes (upstream + Red Bear patches)
|
||||
[patched]
|
||||
recipes = [
|
||||
"relibc", "bootloader", "kernel", "base", "base-initfs",
|
||||
"installer", "redoxfs", "grub",
|
||||
]
|
||||
|
||||
# Red Bear custom core recipes
|
||||
[custom]
|
||||
recipes = [
|
||||
"ext4d", "fatd",
|
||||
]
|
||||
|
||||
# Red Bear driver infrastructure
|
||||
[drivers]
|
||||
recipes = [
|
||||
"redox-driver-sys", "linux-kpi", "firmware-loader",
|
||||
"redbear-btusb", "redbear-iwlwifi",
|
||||
"redox-drm", "amdgpu",
|
||||
]
|
||||
|
||||
# Red Bear system tools
|
||||
[system]
|
||||
recipes = [
|
||||
"cub", "evdevd", "udev-shim", "iommu",
|
||||
"redbear-firmware", "redbear-hwutils", "redbear-info", "rbos-info",
|
||||
"redbear-meta", "redbear-netctl", "redbear-netctl-console",
|
||||
"redbear-netstat", "redbear-btctl", "redbear-wifictl",
|
||||
"redbear-traceroute", "redbear-mtr", "redbear-nmap",
|
||||
"redbear-sessiond", "redbear-authd", "redbear-session-launch",
|
||||
"redbear-greeter", "redbear-dbus-services", "redbear-notifications",
|
||||
"redbear-upower", "redbear-udisks", "redbear-polkit", "redbear-quirks",
|
||||
"redbear-release", "redbear-keymapd", "redbear-ime", "redbear-accessibility",
|
||||
]
|
||||
|
||||
# Qt stack with Red Bear patches
|
||||
[qt]
|
||||
recipes = [
|
||||
"qtbase", "qtwayland", "qtdeclarative", "qtbase-compat",
|
||||
]
|
||||
|
||||
# Graphics / display stack with Red Bear patches
|
||||
[graphics]
|
||||
recipes = [
|
||||
"libdrm", "mesa",
|
||||
"libwayland", "libevdev", "libinput",
|
||||
"dbus", "glib",
|
||||
]
|
||||
|
||||
# Red Bear library stubs and custom libs
|
||||
[libs]
|
||||
recipes = [
|
||||
"libepoxy-stub", "libdisplay-info-stub", "lcms2-stub",
|
||||
"libxcvt-stub", "libudev-stub", "zbus", "libqrencode",
|
||||
]
|
||||
|
||||
# Red Bear Wayland
|
||||
[wayland]
|
||||
recipes = [
|
||||
"qt6-wayland-smoke", "smallvil", "seatd-redox",
|
||||
]
|
||||
|
||||
# Red Bear KDE (47 recipes)
|
||||
[kde]
|
||||
recipes = [
|
||||
"kf6-extra-cmake-modules", "kf6-kcoreaddons", "kf6-kwidgetsaddons",
|
||||
"kf6-kconfig", "kf6-ki18n", "kf6-kcodecs", "kf6-kguiaddons",
|
||||
"kf6-kcolorscheme", "kf6-kauth", "kf6-kitemmodels", "kf6-kitemviews",
|
||||
"kf6-karchive", "kf6-kwindowsystem", "kf6-knotifications",
|
||||
"kf6-kjobwidgets", "kf6-kconfigwidgets", "kf6-kcrash", "kf6-kdbusaddons",
|
||||
"kf6-kglobalaccel", "kf6-kservice", "kf6-kpackage", "kf6-kiconthemes",
|
||||
"kf6-kxmlgui", "kf6-ktextwidgets", "kf6-solid", "kf6-sonnet",
|
||||
"kf6-kio", "kf6-kbookmarks", "kf6-kcompletion", "kf6-kdeclarative",
|
||||
"kf6-kcmutils", "kf6-kidletime", "kf6-kwayland", "kf6-knewstuff",
|
||||
"kf6-kwallet", "kf6-prison", "kf6-kirigami",
|
||||
"kf6-ksvg", "kf6-pty", "kf6-notifyconfig", "kf6-parts",
|
||||
"kdecoration", "kwin", "plasma-desktop", "plasma-workspace",
|
||||
"plasma-framework", "plasma-wayland-protocols", "kirigami",
|
||||
"kglobalacceld",
|
||||
]
|
||||
|
||||
# Orbutils (has local patch)
|
||||
[other]
|
||||
recipes = [
|
||||
"orbutils",
|
||||
]
|
||||
@@ -7,7 +7,7 @@
|
||||
# The current slice is explicit-startup, USB-attached, BLE-first, and intentionally not wired to
|
||||
# USB-class autospawn yet.
|
||||
|
||||
include = ["redbear-minimal.toml", "redbear-bluetooth-services.toml"]
|
||||
include = ["redbear-mini.toml", "redbear-bluetooth-services.toml"]
|
||||
|
||||
[general]
|
||||
filesystem_size = 2048
|
||||
|
||||
@@ -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
|
||||
#
|
||||
# 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]
|
||||
redbear-quirks = {}
|
||||
@@ -32,9 +37,9 @@ data = """
|
||||
path = "/etc/init.d/12_boot-late.target"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Late boot services target"
|
||||
description = "Late boot services target (compat alias for 04_drivers.target)"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"04_drivers.target",
|
||||
]
|
||||
"""
|
||||
|
||||
@@ -54,6 +59,7 @@ priority = 100
|
||||
command = ["/usr/lib/drivers/nvmed"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 8
|
||||
|
||||
@@ -64,6 +70,7 @@ priority = 100
|
||||
command = ["/usr/lib/drivers/ahcid"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 6
|
||||
|
||||
@@ -74,6 +81,7 @@ priority = 100
|
||||
command = ["/usr/lib/drivers/ided"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 1
|
||||
|
||||
@@ -84,6 +92,7 @@ priority = 100
|
||||
command = ["/usr/lib/drivers/virtio-blkd"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1AF4
|
||||
device = 0x1001
|
||||
class = 1
|
||||
@@ -100,6 +109,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/e1000d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 2
|
||||
|
||||
@@ -110,6 +120,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/rtl8168d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x10EC
|
||||
class = 2
|
||||
|
||||
@@ -120,6 +131,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/rtl8139d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x10EC
|
||||
device = 0x8139
|
||||
|
||||
@@ -130,6 +142,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/ixgbed"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 2
|
||||
subclass = 0
|
||||
@@ -141,6 +154,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/virtio-netd"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1AF4
|
||||
class = 2
|
||||
"""
|
||||
@@ -155,6 +169,7 @@ priority = 80
|
||||
command = ["/usr/lib/drivers/xhcid"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x0C
|
||||
subclass = 0x03
|
||||
prog_if = 0x30
|
||||
@@ -169,6 +184,7 @@ command = ["/usr/lib/drivers/ehcid"]
|
||||
# control-transfer pass-through while the wider USB stack continues converging.
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x0C
|
||||
subclass = 0x03
|
||||
prog_if = 0x20
|
||||
@@ -180,6 +196,7 @@ priority = 80
|
||||
command = ["/usr/lib/drivers/ohcid"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x0C
|
||||
subclass = 0x03
|
||||
prog_if = 0x10
|
||||
@@ -191,6 +208,7 @@ priority = 80
|
||||
command = ["/usr/lib/drivers/uhcid"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x0C
|
||||
subclass = 0x03
|
||||
prog_if = 0x00
|
||||
@@ -206,6 +224,7 @@ priority = 60
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x03
|
||||
"""
|
||||
|
||||
@@ -233,6 +252,7 @@ priority = 40
|
||||
command = ["/usr/lib/drivers/ihdad"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 0x04
|
||||
|
||||
@@ -243,10 +263,89 @@ priority = 40
|
||||
command = ["/usr/lib/drivers/ac97d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x04
|
||||
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]]
|
||||
path = "/lib/drivers.d/70-usb-class.toml"
|
||||
data = """
|
||||
@@ -281,15 +380,15 @@ vendor = 0xFFFF
|
||||
device = 0xFFFF
|
||||
"""
|
||||
|
||||
# Profiles that include this fragment should start `driver-manager` instead of
|
||||
# `pcid-spawner`; the manager performs the PCI bind/channel handoff itself.
|
||||
# driver-manager owns PCI device enumeration, driver matching, and bind/channel
|
||||
# handoff — replacing the old pcid + pcid-spawner pair entirely.
|
||||
[[files]]
|
||||
path = "/etc/init.d/00_driver-manager.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Red Bear driver manager"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"02_early_hw.target",
|
||||
]
|
||||
|
||||
[service]
|
||||
@@ -298,33 +397,26 @@ args = ["--hotplug"]
|
||||
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]]
|
||||
path = "/etc/init.d/10_evdevd.service"
|
||||
path = "/etc/init.d/30_thermald.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Evdev input daemon"
|
||||
requires_weak = [
|
||||
"12_boot-late.target",
|
||||
"00_driver-manager.service",
|
||||
]
|
||||
description = "Thermal management daemon (suppressed; use 15_thermald.service)"
|
||||
|
||||
[service]
|
||||
cmd = "evdevd"
|
||||
type = "oneshot_async"
|
||||
cmd = "echo"
|
||||
args = ["thermald: started earlier as 15_thermald.service"]
|
||||
type = "oneshot"
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/firmware-fallbacks.d"
|
||||
data = ""
|
||||
directory = true
|
||||
mode = 0o755
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/15_cpufreqd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "CPU frequency scaling daemon"
|
||||
requires_weak = ["12_boot-late.target"]
|
||||
requires_weak = ["04_drivers.target"]
|
||||
|
||||
[service]
|
||||
cmd = "/usr/bin/cpufreqd"
|
||||
@@ -336,13 +428,25 @@ path = "/etc/init.d/15_thermald.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Thermal management daemon"
|
||||
requires_weak = ["12_boot-late.target"]
|
||||
requires_weak = ["04_drivers.target"]
|
||||
|
||||
[service]
|
||||
cmd = "/usr/bin/thermald"
|
||||
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 = "oneshot_async"
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/15_hwrngd.service"
|
||||
data = """
|
||||
@@ -372,7 +476,7 @@ path = "/etc/init.d/16_redbear-acmd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "USB CDC ACM serial daemon"
|
||||
requires_weak = ["12_boot-late.target"]
|
||||
requires_weak = ["04_drivers.target"]
|
||||
|
||||
[service]
|
||||
cmd = "/usr/bin/redbear-acmd"
|
||||
@@ -384,7 +488,7 @@ path = "/etc/init.d/16_redbear-ecmd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "USB CDC ECM/NCM ethernet daemon"
|
||||
requires_weak = ["12_boot-late.target"]
|
||||
requires_weak = ["04_drivers.target"]
|
||||
|
||||
[service]
|
||||
cmd = "/usr/bin/redbear-ecmd"
|
||||
@@ -396,7 +500,7 @@ path = "/etc/init.d/16_redbear-usbaudiod.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "USB Audio Class daemon"
|
||||
requires_weak = ["12_boot-late.target"]
|
||||
requires_weak = ["04_drivers.target"]
|
||||
|
||||
[service]
|
||||
cmd = "/usr/bin/redbear-usbaudiod"
|
||||
|
||||
+103
-22
@@ -6,18 +6,25 @@
|
||||
#
|
||||
# Extends redbear-mini with the full desktop/graphics stack:
|
||||
# Wayland, Qt6, KF6, KWin, Mesa, DRM drivers, firmware, greeter.
|
||||
#
|
||||
# GPU/display policy: DRM/KMS ONLY. No VESA. Real GPU drivers:
|
||||
# QEMU → virtio-gpu via redox-drm (virgl 3D)
|
||||
# Intel → i915-like via redox-drm (Mesa i965/iris)
|
||||
# AMD → amdgpu via redox-drm + linux-kpi (Mesa radeonsi)
|
||||
# Display path: bootloader FB → redox-drm → DRM/KMS → KWin compositor
|
||||
# Consult local/reference/linux-7.0/ for driver behavior reference.
|
||||
|
||||
include = ["redbear-mini.toml"]
|
||||
|
||||
[general]
|
||||
filesystem_size = 4096
|
||||
filesystem_size = 2048
|
||||
|
||||
[users.messagebus]
|
||||
uid = 100
|
||||
gid = 100
|
||||
name = "messagebus"
|
||||
home = "/nonexistent"
|
||||
shell = "/usr/bin/zsh"
|
||||
shell = "/usr/bin/false"
|
||||
|
||||
[users.root]
|
||||
password = "password"
|
||||
@@ -25,6 +32,14 @@ uid = 0
|
||||
gid = 0
|
||||
shell = "/usr/bin/zsh"
|
||||
|
||||
[users.user]
|
||||
password = ""
|
||||
uid = 1000
|
||||
gid = 1000
|
||||
name = "user"
|
||||
home = "/home/user"
|
||||
shell = "/usr/bin/zsh"
|
||||
|
||||
[packages]
|
||||
# Runtime driver parameter control surface.
|
||||
driver-params = {}
|
||||
@@ -57,7 +72,7 @@ fontconfig = {}
|
||||
libwayland = {}
|
||||
wayland-protocols = {}
|
||||
plasma-wayland-protocols = {}
|
||||
redbear-compositor = "ignore" # replaced by kwin
|
||||
redbear-compositor = {}
|
||||
|
||||
# Keyboard/input
|
||||
libxkbcommon = {}
|
||||
@@ -138,6 +153,7 @@ redbear-authd = {}
|
||||
redbear-session-launch = {}
|
||||
seatd = {}
|
||||
redbear-greeter = {}
|
||||
sddm = {}
|
||||
amdgpu = {}
|
||||
|
||||
# Core Red Bear umbrella package
|
||||
@@ -146,12 +162,10 @@ redbear-meta = {}
|
||||
# Phase 1 runtime validation tests (POSIX: signalfd, timerfd, eventfd, shm_open, sem_open, waitid)
|
||||
relibc-phase1-tests = {}
|
||||
|
||||
# Native build toolchain (Phase 3: GCC + binutils running on redox)
|
||||
# Produces gcc/g++/as/ld that execute inside Red Bear OS
|
||||
gcc-native = {}
|
||||
binutils-native = {}
|
||||
# llvm-native = {} # suppressed: Redox C++/pthread header gaps; not needed for greeter proof
|
||||
# rust-native = {} # suppressed: depends on llvm-native; not needed for greeter proof
|
||||
# Native build toolchain — excluded from desktop ISO to reduce size.
|
||||
# For on-OS development, build redbear-dev config or install separately.
|
||||
# gcc-native = {}
|
||||
# binutils-native = {}
|
||||
|
||||
# Desktop fonts and icons
|
||||
dejavu = {}
|
||||
@@ -199,6 +213,15 @@ depends_on = ["pci"]
|
||||
|
||||
[[driver.match]]
|
||||
class = 0x03
|
||||
vendor = 0x1002
|
||||
|
||||
[[driver.match]]
|
||||
class = 0x03
|
||||
vendor = 0x8086
|
||||
|
||||
[[driver.match]]
|
||||
class = 0x03
|
||||
vendor = 0x1af4
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
@@ -223,7 +246,7 @@ data = """
|
||||
[unit]
|
||||
description = "Firmware loading scheme"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"05_boot-essential.target",
|
||||
]
|
||||
|
||||
[service]
|
||||
@@ -237,7 +260,7 @@ data = """
|
||||
[unit]
|
||||
description = "Boot essential services target"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"04_drivers.target",
|
||||
]
|
||||
"""
|
||||
|
||||
@@ -247,7 +270,7 @@ data = """
|
||||
[unit]
|
||||
description = "IOMMU DMA remapping daemon"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"05_boot-essential.target",
|
||||
]
|
||||
|
||||
[service]
|
||||
@@ -256,12 +279,13 @@ type = "oneshot_async"
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/14_redox-drm.service"
|
||||
path = "/etc/init.d/10_redox-drm.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "DRM/KMS display driver (AMD + Intel + VirtIO)"
|
||||
requires_weak = [
|
||||
"05_boot-essential.target",
|
||||
"00_driver-manager.service",
|
||||
]
|
||||
|
||||
[service]
|
||||
@@ -405,7 +429,7 @@ type = "oneshot_async"
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/19_redbear-authd.service"
|
||||
path = "/etc/init.d/11_redbear-authd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Red Bear authentication daemon"
|
||||
@@ -420,22 +444,23 @@ type = "oneshot_async"
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/20_greeter.service"
|
||||
path = "/etc/init.d/12_sddm.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Red Bear greeter service"
|
||||
description = "SDDM display manager"
|
||||
requires_weak = [
|
||||
"00_driver-manager.service",
|
||||
"14_redox-drm.service",
|
||||
"10_redox-drm.service",
|
||||
"10_evdevd.service",
|
||||
"12_dbus.service",
|
||||
"13_redbear-sessiond.service",
|
||||
"13_seatd.service",
|
||||
"19_redbear-authd.service",
|
||||
"11_redbear-authd.service",
|
||||
]
|
||||
|
||||
[service]
|
||||
cmd = "/usr/bin/redbear-greeterd"
|
||||
envs = { VT = "3", REDBEAR_GREETER_USER = "greeter", KWIN_DRM_DEVICES = "/scheme/drm/card0", REDBEAR_DRM_WAIT_SECONDS = "10" }
|
||||
cmd = "/usr/bin/sddm"
|
||||
envs = { QT_PLUGIN_PATH = "/usr/plugins", QT_QPA_PLATFORM_PLUGIN_PATH = "/usr/plugins/platforms", QML2_IMPORT_PATH = "/usr/qml", XCURSOR_THEME = "Pop", XKB_CONFIG_ROOT = "/usr/share/X11/xkb" }
|
||||
type = "oneshot_async"
|
||||
"""
|
||||
|
||||
@@ -506,17 +531,73 @@ password = ""
|
||||
uid = 101
|
||||
gid = 101
|
||||
name = "greeter"
|
||||
home = "/nonexistent"
|
||||
home = "/var/lib/sddm"
|
||||
shell = "/usr/bin/zsh"
|
||||
|
||||
[users.sddm]
|
||||
password = ""
|
||||
uid = 102
|
||||
gid = 102
|
||||
name = "sddm"
|
||||
home = "/var/lib/sddm"
|
||||
shell = "/usr/bin/nologin"
|
||||
|
||||
[groups.greeter]
|
||||
gid = 101
|
||||
members = ["greeter"]
|
||||
members = ["greeter", "sddm"]
|
||||
|
||||
[groups.sddm]
|
||||
gid = 102
|
||||
members = ["sddm"]
|
||||
|
||||
[groups.sudo]
|
||||
gid = 1
|
||||
members = ["user"]
|
||||
|
||||
[groups.user]
|
||||
gid = 1000
|
||||
members = ["user"]
|
||||
|
||||
[groups.messagebus]
|
||||
gid = 100
|
||||
members = ["messagebus"]
|
||||
|
||||
[[files]]
|
||||
path = "/etc/sddm.conf"
|
||||
data = """
|
||||
[General]
|
||||
DisplayServer=wayland
|
||||
GreeterEnvironment=QT_PLUGIN_PATH=/usr/plugins,QML2_IMPORT_PATH=/usr/qml,QT_QPA_PLATFORM_PLUGIN_PATH=/usr/plugins/platforms
|
||||
|
||||
[Theme]
|
||||
Current=mayagrid
|
||||
ThemeDir=/usr/share/sddm/themes
|
||||
|
||||
[Wayland]
|
||||
CompositorCommand=/usr/libexec/sddm-helper-start-wayland kwin_wayland --drm /scheme/drm/card0
|
||||
|
||||
[Users]
|
||||
DefaultPath=/usr/bin
|
||||
MinimumUid=1000
|
||||
MaximumUid=60000
|
||||
RememberLastUser=true
|
||||
|
||||
[Autologin]
|
||||
User=
|
||||
Session=plasmawayland
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/usr/share/wayland-sessions/plasmawayland.desktop"
|
||||
data = """
|
||||
[Desktop Entry]
|
||||
Name=Plasma Wayland
|
||||
Comment=KDE Plasma on Wayland
|
||||
Exec=/usr/bin/kwin_wayland --drm /scheme/drm/card0
|
||||
Type=Application
|
||||
DesktopNames=KDE
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/pcid.d/ihdgd.toml"
|
||||
data = """
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# Red Bear greeter/login service wiring
|
||||
#
|
||||
# This fragment is intended to be included by the active desktop/graphics target.
|
||||
# DEPRECATED: This fragment is NO LONGER INCLUDED by any active config.
|
||||
# All greeter/auth/session wiring is now inlined in redbear-full.toml.
|
||||
# This file is retained for reference only. Do not include it in new configs.
|
||||
# To add greeter services, edit redbear-full.toml directly.
|
||||
|
||||
[[files]]
|
||||
# Original contents below (preserved for reference):
|
||||
#[[files]]
|
||||
path = "/etc/init.d/05_boot-essential.target"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Boot essential services target"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"04_drivers.target",
|
||||
]
|
||||
"""
|
||||
|
||||
@@ -30,7 +32,7 @@ redbear-session-launch = {}
|
||||
redbear-greeter = {}
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/19_redbear-authd.service"
|
||||
path = "/etc/init.d/11_redbear-authd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Red Bear authentication daemon"
|
||||
@@ -61,7 +63,7 @@ type = "oneshot_async"
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/20_greeter.service"
|
||||
path = "/etc/init.d/12_greeter.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Red Bear greeter service (experimental — Phase 3 user session bring-up)"
|
||||
@@ -70,7 +72,7 @@ requires_weak = [
|
||||
"12_dbus.service",
|
||||
"13_redbear-sessiond.service",
|
||||
"13_seatd.service",
|
||||
"19_redbear-authd.service",
|
||||
"11_redbear-authd.service",
|
||||
]
|
||||
|
||||
[service]
|
||||
@@ -101,7 +103,7 @@ data = """
|
||||
[unit]
|
||||
description = "Activate fallback console VT"
|
||||
requires_weak = [
|
||||
"05_boot-essential.target",
|
||||
"08_userland.target",
|
||||
]
|
||||
|
||||
[service]
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
# 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
|
||||
# 00_ipcd.service and 00_ptyd.service from the base recipe.
|
||||
# 00_drivers / 10_net: no longer overridden — the legacy scripts were removed
|
||||
# from base.toml. The retained 00_pcid-spawner.service unit name now
|
||||
# launches driver-manager so existing init ordering remains stable.
|
||||
# 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.
|
||||
# 00_pcid-spawner.service has been fully replaced by 00_driver-manager.service
|
||||
# (defined in redbear-device-services.toml). The old pcid-spawner
|
||||
# unit name is no longer used anywhere.
|
||||
|
||||
[packages]
|
||||
zsh = {}
|
||||
@@ -37,17 +32,4 @@ default_dependencies = false
|
||||
[service]
|
||||
cmd = "audiod"
|
||||
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"
|
||||
"""
|
||||
"""
|
||||
+30
-24
@@ -9,7 +9,7 @@
|
||||
# - all non-graphics, non-firmware packages from the full profile
|
||||
# - 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]
|
||||
filesystem_size = 1536
|
||||
@@ -19,7 +19,7 @@ uid = 100
|
||||
gid = 100
|
||||
name = "messagebus"
|
||||
home = "/nonexistent"
|
||||
shell = "/usr/bin/zsh"
|
||||
shell = "/usr/bin/false"
|
||||
|
||||
[packages]
|
||||
# Red Bear OS branding and host utilities.
|
||||
@@ -27,9 +27,8 @@ redbear-release = {}
|
||||
redbear-hwutils = {}
|
||||
redbear-quirks = {}
|
||||
|
||||
# Device driver infrastructure: driver-manager is started by
|
||||
# redbear-device-services.toml, with 00_pcid-spawner.service retained only as a
|
||||
# compatibility dependency alias for older service units.
|
||||
# Device driver infrastructure: driver-manager replaces pcid-spawner;
|
||||
# 00_driver-manager.service is defined in redbear-device-services.toml.
|
||||
ehcid = {}
|
||||
ohcid = {}
|
||||
uhcid = {}
|
||||
@@ -53,6 +52,7 @@ redbear-info = {}
|
||||
cub = {}
|
||||
cpufreqd = {}
|
||||
thermald = {}
|
||||
coretempd = {}
|
||||
hwrngd = {}
|
||||
redbear-acmd = {}
|
||||
redbear-ecmd = {}
|
||||
@@ -99,7 +99,7 @@ meson = {}
|
||||
ninja-build = {}
|
||||
m4 = {}
|
||||
#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
|
||||
|
||||
# ── Build / packaging utilities ──
|
||||
@@ -231,6 +231,7 @@ path = "/etc/init.d/00_i2c-dw-acpi.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "DesignWare ACPI I2C controller (non-blocking)"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"00_i2cd.service",
|
||||
]
|
||||
@@ -245,6 +246,7 @@ path = "/etc/init.d/00_intel-gpiod.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Intel ACPI GPIO registrar (non-blocking)"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"00_gpiod.service",
|
||||
"00_i2cd.service",
|
||||
@@ -260,6 +262,7 @@ path = "/etc/init.d/00_i2c-gpio-expanderd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "I2C GPIO expander companion bridge (non-blocking on live-mini)"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"00_i2cd.service",
|
||||
"00_gpiod.service",
|
||||
@@ -275,6 +278,8 @@ path = "/etc/init.d/00_i2c-hidd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "ACPI I2C HID bring-up daemon (non-blocking)"
|
||||
default_dependencies = false
|
||||
requires = ["00_acpid.service"]
|
||||
requires_weak = [
|
||||
"00_i2cd.service",
|
||||
"00_i2c-dw-acpi.service",
|
||||
@@ -292,6 +297,7 @@ path = "/etc/init.d/00_ucsid.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "USB-C UCSI topology detector (non-blocking on live-mini)"
|
||||
default_dependencies = false
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"00_i2cd.service",
|
||||
@@ -306,9 +312,9 @@ type = { scheme = "ucsi" }
|
||||
path = "/etc/init.d/12_boot-late.target"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Late boot services target"
|
||||
description = "Late boot services target (compat alias for 04_drivers.target)"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"04_drivers.target",
|
||||
]
|
||||
"""
|
||||
|
||||
@@ -467,23 +473,7 @@ data = ""
|
||||
directory = true
|
||||
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]]
|
||||
path = "/lib/drivers.d/30-graphics.toml"
|
||||
@@ -502,6 +492,7 @@ path = "/etc/init.d/29_activate_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Activate console VT"
|
||||
default_dependencies = false
|
||||
requires_weak = ["00_base.target"]
|
||||
|
||||
[service]
|
||||
@@ -515,6 +506,7 @@ path = "/etc/init.d/30_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Console terminals"
|
||||
default_dependencies = false
|
||||
requires_weak = ["29_activate_console.service"]
|
||||
|
||||
[service]
|
||||
@@ -528,6 +520,7 @@ path = "/etc/init.d/31_debug_console.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Debug console"
|
||||
default_dependencies = false
|
||||
requires_weak = ["29_activate_console.service"]
|
||||
|
||||
[service]
|
||||
@@ -535,3 +528,16 @@ cmd = "getty"
|
||||
args = ["/scheme/debug/no-preserve", "-J"]
|
||||
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,6 +1,6 @@
|
||||
# Red Bear OS shared network profile wiring
|
||||
#
|
||||
# Shared by redbear-minimal, redbear-desktop, redbear-full, and redbear-kde.
|
||||
# Shared by redbear-mini, redbear-full, and other network-enabled configs.
|
||||
|
||||
[[files]]
|
||||
path = "/etc/netctl"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# to the bounded Wi-Fi path and adds the first Intel driver-side package on top of the shared
|
||||
# firmware/control/profile tooling.
|
||||
|
||||
include = ["redbear-minimal.toml"]
|
||||
include = ["redbear-mini.toml"]
|
||||
|
||||
[general]
|
||||
filesystem_size = 2048
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ path = "/usr/lib/init.d/10_smolnetd.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Network stack for redoxer"
|
||||
requires_weak = ["00_pcid-spawner.service"]
|
||||
requires_weak = ["00_driver-manager.service"]
|
||||
|
||||
[service]
|
||||
cmd = "netstack"
|
||||
|
||||
+1
-1
@@ -239,7 +239,7 @@ fi
|
||||
export XCURSOR_THEME="${XCURSOR_THEME:-Pop}"
|
||||
export XKB_CONFIG_ROOT="${XKB_CONFIG_ROOT:-/usr/share/X11/xkb}"
|
||||
|
||||
if [ -z "${KWIN_DRM_DEVICES:-}" ] && [ -e /scheme/drm/card0 ]; then
|
||||
if [ -z "${KWIN_DRM_DEVICES:-}" ] && ( exec 3<"/scheme/drm/card0" && exec 3>&- ) >/dev/null 2>&1; then
|
||||
export KWIN_DRM_DEVICES=/scheme/drm/card0
|
||||
fi
|
||||
|
||||
|
||||
+1
-3
@@ -21,8 +21,7 @@ current/canonical versus historical/reference split obvious.
|
||||
> **Red Bear note:** newer subsystem plans can also live under `local/docs/` when they are Red Bear-
|
||||
> specific rather than general Redox architecture material. In particular, see
|
||||
> `local/docs/WIFI-IMPLEMENTATION-PLAN.md` for the current Wi-Fi direction,
|
||||
> `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` for the canonical desktop path,
|
||||
> and `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` for the canonical desktop path.
|
||||
> `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` for the canonical desktop path.
|
||||
|
||||
> **Repository model:** RedBearOS relates to Redox in the same way Ubuntu relates to Debian.
|
||||
> Upstream Redox remains the base platform; Red Bear carries packaging, patch, validation, and
|
||||
@@ -101,7 +100,6 @@ This summary is only a quick orientation layer. For canonical current-state deta
|
||||
- `docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md` for repository-wide execution order,
|
||||
- `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` for the canonical comprehensive plan,
|
||||
- `local/docs/PROFILE-MATRIX.md` for support-language by tracked profile,
|
||||
- `local/docs/PROFILE-MATRIX.md` for support-language by tracked profile,
|
||||
- and the active subsystem plans under `local/docs/` for detailed current workstreams.
|
||||
|
||||
- **Compile targets**: the supported compile targets are `redbear-mini`, `redbear-full`, and `redbear-grub`
|
||||
|
||||
+96
-10
@@ -1,5 +1,29 @@
|
||||
# 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
|
||||
|
||||
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."**
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
→ Desktop/graphics are available only on redbear-full
|
||||
→ 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
|
||||
→ 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:
|
||||
```
|
||||
# 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)
|
||||
│ ├── firmware/ ← GPU firmware blobs (gitignored, fetched)
|
||||
│ ├── scripts/
|
||||
│ │ ├── rebuild-cascade.sh ← Rebuild package + all dependents (BFS reverse-dep graph)
|
||||
│ │ ├── provision-release.sh ← Provision new release from Redox ref
|
||||
│ │ ├── build-redbear.sh ← Unified Red Bear OS build script
|
||||
│ │ ├── 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-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
|
||||
./local/scripts/validate-vm-network-baseline.sh
|
||||
./local/scripts/test-vm-network-qemu.sh redbear-mini
|
||||
@@ -442,15 +534,10 @@ When mainline updates affect our work:
|
||||
- `local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md` is the current DRM-focused execution plan beneath
|
||||
the canonical desktop path. It keeps Intel and AMD at the same evidence bar while separating
|
||||
display/KMS maturity from render/3D maturity.
|
||||
- Older GPU-specific docs such as `local/docs/AMD-FIRST-INTEGRATION.md`,
|
||||
`local/docs/HARDWARE-3D-ASSESSMENT.md`, and `local/docs/DMA-BUF-IMPROVEMENT-PLAN.md` remain
|
||||
useful reference material, but they are not the planning authority when sequencing or acceptance
|
||||
criteria differ.
|
||||
- Older GPU-specific docs (`AMD-FIRST-INTEGRATION.md`, `HARDWARE-3D-ASSESSMENT.md`, `DMA-BUF-IMPROVEMENT-PLAN.md`) have been retired and removed from the tree. Their content is subsumed by `CONSOLE-TO-KDE-DESKTOP-PLAN.md` and `DRM-MODERNIZATION-EXECUTION-PLAN.md`.
|
||||
- `DESKTOP-STACK-CURRENT-STATUS.md` has been retired — its content merged into `CONSOLE-TO-KDE-DESKTOP-PLAN.md`.
|
||||
- `local/docs/AMD-FIRST-INTEGRATION.md` remains the deeper AMD-specific technical roadmap, but AMD
|
||||
and Intel machines are now equal-priority Red Bear OS targets.
|
||||
- The earlier Phase 0–3 reassessment bridge has been retired. Its reconciliation role is now
|
||||
covered by `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md`,
|
||||
`local/docs/DESKTOP-STACK-CURRENT-STATUS.md`, and `docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md`.
|
||||
- `local/docs/WIFI-IMPLEMENTATION-PLAN.md` is the current Wi-Fi architecture and rollout plan,
|
||||
including the bounded role of `linux-kpi` and the native wireless control-plane direction.
|
||||
- `local/docs/USB-IMPLEMENTATION-PLAN.md` and `local/docs/BLUETOOTH-IMPLEMENTATION-PLAN.md` should
|
||||
@@ -459,8 +546,7 @@ When mainline updates affect our work:
|
||||
IRQ delivery, MSI/MSI-X quality, IOMMU validation, and other low-level controller completeness work.
|
||||
- `local/docs/QUIRKS-SYSTEM.md` documents the hardware quirks infrastructure: compiled-in tables,
|
||||
TOML runtime files, DMI matching, driver integration, and the linux-kpi C FFI bridge.
|
||||
- `local/docs/QUIRKS-IMPROVEMENT-PLAN.md` is the current follow-up plan for removing quirks drift,
|
||||
integrating quirks into real drivers, and converging on one source of truth.
|
||||
- `local/docs/QUIRKS-IMPROVEMENT-PLAN.md` has been retired — quirks convergence is tracked in `QUIRKS-SYSTEM.md` and the canonical desktop path plan.
|
||||
- `local/docs/DBUS-INTEGRATION-PLAN.md` is the canonical D-Bus architecture and implementation plan for KDE Plasma 6 on Wayland. It defines the phased approach to D-Bus service integration, the `redbear-sessiond` login1-compatible session broker, and the gap analysis for desktop-facing D-Bus services.
|
||||
- `local/docs/GREETER-LOGIN-IMPLEMENTATION-PLAN.md` is the canonical Red Bear-native greeter/login design and current implementation plan for the `redbear-full` desktop path. It defines the `redbear-authd` / `redbear-session-launch` / `redbear-greeter` split, service wiring, validation surface, and the current boundary between the active greeter path and the older `redbear-validation-session` helper flows.
|
||||
|
||||
@@ -848,4 +934,4 @@ Config comparison:
|
||||
|
||||
## 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"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 8
|
||||
|
||||
@@ -17,6 +18,7 @@ priority = 100
|
||||
command = ["/usr/lib/drivers/ahcid"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 6
|
||||
|
||||
@@ -27,6 +29,7 @@ priority = 100
|
||||
command = ["/usr/lib/drivers/ided"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 1
|
||||
|
||||
@@ -37,6 +40,7 @@ priority = 100
|
||||
command = ["/usr/lib/drivers/virtio-blkd"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1AF4
|
||||
device = 0x1001
|
||||
class = 1
|
||||
|
||||
@@ -7,6 +7,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/e1000d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 2
|
||||
|
||||
@@ -17,6 +18,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/rtl8168d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x10EC
|
||||
class = 2
|
||||
|
||||
@@ -27,6 +29,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/rtl8139d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x10EC
|
||||
device = 0x8139
|
||||
|
||||
@@ -37,6 +40,7 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/ixgbed"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 2
|
||||
subclass = 0
|
||||
@@ -48,5 +52,6 @@ priority = 50
|
||||
command = ["/usr/lib/drivers/virtio-netd"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1AF4
|
||||
class = 2
|
||||
|
||||
@@ -44,6 +44,49 @@ priority = 80
|
||||
command = ["/usr/lib/drivers/uhcid"]
|
||||
|
||||
[[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
|
||||
subclass = 0x03
|
||||
prog_if = 0x00
|
||||
|
||||
@@ -7,6 +7,7 @@ priority = 60
|
||||
command = ["/usr/lib/drivers/vesad"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x03
|
||||
|
||||
[[driver]]
|
||||
@@ -18,14 +19,17 @@ command = ["/usr/bin/redox-drm"]
|
||||
# Only match known GPU vendors. Class 0x03 alone catches QEMU VGA
|
||||
# (vendor 0x1234) which redox-drm rejects with a fatal error.
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1002
|
||||
class = 0x03
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 0x03
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1AF4
|
||||
class = 0x03
|
||||
|
||||
@@ -36,6 +40,7 @@ priority = 61
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1AF4
|
||||
class = 0x03
|
||||
|
||||
@@ -47,6 +52,7 @@ priority = 61
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 0x03
|
||||
subclass = 0x00
|
||||
@@ -59,6 +65,7 @@ priority = 61
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x1002
|
||||
class = 0x03
|
||||
subclass = 0x00
|
||||
|
||||
@@ -7,6 +7,7 @@ priority = 40
|
||||
command = ["/usr/lib/drivers/ihdad"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
vendor = 0x8086
|
||||
class = 0x04
|
||||
|
||||
@@ -17,6 +18,7 @@ priority = 40
|
||||
command = ["/usr/lib/drivers/ac97d"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 0x04
|
||||
subclass = 0x01
|
||||
|
||||
|
||||
@@ -1,49 +1,139 @@
|
||||
# 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]]
|
||||
name = "i2cd"
|
||||
description = "I2C host adapter registry"
|
||||
priority = 85
|
||||
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]]
|
||||
name = "gpiod"
|
||||
description = "GPIO controller registry"
|
||||
priority = 85
|
||||
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]]
|
||||
name = "dw-acpi-i2cd"
|
||||
description = "DesignWare ACPI I2C controller"
|
||||
priority = 80
|
||||
command = ["/usr/lib/drivers/dw-acpi-i2cd"]
|
||||
depends_on = ["acpi", "i2c"]
|
||||
|
||||
[[driver]]
|
||||
name = "intel-gpiod"
|
||||
description = "Intel ACPI GPIO registrar"
|
||||
priority = 80
|
||||
command = ["/usr/lib/drivers/intel-gpiod"]
|
||||
[[driver.match]]
|
||||
bus = "acpi"
|
||||
class = 0x0C
|
||||
subclass = 0x05
|
||||
vendor = 0x8086
|
||||
|
||||
[[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
|
||||
|
||||
[[driver]]
|
||||
name = "intel-lpss-i2cd"
|
||||
description = "Intel LPSS I2C controller"
|
||||
priority = 80
|
||||
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]]
|
||||
name = "i2c-gpio-expanderd"
|
||||
description = "I2C GPIO expander companion bridge"
|
||||
priority = 75
|
||||
command = ["/usr/lib/drivers/i2c-gpio-expanderd"]
|
||||
depends_on = ["i2c", "gpio"]
|
||||
|
||||
[[driver]]
|
||||
name = "intel-thc-hidd"
|
||||
description = "Intel THC QuickI2C HID transport"
|
||||
priority = 75
|
||||
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,363 @@
|
||||
# Driver Discovery and Dynamic Hardware Mapping Plan
|
||||
|
||||
**Status**: Draft — implementation pending
|
||||
**Date**: 2026-05-27
|
||||
**Supersedes**: Ad-hoc pcid-spawner + hardcoded lived disk paths
|
||||
**Author**: Red Bear OS team
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem Statement
|
||||
|
||||
Red Bear OS has two critical gaps in hardware discovery:
|
||||
|
||||
1. **lived's disk fallback is broken**: The live ISO boot daemon (`lived`) tries hardcoded paths `/scheme/disk/0` and `/scheme/usbscsi/0` to find the physical boot disk. But no disk driver registers those exact scheme names — they register `disk.pci-00-1F-2_ahci`, `disk.usb-xhci+1-scsi`, etc. The fallback **never works**.
|
||||
|
||||
2. **No dynamic hardware mapping**: The system does not distinguish between "hardware present" and "driver needed." On bare metal with no virtio devices, the system should not try to load `virtio-blkd`. On QEMU with no real AHCI controller, the system should not try to load `ahcid`. Today, the driver-manager loads whatever matches its static config files regardless of whether the hardware exists.
|
||||
|
||||
Linux solves both problems with a two-stage model:
|
||||
- **Stage 1 (initramfs)**: Enumerate PCI bus, load ONLY the storage driver matching the boot controller, mount rootfs.
|
||||
- **Stage 2 (rootfs)**: Full enumeration, udev + modprobe dynamically load all remaining drivers based on actual hardware.
|
||||
|
||||
---
|
||||
|
||||
## 2. Current Architecture
|
||||
|
||||
### 2.1 Boot Sequence (Initfs Phase)
|
||||
|
||||
```
|
||||
Bootstrap (PID 1) → init → services start in dependency order:
|
||||
|
||||
00_runtime.target randd, nulld, zerod, rtcd, logd
|
||||
10_inputd.service VT input multiplexer
|
||||
10_lived.service Live disk daemon (RAM preload + disk fallback)
|
||||
20_graphics.target vesad (FB handoff), fbcond, fbbootlogd
|
||||
41_acpid.service ACPI interpreter → scheme:acpi
|
||||
40_hwd.service Hardware manager → spawns pcid internally
|
||||
pcid → enumerates PCI bus → registers scheme:pci
|
||||
00_driver-manager-initfs.service (if P26 applied)
|
||||
Loads /scheme/initfs/lib/drivers.d/00-storage.toml
|
||||
Only: ahcid, ided, nvmed, virtio-blkd
|
||||
40_drivers.target All initfs drivers
|
||||
50_rootfs.service Mount rootfs (hard dep on drivers.target)
|
||||
90_initfs.target Trigger switchroot
|
||||
```
|
||||
|
||||
### 2.2 Driver Registration Contract
|
||||
|
||||
All disk drivers using `driver_block::DiskScheme` register schemes starting with `"disk"`:
|
||||
|
||||
| Driver | Scheme Name Pattern | Match Criteria |
|
||||
|--------|---------------------|----------------|
|
||||
| ided | `disk.pci-XX-XX-X_ide` | PCI class 0x01, subclass 0x01 |
|
||||
| ahcid | `disk.pci-XX-XX-X_ahci` | PCI class 0x01, subclass 0x06 |
|
||||
| nvmed | `disk.pci-XX-XX-X-nvme` | PCI class 0x01, subclass 0x08 |
|
||||
| virtio-blkd | `disk.pci-XX-XX-X_virtio_blk` | PCI vendor 0x1AF4, device 0x1001 |
|
||||
| usbscsid | `disk.usb-xhci+PORT-scsi` | USB SCSI transport |
|
||||
| lived | `disk.live` | RAM-backed (our daemon) |
|
||||
|
||||
The `DiskScheme::new()` assertion (`assert!(scheme_name.starts_with("disk"))`) is the **contract** that enables dynamic discovery: any consumer can find all disk schemes by listing `/scheme/` and filtering for the `"disk"` prefix.
|
||||
|
||||
### 2.3 The Two Driver-Loading Paths
|
||||
|
||||
| Path | Mechanism | Config Source | Drivers |
|
||||
|------|-----------|---------------|---------|
|
||||
| **Initfs** | `driver-manager --initfs` | `/scheme/initfs/lib/drivers.d/00-storage.toml` | Storage only (4 drivers) |
|
||||
| **Rootfs** | `driver-manager --hotplug` | `/lib/drivers.d/*.toml` | All categories (40+ drivers) |
|
||||
|
||||
### 2.4 How Linux Does It (Reference)
|
||||
|
||||
Linux uses a two-tier ordering:
|
||||
|
||||
**Tier 1 — Initcall levels** (include/linux/init.h):
|
||||
```
|
||||
Level 0: pure_initcall (architecture setup)
|
||||
Level 2: postcore_initcall (PCI subsystem registers here)
|
||||
Level 4: subsys_initcall (SCSI, networking subsystems)
|
||||
Level 6: device_initcall (module_init → all built-in drivers)
|
||||
Level 7: late_initcall (late-stage platform drivers)
|
||||
```
|
||||
|
||||
**Tier 2 — Link order** within device_initcall (drivers/Makefile):
|
||||
```
|
||||
Line 49: obj-y += virtio/ # VirtIO before block
|
||||
Line 76: obj-y += block/ # Block devices (storage)
|
||||
Line 84: obj-y += nvme/ # NVMe
|
||||
Line 85: obj-y += ata/ # ATA/AHCI
|
||||
Line 92: obj-y += net/ # Network
|
||||
Line 68: obj-y += gpu/ # GPU comes AFTER storage
|
||||
```
|
||||
|
||||
**The critical principle**: Storage must load before GPU not because of PCI ordering, but because GPU drivers need firmware blobs from `/lib/firmware/` — which requires a mounted filesystem. Storage drivers are needed to mount that filesystem.
|
||||
|
||||
**Dynamic loading** (after rootfs mount): `MODULE_DEVICE_TABLE` entries in every driver generate `modules.alias` patterns. udev receives kernel uevents with `MODALIAS=pci:v00001AF4d00001001...`, calls `modprobe`, which looks up the alias and loads the matching `.ko` module.
|
||||
|
||||
---
|
||||
|
||||
## 3. Design: Two-Stage Dynamic Hardware Discovery
|
||||
|
||||
### 3.1 Stage 1 — Initfs Boot (Storage-Only)
|
||||
|
||||
**Goal**: Load exactly the storage driver(s) needed to mount the root filesystem. No more, no less.
|
||||
|
||||
**Mechanism**: driver-manager `--initfs` already exists and does PCI class/vendor matching. The missing piece is that the P26 patch (which creates `00_driver-manager-initfs.service` and `initfs-storage.toml`) is wired in `recipe.toml` but needs to be applied.
|
||||
|
||||
**Initfs driver config** (`initfs-storage.toml`):
|
||||
|
||||
```toml
|
||||
# Only storage drivers — needed to mount rootfs
|
||||
# GPU/display deliberately excluded (handled by rootfs DRM/KMS stack)
|
||||
|
||||
[[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 = "ahcid"
|
||||
description = "AHCI SATA driver"
|
||||
priority = 100
|
||||
command = ["/scheme/initfs/lib/drivers/ahcid"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 6
|
||||
|
||||
[[driver]]
|
||||
name = "ided"
|
||||
description = "PATA IDE driver"
|
||||
priority = 100
|
||||
command = ["/scheme/initfs/lib/drivers/ided"]
|
||||
|
||||
[[driver.match]]
|
||||
bus = "pci"
|
||||
class = 1
|
||||
subclass = 1
|
||||
|
||||
[[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
|
||||
```
|
||||
|
||||
**How this is already dynamic**: The driver-manager only spawns a driver when the PCI bus actually reports a matching device. If QEMU has no AHCI controller, `ahcid` is never spawned. If bare metal has no VirtIO devices, `virtio-blkd` is never spawned. The TOML match table is a **candidate list**, not a **must-load list**.
|
||||
|
||||
**What's needed**: Ensure P26 is applied, ensure `virtio-blkd` is in the BINS list, and ensure the initfs binary staging includes all 4 storage drivers.
|
||||
|
||||
### 3.2 Stage 2 — Rootfs (Full Hardware Discovery)
|
||||
|
||||
**Goal**: After rootfs is mounted, dynamically discover and load ALL remaining drivers based on actual hardware.
|
||||
|
||||
**Mechanism**: `driver-manager --hotplug` already reads `/lib/drivers.d/*.toml` (8 config files, 40+ drivers), enumerates PCI + ACPI buses, and spawns matching drivers. It also runs a hotplug loop for device add/remove.
|
||||
|
||||
**The existing driver configs are already data-driven and dynamic**:
|
||||
|
||||
| Config File | Category | Priority | Matching |
|
||||
|-------------|----------|----------|----------|
|
||||
| `00-storage.toml` | Storage | 100 | PCI class-based |
|
||||
| `10-network.toml` | Network | 50 | PCI vendor + class |
|
||||
| `20-usb.toml` | USB | 80 | PCI class + prog_if |
|
||||
| `30-graphics.toml` | GPU/Display | 60 | PCI class 0x03 |
|
||||
| `40-input.toml` | Input | 40 | Sentinel (vendor=0xFFFF) |
|
||||
| `50-audio.toml` | Audio | 40 | PCI vendor + class |
|
||||
| `60-gpio-i2c.toml` | GPIO/I2C | 30 | ACPI bus matching |
|
||||
| `70-usb-class.toml` | USB class | 20 | Sentinel (vendor=0xFFFF) |
|
||||
|
||||
**Key property**: Priority ordering ensures storage (100) > USB (80) > GPU (60) > network (50) > audio (40). This mirrors Linux's link-order principle.
|
||||
|
||||
### 3.3 lived Disk Fallback Fix
|
||||
|
||||
**Current bug**: `lived` tries `/scheme/disk/0` — but real schemes are named `disk.pci-00-1F-2_ahci`, never just `disk`.
|
||||
|
||||
**Fix**: Replace hardcoded paths with RedoxFS-style dynamic scheme discovery (same pattern as `filesystem_by_uuid` in `redoxfs/src/bin/mount.rs`):
|
||||
|
||||
```rust
|
||||
fn try_open_disk(&self) -> Result<File, String> {
|
||||
for attempt in 0..DISK_OPEN_MAX_RETRIES {
|
||||
// List /scheme/ to find all registered disk schemes
|
||||
if let Ok(entries) = std::fs::read_dir("/scheme") {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
|
||||
// All disk schemes start with "disk." (driver-block contract)
|
||||
// Skip our own "disk.live" scheme
|
||||
if name_str.starts_with("disk.") && name_str != "disk.live" {
|
||||
// Try opening disk 0 on this scheme
|
||||
let path = format!("/scheme/{}/0", name_str);
|
||||
if let Ok(file) = File::open(&path) {
|
||||
eprintln!("lived: opened physical disk at {} (attempt {})",
|
||||
path, attempt + 1);
|
||||
return Ok(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if attempt < DISK_OPEN_MAX_RETRIES - 1 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(
|
||||
DISK_OPEN_RETRY_INTERVAL_MS
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("no disk scheme found after {} retries", DISK_OPEN_MAX_RETRIES))
|
||||
}
|
||||
```
|
||||
|
||||
**This is the exact pattern RedoxFS uses** in `filesystem_by_uuid()`. It:
|
||||
1. Lists `/scheme/` (all registered schemes)
|
||||
2. Filters to names starting with `"disk."` (the `driver-block` contract)
|
||||
3. Skips `disk.live` (our own RAM-backed scheme)
|
||||
4. Tries opening disk 0 on each discovered scheme
|
||||
|
||||
**Boot timing**: lived starts at service 10, before disk drivers. The retry loop (60 × 500ms = 30s) gives driver-manager and storage drivers time to load and register their schemes. As soon as ANY storage driver registers `disk.*`, lived finds it.
|
||||
|
||||
---
|
||||
|
||||
## 4. What Needs to Change
|
||||
|
||||
### 4.1 Patches Required
|
||||
|
||||
| Component | Patch | What It Does |
|
||||
|-----------|-------|--------------|
|
||||
| **base** | P60 (new) | Add `virtio-blkd` to BINS + staged files; update lived's `try_open_disk()` with dynamic scheme discovery |
|
||||
| **kernel** | P26 (existing) | DebugDisplay scrolling fix (already done) |
|
||||
| **base** | P26-driver-manager-initfs-conversion.patch (existing, wired but needs application verification) | Replaces pcid-spawner with driver-manager in initfs |
|
||||
|
||||
### 4.2 Changes to `recipes/core/base/recipe.toml`
|
||||
|
||||
1. **Add `virtio-blkd` to BINS** (already done in working tree)
|
||||
2. **Add `virtio-blkd` to staged files list** (already done in working tree)
|
||||
3. **No changes to driver configs** — `initfs-storage.toml` already lists all 4 storage drivers
|
||||
|
||||
### 4.3 Changes to `recipes/core/base/source/drivers/storage/lived/src/main.rs`
|
||||
|
||||
Replace the hardcoded `candidates` array in `try_open_disk()` with `/scheme/` directory enumeration that discovers disk schemes dynamically.
|
||||
|
||||
### 4.4 No Changes Needed
|
||||
|
||||
- **driver-manager** — already does dynamic PCI matching
|
||||
- **initfs-storage.toml** — already has the right 4 storage drivers
|
||||
- **Driver configs** (`/lib/drivers.d/*.toml`) — already data-driven with vendor/class matching
|
||||
- **pcid** — already enumerates PCI bus correctly
|
||||
- **Boot service order** — already correct (lived at 10, driver-manager-initfs at 00, rootfs at 50)
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification Plan
|
||||
|
||||
### 5.1 QEMU with IDE (default)
|
||||
|
||||
```bash
|
||||
timeout 60 qemu-system-x86_64 \
|
||||
-drive file=build/x86_64/redbear-full.iso,format=raw \
|
||||
-m 4G -smp 4 -serial stdio -no-reboot
|
||||
```
|
||||
|
||||
Expected: lived finds `disk.pci-00-01-1_ide` scheme from `ided`, mounts rootfs.
|
||||
|
||||
### 5.2 QEMU with virtio-blk
|
||||
|
||||
```bash
|
||||
timeout 60 qemu-system-x86_64 \
|
||||
-device virtio-blk-pci,drive=drive0 \
|
||||
-drive id=drive0,file=build/x86_64/redbear-full.iso,format=raw,if=none \
|
||||
-m 4G -smp 4 -serial stdio -no-reboot
|
||||
```
|
||||
|
||||
Expected: lived finds `disk.pci-00-XX-X_virtio_blk` scheme from `virtio-blkd`, mounts rootfs.
|
||||
|
||||
### 5.3 Bare Metal USB Boot
|
||||
|
||||
Expected: lived finds `disk.usb-xhci+PORT-scsi` scheme from `usbscsid`, mounts rootfs.
|
||||
|
||||
### 5.4 No Unnecessary Drivers
|
||||
|
||||
On QEMU with only virtio-blk (no AHCI), `ahcid` should NOT be spawned. Verify via boot log:
|
||||
```
|
||||
driver-manager: no driver found for pci 0000:00:01.1 # IDE controller — no match
|
||||
driver-manager: bound: 0000:00:04.0 -> virtio-blkd # VirtIO block — matched
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. PCI Class Code Reference
|
||||
|
||||
From Linux `include/linux/pci_ids.h` and our driver configs:
|
||||
|
||||
| Class | Subclass | Prog IF | Device Type | Red Bear Driver |
|
||||
|-------|----------|---------|-------------|-----------------|
|
||||
| 0x01 | 0x01 | — | IDE/PATA | `ided` |
|
||||
| 0x01 | 0x06 | 0x01 | AHCI SATA | `ahcid` |
|
||||
| 0x01 | 0x08 | 0x02 | NVMe | `nvmed` |
|
||||
| 0x01 | 0x00 | — | VirtIO Block (vendor 0x1AF4, device 0x1001) | `virtio-blkd` |
|
||||
| 0x02 | — | — | Ethernet | `e1000d`, `rtl8168d`, etc. |
|
||||
| 0x03 | — | — | Display/GPU | `redox-drm` |
|
||||
| 0x04 | 0x03 | — | Audio (HDA) | `ihdad` |
|
||||
| 0x0C | 0x03 | 0x30 | xHCI USB | `xhcid` |
|
||||
| 0x0C | 0x03 | 0x00 | UHCI USB | `uhcid` |
|
||||
| 0x0C | 0x03 | 0x10 | OHCI USB | `ohcid` |
|
||||
| 0x0C | 0x03 | 0x20 | EHCI USB | `ehcid` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Boot Timeline (Target State)
|
||||
|
||||
```
|
||||
T+0ms Bootstrap starts, creates initfs/procmgr/namespace schemes
|
||||
T+50ms init starts, launches 00_randd → 00_logd → 00_runtime.target
|
||||
T+200ms lived starts (service 10), loads 128 MiB preload
|
||||
T+300ms vesad starts (FB handoff for text console)
|
||||
T+400ms acpid starts → ACPI interpreter → scheme:acpi
|
||||
T+500ms hwd starts → spawns pcid → PCI bus scan → scheme:pci
|
||||
driver-manager --initfs starts:
|
||||
Loads 00-storage.toml (4 storage drivers)
|
||||
Enumerates PCI bus via /scheme/pci/
|
||||
QEMU: finds 8086:7010 (IDE) → spawns ided
|
||||
finds 1234:1111 (virtio-gpu) → no storage match, skipped
|
||||
finds 1AF4:1050 (virtio-net) → no storage match, skipped
|
||||
T+1500ms ided registers disk.pci-00-01-1_ide
|
||||
lived discovers disk.pci-00-01-1_ide via /scheme/ enumeration
|
||||
lived disk fallback succeeds
|
||||
T+2000ms redoxfs mounts rootfs from lived
|
||||
T+2500ms switchroot → rootfs init starts
|
||||
T+3000ms driver-manager --hotplug starts (rootfs):
|
||||
Loads all /lib/drivers.d/*.toml configs
|
||||
Detects ided already bound → skips
|
||||
Finds 1234:1111 (display class 0x03) → spawns redox-drm
|
||||
Finds 8086:100E (network class 0x02) → spawns e1000d
|
||||
Finds 1AF4:1050 (virtio-net) → spawns virtio-netd
|
||||
T+5000ms All drivers bound, system fully operational
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Principles
|
||||
|
||||
1. **Data-driven, not hardcoded**: Driver matching via TOML configs with vendor/device/class fields. No binary name hardcoding, no path guessing.
|
||||
|
||||
2. **Enumerate first, match second**: PCI bus scan produces ALL devices. Driver matching filters to supported ones. Unknown hardware is logged but doesn't block boot.
|
||||
|
||||
3. **Priority ordering**: Storage (100) before USB (80) before GPU (60) before network (50) before audio (40). Mirrors Linux's link-order principle.
|
||||
|
||||
4. **Stage 1 = minimum viable set**: Initfs loads ONLY storage drivers. Everything else waits for rootfs.
|
||||
|
||||
5. **Dynamic scheme discovery**: lived discovers disk schemes by reading `/scheme/` and filtering for the `"disk."` prefix — the same contract that `driver-block` enforces.
|
||||
|
||||
6. **No unnecessary drivers**: If hardware doesn't exist, the driver is never spawned. `driver-manager` only calls `probe()` for devices that actually exist on the PCI/ACPI bus.
|
||||
|
||||
7. **Deferred retry for timing**: Drivers that start before their dependencies are ready get retried (3 times in initfs, 5 times in hotplug). After max retries, the device is permanently skipped with a logged reason.
|
||||
@@ -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,483 @@
|
||||
# Live ISO Mount — Architecture, Failure Analysis, and Fix Plan
|
||||
|
||||
**Date:** 2026-05-27
|
||||
**Status:** Draft — fixes not yet implemented
|
||||
**Scope:** Bootloader live preload, lived daemon, RedoxFS mount chain
|
||||
|
||||
---
|
||||
|
||||
## 1. Current Architecture
|
||||
|
||||
### 1.1 Boot Flow (Live ISO)
|
||||
|
||||
```
|
||||
UEFI firmware
|
||||
→ Bootloader (recipes/core/bootloader/source/src/main.rs)
|
||||
1. Find RedoxFS partition on disk
|
||||
2. Read filesystem header → get total filesystem size (e.g., 4093 MiB)
|
||||
3. Live preload: read first N MiB of filesystem into RAM
|
||||
- Cap: max_preload = 1024 MiB (line 559)
|
||||
- Set env: DISK_LIVE_ADDR=<phys addr>, DISK_LIVE_SIZE=<preload size>
|
||||
- Set env: REDOXFS_BLOCK=0 (start of partition)
|
||||
4. Load kernel from RedoxFS into memory
|
||||
5. Load initfs from RedoxFS into memory
|
||||
6. Set up paging, pass env to kernel
|
||||
7. Jump to kernel entry point
|
||||
|
||||
Kernel
|
||||
→ bootstrap (initfs)
|
||||
→ init daemon
|
||||
→ lived daemon (10_lived.service)
|
||||
- Reads DISK_LIVE_ADDR + DISK_LIVE_SIZE from env
|
||||
- Maps preloaded RAM as LiveDisk via /scheme/memory/physical
|
||||
- Registers scheme:disk.live
|
||||
- LiveDisk.size() = preloaded size (1024 MiB)
|
||||
- LiveDisk.block_size() = PAGE_SIZE (4096) [P6 patch changes to 512]
|
||||
|
||||
→ redoxfs daemon (50_rootfs.service)
|
||||
- Opens /scheme/disk.live/0 as DiskFile
|
||||
- Calls FileSystem::open(disk, password, block=0, cleanup=true)
|
||||
- Reads header at block 0 (inside preloaded region → works)
|
||||
- Calls fs.reset_allocator() → walks the allocation tree
|
||||
- Calls fs.cleanup() → may read blocks across the entire filesystem
|
||||
- FAILURE: any read beyond preloaded size returns EINVAL
|
||||
```
|
||||
|
||||
### 1.2 Component Map
|
||||
|
||||
| Component | Source | Role |
|
||||
|-----------|--------|------|
|
||||
| **Bootloader** | `recipes/core/bootloader/source/src/main.rs` | Preloads filesystem into RAM, passes env vars |
|
||||
| **lived** | `recipes/core/base/source/drivers/storage/lived/src/main.rs` | Maps preloaded RAM as `scheme:disk.live` |
|
||||
| **RedoxFS mount** | `recipes/core/redoxfs/source/src/bin/mount.rs` | Opens disk scheme, calls FileSystem::open |
|
||||
| **RedoxFS lib** | `recipes/core/redoxfs/source/src/filesystem.rs` | Reads header, walks allocator tree |
|
||||
| **driver-block** | `recipes/core/base/source/drivers/storage/driver-block/src/lib.rs` | DiskWrapper with block_size alignment checks |
|
||||
| **P6 patch** | `local/patches/base/P6-lived-block-size-512.patch` | Changes block_size from PAGE_SIZE to 512 |
|
||||
|
||||
### 1.3 The Preload Cap
|
||||
|
||||
```rust
|
||||
// bootloader/src/main.rs:559
|
||||
let max_preload: u64 = 1024 * MIBI as u64; // 1 GiB hard cap
|
||||
let preload_size = if size > max_preload {
|
||||
max_preload // Cap at 1 GiB
|
||||
} else {
|
||||
size // Preload entire filesystem if ≤ 1 GiB
|
||||
};
|
||||
```
|
||||
|
||||
For redbear-full (4093 MiB filesystem): preloads 1024 MiB, 3069 MiB must come from disk.
|
||||
For redbear-mini (1533 MiB filesystem): preloads 1024 MiB, 509 MiB must come from disk.
|
||||
|
||||
### 1.4 The lived Disk
|
||||
|
||||
```rust
|
||||
// lived/src/main.rs - LiveDisk::read (CURRENT, unpatched source)
|
||||
fn block_size(&self) -> u32 {
|
||||
PAGE_SIZE as u32 // P6 changes this to 512
|
||||
}
|
||||
|
||||
fn size(&self) -> u64 {
|
||||
self.original.len() as u64 // This is the PRELOADED size, not total filesystem size
|
||||
}
|
||||
|
||||
async fn read(&mut self, mut block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
||||
let mut offset = (block as usize) * PAGE_SIZE;
|
||||
if offset + buffer.len() > self.original.len() {
|
||||
return Err(syscall::Error::new(EINVAL)); // ← THIS IS THE FAILURE POINT
|
||||
}
|
||||
// ... read from preloaded buffer
|
||||
}
|
||||
```
|
||||
|
||||
**The fundamental problem:** `lived` only has the preloaded buffer (1024 MiB). It has no
|
||||
access to the remaining filesystem data on the physical disk. When RedoxFS tries to read
|
||||
beyond 1024 MiB, lived returns EINVAL.
|
||||
|
||||
---
|
||||
|
||||
## 2. Failure Analysis
|
||||
|
||||
### 2.1 Why Does the Mini ISO Work?
|
||||
|
||||
The mini ISO (1533 MiB) also has 509 MiB beyond the preload. However:
|
||||
|
||||
1. RedoxFS `FileSystem::open` reads the header at block 0 (within preload) → OK
|
||||
2. `reset_allocator` walks the free block tree. For a 1533 MiB filesystem with minimal
|
||||
contents, the allocator metadata is concentrated near the start → likely within 1024 MiB
|
||||
3. `cleanup` reads extent nodes — for a small filesystem, these are also near the start
|
||||
|
||||
For the full ISO (4093 MiB) with hundreds of packages:
|
||||
- The allocator tree and extent nodes span the entire 4093 MiB range
|
||||
- RedoxFS needs to read blocks at offsets > 1024 MiB during `FileSystem::open`
|
||||
- lived rejects those reads → mount fails
|
||||
|
||||
**The mini ISO works by luck** — its metadata happens to fit within the preload window.
|
||||
This is not a reliable design.
|
||||
|
||||
### 2.2 The Exact Error Chain
|
||||
|
||||
```
|
||||
RedoxFS FileSystem::open
|
||||
→ disk.read_at(block_N, &mut header)
|
||||
→ DiskFile::read_at(buffer, block_N * BLOCK_SIZE)
|
||||
→ syscall::read(scheme:disk.live/0, offset=block_N * 512)
|
||||
→ lived::LiveDisk::read(block_N, buffer)
|
||||
→ offset = block_N * PAGE_SIZE // or 512 with P6
|
||||
→ if offset + buffer.len() > self.original.len():
|
||||
return Err(EINVAL) // ← HERE
|
||||
```
|
||||
|
||||
The error propagates:
|
||||
- lived → EINVAL
|
||||
- DiskFile → "RedoxFS: IO ERROR: Invalid argument (os error 22)"
|
||||
- FileSystem::open → Err(EINVAL)
|
||||
- mount.rs → "not able to mount uuid ..."
|
||||
|
||||
### 2.3 The P6 Block Size Patch
|
||||
|
||||
The P6 patch (`local/patches/base/P6-lived-block-size-512.patch`) fixes a different but
|
||||
related issue: the original `block_size()` returned `PAGE_SIZE` (4096), but RedoxFS reads
|
||||
in 512-byte chunks (`BLOCK_SIZE = 4096` but individual reads may be 512). The `DiskWrapper`
|
||||
in `driver-block` rejects misaligned reads. Changing to 512 fixes alignment but does NOT
|
||||
fix the size/out-of-bounds problem.
|
||||
|
||||
**Note:** The current source tree (`recipes/core/base/source/drivers/storage/lived/src/main.rs`)
|
||||
does NOT have the P6 patch applied — it still shows `PAGE_SIZE as u32`. The P6 patch is
|
||||
applied during `repo fetch base` and only exists durably in `local/patches/base/`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Fix Strategy
|
||||
|
||||
### 3.1 Design Principle
|
||||
|
||||
> Preload the minimum needed to boot the kernel + initfs. Once the OS is running, mount
|
||||
> the filesystem from the actual disk device, not from the RAM preload.
|
||||
|
||||
The bootloader already loads kernel + initfs from RedoxFS before switching to live mode.
|
||||
After that, the running OS has access to the AHCI driver (ahcid) and can mount the
|
||||
filesystem directly from the physical disk.
|
||||
|
||||
### 3.2 Two-Phase Approach
|
||||
|
||||
**Phase A: Bootloader Changes** (bootloader is UEFI code, runs before the OS)
|
||||
|
||||
1. **Reduce preload to the minimum needed for kernel + initfs discovery**
|
||||
- The bootloader needs to read the RedoxFS superblock + directory tree to find
|
||||
`usr/lib/boot/kernel` and `usr/lib/boot/initfs`. This requires reading the header,
|
||||
the root node, and walking directory entries.
|
||||
- Instead of preloading a fixed 1024 MiB, preload only what's needed to locate and
|
||||
read these two files. In practice, this is the first few MiB of the filesystem.
|
||||
- Fallback: if the filesystem is small enough (≤ 64 MiB?), preload everything.
|
||||
|
||||
2. **Pass the physical disk location to the kernel**
|
||||
- Set `DISK_PHYS_ADDR` and `DISK_PHYS_SIZE` env vars with the full disk geometry
|
||||
- Keep `DISK_LIVE_ADDR` / `DISK_LIVE_SIZE` for the minimal preload
|
||||
- Add `REDOXFS_FULL_SIZE` so the OS knows the true filesystem extent
|
||||
|
||||
**Phase B: lived Daemon Changes** (OS-level, patchable via `local/patches/base/`)
|
||||
|
||||
1. **Accept the full filesystem size as an additional env var**
|
||||
- Read `REDOXFS_FULL_SIZE` or derive from the RedoxFS header
|
||||
- Report `LiveDisk::size()` as the FULL filesystem size, not just the preload
|
||||
|
||||
2. **Fall through to the physical disk for reads beyond the preload**
|
||||
- When `read(block, buffer)` is called with an offset beyond `self.original.len()`:
|
||||
- Open the underlying block device (e.g., `/scheme/disk/0` after ahcid starts)
|
||||
- Read the data from the physical disk
|
||||
- Cache the result in the overlay HashMap
|
||||
- This makes lived act as a write-through cache: preload in RAM, fallback to disk
|
||||
|
||||
3. **Alternative simpler approach: bypass lived entirely for large images**
|
||||
- After ahcid starts and registers `/scheme/disk/0`, the init system could mount
|
||||
RedoxFS directly from `/scheme/disk/0` instead of `/scheme/disk.live/0`
|
||||
- The preload would only be used by the bootloader to load kernel + initfs
|
||||
- Once the OS boots, lived is unnecessary — mount from the real disk
|
||||
|
||||
---
|
||||
|
||||
## 4. Concrete Fix Plan
|
||||
|
||||
### 4.1 Fix 1: Reduce Bootloader Preload (bootloader patch)
|
||||
|
||||
**File:** `recipes/core/bootloader/source/src/main.rs`
|
||||
|
||||
**Current:**
|
||||
```rust
|
||||
let max_preload: u64 = 1024 * MIBI as u64;
|
||||
```
|
||||
|
||||
**Proposed change:**
|
||||
```rust
|
||||
// Only preload what the bootloader actually needs:
|
||||
// - RedoxFS header + allocator (first ~1 MiB)
|
||||
// - Root directory tree (typically first 32-64 MiB)
|
||||
// - kernel and initfs files (loaded separately after preload)
|
||||
// 64 MiB is generous for the metadata region of any reasonable filesystem.
|
||||
// The kernel and initfs are loaded separately via fs.disk.read_at() directly
|
||||
// from the physical disk, so they don't need to be in the preload.
|
||||
let max_preload: u64 = 64 * MIBI as u64;
|
||||
```
|
||||
|
||||
Wait — this doesn't work. The bootloader reads kernel and initfs from the RedoxFS
|
||||
filesystem using `load_to_memory(os, &mut fs, "usr/lib/boot/kernel", ...)`. After the
|
||||
preload, the bootloader has already switched the disk to the live buffer. So the kernel
|
||||
and initfs must be within the preload, OR the bootloader must load them before switching
|
||||
to live mode.
|
||||
|
||||
**Looking at the actual bootloader flow:**
|
||||
```
|
||||
1. Open RedoxFS from physical disk → fs
|
||||
2. Preload first N MiB into RAM buffer
|
||||
3. Set LIVE_OPT = Some((fs.block, buffer))
|
||||
4. Load kernel from fs (still using physical disk? or from buffer?)
|
||||
5. Load initfs from fs
|
||||
6. Pass LIVE_OPT to kernel env
|
||||
```
|
||||
|
||||
The live buffer is set in `LIVE_OPT` at line 625, but the kernel and initfs are loaded
|
||||
at lines 642-663, AFTER the live preload. The `load_to_memory` function uses `fs` which
|
||||
still uses the original disk handle. So the kernel and initfs are read from the physical
|
||||
disk, not from the live buffer.
|
||||
|
||||
**This means the preload doesn't need to include kernel or initfs at all.** The preload
|
||||
exists solely so that `lived` can serve the filesystem to the running OS via `scheme:disk.live`.
|
||||
|
||||
**Revised Fix 1:** Reduce max_preload to a small value (e.g., 4-64 MiB) that covers just
|
||||
the RedoxFS metadata needed for initial mount, then rely on the disk fallback for the rest.
|
||||
|
||||
BUT: this only works if `lived` can fall through to the physical disk for out-of-bounds
|
||||
reads. Without the fallback, reducing preload makes the problem worse.
|
||||
|
||||
### 4.2 Fix 2: lived Disk Fallback (base patch)
|
||||
|
||||
**File:** `recipes/core/base/source/drivers/storage/lived/src/main.rs`
|
||||
|
||||
This is the core fix. Make `lived` aware of the full filesystem and able to read from
|
||||
the physical disk when the preload doesn't cover the requested region.
|
||||
|
||||
**Design:**
|
||||
|
||||
```rust
|
||||
struct LiveDisk {
|
||||
// Preloaded RAM buffer (may be smaller than total filesystem)
|
||||
preload: &'static [u8],
|
||||
// Full filesystem size (from RedoxFS header or env var)
|
||||
total_size: u64,
|
||||
// Physical disk offset where the filesystem starts
|
||||
disk_block: u64,
|
||||
// Handle to the physical disk (opened after ahcid starts)
|
||||
disk_handle: Option<File>,
|
||||
// Write overlay (same as before)
|
||||
overlay: HashMap<u64, Box<[u8]>>,
|
||||
}
|
||||
|
||||
impl Disk for LiveDisk {
|
||||
fn block_size(&self) -> u32 { 512 }
|
||||
|
||||
fn size(&self) -> u64 { self.total_size }
|
||||
|
||||
async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
||||
let bs = self.block_size() as usize;
|
||||
let offset = (block as usize) * bs;
|
||||
|
||||
if offset + buffer.len() > self.total_size as usize {
|
||||
return Err(syscall::Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let preload_bytes = self.preload.len();
|
||||
|
||||
for (i, chunk) in buffer.chunks_mut(bs).enumerate() {
|
||||
let block_i = block + i as u64;
|
||||
let offset_i = offset + i * bs;
|
||||
|
||||
// Check overlay first
|
||||
if let Some(overlay) = self.overlay.get(&block_i) {
|
||||
chunk.copy_from_slice(&overlay[..chunk.len()]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if offset_i + chunk.len() <= preload_bytes {
|
||||
// Within preload → read from RAM
|
||||
chunk.copy_from_slice(&self.preload[offset_i..offset_i + chunk.len()]);
|
||||
} else {
|
||||
// Beyond preload → read from physical disk
|
||||
self.read_from_disk(block_i, chunk)?;
|
||||
}
|
||||
}
|
||||
Ok(buffer.len())
|
||||
}
|
||||
|
||||
fn read_from_disk(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<()> {
|
||||
// Try to open the physical disk if not already open
|
||||
if self.disk_handle.is_none() {
|
||||
// Try common disk scheme paths
|
||||
for path in &["/scheme/disk/0", "/scheme/disk/1"] {
|
||||
if let Ok(file) = OpenOptions::new().read(true).open(path) {
|
||||
self.disk_handle = Some(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut disk) = self.disk_handle {
|
||||
// Seek to the correct block (accounting for partition offset)
|
||||
let abs_block = self.disk_block + block;
|
||||
disk.read_at(buffer, abs_block * self.block_size() as u64)
|
||||
.map_err(|_| syscall::Error::new(EIO))?;
|
||||
Ok(())
|
||||
} else {
|
||||
// No disk available yet — return what we have from preload
|
||||
// (fill with zeros for regions not in preload)
|
||||
buffer.fill(0);
|
||||
Err(syscall::Error::new(EIO))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problem with this approach:** `lived` starts before `ahcid` (it's at priority 10 in
|
||||
init.initfs.d, while ahcid is at priority 40). So when lived first starts, there IS no
|
||||
`/scheme/disk/0` to fall back to. The disk fallback would only work after ahcid initializes.
|
||||
|
||||
### 4.3 Fix 3: Two-Stage Mount (Recommended)
|
||||
|
||||
The cleanest fix is to change the init sequence:
|
||||
|
||||
**Stage 1: lived serves the preloaded buffer (for early boot)**
|
||||
- lived starts as before, serves the preload via `scheme:disk.live`
|
||||
- RedoxFS does NOT mount from `disk.live` for the root filesystem
|
||||
- The initfs has everything needed for early boot (lived, ahcid, basic tools)
|
||||
|
||||
**Stage 2: Mount from physical disk (after drivers start)**
|
||||
- After `40_drivers.target` completes, ahcid has registered `/scheme/disk/0`
|
||||
- Init runs `redoxfs --uuid $REDOXFS_UUID file $REDOXFS_BLOCK` pointing to `/scheme/disk/0`
|
||||
- This reads the full filesystem from the physical disk
|
||||
|
||||
**Current init flow:**
|
||||
```
|
||||
10_lived.service → starts lived (scheme:disk.live)
|
||||
40_drivers.target → starts ahcid (scheme:disk/0)
|
||||
50_rootfs.service → redoxfs mounts from... whichever disk scheme it finds first
|
||||
(scans all /scheme/disk/* for matching UUID)
|
||||
```
|
||||
|
||||
**The issue is timing:** `50_rootfs.service` requires `40_drivers.target`, so it should
|
||||
wait for ahcid. But `redoxfs` scans ALL disk schemes, and `disk.live` matches first
|
||||
(since lived starts earlier). RedoxFS finds the UUID in `disk.live` and tries to mount
|
||||
from it, but the disk is too small.
|
||||
|
||||
**Proposed fix:**
|
||||
|
||||
1. **Make lived report the full filesystem size** by reading the RedoxFS header from
|
||||
the preload buffer to determine `total_size`. Report that as `size()`.
|
||||
|
||||
2. **Make lived fall through to disk reads** for out-of-bounds regions. Use a lazy-open
|
||||
approach: when a read goes beyond the preload and no disk handle is open yet, try
|
||||
to open `/scheme/disk/0`. If it fails, return EIO (which RedoxFS will retry).
|
||||
|
||||
3. **Reduce the preload** in the bootloader. Since lived now handles disk fallback,
|
||||
we can preload much less (e.g., 4-64 MiB). The preload just needs to cover the
|
||||
RedoxFS header and enough metadata for the initial mount.
|
||||
|
||||
---
|
||||
|
||||
## 5. Recommended Implementation Order
|
||||
|
||||
### Step 1: Fix lived to report full size + disk fallback (base patch)
|
||||
|
||||
Create `local/patches/base/P59-lived-disk-fallback.patch`:
|
||||
|
||||
1. Add `total_size: u64` field to LiveDisk
|
||||
2. Parse RedoxFS header from preload buffer to determine total filesystem size
|
||||
3. Report `total_size` from `size()` instead of `preload.len()`
|
||||
4. For reads beyond preload: attempt to open and read from `/scheme/disk/0`
|
||||
5. Keep overlay for writes
|
||||
6. Keep block_size = 512 (from P6)
|
||||
7. Add env var `DISK_PHYS_BLOCK` for the partition offset on the physical disk
|
||||
|
||||
### Step 2: Reduce bootloader preload cap (bootloader patch)
|
||||
|
||||
Create `local/patches/bootloader/P1-reduce-live-preload.patch`:
|
||||
|
||||
1. Change `max_preload` from 1024 MiB to a calculated minimum:
|
||||
- Read the RedoxFS header to determine filesystem size
|
||||
- Calculate the minimum preload needed: max(header + allocator extent, 4 MiB)
|
||||
- Cap at 128 MiB (generous upper bound for metadata region)
|
||||
2. Add `DISK_PHYS_BLOCK` env var so lived knows where the partition starts on disk
|
||||
|
||||
### Step 3: Verify
|
||||
|
||||
1. Build and test redbear-full ISO in QEMU with virtio-gpu
|
||||
2. Verify RedoxFS mounts the full 4093 MiB filesystem
|
||||
3. Verify login prompt appears
|
||||
4. Verify KDE desktop loads (or at minimum, the greeter starts)
|
||||
|
||||
---
|
||||
|
||||
## 6. Risk Assessment
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| RedoxFS header format changes between versions | lived parses header incorrectly | Use the same header parsing code as RedoxFS lib |
|
||||
| ahcid not started when lived first needs disk | Read fails with ENOENT | Retry with backoff; RedoxFS mount retries automatically |
|
||||
| Physical disk block offset wrong | Read corrupt data | Pass exact block offset from bootloader via env var |
|
||||
| Preload too small for RedoxFS to find header | Mount fails immediately | Keep minimum preload at 4 MiB (covers any superblock) |
|
||||
| Mini ISO regression | Small images broken | Test mini ISO after every change |
|
||||
|
||||
---
|
||||
|
||||
## 7. Alternative Approach: Mount From Physical Disk Directly
|
||||
|
||||
Instead of fixing lived, we could modify the init sequence to skip `disk.live` entirely
|
||||
for the root filesystem mount:
|
||||
|
||||
1. Bootloader preloads just enough for kernel + initfs (no change needed)
|
||||
2. lived starts but is only used for early boot I/O
|
||||
3. `50_rootfs.service` is changed to explicitly mount from `/scheme/disk/0` (via ahcid)
|
||||
instead of scanning all disk schemes
|
||||
4. This requires passing the disk path and block offset from bootloader to init
|
||||
|
||||
**Pros:** Simpler lived (no disk fallback), cleaner architecture
|
||||
**Cons:** Requires knowing which disk scheme serves the boot device; may not work if
|
||||
the AHCI driver assigns a different number to the boot disk
|
||||
|
||||
**Verdict:** Fix 3 (lived with disk fallback) is more robust because it works regardless
|
||||
of which disk scheme is assigned. The lived approach acts as a transparent cache layer.
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation Notes
|
||||
|
||||
### Bootloader env vars (current)
|
||||
|
||||
```
|
||||
DISK_LIVE_ADDR=<hex phys addr of preload buffer>
|
||||
DISK_LIVE_SIZE=<hex size of preload buffer>
|
||||
REDOXFS_BLOCK=0 (always 0 for live mode)
|
||||
REDOXFS_UUID=<uuid>
|
||||
```
|
||||
|
||||
### Bootloader env vars (proposed additions)
|
||||
|
||||
```
|
||||
DISK_PHYS_BLOCK=<hex block offset of partition on physical disk>
|
||||
REDOXFS_FULL_SIZE=<hex total filesystem size>
|
||||
```
|
||||
|
||||
### lived env vars (current)
|
||||
|
||||
```
|
||||
DISK_LIVE_ADDR → phys addr to mmap
|
||||
DISK_LIVE_SIZE → size to mmap (= preload size, NOT total filesystem size)
|
||||
```
|
||||
|
||||
### lived env vars (proposed)
|
||||
|
||||
```
|
||||
DISK_LIVE_ADDR → phys addr of preload buffer
|
||||
DISK_LIVE_SIZE → size of preload buffer
|
||||
DISK_PHYS_BLOCK → block offset for disk fallback reads
|
||||
REDOXFS_FULL_SIZE → total filesystem size (for size() reporting)
|
||||
```
|
||||
@@ -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
|
||||
index 82120c21..50faead5 100644
|
||||
--- a/bootstrap/Cargo.toml
|
||||
+++ b/bootstrap/Cargo.toml
|
||||
@@ -7,5 +7,3 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
-[workspace]
|
||||
@@ -8 +7,0 @@ license = "MIT"
|
||||
-
|
||||
[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
|
||||
--- a/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::scheme::{SchemeAsync, SchemeSync};
|
||||
|
||||
@@ -10,7 +10,6 @@ index 9f507221..c69c2cfa 100644
|
||||
- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
|
||||
+unsafe fn get_fd(var: &str) -> Option<RawFd> {
|
||||
+ let fd: RawFd = match std::env::var(var)
|
||||
+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
|
||||
+ .ok()
|
||||
+ .and_then(|val| {
|
||||
+ val.parse()
|
||||
@@ -33,7 +32,7 @@ index 9f507221..c69c2cfa 100644
|
||||
}
|
||||
|
||||
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.
|
||||
#[must_use = "Daemon::ready must be called"]
|
||||
pub struct Daemon {
|
||||
@@ -63,7 +62,7 @@ index 9f507221..c69c2cfa 100644
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[must_use = "SchemeDaemon::ready must be called"]
|
||||
pub struct SchemeDaemon {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
diff --git a/init.initfs.d/50_rootfs.service b/init.initfs.d/50_rootfs.service
|
||||
index db7ba429..59f2c61c 100644
|
||||
--- a/init.initfs.d/50_rootfs.service
|
||||
+++ b/init.initfs.d/50_rootfs.service
|
||||
@@ -7 +7 @@ cmd = "redoxfs"
|
||||
-args = ["--uuid" ,"$REDOXFS_UUID", "file", "$REDOXFS_BLOCK"]
|
||||
+args = ["--uuid" ,"$REDOXFS_UUID", "file"]
|
||||
@@ -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,248 @@
|
||||
diff --git a/drivers/storage/lived/src/main.rs b/drivers/storage/lived/src/main.rs
|
||||
index 2ca1ff27..8582e42a 100644
|
||||
--- a/drivers/storage/lived/src/main.rs
|
||||
+++ b/drivers/storage/lived/src/main.rs
|
||||
@@ -1,0 +2,8 @@
|
||||
+//!
|
||||
+//! For live ISO boot: bootloader preloads the first N MiB of the filesystem into RAM.
|
||||
+//! This daemon serves that preloaded region from RAM, and falls through to the physical
|
||||
+//! disk (USB, AHCI, NVMe) for reads beyond the preload boundary.
|
||||
+//!
|
||||
+//! Boot order: lived(10) → drivers(40) → usbscsid(45) → rootfs(50)
|
||||
+//! Since drivers load AFTER lived starts, the disk fallback is lazy: the physical disk
|
||||
+//! handle is opened on the first out-of-range read, with retry backoff.
|
||||
@@ -4,0 +13 @@
|
||||
+use std::cell::RefCell;
|
||||
@@ -7 +15,0 @@ use std::fs::File;
|
||||
-
|
||||
@@ -8,0 +17 @@ use std::os::fd::AsRawFd;
|
||||
+use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -19,0 +29,11 @@ use anyhow::{anyhow, Context};
|
||||
+/// Block size 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.
|
||||
+const BLOCK_SIZE: usize = 512;
|
||||
+
|
||||
+/// Maximum retries for opening the physical disk before giving up.
|
||||
+/// Drivers (xhcid → usbscsid → /scheme/disk/) load between service 40-45,
|
||||
+/// while lived starts at 10. Give plenty of retries.
|
||||
+const DISK_OPEN_MAX_RETRIES: u32 = 60;
|
||||
+const DISK_OPEN_RETRY_INTERVAL_MS: u64 = 500;
|
||||
+
|
||||
@@ -22 +41,0 @@ struct LiveDisk {
|
||||
- //TODO: drop overlay blocks if they match the original
|
||||
@@ -23,0 +43,4 @@ struct LiveDisk {
|
||||
+ full_size: u64,
|
||||
+ disk_phys_offset: u64,
|
||||
+ disk_file: RefCell<Option<File>>,
|
||||
+ logged_first_fallback: AtomicBool,
|
||||
@@ -27 +50,6 @@ impl LiveDisk {
|
||||
- fn new(phys: usize, size: usize) -> anyhow::Result<LiveDisk> {
|
||||
+ fn new(
|
||||
+ phys: usize,
|
||||
+ preload_size: usize,
|
||||
+ full_size: u64,
|
||||
+ disk_phys_offset: u64,
|
||||
+ ) -> anyhow::Result<LiveDisk> {
|
||||
@@ -30,2 +58,2 @@ impl LiveDisk {
|
||||
- .checked_add(size)
|
||||
- .context("phys + size overflow")?
|
||||
+ .checked_add(preload_size)
|
||||
+ .context("phys + preload_size overflow")?
|
||||
@@ -33 +61 @@ impl LiveDisk {
|
||||
- let size = end - start;
|
||||
+ let mapped_size = end - start;
|
||||
@@ -41 +69 @@ impl LiveDisk {
|
||||
- length: size,
|
||||
+ length: mapped_size,
|
||||
@@ -47 +75 @@ impl LiveDisk {
|
||||
- std::slice::from_raw_parts_mut(base as *mut u8, size)
|
||||
+ std::slice::from_raw_parts_mut(base as *mut u8, mapped_size)
|
||||
@@ -49,0 +78,7 @@ impl LiveDisk {
|
||||
+ eprintln!(
|
||||
+ "lived: preload {} MiB, full filesystem {} MiB, disk_phys_offset {:#x}",
|
||||
+ preload_size / (1024 * 1024),
|
||||
+ full_size / (1024 * 1024),
|
||||
+ disk_phys_offset,
|
||||
+ );
|
||||
+
|
||||
@@ -52,0 +88,4 @@ impl LiveDisk {
|
||||
+ full_size,
|
||||
+ disk_phys_offset,
|
||||
+ disk_file: RefCell::new(None),
|
||||
+ logged_first_fallback: AtomicBool::new(false),
|
||||
@@ -54,0 +94,69 @@ impl LiveDisk {
|
||||
+
|
||||
+ fn open_disk(&self) -> syscall::Result<()> {
|
||||
+ if self.disk_file.borrow().is_some() {
|
||||
+ return Ok(());
|
||||
+ }
|
||||
+ match self.try_open_disk() {
|
||||
+ Ok(file) => {
|
||||
+ *self.disk_file.borrow_mut() = Some(file);
|
||||
+ Ok(())
|
||||
+ }
|
||||
+ Err(msg) => {
|
||||
+ eprintln!("lived: disk fallback unavailable: {}", msg);
|
||||
+ Err(syscall::Error::new(EIO))
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ fn try_open_disk(&self) -> Result<File, String> {
|
||||
+ // Try common disk scheme paths. USB boot appears as /scheme/disk/0 or /scheme/usbscsi/0.
|
||||
+ // AHCI boot appears as /scheme/disk/0. NVMe appears as /scheme/disk/0.
|
||||
+ let candidates = [
|
||||
+ "/scheme/disk/0",
|
||||
+ "/scheme/usbscsi/0",
|
||||
+ "/scheme/disk/1",
|
||||
+ "/scheme/usbscsi/1",
|
||||
+ ];
|
||||
+
|
||||
+ for attempt in 0..DISK_OPEN_MAX_RETRIES {
|
||||
+ for path in &candidates {
|
||||
+ if let Ok(file) = File::open(path) {
|
||||
+ eprintln!("lived: opened physical disk at {} (attempt {})", path, attempt + 1);
|
||||
+ return Ok(file);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if attempt < DISK_OPEN_MAX_RETRIES - 1 {
|
||||
+ std::thread::sleep(std::time::Duration::from_millis(DISK_OPEN_RETRY_INTERVAL_MS));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ Err(format!(
|
||||
+ "no /scheme/disk/ found after {} retries",
|
||||
+ DISK_OPEN_MAX_RETRIES
|
||||
+ ))
|
||||
+ }
|
||||
+
|
||||
+ fn read_from_disk(&self, offset: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
|
||||
+ let disk_offset = self.disk_phys_offset + offset;
|
||||
+
|
||||
+ if !self.logged_first_fallback.swap(true, Ordering::Relaxed) {
|
||||
+ eprintln!(
|
||||
+ "lived: first disk fallback read at offset {} MiB (disk offset {:#x})",
|
||||
+ offset / (1024 * 1024),
|
||||
+ disk_offset,
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ self.open_disk()?;
|
||||
+
|
||||
+ use std::io::{Read, Seek, SeekFrom};
|
||||
+ let mut disk = self.disk_file.borrow_mut();
|
||||
+ let file = disk.as_mut().unwrap();
|
||||
+ file.seek(SeekFrom::Start(disk_offset))
|
||||
+ .map_err(|_| syscall::Error::new(EIO))?;
|
||||
+ file.read_exact(buffer)
|
||||
+ .map_err(|_| syscall::Error::new(EIO))?;
|
||||
+
|
||||
+ Ok(buffer.len())
|
||||
+ }
|
||||
@@ -59 +167 @@ impl Disk for LiveDisk {
|
||||
- PAGE_SIZE as u32
|
||||
+ BLOCK_SIZE as u32
|
||||
@@ -63 +171 @@ impl Disk for LiveDisk {
|
||||
- self.original.len() as u64
|
||||
+ self.full_size
|
||||
@@ -67,2 +175,4 @@ impl Disk for LiveDisk {
|
||||
- let mut offset = (block as usize) * PAGE_SIZE;
|
||||
- if offset + buffer.len() > self.original.len() {
|
||||
+ let bs = self.block_size() as usize;
|
||||
+ let mut offset = (block as usize) * bs;
|
||||
+
|
||||
+ if offset + buffer.len() > self.full_size as usize {
|
||||
@@ -71 +181,28 @@ impl Disk for LiveDisk {
|
||||
- for chunk in buffer.chunks_mut(PAGE_SIZE) {
|
||||
+
|
||||
+ let preload_len = self.original.len();
|
||||
+
|
||||
+ if offset + buffer.len() <= preload_len {
|
||||
+ for chunk in buffer.chunks_mut(bs) {
|
||||
+ match self.overlay.get(&block) {
|
||||
+ Some(overlay) => {
|
||||
+ chunk.copy_from_slice(&overlay[..chunk.len()]);
|
||||
+ }
|
||||
+ None => {
|
||||
+ chunk.copy_from_slice(&self.original[offset..offset + chunk.len()]);
|
||||
+ }
|
||||
+ }
|
||||
+ block += 1;
|
||||
+ offset += bs;
|
||||
+ }
|
||||
+ return Ok(buffer.len());
|
||||
+ }
|
||||
+
|
||||
+ if offset >= preload_len {
|
||||
+ let fs_byte_offset = (block as u64) * bs as u64;
|
||||
+ return self.read_from_disk(fs_byte_offset, buffer);
|
||||
+ }
|
||||
+
|
||||
+ let preload_remaining = preload_len - offset;
|
||||
+ let (ram_part, disk_part) = buffer.split_at_mut(preload_remaining);
|
||||
+
|
||||
+ for chunk in ram_part.chunks_mut(bs) {
|
||||
@@ -81 +218 @@ impl Disk for LiveDisk {
|
||||
- offset += PAGE_SIZE;
|
||||
+ offset += bs;
|
||||
@@ -82,0 +220,4 @@ impl Disk for LiveDisk {
|
||||
+
|
||||
+ let disk_fs_offset = (block as u64) * bs as u64;
|
||||
+ self.read_from_disk(disk_fs_offset, disk_part)?;
|
||||
+
|
||||
@@ -87,2 +228,3 @@ impl Disk for LiveDisk {
|
||||
- let mut offset = (block as usize) * PAGE_SIZE;
|
||||
- if offset + buffer.len() > self.original.len() {
|
||||
+ let bs = self.block_size() as usize;
|
||||
+ let mut offset = (block as usize) * bs;
|
||||
+ if offset + buffer.len() > self.full_size as usize {
|
||||
@@ -91 +233 @@ impl Disk for LiveDisk {
|
||||
- for chunk in buffer.chunks(PAGE_SIZE) {
|
||||
+ for chunk in buffer.chunks(bs) {
|
||||
@@ -93,2 +235,3 @@ impl Disk for LiveDisk {
|
||||
- let offset = (block as usize) * PAGE_SIZE;
|
||||
- self.original[offset..offset + PAGE_SIZE]
|
||||
+ let off = (block as usize) * bs;
|
||||
+ if off + bs <= self.original.len() {
|
||||
+ self.original[off..off + bs]
|
||||
@@ -96,0 +240,3 @@ impl Disk for LiveDisk {
|
||||
+ } else {
|
||||
+ vec![0u8; bs].into_boxed_slice()
|
||||
+ }
|
||||
@@ -100 +246 @@ impl Disk for LiveDisk {
|
||||
- offset += PAGE_SIZE;
|
||||
+ offset += bs;
|
||||
@@ -112 +258,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
- let mut size = 0;
|
||||
+ let mut preload_size = 0;
|
||||
+ let mut full_size = 0u64;
|
||||
+ let mut disk_phys_offset = 0u64;
|
||||
@@ -129 +277 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
- size = usize::from_str_radix(value, 16).unwrap_or(0);
|
||||
+ preload_size = usize::from_str_radix(value, 16).unwrap_or(0);
|
||||
@@ -130,0 +279,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
+
|
||||
+ if name == "REDOXFS_FULL_SIZE" {
|
||||
+ full_size = u64::from_str_radix(value, 16).unwrap_or(0);
|
||||
@@ -133,2 +284,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
- if phys == 0 || size == 0 {
|
||||
- // No live disk data, no need to say anything or exit with
|
||||
+ if name == "DISK_PHYS_BLOCK" {
|
||||
+ disk_phys_offset = u64::from_str_radix(value, 16).unwrap_or(0) * BLOCK_SIZE as u64;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if phys == 0 || preload_size == 0 {
|
||||
@@ -138,0 +294,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
+ if full_size == 0 {
|
||||
+ full_size = preload_size as u64;
|
||||
+ }
|
||||
+
|
||||
@@ -152 +311 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
- LiveDisk::new(phys, size).unwrap_or_else(|err| {
|
||||
+ LiveDisk::new(phys, preload_size, full_size, disk_phys_offset).unwrap_or_else(|err| {
|
||||
@@ -160 +319 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
- libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace");
|
||||
+ libredox::call::setrens(0, 0).expect("lived: failed to enter null namespace");
|
||||
@@ -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,118 @@
|
||||
--- a/drivers/storage/lived/src/main.rs
|
||||
+++ b/drivers/storage/lived/src/main.rs
|
||||
@@ -45 +45 @@
|
||||
- disk_file: RefCell<Option<File>>,
|
||||
+ disk_path: RefCell<Option<String>>,
|
||||
@@ -90 +90 @@
|
||||
- disk_file: RefCell::new(None),
|
||||
+ disk_path: RefCell::new(None),
|
||||
@@ -96 +96 @@
|
||||
- if self.disk_file.borrow().is_some() {
|
||||
+ if self.disk_path.borrow().is_some() {
|
||||
@@ -100,2 +100,2 @@
|
||||
- Ok(file) => {
|
||||
- *self.disk_file.borrow_mut() = Some(file);
|
||||
+ Ok(path) => {
|
||||
+ *self.disk_path.borrow_mut() = Some(path);
|
||||
@@ -111,10 +111 @@
|
||||
- fn try_open_disk(&self) -> Result<File, String> {
|
||||
- // Try common disk scheme paths. USB boot appears as /scheme/disk/0 or /scheme/usbscsi/0.
|
||||
- // AHCI boot appears as /scheme/disk/0. NVMe appears as /scheme/disk/0.
|
||||
- let candidates = [
|
||||
- "/scheme/disk/0",
|
||||
- "/scheme/usbscsi/0",
|
||||
- "/scheme/disk/1",
|
||||
- "/scheme/usbscsi/1",
|
||||
- ];
|
||||
-
|
||||
+ fn try_open_disk(&self) -> Result<String, String> {
|
||||
@@ -122,4 +113,70 @@
|
||||
- for path in &candidates {
|
||||
- if let Ok(file) = File::open(path) {
|
||||
- eprintln!("lived: opened physical disk at {} (attempt {})", path, attempt + 1);
|
||||
- return Ok(file);
|
||||
+ if let Ok(entries) = std::fs::read_dir("/scheme") {
|
||||
+ let all_schemes: Vec<String> = entries
|
||||
+ .flatten()
|
||||
+ .map(|e| e.file_name().to_string_lossy().to_string())
|
||||
+ .collect();
|
||||
+ let has_disk = all_schemes
|
||||
+ .iter()
|
||||
+ .any(|s| s.starts_with("disk.") && s != "disk.live");
|
||||
+ if attempt == 0 || attempt == DISK_OPEN_MAX_RETRIES - 1 || has_disk {
|
||||
+ eprintln!(
|
||||
+ "lived: attempt {} /scheme/ = {:?} (has_disk={})",
|
||||
+ attempt + 1,
|
||||
+ all_schemes,
|
||||
+ has_disk
|
||||
+ );
|
||||
+ }
|
||||
+ for name_str in &all_schemes {
|
||||
+ if name_str.starts_with("disk.") && name_str != "disk.live" {
|
||||
+ for idx in [0u32, 2, 1, 3, 4, 5] {
|
||||
+ let path = format!("/scheme/{}/{}", name_str, idx);
|
||||
+ if let Ok(mut file) = File::open(&path) {
|
||||
+ use std::io::{Read, Seek, SeekFrom};
|
||||
+ let test_offset = self.disk_phys_offset + self.full_size - 4096;
|
||||
+ let mut probe = vec![0u8; 4096];
|
||||
+ if file.seek(SeekFrom::Start(test_offset)).is_err() {
|
||||
+ eprintln!(
|
||||
+ "lived: skipping {} — seek to {:#x} failed",
|
||||
+ path, test_offset
|
||||
+ );
|
||||
+ continue;
|
||||
+ }
|
||||
+ match file.read_exact(&mut probe) {
|
||||
+ Ok(()) => {
|
||||
+ eprintln!(
|
||||
+ "lived: validated physical disk at {} (attempt {}, verified at end {:#x})",
|
||||
+ path,
|
||||
+ attempt + 1,
|
||||
+ test_offset
|
||||
+ );
|
||||
+ return Ok(path);
|
||||
+ }
|
||||
+ Err(e) => {
|
||||
+ eprintln!(
|
||||
+ "lived: skipping {} — read at {:#x}: {}",
|
||||
+ path, test_offset, e
|
||||
+ );
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+ } else if attempt == 0 {
|
||||
+ eprintln!("lived: {} not openable", path);
|
||||
+ }
|
||||
+ }
|
||||
+ let path_root = format!("/scheme/{}", name_str);
|
||||
+ if let Ok(mut file) = File::open(&path_root) {
|
||||
+ use std::io::{Read, Seek, SeekFrom};
|
||||
+ let test_offset = self.disk_phys_offset + self.full_size - 4096;
|
||||
+ let mut probe = vec![0u8; 4096];
|
||||
+ if file.seek(SeekFrom::Start(test_offset)).is_ok()
|
||||
+ && file.read_exact(&mut probe).is_ok()
|
||||
+ {
|
||||
+ eprintln!(
|
||||
+ "lived: validated physical disk at {} (attempt {})",
|
||||
+ path_root,
|
||||
+ attempt + 1
|
||||
+ );
|
||||
+ return Ok(path_root);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
@@ -152,0 +210,4 @@
|
||||
+ let path = self.disk_path.borrow();
|
||||
+ let disk_path = path.as_ref().unwrap();
|
||||
+ let mut file = File::open(disk_path).map_err(|_| syscall::Error::new(EIO))?;
|
||||
+
|
||||
@@ -154,2 +214,0 @@
|
||||
- let mut disk = self.disk_file.borrow_mut();
|
||||
- let file = disk.as_mut().unwrap();
|
||||
@@ -319,2 +378,4 @@
|
||||
- libredox::call::setrens(0, 0).expect("lived: failed to enter null namespace");
|
||||
-
|
||||
+ // Lived must NOT call setrens(0, 0). The null namespace only contains
|
||||
+ // "memory" and "pipe" (see relibc redox_setrens_v1). Lived needs
|
||||
+ // ongoing access to /scheme/ for disk scheme discovery after storage
|
||||
+ // drivers register their schemes asynchronously.
|
||||
@@ -0,0 +1,202 @@
|
||||
diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs
|
||||
index 070de3d6..f5c1549a 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;
|
||||
@@ -6,0 +8 @@ use std::sync::mpsc::{self, Sender};
|
||||
+use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@@ -13,0 +16,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 +38 @@ enum OutputCmd {
|
||||
- Log(Vec<u8>),
|
||||
+ Log { context: String, line: Vec<u8> },
|
||||
@@ -34,0 +42,96 @@ enum OutputCmd {
|
||||
+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,
|
||||
+ 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(())
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
@@ -43,0 +147,13 @@ impl<'sock> LogScheme<'sock> {
|
||||
+ let _ = std::fs::create_dir_all("/var/log");
|
||||
+ let persistent_log: Option<File> = OpenOptions::new()
|
||||
+ .create(true)
|
||||
+ .append(true)
|
||||
+ .open("/var/log/system.log")
|
||||
+ .ok();
|
||||
+ let mut service_logs: HashMap<String, LogFile> = HashMap::new();
|
||||
+
|
||||
+ let _ = std::fs::create_dir_all(LOG_DIR);
|
||||
+
|
||||
+
|
||||
+ let json_format = std::env::var("LOGD_JSON").map_or(false, |v| v == "1");
|
||||
+
|
||||
@@ -48,0 +165,5 @@ impl<'sock> LogScheme<'sock> {
|
||||
+ let mut persistent = persistent_log;
|
||||
+ if let Some(ref mut f) = persistent {
|
||||
+ let _ = f.write(b"--- logd started ---
|
||||
+");
|
||||
+ }
|
||||
@@ -51 +172,32 @@ impl<'sock> LogScheme<'sock> {
|
||||
- OutputCmd::Log(line) => {
|
||||
+ 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(&out_line);
|
||||
+ let _ = f.flush();
|
||||
+ }
|
||||
+
|
||||
+ 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(&out_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(&out_line);
|
||||
+ let _ = system_entry.maybe_rotate();
|
||||
+
|
||||
@@ -53 +205 @@ impl<'sock> LogScheme<'sock> {
|
||||
- let _ = file.write(&line);
|
||||
+ let _ = file.write(&out_line);
|
||||
@@ -56,3 +208,2 @@ impl<'sock> LogScheme<'sock> {
|
||||
- logs.push_back(line);
|
||||
- // Keep a limited amount of logs for backfilling to bound memory usage
|
||||
- while logs.len() > 1000 {
|
||||
+ logs.push_back(out_line);
|
||||
+ while logs.len() > MEMORY_LOG_LIMIT {
|
||||
@@ -68 +218,0 @@ impl<'sock> LogScheme<'sock> {
|
||||
-
|
||||
@@ -83 +232,0 @@ impl<'sock> LogScheme<'sock> {
|
||||
- // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue
|
||||
@@ -118 +266,0 @@ impl<'sock> LogScheme<'sock> {
|
||||
- // Writing to the kernel debug log never blocks
|
||||
@@ -124 +272,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 +323,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> {
|
||||
-
|
||||
- // TODO
|
||||
-
|
||||
@@ -244,3 +391,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> {
|
||||
-
|
||||
- //TODO: flush remaining data?
|
||||
-
|
||||
@@ -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,48 @@
|
||||
diff --git a/src/main.rs b/src/main.rs
|
||||
index 78dabb0..a41086e 100644
|
||||
--- a/src/main.rs
|
||||
+++ b/src/main.rs
|
||||
@@ -557,0 +558 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
+ let disk_block = fs.block;
|
||||
@@ -559 +560,13 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
- print!("live: 0/{} MiB", size / MIBI as u64);
|
||||
+ let max_preload: u64 = 128 * MIBI as u64;
|
||||
+ let preload_size = if size > max_preload {
|
||||
+ println!(
|
||||
+ "live: filesystem is {} MiB, capping preload at {} MiB",
|
||||
+ size / MIBI as u64,
|
||||
+ max_preload / MIBI as u64
|
||||
+ );
|
||||
+ max_preload
|
||||
+ } else {
|
||||
+ size
|
||||
+ };
|
||||
+
|
||||
+ print!("live: 0/{} MiB", preload_size / MIBI as u64);
|
||||
@@ -561 +574 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
- let live_size = match usize::try_from(size) {
|
||||
+ let live_size = match usize::try_from(preload_size) {
|
||||
@@ -593 +606 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
- print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
|
||||
+ print!("\rlive: {}/{} MiB", i / MIBI as u64, preload_size / MIBI as u64);
|
||||
@@ -600 +613,10 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
- println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
|
||||
+ println!("\rlive: {}/{} MiB", i / MIBI as u64, preload_size / MIBI as u64);
|
||||
+
|
||||
+ if preload_size < size {
|
||||
+ println!(
|
||||
+ "live: preloaded {} MiB of {} MiB filesystem (remaining {} MiB from disk)",
|
||||
+ preload_size / MIBI as u64,
|
||||
+ size / MIBI as u64,
|
||||
+ (size - preload_size) / MIBI as u64
|
||||
+ );
|
||||
+ }
|
||||
@@ -613 +635 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
- Some(live)
|
||||
+ Some((disk_block, size, live))
|
||||
@@ -672 +694 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
- if let Some(live) = live_opt {
|
||||
+ if let Some((disk_block, fs_size, live)) = live_opt {
|
||||
@@ -674,0 +697,2 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
|
||||
+ writeln!(w, "DISK_PHYS_BLOCK={:016x}", disk_block).unwrap();
|
||||
+ writeln!(w, "REDOXFS_FULL_SIZE={:016x}", fs_size).unwrap();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user