Compare commits
	
		
			1712 Commits
		
	
	
		
			embassy-sy
			...
			boot-hil
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9010bfe79b | ||
|  | 2a437d31c4 | ||
|  | 2d78aa763a | ||
|  | 1133cbf90e | ||
|  | 77c357e744 | ||
|  | 521970535e | ||
|  | 93d4cfe7c1 | ||
|  | 8413a89752 | ||
|  | db717d9c81 | ||
|  | 808fa9dce6 | ||
|  | ceb13def19 | ||
|  | 5cf494113f | ||
|  | 8edb7bb012 | ||
|  | 8900f5f52b | ||
|  | 8201979d71 | ||
|  | 2d9f50addc | ||
|  | 18da91e252 | ||
|  | 28e2570533 | ||
|  | 26e0823c35 | ||
|  | ceca8b4336 | ||
|  | e308286f3c | ||
|  | 268da2fcde | ||
|  | a992d4895d | ||
|  | 1bae34d37c | ||
|  | aac42e3209 | ||
|  | 08415e001e | ||
|  | d6a1b567ee | ||
|  | a47fb42962 | ||
|  | 70a4a193c5 | ||
|  | 2132afb48b | ||
|  | 0e9131fd14 | ||
|  | 40a18b075d | ||
|  | f17f09057d | ||
|  | 11a78fb1e4 | ||
|  | 48154e18bf | ||
|  | cf2d4eca7c | ||
|  | 3e0b752bef | ||
|  | 6070d61d8c | ||
|  | a4f8d82ef5 | ||
|  | c27b0296fe | ||
|  | 336ae54a56 | ||
|  | d6a1118406 | ||
|  | 9de08d56a0 | ||
|  | 26740bb3ef | ||
|  | 4550452f43 | ||
|  | 08410432b5 | ||
|  | 3cf3caa3ab | ||
|  | c21ad04c2e | ||
|  | d097c99719 | ||
|  | af7c93abba | ||
|  | c356585a59 | ||
|  | 0d3ff34d80 | ||
|  | bb2d6c8542 | ||
|  | a05afc5426 | ||
|  | 50116e5b27 | ||
|  | a80acf686e | ||
|  | 6770d8e8a6 | ||
|  | 7307098780 | ||
|  | f4601af2a4 | ||
|  | fd22f4fac5 | ||
|  | 7622d2eb61 | ||
|  | 7573160077 | ||
|  | 7e18e5e0c1 | ||
|  | 9d76a6e933 | ||
|  | f502271940 | ||
|  | e2f8bf19ea | ||
|  | 1180e1770d | ||
|  | 49ba9c3da2 | ||
|  | ce662766be | ||
|  | a03b6be693 | ||
|  | 87d3086533 | ||
|  | d19e1c1dd1 | ||
|  | 8a55adbfd8 | ||
|  | 6dc56d2b35 | ||
|  | 394503ab69 | ||
|  | bfb4cf775a | ||
|  | 274f63a879 | ||
|  | 615882ebd6 | ||
|  | 6e38b07642 | ||
|  | 1eb03dc41a | ||
|  | a2656f402b | ||
|  | 3466c9cfa9 | ||
|  | 3295ec94e5 | ||
|  | c5732ce285 | ||
|  | 2e6f4237f2 | ||
|  | ac11635b0b | ||
|  | 9dd58660c3 | ||
|  | 9baa3bafb0 | ||
|  | 360286e67c | ||
|  | 0c66636d00 | ||
|  | 9d8c527308 | ||
|  | 1d87ec9cc3 | ||
|  | b74645e259 | ||
|  | 56351cedcb | ||
|  | 0823d9dc93 | ||
|  | c10fb7c1c4 | ||
|  | a2ce3aa1a1 | ||
|  | 3769447382 | ||
|  | 2c6b428ed4 | ||
|  | 83a976cf4b | ||
|  | a76ff53fb3 | ||
|  | f69e8459c9 | ||
|  | 891f1758bc | ||
|  | ae174fd0e0 | ||
|  | 5c936d33d4 | ||
|  | 36ec9bcc1d | ||
|  | 5adc80f6d9 | ||
|  | 98f55fa54d | ||
|  | 416ecc73d8 | ||
|  | 27dfced285 | ||
|  | 21681d8b4e | ||
|  | 989c98f316 | ||
|  | fdb2c4946a | ||
|  | 5e613d9abb | ||
|  | 40b576584e | ||
|  | aa7752020e | ||
|  | 6c165f8dc0 | ||
|  | 975f2f23c0 | ||
|  | 0eeefd3dbf | ||
|  | a4d78a6552 | ||
|  | f503417f4c | ||
|  | 6b8b145266 | ||
|  | e07f943562 | ||
|  | 70a5221b2e | ||
|  | b315c28d4e | ||
|  | e025693914 | ||
|  | 05ee02b593 | ||
|  | 4098a61ef0 | ||
|  | 1db00f5439 | ||
|  | 7fc17bc150 | ||
|  | 6e616a6fe6 | ||
|  | d02886786e | ||
|  | 2db4d01198 | ||
|  | a36ee75d19 | ||
|  | 1f63bf4153 | ||
|  | fd739250ea | ||
|  | 71c4e7e4d2 | ||
|  | 2c80784fe6 | ||
|  | 538001a4bc | ||
|  | e981cd4968 | ||
|  | 91bb3aae3f | ||
|  | e08dbcd027 | ||
|  | 5c27265a21 | ||
|  | 2c36199dea | ||
|  | 13a0be6289 | ||
|  | 9f928010a8 | ||
|  | 88146eb53e | ||
|  | 326e78757b | ||
|  | f77a7fe4bf | ||
|  | cbc92dce05 | ||
|  | 531f51d0eb | ||
|  | f28ab18d7b | ||
|  | 3bf6081eb5 | ||
|  | fb942e6675 | ||
|  | 10ea068027 | ||
|  | 4caa8497fc | ||
|  | 48085939e7 | ||
|  | 7f7256050c | ||
|  | 4b6538c8a8 | ||
|  | db6f9afa2e | ||
|  | 59a5e84df5 | ||
|  | 13f0501673 | ||
|  | 94de1a5353 | ||
|  | db71887817 | ||
|  | 1e430f7413 | ||
|  | 2897670f24 | ||
|  | ca738d6c99 | ||
|  | d33246b072 | ||
|  | 54e2e17520 | ||
|  | 3023e70ccf | ||
|  | f8299d10f7 | ||
|  | 8339423a2f | ||
|  | 295542f4d3 | ||
|  | d812cc5745 | ||
|  | 623f37a273 | ||
|  | a026db3f57 | ||
|  | c2d601abef | ||
|  | 2303382dfd | ||
|  | aacf14b62a | ||
|  | 100200d021 | ||
|  | b5748524f8 | ||
|  | 6d3377e6a6 | ||
|  | cc8961034e | ||
|  | cda4047310 | ||
|  | 2a6b743b9e | ||
|  | ecc305bbfe | ||
|  | 9c94eac919 | ||
|  | 00aadf6085 | ||
|  | 83f224e140 | ||
|  | e987259716 | ||
|  | fc04d2a33c | ||
|  | 1d4d11ba25 | ||
|  | 5f5e3bcd18 | ||
|  | 4b6045d446 | ||
|  | ca588f901f | ||
|  | 681165e84c | ||
|  | 0a7c061ddc | ||
|  | 1ded213ae9 | ||
|  | 0e9524de62 | ||
|  | e082cd0cda | ||
|  | 10bf33dcac | ||
|  | 46ddf7013a | ||
|  | 577d644e22 | ||
|  | 2cf6a59114 | ||
|  | e19f7d9a76 | ||
|  | bed1f07c15 | ||
|  | a2c718f61c | ||
|  | 0568738f77 | ||
|  | a6d22e199a | ||
|  | 1ea4c58c39 | ||
|  | faab2d0d53 | ||
|  | 6d35bcc3d9 | ||
|  | 7d6edd7b15 | ||
|  | 9e3266b745 | ||
|  | 7bff2ebab3 | ||
|  | 048bdf6968 | ||
|  | b3212ae383 | ||
|  | b436aad2a0 | ||
|  | 31c5bdfa29 | ||
|  | 906ab06a6e | ||
|  | a5484cd119 | ||
|  | c39671266e | ||
|  | 8878ce046c | ||
|  | 5bfddfc9b6 | ||
|  | 7148397771 | ||
|  | 8c12453544 | ||
|  | f723982bec | ||
|  | 0a73c84df0 | ||
|  | 977ae5e3e4 | ||
|  | 2687008242 | ||
|  | 6ef888d73e | ||
|  | 67ca88d5a0 | ||
|  | 17e9a8ebe1 | ||
|  | 0032f8a2ec | ||
|  | 2d980068c0 | ||
|  | 6babd5752e | ||
|  | 5d4da78c94 | ||
|  | cc400aa178 | ||
|  | 7b3cb2ce04 | ||
|  | afd8be416e | ||
|  | 5bc0175be9 | ||
|  | eb05a18c45 | ||
|  | 97da34595c | ||
|  | 2ea17d2783 | ||
|  | 8754a1d378 | ||
|  | d327b626e3 | ||
|  | f48d13a16a | ||
|  | 4c11fffc90 | ||
|  | 7f97efd922 | ||
|  | 78bb261246 | ||
|  | 73942f50cb | ||
|  | 3ebb93e47d | ||
|  | 5329f234ba | ||
|  | 2cc82ef660 | ||
|  | b948e37769 | ||
|  | 91b10dd799 | ||
|  | 94fa95c699 | ||
|  | 62e66cdda3 | ||
|  | 1cb76e0d99 | ||
|  | ef7523e5b7 | ||
|  | 065b0f34af | ||
|  | 2a4ebdc150 | ||
|  | bb275f7e25 | ||
|  | 4f453d7ed6 | ||
|  | 8a9f49c163 | ||
|  | 19b1e32c2c | ||
|  | c80c323634 | ||
|  | dd5a886830 | ||
|  | df6952648e | ||
|  | f26dd54f63 | ||
|  | ffe9688952 | ||
|  | 11b66a73b4 | ||
|  | 1d4b941d52 | ||
|  | 76276c326a | ||
|  | a436bd068f | ||
|  | c367b84ee5 | ||
|  | 46f671ae42 | ||
|  | 96e0ace89e | ||
|  | 8655ba110c | ||
|  | 7d24e433d8 | ||
|  | 098fcb14b5 | ||
|  | c114ea024a | ||
|  | b1ef009c6b | ||
|  | b6b4448045 | ||
|  | cb3644856d | ||
|  | 03576b9e83 | ||
|  | ea9f887ee1 | ||
|  | f7f75167ac | ||
|  | 253b28deba | ||
|  | 4af1cf88d2 | ||
|  | 2c1402843a | ||
|  | 0fd9d7400b | ||
|  | bd58b5002a | ||
|  | 890f29ccfe | ||
|  | e4f3979ec8 | ||
|  | 07c3600127 | ||
|  | da4f15d944 | ||
|  | fbec797d64 | ||
|  | 9954346143 | ||
|  | 3a51e2d9ca | ||
|  | 986a63ebb8 | ||
|  | 4c4b12c307 | ||
|  | f6007869bf | ||
|  | 454a7cbf4c | ||
|  | ec6bd27df6 | ||
|  | 6ab0d71d92 | ||
|  | d5e66f6f87 | ||
|  | bce250bbdc | ||
|  | fbf50cdae8 | ||
|  | 675b7fb605 | ||
|  | 0727f8690c | ||
|  | 55ff397c0c | ||
|  | b1ec460b9a | ||
|  | f9d251cd5c | ||
|  | b658f10db9 | ||
|  | 3a3f3b492f | ||
|  | b69861013a | ||
|  | a0c69ffe02 | ||
|  | c1da2c0219 | ||
|  | 611e82711a | ||
|  | baef856206 | ||
|  | 858d520882 | ||
|  | 7d8e3951ba | ||
|  | 3b43b00867 | ||
|  | 95262ad559 | ||
|  | 43e2edfbda | ||
|  | fb49e03eda | ||
|  | c312884692 | ||
|  | bc156afbb2 | ||
|  | 8a9622ec2e | ||
|  | 0705152105 | ||
|  | 8d71fbd032 | ||
|  | 820852be28 | ||
|  | fcb77f3f96 | ||
|  | 32fdd4c787 | ||
|  | 6a73ab1afa | ||
|  | 6fc5c608f8 | ||
|  | b7114fb951 | ||
|  | d375c46590 | ||
|  | c40b944da6 | ||
|  | e31545860e | ||
|  | 47b8e04b1c | ||
|  | 2ab9a07b64 | ||
|  | 5d5cd23715 | ||
|  | 77844e2055 | ||
|  | 28eb3b95b0 | ||
|  | 8b837fae07 | ||
|  | d49f40dd5c | ||
|  | 477a90b8e3 | ||
|  | a34331ae5f | ||
|  | b555af1c5d | ||
|  | ae608cf2fa | ||
|  | 28618d12a1 | ||
|  | 66c1712118 | ||
|  | 7b3d7a3826 | ||
|  | d7031fbe92 | ||
|  | 02fcb07aa9 | ||
|  | 66faba2df7 | ||
|  | 766b5fc6f6 | ||
|  | a2fd7108ff | ||
|  | 630372b183 | ||
|  | 3c5f011245 | ||
|  | e80db42061 | ||
|  | 7e269f6f17 | ||
|  | 30358c435e | ||
|  | 55e07712e5 | ||
|  | 293edc5772 | ||
|  | dc5acc687f | ||
|  | a40daa923b | ||
|  | 9dfda46e0c | ||
|  | 4d60c715e6 | ||
|  | 2c96fe917d | ||
|  | b42a7ebd8c | ||
|  | 9b1bdc3099 | ||
|  | bcaef1de18 | ||
|  | 33778d3772 | ||
|  | a6b8f3d994 | ||
|  | b166ed6b78 | ||
|  | 54d31c98fe | ||
|  | 48eac0b146 | ||
|  | 0d8a9b1e7a | ||
|  | ef3b1f46a9 | ||
|  | 5e4f65fe1f | ||
|  | 5fcebd28f4 | ||
|  | a1fce1b554 | ||
|  | bbc8424a5b | ||
|  | 036bc669cd | ||
|  | 26cc0e634d | ||
|  | 1b0f4ee653 | ||
|  | f3ad0c6ade | ||
|  | 4da9743317 | ||
|  | dca1777a2f | ||
|  | 4a9df60a7b | ||
|  | 2c6fcdbd3f | ||
|  | e6d4043279 | ||
|  | 5c2ba3b212 | ||
|  | ebc173ea75 | ||
|  | b394cc3394 | ||
|  | 6caf627262 | ||
|  | 3d68d42132 | ||
|  | bdd59b8988 | ||
|  | 9f55228be0 | ||
|  | 83ff3cbc69 | ||
|  | 4afdce4ec5 | ||
|  | 71fcea159f | ||
|  | 036e00113e | ||
|  | 958cace36d | ||
|  | 2568c714c8 | ||
|  | 83ab8e057a | ||
|  | 0ddabf0423 | ||
|  | b4d0f24bf9 | ||
|  | 5a2f61a031 | ||
|  | 5b4c099afc | ||
|  | c3357f884a | ||
|  | eff2d71f28 | ||
|  | 42b21fd7ae | ||
|  | 027801db60 | ||
|  | ad85beb677 | ||
|  | 780569c08a | ||
|  | ffa0c08140 | ||
|  | c38c85ef1f | ||
|  | 6c6bd11c1a | ||
|  | 4999b045df | ||
|  | 105aa8f452 | ||
|  | d8f02e151b | ||
|  | 3aef5999d5 | ||
|  | 3c3a1d89b5 | ||
|  | e8d3e86591 | ||
|  | d6c5c1772c | ||
|  | b65406791a | ||
|  | f3237d7a2c | ||
|  | 56b21ad429 | ||
|  | 538cf2bc24 | ||
|  | d8420ed5a0 | ||
|  | 04ed45941a | ||
|  | 55fb1d5126 | ||
|  | 4f791799a9 | ||
|  | d2127f6b82 | ||
|  | 1d815f4ba0 | ||
|  | aef93246b4 | ||
|  | 6b1d802caa | ||
|  | 6f30e92c7a | ||
|  | 39c1cc9f00 | ||
|  | 73057ee241 | ||
|  | a8a491212b | ||
|  | 2f18770e27 | ||
|  | 087e649bc2 | ||
|  | fd9b6487e1 | ||
|  | 603c4cb4fa | ||
|  | 8bed573b88 | ||
|  | 2a004251a7 | ||
|  | 8064f4bfe0 | ||
|  | 6256a6c57c | ||
|  | bae31ebce7 | ||
|  | e0ce7fcde7 | ||
|  | a9f6e30bcd | ||
|  | 0d7b005252 | ||
|  | fcbfd224a7 | ||
|  | eb097b9d03 | ||
|  | 7ed9e29326 | ||
|  | bdc4aa4a3b | ||
|  | 5bb5654d84 | ||
|  | a8d3bcbb75 | ||
|  | ec787d3518 | ||
|  | c7c701b3e3 | ||
|  | e495d606ec | ||
|  | 28136579e9 | ||
|  | cc414e63d3 | ||
|  | fd47445d75 | ||
|  | d39404cdda | ||
|  | 29acc46501 | ||
|  | cffb819e61 | ||
|  | b344c843c4 | ||
|  | e3cc0d168c | ||
|  | cbc8871a0b | ||
|  | e97b14c068 | ||
|  | 5a8704b4d8 | ||
|  | 6dd2fc5941 | ||
|  | 69c0a89aa5 | ||
|  | 937a63ce28 | ||
|  | b57ba84da5 | ||
|  | c3ba08ffb6 | ||
|  | c52d1d11f9 | ||
|  | d752a3f980 | ||
|  | 973b152375 | ||
|  | 3690af9bea | ||
|  | f81ee103bf | ||
|  | b124222649 | ||
|  | 8d8c642845 | ||
|  | d5f9d17b7c | ||
|  | 036e6ae30c | ||
|  | 38b5d1ee2b | ||
|  | 146c744223 | ||
|  | 0ced8400d0 | ||
|  | 2e4f89068a | ||
|  | f9dd751b6b | ||
|  | 6b6acc256d | ||
|  | 91338adc15 | ||
|  | 1d4e1092c4 | ||
|  | 0f1ff77fcc | ||
|  | e947aa0153 | ||
|  | 44c8db2911 | ||
|  | 93864610ce | ||
|  | bbd2563e13 | ||
|  | a6543cef16 | ||
|  | 2815540167 | ||
|  | 8f1ea85938 | ||
|  | 3ee3f0e21c | ||
|  | 13acca624f | ||
|  | c54ae73d49 | ||
|  | 858ddf6777 | ||
|  | a5f2152077 | ||
|  | a56ef685f3 | ||
|  | 62ab0bf2e7 | ||
|  | 77e34c5e8a | ||
|  | 270d1d59a0 | ||
|  | 3c41784de8 | ||
|  | 1425dda0a7 | ||
|  | a60d92cfbb | ||
|  | 9f898c460f | ||
|  | 2a0fe73045 | ||
|  | 8d50f8a3d3 | ||
|  | 622fcb0e10 | ||
|  | 7fc138c91e | ||
|  | 5b32db7564 | ||
|  | bd60f003e0 | ||
|  | 4883fdd154 | ||
|  | 18b9b6c780 | ||
|  | fbcc587eca | ||
|  | fbe30b2453 | ||
|  | d42dff45de | ||
|  | 8e230bf6ec | ||
|  | 603ea9f310 | ||
|  | a56b3e9a44 | ||
|  | 0378366e29 | ||
|  | 80ce6d1fb7 | ||
|  | ba8e5d8589 | ||
|  | 192cdc2f85 | ||
|  | 64f8a779ca | ||
|  | 5693ed1178 | ||
|  | 19c6c698b5 | ||
|  | 224fbc8125 | ||
|  | e5b4641f9e | ||
|  | c83552eadc | ||
|  | 4db63677f6 | ||
|  | f4d6a23f92 | ||
|  | 2cdd593290 | ||
|  | c675208b8a | ||
|  | e9445ec72d | ||
|  | 899a68325c | ||
|  | c80c232a72 | ||
|  | 83ff626c47 | ||
|  | 809d3476aa | ||
|  | 4d1d125f41 | ||
|  | 4d6b3c57b1 | ||
|  | a3d4ae85b0 | ||
|  | 02d57afd51 | ||
|  | 28254842db | ||
|  | 3382ca1a54 | ||
|  | 07a9a4ffd8 | ||
|  | 36ff688fab | ||
|  | 3df2c71e6c | ||
|  | ca1d4179a7 | ||
|  | 890d113b85 | ||
|  | 7555a1e302 | ||
|  | be7fbe50d7 | ||
|  | 2c01f277c2 | ||
|  | c333d855fc | ||
|  | 42de1c3a06 | ||
|  | a1d3bc30fa | ||
|  | 98576c17b6 | ||
|  | 27a3d2cd0b | ||
|  | 10f5966787 | ||
|  | 48957dce87 | ||
|  | fc901f9856 | ||
|  | 13964c7fca | ||
|  | a1cc3f2c60 | ||
|  | 15a6e04887 | ||
|  | 6bf4717b0a | ||
|  | f581831b86 | ||
|  | 6f02403184 | ||
|  | d040871f7a | ||
|  | 8f23b6faa6 | ||
|  | 1d2c47273d | ||
|  | 55ac480cb0 | ||
|  | e4ad1aa542 | ||
|  | 6b5df4523a | ||
|  | 7b34f5e866 | ||
|  | fe1e7c4d76 | ||
|  | 34217ea797 | ||
|  | a0515ca7ac | ||
|  | 28b419d65e | ||
|  | 7c465465c1 | ||
|  | e95a7dc555 | ||
|  | 582006c75c | ||
|  | c7ec45a004 | ||
|  | 88d1976e81 | ||
|  | cd592cb055 | ||
|  | 0b63af3313 | ||
|  | 25197308e3 | ||
|  | cf278ea1b6 | ||
|  | 4db4200c37 | ||
|  | 758862f4b1 | ||
|  | 3705b4f40d | ||
|  | 1f63fdbb15 | ||
|  | d11a94e2a7 | ||
|  | d6dd5ea5d3 | ||
|  | 8a146a50ec | ||
|  | 17d5e1c470 | ||
|  | 975a780efe | ||
|  | c3774607a5 | ||
|  | bb24cfd1e8 | ||
|  | 48b37aa2bf | ||
|  | 0bde4992ea | ||
|  | 7ec7d1bbcc | ||
|  | 0628dd997f | ||
|  | 283ec756a9 | ||
|  | 5b076cb0dd | ||
|  | 3bae533066 | ||
|  | 7b36fe049d | ||
|  | 4b3fda4f96 | ||
|  | 56ca179475 | ||
|  | 460cdc9e0f | ||
|  | f90b170dad | ||
|  | 68792bb918 | ||
|  | 3f0c8bafb0 | ||
|  | 588c0479f5 | ||
|  | d979841f17 | ||
|  | dff9bd9711 | ||
|  | eccd2ecebf | ||
|  | ed86fc175f | ||
|  | 132327a40d | ||
|  | a615a70eda | ||
|  | a2501bd5c1 | ||
|  | ff2daaff67 | ||
|  | d5a4457b5e | ||
|  | 6d402fe393 | ||
|  | 466a391b52 | ||
|  | a93714327e | ||
|  | 029b156563 | ||
|  | 55a5e9b3a5 | ||
|  | d8c7c3fc4b | ||
|  | f192f44018 | ||
|  | b81c14f442 | ||
|  | f54e1cea90 | ||
|  | fbddfcbfb7 | ||
|  | 67b14e6e7a | ||
|  | 6f4172fbc1 | ||
|  | c6e2f4a90b | ||
|  | 91c1d17f16 | ||
|  | 29f3d5b68d | ||
|  | 4aca7c8811 | ||
|  | 8a811cfcf7 | ||
|  | bf4493dbdf | ||
|  | c1bf5aee24 | ||
|  | 735d676a72 | ||
|  | 37c103b5f3 | ||
|  | 05c524a7db | ||
|  | 758a2c528f | ||
|  | 69b4e898b3 | ||
|  | b0da6318f3 | ||
|  | 972cdd4265 | ||
|  | f9aebfce01 | ||
|  | 151557fec3 | ||
|  | 7d68ca1f3b | ||
|  | 4b63829110 | ||
|  | e196387e69 | ||
|  | f8d608093f | ||
|  | ffeb40ff43 | ||
|  | 47305c2bf2 | ||
|  | c421b7f5f0 | ||
|  | d137286981 | ||
|  | 864202a23a | ||
|  | a77fb0f630 | ||
|  | a42ac86f1b | ||
|  | c6cd69887c | ||
|  | d1711036db | ||
|  | 8313b7315a | ||
|  | d7ecf6f593 | ||
|  | 082147939d | ||
|  | 67c4d165c7 | ||
|  | fb3e6a2b40 | ||
|  | 8ee2f50b8c | ||
|  | 46a4600952 | ||
|  | 27992d9c07 | ||
|  | a0dc87d64e | ||
|  | 1255d8a8ce | ||
|  | 7d3eb6463a | ||
|  | ab7fcf1d5b | ||
|  | 2c5146f19f | ||
|  | 3341b53eb4 | ||
|  | 70c05c62e4 | ||
|  | eb57bb298f | ||
|  | 953c745ed8 | ||
|  | ce73c29246 | ||
|  | 9c4df46c46 | ||
|  | 2a035a24a6 | ||
|  | 10c0174903 | ||
|  | d26a247a32 | ||
|  | a101d9078d | ||
|  | b2f843a4ce | ||
|  | 40d25da793 | ||
|  | b956d5d06c | ||
|  | 0845eab8a0 | ||
|  | 582c721aec | ||
|  | 0c4180cdd0 | ||
|  | d9824dfd64 | ||
|  | a088c4bee6 | ||
|  | 8359d8c020 | ||
|  | 1869fe02ba | ||
|  | e3e8d82933 | ||
|  | a96f30edf4 | ||
|  | af15b49bfe | ||
|  | 60b2f075dc | ||
|  | 0c49e6747c | ||
|  | 99b4ea7c1d | ||
|  | c9b9be5b81 | ||
|  | 2e6b813225 | ||
|  | aceba1c03f | ||
|  | 8141d53d94 | ||
|  | 21a8653195 | ||
|  | d372df7ddb | ||
|  | 6e13f5b387 | ||
|  | c07854fed8 | ||
|  | 8c4997c5fc | ||
|  | 3252eaa060 | ||
|  | 348019e37f | ||
|  | b9eb3dfad7 | ||
|  | 71513ccb39 | ||
|  | cdb3fb059f | ||
|  | 93caf97a04 | ||
|  | bca2c54948 | ||
|  | 81cbb0fc32 | ||
|  | c69f2929c0 | ||
|  | 4d23ea554b | ||
|  | d6fde756a8 | ||
|  | ba43444292 | ||
|  | 6eac49186d | ||
|  | 2432cece38 | ||
|  | fef338f5c2 | ||
|  | 24e186e684 | ||
|  | 4feabb13bf | ||
|  | 3f19879f41 | ||
|  | e90f47aba3 | ||
|  | 2aa2b843ce | ||
|  | fa2cda81db | ||
|  | 837d3bcdbb | ||
|  | 9f50f34547 | ||
|  | f0b17675d8 | ||
|  | 6eb46c419c | ||
|  | 96f1525ffe | ||
|  | 01101e3df0 | ||
|  | b95e5a4ea6 | ||
|  | f7ec579c18 | ||
|  | 4f0aca481f | ||
|  | ce889900d6 | ||
|  | 8a0a7c81b6 | ||
|  | e892014b65 | ||
|  | 8cbe5b8e20 | ||
|  | 5666c56903 | ||
|  | d5898c11eb | ||
|  | daedfbbd87 | ||
|  | bf7e24e9d7 | ||
|  | 02f367f733 | ||
|  | f2e7a23148 | ||
|  | 27a89019ad | ||
|  | 2eb7a67c70 | ||
|  | 59f829c6cc | ||
|  | 91c31d5e43 | ||
|  | 9b5d7ec061 | ||
|  | ed493be869 | ||
|  | f5ca687e9b | ||
|  | 9c81d63155 | ||
|  | 60c54107ce | ||
|  | 56dd22f0ac | ||
|  | afec1b439b | ||
|  | 219ef5b37a | ||
|  | a2d1e7f02c | ||
|  | 5e6e18b310 | ||
|  | 80407aa930 | ||
|  | a575e40a35 | ||
|  | 28fb492c40 | ||
|  | 45561f1622 | ||
|  | 715bf20c41 | ||
|  | ac2b7928c5 | ||
|  | 64cba950e5 | ||
|  | e7bc84dda8 | ||
|  | d8c70c5c3e | ||
|  | 12872ce49b | ||
|  | 2809e926cf | ||
|  | aa0ab06645 | ||
|  | 03e0116a56 | ||
|  | 018622f607 | ||
|  | 8cafaa1f3c | ||
|  | df944edeef | ||
|  | 388d3e273d | ||
|  | 49333ce6ad | ||
|  | d43417e97c | ||
|  | 91fdd76053 | ||
|  | f23b34951a | ||
|  | 29f32ce00e | ||
|  | 4dd48099be | ||
|  | 915f79c974 | ||
|  | ea04a0277b | ||
|  | 71afa40a69 | ||
|  | 6f17286c75 | ||
|  | caf63b9e73 | ||
|  | 64ff1a6b75 | ||
|  | 558247d8f6 | ||
|  | 23c51a1874 | ||
|  | 70907d84f1 | ||
|  | 8bbfa6827c | ||
|  | 6e65282f18 | ||
|  | 0d02298ea6 | ||
|  | 1ed909ea74 | ||
|  | 764b43e82c | ||
|  | 082f1ab494 | ||
|  | ec2c095a76 | ||
|  | 6c123596b7 | ||
|  | 89fbb02979 | ||
|  | 76a334bd7c | ||
|  | f47a148f51 | ||
|  | 5ecf9ec7bc | ||
|  | 3dbd58f40e | ||
|  | 810c6af77a | ||
|  | cd4f8f13a2 | ||
|  | 78736328a0 | ||
|  | 8d0095c618 | ||
|  | 1f2be2dac5 | ||
|  | 5247c1c795 | ||
|  | fdb3c3d6ff | ||
|  | 2e625138ff | ||
|  | ca21027eea | ||
|  | 0a551eb7c6 | ||
|  | 5dd0d35021 | ||
|  | 0d67ef795e | ||
|  | 978e7b5e77 | ||
|  | 161d3ce05c | ||
|  | 37a1e9f971 | ||
|  | 5a075acc6a | ||
|  | 0998221478 | ||
|  | 428a4ba3f9 | ||
|  | aaad906815 | ||
|  | 76659d9003 | ||
|  | 990dd5e5db | ||
|  | 56ab6d9f14 | ||
|  | 88052480b1 | ||
|  | 218b102b28 | ||
|  | fe7b72948a | ||
|  | 3c70f799a2 | ||
|  | e0747e937f | ||
|  | 320e2cf35b | ||
|  | bbc81146ec | ||
|  | faf7aeba27 | ||
|  | 558918651e | ||
|  | 0122b813d3 | ||
|  | 72fd648d92 | ||
|  | b95c0210b8 | ||
|  | f8ee33abb9 | ||
|  | 9f63158aad | ||
|  | 748d1ea89d | ||
|  | 39334f7280 | ||
|  | 7177e7ea1a | ||
|  | adaed307b4 | ||
|  | b4f96e192c | ||
|  | ae83e6f536 | ||
|  | 443550b353 | ||
|  | 6d7d617f40 | ||
|  | c7b0df569b | ||
|  | 041a4a4208 | ||
|  | 4d2d7d7684 | ||
|  | faa58b9074 | ||
|  | 6b5d55eb29 | ||
|  | b0a2f0c4fe | ||
|  | 4c9b7befaa | ||
|  | 6ef060ca17 | ||
|  | 391f0b5d09 | ||
|  | 9e8de5f596 | ||
|  | 78a2ca8a0e | ||
|  | f5d084552d | ||
|  | e1161dfc80 | ||
|  | ec36225f8a | ||
|  | 0ac43d3e7c | ||
|  | 6ed36cd9c7 | ||
|  | f6c1108bdf | ||
|  | 54fc933932 | ||
|  | af451b5462 | ||
|  | 837ebe405f | ||
|  | d236f3dbf9 | ||
|  | d23717904b | ||
|  | 837950cd74 | ||
|  | 61aa6b5236 | ||
|  | c94ba84892 | ||
|  | 64e3310e64 | ||
|  | 4601f4e1eb | ||
|  | ae9983324d | ||
|  | bc0734eee5 | ||
|  | c1fc98c313 | ||
|  | c484f0715b | ||
|  | 29513074ee | ||
|  | f15a0203e4 | ||
|  | 6c13f381c4 | ||
|  | 38891c29ea | ||
|  | d82c2a1c26 | ||
|  | 055f6afdcc | ||
|  | b55e618175 | ||
|  | 0053a8a1a7 | ||
|  | 3c98587a88 | ||
|  | 9cfcc5b89a | ||
|  | 7f63fbbf4a | ||
|  | c822fd46e5 | ||
|  | 35db5cf416 | ||
|  | 23724b6bf6 | ||
|  | 6863786243 | ||
|  | c5a0e8579a | ||
|  | 8d5f995a01 | ||
|  | bf32dc5d3a | ||
|  | 802416d267 | ||
|  | 553c934325 | ||
|  | bb5ceb2d9c | ||
|  | a1b27783a6 | ||
|  | cf83f6820c | ||
|  | 2d89cfb18f | ||
|  | 2dd5ce83ec | ||
|  | ca8957da43 | ||
|  | ab86b06050 | ||
|  | 6653f262d7 | ||
|  | 98c821ac39 | ||
|  | dc8e34420f | ||
|  | 479ccf17df | ||
|  | d46b2b49c7 | ||
|  | 3465452a93 | ||
|  | c0331cdf89 | ||
|  | 8ddeaddc67 | ||
|  | 0915fb73b2 | ||
|  | 2e0bc71c86 | ||
|  | 3dde01597a | ||
|  | 4716166041 | ||
|  | 8a1d3d5c84 | ||
|  | b68cf6c5c8 | ||
|  | 8c93805ab5 | ||
|  | f498c689e7 | ||
|  | 5c2f02c735 | ||
|  | bce24e8005 | ||
|  | 921780e6bf | ||
|  | ce1d72c609 | ||
|  | 2455fd4dbe | ||
|  | 352f0b6c38 | ||
|  | 87ad66f2b4 | ||
|  | 2eb08b2dc9 | ||
|  | ca47af6978 | ||
|  | ae1dedc059 | ||
|  | ada7ec2289 | ||
|  | d2a6c5c608 | ||
|  | 18578fd15f | ||
|  | d7f674e410 | ||
|  | e871324bde | ||
|  | 54bab33c73 | ||
|  | a9fdd468d5 | ||
|  | 6701606e4c | ||
|  | 79b982c941 | ||
|  | 593fc78dd8 | ||
|  | 4f03dff577 | ||
|  | 162d485304 | ||
|  | 70e1b976d8 | ||
|  | adf053a935 | ||
|  | 5ee26a5dd1 | ||
|  | 44624b2d7a | ||
|  | d690a1717f | ||
|  | f6d75970d8 | ||
|  | a4b8fc420a | ||
|  | 4185c10bf8 | ||
|  | ade46489f1 | ||
|  | e83762e979 | ||
|  | 6fe853a7d3 | ||
|  | 24c4ea71b1 | ||
|  | 675499449f | ||
|  | 2f269f3256 | ||
|  | f2c2536cf3 | ||
|  | 3539dd7d4c | ||
|  | d414f4e4f7 | ||
|  | dd5ce985bd | ||
|  | f901cf57e5 | ||
|  | fa7510968a | ||
|  | d36feb6405 | ||
|  | 404aa29289 | ||
|  | 2a435e53b7 | ||
|  | c036eab62c | ||
|  | 1d8321b821 | ||
|  | d7d66bd74f | ||
|  | e8527bac69 | ||
|  | 25f367432d | ||
|  | 7fcded5705 | ||
|  | c7e6c7ed18 | ||
|  | 046a99aba0 | ||
|  | 35083b262b | ||
|  | 16bfbd4e99 | ||
|  | 98398d31f7 | ||
|  | 4f203ae175 | ||
|  | d70994e4a8 | ||
|  | 82d765689a | ||
|  | 7f0e778145 | ||
|  | 3f90620343 | ||
|  | 3f35a8876e | ||
|  | b3bbe5eb2d | ||
|  | c327c6cd6f | ||
|  | f5d0d28ac3 | ||
|  | 05688934a1 | ||
|  | 36bd6c817e | ||
|  | c22d2b5b5b | ||
|  | b703db4c09 | ||
|  | b527cc98af | ||
|  | 36e00caf4d | ||
|  | c2aca45b8d | ||
|  | 887ecef369 | ||
|  | 24dee870a8 | ||
|  | 54bbb4400d | ||
|  | c6a984f506 | ||
|  | 551f76c700 | ||
|  | b23e40f722 | ||
|  | 1cd87f0028 | ||
|  | c5ec453ec1 | ||
|  | 5205b5b095 | ||
|  | 311236e81e | ||
|  | 392ed64f6f | ||
|  | f8d35806dc | ||
|  | 1d34078fa1 | ||
|  | 020e956f1b | ||
|  | 3760bdbb1b | ||
|  | 42a5b14724 | ||
|  | 08753f74ae | ||
|  | da0be7114f | ||
|  | f8a835be27 | ||
|  | 68441a74c2 | ||
|  | 1a31b03976 | ||
|  | 94046f30ff | ||
|  | aba0f8fd6c | ||
|  | 3b38079490 | ||
|  | c844894a6e | ||
|  | 642eb1400b | ||
|  | 1acbc5b1a9 | ||
|  | 421ee4dfbf | ||
|  | 46961cfdf7 | ||
|  | cc23129456 | ||
|  | 6cb6e57592 | ||
|  | 760a32246a | ||
|  | 7b2a39a6fb | ||
|  | bab03a3927 | ||
|  | 403cbb1dc9 | ||
|  | bd6a1d38d2 | ||
|  | 0a136c308e | ||
|  | 5d7301e510 | ||
|  | 09d52638b5 | ||
|  | 37e104a6b3 | ||
|  | 7e501855fc | ||
|  | c19967dcf2 | ||
|  | 85ce44f78e | ||
|  | 6d8f409018 | ||
|  | bea42a78a4 | ||
|  | cb5df138d6 | ||
|  | ef8695cecb | ||
|  | e495473fc3 | ||
|  | 62e799da09 | ||
|  | a8b426d0fe | ||
|  | f4736457f5 | ||
|  | f501a1ba2c | ||
|  | ee20fd4c16 | ||
|  | fee89ed7c7 | ||
|  | c5c5b64729 | ||
|  | 3081ecf301 | ||
|  | 66304a102d | ||
|  | 859e539f85 | ||
|  | 984cd47b41 | ||
|  | 2ccf9f3abd | ||
|  | 31b364b9b0 | ||
|  | 307f2365da | ||
|  | d82ba4af8a | ||
|  | 35d8edbc41 | ||
|  | 9115431d35 | ||
|  | e08267df54 | ||
|  | 74104aafda | ||
|  | 4478d8322b | ||
|  | 88543445d8 | ||
|  | 41a632a56c | ||
|  | 524a89cc72 | ||
|  | 8938d928f8 | ||
|  | b50d04336e | ||
|  | ce331b411c | ||
|  | 8528455a75 | ||
|  | d28dc08f09 | ||
|  | 344e28360f | ||
|  | 983f01016b | ||
|  | 9eca19b49d | ||
|  | 860b519f99 | ||
|  | 181c4c5311 | ||
|  | 18d14dff48 | ||
|  | b412784a7a | ||
|  | dc28a42fd2 | ||
|  | 0d80a95e54 | ||
|  | 8073bf22e9 | ||
|  | e764a3d9ca | ||
|  | 49a31bd5d8 | ||
|  | 7371eefa86 | ||
|  | 15636f05f5 | ||
|  | efc71e08c4 | ||
|  | bac8ad565e | ||
|  | 525e065474 | ||
|  | 44b6494ab7 | ||
|  | 6df6239704 | ||
|  | c6ffece410 | ||
|  | 34a2804b54 | ||
|  | cdb1447569 | ||
|  | 7477785bbb | ||
|  | 0e90e98e9b | ||
|  | f616b22159 | ||
|  | c5bf36eebf | ||
|  | abbaaeee37 | ||
|  | 387a4fcb8e | ||
|  | cd6256a924 | ||
|  | 06f5c309c0 | ||
|  | a58c7b60bc | ||
|  | 673396c0e7 | ||
|  | 963f3e3059 | ||
|  | 373eb97357 | ||
|  | b20427b2ec | ||
|  | c02759ad91 | ||
|  | 8b1eaf00a0 | ||
|  | baf1c2efbe | ||
|  | cd8198037f | ||
|  | e65ff85b88 | ||
|  | 9370973846 | ||
|  | 8b13a7b338 | ||
|  | dfd5603171 | ||
|  | 966f0abf48 | ||
|  | 1329a387e0 | ||
|  | 6804b6c0b4 | ||
|  | ff3a70ed9d | ||
|  | 0a26870d36 | ||
|  | b2775fc90c | ||
|  | 6efcc9acaa | ||
|  | 2a589b7904 | ||
|  | 5f10eadb8d | ||
|  | 224faccd4c | ||
|  | ce1078994d | ||
|  | b6ba1ea53a | ||
|  | 316be179af | ||
|  | e785e1bc22 | ||
|  | 3cc0ec654a | ||
|  | a19f8c32ff | ||
|  | 87acf5f50f | ||
|  | 14e3e72b0f | ||
|  | faf506b300 | ||
|  | 879c621394 | ||
|  | 627d7f66ef | ||
|  | 3ad52f837d | ||
|  | 49eaf000b8 | ||
|  | 1fdde8f03f | ||
|  | 64092169e3 | ||
|  | 187551f914 | ||
|  | cd1bf31fed | ||
|  | d54eb1107e | ||
|  | e9121cba2c | ||
|  | 059ab358a5 | ||
|  | ab7d129e15 | ||
|  | 12720737e1 | ||
|  | 18c62aa5b4 | ||
|  | d1dfaa1905 | ||
|  | 1f65a4eb6f | ||
|  | eb09d7d671 | ||
|  | 7f702fd6f1 | ||
|  | 383bef1711 | ||
|  | 5e86188c25 | ||
|  | 661b1f3373 | ||
|  | d55b9bc6e2 | ||
|  | 1ebb742fbf | ||
|  | 1be6e53316 | ||
|  | d737e3dcbb | ||
|  | a1cbdd8d29 | ||
|  | 8b9306ed5c | ||
|  | df56f901de | ||
|  | 9f7392474b | ||
|  | 9dff6b9d81 | ||
|  | d736c9205c | ||
|  | 464018e12d | ||
|  | 55e4a89819 | ||
|  | a521a9b5ce | ||
|  | f43d57846e | ||
|  | 4b303949bf | ||
|  | fc746a88b5 | ||
|  | 1e029a9e66 | ||
|  | 053d5629ba | ||
|  | 1b3d9a0aef | ||
|  | f79d8cb2d3 | ||
|  | 0d4ab559a7 | ||
|  | 1379eb4e70 | ||
|  | f97b591831 | ||
|  | d97a771479 | ||
|  | 5bbed31513 | ||
|  | d3494a4bdf | ||
|  | 2f2860b096 | ||
|  | e1e87fef25 | ||
|  | 908ec5faef | ||
|  | a4772c15c0 | ||
|  | 4e9ed223a9 | ||
|  | 56f2e0c9a0 | ||
|  | b950d6d72b | ||
|  | ab63f3832f | ||
|  | b0541c01be | ||
|  | 56c3a949af | ||
|  | 34a0c2172b | ||
|  | 0c18a13cc0 | ||
|  | 1a87f7477a | ||
|  | 14a5d03af2 | ||
|  | a8953b5c66 | ||
|  | d97724cca3 | ||
|  | 2bf2e54db9 | ||
|  | 288309b9d5 | ||
|  | d07821d851 | ||
|  | 62857bdb2d | ||
|  | 26d7610554 | ||
|  | 6e93d193cf | ||
|  | 4567eff78e | ||
|  | 2d65373f63 | ||
|  | ae4827587c | ||
|  | a3d6aa5d7d | ||
|  | db907a914c | ||
|  | cdd326284a | ||
|  | 3e9d5978c0 | ||
|  | ec7a4fd9cc | ||
|  | 977a7906e4 | ||
|  | 7cfce05bd2 | ||
|  | f46e0eb5f2 | ||
|  | 5fe36b6bb0 | ||
|  | 6c1137177f | ||
|  | 8800caa216 | ||
|  | 82f7e104d9 | ||
|  | 2fcdfc4876 | ||
|  | dec75474d5 | ||
|  | 3edd81a94e | ||
|  | 3810fe6a20 | ||
|  | 7f96359804 | ||
|  | bf45b1d83d | ||
|  | d7262f9849 | ||
|  | 8a620fd59c | ||
|  | bbd687fcb0 | ||
|  | 7b83d53bbf | ||
|  | adefa4f86b | ||
|  | e179e7cf85 | ||
|  | 9d971e5b15 | ||
|  | 5cfe1a1fb4 | ||
|  | 856b944eaf | ||
|  | 0e3cd87a32 | ||
|  | 6bea078487 | ||
|  | 5015c845c5 | ||
|  | c1eaad41f3 | ||
|  | d40589f082 | ||
|  | 9d018a0075 | ||
|  | fdc87a8e7f | ||
|  | 72b0379125 | ||
|  | 14eecf2fc4 | ||
|  | 0b4b87e344 | ||
|  | 5df263db38 | ||
|  | 0584312ef0 | ||
|  | 6b5d9642d5 | ||
|  | 881e9d07d2 | ||
|  | a7dee5b65c | ||
|  | d3d424dad3 | ||
|  | 008b1fd30c | ||
|  | d0703f83db | ||
|  | 2910b09cba | ||
|  | 59132514cf | ||
|  | 1a96eae22c | ||
|  | 79c60f4a7d | ||
|  | fb2d5b484a | ||
|  | 87795cbca8 | ||
|  | db9b8eb88f | ||
|  | b38d496d51 | ||
|  | 374c7513f9 | ||
|  | bdcea84ca1 | ||
|  | 8e4d65e163 | ||
|  | 2873cb93ee | ||
|  | 37b460637d | ||
|  | 41ec4170a5 | ||
|  | a9c7263ba0 | ||
|  | 5f7ef8bed0 | ||
|  | ed843b519b | ||
|  | 09f078a1cc | ||
|  | 8ebe6e5f20 | ||
|  | db2bc8783e | ||
|  | 705270faae | ||
|  | c37f86ff1c | ||
|  | 2dcbe75cca | ||
|  | 5158014f3f | ||
|  | 4439031d43 | ||
|  | 067f1382e4 | ||
|  | 1cc61dc68a | ||
|  | 0d8d8d3320 | ||
|  | 72e36d8997 | ||
|  | 91612b7446 | ||
|  | 007f452927 | ||
|  | 3e728d5e73 | ||
|  | b1ef856242 | ||
|  | 91d1fff4ed | ||
|  | 629e0ea595 | ||
|  | 02d6e0d14d | ||
|  | 7750ea65ba | ||
|  | a0b1299890 | ||
|  | 733b83e44f | ||
|  | 8dbe397f99 | ||
|  | c44c108db5 | ||
|  | 77f7830da3 | ||
|  | 909a5fe2e5 | ||
|  | 0997021a05 | ||
|  | 486fe9e59d | ||
|  | 906d2b2db7 | ||
|  | 79985f0036 | ||
|  | 6ad58f428a | ||
|  | 4ccb2bc95a | ||
|  | 17e78175a6 | ||
|  | 62841dd5b9 | ||
|  | 1e8da91def | ||
|  | 374c92a4f0 | ||
|  | 433422b9f2 | ||
|  | a85b34c1fe | ||
|  | 1078f6f4e7 | ||
|  | 2bb6e93e86 | ||
|  | 2afa08c923 | ||
|  | a61701b756 | ||
|  | 7a36072a15 | ||
|  | a167c77d39 | ||
|  | 8839f3f62a | ||
|  | ac111f40d8 | ||
|  | 3229b5e809 | ||
|  | 0c07eef3a9 | ||
|  | 371a80e1a2 | ||
|  | b2047c4351 | ||
|  | 849011b826 | ||
|  | 6cec6fa09b | ||
|  | 0d224a00e1 | ||
|  | 47ae9b7981 | ||
|  | 8e22d57447 | ||
|  | 0107f83b53 | ||
|  | bab30a7e87 | ||
|  | 5f99ccf54c | ||
|  | 54e695b1b2 | ||
|  | 8fc92fdf62 | ||
|  | c6424fdc11 | ||
|  | 3c31236c10 | ||
|  | cd88e39f5f | ||
|  | 6096f0cf4b | ||
|  | a1d45303c3 | ||
|  | 7601779693 | ||
|  | 1806422763 | ||
|  | 00cde67abe | ||
|  | 96e8a7ddb9 | ||
|  | 25864ae4dc | ||
|  | 14e0090cb1 | ||
|  | 45843034ec | ||
|  | 7757405908 | ||
|  | fc268df6f5 | ||
|  | 4ea6662e55 | ||
|  | 49455792cb | ||
|  | 855c0d1423 | ||
|  | 6ee45f5ec0 | ||
|  | 1296817f7b | ||
|  | 534cf7c618 | ||
|  | 05c36e05f9 | ||
|  | 73cd016885 | ||
|  | 0589f2f36e | ||
|  | b612976cc7 | ||
|  | b58b9ff390 | ||
|  | 1d5adb8974 | ||
|  | be66e0f7ce | ||
|  | 861f49cfd4 | ||
|  | 7ab9fe0522 | ||
|  | 1c8492bab2 | ||
|  | 19588a9e6f | ||
|  | 1d2f6667df | ||
|  | ac0ea406f9 | ||
|  | 7336b8cd88 | ||
|  | bcbe3040a1 | ||
|  | f4ade6af8b | ||
|  | fa1ec29ae6 | ||
|  | 58e727d3b9 | ||
|  | 4cd5ed81aa | ||
|  | 4618b79b22 | ||
|  | db16b6ff3f | ||
|  | a9074fd09b | ||
|  | f2469776f4 | ||
|  | a10850a6da | ||
|  | 03737e4be4 | ||
|  | bc34f3c60f | ||
|  | c70a66fe81 | ||
|  | a186694fdd | ||
|  | af368676ef | ||
|  | ce04b732d1 | ||
|  | 099ec7443b | ||
|  | ff6748a0d8 | ||
|  | 7646f18836 | ||
|  | 5659269c8f | ||
|  | 41fe718ea8 | ||
|  | 94c6727b3f | ||
|  | b77794c9a7 | ||
|  | ba886b45b8 | ||
|  | 76b967a966 | ||
|  | 2c5d94493c | ||
|  | 2119b8e1ca | ||
|  | 49bed094a3 | ||
|  | 49ecd8d7c5 | ||
|  | 29cc661dca | ||
|  | c19de29847 | ||
|  | f4bfda345d | ||
|  | 3e730aa8b0 | ||
|  | 28a3454846 | ||
|  | 4d551a5865 | ||
|  | 91cddd50f6 | ||
|  | 9d610c6866 | ||
|  | 03d6363d5a | ||
|  | d960bf344a | ||
|  | 31b54e0fbd | ||
|  | 3ba73b5ff4 | ||
|  | 8c733c29cc | ||
|  | 278818395e | ||
|  | 42a8f1671d | ||
|  | 1cf26f0eb3 | ||
|  | d91c37dae3 | ||
|  | 759d911b50 | ||
|  | a277deeaa5 | ||
|  | cb00fb18cb | ||
|  | f8b359dc5a | ||
|  | edef790e1a | ||
|  | 18af9f304a | ||
|  | deadf40c85 | ||
|  | 405649ddc7 | ||
|  | 0c8e5f92c7 | ||
|  | e2410cbb6a | ||
|  | 52decfb16c | ||
|  | 91047c61b9 | ||
|  | 054ca17f66 | ||
|  | 0a2d6f0be0 | ||
|  | 0c7ce80384 | ||
|  | 123c110427 | ||
|  | 0d82ebea29 | ||
|  | f729d2d060 | ||
|  | 9e96655757 | ||
|  | 582a15a693 | ||
|  | 2d7ba44621 | ||
|  | 73f25093c7 | ||
|  | a3f727e2e1 | ||
|  | 0dea7b02d6 | ||
|  | a4866ad278 | ||
|  | d78edba0d4 | ||
|  | b283f213d9 | ||
|  | ba47fe9c41 | ||
|  | 8285263fc2 | ||
|  | cc5bca8e83 | ||
|  | 0a2f7b4661 | ||
|  | 6a1a3e6877 | ||
|  | 02c86bca52 | ||
|  | fb27594b2e | ||
|  | a73f9474a0 | ||
|  | 3bf41e9a06 | ||
|  | 0e01b28d5e | ||
|  | 8aca324c2d | ||
|  | 7ee9e8322c | ||
|  | 43c20dbe65 | ||
|  | 8cd117fd5d | ||
|  | f64d1131b6 | ||
|  | b153a5b0d7 | ||
|  | 9b51c8f4d4 | ||
|  | 510ae7e3dc | ||
|  | f67eb84ec7 | ||
|  | 5de6bb3adf | ||
|  | 54fe50c685 | ||
|  | 837cdacd16 | ||
|  | 41e90e22e2 | ||
|  | 64b80c2e4d | ||
|  | 26f4d7d283 | ||
|  | fdd6e08ed6 | ||
|  | 7d64de153f | ||
|  | e24421a393 | ||
|  | 4de4039417 | ||
|  | f589247c1f | ||
|  | 37181c79d9 | ||
|  | a2ac1eed1b | ||
|  | bfa3cbaf30 | ||
|  | 216b120f15 | ||
|  | 08d9e5981e | ||
|  | 8a9136e4e4 | ||
|  | 3260f6b2af | ||
|  | 2080d8bb6d | ||
|  | a673b9aa29 | ||
|  | 38c5b97df0 | ||
|  | 21ea98810a | ||
|  | fbd6eeb748 | ||
|  | dbded7a6ce | ||
|  | efc70debb3 | ||
|  | 173c65b543 | ||
|  | a86a100879 | ||
|  | bba8b0ded5 | ||
|  | 095f5ef279 | ||
|  | 1c68c62ebd | ||
|  | f5216624bb | ||
|  | 46227bec1e | ||
|  | e63a34ba21 | ||
|  | 82dd7a5f8c | ||
|  | 0dfa192992 | ||
|  | e14fa11fc3 | ||
|  | df7ef1d98f | ||
|  | 9202dbf32a | ||
|  | 4044d728a6 | ||
|  | 6acc361109 | ||
|  | 27ec29e2c5 | ||
|  | 90c1422381 | ||
|  | 9e1ddeac86 | ||
|  | 99dcbf00c4 | ||
|  | 776e001b5b | ||
|  | 6ba2bb1a7f | ||
|  | a258e15c23 | ||
|  | 7a682ec02a | ||
|  | e9ede443bc | ||
|  | bc550cbfda | ||
|  | 8da9c07a65 | ||
|  | bd6bb2d248 | ||
|  | 81f10e136a | ||
|  | 1fdce6e52a | ||
|  | efb67dfc1b | ||
|  | f395ec44e8 | ||
|  | 63941432e3 | ||
|  | be0f93ff37 | ||
|  | 224eaaf797 | ||
|  | f681b9d4e5 | ||
|  | 650589ab3f | ||
|  | b9fc2a6b33 | ||
|  | 3002ee0dcf | ||
|  | ce0e1a5db3 | ||
|  | 9ca5bcd576 | ||
|  | 4be1e4bd44 | ||
|  | a3ecf5caf6 | ||
|  | a7629299f4 | ||
|  | e7ff759f1c | ||
|  | 6a6c673c5f | ||
|  | c1d5f86871 | ||
|  | 0289630fe4 | ||
|  | 1b86570cfd | ||
|  | f51cbebffd | ||
|  | c309797488 | ||
|  | 46efce6ea2 | ||
|  | 683ad80479 | ||
|  | 4d2710ed4d | ||
|  | 056b8ab5a2 | ||
|  | eb32d8ebbd | ||
|  | cbbfeb23be | ||
|   | 8d7abeb06f | ||
|   | 9876571887 | ||
|   | 289762c0ef | ||
|  | fd5c7acafc | ||
|  | 76ebebd0c5 | ||
|  | 608eb9b1fd | ||
|  | 6b2aaacf83 | ||
|  | e6e5685f7c | ||
|  | 69db1535b2 | ||
|  | d3fb9ddf33 | ||
|  | b2d63d851d | ||
|  | 869b337715 | ||
|  | fe57e4d9f8 | ||
|  | 781c7f978c | ||
|  | c15f07de4f | ||
|  | 983a94a9c5 | ||
|  | e9e2be137f | ||
|  | 20ea35fc96 | ||
|  | d918919cb2 | ||
|  | 056df98d47 | ||
|  | 273e6f5b83 | ||
|  | 8926397f45 | ||
|  | 07fe37b5ee | ||
|  | 1c721cb20e | ||
|  | b58cc2aa23 | ||
|  | a2272dda08 | ||
|  | cde6f0f862 | ||
|  | c7646eb699 | ||
|  | 4c52104413 | ||
|  | fd3de78b43 | ||
|  | 6f547cf05d | ||
|  | 4721381225 | ||
|  | 5da6108bec | ||
|  | ed601d439a | ||
|  | cffc3fc795 | ||
|  | bb90bb8c56 | ||
|  | ba9afbc26d | ||
|  | 20923080e6 | ||
|  | 8b24fe3df0 | ||
|  | 369f205962 | ||
|  | 33cbc22236 | ||
|  | 359b1c7fdb | ||
|  | 29494a9296 | ||
|  | f82f931dc2 | ||
|  | 3034e8fb45 | ||
|  | b4b8d82980 | ||
|  | 0e946dfb20 | ||
|  | b411b7ce63 | ||
|  | ee3d284609 | ||
|  | 67743bb122 | ||
|  | 1b410d6f3f | ||
|  | a6a2a035d5 | ||
|  | 0ff606dfc1 | ||
|  | d57fe0de86 | ||
|  | e33b99e9ec | ||
|  | f6f041b05d | ||
|  | f34829f534 | ||
|  | e3492862e9 | ||
|  | 0bcd1b1e10 | ||
|  | 5e74926907 | ||
|  | 218b44652c | ||
|  | 9900ac2c9a | ||
|  | a2bae33d84 | ||
|  | 001610f0d0 | ||
|  | 072b8ce035 | ||
|  | c1fa46bd36 | ||
|  | 871700f05d | ||
|  | 2548bbdd65 | ||
|  | 1b6799d93f | ||
|  | 076ada4c02 | ||
|  | 42cc0c6d73 | ||
|  | 56b50f8b62 | ||
|  | 432240162a | ||
|  | f4c9014fe4 | ||
|  | 8a81114baf | ||
|  | 27771e60af | ||
|  | 86113e199f | ||
|  | 9223b67306 | ||
|  | aff265a7f5 | ||
|  | 79cee74151 | ||
|  | 0d84533bcb | ||
|  | c385bbf07d | ||
|  | 62c0b18f10 | ||
|  | a83560c6b1 | ||
|  | cb6d1fc514 | ||
|  | 3b04ef265c | ||
|  | 7bbd4671d3 | ||
|  | 9962db4ecf | ||
|  | 79a9a4eb98 | ||
|  | 270c4d2476 | ||
|  | 6b35f654ba | ||
|  | 11387c3b03 | ||
|  | 612000aa8f | ||
|  | 9aaefa6e71 | ||
|  | 281cbcb1e8 | ||
|  | 28bf4b7b6d | ||
|  | 3ba0b3ef3b | ||
|  | 8f21a5b116 | ||
|  | 9db9333d05 | ||
|  | 483edf694b | ||
|  | e7d30194e3 | ||
|  | 520860622b | ||
|  | e727fe8675 | ||
|  | 443e275f1f | ||
|  | 96214f9db6 | ||
|  | be20512f17 | ||
|  | a19bcb69d1 | ||
|  | 92136d27f6 | ||
|  | f0b7f43c41 | ||
|  | fe5229670f | ||
|  | 6b90ab8664 | ||
|  | ea0738c485 | ||
|  | 29145e5f92 | ||
|  | 5c4d6232ae | ||
|  | 95f3484b87 | ||
|  | 2bd7205c79 | ||
|  | acaa8b3e8b | ||
|  | 69e92e5639 | ||
|  | f2ac14b86f | ||
|  | 193124bed1 | ||
|  | 63806022f3 | ||
|  | 3826b4f713 | ||
|  | bb76a29ff1 | ||
|  | d896f80405 | ||
|  | 2900ab79e7 | ||
|  | 9218aff498 | ||
|  | 945449b10f | ||
|  | 79c7be3fc6 | ||
|  | 14eae9ca06 | ||
|  | 64154fec8c | ||
|  | ed97e61dbe | ||
|  | 029713eca0 | ||
|  | 3d26573c6b | ||
|  | 0963b5f92c | ||
|  | 530f192acc | ||
|  | a46f33b214 | ||
|  | 6b4555a6a7 | ||
|  | f76815d642 | ||
|  | 3388b5cecf | ||
|  | ddfbfa0132 | ||
|  | 5ef40acd1d | ||
|  | 92505f53e2 | ||
|  | 726d68a706 | ||
|  | 54269a0761 | ||
|  | 13c88a9ca3 | ||
|  | 4205eef3ec | ||
|  | 931e3d7ee0 | ||
|  | 7dfdea8797 | ||
|  | 31410aa5b7 | ||
|  | ce7353fba4 | ||
|  | f60407feb3 | ||
|  | e1fd7dfc40 | ||
|  | 18b11e7417 | ||
|  | d96ad248b3 | ||
|  | 3ffdbd2ca3 | ||
|  | 30b7800f9a | ||
|  | 7ddcacac7b | ||
|  | 069a57fcf8 | ||
|  | e560415fde | 
							
								
								
									
										41
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| * text=auto | ||||
|  | ||||
| *.adoc     text | ||||
| *.html     text | ||||
| *.in       text | ||||
| *.json     text | ||||
| *.md       text | ||||
| *.proto    text | ||||
| *.py       text | ||||
| *.rs       text | ||||
| *.service  text | ||||
| *.sh       text | ||||
| *.toml     text | ||||
| *.txt      text | ||||
| *.x        text | ||||
| *.yml      text | ||||
|  | ||||
| *.raw    binary | ||||
| *.bin    binary | ||||
| *.png    binary | ||||
| *.jpg    binary | ||||
| *.jpeg   binary | ||||
| *.gif    binary | ||||
| *.ico    binary | ||||
| *.mov    binary | ||||
| *.mp4    binary | ||||
| *.mp3    binary | ||||
| *.flv    binary | ||||
| *.fla    binary | ||||
| *.swf    binary | ||||
| *.gz     binary | ||||
| *.zip    binary | ||||
| *.7z     binary | ||||
| *.ttf    binary | ||||
| *.eot    binary | ||||
| *.woff   binary | ||||
| *.pyc    binary | ||||
| *.pdf    binary | ||||
| *.ez     binary | ||||
| *.bz2    binary | ||||
| *.swp    binary | ||||
							
								
								
									
										4
									
								
								.github/bors.toml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/bors.toml
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +0,0 @@ | ||||
| status = [ | ||||
|     "all", | ||||
| ] | ||||
| delete_merged_branches = true | ||||
							
								
								
									
										16
									
								
								.github/ci/build-stable.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										16
									
								
								.github/ci/build-stable.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #!/bin/bash | ||||
| ## on push branch~=gh-readonly-queue/main/.* | ||||
| ## on pull_request | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| export RUSTUP_HOME=/ci/cache/rustup | ||||
| export CARGO_HOME=/ci/cache/cargo | ||||
| export CARGO_TARGET_DIR=/ci/cache/target | ||||
|  | ||||
| hashtime restore /ci/cache/filetime.json || true | ||||
| hashtime save /ci/cache/filetime.json | ||||
|  | ||||
| sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml | ||||
|  | ||||
| ./ci_stable.sh | ||||
							
								
								
									
										20
									
								
								.github/ci/build.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										20
									
								
								.github/ci/build.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #!/bin/bash | ||||
| ## on push branch~=gh-readonly-queue/main/.* | ||||
| ## on pull_request | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| export RUSTUP_HOME=/ci/cache/rustup | ||||
| export CARGO_HOME=/ci/cache/cargo | ||||
| export CARGO_TARGET_DIR=/ci/cache/target | ||||
| if [ -f /ci/secrets/teleprobe-token.txt ]; then  | ||||
|     echo Got teleprobe token! | ||||
|     export TELEPROBE_HOST=https://teleprobe.embassy.dev | ||||
|     export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt) | ||||
|     export TELEPROBE_CACHE=/ci/cache/teleprobe_cache.json | ||||
| fi | ||||
|  | ||||
| hashtime restore /ci/cache/filetime.json || true | ||||
| hashtime save /ci/cache/filetime.json | ||||
|  | ||||
| ./ci.sh | ||||
							
								
								
									
										17
									
								
								.github/ci/crlf.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								.github/ci/crlf.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| #!/bin/bash | ||||
| ## on push branch~=gh-readonly-queue/main/.* | ||||
| ## on pull_request | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| FILES_WITH_CRLF=$(find  ! -path "./.git/*" -not -type d | xargs file -N | (grep " CRLF " || true)) | ||||
|  | ||||
| if [ -z "$FILES_WITH_CRLF" ]; then | ||||
|   echo -e "No files with CRLF endings found." | ||||
|   exit 0 | ||||
| else | ||||
|   NR_FILES=$(echo "$FILES_WITH_CRLF" | wc -l) | ||||
|   echo -e "ERROR: Found ${NR_FILES} files with CRLF endings." | ||||
|   echo "$FILES_WITH_CRLF" | ||||
|   exit "$NR_FILES" | ||||
| fi | ||||
							
								
								
									
										55
									
								
								.github/ci/doc.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										55
									
								
								.github/ci/doc.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| #!/bin/bash | ||||
| ## on push branch=main | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| export RUSTUP_HOME=/ci/cache/rustup | ||||
| export CARGO_HOME=/ci/cache/cargo | ||||
| export CARGO_TARGET_DIR=/ci/cache/target | ||||
| export BUILDER_THREADS=4 | ||||
| export BUILDER_COMPRESS=true | ||||
|  | ||||
| # force rustup to download the toolchain before starting building. | ||||
| # Otherwise, the docs builder is running multiple instances of cargo rustdoc concurrently. | ||||
| # They all see the toolchain is not installed and try to install it in parallel | ||||
| # which makes rustup very sad | ||||
| rustc --version > /dev/null | ||||
|  | ||||
| docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup | ||||
| docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup | ||||
| docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup | ||||
| docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup | ||||
| docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup | ||||
| docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup | ||||
| docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup | ||||
| docserver-builder -i ./embassy-lora -o webroot/crates/embassy-lora/git.zup | ||||
| docserver-builder -i ./embassy-net -o webroot/crates/embassy-net/git.zup | ||||
| docserver-builder -i ./embassy-net-driver -o webroot/crates/embassy-net-driver/git.zup | ||||
| docserver-builder -i ./embassy-net-driver-channel -o webroot/crates/embassy-net-driver-channel/git.zup | ||||
| docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup | ||||
| docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup | ||||
| docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup | ||||
| docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup | ||||
| docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup | ||||
| docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/git.zup | ||||
| docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup | ||||
| docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup | ||||
| docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup | ||||
| docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/git.zup | ||||
| docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup | ||||
| docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup | ||||
| docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static | ||||
| docserver-builder -i ./embassy-net-adin1110 -o webroot/crates/embassy-net-adin1110/git.zup | ||||
|  | ||||
| export KUBECONFIG=/ci/secrets/kubeconfig.yml | ||||
| POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) | ||||
| kubectl cp webroot/crates $POD:/data | ||||
| kubectl cp webroot/static $POD:/data | ||||
|  | ||||
| # build and upload stm32 last | ||||
| # so that it doesn't prevent other crates from getting docs updates when it breaks. | ||||
| rm -rf webroot | ||||
| docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup | ||||
|  | ||||
| POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) | ||||
| kubectl cp webroot/crates $POD:/data | ||||
							
								
								
									
										32
									
								
								.github/ci/test.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										32
									
								
								.github/ci/test.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| #!/bin/bash | ||||
| ## on push branch~=gh-readonly-queue/main/.* | ||||
| ## on pull_request | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| export RUSTUP_HOME=/ci/cache/rustup | ||||
| export CARGO_HOME=/ci/cache/cargo | ||||
| export CARGO_TARGET_DIR=/ci/cache/target | ||||
|  | ||||
| hashtime restore /ci/cache/filetime.json || true | ||||
| hashtime save /ci/cache/filetime.json | ||||
|  | ||||
| cargo test --manifest-path ./embassy-sync/Cargo.toml  | ||||
| cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml  | ||||
| cargo test --manifest-path ./embassy-hal-internal/Cargo.toml  | ||||
| cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue | ||||
|  | ||||
| cargo test --manifest-path ./embassy-boot/boot/Cargo.toml | ||||
| cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly | ||||
| cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-dalek | ||||
| cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-salty | ||||
|  | ||||
| cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1,gpiote | ||||
|  | ||||
| cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features nightly,time-driver | ||||
|  | ||||
| cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti | ||||
| cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti | ||||
| cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti | ||||
|  | ||||
| cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml | ||||
							
								
								
									
										88
									
								
								.github/workflows/doc.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										88
									
								
								.github/workflows/doc.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,88 +0,0 @@ | ||||
| name: Docs | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [master] | ||||
|  | ||||
| env: | ||||
|   BUILDER_THREADS: '1' | ||||
|  | ||||
| jobs: | ||||
|   doc: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     # Since stm32 crates take SO LONG to build, we split them | ||||
|     # into a separate job. This way it doesn't slow down updating | ||||
|     # the rest. | ||||
|     strategy: | ||||
|       matrix: | ||||
|         crates: | ||||
|           #- stm32  # runs out of disk space... | ||||
|           - rest | ||||
|  | ||||
|     # This will ensure at most one doc build job is running at a time | ||||
|     # (for stm32 and non-stm32 independently). | ||||
|     # If another job is already running, the new job will wait. | ||||
|     # If another job is already waiting, it'll be canceled. | ||||
|     # This means some commits will be skipped, but that's fine because | ||||
|     # we only care that the latest gets built. | ||||
|     concurrency: doc-${{ matrix.crates }} | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: true | ||||
|       - name: Install Rust targets | ||||
|         run: | | ||||
|           rustup target add x86_64-unknown-linux-gnu | ||||
|           rustup target add wasm32-unknown-unknown | ||||
|           rustup target add thumbv6m-none-eabi | ||||
|           rustup target add thumbv7m-none-eabi | ||||
|           rustup target add thumbv7em-none-eabi | ||||
|           rustup target add thumbv7em-none-eabihf | ||||
|           rustup target add thumbv8m.base-none-eabi | ||||
|           rustup target add thumbv8m.main-none-eabi | ||||
|           rustup target add thumbv8m.main-none-eabihf | ||||
|  | ||||
|       - name: Install docserver | ||||
|         run: | | ||||
|           wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder" | ||||
|           chmod +x /usr/local/bin/builder | ||||
|  | ||||
|       - name: build-stm32 | ||||
|         if: ${{ matrix.crates=='stm32' }} | ||||
|         run: | | ||||
|           mkdir crates | ||||
|           builder ./embassy-stm32 crates/embassy-stm32/git.zup | ||||
|  | ||||
|       - name: build-rest | ||||
|         if: ${{ matrix.crates=='rest' }} | ||||
|         run: | | ||||
|           mkdir crates | ||||
|           builder ./embassy-boot/boot crates/embassy-boot/git.zup | ||||
|           builder ./embassy-boot/nrf crates/embassy-boot-nrf/git.zup | ||||
|           builder ./embassy-boot/stm32 crates/embassy-boot-stm32/git.zup | ||||
|           builder ./embassy-cortex-m crates/embassy-cortex-m/git.zup | ||||
|           builder ./embassy-embedded-hal crates/embassy-embedded-hal/git.zup | ||||
|           builder ./embassy-executor crates/embassy-executor/git.zup | ||||
|           builder ./embassy-futures crates/embassy-futures/git.zup | ||||
|           builder ./embassy-lora crates/embassy-lora/git.zup | ||||
|           builder ./embassy-net crates/embassy-net/git.zup | ||||
|           builder ./embassy-net-driver crates/embassy-net-driver/git.zup | ||||
|           builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup | ||||
|           builder ./embassy-nrf crates/embassy-nrf/git.zup | ||||
|           builder ./embassy-rp crates/embassy-rp/git.zup | ||||
|           builder ./embassy-sync crates/embassy-sync/git.zup | ||||
|           builder ./embassy-time crates/embassy-time/git.zup | ||||
|           builder ./embassy-usb crates/embassy-usb/git.zup | ||||
|           builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup | ||||
|           builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup | ||||
|  | ||||
|       - name: upload | ||||
|         run: | | ||||
|           mkdir -p ~/.kube | ||||
|           echo "${{secrets.KUBECONFIG}}" > ~/.kube/config | ||||
|           POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) | ||||
|           kubectl cp crates $POD:/data | ||||
|  | ||||
|            | ||||
							
								
								
									
										78
									
								
								.github/workflows/rust.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										78
									
								
								.github/workflows/rust.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,78 +0,0 @@ | ||||
| name: Rust | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [staging, trying, master] | ||||
|   pull_request: | ||||
|     branches: [master] | ||||
|  | ||||
| env: | ||||
|   CARGO_TERM_COLOR: always | ||||
|  | ||||
| jobs: | ||||
|   all: | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: [build-nightly, build-stable, test] | ||||
|     steps: | ||||
|       - name: Done | ||||
|         run: exit 0 | ||||
|   build-nightly: | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       id-token: write | ||||
|       contents: read | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: true | ||||
|       - name: Cache multiple paths | ||||
|         uses: actions/cache@v2 | ||||
|         with: | ||||
|           path: | | ||||
|             ~/.cargo/bin/ | ||||
|             ~/.cargo/registry/index/ | ||||
|             ~/.cargo/registry/cache/ | ||||
|             ~/.cargo/git/db/ | ||||
|             target_ci | ||||
|           key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} | ||||
|       - name: build | ||||
|         run: | | ||||
|           curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch | ||||
|           chmod +x /usr/local/bin/cargo-batch | ||||
|           ./ci.sh | ||||
|           rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* | ||||
|   build-stable: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: true | ||||
|       - name: Cache multiple paths | ||||
|         uses: actions/cache@v2 | ||||
|         with: | ||||
|           path: | | ||||
|             ~/.cargo/bin/ | ||||
|             ~/.cargo/registry/index/ | ||||
|             ~/.cargo/registry/cache/ | ||||
|             ~/.cargo/git/db/ | ||||
|             target_ci_stable | ||||
|           key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} | ||||
|       - name: build | ||||
|         run: | | ||||
|           curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch | ||||
|           chmod +x /usr/local/bin/cargo-batch | ||||
|           ./ci_stable.sh | ||||
|           rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* | ||||
|  | ||||
|   test: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|  | ||||
|       - name: Test boot | ||||
|         working-directory: ./embassy-boot/boot | ||||
|         run: cargo test && cargo test --features "ed25519-dalek" && cargo test --features "ed25519-salty" | ||||
|  | ||||
|       - name: Test sync | ||||
|         working-directory: ./embassy-sync | ||||
|         run: cargo test | ||||
							
								
								
									
										4
									
								
								.vscode/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.vscode/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| *.cortex-debug.*.json | ||||
| launch.json | ||||
| tasks.json | ||||
| *.cfg | ||||
							
								
								
									
										16
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -6,28 +6,36 @@ | ||||
|   "rust-analyzer.check.allTargets": false, | ||||
|   "rust-analyzer.check.noDefaultFeatures": true, | ||||
|   "rust-analyzer.cargo.noDefaultFeatures": true, | ||||
|   "rust-analyzer.showUnlinkedFileNotification": false, | ||||
|   // uncomment the target of your chip. | ||||
|   //"rust-analyzer.cargo.target": "thumbv6m-none-eabi", | ||||
|   //"rust-analyzer.cargo.target": "thumbv7m-none-eabi", | ||||
|   "rust-analyzer.cargo.target": "thumbv7em-none-eabi", | ||||
|   //"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", | ||||
|   "rust-analyzer.cargo.features": [ | ||||
|     // Uncomment if the example has a "nightly" feature. | ||||
|     "nightly", | ||||
|   ], | ||||
|   "rust-analyzer.linkedProjects": [ | ||||
|     // Declare for the target you wish to develop | ||||
|     // "embassy-executor/Cargo.toml", | ||||
|     // "embassy-sync/Cargo.toml", | ||||
|     // Uncomment ONE line for the chip you want to work on. | ||||
|     // This makes rust-analyzer work on the example crate and all its dependencies. | ||||
|     "examples/nrf52840/Cargo.toml", | ||||
|     // "examples/nrf52840-rtic/Cargo.toml", | ||||
|     // "examples/nrf5340/Cargo.toml", | ||||
|     // "examples/nrf-rtos-trace/Cargo.toml", | ||||
|     // "examples/rp/Cargo.toml", | ||||
|     // "examples/std/Cargo.toml", | ||||
|     // "examples/stm32c0/Cargo.toml", | ||||
|     // "examples/stm32f0/Cargo.toml", | ||||
|     // "examples/stm32f1/Cargo.toml", | ||||
|     // "examples/stm32f2/Cargo.toml", | ||||
|     // "examples/stm32f3/Cargo.toml", | ||||
|     // "examples/stm32f334/Cargo.toml", | ||||
|     // "examples/stm32f4/Cargo.toml", | ||||
|     // "examples/stm32f7/Cargo.toml", | ||||
|     // "examples/stm32g0/Cargo.toml", | ||||
|     // "examples/stm32g4/Cargo.toml", | ||||
|     // "examples/stm32h5/Cargo.toml", | ||||
|     // "examples/stm32h7/Cargo.toml", | ||||
|     // "examples/stm32l0/Cargo.toml", | ||||
|     // "examples/stm32l1/Cargo.toml", | ||||
| @@ -35,9 +43,7 @@ | ||||
|     // "examples/stm32l5/Cargo.toml", | ||||
|     // "examples/stm32u5/Cargo.toml", | ||||
|     // "examples/stm32wb/Cargo.toml", | ||||
|     // "examples/stm32wb55/Cargo.toml", | ||||
|     // "examples/stm32wl/Cargo.toml", | ||||
|     // "examples/stm32wl55/Cargo.toml", | ||||
|     // "examples/wasm/Cargo.toml", | ||||
|   ], | ||||
| } | ||||
							
								
								
									
										26
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								README.md
									
									
									
									
									
								
							| @@ -33,9 +33,10 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac | ||||
|  | ||||
| - **Bluetooth** -  | ||||
| The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. | ||||
| The <a href="https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan">embassy-stm32-wpan</a> crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers. | ||||
|  | ||||
| - **LoRa** -  | ||||
| <a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers. | ||||
| <a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking. | ||||
|  | ||||
| - **USB** -  | ||||
| <a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own. | ||||
| @@ -99,17 +100,10 @@ Examples are found in the `examples/` folder seperated by the chip manufacturer | ||||
|  | ||||
| ### Running examples | ||||
|  | ||||
| - Setup git submodules (needed for STM32 examples) | ||||
| - Install `probe-rs`. | ||||
|  | ||||
| ```bash | ||||
| git submodule init | ||||
| git submodule update | ||||
| ``` | ||||
|  | ||||
| - Install `probe-run` with defmt support. | ||||
|  | ||||
| ```bash | ||||
| cargo install probe-run | ||||
| cargo install probe-rs --features cli | ||||
| ``` | ||||
|  | ||||
| - Change directory to the sample's base directory. For example: | ||||
| @@ -118,14 +112,22 @@ cargo install probe-run | ||||
| cd examples/nrf52840 | ||||
| ``` | ||||
|  | ||||
| - Ensure `Cargo.toml` sets the right feature for the name of the chip you are programming. | ||||
|   If this name is incorrect, the example may fail to run or immediately crash | ||||
|   after being programmed. | ||||
|  | ||||
| - Ensure `.cargo/config.toml` contains the name of the chip you are programming. | ||||
|  | ||||
| - Run the example | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ```bash | ||||
| cargo run --bin blinky | ||||
| cargo run --release --bin blinky | ||||
| ``` | ||||
|  | ||||
| For more help getting started, see [Getting Started][1] and [Running the Examples][2]. | ||||
|  | ||||
| ## Developing Embassy with Rust Analyzer based editors | ||||
|  | ||||
| The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/) | ||||
| @@ -158,3 +160,5 @@ This work is licensed under either of | ||||
|  | ||||
| at your option. | ||||
|  | ||||
| [1]: https://github.com/embassy-rs/embassy/wiki/Getting-Started | ||||
| [2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples | ||||
|   | ||||
							
								
								
									
										134
									
								
								ci.sh
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								ci.sh
									
									
									
									
									
								
							| @@ -2,9 +2,8 @@ | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| export CARGO_TARGET_DIR=$PWD/target_ci | ||||
| export RUSTFLAGS=-Dwarnings | ||||
| export DEFMT_LOG=trace | ||||
| export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info | ||||
|  | ||||
| TARGET=$(rustc -vV | sed -n 's|host: ||p') | ||||
|  | ||||
| @@ -13,19 +12,40 @@ if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then | ||||
|     BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std" | ||||
| fi | ||||
|  | ||||
| find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check  --skip-children --unstable-features --edition 2018 | ||||
| find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check  --skip-children --unstable-features --edition 2021 | ||||
|  | ||||
| cargo batch  \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,integrated-timers \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,integrated-timers \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt,integrated-timers \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,integrated-timers \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread,integrated-timers \ | ||||
|     --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ | ||||
|     --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ | ||||
|     --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,nightly \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,nightly \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,nightly \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,nightly \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154,nightly \ | ||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \ | ||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \ | ||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ | ||||
| @@ -45,6 +65,21 @@ cargo batch  \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,qspi-as-gpio \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,time-driver-any \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,time-driver-any \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \ | ||||
| @@ -53,37 +88,49 @@ cargo batch  \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l422cb,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wb15cc,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l072cz,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f398ve,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f378cc,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32g0c1ve,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5jb,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32g474pe,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h503rb,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h562ag,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ | ||||
|     --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ | ||||
|     --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \ | ||||
|     --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ | ||||
|     --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\ | ||||
|     --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \ | ||||
|     --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \ | ||||
|     --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs' \ | ||||
|     --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \ | ||||
|     --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \ | ||||
|     --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \ | ||||
|     --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,nightly \ | ||||
|     --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,nightly \ | ||||
|     --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \ | ||||
|     --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4,nightly \ | ||||
|     --- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ | ||||
|     --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ | ||||
|     --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ | ||||
|     --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \ | ||||
|     --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \ | ||||
|     --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \ | ||||
|     --- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \ | ||||
|     --- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \ | ||||
|     --- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \ | ||||
|     --- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \ | ||||
|     --- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \ | ||||
|     --- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \ | ||||
|     --- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \ | ||||
|     --- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f334 \ | ||||
|     --- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \ | ||||
|     --- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f7 \ | ||||
|     --- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \ | ||||
| @@ -98,63 +145,40 @@ cargo batch  \ | ||||
|     --- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \ | ||||
|     --- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \ | ||||
|     --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \ | ||||
|     --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 --out-dir out/examples/boot/nrf --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns --out-dir out/examples/boot/nrf --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/rp --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f3 --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f7 --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32h7 --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/stm32l0 --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/boot/stm32l1 --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32l4 --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wl --bin b \ | ||||
|     --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ | ||||
|     --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf  \ | ||||
|     --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf \ | ||||
|     --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32h7 \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l0 \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ | ||||
|     --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \ | ||||
|     --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 --out-dir out/examples/bootloader/nrf \ | ||||
|     --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ | ||||
|     --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ | ||||
|     --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ | ||||
|     --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/bluepill-stm32f103c8 \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/nucleo-stm32f429zi \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/nucleo-stm32g491re \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/nucleo-stm32g071rb \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/nucleo-stm32c031c6 \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/nucleo-stm32h755zi \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/nucleo-stm32wb55rg \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/nucleo-stm32h563zi \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/iot-stm32u585ai \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \ | ||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \ | ||||
|     --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \ | ||||
|     --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \ | ||||
|     --- build --release --manifest-path tests/boot/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/boot/nrf52840-dk \ | ||||
|     --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ | ||||
|     $BUILD_EXTRA | ||||
|  | ||||
|  | ||||
| function run_elf { | ||||
|     echo Running target=$1 elf=$2 | ||||
|     STATUSCODE=$( | ||||
|         curl \ | ||||
|             -sS \ | ||||
|             --output /dev/stderr \ | ||||
|             --write-out "%{http_code}" \ | ||||
|             -H "Authorization: Bearer $TELEPROBE_TOKEN" \ | ||||
|             https://teleprobe.embassy.dev/targets/$1/run --data-binary @$2 | ||||
|     ) | ||||
|     echo | ||||
|     echo HTTP Status code: $STATUSCODE | ||||
|     test "$STATUSCODE" -eq 200 | ||||
| } | ||||
|  | ||||
| if [[ -z "${TELEPROBE_TOKEN-}" ]]; then | ||||
|     if [[ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN-}" ]]; then | ||||
|     echo No teleprobe token found, skipping running HIL tests | ||||
|     exit | ||||
| fi | ||||
|  | ||||
|     export TELEPROBE_TOKEN=$(curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value') | ||||
| fi | ||||
|  | ||||
| for board in $(ls out/tests); do  | ||||
|     echo Running tests for board: $board | ||||
|     for elf in $(ls out/tests/$board); do  | ||||
|         run_elf $board out/tests/$board/$elf | ||||
|     done | ||||
| done | ||||
| teleprobe client run -r out/tests | ||||
|   | ||||
							
								
								
									
										11
									
								
								ci_stable.sh
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								ci_stable.sh
									
									
									
									
									
								
							| @@ -2,19 +2,21 @@ | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| export CARGO_TARGET_DIR=$PWD/target_ci_stable | ||||
| export RUSTFLAGS=-Dwarnings | ||||
| export DEFMT_LOG=trace | ||||
|  | ||||
| sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml | ||||
|  | ||||
| cargo batch  \ | ||||
|     --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ | ||||
|     --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ | ||||
|     --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \ | ||||
|     --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \ | ||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ | ||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \ | ||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \ | ||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \ | ||||
| @@ -32,6 +34,7 @@ cargo batch  \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,defmt \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,log \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi \ | ||||
|     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features qspi-as-gpio \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g473cc,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,unstable-traits \ | ||||
|     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \ | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								cyw43-firmware/43439A0.bin
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cyw43-firmware/43439A0.bin
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								cyw43-firmware/43439A0_clm.bin
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cyw43-firmware/43439A0_clm.bin
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										49
									
								
								cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| Permissive Binary License | ||||
|  | ||||
| Version 1.0, July 2019 | ||||
|  | ||||
| Redistribution.  Redistribution and use in binary form, without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
| 1) Redistributions must reproduce the above copyright notice and the | ||||
|    following disclaimer in the documentation and/or other materials | ||||
|    provided with the distribution. | ||||
|  | ||||
| 2) Unless to the extent explicitly permitted by law, no reverse | ||||
|    engineering, decompilation, or disassembly of this software is | ||||
|    permitted. | ||||
|  | ||||
| 3) Redistribution as part of a software development kit must include the | ||||
|    accompanying file named <20>DEPENDENCIES<45> and any dependencies listed in | ||||
|    that file. | ||||
|  | ||||
| 4) Neither the name of the copyright holder nor the names of its | ||||
|    contributors may be used to endorse or promote products derived from | ||||
|    this software without specific prior written permission. | ||||
|  | ||||
| Limited patent license. The copyright holders (and contributors) grant a | ||||
| worldwide, non-exclusive, no-charge, royalty-free patent license to | ||||
| make, have made, use, offer to sell, sell, import, and otherwise | ||||
| transfer this software, where such license applies only to those patent | ||||
| claims licensable by the copyright holders (and contributors) that are | ||||
| necessarily infringed by this software. This patent license shall not | ||||
| apply to any combinations that include this software.  No hardware is | ||||
| licensed hereunder. | ||||
|  | ||||
| If you institute patent litigation against any entity (including a | ||||
| cross-claim or counterclaim in a lawsuit) alleging that the software | ||||
| itself infringes your patent(s), then your rights granted under this | ||||
| license shall terminate as of the date such litigation is filed. | ||||
|  | ||||
| DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||
| CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT | ||||
| NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||
| FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||
| TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||
| NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										9
									
								
								cyw43-firmware/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								cyw43-firmware/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| # WiFi firmware | ||||
|  | ||||
| Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439 | ||||
|  | ||||
| Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) | ||||
|  | ||||
| ## Changelog | ||||
|  | ||||
| * 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 ot 7.95.62 | ||||
							
								
								
									
										22
									
								
								cyw43-pio/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								cyw43-pio/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| [package] | ||||
| name = "cyw43-pio" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [features] | ||||
| # If disabled, SPI runs at 31.25MHz | ||||
| # If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet. | ||||
| overclock = [] | ||||
|  | ||||
| [dependencies] | ||||
| cyw43 = { version = "0.1.0", path = "../cyw43" } | ||||
| embassy-rp = { version = "0.1.0", path = "../embassy-rp" } | ||||
| pio-proc = "0.2" | ||||
| pio = "0.2.1" | ||||
| fixed = "1.23.1" | ||||
| defmt = { version = "0.3", optional = true } | ||||
|  | ||||
| [package.metadata.embassy_docs] | ||||
| src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/" | ||||
| src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/" | ||||
| target = "thumbv6m-none-eabi" | ||||
							
								
								
									
										227
									
								
								cyw43-pio/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								cyw43-pio/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | ||||
| #![no_std] | ||||
| #![allow(incomplete_features)] | ||||
| #![feature(async_fn_in_trait)] | ||||
|  | ||||
| use core::slice; | ||||
|  | ||||
| use cyw43::SpiBusCyw43; | ||||
| use embassy_rp::dma::Channel; | ||||
| use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate}; | ||||
| use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; | ||||
| use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef}; | ||||
| use fixed::FixedU32; | ||||
| use pio_proc::pio_asm; | ||||
|  | ||||
| pub struct PioSpi<'d, CS: Pin, PIO: Instance, const SM: usize, DMA> { | ||||
|     cs: Output<'d, CS>, | ||||
|     sm: StateMachine<'d, PIO, SM>, | ||||
|     irq: Irq<'d, PIO, 0>, | ||||
|     dma: PeripheralRef<'d, DMA>, | ||||
|     wrap_target: u8, | ||||
| } | ||||
|  | ||||
| impl<'d, CS, PIO, const SM: usize, DMA> PioSpi<'d, CS, PIO, SM, DMA> | ||||
| where | ||||
|     DMA: Channel, | ||||
|     CS: Pin, | ||||
|     PIO: Instance, | ||||
| { | ||||
|     pub fn new<DIO, CLK>( | ||||
|         common: &mut Common<'d, PIO>, | ||||
|         mut sm: StateMachine<'d, PIO, SM>, | ||||
|         irq: Irq<'d, PIO, 0>, | ||||
|         cs: Output<'d, CS>, | ||||
|         dio: DIO, | ||||
|         clk: CLK, | ||||
|         dma: impl Peripheral<P = DMA> + 'd, | ||||
|     ) -> Self | ||||
|     where | ||||
|         DIO: PioPin, | ||||
|         CLK: PioPin, | ||||
|     { | ||||
|         #[cfg(feature = "overclock")] | ||||
|         let program = pio_asm!( | ||||
|             ".side_set 1" | ||||
|  | ||||
|             ".wrap_target" | ||||
|             // write out x-1 bits | ||||
|             "lp:" | ||||
|             "out pins, 1    side 0" | ||||
|             "jmp x-- lp     side 1" | ||||
|             // switch directions | ||||
|             "set pindirs, 0 side 0" | ||||
|             "nop            side 1"  // necessary for clkdiv=1. | ||||
|             "nop            side 0" | ||||
|             // read in y-1 bits | ||||
|             "lp2:" | ||||
|             "in pins, 1     side 1" | ||||
|             "jmp y-- lp2    side 0" | ||||
|  | ||||
|             // wait for event and irq host | ||||
|             "wait 1 pin 0   side 0" | ||||
|             "irq 0          side 0" | ||||
|  | ||||
|             ".wrap" | ||||
|         ); | ||||
|         #[cfg(not(feature = "overclock"))] | ||||
|         let program = pio_asm!( | ||||
|             ".side_set 1" | ||||
|  | ||||
|             ".wrap_target" | ||||
|             // write out x-1 bits | ||||
|             "lp:" | ||||
|             "out pins, 1    side 0" | ||||
|             "jmp x-- lp     side 1" | ||||
|             // switch directions | ||||
|             "set pindirs, 0 side 0" | ||||
|             "nop            side 0" | ||||
|             // read in y-1 bits | ||||
|             "lp2:" | ||||
|             "in pins, 1     side 1" | ||||
|             "jmp y-- lp2    side 0" | ||||
|  | ||||
|             // wait for event and irq host | ||||
|             "wait 1 pin 0   side 0" | ||||
|             "irq 0          side 0" | ||||
|  | ||||
|             ".wrap" | ||||
|         ); | ||||
|  | ||||
|         let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio); | ||||
|         pin_io.set_pull(Pull::None); | ||||
|         pin_io.set_schmitt(true); | ||||
|         pin_io.set_input_sync_bypass(true); | ||||
|         pin_io.set_drive_strength(Drive::_12mA); | ||||
|         pin_io.set_slew_rate(SlewRate::Fast); | ||||
|  | ||||
|         let mut pin_clk = common.make_pio_pin(clk); | ||||
|         pin_clk.set_drive_strength(Drive::_12mA); | ||||
|         pin_clk.set_slew_rate(SlewRate::Fast); | ||||
|  | ||||
|         let mut cfg = Config::default(); | ||||
|         let loaded_program = common.load_program(&program.program); | ||||
|         cfg.use_program(&loaded_program, &[&pin_clk]); | ||||
|         cfg.set_out_pins(&[&pin_io]); | ||||
|         cfg.set_in_pins(&[&pin_io]); | ||||
|         cfg.set_set_pins(&[&pin_io]); | ||||
|         cfg.shift_out.direction = ShiftDirection::Left; | ||||
|         cfg.shift_out.auto_fill = true; | ||||
|         //cfg.shift_out.threshold = 32; | ||||
|         cfg.shift_in.direction = ShiftDirection::Left; | ||||
|         cfg.shift_in.auto_fill = true; | ||||
|         //cfg.shift_in.threshold = 32; | ||||
|  | ||||
|         #[cfg(feature = "overclock")] | ||||
|         { | ||||
|             // 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to | ||||
|             // data sheet, but seems to work fine. | ||||
|             cfg.clock_divider = FixedU32::from_bits(0x0100); | ||||
|         } | ||||
|  | ||||
|         #[cfg(not(feature = "overclock"))] | ||||
|         { | ||||
|             // same speed as pico-sdk, 62.5Mhz | ||||
|             // This is actually the fastest we can go without overclocking. | ||||
|             // According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. | ||||
|             // However, the PIO uses a fractional divider, which works by introducing jitter when | ||||
|             // the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz | ||||
|             // so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles | ||||
|             // violate the maximum from the data sheet. | ||||
|             cfg.clock_divider = FixedU32::from_bits(0x0200); | ||||
|         } | ||||
|  | ||||
|         sm.set_config(&cfg); | ||||
|  | ||||
|         sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]); | ||||
|         sm.set_pins(Level::Low, &[&pin_clk, &pin_io]); | ||||
|  | ||||
|         Self { | ||||
|             cs, | ||||
|             sm, | ||||
|             irq, | ||||
|             dma: dma.into_ref(), | ||||
|             wrap_target: loaded_program.wrap.target, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn write(&mut self, write: &[u32]) -> u32 { | ||||
|         self.sm.set_enable(false); | ||||
|         let write_bits = write.len() * 32 - 1; | ||||
|         let read_bits = 31; | ||||
|  | ||||
|         #[cfg(feature = "defmt")] | ||||
|         defmt::trace!("write={} read={}", write_bits, read_bits); | ||||
|  | ||||
|         unsafe { | ||||
|             pio_instr_util::set_x(&mut self.sm, write_bits as u32); | ||||
|             pio_instr_util::set_y(&mut self.sm, read_bits as u32); | ||||
|             pio_instr_util::set_pindir(&mut self.sm, 0b1); | ||||
|             pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); | ||||
|         } | ||||
|  | ||||
|         self.sm.set_enable(true); | ||||
|  | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), write).await; | ||||
|  | ||||
|         let mut status = 0; | ||||
|         self.sm | ||||
|             .rx() | ||||
|             .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) | ||||
|             .await; | ||||
|         status | ||||
|     } | ||||
|  | ||||
|     pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) -> u32 { | ||||
|         self.sm.set_enable(false); | ||||
|         let write_bits = 31; | ||||
|         let read_bits = read.len() * 32 + 32 - 1; | ||||
|  | ||||
|         #[cfg(feature = "defmt")] | ||||
|         defmt::trace!("write={} read={}", write_bits, read_bits); | ||||
|  | ||||
|         unsafe { | ||||
|             pio_instr_util::set_y(&mut self.sm, read_bits as u32); | ||||
|             pio_instr_util::set_x(&mut self.sm, write_bits as u32); | ||||
|             pio_instr_util::set_pindir(&mut self.sm, 0b1); | ||||
|             pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); | ||||
|         } | ||||
|  | ||||
|         // self.cs.set_low(); | ||||
|         self.sm.set_enable(true); | ||||
|  | ||||
|         self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await; | ||||
|         self.sm.rx().dma_pull(self.dma.reborrow(), read).await; | ||||
|  | ||||
|         let mut status = 0; | ||||
|         self.sm | ||||
|             .rx() | ||||
|             .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) | ||||
|             .await; | ||||
|         status | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'d, CS, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, CS, PIO, SM, DMA> | ||||
| where | ||||
|     CS: Pin, | ||||
|     PIO: Instance, | ||||
|     DMA: Channel, | ||||
| { | ||||
|     async fn cmd_write(&mut self, write: &[u32]) -> u32 { | ||||
|         self.cs.set_low(); | ||||
|         let status = self.write(write).await; | ||||
|         self.cs.set_high(); | ||||
|         status | ||||
|     } | ||||
|  | ||||
|     async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 { | ||||
|         self.cs.set_low(); | ||||
|         let status = self.cmd_read(write, read).await; | ||||
|         self.cs.set_high(); | ||||
|         status | ||||
|     } | ||||
|  | ||||
|     async fn wait_for_event(&mut self) { | ||||
|         self.irq.wait().await; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								cyw43/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								cyw43/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| [package] | ||||
| name = "cyw43" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [features] | ||||
| defmt = ["dep:defmt"] | ||||
| log = ["dep:log"] | ||||
|  | ||||
| # Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. | ||||
| firmware-logs = [] | ||||
|  | ||||
| [dependencies] | ||||
| embassy-time = { version = "0.1.3", path = "../embassy-time"} | ||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync"} | ||||
| embassy-futures = { version = "0.1.0", path = "../embassy-futures"} | ||||
| embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} | ||||
| atomic-polyfill = "0.1.5" | ||||
|  | ||||
| defmt = { version = "0.3", optional = true } | ||||
| log = { version = "0.4.17", optional = true } | ||||
|  | ||||
| cortex-m = "0.7.6" | ||||
| cortex-m-rt = "0.7.0" | ||||
| futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } | ||||
|  | ||||
| embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-rc.1" } | ||||
| num_enum = { version = "0.5.7", default-features = false } | ||||
|  | ||||
| [package.metadata.embassy_docs] | ||||
| src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" | ||||
| src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" | ||||
| target = "thumbv6m-none-eabi" | ||||
| features = ["defmt", "firmware-logs"] | ||||
							
								
								
									
										57
									
								
								cyw43/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								cyw43/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| # cyw43 | ||||
|  | ||||
| Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). | ||||
|  | ||||
| ## Current status | ||||
|  | ||||
| Working: | ||||
|  | ||||
| - Station mode (joining an AP). | ||||
| - AP mode (creating an AP) | ||||
| - Scanning | ||||
| - Sending and receiving Ethernet frames. | ||||
| - Using the default MAC address. | ||||
| - [`embassy-net`](https://embassy.dev) integration. | ||||
| - RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. | ||||
| - Using IRQ for device events | ||||
| - GPIO support (for LED on the Pico W) | ||||
|  | ||||
| TODO: | ||||
|  | ||||
| - Setting a custom MAC address. | ||||
| - Bus sleep (for power consumption optimization) | ||||
|  | ||||
| ## Running the examples | ||||
|  | ||||
| - `cargo install probe-rs --features cli` | ||||
| - `cd examples/rp` | ||||
| ### Example 1: Scan the wifi stations | ||||
| - `cargo run --release --bin wifi_scan` | ||||
| ### Example 2: Create an access point (IP and credentials in the code) | ||||
| - `cargo run --release --bin wifi_ap_tcp_server` | ||||
| ### Example 3: Connect to an existing network and create a server | ||||
| - `cargo run --release --bin wifi_tcp_server` | ||||
|  | ||||
| After a few seconds, you should see that DHCP picks up an IP address like this | ||||
| ``` | ||||
| 11.944489 DEBUG Acquired IP configuration: | ||||
| 11.944517 DEBUG    IP address:      192.168.0.250/24 | ||||
| 11.944620 DEBUG    Default gateway: 192.168.0.33 | ||||
| 11.944722 DEBUG    DNS server 0:    192.168.0.33 | ||||
| ``` | ||||
| This example implements a TCP echo server on port 1234. You can try connecting to it with: | ||||
| ``` | ||||
| nc 192.168.0.250 1234 | ||||
| ``` | ||||
| Send it some data, you should see it echoed back and printed in the firmware's logs. | ||||
|  | ||||
| ## License | ||||
|  | ||||
| This work is licensed under either of | ||||
|  | ||||
| - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||||
|   <http://www.apache.org/licenses/LICENSE-2.0>) | ||||
| - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||||
|  | ||||
| at your option. | ||||
|  | ||||
							
								
								
									
										328
									
								
								cyw43/src/bus.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										328
									
								
								cyw43/src/bus.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,328 @@ | ||||
| use embassy_futures::yield_now; | ||||
| use embassy_time::{Duration, Timer}; | ||||
| use embedded_hal_1::digital::OutputPin; | ||||
| use futures::FutureExt; | ||||
|  | ||||
| use crate::consts::*; | ||||
| use crate::slice8_mut; | ||||
|  | ||||
| /// Custom Spi Trait that _only_ supports the bus operation of the cyw43 | ||||
| /// Implementors are expected to hold the CS pin low during an operation. | ||||
| pub trait SpiBusCyw43 { | ||||
|     /// Issues a write command on the bus | ||||
|     /// First 32 bits of `word` are expected to be a cmd word | ||||
|     async fn cmd_write(&mut self, write: &[u32]) -> u32; | ||||
|  | ||||
|     /// Issues a read command on the bus | ||||
|     /// `write` is expected to be a 32 bit cmd word | ||||
|     /// `read` will contain the response of the device | ||||
|     /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. | ||||
|     /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. | ||||
|     async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32; | ||||
|  | ||||
|     /// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high. | ||||
|     /// The default implementation always reports ready, resulting in active polling of the device. | ||||
|     async fn wait_for_event(&mut self) { | ||||
|         yield_now().await; | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(crate) struct Bus<PWR, SPI> { | ||||
|     backplane_window: u32, | ||||
|     pwr: PWR, | ||||
|     spi: SPI, | ||||
|     status: u32, | ||||
| } | ||||
|  | ||||
| impl<PWR, SPI> Bus<PWR, SPI> | ||||
| where | ||||
|     PWR: OutputPin, | ||||
|     SPI: SpiBusCyw43, | ||||
| { | ||||
|     pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { | ||||
|         Self { | ||||
|             backplane_window: 0xAAAA_AAAA, | ||||
|             pwr, | ||||
|             spi, | ||||
|             status: 0, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn init(&mut self) { | ||||
|         // Reset | ||||
|         self.pwr.set_low().unwrap(); | ||||
|         Timer::after(Duration::from_millis(20)).await; | ||||
|         self.pwr.set_high().unwrap(); | ||||
|         Timer::after(Duration::from_millis(250)).await; | ||||
|  | ||||
|         while self | ||||
|             .read32_swapped(REG_BUS_TEST_RO) | ||||
|             .inspect(|v| trace!("{:#x}", v)) | ||||
|             .await | ||||
|             != FEEDBEAD | ||||
|         {} | ||||
|  | ||||
|         self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; | ||||
|         let val = self.read32_swapped(REG_BUS_TEST_RW).await; | ||||
|         trace!("{:#x}", val); | ||||
|         assert_eq!(val, TEST_PATTERN); | ||||
|  | ||||
|         let val = self.read32_swapped(REG_BUS_CTRL).await; | ||||
|         trace!("{:#010b}", (val & 0xff)); | ||||
|  | ||||
|         // 32-bit word length, little endian (which is the default endianess). | ||||
|         self.write32_swapped( | ||||
|             REG_BUS_CTRL, | ||||
|             WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE | INTERRUPT_WITH_STATUS, | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|         let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; | ||||
|         trace!("{:#b}", val); | ||||
|  | ||||
|         let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; | ||||
|         trace!("{:#x}", val); | ||||
|         assert_eq!(val, FEEDBEAD); | ||||
|         let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; | ||||
|         trace!("{:#x}", val); | ||||
|         assert_eq!(val, TEST_PATTERN); | ||||
|     } | ||||
|  | ||||
|     pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { | ||||
|         let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); | ||||
|         let len_in_u32 = (len_in_u8 as usize + 3) / 4; | ||||
|  | ||||
|         self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; | ||||
|     } | ||||
|  | ||||
|     pub async fn wlan_write(&mut self, buf: &[u32]) { | ||||
|         let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); | ||||
|         //TODO try to remove copy? | ||||
|         let mut cmd_buf = [0_u32; 513]; | ||||
|         cmd_buf[0] = cmd; | ||||
|         cmd_buf[1..][..buf.len()].copy_from_slice(buf); | ||||
|  | ||||
|         self.status = self.spi.cmd_write(&cmd_buf[..buf.len() + 1]).await; | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { | ||||
|         // It seems the HW force-aligns the addr | ||||
|         // to 2 if data.len() >= 2 | ||||
|         // to 4 if data.len() >= 4 | ||||
|         // To simplify, enforce 4-align for now. | ||||
|         assert!(addr % 4 == 0); | ||||
|  | ||||
|         // Backplane read buffer has one extra word for the response delay. | ||||
|         let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; | ||||
|  | ||||
|         while !data.is_empty() { | ||||
|             // Ensure transfer doesn't cross a window boundary. | ||||
|             let window_offs = addr & BACKPLANE_ADDRESS_MASK; | ||||
|             let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; | ||||
|  | ||||
|             let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); | ||||
|  | ||||
|             self.backplane_set_window(addr).await; | ||||
|  | ||||
|             let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); | ||||
|  | ||||
|             // round `buf` to word boundary, add one extra word for the response delay | ||||
|             self.status = self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; | ||||
|  | ||||
|             // when writing out the data, we skip the response-delay byte | ||||
|             data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); | ||||
|  | ||||
|             // Advance ptr. | ||||
|             addr += len as u32; | ||||
|             data = &mut data[len..]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { | ||||
|         // It seems the HW force-aligns the addr | ||||
|         // to 2 if data.len() >= 2 | ||||
|         // to 4 if data.len() >= 4 | ||||
|         // To simplify, enforce 4-align for now. | ||||
|         assert!(addr % 4 == 0); | ||||
|  | ||||
|         let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; | ||||
|  | ||||
|         while !data.is_empty() { | ||||
|             // Ensure transfer doesn't cross a window boundary. | ||||
|             let window_offs = addr & BACKPLANE_ADDRESS_MASK; | ||||
|             let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; | ||||
|  | ||||
|             let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); | ||||
|             slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]); | ||||
|  | ||||
|             self.backplane_set_window(addr).await; | ||||
|  | ||||
|             let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); | ||||
|             buf[0] = cmd; | ||||
|  | ||||
|             self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; | ||||
|  | ||||
|             // Advance ptr. | ||||
|             addr += len as u32; | ||||
|             data = &data[len..]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn bp_read8(&mut self, addr: u32) -> u8 { | ||||
|         self.backplane_readn(addr, 1).await as u8 | ||||
|     } | ||||
|  | ||||
|     pub async fn bp_write8(&mut self, addr: u32, val: u8) { | ||||
|         self.backplane_writen(addr, val as u32, 1).await | ||||
|     } | ||||
|  | ||||
|     pub async fn bp_read16(&mut self, addr: u32) -> u16 { | ||||
|         self.backplane_readn(addr, 2).await as u16 | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     pub async fn bp_write16(&mut self, addr: u32, val: u16) { | ||||
|         self.backplane_writen(addr, val as u32, 2).await | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     pub async fn bp_read32(&mut self, addr: u32) -> u32 { | ||||
|         self.backplane_readn(addr, 4).await | ||||
|     } | ||||
|  | ||||
|     pub async fn bp_write32(&mut self, addr: u32, val: u32) { | ||||
|         self.backplane_writen(addr, val, 4).await | ||||
|     } | ||||
|  | ||||
|     async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { | ||||
|         self.backplane_set_window(addr).await; | ||||
|  | ||||
|         let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; | ||||
|         if len == 4 { | ||||
|             bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG | ||||
|         } | ||||
|         self.readn(FUNC_BACKPLANE, bus_addr, len).await | ||||
|     } | ||||
|  | ||||
|     async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { | ||||
|         self.backplane_set_window(addr).await; | ||||
|  | ||||
|         let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; | ||||
|         if len == 4 { | ||||
|             bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG | ||||
|         } | ||||
|         self.writen(FUNC_BACKPLANE, bus_addr, val, len).await | ||||
|     } | ||||
|  | ||||
|     async fn backplane_set_window(&mut self, addr: u32) { | ||||
|         let new_window = addr & !BACKPLANE_ADDRESS_MASK; | ||||
|  | ||||
|         if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { | ||||
|             self.write8( | ||||
|                 FUNC_BACKPLANE, | ||||
|                 REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, | ||||
|                 (new_window >> 24) as u8, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|         if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { | ||||
|             self.write8( | ||||
|                 FUNC_BACKPLANE, | ||||
|                 REG_BACKPLANE_BACKPLANE_ADDRESS_MID, | ||||
|                 (new_window >> 16) as u8, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|         if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { | ||||
|             self.write8( | ||||
|                 FUNC_BACKPLANE, | ||||
|                 REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, | ||||
|                 (new_window >> 8) as u8, | ||||
|             ) | ||||
|             .await; | ||||
|         } | ||||
|         self.backplane_window = new_window; | ||||
|     } | ||||
|  | ||||
|     pub async fn read8(&mut self, func: u32, addr: u32) -> u8 { | ||||
|         self.readn(func, addr, 1).await as u8 | ||||
|     } | ||||
|  | ||||
|     pub async fn write8(&mut self, func: u32, addr: u32, val: u8) { | ||||
|         self.writen(func, addr, val as u32, 1).await | ||||
|     } | ||||
|  | ||||
|     pub async fn read16(&mut self, func: u32, addr: u32) -> u16 { | ||||
|         self.readn(func, addr, 2).await as u16 | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     pub async fn write16(&mut self, func: u32, addr: u32, val: u16) { | ||||
|         self.writen(func, addr, val as u32, 2).await | ||||
|     } | ||||
|  | ||||
|     pub async fn read32(&mut self, func: u32, addr: u32) -> u32 { | ||||
|         self.readn(func, addr, 4).await | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     pub async fn write32(&mut self, func: u32, addr: u32, val: u32) { | ||||
|         self.writen(func, addr, val, 4).await | ||||
|     } | ||||
|  | ||||
|     async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { | ||||
|         let cmd = cmd_word(READ, INC_ADDR, func, addr, len); | ||||
|         let mut buf = [0; 2]; | ||||
|         // if we are reading from the backplane, we need an extra word for the response delay | ||||
|         let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; | ||||
|  | ||||
|         self.status = self.spi.cmd_read(cmd, &mut buf[..len]).await; | ||||
|  | ||||
|         // if we read from the backplane, the result is in the second word, after the response delay | ||||
|         if func == FUNC_BACKPLANE { | ||||
|             buf[1] | ||||
|         } else { | ||||
|             buf[0] | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { | ||||
|         let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); | ||||
|  | ||||
|         self.status = self.spi.cmd_write(&[cmd, val]).await; | ||||
|     } | ||||
|  | ||||
|     async fn read32_swapped(&mut self, addr: u32) -> u32 { | ||||
|         let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); | ||||
|         let cmd = swap16(cmd); | ||||
|         let mut buf = [0; 1]; | ||||
|  | ||||
|         self.status = self.spi.cmd_read(cmd, &mut buf).await; | ||||
|  | ||||
|         swap16(buf[0]) | ||||
|     } | ||||
|  | ||||
|     async fn write32_swapped(&mut self, addr: u32, val: u32) { | ||||
|         let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); | ||||
|         let buf = [swap16(cmd), swap16(val)]; | ||||
|  | ||||
|         self.status = self.spi.cmd_write(&buf).await; | ||||
|     } | ||||
|  | ||||
|     pub async fn wait_for_event(&mut self) { | ||||
|         self.spi.wait_for_event().await; | ||||
|     } | ||||
|  | ||||
|     pub fn status(&self) -> u32 { | ||||
|         self.status | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn swap16(x: u32) -> u32 { | ||||
|     x.rotate_left(16) | ||||
| } | ||||
|  | ||||
| fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { | ||||
|     (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) | ||||
| } | ||||
							
								
								
									
										319
									
								
								cyw43/src/consts.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								cyw43/src/consts.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,319 @@ | ||||
| #![allow(unused)] | ||||
|  | ||||
| pub(crate) const FUNC_BUS: u32 = 0; | ||||
| pub(crate) const FUNC_BACKPLANE: u32 = 1; | ||||
| pub(crate) const FUNC_WLAN: u32 = 2; | ||||
| pub(crate) const FUNC_BT: u32 = 3; | ||||
|  | ||||
| pub(crate) const REG_BUS_CTRL: u32 = 0x0; | ||||
| pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status | ||||
| pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask | ||||
| pub(crate) const REG_BUS_STATUS: u32 = 0x8; | ||||
| pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; | ||||
| pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; | ||||
| pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; | ||||
| pub(crate) const WORD_LENGTH_32: u32 = 0x1; | ||||
| pub(crate) const HIGH_SPEED: u32 = 0x10; | ||||
| pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5; | ||||
| pub(crate) const WAKE_UP: u32 = 1 << 7; | ||||
| pub(crate) const STATUS_ENABLE: u32 = 1 << 16; | ||||
| pub(crate) const INTERRUPT_WITH_STATUS: u32 = 1 << 17; | ||||
|  | ||||
| // SPI_STATUS_REGISTER bits | ||||
| pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; | ||||
| pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002; | ||||
| pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004; | ||||
| pub(crate) const STATUS_F2_INTR: u32 = 0x00000008; | ||||
| pub(crate) const STATUS_F3_INTR: u32 = 0x00000010; | ||||
| pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020; | ||||
| pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040; | ||||
| pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; | ||||
| pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; | ||||
| pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; | ||||
| pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; | ||||
| pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; | ||||
| pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; | ||||
| pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; | ||||
|  | ||||
| pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; | ||||
| pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; | ||||
| pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; | ||||
| pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; | ||||
| pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; | ||||
| pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; | ||||
| pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; | ||||
| pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; | ||||
| pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; | ||||
| pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; | ||||
| pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; | ||||
| pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; | ||||
| pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; | ||||
| pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; | ||||
| pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; | ||||
|  | ||||
| pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; | ||||
| pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; | ||||
| pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; | ||||
| pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; | ||||
| // Active Low Power (ALP) clock constants | ||||
| pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; | ||||
| pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; | ||||
|  | ||||
| // Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect | ||||
| // (AI) pub (crate) constants | ||||
| pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408; | ||||
| pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002; | ||||
| pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; | ||||
| pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; | ||||
|  | ||||
| pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800; | ||||
| pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1; | ||||
|  | ||||
| pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804; | ||||
|  | ||||
| pub(crate) const TEST_PATTERN: u32 = 0x12345678; | ||||
| pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD; | ||||
|  | ||||
| // SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits | ||||
| pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" | ||||
| pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; | ||||
| pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; | ||||
| pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 | ||||
| pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 | ||||
| pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; | ||||
| pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; | ||||
| pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests | ||||
| pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100; | ||||
| pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200; | ||||
| pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400; | ||||
| pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800; | ||||
| pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000; | ||||
| pub(crate) const IRQ_F1_INTR: u16 = 0x2000; | ||||
| pub(crate) const IRQ_F2_INTR: u16 = 0x4000; | ||||
| pub(crate) const IRQ_F3_INTR: u16 = 0x8000; | ||||
|  | ||||
| pub(crate) const IOCTL_CMD_UP: u32 = 2; | ||||
| pub(crate) const IOCTL_CMD_DOWN: u32 = 3; | ||||
| pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; | ||||
| pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; | ||||
| pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52; | ||||
| pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; | ||||
| pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; | ||||
| pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; | ||||
| pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; | ||||
| pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; | ||||
|  | ||||
| pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; | ||||
| pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; | ||||
| pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; | ||||
|  | ||||
| // CYW_SPID command structure constants. | ||||
| pub(crate) const WRITE: bool = true; | ||||
| pub(crate) const READ: bool = false; | ||||
| pub(crate) const INC_ADDR: bool = true; | ||||
| pub(crate) const FIXED_ADDR: bool = false; | ||||
|  | ||||
| pub(crate) const AES_ENABLED: u32 = 0x0004; | ||||
| pub(crate) const WPA2_SECURITY: u32 = 0x00400000; | ||||
|  | ||||
| pub(crate) const MIN_PSK_LEN: usize = 8; | ||||
| pub(crate) const MAX_PSK_LEN: usize = 64; | ||||
|  | ||||
| // Security type (authentication and encryption types are combined using bit mask) | ||||
| #[allow(non_camel_case_types)] | ||||
| #[derive(Copy, Clone, PartialEq)] | ||||
| #[repr(u32)] | ||||
| pub(crate) enum Security { | ||||
|     OPEN = 0, | ||||
|     WPA2_AES_PSK = WPA2_SECURITY | AES_ENABLED, | ||||
| } | ||||
|  | ||||
| #[allow(non_camel_case_types)] | ||||
| #[derive(Copy, Clone)] | ||||
| #[repr(u8)] | ||||
| pub enum EStatus { | ||||
|     /// operation was successful | ||||
|     SUCCESS = 0, | ||||
|     /// operation failed | ||||
|     FAIL = 1, | ||||
|     /// operation timed out | ||||
|     TIMEOUT = 2, | ||||
|     /// failed due to no matching network found | ||||
|     NO_NETWORKS = 3, | ||||
|     /// operation was aborted | ||||
|     ABORT = 4, | ||||
|     /// protocol failure: packet not ack'd | ||||
|     NO_ACK = 5, | ||||
|     /// AUTH or ASSOC packet was unsolicited | ||||
|     UNSOLICITED = 6, | ||||
|     /// attempt to assoc to an auto auth configuration | ||||
|     ATTEMPT = 7, | ||||
|     /// scan results are incomplete | ||||
|     PARTIAL = 8, | ||||
|     /// scan aborted by another scan | ||||
|     NEWSCAN = 9, | ||||
|     /// scan aborted due to assoc in progress | ||||
|     NEWASSOC = 10, | ||||
|     /// 802.11h quiet period started | ||||
|     _11HQUIET = 11, | ||||
|     /// user disabled scanning (WLC_SET_SCANSUPPRESS) | ||||
|     SUPPRESS = 12, | ||||
|     /// no allowable channels to scan | ||||
|     NOCHANS = 13, | ||||
|     /// scan aborted due to CCX fast roam | ||||
|     CCXFASTRM = 14, | ||||
|     /// abort channel select | ||||
|     CS_ABORT = 15, | ||||
| } | ||||
|  | ||||
| impl PartialEq<EStatus> for u32 { | ||||
|     fn eq(&self, other: &EStatus) -> bool { | ||||
|         *self == *other as Self | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| pub(crate) struct FormatStatus(pub u32); | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl defmt::Format for FormatStatus { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         macro_rules! implm { | ||||
|             ($($name:ident),*) => { | ||||
|                 $( | ||||
|                     if self.0 & $name > 0 { | ||||
|                         defmt::write!(fmt, " | {}", &stringify!($name)[7..]); | ||||
|                     } | ||||
|                 )* | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         implm!( | ||||
|             STATUS_DATA_NOT_AVAILABLE, | ||||
|             STATUS_UNDERFLOW, | ||||
|             STATUS_OVERFLOW, | ||||
|             STATUS_F2_INTR, | ||||
|             STATUS_F3_INTR, | ||||
|             STATUS_F2_RX_READY, | ||||
|             STATUS_F3_RX_READY, | ||||
|             STATUS_HOST_CMD_DATA_ERR, | ||||
|             STATUS_F2_PKT_AVAILABLE, | ||||
|             STATUS_F3_PKT_AVAILABLE | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "log")] | ||||
| impl core::fmt::Debug for FormatStatus { | ||||
|     fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { | ||||
|         macro_rules! implm { | ||||
|             ($($name:ident),*) => { | ||||
|                 $( | ||||
|                     if self.0 & $name > 0 { | ||||
|                         core::write!(fmt, " | {}", &stringify!($name)[7..])?; | ||||
|                     } | ||||
|                 )* | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         implm!( | ||||
|             STATUS_DATA_NOT_AVAILABLE, | ||||
|             STATUS_UNDERFLOW, | ||||
|             STATUS_OVERFLOW, | ||||
|             STATUS_F2_INTR, | ||||
|             STATUS_F3_INTR, | ||||
|             STATUS_F2_RX_READY, | ||||
|             STATUS_F3_RX_READY, | ||||
|             STATUS_HOST_CMD_DATA_ERR, | ||||
|             STATUS_F2_PKT_AVAILABLE, | ||||
|             STATUS_F3_PKT_AVAILABLE | ||||
|         ); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "log")] | ||||
| impl core::fmt::Display for FormatStatus { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         core::fmt::Debug::fmt(self, f) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| pub(crate) struct FormatInterrupt(pub u16); | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl defmt::Format for FormatInterrupt { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         macro_rules! implm { | ||||
|             ($($name:ident),*) => { | ||||
|                 $( | ||||
|                     if self.0 & $name > 0 { | ||||
|                         defmt::write!(fmt, " | {}", &stringify!($name)[4..]); | ||||
|                     } | ||||
|                 )* | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         implm!( | ||||
|             IRQ_DATA_UNAVAILABLE, | ||||
|             IRQ_F2_F3_FIFO_RD_UNDERFLOW, | ||||
|             IRQ_F2_F3_FIFO_WR_OVERFLOW, | ||||
|             IRQ_COMMAND_ERROR, | ||||
|             IRQ_DATA_ERROR, | ||||
|             IRQ_F2_PACKET_AVAILABLE, | ||||
|             IRQ_F3_PACKET_AVAILABLE, | ||||
|             IRQ_F1_OVERFLOW, | ||||
|             IRQ_MISC_INTR0, | ||||
|             IRQ_MISC_INTR1, | ||||
|             IRQ_MISC_INTR2, | ||||
|             IRQ_MISC_INTR3, | ||||
|             IRQ_MISC_INTR4, | ||||
|             IRQ_F1_INTR, | ||||
|             IRQ_F2_INTR, | ||||
|             IRQ_F3_INTR | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "log")] | ||||
| impl core::fmt::Debug for FormatInterrupt { | ||||
|     fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { | ||||
|         macro_rules! implm { | ||||
|             ($($name:ident),*) => { | ||||
|                 $( | ||||
|                     if self.0 & $name > 0 { | ||||
|                         core::write!(fmt, " | {}", &stringify!($name)[7..])?; | ||||
|                     } | ||||
|                 )* | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         implm!( | ||||
|             IRQ_DATA_UNAVAILABLE, | ||||
|             IRQ_F2_F3_FIFO_RD_UNDERFLOW, | ||||
|             IRQ_F2_F3_FIFO_WR_OVERFLOW, | ||||
|             IRQ_COMMAND_ERROR, | ||||
|             IRQ_DATA_ERROR, | ||||
|             IRQ_F2_PACKET_AVAILABLE, | ||||
|             IRQ_F3_PACKET_AVAILABLE, | ||||
|             IRQ_F1_OVERFLOW, | ||||
|             IRQ_MISC_INTR0, | ||||
|             IRQ_MISC_INTR1, | ||||
|             IRQ_MISC_INTR2, | ||||
|             IRQ_MISC_INTR3, | ||||
|             IRQ_MISC_INTR4, | ||||
|             IRQ_F1_INTR, | ||||
|             IRQ_F2_INTR, | ||||
|             IRQ_F3_INTR | ||||
|         ); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "log")] | ||||
| impl core::fmt::Display for FormatInterrupt { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         core::fmt::Debug::fmt(self, f) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										469
									
								
								cyw43/src/control.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										469
									
								
								cyw43/src/control.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,469 @@ | ||||
| use core::cmp::{max, min}; | ||||
|  | ||||
| use ch::driver::LinkState; | ||||
| use embassy_net_driver_channel as ch; | ||||
| use embassy_time::{Duration, Timer}; | ||||
|  | ||||
| pub use crate::bus::SpiBusCyw43; | ||||
| use crate::consts::*; | ||||
| use crate::events::{Event, EventSubscriber, Events}; | ||||
| use crate::fmt::Bytes; | ||||
| use crate::ioctl::{IoctlState, IoctlType}; | ||||
| use crate::structs::*; | ||||
| use crate::{countries, events, PowerManagementMode}; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Error { | ||||
|     pub status: u32, | ||||
| } | ||||
|  | ||||
| pub struct Control<'a> { | ||||
|     state_ch: ch::StateRunner<'a>, | ||||
|     events: &'a Events, | ||||
|     ioctl_state: &'a IoctlState, | ||||
| } | ||||
|  | ||||
| impl<'a> Control<'a> { | ||||
|     pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { | ||||
|         Self { | ||||
|             state_ch, | ||||
|             events: event_sub, | ||||
|             ioctl_state, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn init(&mut self, clm: &[u8]) { | ||||
|         const CHUNK_SIZE: usize = 1024; | ||||
|  | ||||
|         debug!("Downloading CLM..."); | ||||
|  | ||||
|         let mut offs = 0; | ||||
|         for chunk in clm.chunks(CHUNK_SIZE) { | ||||
|             let mut flag = DOWNLOAD_FLAG_HANDLER_VER; | ||||
|             if offs == 0 { | ||||
|                 flag |= DOWNLOAD_FLAG_BEGIN; | ||||
|             } | ||||
|             offs += chunk.len(); | ||||
|             if offs == clm.len() { | ||||
|                 flag |= DOWNLOAD_FLAG_END; | ||||
|             } | ||||
|  | ||||
|             let header = DownloadHeader { | ||||
|                 flag, | ||||
|                 dload_type: DOWNLOAD_TYPE_CLM, | ||||
|                 len: chunk.len() as _, | ||||
|                 crc: 0, | ||||
|             }; | ||||
|             let mut buf = [0; 8 + 12 + CHUNK_SIZE]; | ||||
|             buf[0..8].copy_from_slice(b"clmload\x00"); | ||||
|             buf[8..20].copy_from_slice(&header.to_bytes()); | ||||
|             buf[20..][..chunk.len()].copy_from_slice(&chunk); | ||||
|             self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) | ||||
|                 .await; | ||||
|         } | ||||
|  | ||||
|         // check clmload ok | ||||
|         assert_eq!(self.get_iovar_u32("clmload_status").await, 0); | ||||
|  | ||||
|         debug!("Configuring misc stuff..."); | ||||
|  | ||||
|         // Disable tx gloming which transfers multiple packets in one request. | ||||
|         // 'glom' is short for "conglomerate" which means "gather together into | ||||
|         // a compact mass". | ||||
|         self.set_iovar_u32("bus:txglom", 0).await; | ||||
|         self.set_iovar_u32("apsta", 1).await; | ||||
|  | ||||
|         // read MAC addr. | ||||
|         let mut mac_addr = [0; 6]; | ||||
|         assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); | ||||
|         debug!("mac addr: {:02x}", Bytes(&mac_addr)); | ||||
|  | ||||
|         let country = countries::WORLD_WIDE_XX; | ||||
|         let country_info = CountryInfo { | ||||
|             country_abbrev: [country.code[0], country.code[1], 0, 0], | ||||
|             country_code: [country.code[0], country.code[1], 0, 0], | ||||
|             rev: if country.rev == 0 { -1 } else { country.rev as _ }, | ||||
|         }; | ||||
|         self.set_iovar("country", &country_info.to_bytes()).await; | ||||
|  | ||||
|         // set country takes some time, next ioctls fail if we don't wait. | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|  | ||||
|         // Set antenna to chip antenna | ||||
|         self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; | ||||
|  | ||||
|         self.set_iovar_u32("bus:txglom", 0).await; | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|         //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? | ||||
|         //Timer::after(Duration::from_millis(100)).await; | ||||
|         self.set_iovar_u32("ampdu_ba_wsize", 8).await; | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|         self.set_iovar_u32("ampdu_mpdu", 4).await; | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|         //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes | ||||
|  | ||||
|         //Timer::after(Duration::from_millis(100)).await; | ||||
|  | ||||
|         // evts | ||||
|         let mut evts = EventMask { | ||||
|             iface: 0, | ||||
|             events: [0xFF; 24], | ||||
|         }; | ||||
|  | ||||
|         // Disable spammy uninteresting events. | ||||
|         evts.unset(Event::RADIO); | ||||
|         evts.unset(Event::IF); | ||||
|         evts.unset(Event::PROBREQ_MSG); | ||||
|         evts.unset(Event::PROBREQ_MSG_RX); | ||||
|         evts.unset(Event::PROBRESP_MSG); | ||||
|         evts.unset(Event::PROBRESP_MSG); | ||||
|         evts.unset(Event::ROAM); | ||||
|  | ||||
|         self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; | ||||
|  | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|  | ||||
|         // set wifi up | ||||
|         self.up().await; | ||||
|  | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|  | ||||
|         self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto | ||||
|         self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any | ||||
|  | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|  | ||||
|         self.state_ch.set_ethernet_address(mac_addr); | ||||
|  | ||||
|         debug!("INIT DONE"); | ||||
|     } | ||||
|  | ||||
|     /// Set the WiFi interface up. | ||||
|     async fn up(&mut self) { | ||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; | ||||
|     } | ||||
|  | ||||
|     /// Set the interface down. | ||||
|     async fn down(&mut self) { | ||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; | ||||
|     } | ||||
|  | ||||
|     pub async fn set_power_management(&mut self, mode: PowerManagementMode) { | ||||
|         // power save mode | ||||
|         let mode_num = mode.mode(); | ||||
|         if mode_num == 2 { | ||||
|             self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; | ||||
|             self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; | ||||
|             self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; | ||||
|             self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; | ||||
|         } | ||||
|         self.ioctl_set_u32(86, 0, mode_num).await; | ||||
|     } | ||||
|  | ||||
|     pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { | ||||
|         self.set_iovar_u32("ampdu_ba_wsize", 8).await; | ||||
|  | ||||
|         self.ioctl_set_u32(134, 0, 0).await; // wsec = open | ||||
|         self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; | ||||
|         self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 | ||||
|         self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) | ||||
|  | ||||
|         let mut i = SsidInfo { | ||||
|             len: ssid.len() as _, | ||||
|             ssid: [0; 32], | ||||
|         }; | ||||
|         i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); | ||||
|  | ||||
|         self.wait_for_join(i).await | ||||
|     } | ||||
|  | ||||
|     pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { | ||||
|         self.set_iovar_u32("ampdu_ba_wsize", 8).await; | ||||
|  | ||||
|         self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 | ||||
|         self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; | ||||
|         self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; | ||||
|         self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; | ||||
|  | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|  | ||||
|         let mut pfi = PassphraseInfo { | ||||
|             len: passphrase.len() as _, | ||||
|             flags: 1, | ||||
|             passphrase: [0; 64], | ||||
|         }; | ||||
|         pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); | ||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) | ||||
|             .await; // WLC_SET_WSEC_PMK | ||||
|  | ||||
|         self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 | ||||
|         self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) | ||||
|         self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth | ||||
|  | ||||
|         let mut i = SsidInfo { | ||||
|             len: ssid.len() as _, | ||||
|             ssid: [0; 32], | ||||
|         }; | ||||
|         i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); | ||||
|  | ||||
|         self.wait_for_join(i).await | ||||
|     } | ||||
|  | ||||
|     async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { | ||||
|         self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); | ||||
|         let mut subscriber = self.events.queue.subscriber().unwrap(); | ||||
|         // the actual join operation starts here | ||||
|         // we make sure to enable events before so we don't miss any | ||||
|  | ||||
|         // set_ssid | ||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) | ||||
|             .await; | ||||
|  | ||||
|         // to complete the join, we wait for a SET_SSID event | ||||
|         // we also save the AUTH status for the user, it may be interesting | ||||
|         let mut auth_status = 0; | ||||
|         let status = loop { | ||||
|             let msg = subscriber.next_message_pure().await; | ||||
|             if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { | ||||
|                 auth_status = msg.header.status; | ||||
|             } else if msg.header.event_type == Event::SET_SSID { | ||||
|                 // join operation ends with SET_SSID event | ||||
|                 break msg.header.status; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         self.events.mask.disable_all(); | ||||
|         if status == EStatus::SUCCESS { | ||||
|             // successful join | ||||
|             self.state_ch.set_link_state(LinkState::Up); | ||||
|             debug!("JOINED"); | ||||
|             Ok(()) | ||||
|         } else { | ||||
|             warn!("JOIN failed with status={} auth={}", status, auth_status); | ||||
|             Err(Error { status }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { | ||||
|         assert!(gpio_n < 3); | ||||
|         self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) | ||||
|             .await | ||||
|     } | ||||
|  | ||||
|     pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { | ||||
|         self.start_ap(ssid, "", Security::OPEN, channel).await; | ||||
|     } | ||||
|  | ||||
|     pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { | ||||
|         self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await; | ||||
|     } | ||||
|  | ||||
|     async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) { | ||||
|         if security != Security::OPEN | ||||
|             && (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN) | ||||
|         { | ||||
|             panic!("Passphrase is too short or too long"); | ||||
|         } | ||||
|  | ||||
|         // Temporarily set wifi down | ||||
|         self.down().await; | ||||
|  | ||||
|         // Turn off APSTA mode | ||||
|         self.set_iovar_u32("apsta", 0).await; | ||||
|  | ||||
|         // Set wifi up again | ||||
|         self.up().await; | ||||
|  | ||||
|         // Turn on AP mode | ||||
|         self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; | ||||
|  | ||||
|         // Set SSID | ||||
|         let mut i = SsidInfoWithIndex { | ||||
|             index: 0, | ||||
|             ssid_info: SsidInfo { | ||||
|                 len: ssid.as_bytes().len() as _, | ||||
|                 ssid: [0; 32], | ||||
|             }, | ||||
|         }; | ||||
|         i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes()); | ||||
|         self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; | ||||
|  | ||||
|         // Set channel number | ||||
|         self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; | ||||
|  | ||||
|         // Set security | ||||
|         self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; | ||||
|  | ||||
|         if security != Security::OPEN { | ||||
|             self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK | ||||
|  | ||||
|             Timer::after(Duration::from_millis(100)).await; | ||||
|  | ||||
|             // Set passphrase | ||||
|             let mut pfi = PassphraseInfo { | ||||
|                 len: passphrase.as_bytes().len() as _, | ||||
|                 flags: 1, // WSEC_PASSPHRASE | ||||
|                 passphrase: [0; 64], | ||||
|             }; | ||||
|             pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); | ||||
|             self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) | ||||
|                 .await; | ||||
|         } | ||||
|  | ||||
|         // Change mutlicast rate from 1 Mbps to 11 Mbps | ||||
|         self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; | ||||
|  | ||||
|         // Start AP | ||||
|         self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP | ||||
|     } | ||||
|  | ||||
|     async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { | ||||
|         let mut buf = [0; 8]; | ||||
|         buf[0..4].copy_from_slice(&val1.to_le_bytes()); | ||||
|         buf[4..8].copy_from_slice(&val2.to_le_bytes()); | ||||
|         self.set_iovar(name, &buf).await | ||||
|     } | ||||
|  | ||||
|     async fn set_iovar_u32(&mut self, name: &str, val: u32) { | ||||
|         self.set_iovar(name, &val.to_le_bytes()).await | ||||
|     } | ||||
|  | ||||
|     async fn get_iovar_u32(&mut self, name: &str) -> u32 { | ||||
|         let mut buf = [0; 4]; | ||||
|         let len = self.get_iovar(name, &mut buf).await; | ||||
|         assert_eq!(len, 4); | ||||
|         u32::from_le_bytes(buf) | ||||
|     } | ||||
|  | ||||
|     async fn set_iovar(&mut self, name: &str, val: &[u8]) { | ||||
|         self.set_iovar_v::<64>(name, val).await | ||||
|     } | ||||
|  | ||||
|     async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) { | ||||
|         debug!("set {} = {:02x}", name, Bytes(val)); | ||||
|  | ||||
|         let mut buf = [0; BUFSIZE]; | ||||
|         buf[..name.len()].copy_from_slice(name.as_bytes()); | ||||
|         buf[name.len()] = 0; | ||||
|         buf[name.len() + 1..][..val.len()].copy_from_slice(val); | ||||
|  | ||||
|         let total_len = name.len() + 1 + val.len(); | ||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     // TODO this is not really working, it always returns all zeros. | ||||
|     async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { | ||||
|         debug!("get {}", name); | ||||
|  | ||||
|         let mut buf = [0; 64]; | ||||
|         buf[..name.len()].copy_from_slice(name.as_bytes()); | ||||
|         buf[name.len()] = 0; | ||||
|  | ||||
|         let total_len = max(name.len() + 1, res.len()); | ||||
|         let res_len = self | ||||
|             .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) | ||||
|             .await; | ||||
|  | ||||
|         let out_len = min(res.len(), res_len); | ||||
|         res[..out_len].copy_from_slice(&buf[..out_len]); | ||||
|         out_len | ||||
|     } | ||||
|  | ||||
|     async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { | ||||
|         let mut buf = val.to_le_bytes(); | ||||
|         self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; | ||||
|     } | ||||
|  | ||||
|     async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { | ||||
|         struct CancelOnDrop<'a>(&'a IoctlState); | ||||
|  | ||||
|         impl CancelOnDrop<'_> { | ||||
|             fn defuse(self) { | ||||
|                 core::mem::forget(self); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl Drop for CancelOnDrop<'_> { | ||||
|             fn drop(&mut self) { | ||||
|                 self.0.cancel_ioctl(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let ioctl = CancelOnDrop(self.ioctl_state); | ||||
|         let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).await; | ||||
|         ioctl.defuse(); | ||||
|  | ||||
|         resp_len | ||||
|     } | ||||
|  | ||||
|     /// Start a wifi scan | ||||
|     /// | ||||
|     /// Returns a `Stream` of networks found by the device | ||||
|     /// | ||||
|     /// # Note | ||||
|     /// Device events are currently implemented using a bounded queue. | ||||
|     /// To not miss any events, you should make sure to always await the stream. | ||||
|     pub async fn scan(&mut self) -> Scanner<'_> { | ||||
|         const SCANTYPE_PASSIVE: u8 = 1; | ||||
|  | ||||
|         let scan_params = ScanParams { | ||||
|             version: 1, | ||||
|             action: 1, | ||||
|             sync_id: 1, | ||||
|             ssid_len: 0, | ||||
|             ssid: [0; 32], | ||||
|             bssid: [0xff; 6], | ||||
|             bss_type: 2, | ||||
|             scan_type: SCANTYPE_PASSIVE, | ||||
|             nprobes: !0, | ||||
|             active_time: !0, | ||||
|             passive_time: !0, | ||||
|             home_time: !0, | ||||
|             channel_num: 0, | ||||
|             channel_list: [0; 1], | ||||
|         }; | ||||
|  | ||||
|         self.events.mask.enable(&[Event::ESCAN_RESULT]); | ||||
|         let subscriber = self.events.queue.subscriber().unwrap(); | ||||
|         self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; | ||||
|  | ||||
|         Scanner { | ||||
|             subscriber, | ||||
|             events: &self.events, | ||||
|         } | ||||
|     } | ||||
|     /// Leave the wifi, with which we are currently associated. | ||||
|     pub async fn leave(&mut self) { | ||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await; | ||||
|         info!("Disassociated") | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Scanner<'a> { | ||||
|     subscriber: EventSubscriber<'a>, | ||||
|     events: &'a Events, | ||||
| } | ||||
|  | ||||
| impl Scanner<'_> { | ||||
|     /// wait for the next found network | ||||
|     pub async fn next(&mut self) -> Option<BssInfo> { | ||||
|         let event = self.subscriber.next_message_pure().await; | ||||
|         if event.header.status != EStatus::PARTIAL { | ||||
|             self.events.mask.disable_all(); | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         if let events::Payload::BssInfo(bss) = event.payload { | ||||
|             Some(bss) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Drop for Scanner<'_> { | ||||
|     fn drop(&mut self) { | ||||
|         self.events.mask.disable_all(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										481
									
								
								cyw43/src/countries.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										481
									
								
								cyw43/src/countries.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,481 @@ | ||||
| #![allow(unused)] | ||||
|  | ||||
| pub struct Country { | ||||
|     pub code: [u8; 2], | ||||
|     pub rev: u16, | ||||
| } | ||||
|  | ||||
| /// AF Afghanistan | ||||
| pub const AFGHANISTAN: Country = Country { code: *b"AF", rev: 0 }; | ||||
| /// AL Albania | ||||
| pub const ALBANIA: Country = Country { code: *b"AL", rev: 0 }; | ||||
| /// DZ Algeria | ||||
| pub const ALGERIA: Country = Country { code: *b"DZ", rev: 0 }; | ||||
| /// AS American_Samoa | ||||
| pub const AMERICAN_SAMOA: Country = Country { code: *b"AS", rev: 0 }; | ||||
| /// AO Angola | ||||
| pub const ANGOLA: Country = Country { code: *b"AO", rev: 0 }; | ||||
| /// AI Anguilla | ||||
| pub const ANGUILLA: Country = Country { code: *b"AI", rev: 0 }; | ||||
| /// AG Antigua_and_Barbuda | ||||
| pub const ANTIGUA_AND_BARBUDA: Country = Country { code: *b"AG", rev: 0 }; | ||||
| /// AR Argentina | ||||
| pub const ARGENTINA: Country = Country { code: *b"AR", rev: 0 }; | ||||
| /// AM Armenia | ||||
| pub const ARMENIA: Country = Country { code: *b"AM", rev: 0 }; | ||||
| /// AW Aruba | ||||
| pub const ARUBA: Country = Country { code: *b"AW", rev: 0 }; | ||||
| /// AU Australia | ||||
| pub const AUSTRALIA: Country = Country { code: *b"AU", rev: 0 }; | ||||
| /// AT Austria | ||||
| pub const AUSTRIA: Country = Country { code: *b"AT", rev: 0 }; | ||||
| /// AZ Azerbaijan | ||||
| pub const AZERBAIJAN: Country = Country { code: *b"AZ", rev: 0 }; | ||||
| /// BS Bahamas | ||||
| pub const BAHAMAS: Country = Country { code: *b"BS", rev: 0 }; | ||||
| /// BH Bahrain | ||||
| pub const BAHRAIN: Country = Country { code: *b"BH", rev: 0 }; | ||||
| /// 0B Baker_Island | ||||
| pub const BAKER_ISLAND: Country = Country { code: *b"0B", rev: 0 }; | ||||
| /// BD Bangladesh | ||||
| pub const BANGLADESH: Country = Country { code: *b"BD", rev: 0 }; | ||||
| /// BB Barbados | ||||
| pub const BARBADOS: Country = Country { code: *b"BB", rev: 0 }; | ||||
| /// BY Belarus | ||||
| pub const BELARUS: Country = Country { code: *b"BY", rev: 0 }; | ||||
| /// BE Belgium | ||||
| pub const BELGIUM: Country = Country { code: *b"BE", rev: 0 }; | ||||
| /// BZ Belize | ||||
| pub const BELIZE: Country = Country { code: *b"BZ", rev: 0 }; | ||||
| /// BJ Benin | ||||
| pub const BENIN: Country = Country { code: *b"BJ", rev: 0 }; | ||||
| /// BM Bermuda | ||||
| pub const BERMUDA: Country = Country { code: *b"BM", rev: 0 }; | ||||
| /// BT Bhutan | ||||
| pub const BHUTAN: Country = Country { code: *b"BT", rev: 0 }; | ||||
| /// BO Bolivia | ||||
| pub const BOLIVIA: Country = Country { code: *b"BO", rev: 0 }; | ||||
| /// BA Bosnia_and_Herzegovina | ||||
| pub const BOSNIA_AND_HERZEGOVINA: Country = Country { code: *b"BA", rev: 0 }; | ||||
| /// BW Botswana | ||||
| pub const BOTSWANA: Country = Country { code: *b"BW", rev: 0 }; | ||||
| /// BR Brazil | ||||
| pub const BRAZIL: Country = Country { code: *b"BR", rev: 0 }; | ||||
| /// IO British_Indian_Ocean_Territory | ||||
| pub const BRITISH_INDIAN_OCEAN_TERRITORY: Country = Country { code: *b"IO", rev: 0 }; | ||||
| /// BN Brunei_Darussalam | ||||
| pub const BRUNEI_DARUSSALAM: Country = Country { code: *b"BN", rev: 0 }; | ||||
| /// BG Bulgaria | ||||
| pub const BULGARIA: Country = Country { code: *b"BG", rev: 0 }; | ||||
| /// BF Burkina_Faso | ||||
| pub const BURKINA_FASO: Country = Country { code: *b"BF", rev: 0 }; | ||||
| /// BI Burundi | ||||
| pub const BURUNDI: Country = Country { code: *b"BI", rev: 0 }; | ||||
| /// KH Cambodia | ||||
| pub const CAMBODIA: Country = Country { code: *b"KH", rev: 0 }; | ||||
| /// CM Cameroon | ||||
| pub const CAMEROON: Country = Country { code: *b"CM", rev: 0 }; | ||||
| /// CA Canada | ||||
| pub const CANADA: Country = Country { code: *b"CA", rev: 0 }; | ||||
| /// CA Canada Revision 950 | ||||
| pub const CANADA_REV950: Country = Country { code: *b"CA", rev: 950 }; | ||||
| /// CV Cape_Verde | ||||
| pub const CAPE_VERDE: Country = Country { code: *b"CV", rev: 0 }; | ||||
| /// KY Cayman_Islands | ||||
| pub const CAYMAN_ISLANDS: Country = Country { code: *b"KY", rev: 0 }; | ||||
| /// CF Central_African_Republic | ||||
| pub const CENTRAL_AFRICAN_REPUBLIC: Country = Country { code: *b"CF", rev: 0 }; | ||||
| /// TD Chad | ||||
| pub const CHAD: Country = Country { code: *b"TD", rev: 0 }; | ||||
| /// CL Chile | ||||
| pub const CHILE: Country = Country { code: *b"CL", rev: 0 }; | ||||
| /// CN China | ||||
| pub const CHINA: Country = Country { code: *b"CN", rev: 0 }; | ||||
| /// CX Christmas_Island | ||||
| pub const CHRISTMAS_ISLAND: Country = Country { code: *b"CX", rev: 0 }; | ||||
| /// CO Colombia | ||||
| pub const COLOMBIA: Country = Country { code: *b"CO", rev: 0 }; | ||||
| /// KM Comoros | ||||
| pub const COMOROS: Country = Country { code: *b"KM", rev: 0 }; | ||||
| /// CG Congo | ||||
| pub const CONGO: Country = Country { code: *b"CG", rev: 0 }; | ||||
| /// CD Congo,_The_Democratic_Republic_Of_The | ||||
| pub const CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE: Country = Country { code: *b"CD", rev: 0 }; | ||||
| /// CR Costa_Rica | ||||
| pub const COSTA_RICA: Country = Country { code: *b"CR", rev: 0 }; | ||||
| /// CI Cote_D'ivoire | ||||
| pub const COTE_DIVOIRE: Country = Country { code: *b"CI", rev: 0 }; | ||||
| /// HR Croatia | ||||
| pub const CROATIA: Country = Country { code: *b"HR", rev: 0 }; | ||||
| /// CU Cuba | ||||
| pub const CUBA: Country = Country { code: *b"CU", rev: 0 }; | ||||
| /// CY Cyprus | ||||
| pub const CYPRUS: Country = Country { code: *b"CY", rev: 0 }; | ||||
| /// CZ Czech_Republic | ||||
| pub const CZECH_REPUBLIC: Country = Country { code: *b"CZ", rev: 0 }; | ||||
| /// DK Denmark | ||||
| pub const DENMARK: Country = Country { code: *b"DK", rev: 0 }; | ||||
| /// DJ Djibouti | ||||
| pub const DJIBOUTI: Country = Country { code: *b"DJ", rev: 0 }; | ||||
| /// DM Dominica | ||||
| pub const DOMINICA: Country = Country { code: *b"DM", rev: 0 }; | ||||
| /// DO Dominican_Republic | ||||
| pub const DOMINICAN_REPUBLIC: Country = Country { code: *b"DO", rev: 0 }; | ||||
| /// AU G'Day mate! | ||||
| pub const DOWN_UNDER: Country = Country { code: *b"AU", rev: 0 }; | ||||
| /// EC Ecuador | ||||
| pub const ECUADOR: Country = Country { code: *b"EC", rev: 0 }; | ||||
| /// EG Egypt | ||||
| pub const EGYPT: Country = Country { code: *b"EG", rev: 0 }; | ||||
| /// SV El_Salvador | ||||
| pub const EL_SALVADOR: Country = Country { code: *b"SV", rev: 0 }; | ||||
| /// GQ Equatorial_Guinea | ||||
| pub const EQUATORIAL_GUINEA: Country = Country { code: *b"GQ", rev: 0 }; | ||||
| /// ER Eritrea | ||||
| pub const ERITREA: Country = Country { code: *b"ER", rev: 0 }; | ||||
| /// EE Estonia | ||||
| pub const ESTONIA: Country = Country { code: *b"EE", rev: 0 }; | ||||
| /// ET Ethiopia | ||||
| pub const ETHIOPIA: Country = Country { code: *b"ET", rev: 0 }; | ||||
| /// FK Falkland_Islands_(Malvinas) | ||||
| pub const FALKLAND_ISLANDS_MALVINAS: Country = Country { code: *b"FK", rev: 0 }; | ||||
| /// FO Faroe_Islands | ||||
| pub const FAROE_ISLANDS: Country = Country { code: *b"FO", rev: 0 }; | ||||
| /// FJ Fiji | ||||
| pub const FIJI: Country = Country { code: *b"FJ", rev: 0 }; | ||||
| /// FI Finland | ||||
| pub const FINLAND: Country = Country { code: *b"FI", rev: 0 }; | ||||
| /// FR France | ||||
| pub const FRANCE: Country = Country { code: *b"FR", rev: 0 }; | ||||
| /// GF French_Guina | ||||
| pub const FRENCH_GUINA: Country = Country { code: *b"GF", rev: 0 }; | ||||
| /// PF French_Polynesia | ||||
| pub const FRENCH_POLYNESIA: Country = Country { code: *b"PF", rev: 0 }; | ||||
| /// TF French_Southern_Territories | ||||
| pub const FRENCH_SOUTHERN_TERRITORIES: Country = Country { code: *b"TF", rev: 0 }; | ||||
| /// GA Gabon | ||||
| pub const GABON: Country = Country { code: *b"GA", rev: 0 }; | ||||
| /// GM Gambia | ||||
| pub const GAMBIA: Country = Country { code: *b"GM", rev: 0 }; | ||||
| /// GE Georgia | ||||
| pub const GEORGIA: Country = Country { code: *b"GE", rev: 0 }; | ||||
| /// DE Germany | ||||
| pub const GERMANY: Country = Country { code: *b"DE", rev: 0 }; | ||||
| /// E0 European_Wide Revision 895 | ||||
| pub const EUROPEAN_WIDE_REV895: Country = Country { code: *b"E0", rev: 895 }; | ||||
| /// GH Ghana | ||||
| pub const GHANA: Country = Country { code: *b"GH", rev: 0 }; | ||||
| /// GI Gibraltar | ||||
| pub const GIBRALTAR: Country = Country { code: *b"GI", rev: 0 }; | ||||
| /// GR Greece | ||||
| pub const GREECE: Country = Country { code: *b"GR", rev: 0 }; | ||||
| /// GD Grenada | ||||
| pub const GRENADA: Country = Country { code: *b"GD", rev: 0 }; | ||||
| /// GP Guadeloupe | ||||
| pub const GUADELOUPE: Country = Country { code: *b"GP", rev: 0 }; | ||||
| /// GU Guam | ||||
| pub const GUAM: Country = Country { code: *b"GU", rev: 0 }; | ||||
| /// GT Guatemala | ||||
| pub const GUATEMALA: Country = Country { code: *b"GT", rev: 0 }; | ||||
| /// GG Guernsey | ||||
| pub const GUERNSEY: Country = Country { code: *b"GG", rev: 0 }; | ||||
| /// GN Guinea | ||||
| pub const GUINEA: Country = Country { code: *b"GN", rev: 0 }; | ||||
| /// GW Guinea-bissau | ||||
| pub const GUINEA_BISSAU: Country = Country { code: *b"GW", rev: 0 }; | ||||
| /// GY Guyana | ||||
| pub const GUYANA: Country = Country { code: *b"GY", rev: 0 }; | ||||
| /// HT Haiti | ||||
| pub const HAITI: Country = Country { code: *b"HT", rev: 0 }; | ||||
| /// VA Holy_See_(Vatican_City_State) | ||||
| pub const HOLY_SEE_VATICAN_CITY_STATE: Country = Country { code: *b"VA", rev: 0 }; | ||||
| /// HN Honduras | ||||
| pub const HONDURAS: Country = Country { code: *b"HN", rev: 0 }; | ||||
| /// HK Hong_Kong | ||||
| pub const HONG_KONG: Country = Country { code: *b"HK", rev: 0 }; | ||||
| /// HU Hungary | ||||
| pub const HUNGARY: Country = Country { code: *b"HU", rev: 0 }; | ||||
| /// IS Iceland | ||||
| pub const ICELAND: Country = Country { code: *b"IS", rev: 0 }; | ||||
| /// IN India | ||||
| pub const INDIA: Country = Country { code: *b"IN", rev: 0 }; | ||||
| /// ID Indonesia | ||||
| pub const INDONESIA: Country = Country { code: *b"ID", rev: 0 }; | ||||
| /// IR Iran,_Islamic_Republic_Of | ||||
| pub const IRAN_ISLAMIC_REPUBLIC_OF: Country = Country { code: *b"IR", rev: 0 }; | ||||
| /// IQ Iraq | ||||
| pub const IRAQ: Country = Country { code: *b"IQ", rev: 0 }; | ||||
| /// IE Ireland | ||||
| pub const IRELAND: Country = Country { code: *b"IE", rev: 0 }; | ||||
| /// IL Israel | ||||
| pub const ISRAEL: Country = Country { code: *b"IL", rev: 0 }; | ||||
| /// IT Italy | ||||
| pub const ITALY: Country = Country { code: *b"IT", rev: 0 }; | ||||
| /// JM Jamaica | ||||
| pub const JAMAICA: Country = Country { code: *b"JM", rev: 0 }; | ||||
| /// JP Japan | ||||
| pub const JAPAN: Country = Country { code: *b"JP", rev: 0 }; | ||||
| /// JE Jersey | ||||
| pub const JERSEY: Country = Country { code: *b"JE", rev: 0 }; | ||||
| /// JO Jordan | ||||
| pub const JORDAN: Country = Country { code: *b"JO", rev: 0 }; | ||||
| /// KZ Kazakhstan | ||||
| pub const KAZAKHSTAN: Country = Country { code: *b"KZ", rev: 0 }; | ||||
| /// KE Kenya | ||||
| pub const KENYA: Country = Country { code: *b"KE", rev: 0 }; | ||||
| /// KI Kiribati | ||||
| pub const KIRIBATI: Country = Country { code: *b"KI", rev: 0 }; | ||||
| /// KR Korea,_Republic_Of | ||||
| pub const KOREA_REPUBLIC_OF: Country = Country { code: *b"KR", rev: 1 }; | ||||
| /// 0A Kosovo | ||||
| pub const KOSOVO: Country = Country { code: *b"0A", rev: 0 }; | ||||
| /// KW Kuwait | ||||
| pub const KUWAIT: Country = Country { code: *b"KW", rev: 0 }; | ||||
| /// KG Kyrgyzstan | ||||
| pub const KYRGYZSTAN: Country = Country { code: *b"KG", rev: 0 }; | ||||
| /// LA Lao_People's_Democratic_Repubic | ||||
| pub const LAO_PEOPLES_DEMOCRATIC_REPUBIC: Country = Country { code: *b"LA", rev: 0 }; | ||||
| /// LV Latvia | ||||
| pub const LATVIA: Country = Country { code: *b"LV", rev: 0 }; | ||||
| /// LB Lebanon | ||||
| pub const LEBANON: Country = Country { code: *b"LB", rev: 0 }; | ||||
| /// LS Lesotho | ||||
| pub const LESOTHO: Country = Country { code: *b"LS", rev: 0 }; | ||||
| /// LR Liberia | ||||
| pub const LIBERIA: Country = Country { code: *b"LR", rev: 0 }; | ||||
| /// LY Libyan_Arab_Jamahiriya | ||||
| pub const LIBYAN_ARAB_JAMAHIRIYA: Country = Country { code: *b"LY", rev: 0 }; | ||||
| /// LI Liechtenstein | ||||
| pub const LIECHTENSTEIN: Country = Country { code: *b"LI", rev: 0 }; | ||||
| /// LT Lithuania | ||||
| pub const LITHUANIA: Country = Country { code: *b"LT", rev: 0 }; | ||||
| /// LU Luxembourg | ||||
| pub const LUXEMBOURG: Country = Country { code: *b"LU", rev: 0 }; | ||||
| /// MO Macao | ||||
| pub const MACAO: Country = Country { code: *b"MO", rev: 0 }; | ||||
| /// MK Macedonia,_Former_Yugoslav_Republic_Of | ||||
| pub const MACEDONIA_FORMER_YUGOSLAV_REPUBLIC_OF: Country = Country { code: *b"MK", rev: 0 }; | ||||
| /// MG Madagascar | ||||
| pub const MADAGASCAR: Country = Country { code: *b"MG", rev: 0 }; | ||||
| /// MW Malawi | ||||
| pub const MALAWI: Country = Country { code: *b"MW", rev: 0 }; | ||||
| /// MY Malaysia | ||||
| pub const MALAYSIA: Country = Country { code: *b"MY", rev: 0 }; | ||||
| /// MV Maldives | ||||
| pub const MALDIVES: Country = Country { code: *b"MV", rev: 0 }; | ||||
| /// ML Mali | ||||
| pub const MALI: Country = Country { code: *b"ML", rev: 0 }; | ||||
| /// MT Malta | ||||
| pub const MALTA: Country = Country { code: *b"MT", rev: 0 }; | ||||
| /// IM Man,_Isle_Of | ||||
| pub const MAN_ISLE_OF: Country = Country { code: *b"IM", rev: 0 }; | ||||
| /// MQ Martinique | ||||
| pub const MARTINIQUE: Country = Country { code: *b"MQ", rev: 0 }; | ||||
| /// MR Mauritania | ||||
| pub const MAURITANIA: Country = Country { code: *b"MR", rev: 0 }; | ||||
| /// MU Mauritius | ||||
| pub const MAURITIUS: Country = Country { code: *b"MU", rev: 0 }; | ||||
| /// YT Mayotte | ||||
| pub const MAYOTTE: Country = Country { code: *b"YT", rev: 0 }; | ||||
| /// MX Mexico | ||||
| pub const MEXICO: Country = Country { code: *b"MX", rev: 0 }; | ||||
| /// FM Micronesia,_Federated_States_Of | ||||
| pub const MICRONESIA_FEDERATED_STATES_OF: Country = Country { code: *b"FM", rev: 0 }; | ||||
| /// MD Moldova,_Republic_Of | ||||
| pub const MOLDOVA_REPUBLIC_OF: Country = Country { code: *b"MD", rev: 0 }; | ||||
| /// MC Monaco | ||||
| pub const MONACO: Country = Country { code: *b"MC", rev: 0 }; | ||||
| /// MN Mongolia | ||||
| pub const MONGOLIA: Country = Country { code: *b"MN", rev: 0 }; | ||||
| /// ME Montenegro | ||||
| pub const MONTENEGRO: Country = Country { code: *b"ME", rev: 0 }; | ||||
| /// MS Montserrat | ||||
| pub const MONTSERRAT: Country = Country { code: *b"MS", rev: 0 }; | ||||
| /// MA Morocco | ||||
| pub const MOROCCO: Country = Country { code: *b"MA", rev: 0 }; | ||||
| /// MZ Mozambique | ||||
| pub const MOZAMBIQUE: Country = Country { code: *b"MZ", rev: 0 }; | ||||
| /// MM Myanmar | ||||
| pub const MYANMAR: Country = Country { code: *b"MM", rev: 0 }; | ||||
| /// NA Namibia | ||||
| pub const NAMIBIA: Country = Country { code: *b"NA", rev: 0 }; | ||||
| /// NR Nauru | ||||
| pub const NAURU: Country = Country { code: *b"NR", rev: 0 }; | ||||
| /// NP Nepal | ||||
| pub const NEPAL: Country = Country { code: *b"NP", rev: 0 }; | ||||
| /// NL Netherlands | ||||
| pub const NETHERLANDS: Country = Country { code: *b"NL", rev: 0 }; | ||||
| /// AN Netherlands_Antilles | ||||
| pub const NETHERLANDS_ANTILLES: Country = Country { code: *b"AN", rev: 0 }; | ||||
| /// NC New_Caledonia | ||||
| pub const NEW_CALEDONIA: Country = Country { code: *b"NC", rev: 0 }; | ||||
| /// NZ New_Zealand | ||||
| pub const NEW_ZEALAND: Country = Country { code: *b"NZ", rev: 0 }; | ||||
| /// NI Nicaragua | ||||
| pub const NICARAGUA: Country = Country { code: *b"NI", rev: 0 }; | ||||
| /// NE Niger | ||||
| pub const NIGER: Country = Country { code: *b"NE", rev: 0 }; | ||||
| /// NG Nigeria | ||||
| pub const NIGERIA: Country = Country { code: *b"NG", rev: 0 }; | ||||
| /// NF Norfolk_Island | ||||
| pub const NORFOLK_ISLAND: Country = Country { code: *b"NF", rev: 0 }; | ||||
| /// MP Northern_Mariana_Islands | ||||
| pub const NORTHERN_MARIANA_ISLANDS: Country = Country { code: *b"MP", rev: 0 }; | ||||
| /// NO Norway | ||||
| pub const NORWAY: Country = Country { code: *b"NO", rev: 0 }; | ||||
| /// OM Oman | ||||
| pub const OMAN: Country = Country { code: *b"OM", rev: 0 }; | ||||
| /// PK Pakistan | ||||
| pub const PAKISTAN: Country = Country { code: *b"PK", rev: 0 }; | ||||
| /// PW Palau | ||||
| pub const PALAU: Country = Country { code: *b"PW", rev: 0 }; | ||||
| /// PA Panama | ||||
| pub const PANAMA: Country = Country { code: *b"PA", rev: 0 }; | ||||
| /// PG Papua_New_Guinea | ||||
| pub const PAPUA_NEW_GUINEA: Country = Country { code: *b"PG", rev: 0 }; | ||||
| /// PY Paraguay | ||||
| pub const PARAGUAY: Country = Country { code: *b"PY", rev: 0 }; | ||||
| /// PE Peru | ||||
| pub const PERU: Country = Country { code: *b"PE", rev: 0 }; | ||||
| /// PH Philippines | ||||
| pub const PHILIPPINES: Country = Country { code: *b"PH", rev: 0 }; | ||||
| /// PL Poland | ||||
| pub const POLAND: Country = Country { code: *b"PL", rev: 0 }; | ||||
| /// PT Portugal | ||||
| pub const PORTUGAL: Country = Country { code: *b"PT", rev: 0 }; | ||||
| /// PR Pueto_Rico | ||||
| pub const PUETO_RICO: Country = Country { code: *b"PR", rev: 0 }; | ||||
| /// QA Qatar | ||||
| pub const QATAR: Country = Country { code: *b"QA", rev: 0 }; | ||||
| /// RE Reunion | ||||
| pub const REUNION: Country = Country { code: *b"RE", rev: 0 }; | ||||
| /// RO Romania | ||||
| pub const ROMANIA: Country = Country { code: *b"RO", rev: 0 }; | ||||
| /// RU Russian_Federation | ||||
| pub const RUSSIAN_FEDERATION: Country = Country { code: *b"RU", rev: 0 }; | ||||
| /// RW Rwanda | ||||
| pub const RWANDA: Country = Country { code: *b"RW", rev: 0 }; | ||||
| /// KN Saint_Kitts_and_Nevis | ||||
| pub const SAINT_KITTS_AND_NEVIS: Country = Country { code: *b"KN", rev: 0 }; | ||||
| /// LC Saint_Lucia | ||||
| pub const SAINT_LUCIA: Country = Country { code: *b"LC", rev: 0 }; | ||||
| /// PM Saint_Pierre_and_Miquelon | ||||
| pub const SAINT_PIERRE_AND_MIQUELON: Country = Country { code: *b"PM", rev: 0 }; | ||||
| /// VC Saint_Vincent_and_The_Grenadines | ||||
| pub const SAINT_VINCENT_AND_THE_GRENADINES: Country = Country { code: *b"VC", rev: 0 }; | ||||
| /// WS Samoa | ||||
| pub const SAMOA: Country = Country { code: *b"WS", rev: 0 }; | ||||
| /// MF Sanit_Martin_/_Sint_Marteen | ||||
| pub const SANIT_MARTIN_SINT_MARTEEN: Country = Country { code: *b"MF", rev: 0 }; | ||||
| /// ST Sao_Tome_and_Principe | ||||
| pub const SAO_TOME_AND_PRINCIPE: Country = Country { code: *b"ST", rev: 0 }; | ||||
| /// SA Saudi_Arabia | ||||
| pub const SAUDI_ARABIA: Country = Country { code: *b"SA", rev: 0 }; | ||||
| /// SN Senegal | ||||
| pub const SENEGAL: Country = Country { code: *b"SN", rev: 0 }; | ||||
| /// RS Serbia | ||||
| pub const SERBIA: Country = Country { code: *b"RS", rev: 0 }; | ||||
| /// SC Seychelles | ||||
| pub const SEYCHELLES: Country = Country { code: *b"SC", rev: 0 }; | ||||
| /// SL Sierra_Leone | ||||
| pub const SIERRA_LEONE: Country = Country { code: *b"SL", rev: 0 }; | ||||
| /// SG Singapore | ||||
| pub const SINGAPORE: Country = Country { code: *b"SG", rev: 0 }; | ||||
| /// SK Slovakia | ||||
| pub const SLOVAKIA: Country = Country { code: *b"SK", rev: 0 }; | ||||
| /// SI Slovenia | ||||
| pub const SLOVENIA: Country = Country { code: *b"SI", rev: 0 }; | ||||
| /// SB Solomon_Islands | ||||
| pub const SOLOMON_ISLANDS: Country = Country { code: *b"SB", rev: 0 }; | ||||
| /// SO Somalia | ||||
| pub const SOMALIA: Country = Country { code: *b"SO", rev: 0 }; | ||||
| /// ZA South_Africa | ||||
| pub const SOUTH_AFRICA: Country = Country { code: *b"ZA", rev: 0 }; | ||||
| /// ES Spain | ||||
| pub const SPAIN: Country = Country { code: *b"ES", rev: 0 }; | ||||
| /// LK Sri_Lanka | ||||
| pub const SRI_LANKA: Country = Country { code: *b"LK", rev: 0 }; | ||||
| /// SR Suriname | ||||
| pub const SURINAME: Country = Country { code: *b"SR", rev: 0 }; | ||||
| /// SZ Swaziland | ||||
| pub const SWAZILAND: Country = Country { code: *b"SZ", rev: 0 }; | ||||
| /// SE Sweden | ||||
| pub const SWEDEN: Country = Country { code: *b"SE", rev: 0 }; | ||||
| /// CH Switzerland | ||||
| pub const SWITZERLAND: Country = Country { code: *b"CH", rev: 0 }; | ||||
| /// SY Syrian_Arab_Republic | ||||
| pub const SYRIAN_ARAB_REPUBLIC: Country = Country { code: *b"SY", rev: 0 }; | ||||
| /// TW Taiwan,_Province_Of_China | ||||
| pub const TAIWAN_PROVINCE_OF_CHINA: Country = Country { code: *b"TW", rev: 0 }; | ||||
| /// TJ Tajikistan | ||||
| pub const TAJIKISTAN: Country = Country { code: *b"TJ", rev: 0 }; | ||||
| /// TZ Tanzania,_United_Republic_Of | ||||
| pub const TANZANIA_UNITED_REPUBLIC_OF: Country = Country { code: *b"TZ", rev: 0 }; | ||||
| /// TH Thailand | ||||
| pub const THAILAND: Country = Country { code: *b"TH", rev: 0 }; | ||||
| /// TG Togo | ||||
| pub const TOGO: Country = Country { code: *b"TG", rev: 0 }; | ||||
| /// TO Tonga | ||||
| pub const TONGA: Country = Country { code: *b"TO", rev: 0 }; | ||||
| /// TT Trinidad_and_Tobago | ||||
| pub const TRINIDAD_AND_TOBAGO: Country = Country { code: *b"TT", rev: 0 }; | ||||
| /// TN Tunisia | ||||
| pub const TUNISIA: Country = Country { code: *b"TN", rev: 0 }; | ||||
| /// TR Turkey | ||||
| pub const TURKEY: Country = Country { code: *b"TR", rev: 0 }; | ||||
| /// TM Turkmenistan | ||||
| pub const TURKMENISTAN: Country = Country { code: *b"TM", rev: 0 }; | ||||
| /// TC Turks_and_Caicos_Islands | ||||
| pub const TURKS_AND_CAICOS_ISLANDS: Country = Country { code: *b"TC", rev: 0 }; | ||||
| /// TV Tuvalu | ||||
| pub const TUVALU: Country = Country { code: *b"TV", rev: 0 }; | ||||
| /// UG Uganda | ||||
| pub const UGANDA: Country = Country { code: *b"UG", rev: 0 }; | ||||
| /// UA Ukraine | ||||
| pub const UKRAINE: Country = Country { code: *b"UA", rev: 0 }; | ||||
| /// AE United_Arab_Emirates | ||||
| pub const UNITED_ARAB_EMIRATES: Country = Country { code: *b"AE", rev: 0 }; | ||||
| /// GB United_Kingdom | ||||
| pub const UNITED_KINGDOM: Country = Country { code: *b"GB", rev: 0 }; | ||||
| /// US United_States | ||||
| pub const UNITED_STATES: Country = Country { code: *b"US", rev: 0 }; | ||||
| /// US United_States Revision 4 | ||||
| pub const UNITED_STATES_REV4: Country = Country { code: *b"US", rev: 4 }; | ||||
| /// Q1 United_States Revision 931 | ||||
| pub const UNITED_STATES_REV931: Country = Country { code: *b"Q1", rev: 931 }; | ||||
| /// Q2 United_States_(No_DFS) | ||||
| pub const UNITED_STATES_NO_DFS: Country = Country { code: *b"Q2", rev: 0 }; | ||||
| /// UM United_States_Minor_Outlying_Islands | ||||
| pub const UNITED_STATES_MINOR_OUTLYING_ISLANDS: Country = Country { code: *b"UM", rev: 0 }; | ||||
| /// UY Uruguay | ||||
| pub const URUGUAY: Country = Country { code: *b"UY", rev: 0 }; | ||||
| /// UZ Uzbekistan | ||||
| pub const UZBEKISTAN: Country = Country { code: *b"UZ", rev: 0 }; | ||||
| /// VU Vanuatu | ||||
| pub const VANUATU: Country = Country { code: *b"VU", rev: 0 }; | ||||
| /// VE Venezuela | ||||
| pub const VENEZUELA: Country = Country { code: *b"VE", rev: 0 }; | ||||
| /// VN Viet_Nam | ||||
| pub const VIET_NAM: Country = Country { code: *b"VN", rev: 0 }; | ||||
| /// VG Virgin_Islands,_British | ||||
| pub const VIRGIN_ISLANDS_BRITISH: Country = Country { code: *b"VG", rev: 0 }; | ||||
| /// VI Virgin_Islands,_U.S. | ||||
| pub const VIRGIN_ISLANDS_US: Country = Country { code: *b"VI", rev: 0 }; | ||||
| /// WF Wallis_and_Futuna | ||||
| pub const WALLIS_AND_FUTUNA: Country = Country { code: *b"WF", rev: 0 }; | ||||
| /// 0C West_Bank | ||||
| pub const WEST_BANK: Country = Country { code: *b"0C", rev: 0 }; | ||||
| /// EH Western_Sahara | ||||
| pub const WESTERN_SAHARA: Country = Country { code: *b"EH", rev: 0 }; | ||||
| /// Worldwide Locale Revision 983 | ||||
| pub const WORLD_WIDE_XV_REV983: Country = Country { code: *b"XV", rev: 983 }; | ||||
| /// Worldwide Locale (passive Ch12-14) | ||||
| pub const WORLD_WIDE_XX: Country = Country { code: *b"XX", rev: 0 }; | ||||
| /// Worldwide Locale (passive Ch12-14) Revision 17 | ||||
| pub const WORLD_WIDE_XX_REV17: Country = Country { code: *b"XX", rev: 17 }; | ||||
| /// YE Yemen | ||||
| pub const YEMEN: Country = Country { code: *b"YE", rev: 0 }; | ||||
| /// ZM Zambia | ||||
| pub const ZAMBIA: Country = Country { code: *b"ZM", rev: 0 }; | ||||
| /// ZW Zimbabwe | ||||
| pub const ZIMBABWE: Country = Country { code: *b"ZW", rev: 0 }; | ||||
							
								
								
									
										400
									
								
								cyw43/src/events.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								cyw43/src/events.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,400 @@ | ||||
| #![allow(dead_code)] | ||||
| #![allow(non_camel_case_types)] | ||||
|  | ||||
| use core::cell::RefCell; | ||||
|  | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embassy_sync::pubsub::{PubSubChannel, Subscriber}; | ||||
|  | ||||
| use crate::structs::BssInfo; | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(u8)] | ||||
| pub enum Event { | ||||
|     #[num_enum(default)] | ||||
|     Unknown = 0xFF, | ||||
|     /// indicates status of set SSID | ||||
|     SET_SSID = 0, | ||||
|     /// differentiates join IBSS from found (START) IBSS | ||||
|     JOIN = 1, | ||||
|     /// STA founded an IBSS or AP started a BSS | ||||
|     START = 2, | ||||
|     /// 802.11 AUTH request | ||||
|     AUTH = 3, | ||||
|     /// 802.11 AUTH indication | ||||
|     AUTH_IND = 4, | ||||
|     /// 802.11 DEAUTH request | ||||
|     DEAUTH = 5, | ||||
|     /// 802.11 DEAUTH indication | ||||
|     DEAUTH_IND = 6, | ||||
|     /// 802.11 ASSOC request | ||||
|     ASSOC = 7, | ||||
|     /// 802.11 ASSOC indication | ||||
|     ASSOC_IND = 8, | ||||
|     /// 802.11 REASSOC request | ||||
|     REASSOC = 9, | ||||
|     /// 802.11 REASSOC indication | ||||
|     REASSOC_IND = 10, | ||||
|     /// 802.11 DISASSOC request | ||||
|     DISASSOC = 11, | ||||
|     /// 802.11 DISASSOC indication | ||||
|     DISASSOC_IND = 12, | ||||
|     /// 802.11h Quiet period started | ||||
|     QUIET_START = 13, | ||||
|     /// 802.11h Quiet period ended | ||||
|     QUIET_END = 14, | ||||
|     /// BEACONS received/lost indication | ||||
|     BEACON_RX = 15, | ||||
|     /// generic link indication | ||||
|     LINK = 16, | ||||
|     /// TKIP MIC error occurred | ||||
|     MIC_ERROR = 17, | ||||
|     /// NDIS style link indication | ||||
|     NDIS_LINK = 18, | ||||
|     /// roam attempt occurred: indicate status & reason | ||||
|     ROAM = 19, | ||||
|     /// change in dot11FailedCount (txfail) | ||||
|     TXFAIL = 20, | ||||
|     /// WPA2 pmkid cache indication | ||||
|     PMKID_CACHE = 21, | ||||
|     /// current AP's TSF value went backward | ||||
|     RETROGRADE_TSF = 22, | ||||
|     /// AP was pruned from join list for reason | ||||
|     PRUNE = 23, | ||||
|     /// report AutoAuth table entry match for join attempt | ||||
|     AUTOAUTH = 24, | ||||
|     /// Event encapsulating an EAPOL message | ||||
|     EAPOL_MSG = 25, | ||||
|     /// Scan results are ready or scan was aborted | ||||
|     SCAN_COMPLETE = 26, | ||||
|     /// indicate to host addts fail/success | ||||
|     ADDTS_IND = 27, | ||||
|     /// indicate to host delts fail/success | ||||
|     DELTS_IND = 28, | ||||
|     /// indicate to host of beacon transmit | ||||
|     BCNSENT_IND = 29, | ||||
|     /// Send the received beacon up to the host | ||||
|     BCNRX_MSG = 30, | ||||
|     /// indicate to host loss of beacon | ||||
|     BCNLOST_MSG = 31, | ||||
|     /// before attempting to roam | ||||
|     ROAM_PREP = 32, | ||||
|     /// PFN network found event | ||||
|     PFN_NET_FOUND = 33, | ||||
|     /// PFN network lost event | ||||
|     PFN_NET_LOST = 34, | ||||
|     RESET_COMPLETE = 35, | ||||
|     JOIN_START = 36, | ||||
|     ROAM_START = 37, | ||||
|     ASSOC_START = 38, | ||||
|     IBSS_ASSOC = 39, | ||||
|     RADIO = 40, | ||||
|     /// PSM microcode watchdog fired | ||||
|     PSM_WATCHDOG = 41, | ||||
|     /// CCX association start | ||||
|     CCX_ASSOC_START = 42, | ||||
|     /// CCX association abort | ||||
|     CCX_ASSOC_ABORT = 43, | ||||
|     /// probe request received | ||||
|     PROBREQ_MSG = 44, | ||||
|     SCAN_CONFIRM_IND = 45, | ||||
|     /// WPA Handshake | ||||
|     PSK_SUP = 46, | ||||
|     COUNTRY_CODE_CHANGED = 47, | ||||
|     /// WMMAC excedded medium time | ||||
|     EXCEEDED_MEDIUM_TIME = 48, | ||||
|     /// WEP ICV error occurred | ||||
|     ICV_ERROR = 49, | ||||
|     /// Unsupported unicast encrypted frame | ||||
|     UNICAST_DECODE_ERROR = 50, | ||||
|     /// Unsupported multicast encrypted frame | ||||
|     MULTICAST_DECODE_ERROR = 51, | ||||
|     TRACE = 52, | ||||
|     /// BT-AMP HCI event | ||||
|     BTA_HCI_EVENT = 53, | ||||
|     /// I/F change (for wlan host notification) | ||||
|     IF = 54, | ||||
|     /// P2P Discovery listen state expires | ||||
|     P2P_DISC_LISTEN_COMPLETE = 55, | ||||
|     /// indicate RSSI change based on configured levels | ||||
|     RSSI = 56, | ||||
|     /// PFN best network batching event | ||||
|     PFN_BEST_BATCHING = 57, | ||||
|     EXTLOG_MSG = 58, | ||||
|     /// Action frame reception | ||||
|     ACTION_FRAME = 59, | ||||
|     /// Action frame Tx complete | ||||
|     ACTION_FRAME_COMPLETE = 60, | ||||
|     /// assoc request received | ||||
|     PRE_ASSOC_IND = 61, | ||||
|     /// re-assoc request received | ||||
|     PRE_REASSOC_IND = 62, | ||||
|     /// channel adopted (xxx: obsoleted) | ||||
|     CHANNEL_ADOPTED = 63, | ||||
|     /// AP started | ||||
|     AP_STARTED = 64, | ||||
|     /// AP stopped due to DFS | ||||
|     DFS_AP_STOP = 65, | ||||
|     /// AP resumed due to DFS | ||||
|     DFS_AP_RESUME = 66, | ||||
|     /// WAI stations event | ||||
|     WAI_STA_EVENT = 67, | ||||
|     /// event encapsulating an WAI message | ||||
|     WAI_MSG = 68, | ||||
|     /// escan result event | ||||
|     ESCAN_RESULT = 69, | ||||
|     /// action frame off channel complete | ||||
|     ACTION_FRAME_OFF_CHAN_COMPLETE = 70, | ||||
|     /// probe response received | ||||
|     PROBRESP_MSG = 71, | ||||
|     /// P2P Probe request received | ||||
|     P2P_PROBREQ_MSG = 72, | ||||
|     DCS_REQUEST = 73, | ||||
|     /// credits for D11 FIFOs. [AC0,AC1,AC2,AC3,BC_MC,ATIM] | ||||
|     FIFO_CREDIT_MAP = 74, | ||||
|     /// Received action frame event WITH wl_event_rx_frame_data_t header | ||||
|     ACTION_FRAME_RX = 75, | ||||
|     /// Wake Event timer fired, used for wake WLAN test mode | ||||
|     WAKE_EVENT = 76, | ||||
|     /// Radio measurement complete | ||||
|     RM_COMPLETE = 77, | ||||
|     /// Synchronize TSF with the host | ||||
|     HTSFSYNC = 78, | ||||
|     /// request an overlay IOCTL/iovar from the host | ||||
|     OVERLAY_REQ = 79, | ||||
|     CSA_COMPLETE_IND = 80, | ||||
|     /// excess PM Wake Event to inform host | ||||
|     EXCESS_PM_WAKE_EVENT = 81, | ||||
|     /// no PFN networks around | ||||
|     PFN_SCAN_NONE = 82, | ||||
|     /// last found PFN network gets lost | ||||
|     PFN_SCAN_ALLGONE = 83, | ||||
|     GTK_PLUMBED = 84, | ||||
|     /// 802.11 ASSOC indication for NDIS only | ||||
|     ASSOC_IND_NDIS = 85, | ||||
|     /// 802.11 REASSOC indication for NDIS only | ||||
|     REASSOC_IND_NDIS = 86, | ||||
|     ASSOC_REQ_IE = 87, | ||||
|     ASSOC_RESP_IE = 88, | ||||
|     /// association recreated on resume | ||||
|     ASSOC_RECREATED = 89, | ||||
|     /// rx action frame event for NDIS only | ||||
|     ACTION_FRAME_RX_NDIS = 90, | ||||
|     /// authentication request received | ||||
|     AUTH_REQ = 91, | ||||
|     /// fast assoc recreation failed | ||||
|     SPEEDY_RECREATE_FAIL = 93, | ||||
|     /// port-specific event and payload (e.g. NDIS) | ||||
|     NATIVE = 94, | ||||
|     /// event for tx pkt delay suddently jump | ||||
|     PKTDELAY_IND = 95, | ||||
|     /// AWDL AW period starts | ||||
|     AWDL_AW = 96, | ||||
|     /// AWDL Master/Slave/NE master role event | ||||
|     AWDL_ROLE = 97, | ||||
|     /// Generic AWDL event | ||||
|     AWDL_EVENT = 98, | ||||
|     /// NIC AF txstatus | ||||
|     NIC_AF_TXS = 99, | ||||
|     /// NAN event | ||||
|     NAN = 100, | ||||
|     BEACON_FRAME_RX = 101, | ||||
|     /// desired service found | ||||
|     SERVICE_FOUND = 102, | ||||
|     /// GAS fragment received | ||||
|     GAS_FRAGMENT_RX = 103, | ||||
|     /// GAS sessions all complete | ||||
|     GAS_COMPLETE = 104, | ||||
|     /// New device found by p2p offload | ||||
|     P2PO_ADD_DEVICE = 105, | ||||
|     /// device has been removed by p2p offload | ||||
|     P2PO_DEL_DEVICE = 106, | ||||
|     /// WNM event to notify STA enter sleep mode | ||||
|     WNM_STA_SLEEP = 107, | ||||
|     /// Indication of MAC tx failures (exhaustion of 802.11 retries) exceeding threshold(s) | ||||
|     TXFAIL_THRESH = 108, | ||||
|     /// Proximity Detection event | ||||
|     PROXD = 109, | ||||
|     /// AWDL RX Probe response | ||||
|     AWDL_RX_PRB_RESP = 111, | ||||
|     /// AWDL RX Action Frames | ||||
|     AWDL_RX_ACT_FRAME = 112, | ||||
|     /// AWDL Wowl nulls | ||||
|     AWDL_WOWL_NULLPKT = 113, | ||||
|     /// AWDL Phycal status | ||||
|     AWDL_PHYCAL_STATUS = 114, | ||||
|     /// AWDL OOB AF status | ||||
|     AWDL_OOB_AF_STATUS = 115, | ||||
|     /// Interleaved Scan status | ||||
|     AWDL_SCAN_STATUS = 116, | ||||
|     /// AWDL AW Start | ||||
|     AWDL_AW_START = 117, | ||||
|     /// AWDL AW End | ||||
|     AWDL_AW_END = 118, | ||||
|     /// AWDL AW Extensions | ||||
|     AWDL_AW_EXT = 119, | ||||
|     AWDL_PEER_CACHE_CONTROL = 120, | ||||
|     CSA_START_IND = 121, | ||||
|     CSA_DONE_IND = 122, | ||||
|     CSA_FAILURE_IND = 123, | ||||
|     /// CCA based channel quality report | ||||
|     CCA_CHAN_QUAL = 124, | ||||
|     /// to report change in BSSID while roaming | ||||
|     BSSID = 125, | ||||
|     /// tx error indication | ||||
|     TX_STAT_ERROR = 126, | ||||
|     /// credit check for BCMC supported | ||||
|     BCMC_CREDIT_SUPPORT = 127, | ||||
|     /// psta primary interface indication | ||||
|     PSTA_PRIMARY_INTF_IND = 128, | ||||
|     /// Handover Request Initiated | ||||
|     BT_WIFI_HANDOVER_REQ = 130, | ||||
|     /// Southpaw TxInhibit notification | ||||
|     SPW_TXINHIBIT = 131, | ||||
|     /// FBT Authentication Request Indication | ||||
|     FBT_AUTH_REQ_IND = 132, | ||||
|     /// Enhancement addition for RSSI | ||||
|     RSSI_LQM = 133, | ||||
|     /// Full probe/beacon (IEs etc) results | ||||
|     PFN_GSCAN_FULL_RESULT = 134, | ||||
|     /// Significant change in rssi of bssids being tracked | ||||
|     PFN_SWC = 135, | ||||
|     /// a STA been authroized for traffic | ||||
|     AUTHORIZED = 136, | ||||
|     /// probe req with wl_event_rx_frame_data_t header | ||||
|     PROBREQ_MSG_RX = 137, | ||||
|     /// PFN completed scan of network list | ||||
|     PFN_SCAN_COMPLETE = 138, | ||||
|     /// RMC Event | ||||
|     RMC_EVENT = 139, | ||||
|     /// DPSTA interface indication | ||||
|     DPSTA_INTF_IND = 140, | ||||
|     /// RRM Event | ||||
|     RRM = 141, | ||||
|     /// ULP entry event | ||||
|     ULP = 146, | ||||
|     /// TCP Keep Alive Offload Event | ||||
|     TKO = 151, | ||||
|     /// authentication request received | ||||
|     EXT_AUTH_REQ = 187, | ||||
|     /// authentication request received | ||||
|     EXT_AUTH_FRAME_RX = 188, | ||||
|     /// mgmt frame Tx complete | ||||
|     MGMT_FRAME_TXSTATUS = 189, | ||||
|     /// highest val + 1 for range checking | ||||
|     LAST = 190, | ||||
| } | ||||
|  | ||||
| // TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient. | ||||
| pub type EventQueue = PubSubChannel<NoopRawMutex, Message, 2, 1, 1>; | ||||
| pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>; | ||||
|  | ||||
| pub struct Events { | ||||
|     pub queue: EventQueue, | ||||
|     pub mask: SharedEventMask, | ||||
| } | ||||
|  | ||||
| impl Events { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             queue: EventQueue::new(), | ||||
|             mask: SharedEventMask::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| pub struct Status { | ||||
|     pub event_type: Event, | ||||
|     pub status: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| pub enum Payload { | ||||
|     None, | ||||
|     BssInfo(BssInfo), | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
|  | ||||
| pub struct Message { | ||||
|     pub header: Status, | ||||
|     pub payload: Payload, | ||||
| } | ||||
|  | ||||
| impl Message { | ||||
|     pub fn new(status: Status, payload: Payload) -> Self { | ||||
|         Self { | ||||
|             header: status, | ||||
|             payload, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default)] | ||||
| struct EventMask { | ||||
|     mask: [u32; Self::WORD_COUNT], | ||||
| } | ||||
|  | ||||
| impl EventMask { | ||||
|     const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize; | ||||
|  | ||||
|     fn enable(&mut self, event: Event) { | ||||
|         let n = event as u32; | ||||
|         let word = n / u32::BITS; | ||||
|         let bit = n % u32::BITS; | ||||
|  | ||||
|         self.mask[word as usize] |= 1 << bit; | ||||
|     } | ||||
|  | ||||
|     fn disable(&mut self, event: Event) { | ||||
|         let n = event as u32; | ||||
|         let word = n / u32::BITS; | ||||
|         let bit = n % u32::BITS; | ||||
|  | ||||
|         self.mask[word as usize] &= !(1 << bit); | ||||
|     } | ||||
|  | ||||
|     fn is_enabled(&self, event: Event) -> bool { | ||||
|         let n = event as u32; | ||||
|         let word = n / u32::BITS; | ||||
|         let bit = n % u32::BITS; | ||||
|  | ||||
|         self.mask[word as usize] & (1 << bit) > 0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default)] | ||||
|  | ||||
| pub struct SharedEventMask { | ||||
|     mask: RefCell<EventMask>, | ||||
| } | ||||
|  | ||||
| impl SharedEventMask { | ||||
|     pub fn enable(&self, events: &[Event]) { | ||||
|         let mut mask = self.mask.borrow_mut(); | ||||
|         for event in events { | ||||
|             mask.enable(*event); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[allow(dead_code)] | ||||
|     pub fn disable(&self, events: &[Event]) { | ||||
|         let mut mask = self.mask.borrow_mut(); | ||||
|         for event in events { | ||||
|             mask.disable(*event); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn disable_all(&self) { | ||||
|         let mut mask = self.mask.borrow_mut(); | ||||
|         mask.mask = Default::default(); | ||||
|     } | ||||
|  | ||||
|     pub fn is_enabled(&self, event: Event) -> bool { | ||||
|         let mask = self.mask.borrow(); | ||||
|         mask.is_enabled(event) | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,8 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
| 
 | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
| 
 | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
| 
 | ||||
| @@ -81,14 +83,17 @@ macro_rules! todo { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::unreachable!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::unreachable!($($x)*); | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| @@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> { | ||||
|         self | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[allow(unused)] | ||||
| pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||
| 
 | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										126
									
								
								cyw43/src/ioctl.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								cyw43/src/ioctl.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| use core::cell::{Cell, RefCell}; | ||||
| use core::future::poll_fn; | ||||
| use core::task::{Poll, Waker}; | ||||
|  | ||||
| use embassy_sync::waitqueue::WakerRegistration; | ||||
|  | ||||
| use crate::fmt::Bytes; | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| pub enum IoctlType { | ||||
|     Get = 0, | ||||
|     Set = 2, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| pub struct PendingIoctl { | ||||
|     pub buf: *mut [u8], | ||||
|     pub kind: IoctlType, | ||||
|     pub cmd: u32, | ||||
|     pub iface: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| enum IoctlStateInner { | ||||
|     Pending(PendingIoctl), | ||||
|     Sent { buf: *mut [u8] }, | ||||
|     Done { resp_len: usize }, | ||||
| } | ||||
|  | ||||
| struct Wakers { | ||||
|     control: WakerRegistration, | ||||
|     runner: WakerRegistration, | ||||
| } | ||||
|  | ||||
| impl Default for Wakers { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             control: WakerRegistration::new(), | ||||
|             runner: WakerRegistration::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct IoctlState { | ||||
|     state: Cell<IoctlStateInner>, | ||||
|     wakers: RefCell<Wakers>, | ||||
| } | ||||
|  | ||||
| impl IoctlState { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), | ||||
|             wakers: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn wake_control(&self) { | ||||
|         self.wakers.borrow_mut().control.wake(); | ||||
|     } | ||||
|  | ||||
|     fn register_control(&self, waker: &Waker) { | ||||
|         self.wakers.borrow_mut().control.register(waker); | ||||
|     } | ||||
|  | ||||
|     fn wake_runner(&self) { | ||||
|         self.wakers.borrow_mut().runner.wake(); | ||||
|     } | ||||
|  | ||||
|     fn register_runner(&self, waker: &Waker) { | ||||
|         self.wakers.borrow_mut().runner.register(waker); | ||||
|     } | ||||
|  | ||||
|     pub async fn wait_complete(&self) -> usize { | ||||
|         poll_fn(|cx| { | ||||
|             if let IoctlStateInner::Done { resp_len } = self.state.get() { | ||||
|                 Poll::Ready(resp_len) | ||||
|             } else { | ||||
|                 self.register_control(cx.waker()); | ||||
|                 Poll::Pending | ||||
|             } | ||||
|         }) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     pub async fn wait_pending(&self) -> PendingIoctl { | ||||
|         let pending = poll_fn(|cx| { | ||||
|             if let IoctlStateInner::Pending(pending) = self.state.get() { | ||||
|                 Poll::Ready(pending) | ||||
|             } else { | ||||
|                 self.register_runner(cx.waker()); | ||||
|                 Poll::Pending | ||||
|             } | ||||
|         }) | ||||
|         .await; | ||||
|  | ||||
|         self.state.set(IoctlStateInner::Sent { buf: pending.buf }); | ||||
|         pending | ||||
|     } | ||||
|  | ||||
|     pub fn cancel_ioctl(&self) { | ||||
|         self.state.set(IoctlStateInner::Done { resp_len: 0 }); | ||||
|     } | ||||
|  | ||||
|     pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { | ||||
|         self.state | ||||
|             .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); | ||||
|         self.wake_runner(); | ||||
|         self.wait_complete().await | ||||
|     } | ||||
|  | ||||
|     pub fn ioctl_done(&self, response: &[u8]) { | ||||
|         if let IoctlStateInner::Sent { buf } = self.state.get() { | ||||
|             trace!("IOCTL Response: {:02x}", Bytes(response)); | ||||
|  | ||||
|             // TODO fix this | ||||
|             (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); | ||||
|  | ||||
|             self.state.set(IoctlStateInner::Done { | ||||
|                 resp_len: response.len(), | ||||
|             }); | ||||
|             self.wake_control(); | ||||
|         } else { | ||||
|             warn!("IOCTL Response but no pending Ioctl"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										236
									
								
								cyw43/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								cyw43/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| #![allow(incomplete_features)] | ||||
| #![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)] | ||||
| #![deny(unused_must_use)] | ||||
|  | ||||
| // This mod MUST go first, so that the others see its macros. | ||||
| pub(crate) mod fmt; | ||||
|  | ||||
| mod bus; | ||||
| mod consts; | ||||
| mod countries; | ||||
| mod events; | ||||
| mod ioctl; | ||||
| mod structs; | ||||
|  | ||||
| mod control; | ||||
| mod nvram; | ||||
| mod runner; | ||||
|  | ||||
| use core::slice; | ||||
|  | ||||
| use embassy_net_driver_channel as ch; | ||||
| use embedded_hal_1::digital::OutputPin; | ||||
| use events::Events; | ||||
| use ioctl::IoctlState; | ||||
|  | ||||
| use crate::bus::Bus; | ||||
| pub use crate::bus::SpiBusCyw43; | ||||
| pub use crate::control::{Control, Error as ControlError, Scanner}; | ||||
| pub use crate::runner::Runner; | ||||
| pub use crate::structs::BssInfo; | ||||
|  | ||||
| const MTU: usize = 1514; | ||||
|  | ||||
| #[allow(unused)] | ||||
| #[derive(Clone, Copy, PartialEq, Eq)] | ||||
| enum Core { | ||||
|     WLAN = 0, | ||||
|     SOCSRAM = 1, | ||||
|     SDIOD = 2, | ||||
| } | ||||
|  | ||||
| impl Core { | ||||
|     fn base_addr(&self) -> u32 { | ||||
|         match self { | ||||
|             Self::WLAN => CHIP.arm_core_base_address, | ||||
|             Self::SOCSRAM => CHIP.socsram_wrapper_base_address, | ||||
|             Self::SDIOD => CHIP.sdiod_core_base_address, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(unused)] | ||||
| struct Chip { | ||||
|     arm_core_base_address: u32, | ||||
|     socsram_base_address: u32, | ||||
|     socsram_wrapper_base_address: u32, | ||||
|     sdiod_core_base_address: u32, | ||||
|     pmu_base_address: u32, | ||||
|     chip_ram_size: u32, | ||||
|     atcm_ram_base_address: u32, | ||||
|     socram_srmem_size: u32, | ||||
|     chanspec_band_mask: u32, | ||||
|     chanspec_band_2g: u32, | ||||
|     chanspec_band_5g: u32, | ||||
|     chanspec_band_shift: u32, | ||||
|     chanspec_bw_10: u32, | ||||
|     chanspec_bw_20: u32, | ||||
|     chanspec_bw_40: u32, | ||||
|     chanspec_bw_mask: u32, | ||||
|     chanspec_bw_shift: u32, | ||||
|     chanspec_ctl_sb_lower: u32, | ||||
|     chanspec_ctl_sb_upper: u32, | ||||
|     chanspec_ctl_sb_none: u32, | ||||
|     chanspec_ctl_sb_mask: u32, | ||||
| } | ||||
|  | ||||
| const WRAPPER_REGISTER_OFFSET: u32 = 0x100000; | ||||
|  | ||||
| // Data for CYW43439 | ||||
| const CHIP: Chip = Chip { | ||||
|     arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET, | ||||
|     socsram_base_address: 0x18004000, | ||||
|     socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET, | ||||
|     sdiod_core_base_address: 0x18002000, | ||||
|     pmu_base_address: 0x18000000, | ||||
|     chip_ram_size: 512 * 1024, | ||||
|     atcm_ram_base_address: 0, | ||||
|     socram_srmem_size: 64 * 1024, | ||||
|     chanspec_band_mask: 0xc000, | ||||
|     chanspec_band_2g: 0x0000, | ||||
|     chanspec_band_5g: 0xc000, | ||||
|     chanspec_band_shift: 14, | ||||
|     chanspec_bw_10: 0x0800, | ||||
|     chanspec_bw_20: 0x1000, | ||||
|     chanspec_bw_40: 0x1800, | ||||
|     chanspec_bw_mask: 0x3800, | ||||
|     chanspec_bw_shift: 11, | ||||
|     chanspec_ctl_sb_lower: 0x0000, | ||||
|     chanspec_ctl_sb_upper: 0x0100, | ||||
|     chanspec_ctl_sb_none: 0x0000, | ||||
|     chanspec_ctl_sb_mask: 0x0700, | ||||
| }; | ||||
|  | ||||
| pub struct State { | ||||
|     ioctl_state: IoctlState, | ||||
|     ch: ch::State<MTU, 4, 4>, | ||||
|     events: Events, | ||||
| } | ||||
|  | ||||
| impl State { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             ioctl_state: IoctlState::new(), | ||||
|             ch: ch::State::new(), | ||||
|             events: Events::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| pub enum PowerManagementMode { | ||||
|     /// Custom, officially unsupported mode. Use at your own risk. | ||||
|     /// All power-saving features set to their max at only a marginal decrease in power consumption | ||||
|     /// as oppposed to `Aggressive`. | ||||
|     SuperSave, | ||||
|  | ||||
|     /// Aggressive power saving mode. | ||||
|     Aggressive, | ||||
|  | ||||
|     /// The default mode. | ||||
|     PowerSave, | ||||
|  | ||||
|     /// Performance is prefered over power consumption but still some power is conserved as opposed to | ||||
|     /// `None`. | ||||
|     Performance, | ||||
|  | ||||
|     /// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of | ||||
|     /// a much lower throughput. | ||||
|     ThroughputThrottling, | ||||
|  | ||||
|     /// No power management is configured. This consumes the most power. | ||||
|     None, | ||||
| } | ||||
|  | ||||
| impl Default for PowerManagementMode { | ||||
|     fn default() -> Self { | ||||
|         Self::PowerSave | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PowerManagementMode { | ||||
|     fn sleep_ret_ms(&self) -> u16 { | ||||
|         match self { | ||||
|             PowerManagementMode::SuperSave => 2000, | ||||
|             PowerManagementMode::Aggressive => 2000, | ||||
|             PowerManagementMode::PowerSave => 200, | ||||
|             PowerManagementMode::Performance => 20, | ||||
|             PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter | ||||
|             PowerManagementMode::None => 0,                 // value doesn't matter | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn beacon_period(&self) -> u8 { | ||||
|         match self { | ||||
|             PowerManagementMode::SuperSave => 255, | ||||
|             PowerManagementMode::Aggressive => 1, | ||||
|             PowerManagementMode::PowerSave => 1, | ||||
|             PowerManagementMode::Performance => 1, | ||||
|             PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter | ||||
|             PowerManagementMode::None => 0,                 // value doesn't matter | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn dtim_period(&self) -> u8 { | ||||
|         match self { | ||||
|             PowerManagementMode::SuperSave => 255, | ||||
|             PowerManagementMode::Aggressive => 1, | ||||
|             PowerManagementMode::PowerSave => 1, | ||||
|             PowerManagementMode::Performance => 1, | ||||
|             PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter | ||||
|             PowerManagementMode::None => 0,                 // value doesn't matter | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn assoc(&self) -> u8 { | ||||
|         match self { | ||||
|             PowerManagementMode::SuperSave => 255, | ||||
|             PowerManagementMode::Aggressive => 10, | ||||
|             PowerManagementMode::PowerSave => 10, | ||||
|             PowerManagementMode::Performance => 1, | ||||
|             PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter | ||||
|             PowerManagementMode::None => 0,                 // value doesn't matter | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn mode(&self) -> u32 { | ||||
|         match self { | ||||
|             PowerManagementMode::ThroughputThrottling => 1, | ||||
|             PowerManagementMode::None => 0, | ||||
|             _ => 2, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub type NetDriver<'a> = ch::Device<'a, MTU>; | ||||
|  | ||||
| pub async fn new<'a, PWR, SPI>( | ||||
|     state: &'a mut State, | ||||
|     pwr: PWR, | ||||
|     spi: SPI, | ||||
|     firmware: &[u8], | ||||
| ) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) | ||||
| where | ||||
|     PWR: OutputPin, | ||||
|     SPI: SpiBusCyw43, | ||||
| { | ||||
|     let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); | ||||
|     let state_ch = ch_runner.state_runner(); | ||||
|  | ||||
|     let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); | ||||
|  | ||||
|     runner.init(firmware).await; | ||||
|  | ||||
|     ( | ||||
|         device, | ||||
|         Control::new(state_ch, &state.events, &state.ioctl_state), | ||||
|         runner, | ||||
|     ) | ||||
| } | ||||
|  | ||||
| fn slice8_mut(x: &mut [u32]) -> &mut [u8] { | ||||
|     let len = x.len() * 4; | ||||
|     unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } | ||||
| } | ||||
							
								
								
									
										54
									
								
								cyw43/src/nvram.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								cyw43/src/nvram.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| macro_rules! nvram { | ||||
|     ($($s:literal,)*) => { | ||||
|         concat_bytes!($($s, b"\x00",)* b"\x00\x00") | ||||
|     }; | ||||
| } | ||||
|  | ||||
| pub static NVRAM: &'static [u8] = &*nvram!( | ||||
|     b"NVRAMRev=$Rev$", | ||||
|     b"manfid=0x2d0", | ||||
|     b"prodid=0x0727", | ||||
|     b"vendid=0x14e4", | ||||
|     b"devid=0x43e2", | ||||
|     b"boardtype=0x0887", | ||||
|     b"boardrev=0x1100", | ||||
|     b"boardnum=22", | ||||
|     b"macaddr=00:A0:50:b5:59:5e", | ||||
|     b"sromrev=11", | ||||
|     b"boardflags=0x00404001", | ||||
|     b"boardflags3=0x04000000", | ||||
|     b"xtalfreq=37400", | ||||
|     b"nocrc=1", | ||||
|     b"ag0=255", | ||||
|     b"aa2g=1", | ||||
|     b"ccode=ALL", | ||||
|     b"pa0itssit=0x20", | ||||
|     b"extpagain2g=0", | ||||
|     b"pa2ga0=-168,6649,-778", | ||||
|     b"AvVmid_c0=0x0,0xc8", | ||||
|     b"cckpwroffset0=5", | ||||
|     b"maxp2ga0=84", | ||||
|     b"txpwrbckof=6", | ||||
|     b"cckbw202gpo=0", | ||||
|     b"legofdmbw202gpo=0x66111111", | ||||
|     b"mcsbw202gpo=0x77711111", | ||||
|     b"propbw202gpo=0xdd", | ||||
|     b"ofdmdigfilttype=18", | ||||
|     b"ofdmdigfilttypebe=18", | ||||
|     b"papdmode=1", | ||||
|     b"papdvalidtest=1", | ||||
|     b"pacalidx2g=45", | ||||
|     b"papdepsoffset=-30", | ||||
|     b"papdendidx=58", | ||||
|     b"ltecxmux=0", | ||||
|     b"ltecxpadnum=0x0102", | ||||
|     b"ltecxfnsel=0x44", | ||||
|     b"ltecxgcigpio=0x01", | ||||
|     b"il0macaddr=00:90:4c:c5:12:38", | ||||
|     b"wl0id=0x431b", | ||||
|     b"deadman_to=0xffffffff", | ||||
|     b"muxenab=0x100", | ||||
|     b"spurconfig=0x3", | ||||
|     b"glitch_based_crsmin=1", | ||||
|     b"btc_mode=1", | ||||
| ); | ||||
							
								
								
									
										585
									
								
								cyw43/src/runner.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								cyw43/src/runner.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,585 @@ | ||||
| use embassy_futures::select::{select3, Either3}; | ||||
| use embassy_net_driver_channel as ch; | ||||
| use embassy_sync::pubsub::PubSubBehavior; | ||||
| use embassy_time::{block_for, Duration, Timer}; | ||||
| use embedded_hal_1::digital::OutputPin; | ||||
|  | ||||
| use crate::bus::Bus; | ||||
| pub use crate::bus::SpiBusCyw43; | ||||
| use crate::consts::*; | ||||
| use crate::events::{Event, Events, Status}; | ||||
| use crate::fmt::Bytes; | ||||
| use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; | ||||
| use crate::nvram::NVRAM; | ||||
| use crate::structs::*; | ||||
| use crate::{events, slice8_mut, Core, CHIP, MTU}; | ||||
|  | ||||
| #[cfg(feature = "firmware-logs")] | ||||
| struct LogState { | ||||
|     addr: u32, | ||||
|     last_idx: usize, | ||||
|     buf: [u8; 256], | ||||
|     buf_count: usize, | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "firmware-logs")] | ||||
| impl Default for LogState { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             addr: Default::default(), | ||||
|             last_idx: Default::default(), | ||||
|             buf: [0; 256], | ||||
|             buf_count: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Runner<'a, PWR, SPI> { | ||||
|     ch: ch::Runner<'a, MTU>, | ||||
|     bus: Bus<PWR, SPI>, | ||||
|  | ||||
|     ioctl_state: &'a IoctlState, | ||||
|     ioctl_id: u16, | ||||
|     sdpcm_seq: u8, | ||||
|     sdpcm_seq_max: u8, | ||||
|  | ||||
|     events: &'a Events, | ||||
|  | ||||
|     #[cfg(feature = "firmware-logs")] | ||||
|     log: LogState, | ||||
| } | ||||
|  | ||||
| impl<'a, PWR, SPI> Runner<'a, PWR, SPI> | ||||
| where | ||||
|     PWR: OutputPin, | ||||
|     SPI: SpiBusCyw43, | ||||
| { | ||||
|     pub(crate) fn new( | ||||
|         ch: ch::Runner<'a, MTU>, | ||||
|         bus: Bus<PWR, SPI>, | ||||
|         ioctl_state: &'a IoctlState, | ||||
|         events: &'a Events, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             ch, | ||||
|             bus, | ||||
|             ioctl_state, | ||||
|             ioctl_id: 0, | ||||
|             sdpcm_seq: 0, | ||||
|             sdpcm_seq_max: 1, | ||||
|             events, | ||||
|             #[cfg(feature = "firmware-logs")] | ||||
|             log: LogState::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn init(&mut self, firmware: &[u8]) { | ||||
|         self.bus.init().await; | ||||
|  | ||||
|         // Init ALP (Active Low Power) clock | ||||
|         self.bus | ||||
|             .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) | ||||
|             .await; | ||||
|         debug!("waiting for clock..."); | ||||
|         while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} | ||||
|         debug!("clock ok"); | ||||
|  | ||||
|         let chip_id = self.bus.bp_read16(0x1800_0000).await; | ||||
|         debug!("chip ID: {}", chip_id); | ||||
|  | ||||
|         // Upload firmware. | ||||
|         self.core_disable(Core::WLAN).await; | ||||
|         self.core_reset(Core::SOCSRAM).await; | ||||
|         self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; | ||||
|         self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; | ||||
|  | ||||
|         let ram_addr = CHIP.atcm_ram_base_address; | ||||
|  | ||||
|         debug!("loading fw"); | ||||
|         self.bus.bp_write(ram_addr, firmware).await; | ||||
|  | ||||
|         debug!("loading nvram"); | ||||
|         // Round up to 4 bytes. | ||||
|         let nvram_len = (NVRAM.len() + 3) / 4 * 4; | ||||
|         self.bus | ||||
|             .bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) | ||||
|             .await; | ||||
|  | ||||
|         let nvram_len_words = nvram_len as u32 / 4; | ||||
|         let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; | ||||
|         self.bus | ||||
|             .bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) | ||||
|             .await; | ||||
|  | ||||
|         // Start core! | ||||
|         debug!("starting up core..."); | ||||
|         self.core_reset(Core::WLAN).await; | ||||
|         assert!(self.core_is_up(Core::WLAN).await); | ||||
|  | ||||
|         while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} | ||||
|  | ||||
|         // "Set up the interrupt mask and enable interrupts" | ||||
|         // self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; | ||||
|  | ||||
|         self.bus | ||||
|             .write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE) | ||||
|             .await; | ||||
|  | ||||
|         // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." | ||||
|         // Sounds scary... | ||||
|         self.bus | ||||
|             .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32) | ||||
|             .await; | ||||
|  | ||||
|         // wait for wifi startup | ||||
|         debug!("waiting for wifi init..."); | ||||
|         while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} | ||||
|  | ||||
|         // Some random configs related to sleep. | ||||
|         // These aren't needed if we don't want to sleep the bus. | ||||
|         // TODO do we need to sleep the bus to read the irq line, due to | ||||
|         // being on the same pin as MOSI/MISO? | ||||
|  | ||||
|         /* | ||||
|         let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; | ||||
|         val |= 0x02; // WAKE_TILL_HT_AVAIL | ||||
|         self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; | ||||
|         self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 | ||||
|         self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT | ||||
|  | ||||
|         let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; | ||||
|         val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON | ||||
|         self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; | ||||
|          */ | ||||
|  | ||||
|         // clear pulls | ||||
|         self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; | ||||
|         let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; | ||||
|  | ||||
|         // start HT clock | ||||
|         //self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; | ||||
|         //debug!("waiting for HT clock..."); | ||||
|         //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} | ||||
|         //debug!("clock ok"); | ||||
|  | ||||
|         #[cfg(feature = "firmware-logs")] | ||||
|         self.log_init().await; | ||||
|  | ||||
|         debug!("wifi init done"); | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "firmware-logs")] | ||||
|     async fn log_init(&mut self) { | ||||
|         // Initialize shared memory for logging. | ||||
|  | ||||
|         let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; | ||||
|         let shared_addr = self.bus.bp_read32(addr).await; | ||||
|         debug!("shared_addr {:08x}", shared_addr); | ||||
|  | ||||
|         let mut shared = [0; SharedMemData::SIZE]; | ||||
|         self.bus.bp_read(shared_addr, &mut shared).await; | ||||
|         let shared = SharedMemData::from_bytes(&shared); | ||||
|  | ||||
|         self.log.addr = shared.console_addr + 8; | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "firmware-logs")] | ||||
|     async fn log_read(&mut self) { | ||||
|         // Read log struct | ||||
|         let mut log = [0; SharedMemLog::SIZE]; | ||||
|         self.bus.bp_read(self.log.addr, &mut log).await; | ||||
|         let log = SharedMemLog::from_bytes(&log); | ||||
|  | ||||
|         let idx = log.idx as usize; | ||||
|  | ||||
|         // If pointer hasn't moved, no need to do anything. | ||||
|         if idx == self.log.last_idx { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Read entire buf for now. We could read only what we need, but then we | ||||
|         // run into annoying alignment issues in `bp_read`. | ||||
|         let mut buf = [0; 0x400]; | ||||
|         self.bus.bp_read(log.buf, &mut buf).await; | ||||
|  | ||||
|         while self.log.last_idx != idx as usize { | ||||
|             let b = buf[self.log.last_idx]; | ||||
|             if b == b'\r' || b == b'\n' { | ||||
|                 if self.log.buf_count != 0 { | ||||
|                     let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; | ||||
|                     debug!("LOGS: {}", s); | ||||
|                     self.log.buf_count = 0; | ||||
|                 } | ||||
|             } else if self.log.buf_count < self.log.buf.len() { | ||||
|                 self.log.buf[self.log.buf_count] = b; | ||||
|                 self.log.buf_count += 1; | ||||
|             } | ||||
|  | ||||
|             self.log.last_idx += 1; | ||||
|             if self.log.last_idx == 0x400 { | ||||
|                 self.log.last_idx = 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn run(mut self) -> ! { | ||||
|         let mut buf = [0; 512]; | ||||
|         loop { | ||||
|             #[cfg(feature = "firmware-logs")] | ||||
|             self.log_read().await; | ||||
|  | ||||
|             if self.has_credit() { | ||||
|                 let ioctl = self.ioctl_state.wait_pending(); | ||||
|                 let tx = self.ch.tx_buf(); | ||||
|                 let ev = self.bus.wait_for_event(); | ||||
|  | ||||
|                 match select3(ioctl, tx, ev).await { | ||||
|                     Either3::First(PendingIoctl { | ||||
|                         buf: iobuf, | ||||
|                         kind, | ||||
|                         cmd, | ||||
|                         iface, | ||||
|                     }) => { | ||||
|                         self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await; | ||||
|                         self.check_status(&mut buf).await; | ||||
|                     } | ||||
|                     Either3::Second(packet) => { | ||||
|                         trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); | ||||
|  | ||||
|                         let mut buf = [0; 512]; | ||||
|                         let buf8 = slice8_mut(&mut buf); | ||||
|  | ||||
|                         // There MUST be 2 bytes of padding between the SDPCM and BDC headers. | ||||
|                         // And ONLY for data packets! | ||||
|                         // No idea why, but the firmware will append two zero bytes to the tx'd packets | ||||
|                         // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it | ||||
|                         // be oversized and get dropped. | ||||
|                         // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90 | ||||
|                         // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 | ||||
|                         // ¯\_(ツ)_/¯ | ||||
|                         const PADDING_SIZE: usize = 2; | ||||
|                         let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len(); | ||||
|  | ||||
|                         let seq = self.sdpcm_seq; | ||||
|                         self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); | ||||
|  | ||||
|                         let sdpcm_header = SdpcmHeader { | ||||
|                             len: total_len as u16, // TODO does this len need to be rounded up to u32? | ||||
|                             len_inv: !total_len as u16, | ||||
|                             sequence: seq, | ||||
|                             channel_and_flags: CHANNEL_TYPE_DATA, | ||||
|                             next_length: 0, | ||||
|                             header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _, | ||||
|                             wireless_flow_control: 0, | ||||
|                             bus_data_credit: 0, | ||||
|                             reserved: [0, 0], | ||||
|                         }; | ||||
|  | ||||
|                         let bdc_header = BdcHeader { | ||||
|                             flags: BDC_VERSION << BDC_VERSION_SHIFT, | ||||
|                             priority: 0, | ||||
|                             flags2: 0, | ||||
|                             data_offset: 0, | ||||
|                         }; | ||||
|                         trace!("tx {:?}", sdpcm_header); | ||||
|                         trace!("    {:?}", bdc_header); | ||||
|  | ||||
|                         buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); | ||||
|                         buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE] | ||||
|                             .copy_from_slice(&bdc_header.to_bytes()); | ||||
|                         buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()] | ||||
|                             .copy_from_slice(packet); | ||||
|  | ||||
|                         let total_len = (total_len + 3) & !3; // round up to 4byte | ||||
|  | ||||
|                         trace!("    {:02x}", Bytes(&buf8[..total_len.min(48)])); | ||||
|  | ||||
|                         self.bus.wlan_write(&buf[..(total_len / 4)]).await; | ||||
|                         self.ch.tx_done(); | ||||
|                         self.check_status(&mut buf).await; | ||||
|                     } | ||||
|                     Either3::Third(()) => { | ||||
|                         self.handle_irq(&mut buf).await; | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 warn!("TX stalled"); | ||||
|                 self.bus.wait_for_event().await; | ||||
|                 self.handle_irq(&mut buf).await; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Wait for IRQ on F2 packet available | ||||
|     async fn handle_irq(&mut self, buf: &mut [u32; 512]) { | ||||
|         // Receive stuff | ||||
|         let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; | ||||
|         trace!("irq{}", FormatInterrupt(irq)); | ||||
|  | ||||
|         if irq & IRQ_F2_PACKET_AVAILABLE != 0 { | ||||
|             self.check_status(buf).await; | ||||
|         } | ||||
|  | ||||
|         if irq & IRQ_DATA_UNAVAILABLE != 0 { | ||||
|             // TODO what should we do here? | ||||
|             warn!("IRQ DATA_UNAVAILABLE, clearing..."); | ||||
|             self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Handle F2 events while status register is set | ||||
|     async fn check_status(&mut self, buf: &mut [u32; 512]) { | ||||
|         loop { | ||||
|             let status = self.bus.status(); | ||||
|             trace!("check status{}", FormatStatus(status)); | ||||
|  | ||||
|             if status & STATUS_F2_PKT_AVAILABLE != 0 { | ||||
|                 let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; | ||||
|                 self.bus.wlan_read(buf, len).await; | ||||
|                 trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); | ||||
|                 self.rx(&mut slice8_mut(buf)[..len as usize]); | ||||
|             } else { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn rx(&mut self, packet: &mut [u8]) { | ||||
|         let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { | ||||
|             return; | ||||
|         }; | ||||
|  | ||||
|         self.update_credit(&sdpcm_header); | ||||
|  | ||||
|         let channel = sdpcm_header.channel_and_flags & 0x0f; | ||||
|  | ||||
|         match channel { | ||||
|             CHANNEL_TYPE_CONTROL => { | ||||
|                 let Some((cdc_header, response)) = CdcHeader::parse(payload) else { | ||||
|                     return; | ||||
|                 }; | ||||
|                 trace!("    {:?}", cdc_header); | ||||
|  | ||||
|                 if cdc_header.id == self.ioctl_id { | ||||
|                     if cdc_header.status != 0 { | ||||
|                         // TODO: propagate error instead | ||||
|                         panic!("IOCTL error {}", cdc_header.status as i32); | ||||
|                     } | ||||
|  | ||||
|                     self.ioctl_state.ioctl_done(response); | ||||
|                 } | ||||
|             } | ||||
|             CHANNEL_TYPE_EVENT => { | ||||
|                 let Some((_, bdc_packet)) = BdcHeader::parse(payload) else { | ||||
|                     warn!("BDC event, incomplete header"); | ||||
|                     return; | ||||
|                 }; | ||||
|  | ||||
|                 let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else { | ||||
|                     warn!("BDC event, incomplete data"); | ||||
|                     return; | ||||
|                 }; | ||||
|  | ||||
|                 const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h | ||||
|                 if event_packet.eth.ether_type != ETH_P_LINK_CTL { | ||||
|                     warn!( | ||||
|                         "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", | ||||
|                         event_packet.eth.ether_type, ETH_P_LINK_CTL | ||||
|                     ); | ||||
|                     return; | ||||
|                 } | ||||
|                 const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; | ||||
|                 if event_packet.hdr.oui != BROADCOM_OUI { | ||||
|                     warn!( | ||||
|                         "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", | ||||
|                         Bytes(&event_packet.hdr.oui), | ||||
|                         Bytes(BROADCOM_OUI) | ||||
|                     ); | ||||
|                     return; | ||||
|                 } | ||||
|                 const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; | ||||
|                 if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { | ||||
|                     warn!("unexpected subtype {}", event_packet.hdr.subtype); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; | ||||
|                 if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { | ||||
|                     warn!("unexpected user_subtype {}", event_packet.hdr.subtype); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 let evt_type = events::Event::from(event_packet.msg.event_type as u8); | ||||
|                 debug!( | ||||
|                     "=== EVENT {:?}: {:?} {:02x}", | ||||
|                     evt_type, | ||||
|                     event_packet.msg, | ||||
|                     Bytes(evt_data) | ||||
|                 ); | ||||
|  | ||||
|                 if self.events.mask.is_enabled(evt_type) { | ||||
|                     let status = event_packet.msg.status; | ||||
|                     let event_payload = match evt_type { | ||||
|                         Event::ESCAN_RESULT if status == EStatus::PARTIAL => { | ||||
|                             let Some((_, bss_info)) = ScanResults::parse(evt_data) else { | ||||
|                                 return; | ||||
|                             }; | ||||
|                             let Some(bss_info) = BssInfo::parse(bss_info) else { | ||||
|                                 return; | ||||
|                             }; | ||||
|                             events::Payload::BssInfo(*bss_info) | ||||
|                         } | ||||
|                         Event::ESCAN_RESULT => events::Payload::None, | ||||
|                         _ => events::Payload::None, | ||||
|                     }; | ||||
|  | ||||
|                     // this intentionally uses the non-blocking publish immediate | ||||
|                     // publish() is a deadlock risk in the current design as awaiting here prevents ioctls | ||||
|                     // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event | ||||
|                     // (if they are actively awaiting the queue) | ||||
|                     self.events.queue.publish_immediate(events::Message::new( | ||||
|                         Status { | ||||
|                             event_type: evt_type, | ||||
|                             status, | ||||
|                         }, | ||||
|                         event_payload, | ||||
|                     )); | ||||
|                 } | ||||
|             } | ||||
|             CHANNEL_TYPE_DATA => { | ||||
|                 let Some((_, packet)) = BdcHeader::parse(payload) else { | ||||
|                     return; | ||||
|                 }; | ||||
|                 trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); | ||||
|  | ||||
|                 match self.ch.try_rx_buf() { | ||||
|                     Some(buf) => { | ||||
|                         buf[..packet.len()].copy_from_slice(packet); | ||||
|                         self.ch.rx_done(packet.len()) | ||||
|                     } | ||||
|                     None => warn!("failed to push rxd packet to the channel."), | ||||
|                 } | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { | ||||
|         if sdpcm_header.channel_and_flags & 0xf < 3 { | ||||
|             let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; | ||||
|             if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { | ||||
|                 sdpcm_seq_max = self.sdpcm_seq + 2; | ||||
|             } | ||||
|             self.sdpcm_seq_max = sdpcm_seq_max; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn has_credit(&self) -> bool { | ||||
|         self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 | ||||
|     } | ||||
|  | ||||
|     async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) { | ||||
|         let mut buf = [0; 512]; | ||||
|         let buf8 = slice8_mut(&mut buf); | ||||
|  | ||||
|         let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); | ||||
|  | ||||
|         let sdpcm_seq = self.sdpcm_seq; | ||||
|         self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); | ||||
|         self.ioctl_id = self.ioctl_id.wrapping_add(1); | ||||
|  | ||||
|         let sdpcm_header = SdpcmHeader { | ||||
|             len: total_len as u16, // TODO does this len need to be rounded up to u32? | ||||
|             len_inv: !total_len as u16, | ||||
|             sequence: sdpcm_seq, | ||||
|             channel_and_flags: CHANNEL_TYPE_CONTROL, | ||||
|             next_length: 0, | ||||
|             header_length: SdpcmHeader::SIZE as _, | ||||
|             wireless_flow_control: 0, | ||||
|             bus_data_credit: 0, | ||||
|             reserved: [0, 0], | ||||
|         }; | ||||
|  | ||||
|         let cdc_header = CdcHeader { | ||||
|             cmd: cmd, | ||||
|             len: data.len() as _, | ||||
|             flags: kind as u16 | (iface as u16) << 12, | ||||
|             id: self.ioctl_id, | ||||
|             status: 0, | ||||
|         }; | ||||
|         trace!("tx {:?}", sdpcm_header); | ||||
|         trace!("    {:?}", cdc_header); | ||||
|  | ||||
|         buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); | ||||
|         buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); | ||||
|         buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); | ||||
|  | ||||
|         let total_len = (total_len + 3) & !3; // round up to 4byte | ||||
|  | ||||
|         trace!("    {:02x}", Bytes(&buf8[..total_len.min(48)])); | ||||
|  | ||||
|         self.bus.wlan_write(&buf[..total_len / 4]).await; | ||||
|     } | ||||
|  | ||||
|     async fn core_disable(&mut self, core: Core) { | ||||
|         let base = core.base_addr(); | ||||
|  | ||||
|         // Dummy read? | ||||
|         let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; | ||||
|  | ||||
|         // Check it isn't already reset | ||||
|         let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; | ||||
|         if r & AI_RESETCTRL_BIT_RESET != 0 { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; | ||||
|         let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; | ||||
|  | ||||
|         block_for(Duration::from_millis(1)); | ||||
|  | ||||
|         self.bus | ||||
|             .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) | ||||
|             .await; | ||||
|         let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; | ||||
|     } | ||||
|  | ||||
|     async fn core_reset(&mut self, core: Core) { | ||||
|         self.core_disable(core).await; | ||||
|  | ||||
|         let base = core.base_addr(); | ||||
|         self.bus | ||||
|             .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) | ||||
|             .await; | ||||
|         let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; | ||||
|  | ||||
|         self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; | ||||
|  | ||||
|         Timer::after(Duration::from_millis(1)).await; | ||||
|  | ||||
|         self.bus | ||||
|             .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) | ||||
|             .await; | ||||
|         let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; | ||||
|  | ||||
|         Timer::after(Duration::from_millis(1)).await; | ||||
|     } | ||||
|  | ||||
|     async fn core_is_up(&mut self, core: Core) -> bool { | ||||
|         let base = core.base_addr(); | ||||
|  | ||||
|         let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; | ||||
|         if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { | ||||
|             debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; | ||||
|         if r & (AI_RESETCTRL_BIT_RESET) != 0 { | ||||
|             debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         true | ||||
|     } | ||||
| } | ||||
							
								
								
									
										496
									
								
								cyw43/src/structs.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										496
									
								
								cyw43/src/structs.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,496 @@ | ||||
| use crate::events::Event; | ||||
| use crate::fmt::Bytes; | ||||
|  | ||||
| macro_rules! impl_bytes { | ||||
|     ($t:ident) => { | ||||
|         impl $t { | ||||
|             pub const SIZE: usize = core::mem::size_of::<Self>(); | ||||
|  | ||||
|             #[allow(unused)] | ||||
|             pub fn to_bytes(&self) -> [u8; Self::SIZE] { | ||||
|                 unsafe { core::mem::transmute(*self) } | ||||
|             } | ||||
|  | ||||
|             #[allow(unused)] | ||||
|             pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { | ||||
|                 let alignment = core::mem::align_of::<Self>(); | ||||
|                 assert_eq!( | ||||
|                     bytes.as_ptr().align_offset(alignment), | ||||
|                     0, | ||||
|                     "{} is not aligned", | ||||
|                     core::any::type_name::<Self>() | ||||
|                 ); | ||||
|                 unsafe { core::mem::transmute(bytes) } | ||||
|             } | ||||
|  | ||||
|             #[allow(unused)] | ||||
|             pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { | ||||
|                 let alignment = core::mem::align_of::<Self>(); | ||||
|                 assert_eq!( | ||||
|                     bytes.as_ptr().align_offset(alignment), | ||||
|                     0, | ||||
|                     "{} is not aligned", | ||||
|                     core::any::type_name::<Self>() | ||||
|                 ); | ||||
|  | ||||
|                 unsafe { core::mem::transmute(bytes) } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct SharedMemData { | ||||
|     pub flags: u32, | ||||
|     pub trap_addr: u32, | ||||
|     pub assert_exp_addr: u32, | ||||
|     pub assert_file_addr: u32, | ||||
|     pub assert_line: u32, | ||||
|     pub console_addr: u32, | ||||
|     pub msgtrace_addr: u32, | ||||
|     pub fwid: u32, | ||||
| } | ||||
| impl_bytes!(SharedMemData); | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct SharedMemLog { | ||||
|     pub buf: u32, | ||||
|     pub buf_size: u32, | ||||
|     pub idx: u32, | ||||
|     pub out_idx: u32, | ||||
| } | ||||
| impl_bytes!(SharedMemLog); | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct SdpcmHeader { | ||||
|     pub len: u16, | ||||
|     pub len_inv: u16, | ||||
|     /// Rx/Tx sequence number | ||||
|     pub sequence: u8, | ||||
|     ///  4 MSB Channel number, 4 LSB arbitrary flag | ||||
|     pub channel_and_flags: u8, | ||||
|     /// Length of next data frame, reserved for Tx | ||||
|     pub next_length: u8, | ||||
|     /// Data offset | ||||
|     pub header_length: u8, | ||||
|     /// Flow control bits, reserved for Tx | ||||
|     pub wireless_flow_control: u8, | ||||
|     /// Maximum Sequence number allowed by firmware for Tx | ||||
|     pub bus_data_credit: u8, | ||||
|     /// Reserved | ||||
|     pub reserved: [u8; 2], | ||||
| } | ||||
| impl_bytes!(SdpcmHeader); | ||||
|  | ||||
| impl SdpcmHeader { | ||||
|     pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { | ||||
|         let packet_len = packet.len(); | ||||
|         if packet_len < Self::SIZE { | ||||
|             warn!("packet too short, len={}", packet.len()); | ||||
|             return None; | ||||
|         } | ||||
|         let (sdpcm_header, sdpcm_packet) = packet.split_at_mut(Self::SIZE); | ||||
|         let sdpcm_header = Self::from_bytes_mut(sdpcm_header.try_into().unwrap()); | ||||
|         trace!("rx {:?}", sdpcm_header); | ||||
|  | ||||
|         if sdpcm_header.len != !sdpcm_header.len_inv { | ||||
|             warn!("len inv mismatch"); | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         if sdpcm_header.len as usize != packet_len { | ||||
|             warn!("len from header doesn't match len from spi"); | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         let sdpcm_packet = &mut sdpcm_packet[(sdpcm_header.header_length as usize - Self::SIZE)..]; | ||||
|         Some((sdpcm_header, sdpcm_packet)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[repr(C, packed(2))] | ||||
| pub struct CdcHeader { | ||||
|     pub cmd: u32, | ||||
|     pub len: u32, | ||||
|     pub flags: u16, | ||||
|     pub id: u16, | ||||
|     pub status: u32, | ||||
| } | ||||
| impl_bytes!(CdcHeader); | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl defmt::Format for CdcHeader { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         fn copy<T: Copy>(t: T) -> T { | ||||
|             t | ||||
|         } | ||||
|  | ||||
|         defmt::write!( | ||||
|             fmt, | ||||
|             "CdcHeader{{cmd: {=u32:08x}, len: {=u32:08x}, flags: {=u16:04x}, id: {=u16:04x}, status: {=u32:08x}}}", | ||||
|             copy(self.cmd), | ||||
|             copy(self.len), | ||||
|             copy(self.flags), | ||||
|             copy(self.id), | ||||
|             copy(self.status), | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl CdcHeader { | ||||
|     pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { | ||||
|         if packet.len() < Self::SIZE { | ||||
|             warn!("payload too short, len={}", packet.len()); | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         let (cdc_header, payload) = packet.split_at_mut(Self::SIZE); | ||||
|         let cdc_header = Self::from_bytes_mut(cdc_header.try_into().unwrap()); | ||||
|  | ||||
|         let payload = &mut payload[..cdc_header.len as usize]; | ||||
|         Some((cdc_header, payload)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub const BDC_VERSION: u8 = 2; | ||||
| pub const BDC_VERSION_SHIFT: u8 = 4; | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct BdcHeader { | ||||
|     pub flags: u8, | ||||
|     /// 802.1d Priority (low 3 bits) | ||||
|     pub priority: u8, | ||||
|     pub flags2: u8, | ||||
|     /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. | ||||
|     pub data_offset: u8, | ||||
| } | ||||
| impl_bytes!(BdcHeader); | ||||
|  | ||||
| impl BdcHeader { | ||||
|     pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { | ||||
|         if packet.len() < Self::SIZE { | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         let (bdc_header, bdc_packet) = packet.split_at_mut(Self::SIZE); | ||||
|         let bdc_header = Self::from_bytes_mut(bdc_header.try_into().unwrap()); | ||||
|         trace!("    {:?}", bdc_header); | ||||
|  | ||||
|         let packet_start = 4 * bdc_header.data_offset as usize; | ||||
|  | ||||
|         let bdc_packet = bdc_packet.get_mut(packet_start..)?; | ||||
|         trace!("    {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)])); | ||||
|  | ||||
|         Some((bdc_header, bdc_packet)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct EthernetHeader { | ||||
|     pub destination_mac: [u8; 6], | ||||
|     pub source_mac: [u8; 6], | ||||
|     pub ether_type: u16, | ||||
| } | ||||
|  | ||||
| impl EthernetHeader { | ||||
|     pub fn byteswap(&mut self) { | ||||
|         self.ether_type = self.ether_type.to_be(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct EventHeader { | ||||
|     pub subtype: u16, | ||||
|     pub length: u16, | ||||
|     pub version: u8, | ||||
|     pub oui: [u8; 3], | ||||
|     pub user_subtype: u16, | ||||
| } | ||||
|  | ||||
| impl EventHeader { | ||||
|     pub fn byteswap(&mut self) { | ||||
|         self.subtype = self.subtype.to_be(); | ||||
|         self.length = self.length.to_be(); | ||||
|         self.user_subtype = self.user_subtype.to_be(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| // #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C, packed(2))] | ||||
| pub struct EventMessage { | ||||
|     /// version    | ||||
|     pub version: u16, | ||||
|     /// see flags below | ||||
|     pub flags: u16, | ||||
|     /// Message (see below) | ||||
|     pub event_type: u32, | ||||
|     /// Status code (see below) | ||||
|     pub status: u32, | ||||
|     /// Reason code (if applicable) | ||||
|     pub reason: u32, | ||||
|     /// WLC_E_AUTH | ||||
|     pub auth_type: u32, | ||||
|     /// data buf | ||||
|     pub datalen: u32, | ||||
|     /// Station address (if applicable) | ||||
|     pub addr: [u8; 6], | ||||
|     /// name of the incoming packet interface | ||||
|     pub ifname: [u8; 16], | ||||
|     /// destination OS i/f index | ||||
|     pub ifidx: u8, | ||||
|     /// source bsscfg index | ||||
|     pub bsscfgidx: u8, | ||||
| } | ||||
| impl_bytes!(EventMessage); | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl defmt::Format for EventMessage { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         let event_type = self.event_type; | ||||
|         let status = self.status; | ||||
|         let reason = self.reason; | ||||
|         let auth_type = self.auth_type; | ||||
|         let datalen = self.datalen; | ||||
|  | ||||
|         defmt::write!( | ||||
|             fmt, | ||||
|             "EventMessage {{ \ | ||||
|             version: {=u16}, \ | ||||
|             flags: {=u16}, \ | ||||
|             event_type: {=u32}, \ | ||||
|             status: {=u32}, \ | ||||
|             reason: {=u32}, \ | ||||
|             auth_type: {=u32}, \ | ||||
|             datalen: {=u32}, \ | ||||
|             addr: {=[u8; 6]:x}, \ | ||||
|             ifname: {=[u8; 16]:x}, \ | ||||
|             ifidx: {=u8}, \ | ||||
|             bsscfgidx: {=u8}, \ | ||||
|         }} ", | ||||
|             self.version, | ||||
|             self.flags, | ||||
|             event_type, | ||||
|             status, | ||||
|             reason, | ||||
|             auth_type, | ||||
|             datalen, | ||||
|             self.addr, | ||||
|             self.ifname, | ||||
|             self.ifidx, | ||||
|             self.bsscfgidx | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl EventMessage { | ||||
|     pub fn byteswap(&mut self) { | ||||
|         self.version = self.version.to_be(); | ||||
|         self.flags = self.flags.to_be(); | ||||
|         self.event_type = self.event_type.to_be(); | ||||
|         self.status = self.status.to_be(); | ||||
|         self.reason = self.reason.to_be(); | ||||
|         self.auth_type = self.auth_type.to_be(); | ||||
|         self.datalen = self.datalen.to_be(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C, packed(2))] | ||||
| pub struct EventPacket { | ||||
|     pub eth: EthernetHeader, | ||||
|     pub hdr: EventHeader, | ||||
|     pub msg: EventMessage, | ||||
| } | ||||
| impl_bytes!(EventPacket); | ||||
|  | ||||
| impl EventPacket { | ||||
|     pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { | ||||
|         if packet.len() < Self::SIZE { | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         let (event_header, event_packet) = packet.split_at_mut(Self::SIZE); | ||||
|         let event_header = Self::from_bytes_mut(event_header.try_into().unwrap()); | ||||
|         // warn!("event_header {:x}", event_header as *const _); | ||||
|         event_header.byteswap(); | ||||
|  | ||||
|         let event_packet = event_packet.get_mut(..event_header.msg.datalen as usize)?; | ||||
|  | ||||
|         Some((event_header, event_packet)) | ||||
|     } | ||||
|  | ||||
|     pub fn byteswap(&mut self) { | ||||
|         self.eth.byteswap(); | ||||
|         self.hdr.byteswap(); | ||||
|         self.msg.byteswap(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[repr(C)] | ||||
| pub struct DownloadHeader { | ||||
|     pub flag: u16, // | ||||
|     pub dload_type: u16, | ||||
|     pub len: u32, | ||||
|     pub crc: u32, | ||||
| } | ||||
| impl_bytes!(DownloadHeader); | ||||
|  | ||||
| #[allow(unused)] | ||||
| pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001; | ||||
| pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002; | ||||
| pub const DOWNLOAD_FLAG_END: u16 = 0x0004; | ||||
| pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000; | ||||
|  | ||||
| // Country Locale Matrix (CLM) | ||||
| pub const DOWNLOAD_TYPE_CLM: u16 = 2; | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct CountryInfo { | ||||
|     pub country_abbrev: [u8; 4], | ||||
|     pub rev: i32, | ||||
|     pub country_code: [u8; 4], | ||||
| } | ||||
| impl_bytes!(CountryInfo); | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct SsidInfo { | ||||
|     pub len: u32, | ||||
|     pub ssid: [u8; 32], | ||||
| } | ||||
| impl_bytes!(SsidInfo); | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct PassphraseInfo { | ||||
|     pub len: u16, | ||||
|     pub flags: u16, | ||||
|     pub passphrase: [u8; 64], | ||||
| } | ||||
| impl_bytes!(PassphraseInfo); | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct SsidInfoWithIndex { | ||||
|     pub index: u32, | ||||
|     pub ssid_info: SsidInfo, | ||||
| } | ||||
| impl_bytes!(SsidInfoWithIndex); | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct EventMask { | ||||
|     pub iface: u32, | ||||
|     pub events: [u8; 24], | ||||
| } | ||||
| impl_bytes!(EventMask); | ||||
|  | ||||
| impl EventMask { | ||||
|     pub fn unset(&mut self, evt: Event) { | ||||
|         let evt = evt as u8 as usize; | ||||
|         self.events[evt / 8] &= !(1 << (evt % 8)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Parameters for a wifi scan | ||||
| #[derive(Clone, Copy)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C)] | ||||
| pub struct ScanParams { | ||||
|     pub version: u32, | ||||
|     pub action: u16, | ||||
|     pub sync_id: u16, | ||||
|     pub ssid_len: u32, | ||||
|     pub ssid: [u8; 32], | ||||
|     pub bssid: [u8; 6], | ||||
|     pub bss_type: u8, | ||||
|     pub scan_type: u8, | ||||
|     pub nprobes: u32, | ||||
|     pub active_time: u32, | ||||
|     pub passive_time: u32, | ||||
|     pub home_time: u32, | ||||
|     pub channel_num: u32, | ||||
|     pub channel_list: [u16; 1], | ||||
| } | ||||
| impl_bytes!(ScanParams); | ||||
|  | ||||
| /// Wifi Scan Results Header, followed by `bss_count` `BssInfo` | ||||
| #[derive(Clone, Copy)] | ||||
| // #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C, packed(2))] | ||||
| pub struct ScanResults { | ||||
|     pub buflen: u32, | ||||
|     pub version: u32, | ||||
|     pub sync_id: u16, | ||||
|     pub bss_count: u16, | ||||
| } | ||||
| impl_bytes!(ScanResults); | ||||
|  | ||||
| impl ScanResults { | ||||
|     pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> { | ||||
|         if packet.len() < ScanResults::SIZE { | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE); | ||||
|         let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap()); | ||||
|  | ||||
|         if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE { | ||||
|             warn!("Scan result, incomplete BssInfo"); | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         Some((scan_results, bssinfo)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Wifi Scan Result | ||||
| #[derive(Clone, Copy)] | ||||
| // #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[repr(C, packed(2))] | ||||
| #[non_exhaustive] | ||||
| pub struct BssInfo { | ||||
|     pub version: u32, | ||||
|     pub length: u32, | ||||
|     pub bssid: [u8; 6], | ||||
|     pub beacon_period: u16, | ||||
|     pub capability: u16, | ||||
|     pub ssid_len: u8, | ||||
|     pub ssid: [u8; 32], | ||||
|     // there will be more stuff here | ||||
| } | ||||
| impl_bytes!(BssInfo); | ||||
|  | ||||
| impl BssInfo { | ||||
|     pub fn parse(packet: &mut [u8]) -> Option<&mut Self> { | ||||
|         if packet.len() < BssInfo::SIZE { | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         Some(BssInfo::from_bytes_mut( | ||||
|             packet[..BssInfo::SIZE].as_mut().try_into().unwrap(), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| @@ -6,7 +6,7 @@ version = "0.1.0" | ||||
| license = "MIT OR Apache-2.0" | ||||
|  | ||||
| [dependencies] | ||||
| embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } | ||||
| embassy-executor = { version = "0.3.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } | ||||
| embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] } | ||||
| embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] } | ||||
|  | ||||
|   | ||||
| @@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0" | ||||
| [dependencies] | ||||
| cortex-m = "0.7" | ||||
| cortex-m-rt = "0.7" | ||||
| embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"], default-features = false  } | ||||
| embassy-executor = { version = "0.1.0", default-features = false, features = ["nightly", "arch-cortex-m", "executor-thread"] } | ||||
| embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"]  } | ||||
| embassy-executor = { version = "0.3.0", features = ["nightly", "arch-cortex-m", "executor-thread"] } | ||||
|  | ||||
| defmt = "0.3.0" | ||||
| defmt-rtt = "0.3.0" | ||||
|   | ||||
| @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" | ||||
| [dependencies] | ||||
| cortex-m = "0.7" | ||||
| cortex-m-rt = "0.7" | ||||
| embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"], default-features = false  } | ||||
| embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] } | ||||
|  | ||||
| defmt = "0.3.0" | ||||
| defmt-rtt = "0.3.0" | ||||
|   | ||||
| @@ -20,13 +20,13 @@ fn main() -> ! { | ||||
|     let led = Output::new(p.PB14, Level::Low, Speed::Low); | ||||
|     let mut button = Input::new(p.PC13, Pull::Up); | ||||
|  | ||||
|     cortex_m::interrupt::free(|cs| unsafe { | ||||
|     cortex_m::interrupt::free(|cs| { | ||||
|         enable_interrupt(&mut button); | ||||
|  | ||||
|         LED.borrow(cs).borrow_mut().replace(led); | ||||
|         BUTTON.borrow(cs).borrow_mut().replace(button); | ||||
|  | ||||
|         NVIC::unmask(pac::Interrupt::EXTI15_10); | ||||
|         unsafe { NVIC::unmask(pac::Interrupt::EXTI15_10) }; | ||||
|     }); | ||||
|  | ||||
|     loop { | ||||
| @@ -64,25 +64,21 @@ const PORT: u8 = 2; | ||||
| const PIN: usize = 13; | ||||
| fn check_interrupt<P: Pin>(_pin: &mut Input<'static, P>) -> bool { | ||||
|     let exti = pac::EXTI; | ||||
|     unsafe { | ||||
|     let pin = PIN; | ||||
|     let lines = exti.pr(0).read(); | ||||
|     lines.line(pin) | ||||
| } | ||||
| } | ||||
|  | ||||
| fn clear_interrupt<P: Pin>(_pin: &mut Input<'static, P>) { | ||||
|     let exti = pac::EXTI; | ||||
|     unsafe { | ||||
|     let pin = PIN; | ||||
|     let mut lines = exti.pr(0).read(); | ||||
|     lines.set_line(pin, true); | ||||
|     exti.pr(0).write_value(lines); | ||||
| } | ||||
| } | ||||
|  | ||||
| fn enable_interrupt<P: Pin>(_pin: &mut Input<'static, P>) { | ||||
|     cortex_m::interrupt::free(|_| unsafe { | ||||
|     cortex_m::interrupt::free(|_| { | ||||
|         let rcc = pac::RCC; | ||||
|         rcc.apb2enr().modify(|w| w.set_syscfgen(true)); | ||||
|  | ||||
|   | ||||
| @@ -49,7 +49,7 @@ cd examples/nrf52840 | ||||
| cargo run --bin blinky --release | ||||
| ---- | ||||
|  | ||||
| == Whats next? | ||||
| == What's next? | ||||
|  | ||||
| Congratulations, you have your first Embassy application running! Here are some alternatives on where to go from here: | ||||
|  | ||||
|   | ||||
| @@ -27,9 +27,10 @@ defmt = { version = "0.3", optional = true } | ||||
| digest = "0.10" | ||||
| log = { version = "0.4", optional = true  } | ||||
| ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } | ||||
| embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } | ||||
| embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } | ||||
| embedded-storage = "0.3.0" | ||||
| embedded-storage-async = "0.4.0" | ||||
| embedded-storage-async = { version = "0.4.0", optional = true } | ||||
| salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } | ||||
| signature = { version = "1.6.4", default-features = false } | ||||
|  | ||||
| @@ -39,6 +40,7 @@ env_logger = "0.9" | ||||
| rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version | ||||
| futures = { version = "0.3", features = ["executor"] } | ||||
| sha1 = "0.10.5" | ||||
| critical-section = { version = "1.1.1", features = ["std"] } | ||||
|  | ||||
| [dev-dependencies.ed25519-dalek] | ||||
| default_features = false | ||||
| @@ -48,5 +50,7 @@ features = ["rand", "std", "u32_backend"] | ||||
| ed25519-dalek = ["dep:ed25519-dalek", "_verify"] | ||||
| ed25519-salty = ["dep:salty", "_verify"] | ||||
|  | ||||
| nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"] | ||||
|  | ||||
| #Internal features | ||||
| _verify = [] | ||||
|   | ||||
| @@ -13,11 +13,12 @@ By design, the bootloader does not provide any network capabilities. Networking | ||||
| The bootloader supports different hardware in separate crates: | ||||
|  | ||||
| * `embassy-boot-nrf` - for the nRF microcontrollers. | ||||
| * `embassy-boot-rp` - for the RP2040 microcontrollers. | ||||
| * `embassy-boot-stm32` - for the STM32 microcontrollers. | ||||
|  | ||||
| ## Minimum supported Rust version (MSRV) | ||||
|  | ||||
| `embassy-boot` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals. | ||||
| `embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||||
|  | ||||
| ## License | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,11 @@ | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; | ||||
| use core::cell::RefCell; | ||||
|  | ||||
| use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; | ||||
| use embassy_embedded_hal::flash::partition::BlockingPartition; | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embassy_sync::blocking_mutex::Mutex; | ||||
| use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; | ||||
|  | ||||
| use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||||
|  | ||||
| /// Errors returned by bootloader | ||||
| #[derive(PartialEq, Eq, Debug)] | ||||
| @@ -30,63 +35,96 @@ where | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Trait defining the flash handles used for active and DFU partition. | ||||
| pub trait FlashConfig { | ||||
|     /// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value. | ||||
|     const STATE_ERASE_VALUE: u8 = 0xFF; | ||||
| /// Bootloader flash configuration holding the three flashes used by the bootloader | ||||
| /// | ||||
| /// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. | ||||
| /// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition | ||||
| /// the provided flash according to symbols defined in the linkerfile. | ||||
| pub struct BootLoaderConfig<ACTIVE, DFU, STATE> { | ||||
|     /// Flash type used for the active partition - the partition which will be booted from. | ||||
|     pub active: ACTIVE, | ||||
|     /// Flash type used for the dfu partition - the partition which will be swapped in when requested. | ||||
|     pub dfu: DFU, | ||||
|     /// Flash type used for the state partition. | ||||
|     type STATE: NorFlash; | ||||
|     /// Flash type used for the active partition. | ||||
|     type ACTIVE: NorFlash; | ||||
|     /// Flash type used for the dfu partition. | ||||
|     type DFU: NorFlash; | ||||
|  | ||||
|     /// Return flash instance used to write/read to/from active partition. | ||||
|     fn active(&mut self) -> &mut Self::ACTIVE; | ||||
|     /// Return flash instance used to write/read to/from dfu partition. | ||||
|     fn dfu(&mut self) -> &mut Self::DFU; | ||||
|     /// Return flash instance used to write/read to/from bootloader state. | ||||
|     fn state(&mut self) -> &mut Self::STATE; | ||||
|     pub state: STATE, | ||||
| } | ||||
|  | ||||
| trait FlashConfigEx { | ||||
|     fn page_size() -> u32; | ||||
| impl<'a, FLASH: NorFlash> | ||||
|     BootLoaderConfig< | ||||
|         BlockingPartition<'a, NoopRawMutex, FLASH>, | ||||
|         BlockingPartition<'a, NoopRawMutex, FLASH>, | ||||
|         BlockingPartition<'a, NoopRawMutex, FLASH>, | ||||
|     > | ||||
| { | ||||
|     /// Create a bootloader config from the flash and address symbols defined in the linkerfile | ||||
|     // #[cfg(target_os = "none")] | ||||
|     pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self { | ||||
|         extern "C" { | ||||
|             static __bootloader_state_start: u32; | ||||
|             static __bootloader_state_end: u32; | ||||
|             static __bootloader_active_start: u32; | ||||
|             static __bootloader_active_end: u32; | ||||
|             static __bootloader_dfu_start: u32; | ||||
|             static __bootloader_dfu_end: u32; | ||||
|         } | ||||
|  | ||||
| impl<T: FlashConfig> FlashConfigEx for T { | ||||
|     /// Get the page size which is the "unit of operation" within the bootloader. | ||||
|     fn page_size() -> u32 { | ||||
|         core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32 | ||||
|         let active = unsafe { | ||||
|             let start = &__bootloader_active_start as *const u32 as u32; | ||||
|             let end = &__bootloader_active_end as *const u32 as u32; | ||||
|             trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); | ||||
|  | ||||
|             BlockingPartition::new(flash, start, end - start) | ||||
|         }; | ||||
|         let dfu = unsafe { | ||||
|             let start = &__bootloader_dfu_start as *const u32 as u32; | ||||
|             let end = &__bootloader_dfu_end as *const u32 as u32; | ||||
|             trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||||
|  | ||||
|             BlockingPartition::new(flash, start, end - start) | ||||
|         }; | ||||
|         let state = unsafe { | ||||
|             let start = &__bootloader_state_start as *const u32 as u32; | ||||
|             let end = &__bootloader_state_end as *const u32 as u32; | ||||
|             trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||||
|  | ||||
|             BlockingPartition::new(flash, start, end - start) | ||||
|         }; | ||||
|  | ||||
|         Self { active, dfu, state } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// BootLoader works with any flash implementing embedded_storage. | ||||
| pub struct BootLoader { | ||||
|     // Page with current state of bootloader. The state partition has the following format: | ||||
|     // All ranges are in multiples of WRITE_SIZE bytes. | ||||
|     // | Range    | Description                                                                      | | ||||
|     // | 0..1     | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | | ||||
|     // | 1..2     | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid.          | | ||||
|     // | 2..2 + N | Progress index used while swapping or reverting                                  | | ||||
|     state: Partition, | ||||
|     // Location of the partition which will be booted from | ||||
|     active: Partition, | ||||
|     // Location of the partition which will be swapped in when requested | ||||
|     dfu: Partition, | ||||
| pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> { | ||||
|     active: ACTIVE, | ||||
|     dfu: DFU, | ||||
|     /// The state partition has the following format: | ||||
|     /// All ranges are in multiples of WRITE_SIZE bytes. | ||||
|     /// | Range    | Description                                                                      | | ||||
|     /// | 0..1     | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | | ||||
|     /// | 1..2     | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid.          | | ||||
|     /// | 2..2 + N | Progress index used while swapping or reverting       | ||||
|     state: STATE, | ||||
| } | ||||
|  | ||||
| impl BootLoader { | ||||
|     /// Create a new instance of a bootloader with the given partitions. | ||||
| impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, STATE> { | ||||
|     /// Get the page size which is the "unit of operation" within the bootloader. | ||||
|     const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { | ||||
|         ACTIVE::ERASE_SIZE as u32 | ||||
|     } else { | ||||
|         DFU::ERASE_SIZE as u32 | ||||
|     }; | ||||
|  | ||||
|     /// Create a new instance of a bootloader with the flash partitions. | ||||
|     /// | ||||
|     /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. | ||||
|     /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. | ||||
|     pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | ||||
|         Self { active, dfu, state } | ||||
|     pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||||
|         Self { | ||||
|             active: config.active, | ||||
|             dfu: config.dfu, | ||||
|             state: config.state, | ||||
|         } | ||||
|  | ||||
|     /// Return the offset of the active partition into the active flash. | ||||
|     pub fn boot_address(&self) -> usize { | ||||
|         self.active.from as usize | ||||
|     } | ||||
|  | ||||
|     /// Perform necessary boot preparations like swapping images. | ||||
| @@ -175,195 +213,174 @@ impl BootLoader { | ||||
|     /// |       DFU |            3 |      3 |      2 |      1 |      3 | | ||||
|     /// +-----------+--------------+--------+--------+--------+--------+ | ||||
|     /// | ||||
|     pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||||
|     pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||||
|         // Ensure we have enough progress pages to store copy progress | ||||
|         assert_eq!(0, P::page_size() % aligned_buf.len() as u32); | ||||
|         assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32); | ||||
|         assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32); | ||||
|         assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32); | ||||
|         assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32); | ||||
|         assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE); | ||||
|         assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE); | ||||
|         assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE); | ||||
|         assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE); | ||||
|         assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); | ||||
|         assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); | ||||
|         assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); | ||||
|         assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); | ||||
|         assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); | ||||
|         assert!(aligned_buf.len() >= STATE::WRITE_SIZE); | ||||
|         assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); | ||||
|         assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); | ||||
|  | ||||
|         assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); | ||||
|  | ||||
|         // Copy contents from partition N to active | ||||
|         let state = self.read_state(p, aligned_buf)?; | ||||
|         let state = self.read_state(aligned_buf)?; | ||||
|         if state == State::Swap { | ||||
|             // | ||||
|             // Check if we already swapped. If we're in the swap state, this means we should revert | ||||
|             // since the app has failed to mark boot as successful | ||||
|             // | ||||
|             if !self.is_swapped(p, aligned_buf)? { | ||||
|             if !self.is_swapped(aligned_buf)? { | ||||
|                 trace!("Swapping"); | ||||
|                 self.swap(p, aligned_buf)?; | ||||
|                 self.swap(aligned_buf)?; | ||||
|                 trace!("Swapping done"); | ||||
|             } else { | ||||
|                 trace!("Reverting"); | ||||
|                 self.revert(p, aligned_buf)?; | ||||
|                 self.revert(aligned_buf)?; | ||||
|  | ||||
|                 let state_flash = p.state(); | ||||
|                 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; | ||||
|                 let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||||
|  | ||||
|                 // Invalidate progress | ||||
|                 state_word.fill(!P::STATE_ERASE_VALUE); | ||||
|                 self.state | ||||
|                     .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; | ||||
|                 state_word.fill(!STATE_ERASE_VALUE); | ||||
|                 self.state.write(STATE::WRITE_SIZE as u32, state_word)?; | ||||
|  | ||||
|                 // Clear magic and progress | ||||
|                 self.state.wipe_blocking(state_flash)?; | ||||
|                 self.state.erase(0, self.state.capacity() as u32)?; | ||||
|  | ||||
|                 // Set magic | ||||
|                 state_word.fill(BOOT_MAGIC); | ||||
|                 self.state.write_blocking(state_flash, 0, state_word)?; | ||||
|                 self.state.write(0, state_word)?; | ||||
|             } | ||||
|         } | ||||
|         Ok(state) | ||||
|     } | ||||
|  | ||||
|     fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> { | ||||
|         let page_count = (self.active.size() / P::page_size()) as usize; | ||||
|         let progress = self.current_progress(p, aligned_buf)?; | ||||
|     fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> { | ||||
|         let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; | ||||
|         let progress = self.current_progress(aligned_buf)?; | ||||
|  | ||||
|         Ok(progress >= page_count * 2) | ||||
|     } | ||||
|  | ||||
|     fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> { | ||||
|         let write_size = P::STATE::WRITE_SIZE as u32; | ||||
|         let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize; | ||||
|         let state_flash = config.state(); | ||||
|     fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> { | ||||
|         let write_size = STATE::WRITE_SIZE as u32; | ||||
|         let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; | ||||
|         let state_word = &mut aligned_buf[..write_size as usize]; | ||||
|  | ||||
|         self.state.read_blocking(state_flash, write_size, state_word)?; | ||||
|         if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) { | ||||
|         self.state.read(write_size, state_word)?; | ||||
|         if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||||
|             // Progress is invalid | ||||
|             return Ok(max_index); | ||||
|         } | ||||
|  | ||||
|         for index in 0..max_index { | ||||
|             self.state | ||||
|                 .read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?; | ||||
|             self.state.read((2 + index) as u32 * write_size, state_word)?; | ||||
|  | ||||
|             if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) { | ||||
|             if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { | ||||
|                 return Ok(index); | ||||
|             } | ||||
|         } | ||||
|         Ok(max_index) | ||||
|     } | ||||
|  | ||||
|     fn update_progress<P: FlashConfig>( | ||||
|         &mut self, | ||||
|         progress_index: usize, | ||||
|         p: &mut P, | ||||
|         aligned_buf: &mut [u8], | ||||
|     ) -> Result<(), BootError> { | ||||
|         let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; | ||||
|         state_word.fill(!P::STATE_ERASE_VALUE); | ||||
|         self.state.write_blocking( | ||||
|             p.state(), | ||||
|             (2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32, | ||||
|             state_word, | ||||
|         )?; | ||||
|     fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||||
|         let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||||
|         state_word.fill(!STATE_ERASE_VALUE); | ||||
|         self.state | ||||
|             .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn copy_page_once_to_active<P: FlashConfig>( | ||||
|     fn copy_page_once_to_active( | ||||
|         &mut self, | ||||
|         progress_index: usize, | ||||
|         from_offset: u32, | ||||
|         to_offset: u32, | ||||
|         p: &mut P, | ||||
|         aligned_buf: &mut [u8], | ||||
|     ) -> Result<(), BootError> { | ||||
|         if self.current_progress(p, aligned_buf)? <= progress_index { | ||||
|             let page_size = P::page_size() as u32; | ||||
|         if self.current_progress(aligned_buf)? <= progress_index { | ||||
|             let page_size = Self::PAGE_SIZE as u32; | ||||
|  | ||||
|             self.active | ||||
|                 .erase_blocking(p.active(), to_offset, to_offset + page_size)?; | ||||
|             self.active.erase(to_offset, to_offset + page_size)?; | ||||
|  | ||||
|             for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { | ||||
|                 self.dfu | ||||
|                     .read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?; | ||||
|                 self.active | ||||
|                     .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?; | ||||
|                 self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; | ||||
|                 self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; | ||||
|             } | ||||
|  | ||||
|             self.update_progress(progress_index, p, aligned_buf)?; | ||||
|             self.update_progress(progress_index, aligned_buf)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn copy_page_once_to_dfu<P: FlashConfig>( | ||||
|     fn copy_page_once_to_dfu( | ||||
|         &mut self, | ||||
|         progress_index: usize, | ||||
|         from_offset: u32, | ||||
|         to_offset: u32, | ||||
|         p: &mut P, | ||||
|         aligned_buf: &mut [u8], | ||||
|     ) -> Result<(), BootError> { | ||||
|         if self.current_progress(p, aligned_buf)? <= progress_index { | ||||
|             let page_size = P::page_size() as u32; | ||||
|         if self.current_progress(aligned_buf)? <= progress_index { | ||||
|             let page_size = Self::PAGE_SIZE as u32; | ||||
|  | ||||
|             self.dfu | ||||
|                 .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?; | ||||
|             self.dfu.erase(to_offset as u32, to_offset + page_size)?; | ||||
|  | ||||
|             for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { | ||||
|                 self.active | ||||
|                     .read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?; | ||||
|                 self.dfu | ||||
|                     .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?; | ||||
|                 self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; | ||||
|                 self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; | ||||
|             } | ||||
|  | ||||
|             self.update_progress(progress_index, p, aligned_buf)?; | ||||
|             self.update_progress(progress_index, aligned_buf)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||||
|         let page_size = P::page_size(); | ||||
|         let page_count = self.active.size() / page_size; | ||||
|     fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||||
|         let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; | ||||
|         for page_num in 0..page_count { | ||||
|             let progress_index = (page_num * 2) as usize; | ||||
|  | ||||
|             // Copy active page to the 'next' DFU page. | ||||
|             let active_from_offset = (page_count - 1 - page_num) * page_size; | ||||
|             let dfu_to_offset = (page_count - page_num) * page_size; | ||||
|             let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||||
|             let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; | ||||
|             //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); | ||||
|             self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; | ||||
|             self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; | ||||
|  | ||||
|             // Copy DFU page to the active page | ||||
|             let active_to_offset = (page_count - 1 - page_num) * page_size; | ||||
|             let dfu_from_offset = (page_count - 1 - page_num) * page_size; | ||||
|             let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||||
|             let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||||
|             //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); | ||||
|             self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; | ||||
|             self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||||
|         let page_size = P::page_size(); | ||||
|         let page_count = self.active.size() / page_size; | ||||
|     fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||||
|         let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; | ||||
|         for page_num in 0..page_count { | ||||
|             let progress_index = (page_count * 2 + page_num * 2) as usize; | ||||
|  | ||||
|             // Copy the bad active page to the DFU page | ||||
|             let active_from_offset = page_num * page_size; | ||||
|             let dfu_to_offset = page_num * page_size; | ||||
|             self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; | ||||
|             let active_from_offset = page_num * Self::PAGE_SIZE; | ||||
|             let dfu_to_offset = page_num * Self::PAGE_SIZE; | ||||
|             self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; | ||||
|  | ||||
|             // Copy the DFU page back to the active page | ||||
|             let active_to_offset = page_num * page_size; | ||||
|             let dfu_from_offset = (page_num + 1) * page_size; | ||||
|             self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; | ||||
|             let active_to_offset = page_num * Self::PAGE_SIZE; | ||||
|             let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; | ||||
|             self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||||
|         let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; | ||||
|         self.state.read_blocking(config.state(), 0, state_word)?; | ||||
|     fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||||
|         let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||||
|         self.state.read(0, state_word)?; | ||||
|  | ||||
|         if !state_word.iter().any(|&b| b != SWAP_MAGIC) { | ||||
|             Ok(State::Swap) | ||||
| @@ -373,161 +390,32 @@ impl BootLoader { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) { | ||||
|     assert_eq!(active.size() % page_size, 0); | ||||
|     assert_eq!(dfu.size() % page_size, 0); | ||||
|     assert!(dfu.size() - active.size() >= page_size); | ||||
|     assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32); | ||||
| } | ||||
|  | ||||
| /// A flash wrapper implementing the Flash and embedded_storage traits. | ||||
| pub struct BootFlash<F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     flash: F, | ||||
| } | ||||
|  | ||||
| impl<F> BootFlash<F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     /// Create a new instance of a bootable flash | ||||
|     pub fn new(flash: F) -> Self { | ||||
|         Self { flash } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F> ErrorType for BootFlash<F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     type Error = F::Error; | ||||
| } | ||||
|  | ||||
| impl<F> NorFlash for BootFlash<F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     const WRITE_SIZE: usize = F::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = F::ERASE_SIZE; | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         F::erase(&mut self.flash, from, to) | ||||
|     } | ||||
|  | ||||
|     fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         F::write(&mut self.flash, offset, bytes) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F> ReadNorFlash for BootFlash<F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     const READ_SIZE: usize = F::READ_SIZE; | ||||
|  | ||||
|     fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         F::read(&mut self.flash, offset, bytes) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         F::capacity(&self.flash) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Convenience provider that uses a single flash for all partitions. | ||||
| pub struct SingleFlashConfig<'a, F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     flash: &'a mut F, | ||||
| } | ||||
|  | ||||
| impl<'a, F> SingleFlashConfig<'a, F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     /// Create a provider for a single flash. | ||||
|     pub fn new(flash: &'a mut F) -> Self { | ||||
|         Self { flash } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a, F> FlashConfig for SingleFlashConfig<'a, F> | ||||
| where | ||||
|     F: NorFlash, | ||||
| { | ||||
|     type STATE = F; | ||||
|     type ACTIVE = F; | ||||
|     type DFU = F; | ||||
|  | ||||
|     fn active(&mut self) -> &mut Self::STATE { | ||||
|         self.flash | ||||
|     } | ||||
|     fn dfu(&mut self) -> &mut Self::ACTIVE { | ||||
|         self.flash | ||||
|     } | ||||
|     fn state(&mut self) -> &mut Self::DFU { | ||||
|         self.flash | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Convenience flash provider that uses separate flash instances for each partition. | ||||
| pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> | ||||
| where | ||||
|     ACTIVE: NorFlash, | ||||
|     STATE: NorFlash, | ||||
|     DFU: NorFlash, | ||||
| { | ||||
|     active: &'a mut ACTIVE, | ||||
|     state: &'a mut STATE, | ||||
|     dfu: &'a mut DFU, | ||||
| } | ||||
|  | ||||
| impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> | ||||
| where | ||||
|     ACTIVE: NorFlash, | ||||
|     STATE: NorFlash, | ||||
|     DFU: NorFlash, | ||||
| { | ||||
|     /// Create a new flash provider with separate configuration for all three partitions. | ||||
|     pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { | ||||
|         Self { active, state, dfu } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU> | ||||
| where | ||||
|     ACTIVE: NorFlash, | ||||
|     STATE: NorFlash, | ||||
|     DFU: NorFlash, | ||||
| { | ||||
|     type STATE = STATE; | ||||
|     type ACTIVE = ACTIVE; | ||||
|     type DFU = DFU; | ||||
|  | ||||
|     fn active(&mut self) -> &mut Self::ACTIVE { | ||||
|         self.active | ||||
|     } | ||||
|     fn dfu(&mut self) -> &mut Self::DFU { | ||||
|         self.dfu | ||||
|     } | ||||
|     fn state(&mut self) -> &mut Self::STATE { | ||||
|         self.state | ||||
|     } | ||||
| fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||||
|     active: &ACTIVE, | ||||
|     dfu: &DFU, | ||||
|     state: &STATE, | ||||
|     page_size: u32, | ||||
| ) { | ||||
|     assert_eq!(active.capacity() as u32 % page_size, 0); | ||||
|     assert_eq!(dfu.capacity() as u32 % page_size, 0); | ||||
|     assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); | ||||
|     assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use crate::mem_flash::MemFlash; | ||||
|  | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn test_range_asserts() { | ||||
|         const ACTIVE: Partition = Partition::new(4096, 4194304); | ||||
|         const DFU: Partition = Partition::new(4194304, 2 * 4194304); | ||||
|         const STATE: Partition = Partition::new(0, 4096); | ||||
|         assert_partitions(ACTIVE, DFU, STATE, 4096, 4); | ||||
|         const ACTIVE_SIZE: usize = 4194304 - 4096; | ||||
|         const DFU_SIZE: usize = 4194304; | ||||
|         const STATE_SIZE: usize = 4096; | ||||
|         static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF); | ||||
|         static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF); | ||||
|         static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF); | ||||
|         assert_partitions(&ACTIVE, &DFU, &STATE, 4096); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,534 +0,0 @@ | ||||
| use digest::Digest; | ||||
| use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; | ||||
| use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; | ||||
|  | ||||
| use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; | ||||
|  | ||||
| /// Errors returned by FirmwareUpdater | ||||
| #[derive(Debug)] | ||||
| pub enum FirmwareUpdaterError { | ||||
|     /// Error from flash. | ||||
|     Flash(NorFlashErrorKind), | ||||
|     /// Signature errors. | ||||
|     Signature(signature::Error), | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl defmt::Format for FirmwareUpdaterError { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         match self { | ||||
|             FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), | ||||
|             FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<E> From<E> for FirmwareUpdaterError | ||||
| where | ||||
|     E: NorFlashError, | ||||
| { | ||||
|     fn from(error: E) -> Self { | ||||
|         FirmwareUpdaterError::Flash(error.kind()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||||
| /// 'mess up' the internal bootloader state | ||||
| pub struct FirmwareUpdater { | ||||
|     state: Partition, | ||||
|     dfu: Partition, | ||||
| } | ||||
|  | ||||
| impl Default for FirmwareUpdater { | ||||
|     fn default() -> Self { | ||||
|         extern "C" { | ||||
|             static __bootloader_state_start: u32; | ||||
|             static __bootloader_state_end: u32; | ||||
|             static __bootloader_dfu_start: u32; | ||||
|             static __bootloader_dfu_end: u32; | ||||
|         } | ||||
|  | ||||
|         let dfu = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_dfu_start as *const u32 as u32, | ||||
|                 &__bootloader_dfu_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|         let state = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_state_start as *const u32 as u32, | ||||
|                 &__bootloader_state_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|         trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||||
|         trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||||
|         FirmwareUpdater::new(dfu, state) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FirmwareUpdater { | ||||
|     /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||||
|     pub const fn new(dfu: Partition, state: Partition) -> Self { | ||||
|         Self { dfu, state } | ||||
|     } | ||||
|  | ||||
|     /// Obtain the current state. | ||||
|     /// | ||||
|     /// This is useful to check if the bootloader has just done a swap, in order | ||||
|     /// to do verifications and self-tests of the new image before calling | ||||
|     /// `mark_booted`. | ||||
|     pub async fn get_state<F: AsyncNorFlash>( | ||||
|         &mut self, | ||||
|         state_flash: &mut F, | ||||
|         aligned: &mut [u8], | ||||
|     ) -> Result<State, FirmwareUpdaterError> { | ||||
|         self.state.read(state_flash, 0, aligned).await?; | ||||
|  | ||||
|         if !aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||||
|             Ok(State::Swap) | ||||
|         } else { | ||||
|             Ok(State::Boot) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Verify the DFU given a public key. If there is an error then DO NOT | ||||
|     /// proceed with updating the firmware as it must be signed with a | ||||
|     /// corresponding private key (otherwise it could be malicious firmware). | ||||
|     /// | ||||
|     /// Mark to trigger firmware swap on next boot if verify suceeds. | ||||
|     /// | ||||
|     /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||||
|     /// been generated from a SHA-512 digest of the firmware bytes. | ||||
|     /// | ||||
|     /// If no signature feature is set then this method will always return a | ||||
|     /// signature error. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||||
|     /// and written to. | ||||
|     #[cfg(feature = "_verify")] | ||||
|     pub async fn verify_and_mark_updated<F: AsyncNorFlash>( | ||||
|         &mut self, | ||||
|         _state_and_dfu_flash: &mut F, | ||||
|         _public_key: &[u8], | ||||
|         _signature: &[u8], | ||||
|         _update_len: u32, | ||||
|         _aligned: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert_eq!(_aligned.len(), F::WRITE_SIZE); | ||||
|         assert!(_update_len <= self.dfu.size()); | ||||
|  | ||||
|         #[cfg(feature = "ed25519-dalek")] | ||||
|         { | ||||
|             use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | ||||
|  | ||||
|             use crate::digest_adapters::ed25519_dalek::Sha512; | ||||
|  | ||||
|             let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||||
|  | ||||
|             let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||||
|             let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut message = [0; 64]; | ||||
|             self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) | ||||
|                 .await?; | ||||
|  | ||||
|             public_key.verify(&message, &signature).map_err(into_signature_error)? | ||||
|         } | ||||
|         #[cfg(feature = "ed25519-salty")] | ||||
|         { | ||||
|             use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||||
|             use salty::{PublicKey, Signature}; | ||||
|  | ||||
|             use crate::digest_adapters::salty::Sha512; | ||||
|  | ||||
|             fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||||
|                 FirmwareUpdaterError::Signature(signature::Error::default()) | ||||
|             } | ||||
|  | ||||
|             let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | ||||
|             let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | ||||
|             let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||||
|             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut message = [0; 64]; | ||||
|             self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) | ||||
|                 .await?; | ||||
|  | ||||
|             let r = public_key.verify(&message, &signature); | ||||
|             trace!( | ||||
|                 "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||||
|                 public_key.to_bytes(), | ||||
|                 signature.to_bytes(), | ||||
|                 message, | ||||
|                 r.is_ok() | ||||
|             ); | ||||
|             r.map_err(into_signature_error)? | ||||
|         } | ||||
|  | ||||
|         self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await | ||||
|     } | ||||
|  | ||||
|     /// Verify the update in DFU with any digest. | ||||
|     pub async fn hash<F: AsyncNorFlash, D: Digest>( | ||||
|         &mut self, | ||||
|         dfu_flash: &mut F, | ||||
|         update_len: u32, | ||||
|         chunk_buf: &mut [u8], | ||||
|         output: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         let mut digest = D::new(); | ||||
|         for offset in (0..update_len).step_by(chunk_buf.len()) { | ||||
|             self.dfu.read(dfu_flash, offset, chunk_buf).await?; | ||||
|             let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||||
|             digest.update(&chunk_buf[..len]); | ||||
|         } | ||||
|         output.copy_from_slice(digest.finalize().as_slice()); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Mark to trigger firmware swap on next boot. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||||
|     #[cfg(not(feature = "_verify"))] | ||||
|     pub async fn mark_updated<F: AsyncNorFlash>( | ||||
|         &mut self, | ||||
|         state_flash: &mut F, | ||||
|         aligned: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert_eq!(aligned.len(), F::WRITE_SIZE); | ||||
|         self.set_magic(aligned, SWAP_MAGIC, state_flash).await | ||||
|     } | ||||
|  | ||||
|     /// Mark firmware boot successful and stop rollback on reset. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||||
|     pub async fn mark_booted<F: AsyncNorFlash>( | ||||
|         &mut self, | ||||
|         state_flash: &mut F, | ||||
|         aligned: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert_eq!(aligned.len(), F::WRITE_SIZE); | ||||
|         self.set_magic(aligned, BOOT_MAGIC, state_flash).await | ||||
|     } | ||||
|  | ||||
|     async fn set_magic<F: AsyncNorFlash>( | ||||
|         &mut self, | ||||
|         aligned: &mut [u8], | ||||
|         magic: u8, | ||||
|         state_flash: &mut F, | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.read(state_flash, 0, aligned).await?; | ||||
|  | ||||
|         if aligned.iter().any(|&b| b != magic) { | ||||
|             // Read progress validity | ||||
|             self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; | ||||
|  | ||||
|             // FIXME: Do not make this assumption. | ||||
|             const STATE_ERASE_VALUE: u8 = 0xFF; | ||||
|  | ||||
|             if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||||
|                 // The current progress validity marker is invalid | ||||
|             } else { | ||||
|                 // Invalidate progress | ||||
|                 aligned.fill(!STATE_ERASE_VALUE); | ||||
|                 self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; | ||||
|             } | ||||
|  | ||||
|             // Clear magic and progress | ||||
|             self.state.wipe(state_flash).await?; | ||||
|  | ||||
|             // Set magic | ||||
|             aligned.fill(magic); | ||||
|             self.state.write(state_flash, 0, aligned).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Write data to a flash page. | ||||
|     /// | ||||
|     /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// Failing to meet alignment and size requirements may result in a panic. | ||||
|     pub async fn write_firmware<F: AsyncNorFlash>( | ||||
|         &mut self, | ||||
|         offset: usize, | ||||
|         data: &[u8], | ||||
|         dfu_flash: &mut F, | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert!(data.len() >= F::ERASE_SIZE); | ||||
|  | ||||
|         self.dfu | ||||
|             .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) | ||||
|             .await?; | ||||
|  | ||||
|         self.dfu.write(dfu_flash, offset as u32, data).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||||
|     /// returning its `Partition`. | ||||
|     /// | ||||
|     /// Using this instead of `write_firmware` allows for an optimized API in | ||||
|     /// exchange for added complexity. | ||||
|     pub async fn prepare_update<F: AsyncNorFlash>( | ||||
|         &mut self, | ||||
|         dfu_flash: &mut F, | ||||
|     ) -> Result<Partition, FirmwareUpdaterError> { | ||||
|         self.dfu.wipe(dfu_flash).await?; | ||||
|  | ||||
|         Ok(self.dfu) | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // Blocking API | ||||
|     // | ||||
|  | ||||
|     /// Obtain the current state. | ||||
|     /// | ||||
|     /// This is useful to check if the bootloader has just done a swap, in order | ||||
|     /// to do verifications and self-tests of the new image before calling | ||||
|     /// `mark_booted`. | ||||
|     pub fn get_state_blocking<F: NorFlash>( | ||||
|         &mut self, | ||||
|         state_flash: &mut F, | ||||
|         aligned: &mut [u8], | ||||
|     ) -> Result<State, FirmwareUpdaterError> { | ||||
|         self.state.read_blocking(state_flash, 0, aligned)?; | ||||
|  | ||||
|         if !aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||||
|             Ok(State::Swap) | ||||
|         } else { | ||||
|             Ok(State::Boot) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Verify the DFU given a public key. If there is an error then DO NOT | ||||
|     /// proceed with updating the firmware as it must be signed with a | ||||
|     /// corresponding private key (otherwise it could be malicious firmware). | ||||
|     /// | ||||
|     /// Mark to trigger firmware swap on next boot if verify suceeds. | ||||
|     /// | ||||
|     /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||||
|     /// been generated from a SHA-512 digest of the firmware bytes. | ||||
|     /// | ||||
|     /// If no signature feature is set then this method will always return a | ||||
|     /// signature error. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||||
|     /// and written to. | ||||
|     #[cfg(feature = "_verify")] | ||||
|     pub fn verify_and_mark_updated_blocking<F: NorFlash>( | ||||
|         &mut self, | ||||
|         _state_and_dfu_flash: &mut F, | ||||
|         _public_key: &[u8], | ||||
|         _signature: &[u8], | ||||
|         _update_len: u32, | ||||
|         _aligned: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert_eq!(_aligned.len(), F::WRITE_SIZE); | ||||
|         assert!(_update_len <= self.dfu.size()); | ||||
|  | ||||
|         #[cfg(feature = "ed25519-dalek")] | ||||
|         { | ||||
|             use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | ||||
|  | ||||
|             use crate::digest_adapters::ed25519_dalek::Sha512; | ||||
|  | ||||
|             let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||||
|  | ||||
|             let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||||
|             let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut message = [0; 64]; | ||||
|             self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; | ||||
|  | ||||
|             public_key.verify(&message, &signature).map_err(into_signature_error)? | ||||
|         } | ||||
|         #[cfg(feature = "ed25519-salty")] | ||||
|         { | ||||
|             use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||||
|             use salty::{PublicKey, Signature}; | ||||
|  | ||||
|             use crate::digest_adapters::salty::Sha512; | ||||
|  | ||||
|             fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||||
|                 FirmwareUpdaterError::Signature(signature::Error::default()) | ||||
|             } | ||||
|  | ||||
|             let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | ||||
|             let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | ||||
|             let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||||
|             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut message = [0; 64]; | ||||
|             self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; | ||||
|  | ||||
|             let r = public_key.verify(&message, &signature); | ||||
|             trace!( | ||||
|                 "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||||
|                 public_key.to_bytes(), | ||||
|                 signature.to_bytes(), | ||||
|                 message, | ||||
|                 r.is_ok() | ||||
|             ); | ||||
|             r.map_err(into_signature_error)? | ||||
|         } | ||||
|  | ||||
|         self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) | ||||
|     } | ||||
|  | ||||
|     /// Verify the update in DFU with any digest. | ||||
|     pub fn hash_blocking<F: NorFlash, D: Digest>( | ||||
|         &mut self, | ||||
|         dfu_flash: &mut F, | ||||
|         update_len: u32, | ||||
|         chunk_buf: &mut [u8], | ||||
|         output: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         let mut digest = D::new(); | ||||
|         for offset in (0..update_len).step_by(chunk_buf.len()) { | ||||
|             self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?; | ||||
|             let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||||
|             digest.update(&chunk_buf[..len]); | ||||
|         } | ||||
|         output.copy_from_slice(digest.finalize().as_slice()); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Mark to trigger firmware swap on next boot. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||||
|     #[cfg(not(feature = "_verify"))] | ||||
|     pub fn mark_updated_blocking<F: NorFlash>( | ||||
|         &mut self, | ||||
|         state_flash: &mut F, | ||||
|         aligned: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert_eq!(aligned.len(), F::WRITE_SIZE); | ||||
|         self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) | ||||
|     } | ||||
|  | ||||
|     /// Mark firmware boot successful and stop rollback on reset. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||||
|     pub fn mark_booted_blocking<F: NorFlash>( | ||||
|         &mut self, | ||||
|         state_flash: &mut F, | ||||
|         aligned: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert_eq!(aligned.len(), F::WRITE_SIZE); | ||||
|         self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) | ||||
|     } | ||||
|  | ||||
|     fn set_magic_blocking<F: NorFlash>( | ||||
|         &mut self, | ||||
|         aligned: &mut [u8], | ||||
|         magic: u8, | ||||
|         state_flash: &mut F, | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.read_blocking(state_flash, 0, aligned)?; | ||||
|  | ||||
|         if aligned.iter().any(|&b| b != magic) { | ||||
|             // Read progress validity | ||||
|             self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; | ||||
|  | ||||
|             // FIXME: Do not make this assumption. | ||||
|             const STATE_ERASE_VALUE: u8 = 0xFF; | ||||
|  | ||||
|             if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||||
|                 // The current progress validity marker is invalid | ||||
|             } else { | ||||
|                 // Invalidate progress | ||||
|                 aligned.fill(!STATE_ERASE_VALUE); | ||||
|                 self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; | ||||
|             } | ||||
|  | ||||
|             // Clear magic and progress | ||||
|             self.state.wipe_blocking(state_flash)?; | ||||
|  | ||||
|             // Set magic | ||||
|             aligned.fill(magic); | ||||
|             self.state.write_blocking(state_flash, 0, aligned)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Write data to a flash page. | ||||
|     /// | ||||
|     /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// Failing to meet alignment and size requirements may result in a panic. | ||||
|     pub fn write_firmware_blocking<F: NorFlash>( | ||||
|         &mut self, | ||||
|         offset: usize, | ||||
|         data: &[u8], | ||||
|         dfu_flash: &mut F, | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert!(data.len() >= F::ERASE_SIZE); | ||||
|  | ||||
|         self.dfu | ||||
|             .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; | ||||
|  | ||||
|         self.dfu.write_blocking(dfu_flash, offset as u32, data)?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||||
|     /// returning its `Partition`. | ||||
|     /// | ||||
|     /// Using this instead of `write_firmware_blocking` allows for an optimized | ||||
|     /// API in exchange for added complexity. | ||||
|     pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> { | ||||
|         self.dfu.wipe_blocking(flash)?; | ||||
|  | ||||
|         Ok(self.dfu) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use futures::executor::block_on; | ||||
|     use sha1::{Digest, Sha1}; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::mem_flash::MemFlash; | ||||
|  | ||||
|     #[test] | ||||
|     fn can_verify_sha1() { | ||||
|         const STATE: Partition = Partition::new(0, 4096); | ||||
|         const DFU: Partition = Partition::new(65536, 131072); | ||||
|  | ||||
|         let mut flash = MemFlash::<131072, 4096, 8>::default(); | ||||
|  | ||||
|         let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||||
|         let mut to_write = [0; 4096]; | ||||
|         to_write[..7].copy_from_slice(update.as_slice()); | ||||
|  | ||||
|         let mut updater = FirmwareUpdater::new(DFU, STATE); | ||||
|         block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); | ||||
|         let mut chunk_buf = [0; 2]; | ||||
|         let mut hash = [0; 20]; | ||||
|         block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||||
|  | ||||
|         assert_eq!(Sha1::digest(update).as_slice(), hash); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										311
									
								
								embassy-boot/boot/src/firmware_updater/asynch.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								embassy-boot/boot/src/firmware_updater/asynch.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | ||||
| use digest::Digest; | ||||
| #[cfg(target_os = "none")] | ||||
| use embassy_embedded_hal::flash::partition::Partition; | ||||
| #[cfg(target_os = "none")] | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embedded_storage_async::nor_flash::NorFlash; | ||||
|  | ||||
| use super::FirmwareUpdaterConfig; | ||||
| use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||||
|  | ||||
| /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||||
| /// 'mess up' the internal bootloader state | ||||
| pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | ||||
|     dfu: DFU, | ||||
|     state: FirmwareState<'d, STATE>, | ||||
| } | ||||
|  | ||||
| #[cfg(target_os = "none")] | ||||
| impl<'a, FLASH: NorFlash> | ||||
|     FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> | ||||
| { | ||||
|     /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||||
|     pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self { | ||||
|         extern "C" { | ||||
|             static __bootloader_state_start: u32; | ||||
|             static __bootloader_state_end: u32; | ||||
|             static __bootloader_dfu_start: u32; | ||||
|             static __bootloader_dfu_end: u32; | ||||
|         } | ||||
|  | ||||
|         let dfu = unsafe { | ||||
|             let start = &__bootloader_dfu_start as *const u32 as u32; | ||||
|             let end = &__bootloader_dfu_end as *const u32 as u32; | ||||
|             trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||||
|  | ||||
|             Partition::new(flash, start, end - start) | ||||
|         }; | ||||
|         let state = unsafe { | ||||
|             let start = &__bootloader_state_start as *const u32 as u32; | ||||
|             let end = &__bootloader_state_end as *const u32 as u32; | ||||
|             trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||||
|  | ||||
|             Partition::new(flash, start, end - start) | ||||
|         }; | ||||
|  | ||||
|         Self { dfu, state } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | ||||
|     /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||||
|     pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||||
|         Self { | ||||
|             dfu: config.dfu, | ||||
|             state: FirmwareState::new(config.state, aligned), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Obtain the current state. | ||||
|     /// | ||||
|     /// This is useful to check if the bootloader has just done a swap, in order | ||||
|     /// to do verifications and self-tests of the new image before calling | ||||
|     /// `mark_booted`. | ||||
|     pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||||
|         self.state.get_state().await | ||||
|     } | ||||
|  | ||||
|     /// Verify the DFU given a public key. If there is an error then DO NOT | ||||
|     /// proceed with updating the firmware as it must be signed with a | ||||
|     /// corresponding private key (otherwise it could be malicious firmware). | ||||
|     /// | ||||
|     /// Mark to trigger firmware swap on next boot if verify suceeds. | ||||
|     /// | ||||
|     /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||||
|     /// been generated from a SHA-512 digest of the firmware bytes. | ||||
|     /// | ||||
|     /// If no signature feature is set then this method will always return a | ||||
|     /// signature error. | ||||
|     #[cfg(feature = "_verify")] | ||||
|     pub async fn verify_and_mark_updated( | ||||
|         &mut self, | ||||
|         _public_key: &[u8], | ||||
|         _signature: &[u8], | ||||
|         _update_len: u32, | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert!(_update_len <= self.dfu.capacity() as u32); | ||||
|  | ||||
|         self.state.verify_booted().await?; | ||||
|  | ||||
|         #[cfg(feature = "ed25519-dalek")] | ||||
|         { | ||||
|             use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | ||||
|  | ||||
|             use crate::digest_adapters::ed25519_dalek::Sha512; | ||||
|  | ||||
|             let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||||
|  | ||||
|             let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||||
|             let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut chunk_buf = [0; 2]; | ||||
|             let mut message = [0; 64]; | ||||
|             self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||||
|  | ||||
|             public_key.verify(&message, &signature).map_err(into_signature_error)? | ||||
|         } | ||||
|         #[cfg(feature = "ed25519-salty")] | ||||
|         { | ||||
|             use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||||
|             use salty::{PublicKey, Signature}; | ||||
|  | ||||
|             use crate::digest_adapters::salty::Sha512; | ||||
|  | ||||
|             fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||||
|                 FirmwareUpdaterError::Signature(signature::Error::default()) | ||||
|             } | ||||
|  | ||||
|             let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | ||||
|             let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | ||||
|             let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||||
|             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut message = [0; 64]; | ||||
|             let mut chunk_buf = [0; 2]; | ||||
|             self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||||
|  | ||||
|             let r = public_key.verify(&message, &signature); | ||||
|             trace!( | ||||
|                 "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||||
|                 public_key.to_bytes(), | ||||
|                 signature.to_bytes(), | ||||
|                 message, | ||||
|                 r.is_ok() | ||||
|             ); | ||||
|             r.map_err(into_signature_error)? | ||||
|         } | ||||
|  | ||||
|         self.state.mark_updated().await | ||||
|     } | ||||
|  | ||||
|     /// Verify the update in DFU with any digest. | ||||
|     pub async fn hash<D: Digest>( | ||||
|         &mut self, | ||||
|         update_len: u32, | ||||
|         chunk_buf: &mut [u8], | ||||
|         output: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         let mut digest = D::new(); | ||||
|         for offset in (0..update_len).step_by(chunk_buf.len()) { | ||||
|             self.dfu.read(offset, chunk_buf).await?; | ||||
|             let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||||
|             digest.update(&chunk_buf[..len]); | ||||
|         } | ||||
|         output.copy_from_slice(digest.finalize().as_slice()); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Mark to trigger firmware swap on next boot. | ||||
|     #[cfg(not(feature = "_verify"))] | ||||
|     pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.mark_updated().await | ||||
|     } | ||||
|  | ||||
|     /// Mark firmware boot successful and stop rollback on reset. | ||||
|     pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.mark_booted().await | ||||
|     } | ||||
|  | ||||
|     /// Write data to a flash page. | ||||
|     /// | ||||
|     /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// Failing to meet alignment and size requirements may result in a panic. | ||||
|     pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert!(data.len() >= DFU::ERASE_SIZE); | ||||
|  | ||||
|         self.state.verify_booted().await?; | ||||
|  | ||||
|         self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; | ||||
|  | ||||
|         self.dfu.write(offset as u32, data).await?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||||
|     /// returning its `Partition`. | ||||
|     /// | ||||
|     /// Using this instead of `write_firmware` allows for an optimized API in | ||||
|     /// exchange for added complexity. | ||||
|     pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { | ||||
|         self.state.verify_booted().await?; | ||||
|         self.dfu.erase(0, self.dfu.capacity() as u32).await?; | ||||
|  | ||||
|         Ok(&mut self.dfu) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Manages the state partition of the firmware update. | ||||
| /// | ||||
| /// Can be used standalone for more fine grained control, or as part of the updater. | ||||
| pub struct FirmwareState<'d, STATE> { | ||||
|     state: STATE, | ||||
|     aligned: &'d mut [u8], | ||||
| } | ||||
|  | ||||
| impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | ||||
|     /// Create a firmware state instance with a buffer for magic content and state partition. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||||
|     /// and written to. | ||||
|     pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { | ||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||||
|         Self { state, aligned } | ||||
|     } | ||||
|  | ||||
|     // Make sure we are running a booted firmware to avoid reverting to a bad state. | ||||
|     async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         if self.get_state().await? == State::Boot { | ||||
|             Ok(()) | ||||
|         } else { | ||||
|             Err(FirmwareUpdaterError::BadState) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Obtain the current state. | ||||
|     /// | ||||
|     /// This is useful to check if the bootloader has just done a swap, in order | ||||
|     /// to do verifications and self-tests of the new image before calling | ||||
|     /// `mark_booted`. | ||||
|     pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||||
|         self.state.read(0, &mut self.aligned).await?; | ||||
|  | ||||
|         if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||||
|             Ok(State::Swap) | ||||
|         } else { | ||||
|             Ok(State::Boot) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Mark to trigger firmware swap on next boot. | ||||
|     pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.set_magic(SWAP_MAGIC).await | ||||
|     } | ||||
|  | ||||
|     /// Mark firmware boot successful and stop rollback on reset. | ||||
|     pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.set_magic(BOOT_MAGIC).await | ||||
|     } | ||||
|  | ||||
|     async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.read(0, &mut self.aligned).await?; | ||||
|  | ||||
|         if self.aligned.iter().any(|&b| b != magic) { | ||||
|             // Read progress validity | ||||
|             self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; | ||||
|  | ||||
|             if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||||
|                 // The current progress validity marker is invalid | ||||
|             } else { | ||||
|                 // Invalidate progress | ||||
|                 self.aligned.fill(!STATE_ERASE_VALUE); | ||||
|                 self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; | ||||
|             } | ||||
|  | ||||
|             // Clear magic and progress | ||||
|             self.state.erase(0, self.state.capacity() as u32).await?; | ||||
|  | ||||
|             // Set magic | ||||
|             self.aligned.fill(magic); | ||||
|             self.state.write(0, &self.aligned).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use embassy_embedded_hal::flash::partition::Partition; | ||||
|     use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
|     use embassy_sync::mutex::Mutex; | ||||
|     use futures::executor::block_on; | ||||
|     use sha1::{Digest, Sha1}; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::mem_flash::MemFlash; | ||||
|  | ||||
|     #[test] | ||||
|     fn can_verify_sha1() { | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default()); | ||||
|         let state = Partition::new(&flash, 0, 4096); | ||||
|         let dfu = Partition::new(&flash, 65536, 65536); | ||||
|         let mut aligned = [0; 8]; | ||||
|  | ||||
|         let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||||
|         let mut to_write = [0; 4096]; | ||||
|         to_write[..7].copy_from_slice(update.as_slice()); | ||||
|  | ||||
|         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||||
|         block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); | ||||
|         let mut chunk_buf = [0; 2]; | ||||
|         let mut hash = [0; 20]; | ||||
|         block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||||
|  | ||||
|         assert_eq!(Sha1::digest(update).as_slice(), hash); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										320
									
								
								embassy-boot/boot/src/firmware_updater/blocking.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								embassy-boot/boot/src/firmware_updater/blocking.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,320 @@ | ||||
| use digest::Digest; | ||||
| #[cfg(target_os = "none")] | ||||
| use embassy_embedded_hal::flash::partition::BlockingPartition; | ||||
| #[cfg(target_os = "none")] | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embedded_storage::nor_flash::NorFlash; | ||||
|  | ||||
| use super::FirmwareUpdaterConfig; | ||||
| use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||||
|  | ||||
| /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||||
| /// 'mess up' the internal bootloader state | ||||
| pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | ||||
|     dfu: DFU, | ||||
|     state: BlockingFirmwareState<'d, STATE>, | ||||
| } | ||||
|  | ||||
| #[cfg(target_os = "none")] | ||||
| impl<'a, FLASH: NorFlash> | ||||
|     FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>> | ||||
| { | ||||
|     /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||||
|     pub fn from_linkerfile_blocking( | ||||
|         flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>, | ||||
|     ) -> Self { | ||||
|         extern "C" { | ||||
|             static __bootloader_state_start: u32; | ||||
|             static __bootloader_state_end: u32; | ||||
|             static __bootloader_dfu_start: u32; | ||||
|             static __bootloader_dfu_end: u32; | ||||
|         } | ||||
|  | ||||
|         let dfu = unsafe { | ||||
|             let start = &__bootloader_dfu_start as *const u32 as u32; | ||||
|             let end = &__bootloader_dfu_end as *const u32 as u32; | ||||
|             trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||||
|  | ||||
|             BlockingPartition::new(flash, start, end - start) | ||||
|         }; | ||||
|         let state = unsafe { | ||||
|             let start = &__bootloader_state_start as *const u32 as u32; | ||||
|             let end = &__bootloader_state_end as *const u32 as u32; | ||||
|             trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||||
|  | ||||
|             BlockingPartition::new(flash, start, end - start) | ||||
|         }; | ||||
|  | ||||
|         Self { dfu, state } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> { | ||||
|     /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||||
|     /// and written to. | ||||
|     pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||||
|         Self { | ||||
|             dfu: config.dfu, | ||||
|             state: BlockingFirmwareState::new(config.state, aligned), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Obtain the current state. | ||||
|     /// | ||||
|     /// This is useful to check if the bootloader has just done a swap, in order | ||||
|     /// to do verifications and self-tests of the new image before calling | ||||
|     /// `mark_booted`. | ||||
|     pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||||
|         self.state.get_state() | ||||
|     } | ||||
|  | ||||
|     /// Verify the DFU given a public key. If there is an error then DO NOT | ||||
|     /// proceed with updating the firmware as it must be signed with a | ||||
|     /// corresponding private key (otherwise it could be malicious firmware). | ||||
|     /// | ||||
|     /// Mark to trigger firmware swap on next boot if verify suceeds. | ||||
|     /// | ||||
|     /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||||
|     /// been generated from a SHA-512 digest of the firmware bytes. | ||||
|     /// | ||||
|     /// If no signature feature is set then this method will always return a | ||||
|     /// signature error. | ||||
|     #[cfg(feature = "_verify")] | ||||
|     pub fn verify_and_mark_updated( | ||||
|         &mut self, | ||||
|         _public_key: &[u8], | ||||
|         _signature: &[u8], | ||||
|         _update_len: u32, | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert!(_update_len <= self.dfu.capacity() as u32); | ||||
|  | ||||
|         self.state.verify_booted()?; | ||||
|  | ||||
|         #[cfg(feature = "ed25519-dalek")] | ||||
|         { | ||||
|             use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | ||||
|  | ||||
|             use crate::digest_adapters::ed25519_dalek::Sha512; | ||||
|  | ||||
|             let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||||
|  | ||||
|             let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||||
|             let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut message = [0; 64]; | ||||
|             let mut chunk_buf = [0; 2]; | ||||
|             self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?; | ||||
|  | ||||
|             public_key.verify(&message, &signature).map_err(into_signature_error)? | ||||
|         } | ||||
|         #[cfg(feature = "ed25519-salty")] | ||||
|         { | ||||
|             use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||||
|             use salty::{PublicKey, Signature}; | ||||
|  | ||||
|             use crate::digest_adapters::salty::Sha512; | ||||
|  | ||||
|             fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||||
|                 FirmwareUpdaterError::Signature(signature::Error::default()) | ||||
|             } | ||||
|  | ||||
|             let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | ||||
|             let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | ||||
|             let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||||
|             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||||
|  | ||||
|             let mut message = [0; 64]; | ||||
|             let mut chunk_buf = [0; 2]; | ||||
|             self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?; | ||||
|  | ||||
|             let r = public_key.verify(&message, &signature); | ||||
|             trace!( | ||||
|                 "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||||
|                 public_key.to_bytes(), | ||||
|                 signature.to_bytes(), | ||||
|                 message, | ||||
|                 r.is_ok() | ||||
|             ); | ||||
|             r.map_err(into_signature_error)? | ||||
|         } | ||||
|  | ||||
|         self.state.mark_updated() | ||||
|     } | ||||
|  | ||||
|     /// Verify the update in DFU with any digest. | ||||
|     pub fn hash<D: Digest>( | ||||
|         &mut self, | ||||
|         update_len: u32, | ||||
|         chunk_buf: &mut [u8], | ||||
|         output: &mut [u8], | ||||
|     ) -> Result<(), FirmwareUpdaterError> { | ||||
|         let mut digest = D::new(); | ||||
|         for offset in (0..update_len).step_by(chunk_buf.len()) { | ||||
|             self.dfu.read(offset, chunk_buf)?; | ||||
|             let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||||
|             digest.update(&chunk_buf[..len]); | ||||
|         } | ||||
|         output.copy_from_slice(digest.finalize().as_slice()); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Mark to trigger firmware swap on next boot. | ||||
|     #[cfg(not(feature = "_verify"))] | ||||
|     pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.mark_updated() | ||||
|     } | ||||
|  | ||||
|     /// Mark firmware boot successful and stop rollback on reset. | ||||
|     pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.mark_booted() | ||||
|     } | ||||
|  | ||||
|     /// Write data to a flash page. | ||||
|     /// | ||||
|     /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// Failing to meet alignment and size requirements may result in a panic. | ||||
|     pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | ||||
|         assert!(data.len() >= DFU::ERASE_SIZE); | ||||
|         self.state.verify_booted()?; | ||||
|  | ||||
|         self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; | ||||
|  | ||||
|         self.dfu.write(offset as u32, data)?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||||
|     /// returning its `Partition`. | ||||
|     /// | ||||
|     /// Using this instead of `write_firmware` allows for an optimized API in | ||||
|     /// exchange for added complexity. | ||||
|     pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { | ||||
|         self.state.verify_booted()?; | ||||
|         self.dfu.erase(0, self.dfu.capacity() as u32)?; | ||||
|  | ||||
|         Ok(&mut self.dfu) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Manages the state partition of the firmware update. | ||||
| /// | ||||
| /// Can be used standalone for more fine grained control, or as part of the updater. | ||||
| pub struct BlockingFirmwareState<'d, STATE> { | ||||
|     state: STATE, | ||||
|     aligned: &'d mut [u8], | ||||
| } | ||||
|  | ||||
| impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | ||||
|     /// Create a firmware state instance with a buffer for magic content and state partition. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||||
|     /// and written to. | ||||
|     pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { | ||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||||
|         Self { state, aligned } | ||||
|     } | ||||
|  | ||||
|     // Make sure we are running a booted firmware to avoid reverting to a bad state. | ||||
|     fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         if self.get_state()? == State::Boot { | ||||
|             Ok(()) | ||||
|         } else { | ||||
|             Err(FirmwareUpdaterError::BadState) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Obtain the current state. | ||||
|     /// | ||||
|     /// This is useful to check if the bootloader has just done a swap, in order | ||||
|     /// to do verifications and self-tests of the new image before calling | ||||
|     /// `mark_booted`. | ||||
|     pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||||
|         self.state.read(0, &mut self.aligned)?; | ||||
|  | ||||
|         if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||||
|             Ok(State::Swap) | ||||
|         } else { | ||||
|             Ok(State::Boot) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Mark to trigger firmware swap on next boot. | ||||
|     pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.set_magic(SWAP_MAGIC) | ||||
|     } | ||||
|  | ||||
|     /// Mark firmware boot successful and stop rollback on reset. | ||||
|     pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.set_magic(BOOT_MAGIC) | ||||
|     } | ||||
|  | ||||
|     fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { | ||||
|         self.state.read(0, &mut self.aligned)?; | ||||
|  | ||||
|         if self.aligned.iter().any(|&b| b != magic) { | ||||
|             // Read progress validity | ||||
|             self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?; | ||||
|  | ||||
|             if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||||
|                 // The current progress validity marker is invalid | ||||
|             } else { | ||||
|                 // Invalidate progress | ||||
|                 self.aligned.fill(!STATE_ERASE_VALUE); | ||||
|                 self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?; | ||||
|             } | ||||
|  | ||||
|             // Clear magic and progress | ||||
|             self.state.erase(0, self.state.capacity() as u32)?; | ||||
|  | ||||
|             // Set magic | ||||
|             self.aligned.fill(magic); | ||||
|             self.state.write(0, &self.aligned)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use core::cell::RefCell; | ||||
|  | ||||
|     use embassy_embedded_hal::flash::partition::BlockingPartition; | ||||
|     use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
|     use embassy_sync::blocking_mutex::Mutex; | ||||
|     use sha1::{Digest, Sha1}; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::mem_flash::MemFlash; | ||||
|  | ||||
|     #[test] | ||||
|     fn can_verify_sha1() { | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); | ||||
|         let state = BlockingPartition::new(&flash, 0, 4096); | ||||
|         let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||||
|         let mut aligned = [0; 8]; | ||||
|  | ||||
|         let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||||
|         let mut to_write = [0; 4096]; | ||||
|         to_write[..7].copy_from_slice(update.as_slice()); | ||||
|  | ||||
|         let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||||
|         updater.write_firmware(0, to_write.as_slice()).unwrap(); | ||||
|         let mut chunk_buf = [0; 2]; | ||||
|         let mut hash = [0; 20]; | ||||
|         updater | ||||
|             .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) | ||||
|             .unwrap(); | ||||
|  | ||||
|         assert_eq!(Sha1::digest(update).as_slice(), hash); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										51
									
								
								embassy-boot/boot/src/firmware_updater/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								embassy-boot/boot/src/firmware_updater/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| #[cfg(feature = "nightly")] | ||||
| mod asynch; | ||||
| mod blocking; | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| pub use asynch::{FirmwareState, FirmwareUpdater}; | ||||
| pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; | ||||
| use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | ||||
|  | ||||
| /// Firmware updater flash configuration holding the two flashes used by the updater | ||||
| /// | ||||
| /// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. | ||||
| /// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition | ||||
| /// the provided flash according to symbols defined in the linkerfile. | ||||
| pub struct FirmwareUpdaterConfig<DFU, STATE> { | ||||
|     /// The dfu flash partition | ||||
|     pub dfu: DFU, | ||||
|     /// The state flash partition | ||||
|     pub state: STATE, | ||||
| } | ||||
|  | ||||
| /// Errors returned by FirmwareUpdater | ||||
| #[derive(Debug)] | ||||
| pub enum FirmwareUpdaterError { | ||||
|     /// Error from flash. | ||||
|     Flash(NorFlashErrorKind), | ||||
|     /// Signature errors. | ||||
|     Signature(signature::Error), | ||||
|     /// Bad state. | ||||
|     BadState, | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl defmt::Format for FirmwareUpdaterError { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         match self { | ||||
|             FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), | ||||
|             FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), | ||||
|             FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<E> From<E> for FirmwareUpdaterError | ||||
| where | ||||
|     E: NorFlashError, | ||||
| { | ||||
|     fn from(error: E) -> Self { | ||||
|         FirmwareUpdaterError::Flash(error.kind()) | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,8 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
|  | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
|  | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
|  | ||||
| @@ -81,14 +83,17 @@ macro_rules! todo { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::unreachable!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::unreachable!($($x)*); | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> { | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(unused)] | ||||
| pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||
|  | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #![feature(async_fn_in_trait)] | ||||
| #![allow(incomplete_features)] | ||||
| #![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] | ||||
| #![no_std] | ||||
| #![warn(missing_docs)] | ||||
| #![doc = include_str!("../README.md")] | ||||
| @@ -8,12 +7,20 @@ mod fmt; | ||||
| mod boot_loader; | ||||
| mod digest_adapters; | ||||
| mod firmware_updater; | ||||
| #[cfg(test)] | ||||
| mod mem_flash; | ||||
| mod partition; | ||||
| #[cfg(test)] | ||||
| mod test_flash; | ||||
|  | ||||
| pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; | ||||
| pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; | ||||
| pub use partition::Partition; | ||||
| // The expected value of the flash after an erase | ||||
| // TODO: Use the value provided by NorFlash when available | ||||
| pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; | ||||
| pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; | ||||
| pub use firmware_updater::{ | ||||
|     BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError, | ||||
| }; | ||||
| #[cfg(feature = "nightly")] | ||||
| pub use firmware_updater::{FirmwareState, FirmwareUpdater}; | ||||
|  | ||||
| pub(crate) const BOOT_MAGIC: u8 = 0xD0; | ||||
| pub(crate) const SWAP_MAGIC: u8 = 0xF0; | ||||
| @@ -46,10 +53,20 @@ impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     #![allow(unused_imports)] | ||||
|  | ||||
|     use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||||
|     #[cfg(feature = "nightly")] | ||||
|     use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; | ||||
|     use futures::executor::block_on; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::boot_loader::BootLoaderConfig; | ||||
|     use crate::firmware_updater::FirmwareUpdaterConfig; | ||||
|     use crate::mem_flash::MemFlash; | ||||
|     #[cfg(feature = "nightly")] | ||||
|     use crate::test_flash::AsyncTestFlash; | ||||
|     use crate::test_flash::BlockingTestFlash; | ||||
|  | ||||
|     /* | ||||
|     #[test] | ||||
| @@ -68,151 +85,193 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     fn test_boot_state() { | ||||
|         const STATE: Partition = Partition::new(0, 4096); | ||||
|         const ACTIVE: Partition = Partition::new(4096, 61440); | ||||
|         const DFU: Partition = Partition::new(61440, 122880); | ||||
|         let flash = BlockingTestFlash::new(BootLoaderConfig { | ||||
|             active: MemFlash::<57344, 4096, 4>::default(), | ||||
|             dfu: MemFlash::<61440, 4096, 4>::default(), | ||||
|             state: MemFlash::<4096, 4096, 4>::default(), | ||||
|         }); | ||||
|  | ||||
|         let mut flash = MemFlash::<131072, 4096, 4>::default(); | ||||
|         flash.mem[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); | ||||
|         let mut flash = SingleFlashConfig::new(&mut flash); | ||||
|         flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); | ||||
|  | ||||
|         let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); | ||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||
|             active: flash.active(), | ||||
|             dfu: flash.dfu(), | ||||
|             state: flash.state(), | ||||
|         }); | ||||
|  | ||||
|         let mut page = [0; 4096]; | ||||
|         assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap()); | ||||
|         assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     #[cfg(not(feature = "_verify"))] | ||||
|     #[cfg(all(feature = "nightly", not(feature = "_verify")))] | ||||
|     fn test_swap_state() { | ||||
|         const STATE: Partition = Partition::new(0, 4096); | ||||
|         const ACTIVE: Partition = Partition::new(4096, 61440); | ||||
|         const DFU: Partition = Partition::new(61440, 122880); | ||||
|         let mut flash = MemFlash::<131072, 4096, 4>::random(); | ||||
|         const FIRMWARE_SIZE: usize = 57344; | ||||
|         let flash = AsyncTestFlash::new(BootLoaderConfig { | ||||
|             active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(), | ||||
|             dfu: MemFlash::<61440, 4096, 4>::default(), | ||||
|             state: MemFlash::<4096, 4096, 4>::default(), | ||||
|         }); | ||||
|  | ||||
|         let original = [rand::random::<u8>(); ACTIVE.size() as usize]; | ||||
|         let update = [rand::random::<u8>(); ACTIVE.size() as usize]; | ||||
|         const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||||
|         const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||||
|         let mut aligned = [0; 4]; | ||||
|  | ||||
|         flash.program(ACTIVE.from, &original).unwrap(); | ||||
|         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||||
|         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||||
|  | ||||
|         let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); | ||||
|         let mut updater = FirmwareUpdater::new(DFU, STATE); | ||||
|         block_on(updater.write_firmware(0, &update, &mut flash)).unwrap(); | ||||
|         block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); | ||||
|         let mut updater = FirmwareUpdater::new( | ||||
|             FirmwareUpdaterConfig { | ||||
|                 dfu: flash.dfu(), | ||||
|                 state: flash.state(), | ||||
|             }, | ||||
|             &mut aligned, | ||||
|         ); | ||||
|         block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||||
|         block_on(updater.mark_updated()).unwrap(); | ||||
|  | ||||
|         // Writing after marking updated is not allowed until marked as booted. | ||||
|         let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); | ||||
|         assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); | ||||
|  | ||||
|         let flash = flash.into_blocking(); | ||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||
|             active: flash.active(), | ||||
|             dfu: flash.dfu(), | ||||
|             state: flash.state(), | ||||
|         }); | ||||
|  | ||||
|         let mut page = [0; 1024]; | ||||
|         assert_eq!( | ||||
|             State::Swap, | ||||
|             bootloader | ||||
|                 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) | ||||
|                 .unwrap() | ||||
|         ); | ||||
|         assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||||
|  | ||||
|         flash.assert_eq(ACTIVE.from, &update); | ||||
|         let mut read_buf = [0; FIRMWARE_SIZE]; | ||||
|         flash.active().read(0, &mut read_buf).unwrap(); | ||||
|         assert_eq!(UPDATE, read_buf); | ||||
|         // First DFU page is untouched | ||||
|         flash.assert_eq(DFU.from + 4096, &original); | ||||
|         flash.dfu().read(4096, &mut read_buf).unwrap(); | ||||
|         assert_eq!(ORIGINAL, read_buf); | ||||
|  | ||||
|         // Running again should cause a revert | ||||
|         assert_eq!( | ||||
|             State::Swap, | ||||
|             bootloader | ||||
|                 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) | ||||
|                 .unwrap() | ||||
|         ); | ||||
|         assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||||
|  | ||||
|         flash.assert_eq(ACTIVE.from, &original); | ||||
|         // Last page is untouched | ||||
|         flash.assert_eq(DFU.from, &update); | ||||
|         let mut read_buf = [0; FIRMWARE_SIZE]; | ||||
|         flash.active().read(0, &mut read_buf).unwrap(); | ||||
|         assert_eq!(ORIGINAL, read_buf); | ||||
|         // Last DFU page is untouched | ||||
|         flash.dfu().read(0, &mut read_buf).unwrap(); | ||||
|         assert_eq!(UPDATE, read_buf); | ||||
|  | ||||
|         // Mark as booted | ||||
|         block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap(); | ||||
|         assert_eq!( | ||||
|             State::Boot, | ||||
|             bootloader | ||||
|                 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) | ||||
|                 .unwrap() | ||||
|         let flash = flash.into_async(); | ||||
|         let mut updater = FirmwareUpdater::new( | ||||
|             FirmwareUpdaterConfig { | ||||
|                 dfu: flash.dfu(), | ||||
|                 state: flash.state(), | ||||
|             }, | ||||
|             &mut aligned, | ||||
|         ); | ||||
|         block_on(updater.mark_booted()).unwrap(); | ||||
|  | ||||
|         let flash = flash.into_blocking(); | ||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||
|             active: flash.active(), | ||||
|             dfu: flash.dfu(), | ||||
|             state: flash.state(), | ||||
|         }); | ||||
|         assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     #[cfg(not(feature = "_verify"))] | ||||
|     fn test_separate_flash_active_page_biggest() { | ||||
|         const STATE: Partition = Partition::new(2048, 4096); | ||||
|         const ACTIVE: Partition = Partition::new(4096, 16384); | ||||
|         const DFU: Partition = Partition::new(0, 16384); | ||||
|     #[cfg(all(feature = "nightly", not(feature = "_verify")))] | ||||
|     fn test_swap_state_active_page_biggest() { | ||||
|         const FIRMWARE_SIZE: usize = 12288; | ||||
|         let flash = AsyncTestFlash::new(BootLoaderConfig { | ||||
|             active: MemFlash::<12288, 4096, 8>::random(), | ||||
|             dfu: MemFlash::<16384, 2048, 8>::random(), | ||||
|             state: MemFlash::<2048, 128, 4>::random(), | ||||
|         }); | ||||
|  | ||||
|         let mut active = MemFlash::<16384, 4096, 8>::random(); | ||||
|         let mut dfu = MemFlash::<16384, 2048, 8>::random(); | ||||
|         let mut state = MemFlash::<4096, 128, 4>::random(); | ||||
|         const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||||
|         const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||||
|         let mut aligned = [0; 4]; | ||||
|  | ||||
|         let original = [rand::random::<u8>(); ACTIVE.size() as usize]; | ||||
|         let update = [rand::random::<u8>(); ACTIVE.size() as usize]; | ||||
|         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||||
|         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||||
|  | ||||
|         active.program(ACTIVE.from, &original).unwrap(); | ||||
|  | ||||
|         let mut updater = FirmwareUpdater::new(DFU, STATE); | ||||
|  | ||||
|         block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap(); | ||||
|         block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); | ||||
|  | ||||
|         let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); | ||||
|         let mut page = [0; 4096]; | ||||
|  | ||||
|         assert_eq!( | ||||
|             State::Swap, | ||||
|             bootloader | ||||
|                 .prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page) | ||||
|                 .unwrap() | ||||
|         let mut updater = FirmwareUpdater::new( | ||||
|             FirmwareUpdaterConfig { | ||||
|                 dfu: flash.dfu(), | ||||
|                 state: flash.state(), | ||||
|             }, | ||||
|             &mut aligned, | ||||
|         ); | ||||
|         block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||||
|         block_on(updater.mark_updated()).unwrap(); | ||||
|  | ||||
|         active.assert_eq(ACTIVE.from, &update); | ||||
|         let flash = flash.into_blocking(); | ||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||
|             active: flash.active(), | ||||
|             dfu: flash.dfu(), | ||||
|             state: flash.state(), | ||||
|         }); | ||||
|  | ||||
|         let mut page = [0; 4096]; | ||||
|         assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||||
|  | ||||
|         let mut read_buf = [0; FIRMWARE_SIZE]; | ||||
|         flash.active().read(0, &mut read_buf).unwrap(); | ||||
|         assert_eq!(UPDATE, read_buf); | ||||
|         // First DFU page is untouched | ||||
|         dfu.assert_eq(DFU.from + 4096, &original); | ||||
|         flash.dfu().read(4096, &mut read_buf).unwrap(); | ||||
|         assert_eq!(ORIGINAL, read_buf); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     #[cfg(not(feature = "_verify"))] | ||||
|     fn test_separate_flash_dfu_page_biggest() { | ||||
|         const STATE: Partition = Partition::new(2048, 4096); | ||||
|         const ACTIVE: Partition = Partition::new(4096, 16384); | ||||
|         const DFU: Partition = Partition::new(0, 16384); | ||||
|     #[cfg(all(feature = "nightly", not(feature = "_verify")))] | ||||
|     fn test_swap_state_dfu_page_biggest() { | ||||
|         const FIRMWARE_SIZE: usize = 12288; | ||||
|         let flash = AsyncTestFlash::new(BootLoaderConfig { | ||||
|             active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(), | ||||
|             dfu: MemFlash::<16384, 4096, 8>::random(), | ||||
|             state: MemFlash::<2048, 128, 4>::random(), | ||||
|         }); | ||||
|  | ||||
|         const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||||
|         const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||||
|         let mut aligned = [0; 4]; | ||||
|         let mut active = MemFlash::<16384, 2048, 4>::random(); | ||||
|         let mut dfu = MemFlash::<16384, 4096, 8>::random(); | ||||
|         let mut state = MemFlash::<4096, 128, 4>::random(); | ||||
|  | ||||
|         let original = [rand::random::<u8>(); ACTIVE.size() as usize]; | ||||
|         let update = [rand::random::<u8>(); ACTIVE.size() as usize]; | ||||
|         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||||
|         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||||
|  | ||||
|         active.program(ACTIVE.from, &original).unwrap(); | ||||
|  | ||||
|         let mut updater = FirmwareUpdater::new(DFU, STATE); | ||||
|  | ||||
|         block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap(); | ||||
|         block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); | ||||
|  | ||||
|         let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); | ||||
|         let mut page = [0; 4096]; | ||||
|         assert_eq!( | ||||
|             State::Swap, | ||||
|             bootloader | ||||
|                 .prepare_boot( | ||||
|                     &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,), | ||||
|                     &mut page | ||||
|                 ) | ||||
|                 .unwrap() | ||||
|         let mut updater = FirmwareUpdater::new( | ||||
|             FirmwareUpdaterConfig { | ||||
|                 dfu: flash.dfu(), | ||||
|                 state: flash.state(), | ||||
|             }, | ||||
|             &mut aligned, | ||||
|         ); | ||||
|         block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||||
|         block_on(updater.mark_updated()).unwrap(); | ||||
|  | ||||
|         active.assert_eq(ACTIVE.from, &update); | ||||
|         let flash = flash.into_blocking(); | ||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||
|             active: flash.active(), | ||||
|             dfu: flash.dfu(), | ||||
|             state: flash.state(), | ||||
|         }); | ||||
|         let mut page = [0; 4096]; | ||||
|         assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||||
|  | ||||
|         let mut read_buf = [0; FIRMWARE_SIZE]; | ||||
|         flash.active().read(0, &mut read_buf).unwrap(); | ||||
|         assert_eq!(UPDATE, read_buf); | ||||
|         // First DFU page is untouched | ||||
|         dfu.assert_eq(DFU.from + 4096, &original); | ||||
|         flash.dfu().read(4096, &mut read_buf).unwrap(); | ||||
|         assert_eq!(ORIGINAL, read_buf); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     #[cfg(feature = "_verify")] | ||||
|     #[cfg(all(feature = "nightly", feature = "_verify"))] | ||||
|     fn test_verify() { | ||||
|         // The following key setup is based on: | ||||
|         // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example | ||||
| @@ -234,29 +293,33 @@ mod tests { | ||||
|         let public_key: PublicKey = keypair.public; | ||||
|  | ||||
|         // Setup flash | ||||
|  | ||||
|         const STATE: Partition = Partition::new(0, 4096); | ||||
|         const DFU: Partition = Partition::new(4096, 8192); | ||||
|         let mut flash = MemFlash::<8192, 4096, 4>::default(); | ||||
|         let flash = BlockingTestFlash::new(BootLoaderConfig { | ||||
|             active: MemFlash::<0, 0, 0>::default(), | ||||
|             dfu: MemFlash::<4096, 4096, 4>::default(), | ||||
|             state: MemFlash::<4096, 4096, 4>::default(), | ||||
|         }); | ||||
|  | ||||
|         let firmware_len = firmware.len(); | ||||
|  | ||||
|         let mut write_buf = [0; 4096]; | ||||
|         write_buf[0..firmware_len].copy_from_slice(firmware); | ||||
|         DFU.write_blocking(&mut flash, 0, &write_buf).unwrap(); | ||||
|         flash.dfu().write(0, &write_buf).unwrap(); | ||||
|  | ||||
|         // On with the test | ||||
|  | ||||
|         let mut updater = FirmwareUpdater::new(DFU, STATE); | ||||
|  | ||||
|         let flash = flash.into_async(); | ||||
|         let mut aligned = [0; 4]; | ||||
|         let mut updater = FirmwareUpdater::new( | ||||
|             FirmwareUpdaterConfig { | ||||
|                 dfu: flash.dfu(), | ||||
|                 state: flash.state(), | ||||
|             }, | ||||
|             &mut aligned, | ||||
|         ); | ||||
|  | ||||
|         assert!(block_on(updater.verify_and_mark_updated( | ||||
|             &mut flash, | ||||
|             &public_key.to_bytes(), | ||||
|             &signature.to_bytes(), | ||||
|             firmware_len as u32, | ||||
|             &mut aligned, | ||||
|         )) | ||||
|         .is_ok()); | ||||
|     } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| use core::ops::{Bound, Range, RangeBounds}; | ||||
|  | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; | ||||
| #[cfg(feature = "nightly")] | ||||
| use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||||
|  | ||||
| pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> { | ||||
| @@ -33,6 +34,52 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { | ||||
|         let len = bytes.len(); | ||||
|         bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { | ||||
|         let offset = offset as usize; | ||||
|         assert!(bytes.len() % WRITE_SIZE == 0); | ||||
|         assert!(offset % WRITE_SIZE == 0); | ||||
|         assert!(offset + bytes.len() <= SIZE); | ||||
|  | ||||
|         if let Some(pending_successes) = self.pending_write_successes { | ||||
|             if pending_successes > 0 { | ||||
|                 self.pending_write_successes = Some(pending_successes - 1); | ||||
|             } else { | ||||
|                 return Err(MemFlashError); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for ((offset, mem_byte), new_byte) in self | ||||
|             .mem | ||||
|             .iter_mut() | ||||
|             .enumerate() | ||||
|             .skip(offset) | ||||
|             .take(bytes.len()) | ||||
|             .zip(bytes) | ||||
|         { | ||||
|             assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); | ||||
|             *mem_byte = *new_byte; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { | ||||
|         let from = from as usize; | ||||
|         let to = to as usize; | ||||
|         assert!(from % ERASE_SIZE == 0); | ||||
|         assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); | ||||
|         for i in from..to { | ||||
|             self.mem[i] = 0xFF; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { | ||||
|         let offset = offset as usize; | ||||
|         assert!(bytes.len() % WRITE_SIZE == 0); | ||||
| @@ -43,12 +90,6 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn assert_eq(&self, offset: u32, expectation: &[u8]) { | ||||
|         for i in 0..expectation.len() { | ||||
|             assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default | ||||
| @@ -77,9 +118,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNo | ||||
|     const READ_SIZE: usize = 1; | ||||
|  | ||||
|     fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         let len = bytes.len(); | ||||
|         bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); | ||||
|         Ok(()) | ||||
|         self.read(offset, bytes) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
| @@ -93,72 +132,42 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFla | ||||
|     const WRITE_SIZE: usize = WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = ERASE_SIZE; | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         let from = from as usize; | ||||
|         let to = to as usize; | ||||
|         assert!(from % ERASE_SIZE == 0); | ||||
|         assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); | ||||
|         for i in from..to { | ||||
|             self.mem[i] = 0xFF; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         let offset = offset as usize; | ||||
|         assert!(bytes.len() % WRITE_SIZE == 0); | ||||
|         assert!(offset % WRITE_SIZE == 0); | ||||
|         assert!(offset + bytes.len() <= SIZE); | ||||
|         self.write(offset, bytes) | ||||
|     } | ||||
|  | ||||
|         if let Some(pending_successes) = self.pending_write_successes { | ||||
|             if pending_successes > 0 { | ||||
|                 self.pending_write_successes = Some(pending_successes - 1); | ||||
|             } else { | ||||
|                 return Err(MemFlashError); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for ((offset, mem_byte), new_byte) in self | ||||
|             .mem | ||||
|             .iter_mut() | ||||
|             .enumerate() | ||||
|             .skip(offset) | ||||
|             .take(bytes.len()) | ||||
|             .zip(bytes) | ||||
|         { | ||||
|             assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); | ||||
|             *mem_byte = *new_byte; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         self.erase(from, to) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     const READ_SIZE: usize = 1; | ||||
|  | ||||
|     async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         <Self as ReadNorFlash>::read(self, offset, bytes) | ||||
|         self.read(offset, bytes) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         <Self as ReadNorFlash>::capacity(self) | ||||
|         SIZE | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     const WRITE_SIZE: usize = WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = ERASE_SIZE; | ||||
|  | ||||
|     async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         <Self as NorFlash>::erase(self, from, to) | ||||
|     async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.write(offset, bytes) | ||||
|     } | ||||
|  | ||||
|     async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         <Self as NorFlash>::write(self, offset, bytes) | ||||
|     async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         self.erase(from, to) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,139 +0,0 @@ | ||||
| use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||||
| use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||||
|  | ||||
| /// A region in flash used by the bootloader. | ||||
| #[derive(Copy, Clone, Debug)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| pub struct Partition { | ||||
|     /// The offset into the flash where the partition starts. | ||||
|     pub from: u32, | ||||
|     /// The offset into the flash where the partition ends. | ||||
|     pub to: u32, | ||||
| } | ||||
|  | ||||
| impl Partition { | ||||
|     /// Create a new partition with the provided range | ||||
|     pub const fn new(from: u32, to: u32) -> Self { | ||||
|         Self { from, to } | ||||
|     } | ||||
|  | ||||
|     /// Return the size of the partition | ||||
|     pub const fn size(&self) -> u32 { | ||||
|         self.to - self.from | ||||
|     } | ||||
|  | ||||
|     /// Read from the partition on the provided flash | ||||
|     pub async fn read<F: AsyncReadNorFlash>( | ||||
|         &self, | ||||
|         flash: &mut F, | ||||
|         offset: u32, | ||||
|         bytes: &mut [u8], | ||||
|     ) -> Result<(), F::Error> { | ||||
|         let offset = self.from as u32 + offset; | ||||
|         flash.read(offset, bytes).await | ||||
|     } | ||||
|  | ||||
|     /// Write to the partition on the provided flash | ||||
|     pub async fn write<F: AsyncNorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { | ||||
|         let offset = self.from as u32 + offset; | ||||
|         flash.write(offset, bytes).await?; | ||||
|         trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Erase part of the partition on the provided flash | ||||
|     pub async fn erase<F: AsyncNorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { | ||||
|         let from = self.from as u32 + from; | ||||
|         let to = self.from as u32 + to; | ||||
|         flash.erase(from, to).await?; | ||||
|         trace!("Erased from 0x{:x} to 0x{:x}", from, to); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Erase the entire partition | ||||
|     pub(crate) async fn wipe<F: AsyncNorFlash>(&self, flash: &mut F) -> Result<(), F::Error> { | ||||
|         let from = self.from as u32; | ||||
|         let to = self.to as u32; | ||||
|         flash.erase(from, to).await?; | ||||
|         trace!("Wiped from 0x{:x} to 0x{:x}", from, to); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Read from the partition on the provided flash | ||||
|     pub fn read_blocking<F: ReadNorFlash>(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> { | ||||
|         let offset = self.from as u32 + offset; | ||||
|         flash.read(offset, bytes) | ||||
|     } | ||||
|  | ||||
|     /// Write to the partition on the provided flash | ||||
|     pub fn write_blocking<F: NorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { | ||||
|         let offset = self.from as u32 + offset; | ||||
|         flash.write(offset, bytes)?; | ||||
|         trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Erase part of the partition on the provided flash | ||||
|     pub fn erase_blocking<F: NorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { | ||||
|         let from = self.from as u32 + from; | ||||
|         let to = self.from as u32 + to; | ||||
|         flash.erase(from, to)?; | ||||
|         trace!("Erased from 0x{:x} to 0x{:x}", from, to); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Erase the entire partition | ||||
|     pub(crate) fn wipe_blocking<F: NorFlash>(&self, flash: &mut F) -> Result<(), F::Error> { | ||||
|         let from = self.from as u32; | ||||
|         let to = self.to as u32; | ||||
|         flash.erase(from, to)?; | ||||
|         trace!("Wiped from 0x{:x} to 0x{:x}", from, to); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::mem_flash::MemFlash; | ||||
|     use crate::Partition; | ||||
|  | ||||
|     #[test] | ||||
|     fn can_erase() { | ||||
|         let mut flash = MemFlash::<1024, 64, 4>::new(0x00); | ||||
|         let partition = Partition::new(256, 512); | ||||
|  | ||||
|         partition.erase_blocking(&mut flash, 64, 192).unwrap(); | ||||
|  | ||||
|         for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) { | ||||
|             assert_eq!(0x00, byte, "Index {}", index); | ||||
|         } | ||||
|  | ||||
|         for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) { | ||||
|             assert_eq!(0xFF, byte, "Index {}", index); | ||||
|         } | ||||
|  | ||||
|         for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) { | ||||
|             assert_eq!(0x00, byte, "Index {}", index); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn can_wipe() { | ||||
|         let mut flash = MemFlash::<1024, 64, 4>::new(0x00); | ||||
|         let partition = Partition::new(256, 512); | ||||
|  | ||||
|         partition.wipe_blocking(&mut flash).unwrap(); | ||||
|  | ||||
|         for (index, byte) in flash.mem.iter().copied().enumerate().take(256) { | ||||
|             assert_eq!(0x00, byte, "Index {}", index); | ||||
|         } | ||||
|  | ||||
|         for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) { | ||||
|             assert_eq!(0xFF, byte, "Index {}", index); | ||||
|         } | ||||
|  | ||||
|         for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) { | ||||
|             assert_eq!(0x00, byte, "Index {}", index); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										64
									
								
								embassy-boot/boot/src/test_flash/asynch.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								embassy-boot/boot/src/test_flash/asynch.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| use embassy_embedded_hal::flash::partition::Partition; | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embassy_sync::mutex::Mutex; | ||||
| use embedded_storage_async::nor_flash::NorFlash; | ||||
|  | ||||
| use crate::BootLoaderConfig; | ||||
|  | ||||
| pub struct AsyncTestFlash<ACTIVE, DFU, STATE> | ||||
| where | ||||
|     ACTIVE: NorFlash, | ||||
|     DFU: NorFlash, | ||||
|     STATE: NorFlash, | ||||
| { | ||||
|     active: Mutex<NoopRawMutex, ACTIVE>, | ||||
|     dfu: Mutex<NoopRawMutex, DFU>, | ||||
|     state: Mutex<NoopRawMutex, STATE>, | ||||
| } | ||||
|  | ||||
| impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE> | ||||
| where | ||||
|     ACTIVE: NorFlash, | ||||
|     DFU: NorFlash, | ||||
|     STATE: NorFlash, | ||||
| { | ||||
|     pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||||
|         Self { | ||||
|             active: Mutex::new(config.active), | ||||
|             dfu: Mutex::new(config.dfu), | ||||
|             state: Mutex::new(config.state), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn active(&self) -> Partition<NoopRawMutex, ACTIVE> { | ||||
|         Self::create_partition(&self.active) | ||||
|     } | ||||
|  | ||||
|     pub fn dfu(&self) -> Partition<NoopRawMutex, DFU> { | ||||
|         Self::create_partition(&self.dfu) | ||||
|     } | ||||
|  | ||||
|     pub fn state(&self) -> Partition<NoopRawMutex, STATE> { | ||||
|         Self::create_partition(&self.state) | ||||
|     } | ||||
|  | ||||
|     fn create_partition<T: NorFlash>(mutex: &Mutex<NoopRawMutex, T>) -> Partition<NoopRawMutex, T> { | ||||
|         Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE> | ||||
| where | ||||
|     ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, | ||||
|     DFU: NorFlash + embedded_storage::nor_flash::NorFlash, | ||||
|     STATE: NorFlash + embedded_storage::nor_flash::NorFlash, | ||||
| { | ||||
|     pub fn into_blocking(self) -> super::BlockingTestFlash<ACTIVE, DFU, STATE> { | ||||
|         let config = BootLoaderConfig { | ||||
|             active: self.active.into_inner(), | ||||
|             dfu: self.dfu.into_inner(), | ||||
|             state: self.state.into_inner(), | ||||
|         }; | ||||
|         super::BlockingTestFlash::new(config) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										69
									
								
								embassy-boot/boot/src/test_flash/blocking.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								embassy-boot/boot/src/test_flash/blocking.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| use core::cell::RefCell; | ||||
|  | ||||
| use embassy_embedded_hal::flash::partition::BlockingPartition; | ||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
| use embassy_sync::blocking_mutex::Mutex; | ||||
| use embedded_storage::nor_flash::NorFlash; | ||||
|  | ||||
| use crate::BootLoaderConfig; | ||||
|  | ||||
| pub struct BlockingTestFlash<ACTIVE, DFU, STATE> | ||||
| where | ||||
|     ACTIVE: NorFlash, | ||||
|     DFU: NorFlash, | ||||
|     STATE: NorFlash, | ||||
| { | ||||
|     active: Mutex<NoopRawMutex, RefCell<ACTIVE>>, | ||||
|     dfu: Mutex<NoopRawMutex, RefCell<DFU>>, | ||||
|     state: Mutex<NoopRawMutex, RefCell<STATE>>, | ||||
| } | ||||
|  | ||||
| impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE> | ||||
| where | ||||
|     ACTIVE: NorFlash, | ||||
|     DFU: NorFlash, | ||||
|     STATE: NorFlash, | ||||
| { | ||||
|     pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||||
|         Self { | ||||
|             active: Mutex::new(RefCell::new(config.active)), | ||||
|             dfu: Mutex::new(RefCell::new(config.dfu)), | ||||
|             state: Mutex::new(RefCell::new(config.state)), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn active(&self) -> BlockingPartition<NoopRawMutex, ACTIVE> { | ||||
|         Self::create_partition(&self.active) | ||||
|     } | ||||
|  | ||||
|     pub fn dfu(&self) -> BlockingPartition<NoopRawMutex, DFU> { | ||||
|         Self::create_partition(&self.dfu) | ||||
|     } | ||||
|  | ||||
|     pub fn state(&self) -> BlockingPartition<NoopRawMutex, STATE> { | ||||
|         Self::create_partition(&self.state) | ||||
|     } | ||||
|  | ||||
|     pub fn create_partition<T: NorFlash>( | ||||
|         mutex: &Mutex<NoopRawMutex, RefCell<T>>, | ||||
|     ) -> BlockingPartition<NoopRawMutex, T> { | ||||
|         BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE> | ||||
| where | ||||
|     ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||||
|     DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||||
|     STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||||
| { | ||||
|     pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> { | ||||
|         let config = BootLoaderConfig { | ||||
|             active: self.active.into_inner().into_inner(), | ||||
|             dfu: self.dfu.into_inner().into_inner(), | ||||
|             state: self.state.into_inner().into_inner(), | ||||
|         }; | ||||
|         super::AsyncTestFlash::new(config) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								embassy-boot/boot/src/test_flash/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								embassy-boot/boot/src/test_flash/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| #[cfg(feature = "nightly")] | ||||
| mod asynch; | ||||
| mod blocking; | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| pub(crate) use asynch::AsyncTestFlash; | ||||
| pub(crate) use blocking::BlockingTestFlash; | ||||
| @@ -17,12 +17,12 @@ target = "thumbv7em-none-eabi" | ||||
| defmt = { version = "0.3", optional = true } | ||||
|  | ||||
| embassy-sync = { path = "../../embassy-sync" } | ||||
| embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] } | ||||
| embassy-nrf = { path = "../../embassy-nrf" } | ||||
| embassy-boot = { path = "../boot", default-features = false } | ||||
| cortex-m = { version = "0.7.6" } | ||||
| cortex-m-rt = { version = "0.7" } | ||||
| embedded-storage = "0.3.0" | ||||
| embedded-storage-async = "0.4.0" | ||||
| embedded-storage-async = { version = "0.4.0", optional = true } | ||||
| cfg-if = "1.0.0" | ||||
|  | ||||
| nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true } | ||||
| @@ -36,3 +36,8 @@ defmt = [ | ||||
| softdevice = [ | ||||
|     "nrf-softdevice-mbr", | ||||
| ] | ||||
| nightly = [ | ||||
|     "dep:embedded-storage-async", | ||||
|     "embassy-boot/nightly", | ||||
|     "embassy-nrf/nightly" | ||||
| ] | ||||
|   | ||||
| @@ -6,14 +6,14 @@ An adaptation of `embassy-boot` for nRF. | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| * Load applications with our without the softdevice. | ||||
| * Load applications with or without the softdevice. | ||||
| * Configure bootloader partitions based on linker script. | ||||
| * Using watchdog timer to detect application failure. | ||||
|  | ||||
|  | ||||
| ## Minimum supported Rust version (MSRV) | ||||
|  | ||||
| `embassy-boot-nrf` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals. | ||||
| `embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||||
|  | ||||
| ## License | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
|  | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
|  | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
|  | ||||
| @@ -81,14 +83,17 @@ macro_rules! todo { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::unreachable!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::unreachable!($($x)*); | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> { | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(unused)] | ||||
| pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||
|  | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,76 +1,30 @@ | ||||
| #![no_std] | ||||
| #![feature(type_alias_impl_trait)] | ||||
| #![warn(missing_docs)] | ||||
| #![doc = include_str!("../README.md")] | ||||
| mod fmt; | ||||
|  | ||||
| pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig}; | ||||
| use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE}; | ||||
| pub use embassy_boot::{ | ||||
|     AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, | ||||
| }; | ||||
| #[cfg(feature = "nightly")] | ||||
| pub use embassy_boot::{FirmwareState, FirmwareUpdater}; | ||||
| use embassy_nrf::nvmc::PAGE_SIZE; | ||||
| use embassy_nrf::peripherals::WDT; | ||||
| use embassy_nrf::wdt; | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||
|  | ||||
| /// A bootloader for nRF devices. | ||||
| pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE> { | ||||
|     boot: embassy_boot::BootLoader, | ||||
|     aligned_buf: AlignedBuffer<BUFFER_SIZE>, | ||||
| } | ||||
|  | ||||
| impl Default for BootLoader<PAGE_SIZE> { | ||||
|     /// Create a new bootloader instance using parameters from linker script | ||||
|     fn default() -> Self { | ||||
|         extern "C" { | ||||
|             static __bootloader_state_start: u32; | ||||
|             static __bootloader_state_end: u32; | ||||
|             static __bootloader_active_start: u32; | ||||
|             static __bootloader_active_end: u32; | ||||
|             static __bootloader_dfu_start: u32; | ||||
|             static __bootloader_dfu_end: u32; | ||||
|         } | ||||
|  | ||||
|         let active = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_active_start as *const u32 as u32, | ||||
|                 &__bootloader_active_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|         let dfu = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_dfu_start as *const u32 as u32, | ||||
|                 &__bootloader_dfu_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|         let state = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_state_start as *const u32 as u32, | ||||
|                 &__bootloader_state_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|         trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); | ||||
|         trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||||
|         trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||||
|  | ||||
|         Self::new(active, dfu, state) | ||||
|     } | ||||
| } | ||||
| pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE>; | ||||
|  | ||||
| impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     /// Create a new bootloader instance using the supplied partitions for active, dfu and state. | ||||
|     pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | ||||
|         Self { | ||||
|             boot: embassy_boot::BootLoader::new(active, dfu, state), | ||||
|             aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping | ||||
|     /// firmware. | ||||
|     pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize { | ||||
|         match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) { | ||||
|             Ok(_) => self.boot.boot_address(), | ||||
|             Err(_) => panic!("boot prepare error!"), | ||||
|         } | ||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. | ||||
|     pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||||
|         config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||||
|     ) -> Self { | ||||
|         let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||||
|         let mut boot = embassy_boot::BootLoader::new(config); | ||||
|         boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); | ||||
|         Self | ||||
|     } | ||||
|  | ||||
|     /// Boots the application without softdevice mechanisms. | ||||
| @@ -79,10 +33,10 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     /// | ||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||
|     #[cfg(not(feature = "softdevice"))] | ||||
|     pub unsafe fn load(&mut self, start: usize) -> ! { | ||||
|     pub unsafe fn load(self, start: u32) -> ! { | ||||
|         let mut p = cortex_m::Peripherals::steal(); | ||||
|         p.SCB.invalidate_icache(); | ||||
|         p.SCB.vtor.write(start as u32); | ||||
|         p.SCB.vtor.write(start); | ||||
|         cortex_m::asm::bootload(start as *const u32) | ||||
|     } | ||||
|  | ||||
| @@ -92,7 +46,7 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     /// | ||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||
|     #[cfg(feature = "softdevice")] | ||||
|     pub unsafe fn load(&mut self, _app: usize) -> ! { | ||||
|     pub unsafe fn load(self, _app: u32) -> ! { | ||||
|         use nrf_softdevice_mbr as mbr; | ||||
|         const NRF_SUCCESS: u32 = 0; | ||||
|  | ||||
| @@ -139,15 +93,15 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A flash implementation that wraps NVMC and will pet a watchdog when touching flash. | ||||
| pub struct WatchdogFlash<'d> { | ||||
|     flash: Nvmc<'d>, | ||||
| /// A flash implementation that wraps any flash and will pet a watchdog when touching flash. | ||||
| pub struct WatchdogFlash<FLASH> { | ||||
|     flash: FLASH, | ||||
|     wdt: wdt::WatchdogHandle, | ||||
| } | ||||
|  | ||||
| impl<'d> WatchdogFlash<'d> { | ||||
| impl<FLASH> WatchdogFlash<FLASH> { | ||||
|     /// Start a new watchdog with a given flash and WDT peripheral and a timeout | ||||
|     pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self { | ||||
|     pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { | ||||
|         let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { | ||||
|             Ok(x) => x, | ||||
|             Err(_) => { | ||||
| @@ -162,13 +116,13 @@ impl<'d> WatchdogFlash<'d> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'d> ErrorType for WatchdogFlash<'d> { | ||||
|     type Error = <Nvmc<'d> as ErrorType>::Error; | ||||
| impl<FLASH: ErrorType> ErrorType for WatchdogFlash<FLASH> { | ||||
|     type Error = FLASH::Error; | ||||
| } | ||||
|  | ||||
| impl<'d> NorFlash for WatchdogFlash<'d> { | ||||
|     const WRITE_SIZE: usize = <Nvmc<'d> as NorFlash>::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = <Nvmc<'d> as NorFlash>::ERASE_SIZE; | ||||
| impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> { | ||||
|     const WRITE_SIZE: usize = FLASH::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = FLASH::ERASE_SIZE; | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         self.wdt.pet(); | ||||
| @@ -180,8 +134,8 @@ impl<'d> NorFlash for WatchdogFlash<'d> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'d> ReadNorFlash for WatchdogFlash<'d> { | ||||
|     const READ_SIZE: usize = <Nvmc<'d> as ReadNorFlash>::READ_SIZE; | ||||
| impl<FLASH: ReadNorFlash> ReadNorFlash for WatchdogFlash<FLASH> { | ||||
|     const READ_SIZE: usize = FLASH::READ_SIZE; | ||||
|     fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.wdt.pet(); | ||||
|         self.flash.read(offset, data) | ||||
|   | ||||
| @@ -18,14 +18,14 @@ defmt-rtt = { version = "0.4", optional = true } | ||||
| log = { version = "0.4", optional = true } | ||||
|  | ||||
| embassy-sync = { path = "../../embassy-sync" } | ||||
| embassy-rp = { path = "../../embassy-rp", default-features = false, features = ["nightly"] } | ||||
| embassy-rp = { path = "../../embassy-rp", default-features = false } | ||||
| embassy-boot = { path = "../boot", default-features = false } | ||||
| embassy-time = { path = "../../embassy-time", features = ["nightly"] } | ||||
| embassy-time = { path = "../../embassy-time" } | ||||
|  | ||||
| cortex-m = { version = "0.7.6" } | ||||
| cortex-m-rt = { version = "0.7" } | ||||
| embedded-storage = "0.3.0" | ||||
| embedded-storage-async = "0.4.0" | ||||
| embedded-storage-async = { version = "0.4.0", optional = true } | ||||
| cfg-if = "1.0.0" | ||||
|  | ||||
| [features] | ||||
| @@ -40,6 +40,12 @@ log = [ | ||||
|     "embassy-rp/log", | ||||
| ] | ||||
| debug = ["defmt-rtt"] | ||||
| nightly = [ | ||||
|     "dep:embedded-storage-async", | ||||
|     "embassy-boot/nightly", | ||||
|     "embassy-rp/nightly", | ||||
|     "embassy-time/nightly" | ||||
| ] | ||||
|  | ||||
| [profile.dev] | ||||
| debug = 2 | ||||
|   | ||||
| @@ -13,7 +13,7 @@ NOTE: The applications using this bootloader should not link with the `link-rp.x | ||||
|  | ||||
| ## Minimum supported Rust version (MSRV) | ||||
|  | ||||
| `embassy-boot-rp` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals. | ||||
| `embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||||
|  | ||||
| ## License | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
|  | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
|  | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
|  | ||||
| @@ -81,14 +83,17 @@ macro_rules! todo { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::unreachable!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::unreachable!($($x)*); | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> { | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(unused)] | ||||
| pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||
|  | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,38 +1,31 @@ | ||||
| #![no_std] | ||||
| #![feature(type_alias_impl_trait)] | ||||
| #![warn(missing_docs)] | ||||
| #![doc = include_str!("../README.md")] | ||||
| mod fmt; | ||||
|  | ||||
| pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; | ||||
| use embassy_rp::flash::{Flash, ERASE_SIZE}; | ||||
| pub use embassy_boot::{ | ||||
|     AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State, | ||||
| }; | ||||
| #[cfg(feature = "nightly")] | ||||
| pub use embassy_boot::{FirmwareState, FirmwareUpdater}; | ||||
| use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; | ||||
| use embassy_rp::peripherals::{FLASH, WATCHDOG}; | ||||
| use embassy_rp::watchdog::Watchdog; | ||||
| use embassy_time::Duration; | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||
|  | ||||
| /// A bootloader for RP2040 devices. | ||||
| pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> { | ||||
|     boot: embassy_boot::BootLoader, | ||||
|     aligned_buf: AlignedBuffer<BUFFER_SIZE>, | ||||
| } | ||||
| pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE>; | ||||
|  | ||||
| impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     /// Create a new bootloader instance using the supplied partitions for active, dfu and state. | ||||
|     pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | ||||
|         Self { | ||||
|             boot: embassy_boot::BootLoader::new(active, dfu, state), | ||||
|             aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping | ||||
|     /// firmware. | ||||
|     pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize { | ||||
|         match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) { | ||||
|             Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(), | ||||
|             Err(_) => panic!("boot prepare error!"), | ||||
|         } | ||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware | ||||
|     pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||||
|         config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||||
|     ) -> Self { | ||||
|         let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||||
|         let mut boot = embassy_boot::BootLoader::new(config); | ||||
|         boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); | ||||
|         Self | ||||
|     } | ||||
|  | ||||
|     /// Boots the application. | ||||
| @@ -40,67 +33,28 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||
|     pub unsafe fn load(&mut self, start: usize) -> ! { | ||||
|     pub unsafe fn load(self, start: u32) -> ! { | ||||
|         trace!("Loading app at 0x{:x}", start); | ||||
|         #[allow(unused_mut)] | ||||
|         let mut p = cortex_m::Peripherals::steal(); | ||||
|         #[cfg(not(armv6m))] | ||||
|         p.SCB.invalidate_icache(); | ||||
|         p.SCB.vtor.write(start as u32); | ||||
|         p.SCB.vtor.write(start); | ||||
|  | ||||
|         cortex_m::asm::bootload(start as *const u32) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for BootLoader<ERASE_SIZE> { | ||||
|     /// Create a new bootloader instance using parameters from linker script | ||||
|     fn default() -> Self { | ||||
|         extern "C" { | ||||
|             static __bootloader_state_start: u32; | ||||
|             static __bootloader_state_end: u32; | ||||
|             static __bootloader_active_start: u32; | ||||
|             static __bootloader_active_end: u32; | ||||
|             static __bootloader_dfu_start: u32; | ||||
|             static __bootloader_dfu_end: u32; | ||||
|         } | ||||
|  | ||||
|         let active = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_active_start as *const u32 as u32, | ||||
|                 &__bootloader_active_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|         let dfu = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_dfu_start as *const u32 as u32, | ||||
|                 &__bootloader_dfu_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|         let state = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_state_start as *const u32 as u32, | ||||
|                 &__bootloader_state_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|         trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); | ||||
|         trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||||
|         trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||||
|  | ||||
|         Self::new(active, dfu, state) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A flash implementation that will feed a watchdog when touching flash. | ||||
| pub struct WatchdogFlash<'d, const SIZE: usize> { | ||||
|     flash: Flash<'d, FLASH, SIZE>, | ||||
|     flash: Flash<'d, FLASH, Blocking, SIZE>, | ||||
|     watchdog: Watchdog, | ||||
| } | ||||
|  | ||||
| impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { | ||||
|     /// Start a new watchdog with a given flash and watchdog peripheral and a timeout | ||||
|     pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { | ||||
|         let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash); | ||||
|         let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); | ||||
|         let mut watchdog = Watchdog::new(watchdog); | ||||
|         watchdog.start(timeout); | ||||
|         Self { flash, watchdog } | ||||
| @@ -108,28 +62,28 @@ impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { | ||||
| } | ||||
|  | ||||
| impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { | ||||
|     type Error = <Flash<'d, FLASH, SIZE> as ErrorType>::Error; | ||||
|     type Error = <Flash<'d, FLASH, Blocking, SIZE> as ErrorType>::Error; | ||||
| } | ||||
|  | ||||
| impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { | ||||
|     const WRITE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::ERASE_SIZE; | ||||
|     const WRITE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::ERASE_SIZE; | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         self.watchdog.feed(); | ||||
|         self.flash.erase(from, to) | ||||
|         self.flash.blocking_erase(from, to) | ||||
|     } | ||||
|     fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.watchdog.feed(); | ||||
|         self.flash.write(offset, data) | ||||
|         self.flash.blocking_write(offset, data) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { | ||||
|     const READ_SIZE: usize = <Flash<'d, FLASH, SIZE> as ReadNorFlash>::READ_SIZE; | ||||
|     const READ_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as ReadNorFlash>::READ_SIZE; | ||||
|     fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.watchdog.feed(); | ||||
|         self.flash.read(offset, data) | ||||
|         self.flash.blocking_read(offset, data) | ||||
|     } | ||||
|     fn capacity(&self) -> usize { | ||||
|         self.flash.capacity() | ||||
|   | ||||
| @@ -19,12 +19,12 @@ defmt-rtt = { version = "0.4", optional = true } | ||||
| log = { version = "0.4", optional = true } | ||||
|  | ||||
| embassy-sync = { path = "../../embassy-sync" } | ||||
| embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] } | ||||
| embassy-stm32 = { path = "../../embassy-stm32", default-features = false } | ||||
| embassy-boot = { path = "../boot", default-features = false } | ||||
| cortex-m = { version = "0.7.6" } | ||||
| cortex-m-rt = { version = "0.7" } | ||||
| embedded-storage = "0.3.0" | ||||
| embedded-storage-async = "0.4.0" | ||||
| embedded-storage-async = { version = "0.4.0", optional = true } | ||||
| cfg-if = "1.0.0" | ||||
|  | ||||
| [features] | ||||
| @@ -39,6 +39,11 @@ log = [ | ||||
|     "embassy-stm32/log", | ||||
| ] | ||||
| debug = ["defmt-rtt"] | ||||
| nightly = [ | ||||
|     "dep:embedded-storage-async", | ||||
|     "embassy-boot/nightly", | ||||
|     "embassy-stm32/nightly" | ||||
| ] | ||||
|  | ||||
| [profile.dev] | ||||
| debug = 2 | ||||
|   | ||||
| @@ -11,7 +11,7 @@ An adaptation of `embassy-boot` for STM32. | ||||
|  | ||||
| ## Minimum supported Rust version (MSRV) | ||||
|  | ||||
| `embassy-boot-stm32` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals. | ||||
| `embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||||
|  | ||||
| ## License | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
|  | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
|  | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
|  | ||||
| @@ -81,14 +83,17 @@ macro_rules! todo { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::unreachable!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::unreachable!($($x)*); | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> { | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(unused)] | ||||
| pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||
|  | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,33 +1,27 @@ | ||||
| #![no_std] | ||||
| #![feature(type_alias_impl_trait)] | ||||
| #![warn(missing_docs)] | ||||
| #![doc = include_str!("../README.md")] | ||||
| mod fmt; | ||||
|  | ||||
| pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; | ||||
| pub use embassy_boot::{ | ||||
|     AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State, | ||||
| }; | ||||
| #[cfg(feature = "nightly")] | ||||
| pub use embassy_boot::{FirmwareState, FirmwareUpdater}; | ||||
| use embedded_storage::nor_flash::NorFlash; | ||||
|  | ||||
| /// A bootloader for STM32 devices. | ||||
| pub struct BootLoader<const BUFFER_SIZE: usize> { | ||||
|     boot: embassy_boot::BootLoader, | ||||
|     aligned_buf: AlignedBuffer<BUFFER_SIZE>, | ||||
| } | ||||
| pub struct BootLoader; | ||||
|  | ||||
| impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     /// Create a new bootloader instance using the supplied partitions for active, dfu and state. | ||||
|     pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | ||||
|         Self { | ||||
|             boot: embassy_boot::BootLoader::new(active, dfu, state), | ||||
|             aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping | ||||
|     /// firmware. | ||||
|     pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize { | ||||
|         match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) { | ||||
|             Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(), | ||||
|             Err(_) => panic!("boot prepare error!"), | ||||
|         } | ||||
| impl BootLoader { | ||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware | ||||
|     pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>( | ||||
|         config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||||
|     ) -> Self { | ||||
|         let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||||
|         let mut boot = embassy_boot::BootLoader::new(config); | ||||
|         boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); | ||||
|         Self | ||||
|     } | ||||
|  | ||||
|     /// Boots the application. | ||||
| @@ -35,53 +29,14 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||
|     pub unsafe fn load(&mut self, start: usize) -> ! { | ||||
|     pub unsafe fn load(self, start: u32) -> ! { | ||||
|         trace!("Loading app at 0x{:x}", start); | ||||
|         #[allow(unused_mut)] | ||||
|         let mut p = cortex_m::Peripherals::steal(); | ||||
|         #[cfg(not(armv6m))] | ||||
|         p.SCB.invalidate_icache(); | ||||
|         p.SCB.vtor.write(start as u32); | ||||
|         p.SCB.vtor.write(start); | ||||
|  | ||||
|         cortex_m::asm::bootload(start as *const u32) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<const BUFFER_SIZE: usize> Default for BootLoader<BUFFER_SIZE> { | ||||
|     /// Create a new bootloader instance using parameters from linker script | ||||
|     fn default() -> Self { | ||||
|         extern "C" { | ||||
|             static __bootloader_state_start: u32; | ||||
|             static __bootloader_state_end: u32; | ||||
|             static __bootloader_active_start: u32; | ||||
|             static __bootloader_active_end: u32; | ||||
|             static __bootloader_dfu_start: u32; | ||||
|             static __bootloader_dfu_end: u32; | ||||
|         } | ||||
|  | ||||
|         let active = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_active_start as *const u32 as u32, | ||||
|                 &__bootloader_active_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|         let dfu = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_dfu_start as *const u32 as u32, | ||||
|                 &__bootloader_dfu_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|         let state = unsafe { | ||||
|             Partition::new( | ||||
|                 &__bootloader_state_start as *const u32 as u32, | ||||
|                 &__bootloader_state_end as *const u32 as u32, | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|         trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); | ||||
|         trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||||
|         trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||||
|  | ||||
|         Self::new(active, dfu, state) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,47 +0,0 @@ | ||||
| [package] | ||||
| name = "embassy-cortex-m" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| license = "MIT OR Apache-2.0" | ||||
|  | ||||
| [package.metadata.embassy_docs] | ||||
| src_base = "https://github.com/embassy-rs/embassy/blob/embassy-cortex-m-v$VERSION/embassy-cortex-m/src/" | ||||
| src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-cortex-m/src/" | ||||
| features = ["prio-bits-3"] | ||||
| flavors = [ | ||||
|     { name = "thumbv6m-none-eabi",        target = "thumbv6m-none-eabi",         features = [] }, | ||||
|     { name = "thumbv7m-none-eabi",        target = "thumbv7m-none-eabi",         features = [] }, | ||||
|     { name = "thumbv7em-none-eabi",       target = "thumbv7em-none-eabi",        features = [] }, | ||||
|     { name = "thumbv7em-none-eabihf",     target = "thumbv7em-none-eabihf",      features = [] }, | ||||
|     { name = "thumbv8m.base-none-eabi",   target = "thumbv8m.base-none-eabi",    features = [] }, | ||||
|     { name = "thumbv8m.main-none-eabi",   target = "thumbv8m.main-none-eabi",    features = [] }, | ||||
|     { name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf",  features = [] }, | ||||
| ] | ||||
|  | ||||
| [features] | ||||
| default = [] | ||||
|  | ||||
| # Define the number of NVIC priority bits. | ||||
| prio-bits-0 = [] | ||||
| prio-bits-1 = [] | ||||
| prio-bits-2 = [] | ||||
| prio-bits-3 = [] | ||||
| prio-bits-4 = [] | ||||
| prio-bits-5 = [] | ||||
| prio-bits-6 = [] | ||||
| prio-bits-7 = [] | ||||
| prio-bits-8 = [] | ||||
|  | ||||
| [dependencies] | ||||
| defmt = { version = "0.3", optional = true } | ||||
| log = { version = "0.4.14", optional = true } | ||||
|  | ||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync" } | ||||
| embassy-executor = { version = "0.1.0", path = "../embassy-executor"} | ||||
| embassy-macros = { version = "0.1.0", path = "../embassy-macros"} | ||||
| embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common"} | ||||
| atomic-polyfill = "1.0.1" | ||||
| critical-section = "1.1" | ||||
| cfg-if = "1.0.0" | ||||
| cortex-m = "0.7.6" | ||||
|  | ||||
| @@ -1,10 +0,0 @@ | ||||
| //! Embassy executor and interrupt handling specific to cortex-m devices. | ||||
| #![no_std] | ||||
| #![warn(missing_docs)] | ||||
|  | ||||
| // This mod MUST go first, so that the others see its macros. | ||||
| pub(crate) mod fmt; | ||||
|  | ||||
| pub use embassy_executor as executor; | ||||
| pub mod interrupt; | ||||
| pub mod peripheral; | ||||
| @@ -1,144 +0,0 @@ | ||||
| //! Peripheral interrupt handling specific to cortex-m devices. | ||||
| use core::mem::MaybeUninit; | ||||
|  | ||||
| use cortex_m::peripheral::scb::VectActive; | ||||
| use cortex_m::peripheral::{NVIC, SCB}; | ||||
| use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; | ||||
|  | ||||
| use crate::interrupt::{Interrupt, InterruptExt, Priority}; | ||||
|  | ||||
| /// A type which can be used as state with `PeripheralMutex`. | ||||
| /// | ||||
| /// It needs to be `Send` because `&mut` references are sent back and forth between the 'thread' which owns the `PeripheralMutex` and the interrupt, | ||||
| /// and `&mut T` is only `Send` where `T: Send`. | ||||
| pub trait PeripheralState: Send { | ||||
|     /// The interrupt that is used for this peripheral. | ||||
|     type Interrupt: Interrupt; | ||||
|  | ||||
|     /// The interrupt handler that should be invoked for the peripheral. Implementations need to clear the appropriate interrupt flags to ensure the handle will not be called again. | ||||
|     fn on_interrupt(&mut self); | ||||
| } | ||||
|  | ||||
| /// A type for storing the state of a peripheral that can be stored in a static. | ||||
| pub struct StateStorage<S>(MaybeUninit<S>); | ||||
|  | ||||
| impl<S> StateStorage<S> { | ||||
|     /// Create a new instance for storing peripheral state. | ||||
|     pub const fn new() -> Self { | ||||
|         Self(MaybeUninit::uninit()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A type for a peripheral that keeps the state of a peripheral that can be accessed from thread mode and an interrupt handler in | ||||
| /// a safe way. | ||||
| pub struct PeripheralMutex<'a, S: PeripheralState> { | ||||
|     state: *mut S, | ||||
|     irq: PeripheralRef<'a, S::Interrupt>, | ||||
| } | ||||
|  | ||||
| /// Whether `irq` can be preempted by the current interrupt. | ||||
| pub(crate) fn can_be_preempted(irq: &impl Interrupt) -> bool { | ||||
|     match SCB::vect_active() { | ||||
|         // Thread mode can't preempt anything. | ||||
|         VectActive::ThreadMode => false, | ||||
|         // Exceptions don't always preempt interrupts, | ||||
|         // but there isn't much of a good reason to be keeping a `PeripheralMutex` in an exception anyway. | ||||
|         VectActive::Exception(_) => true, | ||||
|         VectActive::Interrupt { irqn } => { | ||||
|             #[derive(Clone, Copy)] | ||||
|             struct NrWrap(u16); | ||||
|             unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap { | ||||
|                 fn number(self) -> u16 { | ||||
|                     self.0 | ||||
|                 } | ||||
|             } | ||||
|             NVIC::get_priority(NrWrap(irqn.into())) < irq.get_priority().into() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a, S: PeripheralState> PeripheralMutex<'a, S> { | ||||
|     /// Create a new `PeripheralMutex` wrapping `irq`, with `init` initializing the initial state. | ||||
|     /// | ||||
|     /// Registers `on_interrupt` as the `irq`'s handler, and enables it. | ||||
|     pub fn new( | ||||
|         irq: impl Peripheral<P = S::Interrupt> + 'a, | ||||
|         storage: &'a mut StateStorage<S>, | ||||
|         init: impl FnOnce() -> S, | ||||
|     ) -> Self { | ||||
|         into_ref!(irq); | ||||
|  | ||||
|         if can_be_preempted(&*irq) { | ||||
|             panic!( | ||||
|                 "`PeripheralMutex` cannot be created in an interrupt with higher priority than the interrupt it wraps" | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         let state_ptr = storage.0.as_mut_ptr(); | ||||
|  | ||||
|         // Safety: The pointer is valid and not used by anyone else | ||||
|         // because we have the `&mut StateStorage`. | ||||
|         unsafe { state_ptr.write(init()) }; | ||||
|  | ||||
|         irq.disable(); | ||||
|         irq.set_handler(|p| unsafe { | ||||
|             // Safety: it's OK to get a &mut to the state, since | ||||
|             // - We checked that the thread owning the `PeripheralMutex` can't preempt us in `new`. | ||||
|             //   Interrupts' priorities can only be changed with raw embassy `Interrupts`, | ||||
|             //   which can't safely store a `PeripheralMutex` across invocations. | ||||
|             // - We can't have preempted a with() call because the irq is disabled during it. | ||||
|             let state = &mut *(p as *mut S); | ||||
|             state.on_interrupt(); | ||||
|         }); | ||||
|         irq.set_handler_context(state_ptr as *mut ()); | ||||
|         irq.enable(); | ||||
|  | ||||
|         Self { irq, state: state_ptr } | ||||
|     } | ||||
|  | ||||
|     /// Access the peripheral state ensuring interrupts are disabled so that the state can be | ||||
|     /// safely accessed. | ||||
|     pub fn with<R>(&mut self, f: impl FnOnce(&mut S) -> R) -> R { | ||||
|         self.irq.disable(); | ||||
|  | ||||
|         // Safety: it's OK to get a &mut to the state, since the irq is disabled. | ||||
|         let state = unsafe { &mut *self.state }; | ||||
|         let r = f(state); | ||||
|  | ||||
|         self.irq.enable(); | ||||
|  | ||||
|         r | ||||
|     } | ||||
|  | ||||
|     /// Returns whether the wrapped interrupt is currently in a pending state. | ||||
|     pub fn is_pending(&self) -> bool { | ||||
|         self.irq.is_pending() | ||||
|     } | ||||
|  | ||||
|     /// Forces the wrapped interrupt into a pending state. | ||||
|     pub fn pend(&self) { | ||||
|         self.irq.pend() | ||||
|     } | ||||
|  | ||||
|     /// Forces the wrapped interrupt out of a pending state. | ||||
|     pub fn unpend(&self) { | ||||
|         self.irq.unpend() | ||||
|     } | ||||
|  | ||||
|     /// Gets the priority of the wrapped interrupt. | ||||
|     pub fn priority(&self) -> Priority { | ||||
|         self.irq.get_priority() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a, S: PeripheralState> Drop for PeripheralMutex<'a, S> { | ||||
|     fn drop(&mut self) { | ||||
|         self.irq.disable(); | ||||
|         self.irq.remove_handler(); | ||||
|  | ||||
|         // safety: | ||||
|         // - we initialized the state in `new`, so we know it's initialized. | ||||
|         // - the irq is disabled, so it won't preempt us while dropping. | ||||
|         unsafe { self.state.drop_in_place() } | ||||
|     } | ||||
| } | ||||
| @@ -14,15 +14,25 @@ target = "x86_64-unknown-linux-gnu" | ||||
| [features] | ||||
| std = [] | ||||
| # Enable nightly-only features | ||||
| nightly = ["embedded-hal-async", "embedded-storage-async"] | ||||
| nightly = ["embassy-futures", "embedded-hal-async", "embedded-storage-async"] | ||||
| time = ["dep:embassy-time"] | ||||
| default = ["time"] | ||||
|  | ||||
| [dependencies] | ||||
| embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true } | ||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync" } | ||||
| embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } | ||||
| embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } | ||||
| embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true } | ||||
| embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true } | ||||
| embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ | ||||
|     "unproven", | ||||
| ] } | ||||
| embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } | ||||
| embedded-hal-async = { version = "=1.0.0-rc.1", optional = true } | ||||
| embedded-storage = "0.3.0" | ||||
| embedded-storage-async = { version = "0.4.0", optional = true } | ||||
| nb = "1.0.0" | ||||
|  | ||||
| defmt = { version = "0.3", optional = true } | ||||
|  | ||||
| [dev-dependencies] | ||||
| critical-section = { version = "1.1.1", features = ["std"] } | ||||
| futures-test = "0.3.17" | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| //! Adapters between embedded-hal traits.
 | ||||
| 
 | ||||
| use embedded_hal_02::{blocking, serial}; | ||||
| use embedded_hal_02::blocking; | ||||
| 
 | ||||
| /// Wrapper that implements async traits using blocking implementations.
 | ||||
| ///
 | ||||
| @@ -76,7 +74,21 @@ where | ||||
|     E: embedded_hal_1::spi::Error + 'static, | ||||
|     T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>, | ||||
| { | ||||
|     async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> { | ||||
|     async fn flush(&mut self) -> Result<(), Self::Error> { | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.write(data)?; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.transfer(data)?; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { | ||||
|         // Ensure we write the expected bytes
 | ||||
|         for i in 0..core::cmp::min(read.len(), write.len()) { | ||||
|             read[i] = write[i].clone(); | ||||
| @@ -85,94 +97,12 @@ where | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> { | ||||
|         todo!() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T, E> embedded_hal_async::spi::SpiBusFlush for BlockingAsync<T> | ||||
| where | ||||
|     E: embedded_hal_1::spi::Error + 'static, | ||||
|     T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>, | ||||
| { | ||||
|     async fn flush(&mut self) -> Result<(), Self::Error> { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T, E> embedded_hal_async::spi::SpiBusWrite<u8> for BlockingAsync<T> | ||||
| where | ||||
|     E: embedded_hal_1::spi::Error + 'static, | ||||
|     T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>, | ||||
| { | ||||
|     async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.write(data)?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T, E> embedded_hal_async::spi::SpiBusRead<u8> for BlockingAsync<T> | ||||
| where | ||||
|     E: embedded_hal_1::spi::Error + 'static, | ||||
|     T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>, | ||||
| { | ||||
|     async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { | ||||
|     async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.transfer(data)?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Uart implementatinos
 | ||||
| impl<T, E> embedded_hal_1::serial::ErrorType for BlockingAsync<T> | ||||
| where | ||||
|     T: serial::Read<u8, Error = E>, | ||||
|     E: embedded_hal_1::serial::Error + 'static, | ||||
| { | ||||
|     type Error = E; | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "_todo_embedded_hal_serial")] | ||||
| impl<T, E> embedded_hal_async::serial::Read for BlockingAsync<T> | ||||
| where | ||||
|     T: serial::Read<u8, Error = E>, | ||||
|     E: embedded_hal_1::serial::Error + 'static, | ||||
| { | ||||
|     type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a; | ||||
|     fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { | ||||
|         async move { | ||||
|             let mut pos = 0; | ||||
|             while pos < buf.len() { | ||||
|                 match self.wrapped.read() { | ||||
|                     Err(nb::Error::WouldBlock) => {} | ||||
|                     Err(nb::Error::Other(e)) => return Err(e), | ||||
|                     Ok(b) => { | ||||
|                         buf[pos] = b; | ||||
|                         pos += 1; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Ok(()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "_todo_embedded_hal_serial")] | ||||
| impl<T, E> embedded_hal_async::serial::Write for BlockingAsync<T> | ||||
| where | ||||
|     T: blocking::serial::Write<u8, Error = E> + serial::Read<u8, Error = E>, | ||||
|     E: embedded_hal_1::serial::Error + 'static, | ||||
| { | ||||
|     type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a; | ||||
|     fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { | ||||
|         async move { self.wrapped.bwrite_all(buf) } | ||||
|     } | ||||
| 
 | ||||
|     type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a; | ||||
|     fn flush(&mut self) -> Result<(), Self::Error> { | ||||
|         async move { self.wrapped.bflush() } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// NOR flash wrapper
 | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||
| use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||||
							
								
								
									
										7
									
								
								embassy-embedded-hal/src/adapter/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								embassy-embedded-hal/src/adapter/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| //! Adapters between embedded-hal traits. | ||||
|  | ||||
| mod blocking_async; | ||||
| mod yielding_async; | ||||
|  | ||||
| pub use blocking_async::BlockingAsync; | ||||
| pub use yielding_async::YieldingAsync; | ||||
							
								
								
									
										169
									
								
								embassy-embedded-hal/src/adapter/yielding_async.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								embassy-embedded-hal/src/adapter/yielding_async.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| use embassy_futures::yield_now; | ||||
|  | ||||
| /// Wrapper that yields for each operation to the wrapped instance | ||||
| /// | ||||
| /// This can be used in combination with BlockingAsync<T> to enforce yields | ||||
| /// between long running blocking operations. | ||||
| pub struct YieldingAsync<T> { | ||||
|     wrapped: T, | ||||
| } | ||||
|  | ||||
| impl<T> YieldingAsync<T> { | ||||
|     /// Create a new instance of a wrapper that yields after each operation. | ||||
|     pub fn new(wrapped: T) -> Self { | ||||
|         Self { wrapped } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // | ||||
| // I2C implementations | ||||
| // | ||||
| impl<T> embedded_hal_1::i2c::ErrorType for YieldingAsync<T> | ||||
| where | ||||
|     T: embedded_hal_1::i2c::ErrorType, | ||||
| { | ||||
|     type Error = T::Error; | ||||
| } | ||||
|  | ||||
| impl<T> embedded_hal_async::i2c::I2c for YieldingAsync<T> | ||||
| where | ||||
|     T: embedded_hal_async::i2c::I2c, | ||||
| { | ||||
|     async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.read(address, read).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.write(address, write).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.write_read(address, write, read).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn transaction( | ||||
|         &mut self, | ||||
|         address: u8, | ||||
|         operations: &mut [embedded_hal_1::i2c::Operation<'_>], | ||||
|     ) -> Result<(), Self::Error> { | ||||
|         self.wrapped.transaction(address, operations).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // | ||||
| // SPI implementations | ||||
| // | ||||
|  | ||||
| impl<T> embedded_hal_async::spi::ErrorType for YieldingAsync<T> | ||||
| where | ||||
|     T: embedded_hal_async::spi::ErrorType, | ||||
| { | ||||
|     type Error = T::Error; | ||||
| } | ||||
|  | ||||
| impl<T, Word: 'static + Copy> embedded_hal_async::spi::SpiBus<Word> for YieldingAsync<T> | ||||
| where | ||||
|     T: embedded_hal_async::spi::SpiBus<Word>, | ||||
| { | ||||
|     async fn flush(&mut self) -> Result<(), Self::Error> { | ||||
|         self.wrapped.flush().await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.write(data).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.read(data).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.transfer(read, write).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.transfer_in_place(words).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// | ||||
| /// NOR flash implementations | ||||
| /// | ||||
| impl<T: embedded_storage::nor_flash::ErrorType> embedded_storage::nor_flash::ErrorType for YieldingAsync<T> { | ||||
|     type Error = T::Error; | ||||
| } | ||||
|  | ||||
| impl<T: embedded_storage_async::nor_flash::ReadNorFlash> embedded_storage_async::nor_flash::ReadNorFlash | ||||
|     for YieldingAsync<T> | ||||
| { | ||||
|     const READ_SIZE: usize = T::READ_SIZE; | ||||
|  | ||||
|     async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.read(offset, bytes).await?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         self.wrapped.capacity() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: embedded_storage_async::nor_flash::NorFlash> embedded_storage_async::nor_flash::NorFlash for YieldingAsync<T> { | ||||
|     const WRITE_SIZE: usize = T::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = T::ERASE_SIZE; | ||||
|  | ||||
|     async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.wrapped.write(offset, bytes).await?; | ||||
|         yield_now().await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         // Yield between each actual erase | ||||
|         for from in (from..to).step_by(T::ERASE_SIZE) { | ||||
|             let to = core::cmp::min(from + T::ERASE_SIZE as u32, to); | ||||
|             self.wrapped.erase(from, to).await?; | ||||
|             yield_now().await; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use embedded_storage_async::nor_flash::NorFlash; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::flash::mem_flash::MemFlash; | ||||
|  | ||||
|     #[futures_test::test] | ||||
|     async fn can_erase() { | ||||
|         let flash = MemFlash::<1024, 128, 4>::new(0x00); | ||||
|         let mut yielding = YieldingAsync::new(flash); | ||||
|  | ||||
|         yielding.erase(0, 256).await.unwrap(); | ||||
|  | ||||
|         let flash = yielding.wrapped; | ||||
|         assert_eq!(2, flash.erases.len()); | ||||
|         assert_eq!((0, 128), flash.erases[0]); | ||||
|         assert_eq!((128, 256), flash.erases[1]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										228
									
								
								embassy-embedded-hal/src/flash/concat_flash.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								embassy-embedded-hal/src/flash/concat_flash.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash}; | ||||
| #[cfg(feature = "nightly")] | ||||
| use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||||
|  | ||||
| /// Convenience helper for concatenating two consecutive flashes into one. | ||||
| /// This is especially useful if used with "flash regions", where one may | ||||
| /// want to concatenate multiple regions into one larger region. | ||||
| pub struct ConcatFlash<First, Second>(First, Second); | ||||
|  | ||||
| impl<First, Second> ConcatFlash<First, Second> { | ||||
|     /// Create a new flash that concatenates two consecutive flashes. | ||||
|     pub fn new(first: First, second: Second) -> Self { | ||||
|         Self(first, second) | ||||
|     } | ||||
| } | ||||
|  | ||||
| const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize { | ||||
|     if first_read_size != second_read_size { | ||||
|         panic!("The read size for the concatenated flashes must be the same"); | ||||
|     } | ||||
|     first_read_size | ||||
| } | ||||
|  | ||||
| const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize { | ||||
|     if first_write_size != second_write_size { | ||||
|         panic!("The write size for the concatenated flashes must be the same"); | ||||
|     } | ||||
|     first_write_size | ||||
| } | ||||
|  | ||||
| const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize { | ||||
|     let max_erase_size = if first_erase_size > second_erase_size { | ||||
|         first_erase_size | ||||
|     } else { | ||||
|         second_erase_size | ||||
|     }; | ||||
|     if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 { | ||||
|         panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size"); | ||||
|     } | ||||
|     max_erase_size | ||||
| } | ||||
|  | ||||
| impl<First, Second, E> ErrorType for ConcatFlash<First, Second> | ||||
| where | ||||
|     First: ErrorType<Error = E>, | ||||
|     Second: ErrorType<Error = E>, | ||||
|     E: NorFlashError, | ||||
| { | ||||
|     type Error = E; | ||||
| } | ||||
|  | ||||
| impl<First, Second, E> ReadNorFlash for ConcatFlash<First, Second> | ||||
| where | ||||
|     First: ReadNorFlash<Error = E>, | ||||
|     Second: ReadNorFlash<Error = E>, | ||||
|     E: NorFlashError, | ||||
| { | ||||
|     const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); | ||||
|  | ||||
|     fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { | ||||
|         if offset < self.0.capacity() as u32 { | ||||
|             let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); | ||||
|             self.0.read(offset, &mut bytes[..len])?; | ||||
|             offset += len as u32; | ||||
|             bytes = &mut bytes[len..]; | ||||
|         } | ||||
|  | ||||
|         if !bytes.is_empty() { | ||||
|             self.1.read(offset - self.0.capacity() as u32, bytes)?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         self.0.capacity() + self.1.capacity() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<First, Second, E> NorFlash for ConcatFlash<First, Second> | ||||
| where | ||||
|     First: NorFlash<Error = E>, | ||||
|     Second: NorFlash<Error = E>, | ||||
|     E: NorFlashError, | ||||
| { | ||||
|     const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); | ||||
|     const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); | ||||
|  | ||||
|     fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { | ||||
|         if offset < self.0.capacity() as u32 { | ||||
|             let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); | ||||
|             self.0.write(offset, &bytes[..len])?; | ||||
|             offset += len as u32; | ||||
|             bytes = &bytes[len..]; | ||||
|         } | ||||
|  | ||||
|         if !bytes.is_empty() { | ||||
|             self.1.write(offset - self.0.capacity() as u32, bytes)?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { | ||||
|         if from < self.0.capacity() as u32 { | ||||
|             let to = core::cmp::min(self.0.capacity() as u32, to); | ||||
|             self.0.erase(from, to)?; | ||||
|             from = self.0.capacity() as u32; | ||||
|         } | ||||
|  | ||||
|         if from < to { | ||||
|             self.1 | ||||
|                 .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| impl<First, Second, E> AsyncReadNorFlash for ConcatFlash<First, Second> | ||||
| where | ||||
|     First: AsyncReadNorFlash<Error = E>, | ||||
|     Second: AsyncReadNorFlash<Error = E>, | ||||
|     E: NorFlashError, | ||||
| { | ||||
|     const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); | ||||
|  | ||||
|     async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { | ||||
|         if offset < self.0.capacity() as u32 { | ||||
|             let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); | ||||
|             self.0.read(offset, &mut bytes[..len]).await?; | ||||
|             offset += len as u32; | ||||
|             bytes = &mut bytes[len..]; | ||||
|         } | ||||
|  | ||||
|         if !bytes.is_empty() { | ||||
|             self.1.read(offset - self.0.capacity() as u32, bytes).await?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         self.0.capacity() + self.1.capacity() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| impl<First, Second, E> AsyncNorFlash for ConcatFlash<First, Second> | ||||
| where | ||||
|     First: AsyncNorFlash<Error = E>, | ||||
|     Second: AsyncNorFlash<Error = E>, | ||||
|     E: NorFlashError, | ||||
| { | ||||
|     const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); | ||||
|     const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); | ||||
|  | ||||
|     async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { | ||||
|         if offset < self.0.capacity() as u32 { | ||||
|             let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); | ||||
|             self.0.write(offset, &bytes[..len]).await?; | ||||
|             offset += len as u32; | ||||
|             bytes = &bytes[len..]; | ||||
|         } | ||||
|  | ||||
|         if !bytes.is_empty() { | ||||
|             self.1.write(offset - self.0.capacity() as u32, bytes).await?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { | ||||
|         if from < self.0.capacity() as u32 { | ||||
|             let to = core::cmp::min(self.0.capacity() as u32, to); | ||||
|             self.0.erase(from, to).await?; | ||||
|             from = self.0.capacity() as u32; | ||||
|         } | ||||
|  | ||||
|         if from < to { | ||||
|             self.1 | ||||
|                 .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32) | ||||
|                 .await?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||||
|  | ||||
|     use super::ConcatFlash; | ||||
|     use crate::flash::mem_flash::MemFlash; | ||||
|  | ||||
|     #[test] | ||||
|     fn can_write_and_read_across_flashes() { | ||||
|         let first = MemFlash::<64, 16, 4>::default(); | ||||
|         let second = MemFlash::<64, 64, 4>::default(); | ||||
|         let mut f = ConcatFlash::new(first, second); | ||||
|  | ||||
|         f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap(); | ||||
|  | ||||
|         assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]); | ||||
|         assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]); | ||||
|  | ||||
|         let mut read_buf = [0; 8]; | ||||
|         f.read(60, &mut read_buf).unwrap(); | ||||
|  | ||||
|         assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn can_erase_across_flashes() { | ||||
|         let first = MemFlash::<128, 16, 4>::new(0x00); | ||||
|         let second = MemFlash::<128, 64, 4>::new(0x00); | ||||
|         let mut f = ConcatFlash::new(first, second); | ||||
|  | ||||
|         f.erase(64, 192).unwrap(); | ||||
|  | ||||
|         assert_eq!(&[0x00; 64], &f.0.mem[0..64]); | ||||
|         assert_eq!(&[0xff; 64], &f.0.mem[64..128]); | ||||
|         assert_eq!(&[0xff; 64], &f.1.mem[0..64]); | ||||
|         assert_eq!(&[0x00; 64], &f.1.mem[64..128]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										128
									
								
								embassy-embedded-hal/src/flash/mem_flash.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								embassy-embedded-hal/src/flash/mem_flash.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| use alloc::vec::Vec; | ||||
|  | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||
| #[cfg(feature = "nightly")] | ||||
| use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||||
|  | ||||
| extern crate alloc; | ||||
|  | ||||
| pub(crate) struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> { | ||||
|     pub mem: [u8; SIZE], | ||||
|     pub writes: Vec<(u32, usize)>, | ||||
|     pub erases: Vec<(u32, u32)>, | ||||
| } | ||||
|  | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> { | ||||
|     #[allow(unused)] | ||||
|     pub const fn new(fill: u8) -> Self { | ||||
|         Self { | ||||
|             mem: [fill; SIZE], | ||||
|             writes: Vec::new(), | ||||
|             erases: Vec::new(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn read(&mut self, offset: u32, bytes: &mut [u8]) { | ||||
|         let len = bytes.len(); | ||||
|         bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); | ||||
|     } | ||||
|  | ||||
|     fn write(&mut self, offset: u32, bytes: &[u8]) { | ||||
|         self.writes.push((offset, bytes.len())); | ||||
|         let offset = offset as usize; | ||||
|         assert_eq!(0, bytes.len() % WRITE_SIZE); | ||||
|         assert_eq!(0, offset % WRITE_SIZE); | ||||
|         assert!(offset + bytes.len() <= SIZE); | ||||
|  | ||||
|         self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); | ||||
|     } | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) { | ||||
|         self.erases.push((from, to)); | ||||
|         let from = from as usize; | ||||
|         let to = to as usize; | ||||
|         assert_eq!(0, from % ERASE_SIZE); | ||||
|         assert_eq!(0, to % ERASE_SIZE); | ||||
|         self.mem[from..to].fill(0xff); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     fn default() -> Self { | ||||
|         Self::new(0xff) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     type Error = core::convert::Infallible; | ||||
| } | ||||
|  | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     const READ_SIZE: usize = 1; | ||||
|  | ||||
|     fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.read(offset, bytes); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         SIZE | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     const WRITE_SIZE: usize = WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = ERASE_SIZE; | ||||
|  | ||||
|     fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.write(offset, bytes); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         self.erase(from, to); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     const READ_SIZE: usize = 1; | ||||
|  | ||||
|     async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         self.read(offset, bytes); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         SIZE | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash | ||||
|     for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||||
| { | ||||
|     const WRITE_SIZE: usize = WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = ERASE_SIZE; | ||||
|  | ||||
|     async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         self.write(offset, bytes); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         self.erase(from, to); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								embassy-embedded-hal/src/flash/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								embassy-embedded-hal/src/flash/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| //! Utilities related to flash. | ||||
|  | ||||
| mod concat_flash; | ||||
| #[cfg(test)] | ||||
| pub(crate) mod mem_flash; | ||||
| pub mod partition; | ||||
|  | ||||
| pub use concat_flash::ConcatFlash; | ||||
							
								
								
									
										139
									
								
								embassy-embedded-hal/src/flash/partition/asynch.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								embassy-embedded-hal/src/flash/partition/asynch.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| use embassy_sync::blocking_mutex::raw::RawMutex; | ||||
| use embassy_sync::mutex::Mutex; | ||||
| use embedded_storage::nor_flash::ErrorType; | ||||
| use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash}; | ||||
|  | ||||
| use super::Error; | ||||
|  | ||||
| /// A logical partition of an underlying shared flash | ||||
| /// | ||||
| /// A partition holds an offset and a size of the flash, | ||||
| /// and is restricted to operate with that range. | ||||
| /// There is no guarantee that muliple partitions on the same flash | ||||
| /// operate on mutually exclusive ranges - such a separation is up to | ||||
| /// the user to guarantee. | ||||
| pub struct Partition<'a, M: RawMutex, T: NorFlash> { | ||||
|     flash: &'a Mutex<M, T>, | ||||
|     offset: u32, | ||||
|     size: u32, | ||||
| } | ||||
|  | ||||
| impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> { | ||||
|     /// Create a new partition | ||||
|     pub const fn new(flash: &'a Mutex<M, T>, offset: u32, size: u32) -> Self { | ||||
|         if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 | ||||
|         { | ||||
|             panic!("Partition offset must be a multiple of read, write and erase size"); | ||||
|         } | ||||
|         if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { | ||||
|             panic!("Partition size must be a multiple of read, write and erase size"); | ||||
|         } | ||||
|         Self { flash, offset, size } | ||||
|     } | ||||
|  | ||||
|     /// Get the partition offset within the flash | ||||
|     pub const fn offset(&self) -> u32 { | ||||
|         self.offset | ||||
|     } | ||||
|  | ||||
|     /// Get the partition size | ||||
|     pub const fn size(&self) -> u32 { | ||||
|         self.size | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M: RawMutex, T: NorFlash> ErrorType for Partition<'_, M, T> { | ||||
|     type Error = Error<T::Error>; | ||||
| } | ||||
|  | ||||
| impl<M: RawMutex, T: NorFlash> ReadNorFlash for Partition<'_, M, T> { | ||||
|     const READ_SIZE: usize = T::READ_SIZE; | ||||
|  | ||||
|     async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         if offset + bytes.len() as u32 > self.size { | ||||
|             return Err(Error::OutOfBounds); | ||||
|         } | ||||
|  | ||||
|         let mut flash = self.flash.lock().await; | ||||
|         flash.read(self.offset + offset, bytes).await.map_err(Error::Flash) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         self.size as usize | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M: RawMutex, T: NorFlash> NorFlash for Partition<'_, M, T> { | ||||
|     const WRITE_SIZE: usize = T::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = T::ERASE_SIZE; | ||||
|  | ||||
|     async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         if offset + bytes.len() as u32 > self.size { | ||||
|             return Err(Error::OutOfBounds); | ||||
|         } | ||||
|  | ||||
|         let mut flash = self.flash.lock().await; | ||||
|         flash.write(self.offset + offset, bytes).await.map_err(Error::Flash) | ||||
|     } | ||||
|  | ||||
|     async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         if to > self.size { | ||||
|             return Err(Error::OutOfBounds); | ||||
|         } | ||||
|  | ||||
|         let mut flash = self.flash.lock().await; | ||||
|         flash | ||||
|             .erase(self.offset + from, self.offset + to) | ||||
|             .await | ||||
|             .map_err(Error::Flash) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::flash::mem_flash::MemFlash; | ||||
|  | ||||
|     #[futures_test::test] | ||||
|     async fn can_read() { | ||||
|         let mut flash = MemFlash::<1024, 128, 4>::default(); | ||||
|         flash.mem[132..132 + 8].fill(0xAA); | ||||
|  | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(flash); | ||||
|         let mut partition = Partition::new(&flash, 128, 256); | ||||
|  | ||||
|         let mut read_buf = [0; 8]; | ||||
|         partition.read(4, &mut read_buf).await.unwrap(); | ||||
|  | ||||
|         assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); | ||||
|     } | ||||
|  | ||||
|     #[futures_test::test] | ||||
|     async fn can_write() { | ||||
|         let flash = MemFlash::<1024, 128, 4>::default(); | ||||
|  | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(flash); | ||||
|         let mut partition = Partition::new(&flash, 128, 256); | ||||
|  | ||||
|         let write_buf = [0xAA; 8]; | ||||
|         partition.write(4, &write_buf).await.unwrap(); | ||||
|  | ||||
|         let flash = flash.try_lock().unwrap(); | ||||
|         assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); | ||||
|     } | ||||
|  | ||||
|     #[futures_test::test] | ||||
|     async fn can_erase() { | ||||
|         let flash = MemFlash::<1024, 128, 4>::new(0x00); | ||||
|  | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(flash); | ||||
|         let mut partition = Partition::new(&flash, 128, 256); | ||||
|  | ||||
|         partition.erase(0, 128).await.unwrap(); | ||||
|  | ||||
|         let flash = flash.try_lock().unwrap(); | ||||
|         assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										149
									
								
								embassy-embedded-hal/src/flash/partition/blocking.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								embassy-embedded-hal/src/flash/partition/blocking.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| use core::cell::RefCell; | ||||
|  | ||||
| use embassy_sync::blocking_mutex::raw::RawMutex; | ||||
| use embassy_sync::blocking_mutex::Mutex; | ||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||
|  | ||||
| use super::Error; | ||||
|  | ||||
| /// A logical partition of an underlying shared flash | ||||
| /// | ||||
| /// A partition holds an offset and a size of the flash, | ||||
| /// and is restricted to operate with that range. | ||||
| /// There is no guarantee that muliple partitions on the same flash | ||||
| /// operate on mutually exclusive ranges - such a separation is up to | ||||
| /// the user to guarantee. | ||||
| pub struct BlockingPartition<'a, M: RawMutex, T: NorFlash> { | ||||
|     flash: &'a Mutex<M, RefCell<T>>, | ||||
|     offset: u32, | ||||
|     size: u32, | ||||
| } | ||||
|  | ||||
| impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> { | ||||
|     /// Create a new partition | ||||
|     pub const fn new(flash: &'a Mutex<M, RefCell<T>>, offset: u32, size: u32) -> Self { | ||||
|         if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 | ||||
|         { | ||||
|             panic!("Partition offset must be a multiple of read, write and erase size"); | ||||
|         } | ||||
|         if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { | ||||
|             panic!("Partition size must be a multiple of read, write and erase size"); | ||||
|         } | ||||
|         Self { flash, offset, size } | ||||
|     } | ||||
|  | ||||
|     /// Get the partition offset within the flash | ||||
|     pub const fn offset(&self) -> u32 { | ||||
|         self.offset | ||||
|     } | ||||
|  | ||||
|     /// Get the partition size | ||||
|     pub const fn size(&self) -> u32 { | ||||
|         self.size | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M: RawMutex, T: NorFlash> ErrorType for BlockingPartition<'_, M, T> { | ||||
|     type Error = Error<T::Error>; | ||||
| } | ||||
|  | ||||
| impl<M: RawMutex, T: NorFlash> ReadNorFlash for BlockingPartition<'_, M, T> { | ||||
|     const READ_SIZE: usize = T::READ_SIZE; | ||||
|  | ||||
|     fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||||
|         if offset + bytes.len() as u32 > self.size { | ||||
|             return Err(Error::OutOfBounds); | ||||
|         } | ||||
|  | ||||
|         self.flash.lock(|flash| { | ||||
|             flash | ||||
|                 .borrow_mut() | ||||
|                 .read(self.offset + offset, bytes) | ||||
|                 .map_err(Error::Flash) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn capacity(&self) -> usize { | ||||
|         self.size as usize | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M: RawMutex, T: NorFlash> NorFlash for BlockingPartition<'_, M, T> { | ||||
|     const WRITE_SIZE: usize = T::WRITE_SIZE; | ||||
|     const ERASE_SIZE: usize = T::ERASE_SIZE; | ||||
|  | ||||
|     fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||||
|         if offset + bytes.len() as u32 > self.size { | ||||
|             return Err(Error::OutOfBounds); | ||||
|         } | ||||
|  | ||||
|         self.flash.lock(|flash| { | ||||
|             flash | ||||
|                 .borrow_mut() | ||||
|                 .write(self.offset + offset, bytes) | ||||
|                 .map_err(Error::Flash) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||
|         if to > self.size { | ||||
|             return Err(Error::OutOfBounds); | ||||
|         } | ||||
|  | ||||
|         self.flash.lock(|flash| { | ||||
|             flash | ||||
|                 .borrow_mut() | ||||
|                 .erase(self.offset + from, self.offset + to) | ||||
|                 .map_err(Error::Flash) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::flash::mem_flash::MemFlash; | ||||
|  | ||||
|     #[test] | ||||
|     fn can_read() { | ||||
|         let mut flash = MemFlash::<1024, 128, 4>::default(); | ||||
|         flash.mem[132..132 + 8].fill(0xAA); | ||||
|  | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash)); | ||||
|         let mut partition = BlockingPartition::new(&flash, 128, 256); | ||||
|  | ||||
|         let mut read_buf = [0; 8]; | ||||
|         partition.read(4, &mut read_buf).unwrap(); | ||||
|  | ||||
|         assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn can_write() { | ||||
|         let flash = MemFlash::<1024, 128, 4>::default(); | ||||
|  | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash)); | ||||
|         let mut partition = BlockingPartition::new(&flash, 128, 256); | ||||
|  | ||||
|         let write_buf = [0xAA; 8]; | ||||
|         partition.write(4, &write_buf).unwrap(); | ||||
|  | ||||
|         let flash = flash.into_inner().take(); | ||||
|         assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn can_erase() { | ||||
|         let flash = MemFlash::<1024, 128, 4>::new(0x00); | ||||
|  | ||||
|         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash)); | ||||
|         let mut partition = BlockingPartition::new(&flash, 128, 256); | ||||
|  | ||||
|         partition.erase(0, 128).unwrap(); | ||||
|  | ||||
|         let flash = flash.into_inner().take(); | ||||
|         assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								embassy-embedded-hal/src/flash/partition/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								embassy-embedded-hal/src/flash/partition/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| //! Flash Partition utilities | ||||
|  | ||||
| use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| mod asynch; | ||||
| mod blocking; | ||||
|  | ||||
| #[cfg(feature = "nightly")] | ||||
| pub use asynch::Partition; | ||||
| pub use blocking::BlockingPartition; | ||||
|  | ||||
| /// Partition error | ||||
| #[derive(Debug)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| pub enum Error<T> { | ||||
|     /// The requested flash area is outside the partition | ||||
|     OutOfBounds, | ||||
|     /// Underlying flash error | ||||
|     Flash(T), | ||||
| } | ||||
|  | ||||
| impl<T: NorFlashError> NorFlashError for Error<T> { | ||||
|     fn kind(&self) -> NorFlashErrorKind { | ||||
|         match self { | ||||
|             Error::OutOfBounds => NorFlashErrorKind::OutOfBounds, | ||||
|             Error::Flash(f) => f.kind(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,9 +1,5 @@ | ||||
| #![cfg_attr(not(feature = "std"), no_std)] | ||||
| #![cfg_attr( | ||||
|     feature = "nightly", | ||||
|     feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections, try_blocks) | ||||
| )] | ||||
| #![cfg_attr(feature = "nightly", allow(incomplete_features))] | ||||
| #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections, try_blocks))] | ||||
| #![warn(missing_docs)] | ||||
|  | ||||
| //! Utilities to use `embedded-hal` traits with Embassy. | ||||
| @@ -11,6 +7,8 @@ | ||||
| #[cfg(feature = "nightly")] | ||||
| pub mod adapter; | ||||
|  | ||||
| pub mod flash; | ||||
|  | ||||
| pub mod shared_bus; | ||||
|  | ||||
| /// Set the configuration of a peripheral driver. | ||||
|   | ||||
| @@ -84,9 +84,11 @@ where | ||||
|         address: u8, | ||||
|         operations: &mut [embedded_hal_async::i2c::Operation<'_>], | ||||
|     ) -> Result<(), I2cDeviceError<BUS::Error>> { | ||||
|         let _ = address; | ||||
|         let _ = operations; | ||||
|         todo!() | ||||
|         let mut bus = self.bus.lock().await; | ||||
|         bus.transaction(address, operations) | ||||
|             .await | ||||
|             .map_err(I2cDeviceError::I2c)?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -150,8 +152,11 @@ where | ||||
|     } | ||||
|  | ||||
|     async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> { | ||||
|         let _ = address; | ||||
|         let _ = operations; | ||||
|         todo!() | ||||
|         let mut bus = self.bus.lock().await; | ||||
|         bus.set_config(&self.config); | ||||
|         bus.transaction(address, operations) | ||||
|             .await | ||||
|             .map_err(I2cDeviceError::I2c)?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -56,62 +56,6 @@ where | ||||
|     type Error = SpiDeviceError<BUS::Error, CS::Error>; | ||||
| } | ||||
|  | ||||
| impl<M, BUS, CS> spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: spi::SpiBusRead, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     async fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> { | ||||
|         let mut bus = self.bus.lock().await; | ||||
|         self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         let op_res: Result<(), BUS::Error> = try { | ||||
|             for buf in operations { | ||||
|                 bus.read(buf).await?; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // On failure, it's important to still flush and deassert CS. | ||||
|         let flush_res = bus.flush().await; | ||||
|         let cs_res = self.cs.set_high(); | ||||
|  | ||||
|         let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|         flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|         cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         Ok(op_res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M, BUS, CS> spi::SpiDeviceWrite for SpiDevice<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: spi::SpiBusWrite, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     async fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> { | ||||
|         let mut bus = self.bus.lock().await; | ||||
|         self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         let op_res: Result<(), BUS::Error> = try { | ||||
|             for buf in operations { | ||||
|                 bus.write(buf).await?; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // On failure, it's important to still flush and deassert CS. | ||||
|         let flush_res = bus.flush().await; | ||||
|         let cs_res = self.cs.set_high(); | ||||
|  | ||||
|         let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|         flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|         cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         Ok(op_res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M, BUS, CS> spi::SpiDevice for SpiDevice<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
| @@ -129,6 +73,12 @@ where | ||||
|                     Operation::Write(buf) => bus.write(buf).await?, | ||||
|                     Operation::Transfer(read, write) => bus.transfer(read, write).await?, | ||||
|                     Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?, | ||||
|                     #[cfg(not(feature = "time"))] | ||||
|                     Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported), | ||||
|                     #[cfg(feature = "time")] | ||||
|                     Operation::DelayUs(us) => { | ||||
|                         embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
| @@ -172,64 +122,6 @@ where | ||||
|     type Error = SpiDeviceError<BUS::Error, CS::Error>; | ||||
| } | ||||
|  | ||||
| impl<M, BUS, CS> spi::SpiDeviceWrite for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: spi::SpiBusWrite + SetConfig, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     async fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> { | ||||
|         let mut bus = self.bus.lock().await; | ||||
|         bus.set_config(&self.config); | ||||
|         self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         let op_res: Result<(), BUS::Error> = try { | ||||
|             for buf in operations { | ||||
|                 bus.write(buf).await?; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // On failure, it's important to still flush and deassert CS. | ||||
|         let flush_res = bus.flush().await; | ||||
|         let cs_res = self.cs.set_high(); | ||||
|  | ||||
|         let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|         flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|         cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         Ok(op_res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M, BUS, CS> spi::SpiDeviceRead for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: spi::SpiBusRead + SetConfig, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     async fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> { | ||||
|         let mut bus = self.bus.lock().await; | ||||
|         bus.set_config(&self.config); | ||||
|         self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         let op_res: Result<(), BUS::Error> = try { | ||||
|             for buf in operations { | ||||
|                 bus.read(buf).await?; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // On failure, it's important to still flush and deassert CS. | ||||
|         let flush_res = bus.flush().await; | ||||
|         let cs_res = self.cs.set_high(); | ||||
|  | ||||
|         let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|         flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|         cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|         Ok(op_res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<M, BUS, CS> spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
| @@ -248,6 +140,12 @@ where | ||||
|                     Operation::Write(buf) => bus.write(buf).await?, | ||||
|                     Operation::Transfer(read, write) => bus.transfer(read, write).await?, | ||||
|                     Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?, | ||||
|                     #[cfg(not(feature = "time"))] | ||||
|                     Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported), | ||||
|                     #[cfg(feature = "time")] | ||||
|                     Operation::DelayUs(us) => { | ||||
|                         embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|   | ||||
| @@ -2,13 +2,12 @@ | ||||
| //! | ||||
| //! # Example (nrf52) | ||||
| //! | ||||
| //! ```rust | ||||
| //! ```rust,ignore | ||||
| //! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; | ||||
| //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; | ||||
| //! | ||||
| //! static I2C_BUS: StaticCell<NoopMutex<RefCell<Twim<TWISPI0>>>> = StaticCell::new(); | ||||
| //! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); | ||||
| //! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, Config::default()); | ||||
| //! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default()); | ||||
| //! let i2c_bus = NoopMutex::new(RefCell::new(i2c)); | ||||
| //! let i2c_bus = I2C_BUS.init(i2c_bus); | ||||
| //! | ||||
|   | ||||
| @@ -2,13 +2,12 @@ | ||||
| //! | ||||
| //! # Example (nrf52) | ||||
| //! | ||||
| //! ```rust | ||||
| //! ```rust,ignore | ||||
| //! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; | ||||
| //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; | ||||
| //! | ||||
| //! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new(); | ||||
| //! let irq = interrupt::take!(SPIM3); | ||||
| //! let spi = Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, Config::default()); | ||||
| //! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default()); | ||||
| //! let spi_bus = NoopMutex::new(RefCell::new(spi)); | ||||
| //! let spi_bus = SPI_BUS.init(spi_bus); | ||||
| //! | ||||
| @@ -23,7 +22,7 @@ use core::cell::RefCell; | ||||
| use embassy_sync::blocking_mutex::raw::RawMutex; | ||||
| use embassy_sync::blocking_mutex::Mutex; | ||||
| use embedded_hal_1::digital::OutputPin; | ||||
| use embedded_hal_1::spi::{self, Operation, SpiBus, SpiBusRead, SpiBusWrite}; | ||||
| use embedded_hal_1::spi::{self, Operation, SpiBus}; | ||||
|  | ||||
| use crate::shared_bus::SpiDeviceError; | ||||
| use crate::SetConfig; | ||||
| @@ -49,58 +48,6 @@ where | ||||
|     type Error = SpiDeviceError<BUS::Error, CS::Error>; | ||||
| } | ||||
|  | ||||
| impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: SpiBusRead, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> { | ||||
|         self.bus.lock(|bus| { | ||||
|             let mut bus = bus.borrow_mut(); | ||||
|             self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|             let op_res = operations.iter_mut().try_for_each(|buf| bus.read(buf)); | ||||
|  | ||||
|             // On failure, it's important to still flush and deassert CS. | ||||
|             let flush_res = bus.flush(); | ||||
|             let cs_res = self.cs.set_high(); | ||||
|  | ||||
|             let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|             flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|             cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|             Ok(op_res) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceWrite for SpiDevice<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: SpiBusWrite, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> { | ||||
|         self.bus.lock(|bus| { | ||||
|             let mut bus = bus.borrow_mut(); | ||||
|             self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|             let op_res = operations.iter().try_for_each(|buf| bus.write(buf)); | ||||
|  | ||||
|             // On failure, it's important to still flush and deassert CS. | ||||
|             let flush_res = bus.flush(); | ||||
|             let cs_res = self.cs.set_high(); | ||||
|  | ||||
|             let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|             flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|             cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|             Ok(op_res) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
| @@ -117,6 +64,13 @@ where | ||||
|                 Operation::Write(buf) => bus.write(buf), | ||||
|                 Operation::Transfer(read, write) => bus.transfer(read, write), | ||||
|                 Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), | ||||
|                 #[cfg(not(feature = "time"))] | ||||
|                 Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported), | ||||
|                 #[cfg(feature = "time")] | ||||
|                 Operation::DelayUs(us) => { | ||||
|                     embassy_time::block_for(embassy_time::Duration::from_micros(*us as _)); | ||||
|                     Ok(()) | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             // On failure, it's important to still flush and deassert CS. | ||||
| @@ -200,58 +154,6 @@ where | ||||
|     type Error = SpiDeviceError<BUS::Error, CS::Error>; | ||||
| } | ||||
|  | ||||
| impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: SpiBusRead + SetConfig, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> { | ||||
|         self.bus.lock(|bus| { | ||||
|             let mut bus = bus.borrow_mut(); | ||||
|             bus.set_config(&self.config); | ||||
|             self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|             let op_res = operations.iter_mut().try_for_each(|buf| bus.read(buf)); | ||||
|  | ||||
|             // On failure, it's important to still flush and deassert CS. | ||||
|             let flush_res = bus.flush(); | ||||
|             let cs_res = self.cs.set_high(); | ||||
|  | ||||
|             let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|             flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|             cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|             Ok(op_res) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceWrite for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
|     BUS: SpiBusWrite + SetConfig, | ||||
|     CS: OutputPin, | ||||
| { | ||||
|     fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> { | ||||
|         self.bus.lock(|bus| { | ||||
|             let mut bus = bus.borrow_mut(); | ||||
|             bus.set_config(&self.config); | ||||
|             self.cs.set_low().map_err(SpiDeviceError::Cs)?; | ||||
|  | ||||
|             let op_res = operations.iter().try_for_each(|buf| bus.write(buf)); | ||||
|  | ||||
|             // On failure, it's important to still flush and deassert CS. | ||||
|             let flush_res = bus.flush(); | ||||
|             let cs_res = self.cs.set_high(); | ||||
|  | ||||
|             let op_res = op_res.map_err(SpiDeviceError::Spi)?; | ||||
|             flush_res.map_err(SpiDeviceError::Spi)?; | ||||
|             cs_res.map_err(SpiDeviceError::Cs)?; | ||||
|             Ok(op_res) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||
| where | ||||
|     M: RawMutex, | ||||
| @@ -269,6 +171,13 @@ where | ||||
|                 Operation::Write(buf) => bus.write(buf), | ||||
|                 Operation::Transfer(read, write) => bus.transfer(read, write), | ||||
|                 Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), | ||||
|                 #[cfg(not(feature = "time"))] | ||||
|                 Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported), | ||||
|                 #[cfg(feature = "time")] | ||||
|                 Operation::DelayUs(us) => { | ||||
|                     embassy_time::block_for(embassy_time::Duration::from_micros(*us as _)); | ||||
|                     Ok(()) | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             // On failure, it's important to still flush and deassert CS. | ||||
|   | ||||
| @@ -30,11 +30,14 @@ where | ||||
| /// Error returned by SPI device implementations in this crate. | ||||
| #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||
| #[non_exhaustive] | ||||
| pub enum SpiDeviceError<BUS, CS> { | ||||
|     /// An operation on the inner SPI bus failed. | ||||
|     Spi(BUS), | ||||
|     /// Setting the value of the Chip Select (CS) pin failed. | ||||
|     Cs(CS), | ||||
|     /// DelayUs operations are not supported when the `time` Cargo feature is not enabled. | ||||
|     DelayUsNotSupported, | ||||
| } | ||||
|  | ||||
| impl<BUS, CS> spi::Error for SpiDeviceError<BUS, CS> | ||||
| @@ -46,6 +49,7 @@ where | ||||
|         match self { | ||||
|             Self::Spi(e) => e.kind(), | ||||
|             Self::Cs(_) => spi::ErrorKind::Other, | ||||
|             Self::DelayUsNotSupported => spi::ErrorKind::Other, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										37
									
								
								embassy-executor/CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								embassy-executor/CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| # Changelog | ||||
|  | ||||
| All notable changes to this project will be documented in this file. | ||||
|  | ||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||
|  | ||||
| ## 0.3.0 - 2023-08-25 | ||||
|  | ||||
| - Replaced Pender. Implementations now must define an extern function called `__pender`. | ||||
| - Made `raw::AvailableTask` public | ||||
| - Made `SpawnToken::new_failed` public | ||||
| - You can now use arbitrary expressions to specify `#[task(pool_size = X)]` | ||||
|  | ||||
| ## 0.2.1 - 2023-08-10 | ||||
|  | ||||
| - Avoid calling `pend()` when waking expired timers | ||||
| - Properly reset finished task state with `integrated-timers` enabled | ||||
| - Introduce `InterruptExecutor::spawner()` | ||||
| - Fix incorrect critical section in Xtensa executor | ||||
|  | ||||
| ## 0.2.0 - 2023-04-27 | ||||
|  | ||||
| - Replace unnecessary atomics in runqueue | ||||
| - add Pender, rework Cargo features. | ||||
| - add support for turbo-wakers. | ||||
| - Allow TaskStorage to auto-implement `Sync` | ||||
| - Use AtomicPtr for signal_ctx, removes 1 unsafe. | ||||
| - Replace unsound critical sections with atomics | ||||
|  | ||||
| ## 0.1.1 - 2022-11-23 | ||||
|  | ||||
| - Fix features for documentation | ||||
|  | ||||
| ## 0.1.0 - 2022-11-23 | ||||
|  | ||||
| - First release | ||||
| @@ -1,6 +1,6 @@ | ||||
| [package] | ||||
| name = "embassy-executor" | ||||
| version = "0.1.1" | ||||
| version = "0.3.0" | ||||
| edition = "2021" | ||||
| license = "MIT OR Apache-2.0" | ||||
| description = "async/await executor designed for embedded usage" | ||||
| @@ -14,7 +14,7 @@ categories = [ | ||||
| [package.metadata.embassy_docs] | ||||
| src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" | ||||
| src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" | ||||
| features = ["nightly", "defmt", "pender-callback"] | ||||
| features = ["nightly", "defmt"] | ||||
| flavors = [ | ||||
|     { name = "std",             target = "x86_64-unknown-linux-gnu",     features = ["arch-std", "executor-thread"] }, | ||||
|     { name = "wasm",            target = "wasm32-unknown-unknown",       features = ["arch-wasm", "executor-thread"] }, | ||||
| @@ -25,7 +25,7 @@ flavors = [ | ||||
| [package.metadata.docs.rs] | ||||
| default-target = "thumbv7em-none-eabi" | ||||
| targets = ["thumbv7em-none-eabi"] | ||||
| features = ["nightly", "defmt", "pender-callback", "arch-cortex-m", "executor-thread", "executor-interrupt"] | ||||
| features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] | ||||
|  | ||||
| [features] | ||||
|  | ||||
| @@ -37,9 +37,6 @@ arch-xtensa = ["_arch"] | ||||
| arch-riscv32 = ["_arch"] | ||||
| arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] | ||||
|  | ||||
| # Enable creating a `Pender` from an arbitrary function pointer callback. | ||||
| pender-callback = [] | ||||
|  | ||||
| # Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) | ||||
| executor-thread = [] | ||||
| # Enable the interrupt-mode executor (available in Cortex-M only) | ||||
| @@ -61,11 +58,11 @@ log = { version = "0.4.14", optional = true } | ||||
| rtos-trace = { version = "0.1.2", optional = true } | ||||
|  | ||||
| futures-util = { version = "0.3.17", default-features = false } | ||||
| embassy-macros  = { version = "0.1.0", path = "../embassy-macros" } | ||||
| embassy-time  = { version = "0.1.0", path = "../embassy-time", optional = true} | ||||
| embassy-macros = { version = "0.2.1", path = "../embassy-macros" } | ||||
| embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true} | ||||
| atomic-polyfill = "1.0.1" | ||||
| critical-section = "1.1" | ||||
| static_cell = "1.0" | ||||
| static_cell = "1.1" | ||||
|  | ||||
| # arch-cortex-m dependencies | ||||
| cortex-m = { version = "0.7.6", optional = true } | ||||
|   | ||||
| @@ -1,25 +1,61 @@ | ||||
| #[export_name = "__pender"] | ||||
| #[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] | ||||
| fn __pender(context: *mut ()) { | ||||
|     unsafe { | ||||
|         // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt | ||||
|         // request number given to `InterruptExecutor::start`. | ||||
|  | ||||
|         let context = context as usize; | ||||
|  | ||||
|         #[cfg(feature = "executor-thread")] | ||||
|         // Try to make Rust optimize the branching away if we only use thread mode. | ||||
|         if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { | ||||
|             core::arch::asm!("sev"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         #[cfg(feature = "executor-interrupt")] | ||||
|         { | ||||
|             use cortex_m::interrupt::InterruptNumber; | ||||
|             use cortex_m::peripheral::NVIC; | ||||
|  | ||||
|             #[derive(Clone, Copy)] | ||||
|             struct Irq(u16); | ||||
|             unsafe impl InterruptNumber for Irq { | ||||
|                 fn number(self) -> u16 { | ||||
|                     self.0 | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             let irq = Irq(context as u16); | ||||
|  | ||||
|             // STIR is faster, but is only available in v7 and higher. | ||||
|             #[cfg(not(armv6m))] | ||||
|             { | ||||
|                 let mut nvic: NVIC = core::mem::transmute(()); | ||||
|                 nvic.request(irq); | ||||
|             } | ||||
|  | ||||
|             #[cfg(armv6m)] | ||||
|             NVIC::pend(irq); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "executor-thread")] | ||||
| pub use thread::*; | ||||
| #[cfg(feature = "executor-thread")] | ||||
| mod thread { | ||||
|     pub(super) const THREAD_PENDER: usize = usize::MAX; | ||||
|  | ||||
|     use core::arch::asm; | ||||
|     use core::marker::PhantomData; | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     pub use embassy_macros::main_cortex_m as main; | ||||
|  | ||||
|     use crate::raw::{Pender, PenderInner}; | ||||
|     use crate::{raw, Spawner}; | ||||
|  | ||||
|     #[derive(Copy, Clone)] | ||||
|     pub(crate) struct ThreadPender; | ||||
|  | ||||
|     impl ThreadPender { | ||||
|         pub(crate) fn pend(self) { | ||||
|             unsafe { core::arch::asm!("sev") } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Thread mode executor, using WFE/SEV. | ||||
|     /// | ||||
|     /// This is the simplest and most common kind of executor. It runs on | ||||
| @@ -39,7 +75,7 @@ mod thread { | ||||
|         /// Create a new Executor. | ||||
|         pub fn new() -> Self { | ||||
|             Self { | ||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), | ||||
|                 inner: raw::Executor::new(THREAD_PENDER as *mut ()), | ||||
|                 not_send: PhantomData, | ||||
|             } | ||||
|         } | ||||
| @@ -86,30 +122,7 @@ mod interrupt { | ||||
|     use cortex_m::interrupt::InterruptNumber; | ||||
|     use cortex_m::peripheral::NVIC; | ||||
|  | ||||
|     use crate::raw::{self, Pender, PenderInner}; | ||||
|  | ||||
|     #[derive(Clone, Copy)] | ||||
|     pub(crate) struct InterruptPender(u16); | ||||
|  | ||||
|     impl InterruptPender { | ||||
|         pub(crate) fn pend(self) { | ||||
|             // STIR is faster, but is only available in v7 and higher. | ||||
|             #[cfg(not(armv6m))] | ||||
|             { | ||||
|                 let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) }; | ||||
|                 nvic.request(self); | ||||
|             } | ||||
|  | ||||
|             #[cfg(armv6m)] | ||||
|             cortex_m::peripheral::NVIC::pend(self); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     unsafe impl cortex_m::interrupt::InterruptNumber for InterruptPender { | ||||
|         fn number(self) -> u16 { | ||||
|             self.0 | ||||
|         } | ||||
|     } | ||||
|     use crate::raw; | ||||
|  | ||||
|     /// Interrupt mode executor. | ||||
|     /// | ||||
| @@ -194,9 +207,7 @@ mod interrupt { | ||||
|             unsafe { | ||||
|                 (&mut *self.executor.get()) | ||||
|                     .as_mut_ptr() | ||||
|                     .write(raw::Executor::new(Pender(PenderInner::Interrupt(InterruptPender( | ||||
|                         irq.number(), | ||||
|                     ))))) | ||||
|                     .write(raw::Executor::new(irq.number() as *mut ())) | ||||
|             } | ||||
|  | ||||
|             let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; | ||||
| @@ -205,5 +216,20 @@ mod interrupt { | ||||
|  | ||||
|             executor.spawner().make_send() | ||||
|         } | ||||
|  | ||||
|         /// Get a SendSpawner for this executor | ||||
|         /// | ||||
|         /// This returns a [`SendSpawner`] you can use to spawn tasks on this | ||||
|         /// executor. | ||||
|         /// | ||||
|         /// This MUST only be called on an executor that has already been spawned. | ||||
|         /// The function will panic otherwise. | ||||
|         pub fn spawner(&'static self) -> crate::SendSpawner { | ||||
|             if !self.started.load(Ordering::Acquire) { | ||||
|                 panic!("InterruptExecutor::spawner() called on uninitialized executor."); | ||||
|             } | ||||
|             let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; | ||||
|             executor.spawner().make_send() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,22 +11,16 @@ mod thread { | ||||
|     #[cfg(feature = "nightly")] | ||||
|     pub use embassy_macros::main_riscv as main; | ||||
|  | ||||
|     use crate::raw::{Pender, PenderInner}; | ||||
|     use crate::{raw, Spawner}; | ||||
|  | ||||
|     #[derive(Copy, Clone)] | ||||
|     pub(crate) struct ThreadPender; | ||||
|  | ||||
|     impl ThreadPender { | ||||
|         #[allow(unused)] | ||||
|         pub(crate) fn pend(self) { | ||||
|             SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV | ||||
|     static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); | ||||
|  | ||||
|     #[export_name = "__pender"] | ||||
|     fn __pender(_context: *mut ()) { | ||||
|         SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); | ||||
|     } | ||||
|  | ||||
|     /// RISCV32 Executor | ||||
|     pub struct Executor { | ||||
|         inner: raw::Executor, | ||||
| @@ -37,7 +31,7 @@ mod thread { | ||||
|         /// Create a new Executor. | ||||
|         pub fn new() -> Self { | ||||
|             Self { | ||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), | ||||
|                 inner: raw::Executor::new(core::ptr::null_mut()), | ||||
|                 not_send: PhantomData, | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -11,17 +11,12 @@ mod thread { | ||||
|     #[cfg(feature = "nightly")] | ||||
|     pub use embassy_macros::main_std as main; | ||||
|  | ||||
|     use crate::raw::{Pender, PenderInner}; | ||||
|     use crate::{raw, Spawner}; | ||||
|  | ||||
|     #[derive(Copy, Clone)] | ||||
|     pub(crate) struct ThreadPender(&'static Signaler); | ||||
|  | ||||
|     impl ThreadPender { | ||||
|         #[allow(unused)] | ||||
|         pub(crate) fn pend(self) { | ||||
|             self.0.signal() | ||||
|         } | ||||
|     #[export_name = "__pender"] | ||||
|     fn __pender(context: *mut ()) { | ||||
|         let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; | ||||
|         signaler.signal() | ||||
|     } | ||||
|  | ||||
|     /// Single-threaded std-based executor. | ||||
| @@ -34,9 +29,9 @@ mod thread { | ||||
|     impl Executor { | ||||
|         /// Create a new Executor. | ||||
|         pub fn new() -> Self { | ||||
|             let signaler = &*Box::leak(Box::new(Signaler::new())); | ||||
|             let signaler = Box::leak(Box::new(Signaler::new())); | ||||
|             Self { | ||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(signaler)))), | ||||
|                 inner: raw::Executor::new(signaler as *mut Signaler as *mut ()), | ||||
|                 not_send: PhantomData, | ||||
|                 signaler, | ||||
|             } | ||||
|   | ||||
| @@ -14,14 +14,12 @@ mod thread { | ||||
|     use wasm_bindgen::prelude::*; | ||||
|  | ||||
|     use crate::raw::util::UninitCell; | ||||
|     use crate::raw::{Pender, PenderInner}; | ||||
|     use crate::{raw, Spawner}; | ||||
|  | ||||
|     /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. | ||||
|     pub struct Executor { | ||||
|         inner: raw::Executor, | ||||
|         ctx: &'static WasmContext, | ||||
|         not_send: PhantomData<*mut ()>, | ||||
|     #[export_name = "__pender"] | ||||
|     fn __pender(context: *mut ()) { | ||||
|         let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; | ||||
|         let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); | ||||
|     } | ||||
|  | ||||
|     pub(crate) struct WasmContext { | ||||
| @@ -29,16 +27,6 @@ mod thread { | ||||
|         closure: UninitCell<Closure<dyn FnMut(JsValue)>>, | ||||
|     } | ||||
|  | ||||
|     #[derive(Copy, Clone)] | ||||
|     pub(crate) struct ThreadPender(&'static WasmContext); | ||||
|  | ||||
|     impl ThreadPender { | ||||
|         #[allow(unused)] | ||||
|         pub(crate) fn pend(self) { | ||||
|             let _ = self.0.promise.then(unsafe { self.0.closure.as_mut() }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl WasmContext { | ||||
|         pub fn new() -> Self { | ||||
|             Self { | ||||
| @@ -48,14 +36,21 @@ mod thread { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. | ||||
|     pub struct Executor { | ||||
|         inner: raw::Executor, | ||||
|         ctx: &'static WasmContext, | ||||
|         not_send: PhantomData<*mut ()>, | ||||
|     } | ||||
|  | ||||
|     impl Executor { | ||||
|         /// Create a new Executor. | ||||
|         pub fn new() -> Self { | ||||
|             let ctx = &*Box::leak(Box::new(WasmContext::new())); | ||||
|             let ctx = Box::leak(Box::new(WasmContext::new())); | ||||
|             Self { | ||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(ctx)))), | ||||
|                 not_send: PhantomData, | ||||
|                 inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()), | ||||
|                 ctx, | ||||
|                 not_send: PhantomData, | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -8,22 +8,16 @@ mod thread { | ||||
|     use core::marker::PhantomData; | ||||
|     use core::sync::atomic::{AtomicBool, Ordering}; | ||||
|  | ||||
|     use crate::raw::{Pender, PenderInner}; | ||||
|     use crate::{raw, Spawner}; | ||||
|  | ||||
|     #[derive(Copy, Clone)] | ||||
|     pub(crate) struct ThreadPender; | ||||
|  | ||||
|     impl ThreadPender { | ||||
|         #[allow(unused)] | ||||
|         pub(crate) fn pend(self) { | ||||
|             SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa | ||||
|     static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); | ||||
|  | ||||
|     #[export_name = "__pender"] | ||||
|     fn __pender(_context: *mut ()) { | ||||
|         SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); | ||||
|     } | ||||
|  | ||||
|     /// Xtensa Executor | ||||
|     pub struct Executor { | ||||
|         inner: raw::Executor, | ||||
| @@ -34,7 +28,7 @@ mod thread { | ||||
|         /// Create a new Executor. | ||||
|         pub fn new() -> Self { | ||||
|             Self { | ||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), | ||||
|                 inner: raw::Executor::new(core::ptr::null_mut()), | ||||
|                 not_send: PhantomData, | ||||
|             } | ||||
|         } | ||||
| @@ -63,13 +57,22 @@ mod thread { | ||||
|             loop { | ||||
|                 unsafe { | ||||
|                     self.inner.poll(); | ||||
|  | ||||
|                     // Manual critical section implementation that only masks interrupts handlers. | ||||
|                     // We must not acquire the cross-core on dual-core systems because that would | ||||
|                     // prevent the other core from doing useful work while this core is sleeping. | ||||
|                     let token: critical_section::RawRestoreState; | ||||
|                     core::arch::asm!("rsil {0}, 5", out(reg) token); | ||||
|  | ||||
|                     // we do not care about race conditions between the load and store operations, interrupts | ||||
|                     // will only set this value to true. | ||||
|                     // if there is work to do, loop back to polling | ||||
|                     // TODO can we relax this? | ||||
|                     critical_section::with(|_| { | ||||
|                     if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { | ||||
|                         SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); | ||||
|  | ||||
|                         core::arch::asm!( | ||||
|                         "wsr.ps {0}", | ||||
|                         "rsync", in(reg) token) | ||||
|                     } else { | ||||
|                         // waiti sets the PS.INTLEVEL when slipping into sleep | ||||
|                         // because critical sections in Xtensa are implemented via increasing | ||||
| @@ -77,7 +80,6 @@ mod thread { | ||||
|                         // take care not add code after `waiti` if it needs to be inside the CS | ||||
|                         core::arch::asm!("waiti 0"); // critical section ends here | ||||
|                     } | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
|  | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
|  | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
|  | ||||
| @@ -81,14 +83,17 @@ macro_rules! todo { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::unreachable!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::unreachable!($($x)*); | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -223,3 +228,31 @@ impl<T, E> Try for Result<T, E> { | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(unused)] | ||||
| pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||
|  | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -147,10 +147,7 @@ impl<F: Future + 'static> TaskStorage<F> { | ||||
|     pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { | ||||
|         let task = AvailableTask::claim(self); | ||||
|         match task { | ||||
|             Some(task) => { | ||||
|                 let task = task.initialize(future); | ||||
|                 unsafe { SpawnToken::<F>::new(task) } | ||||
|             } | ||||
|             Some(task) => task.initialize(future), | ||||
|             None => SpawnToken::new_failed(), | ||||
|         } | ||||
|     } | ||||
| @@ -165,6 +162,9 @@ impl<F: Future + 'static> TaskStorage<F> { | ||||
|             Poll::Ready(_) => { | ||||
|                 this.future.drop_in_place(); | ||||
|                 this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); | ||||
|  | ||||
|                 #[cfg(feature = "integrated-timers")] | ||||
|                 this.raw.expires_at.set(Instant::MAX); | ||||
|             } | ||||
|             Poll::Pending => {} | ||||
|         } | ||||
| @@ -183,12 +183,16 @@ impl<F: Future + 'static> TaskStorage<F> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct AvailableTask<F: Future + 'static> { | ||||
| /// An uninitialized [`TaskStorage`]. | ||||
| pub struct AvailableTask<F: Future + 'static> { | ||||
|     task: &'static TaskStorage<F>, | ||||
| } | ||||
|  | ||||
| impl<F: Future + 'static> AvailableTask<F> { | ||||
|     fn claim(task: &'static TaskStorage<F>) -> Option<Self> { | ||||
|     /// Try to claim a [`TaskStorage`]. | ||||
|     /// | ||||
|     /// This function returns `None` if a task has already been spawned and has not finished running. | ||||
|     pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> { | ||||
|         task.raw | ||||
|             .state | ||||
|             .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) | ||||
| @@ -196,61 +200,30 @@ impl<F: Future + 'static> AvailableTask<F> { | ||||
|             .map(|_| Self { task }) | ||||
|     } | ||||
|  | ||||
|     fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { | ||||
|     fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> { | ||||
|         unsafe { | ||||
|             self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll)); | ||||
|             self.task.future.write(future()); | ||||
|         } | ||||
|         TaskRef::new(self.task) | ||||
|  | ||||
|             let task = TaskRef::new(self.task); | ||||
|  | ||||
|             SpawnToken::new(task) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| /// Raw storage that can hold up to N tasks of the same type. | ||||
| /// | ||||
| /// This is essentially a `[TaskStorage<F>; N]`. | ||||
| pub struct TaskPool<F: Future + 'static, const N: usize> { | ||||
|     pool: [TaskStorage<F>; N], | ||||
|     /// Initialize the [`TaskStorage`] to run the given future. | ||||
|     pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken<F> { | ||||
|         self.initialize_impl::<F>(future) | ||||
|     } | ||||
|  | ||||
| impl<F: Future + 'static, const N: usize> TaskPool<F, N> { | ||||
|     /// Create a new TaskPool, with all tasks in non-spawned state. | ||||
|     pub const fn new() -> Self { | ||||
|         Self { | ||||
|             pool: [TaskStorage::NEW; N], | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Try to spawn a task in the pool. | ||||
|     /// Initialize the [`TaskStorage`] to run the given future. | ||||
|     /// | ||||
|     /// See [`TaskStorage::spawn()`] for details. | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// This will loop over the pool and spawn the task in the first storage that | ||||
|     /// is currently free. If none is free, a "poisoned" SpawnToken is returned, | ||||
|     /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. | ||||
|     pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { | ||||
|         let task = self.pool.iter().find_map(AvailableTask::claim); | ||||
|         match task { | ||||
|             Some(task) => { | ||||
|                 let task = task.initialize(future); | ||||
|                 unsafe { SpawnToken::<F>::new(task) } | ||||
|             } | ||||
|             None => SpawnToken::new_failed(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Like spawn(), but allows the task to be send-spawned if the args are Send even if | ||||
|     /// the future is !Send. | ||||
|     /// | ||||
|     /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used | ||||
|     /// by the Embassy macros ONLY. | ||||
|     /// | ||||
|     /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` | ||||
|     /// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` | ||||
|     /// is an `async fn`, NOT a hand-written `Future`. | ||||
|     #[doc(hidden)] | ||||
|     pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized> | ||||
|     where | ||||
|         FutFn: FnOnce() -> F, | ||||
|     { | ||||
|     pub unsafe fn __initialize_async_fn<FutFn>(self, future: impl FnOnce() -> F) -> SpawnToken<FutFn> { | ||||
|         // When send-spawning a task, we construct the future in this thread, and effectively | ||||
|         // "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory, | ||||
|         // send-spawning should require the future `F` to be `Send`. | ||||
| @@ -276,66 +249,73 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> { | ||||
|         // | ||||
|         // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly | ||||
|         // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`. | ||||
|  | ||||
|         let task = self.pool.iter().find_map(AvailableTask::claim); | ||||
|         match task { | ||||
|             Some(task) => { | ||||
|                 let task = task.initialize(future); | ||||
|                 unsafe { SpawnToken::<FutFn>::new(task) } | ||||
|         self.initialize_impl::<FutFn>(future) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Raw storage that can hold up to N tasks of the same type. | ||||
| /// | ||||
| /// This is essentially a `[TaskStorage<F>; N]`. | ||||
| pub struct TaskPool<F: Future + 'static, const N: usize> { | ||||
|     pool: [TaskStorage<F>; N], | ||||
| } | ||||
|  | ||||
| impl<F: Future + 'static, const N: usize> TaskPool<F, N> { | ||||
|     /// Create a new TaskPool, with all tasks in non-spawned state. | ||||
|     pub const fn new() -> Self { | ||||
|         Self { | ||||
|             pool: [TaskStorage::NEW; N], | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn spawn_impl<T>(&'static self, future: impl FnOnce() -> F) -> SpawnToken<T> { | ||||
|         match self.pool.iter().find_map(AvailableTask::claim) { | ||||
|             Some(task) => task.initialize_impl::<T>(future), | ||||
|             None => SpawnToken::new_failed(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Try to spawn a task in the pool. | ||||
|     /// | ||||
|     /// See [`TaskStorage::spawn()`] for details. | ||||
|     /// | ||||
|     /// This will loop over the pool and spawn the task in the first storage that | ||||
|     /// is currently free. If none is free, a "poisoned" SpawnToken is returned, | ||||
|     /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. | ||||
|     pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { | ||||
|         self.spawn_impl::<F>(future) | ||||
|     } | ||||
|  | ||||
|     /// Like spawn(), but allows the task to be send-spawned if the args are Send even if | ||||
|     /// the future is !Send. | ||||
|     /// | ||||
|     /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used | ||||
|     /// by the Embassy macros ONLY. | ||||
|     /// | ||||
|     /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` | ||||
|     /// is an `async fn`, NOT a hand-written `Future`. | ||||
|     #[doc(hidden)] | ||||
|     pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized> | ||||
|     where | ||||
|         FutFn: FnOnce() -> F, | ||||
|     { | ||||
|         // See the comment in AvailableTask::__initialize_async_fn for explanation. | ||||
|         self.spawn_impl::<FutFn>(future) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| pub(crate) enum PenderInner { | ||||
|     #[cfg(feature = "executor-thread")] | ||||
|     Thread(crate::arch::ThreadPender), | ||||
|     #[cfg(feature = "executor-interrupt")] | ||||
|     Interrupt(crate::arch::InterruptPender), | ||||
|     #[cfg(feature = "pender-callback")] | ||||
|     Callback { func: fn(*mut ()), context: *mut () }, | ||||
| } | ||||
| pub(crate) struct Pender(*mut ()); | ||||
|  | ||||
| unsafe impl Send for PenderInner {} | ||||
| unsafe impl Sync for PenderInner {} | ||||
|  | ||||
| /// Platform/architecture-specific action executed when an executor has pending work. | ||||
| /// | ||||
| /// When a task within an executor is woken, the `Pender` is called. This does a | ||||
| /// platform/architecture-specific action to signal there is pending work in the executor. | ||||
| /// When this happens, you must arrange for [`Executor::poll`] to be called. | ||||
| /// | ||||
| /// You can think of it as a waker, but for the whole executor. | ||||
| pub struct Pender(pub(crate) PenderInner); | ||||
| unsafe impl Send for Pender {} | ||||
| unsafe impl Sync for Pender {} | ||||
|  | ||||
| impl Pender { | ||||
|     /// Create a `Pender` that will call an arbitrary function pointer. | ||||
|     /// | ||||
|     /// # Arguments | ||||
|     /// | ||||
|     /// - `func`: The function pointer to call. | ||||
|     /// - `context`: Opaque context pointer, that will be passed to the function pointer. | ||||
|     #[cfg(feature = "pender-callback")] | ||||
|     pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self { | ||||
|         Self(PenderInner::Callback { | ||||
|             func, | ||||
|             context: context.into(), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Pender { | ||||
|     pub(crate) fn pend(&self) { | ||||
|         match self.0 { | ||||
|             #[cfg(feature = "executor-thread")] | ||||
|             PenderInner::Thread(x) => x.pend(), | ||||
|             #[cfg(feature = "executor-interrupt")] | ||||
|             PenderInner::Interrupt(x) => x.pend(), | ||||
|             #[cfg(feature = "pender-callback")] | ||||
|             PenderInner::Callback { func, context } => func(context), | ||||
|     pub(crate) fn pend(self) { | ||||
|         extern "Rust" { | ||||
|             fn __pender(context: *mut ()); | ||||
|         } | ||||
|         unsafe { __pender(self.0) }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -406,7 +386,7 @@ impl SyncExecutor { | ||||
|         #[allow(clippy::never_loop)] | ||||
|         loop { | ||||
|             #[cfg(feature = "integrated-timers")] | ||||
|             self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); | ||||
|             self.timer_queue.dequeue_expired(Instant::now(), wake_task_no_pend); | ||||
|  | ||||
|             self.run_queue.dequeue_all(|p| { | ||||
|                 let task = p.header(); | ||||
| @@ -469,15 +449,31 @@ impl SyncExecutor { | ||||
| /// | ||||
| /// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks | ||||
| ///   that "want to run"). | ||||
| /// - You must supply a [`Pender`]. The executor will call it to notify you it has work | ||||
| ///   to do. You must arrange for `poll()` to be called as soon as possible. | ||||
| /// - You must supply a pender function, as shown below. The executor will call it to notify you | ||||
| ///   it has work to do. You must arrange for `poll()` to be called as soon as possible. | ||||
| /// - Enabling `arch-xx` features will define a pender function for you. This means that you | ||||
| ///   are limited to using the executors provided to you by the architecture/platform | ||||
| ///   implementation. If you need a different executor, you must not enable `arch-xx` features. | ||||
| /// | ||||
| /// The [`Pender`] can be called from *any* context: any thread, any interrupt priority | ||||
| /// The pender can be called from *any* context: any thread, any interrupt priority | ||||
| /// level, etc. It may be called synchronously from any `Executor` method call as well. | ||||
| /// You must deal with this correctly. | ||||
| /// | ||||
| /// In particular, you must NOT call `poll` directly from the pender callback, as this violates | ||||
| /// the requirement for `poll` to not be called reentrantly. | ||||
| /// | ||||
| /// The pender function must be exported with the name `__pender` and have the following signature: | ||||
| /// | ||||
| /// ```rust | ||||
| /// #[export_name = "__pender"] | ||||
| /// fn pender(context: *mut ()) { | ||||
| ///    // schedule `poll()` to be called | ||||
| /// } | ||||
| /// ``` | ||||
| /// | ||||
| /// The `context` argument is a piece of arbitrary data the executor will pass to the pender. | ||||
| /// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example, | ||||
| /// differentiate between executors, or to pass a pointer to a callback that should be called. | ||||
| #[repr(transparent)] | ||||
| pub struct Executor { | ||||
|     pub(crate) inner: SyncExecutor, | ||||
| @@ -492,12 +488,12 @@ impl Executor { | ||||
|  | ||||
|     /// Create a new executor. | ||||
|     /// | ||||
|     /// When the executor has work to do, it will call the [`Pender`]. | ||||
|     /// When the executor has work to do, it will call the pender function and pass `context` to it. | ||||
|     /// | ||||
|     /// See [`Executor`] docs for details on `Pender`. | ||||
|     pub fn new(pender: Pender) -> Self { | ||||
|     /// See [`Executor`] docs for details on the pender. | ||||
|     pub fn new(context: *mut ()) -> Self { | ||||
|         Self { | ||||
|             inner: SyncExecutor::new(pender), | ||||
|             inner: SyncExecutor::new(Pender(context)), | ||||
|             _not_sync: PhantomData, | ||||
|         } | ||||
|     } | ||||
| @@ -520,16 +516,16 @@ impl Executor { | ||||
|     /// This loops over all tasks that are queued to be polled (i.e. they're | ||||
|     /// freshly spawned or they've been woken). Other tasks are not polled. | ||||
|     /// | ||||
|     /// You must call `poll` after receiving a call to the [`Pender`]. It is OK | ||||
|     /// to call `poll` even when not requested by the `Pender`, but it wastes | ||||
|     /// You must call `poll` after receiving a call to the pender. It is OK | ||||
|     /// to call `poll` even when not requested by the pender, but it wastes | ||||
|     /// energy. | ||||
|     /// | ||||
|     /// # Safety | ||||
|     /// | ||||
|     /// You must NOT call `poll` reentrantly on the same executor. | ||||
|     /// | ||||
|     /// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you | ||||
|     /// must NOT directly call `poll()` from the `Pender` callback. Instead, the callback has to | ||||
|     /// In particular, note that `poll` may call the pender synchronously. Therefore, you | ||||
|     /// must NOT directly call `poll()` from the pender callback. Instead, the callback has to | ||||
|     /// somehow schedule for `poll()` to be called later, at a time you know for sure there's | ||||
|     /// no `poll()` already running. | ||||
|     pub unsafe fn poll(&'static self) { | ||||
| @@ -570,6 +566,31 @@ pub fn wake_task(task: TaskRef) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Wake a task by `TaskRef` without calling pend. | ||||
| /// | ||||
| /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. | ||||
| pub fn wake_task_no_pend(task: TaskRef) { | ||||
|     let header = task.header(); | ||||
|  | ||||
|     let res = header.state.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { | ||||
|         // If already scheduled, or if not started, | ||||
|         if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { | ||||
|             None | ||||
|         } else { | ||||
|             // Mark it as scheduled | ||||
|             Some(state | STATE_RUN_QUEUED) | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     if res.is_ok() { | ||||
|         // We have just marked the task as scheduled, so enqueue it. | ||||
|         unsafe { | ||||
|             let executor = header.executor.get().unwrap_unchecked(); | ||||
|             executor.run_queue.enqueue(task); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "integrated-timers")] | ||||
| struct TimerQueue; | ||||
|  | ||||
|   | ||||
| @@ -4,15 +4,16 @@ use core::ptr::NonNull; | ||||
| use atomic_polyfill::{AtomicPtr, Ordering}; | ||||
|  | ||||
| use super::{TaskHeader, TaskRef}; | ||||
| use crate::raw::util::SyncUnsafeCell; | ||||
|  | ||||
| pub(crate) struct RunQueueItem { | ||||
|     next: AtomicPtr<TaskHeader>, | ||||
|     next: SyncUnsafeCell<Option<TaskRef>>, | ||||
| } | ||||
|  | ||||
| impl RunQueueItem { | ||||
|     pub const fn new() -> Self { | ||||
|         Self { | ||||
|             next: AtomicPtr::new(ptr::null_mut()), | ||||
|             next: SyncUnsafeCell::new(None), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -51,7 +52,12 @@ impl RunQueue { | ||||
|         self.head | ||||
|             .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| { | ||||
|                 was_empty = prev.is_null(); | ||||
|                 task.header().run_queue_item.next.store(prev, Ordering::Relaxed); | ||||
|                 unsafe { | ||||
|                     // safety: the pointer is either null or valid | ||||
|                     let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())); | ||||
|                     // safety: there are no concurrent accesses to `next` | ||||
|                     task.header().run_queue_item.next.set(prev); | ||||
|                 } | ||||
|                 Some(task.as_ptr() as *mut _) | ||||
|             }) | ||||
|             .ok(); | ||||
| @@ -64,18 +70,19 @@ impl RunQueue { | ||||
|     /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. | ||||
|     pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { | ||||
|         // Atomically empty the queue. | ||||
|         let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); | ||||
|         let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); | ||||
|  | ||||
|         // safety: the pointer is either null or valid | ||||
|         let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) }; | ||||
|  | ||||
|         // Iterate the linked list of tasks that were previously in the queue. | ||||
|         while let Some(task) = NonNull::new(ptr) { | ||||
|             let task = unsafe { TaskRef::from_ptr(task.as_ptr()) }; | ||||
|         while let Some(task) = next { | ||||
|             // If the task re-enqueues itself, the `next` pointer will get overwritten. | ||||
|             // Therefore, first read the next pointer, and only then process the task. | ||||
|             let next = task.header().run_queue_item.next.load(Ordering::Relaxed); | ||||
|             // safety: there are no concurrent accesses to `next` | ||||
|             next = unsafe { task.header().run_queue_item.next.get() }; | ||||
|  | ||||
|             on_task(task); | ||||
|  | ||||
|             ptr = next | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -33,7 +33,8 @@ impl<S> SpawnToken<S> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_failed() -> Self { | ||||
|     /// Return a SpawnToken that represents a failed spawn. | ||||
|     pub fn new_failed() -> Self { | ||||
|         Self { | ||||
|             raw_task: None, | ||||
|             phantom: PhantomData, | ||||
|   | ||||
| @@ -31,3 +31,15 @@ pub fn block_on<F: Future>(mut fut: F) -> F::Output { | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Poll a future once. | ||||
| pub fn poll_once<F: Future>(mut fut: F) -> Poll<F::Output> { | ||||
|     // safety: we don't move the future after this line. | ||||
|     let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; | ||||
|  | ||||
|     let raw_waker = RawWaker::new(ptr::null(), &VTABLE); | ||||
|     let waker = unsafe { Waker::from_raw(raw_waker) }; | ||||
|     let mut cx = Context::from_waker(&waker); | ||||
|  | ||||
|     fut.as_mut().poll(&mut cx) | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
|  | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
|  | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
|  | ||||
| @@ -81,14 +83,17 @@ macro_rules! todo { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::unreachable!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::unreachable!($($x)*); | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -195,9 +200,6 @@ macro_rules! unwrap { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt-timestamp-uptime")] | ||||
| defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() } | ||||
|  | ||||
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||||
| pub struct NoneError; | ||||
|  | ||||
| @@ -226,3 +228,31 @@ impl<T, E> Try for Result<T, E> { | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(unused)] | ||||
| pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||
|  | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,13 +0,0 @@ | ||||
| [package] | ||||
| name = "embassy-hal-common" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| license = "MIT OR Apache-2.0" | ||||
|  | ||||
| [features] | ||||
|  | ||||
| [dependencies] | ||||
| defmt = { version = "0.3", optional = true } | ||||
| log = { version = "0.4.14", optional = true } | ||||
|  | ||||
| num-traits = { version = "0.2.14", default-features = false } | ||||
							
								
								
									
										29
									
								
								embassy-hal-internal/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								embassy-hal-internal/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| [package] | ||||
| name = "embassy-hal-internal" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| license = "MIT OR Apache-2.0" | ||||
|  | ||||
| [features] | ||||
|  | ||||
| # Define the number of NVIC priority bits. | ||||
| prio-bits-0 = [] | ||||
| prio-bits-1 = [] | ||||
| prio-bits-2 = [] | ||||
| prio-bits-3 = [] | ||||
| prio-bits-4 = [] | ||||
| prio-bits-5 = [] | ||||
| prio-bits-6 = [] | ||||
| prio-bits-7 = [] | ||||
| prio-bits-8 = [] | ||||
|  | ||||
| cortex-m = ["dep:cortex-m", "dep:critical-section"] | ||||
|  | ||||
| [dependencies] | ||||
| defmt = { version = "0.3", optional = true } | ||||
| log = { version = "0.4.14", optional = true } | ||||
|  | ||||
| num-traits = { version = "0.2.14", default-features = false } | ||||
|  | ||||
| cortex-m = { version = "0.7.6", optional = true } | ||||
| critical-section = { version = "1", optional = true } | ||||
							
								
								
									
										16
									
								
								embassy-hal-internal/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								embassy-hal-internal/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| # embassy-macros | ||||
|  | ||||
| An [Embassy](https://embassy.dev) project. | ||||
|  | ||||
| Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY. Embassy HALs (`embassy-nrf`, `embassy-stm32`, `embassy-rp`) already reexport | ||||
| everything you need to use them effectively. | ||||
|  | ||||
| ## License | ||||
|  | ||||
| This work is licensed under either of | ||||
|  | ||||
| - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||||
|   <http://www.apache.org/licenses/LICENSE-2.0>) | ||||
| - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||||
|  | ||||
| at your option. | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user