Compare commits
	
		
			754 Commits
		
	
	
		
			embassy-ne
			...
			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 | ||
|  | 2432cece38 | ||
|  | fef338f5c2 | ||
|  | 24e186e684 | ||
|  | 71afa40a69 | ||
|  | 89fbb02979 | ||
|  | 76a334bd7c | ||
|  | f47a148f51 | ||
|  | 5ecf9ec7bc | ||
|  | d896f80405 | ||
|  | 2900ab79e7 | ||
|  | 14eae9ca06 | ||
|  | 64154fec8c | ||
|  | ed97e61dbe | ||
|  | 029713eca0 | ||
|  | 3d26573c6b | ||
|  | 0963b5f92c | ||
|  | 530f192acc | ||
|  | a46f33b214 | 
							
								
								
									
										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 |  | ||||||
							
								
								
									
										1
									
								
								.github/ci/build.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/ci/build.sh
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,7 @@ if [ -f /ci/secrets/teleprobe-token.txt ]; then | |||||||
|     echo Got teleprobe token! |     echo Got teleprobe token! | ||||||
|     export TELEPROBE_HOST=https://teleprobe.embassy.dev |     export TELEPROBE_HOST=https://teleprobe.embassy.dev | ||||||
|     export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt) |     export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt) | ||||||
|  |     export TELEPROBE_CACHE=/ci/cache/teleprobe_cache.json | ||||||
| fi | fi | ||||||
|  |  | ||||||
| hashtime restore /ci/cache/filetime.json || true | hashtime restore /ci/cache/filetime.json || true | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
							
								
								
									
										61
									
								
								.github/ci/doc.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										61
									
								
								.github/ci/doc.sh
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ set -euo pipefail | |||||||
| export RUSTUP_HOME=/ci/cache/rustup | export RUSTUP_HOME=/ci/cache/rustup | ||||||
| export CARGO_HOME=/ci/cache/cargo | export CARGO_HOME=/ci/cache/cargo | ||||||
| export CARGO_TARGET_DIR=/ci/cache/target | export CARGO_TARGET_DIR=/ci/cache/target | ||||||
| export BUILDER_THREADS=6 | export BUILDER_THREADS=4 | ||||||
| export BUILDER_COMPRESS=true | export BUILDER_COMPRESS=true | ||||||
|  |  | ||||||
| # force rustup to download the toolchain before starting building. | # force rustup to download the toolchain before starting building. | ||||||
| @@ -15,30 +15,41 @@ export BUILDER_COMPRESS=true | |||||||
| # which makes rustup very sad | # which makes rustup very sad | ||||||
| rustc --version > /dev/null | rustc --version > /dev/null | ||||||
|  |  | ||||||
| docserver-builder -i ./embassy-stm32 -o crates/embassy-stm32/git.zup | docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup | ||||||
| docserver-builder -i ./embassy-boot/boot -o crates/embassy-boot/git.zup | docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup | ||||||
| docserver-builder -i ./embassy-boot/nrf -o 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/rp -o crates/embassy-boot-rp/git.zup | docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup | ||||||
| docserver-builder -i ./embassy-boot/stm32 -o crates/embassy-boot-stm32/git.zup | docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup | ||||||
| docserver-builder -i ./embassy-embedded-hal -o crates/embassy-embedded-hal/git.zup | docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup | ||||||
| docserver-builder -i ./embassy-executor -o crates/embassy-executor/git.zup | docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup | ||||||
| docserver-builder -i ./embassy-futures -o crates/embassy-futures/git.zup | docserver-builder -i ./embassy-lora -o webroot/crates/embassy-lora/git.zup | ||||||
| docserver-builder -i ./embassy-lora -o crates/embassy-lora/git.zup | docserver-builder -i ./embassy-net -o webroot/crates/embassy-net/git.zup | ||||||
| docserver-builder -i ./embassy-net -o 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 -o 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-net-driver-channel -o crates/embassy-net-driver-channel/git.zup | docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup | ||||||
| docserver-builder -i ./embassy-nrf -o crates/embassy-nrf/git.zup | docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup | ||||||
| docserver-builder -i ./embassy-rp -o crates/embassy-rp/git.zup | docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup | ||||||
| docserver-builder -i ./embassy-sync -o crates/embassy-sync/git.zup | docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup | ||||||
| docserver-builder -i ./embassy-time -o crates/embassy-time/git.zup | docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup | ||||||
| docserver-builder -i ./embassy-usb -o crates/embassy-usb/git.zup | docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/git.zup | ||||||
| docserver-builder -i ./embassy-usb-driver -o crates/embassy-usb-driver/git.zup | docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup | ||||||
| docserver-builder -i ./embassy-usb-logger -o crates/embassy-usb-logger/git.zup | docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup | ||||||
| docserver-builder -i ./cyw43 -o crates/cyw43/git.zup | docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup | ||||||
| docserver-builder -i ./cyw43-pio -o crates/cyw43-pio/git.zup | docserver-builder -i ./embassy-net-wiznet -o webroot/crates/embassy-net-wiznet/git.zup | ||||||
| docserver-builder -i ./embassy-net-w5500 -o crates/embassy-net-w5500/git.zup | docserver-builder -i ./embassy-net-enc28j60 -o webroot/crates/embassy-net-enc28j60/git.zup | ||||||
| docserver-builder -i ./embassy-stm32-wpan -o crates/embassy-stm32-wpan/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 | export KUBECONFIG=/ci/secrets/kubeconfig.yml | ||||||
| POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) | POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) | ||||||
| kubectl cp crates $POD:/data | 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 | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/ci/test.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/ci/test.sh
									
									
									
									
										vendored
									
									
								
							| @@ -13,7 +13,7 @@ hashtime save /ci/cache/filetime.json | |||||||
|  |  | ||||||
| cargo test --manifest-path ./embassy-sync/Cargo.toml  | cargo test --manifest-path ./embassy-sync/Cargo.toml  | ||||||
| cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml  | cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml  | ||||||
| cargo test --manifest-path ./embassy-hal-common/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-time/Cargo.toml --features generic-queue | ||||||
|  |  | ||||||
| cargo test --manifest-path ./embassy-boot/boot/Cargo.toml | cargo test --manifest-path ./embassy-boot/boot/Cargo.toml | ||||||
| @@ -28,3 +28,5 @@ cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --featu | |||||||
| 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,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,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-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti | ||||||
|  |  | ||||||
|  | cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -6,16 +6,21 @@ | |||||||
|   "rust-analyzer.check.allTargets": false, |   "rust-analyzer.check.allTargets": false, | ||||||
|   "rust-analyzer.check.noDefaultFeatures": true, |   "rust-analyzer.check.noDefaultFeatures": true, | ||||||
|   "rust-analyzer.cargo.noDefaultFeatures": true, |   "rust-analyzer.cargo.noDefaultFeatures": true, | ||||||
|   "rust-analyzer.cargo.target": "thumbv7m-none-eabi", |   "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.target": "thumbv8m.main-none-eabihf", | ||||||
|   "rust-analyzer.cargo.features": [ |   "rust-analyzer.cargo.features": [ | ||||||
|     ///"nightly", |     // Uncomment if the example has a "nightly" feature. | ||||||
|  |     "nightly", | ||||||
|   ], |   ], | ||||||
|   "rust-analyzer.linkedProjects": [ |   "rust-analyzer.linkedProjects": [ | ||||||
|     // Declare for the target you wish to develop |     // Uncomment ONE line for the chip you want to work on. | ||||||
|     // "embassy-executor/Cargo.toml", |     // This makes rust-analyzer work on the example crate and all its dependencies. | ||||||
|     // "embassy-sync/Cargo.toml", |     "examples/nrf52840/Cargo.toml", | ||||||
|     "examples/stm32wl/Cargo.toml", |     // "examples/nrf52840-rtic/Cargo.toml", | ||||||
|     // "examples/nrf5340/Cargo.toml", |     // "examples/nrf5340/Cargo.toml", | ||||||
|     // "examples/nrf-rtos-trace/Cargo.toml", |     // "examples/nrf-rtos-trace/Cargo.toml", | ||||||
|     // "examples/rp/Cargo.toml", |     // "examples/rp/Cargo.toml", | ||||||
| @@ -25,6 +30,7 @@ | |||||||
|     // "examples/stm32f1/Cargo.toml", |     // "examples/stm32f1/Cargo.toml", | ||||||
|     // "examples/stm32f2/Cargo.toml", |     // "examples/stm32f2/Cargo.toml", | ||||||
|     // "examples/stm32f3/Cargo.toml", |     // "examples/stm32f3/Cargo.toml", | ||||||
|  |     // "examples/stm32f334/Cargo.toml", | ||||||
|     // "examples/stm32f4/Cargo.toml", |     // "examples/stm32f4/Cargo.toml", | ||||||
|     // "examples/stm32f7/Cargo.toml", |     // "examples/stm32f7/Cargo.toml", | ||||||
|     // "examples/stm32g0/Cargo.toml", |     // "examples/stm32g0/Cargo.toml", | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @@ -33,6 +33,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac | |||||||
|  |  | ||||||
| - **Bluetooth** -  | - **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/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** -  | - **LoRa** -  | ||||||
| <a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking. | <a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking. | ||||||
| @@ -111,6 +112,12 @@ cargo install probe-rs --features cli | |||||||
| cd examples/nrf52840 | 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 | - Run the example | ||||||
|  |  | ||||||
| For example: | For example: | ||||||
| @@ -119,6 +126,8 @@ For example: | |||||||
| cargo run --release --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 | ## 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/) | The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/) | ||||||
| @@ -151,3 +160,5 @@ This work is licensed under either of | |||||||
|  |  | ||||||
| at your option. | at your option. | ||||||
|  |  | ||||||
|  | [1]: https://github.com/embassy-rs/embassy/wiki/Getting-Started | ||||||
|  | [2]: https://github.com/embassy-rs/embassy/wiki/Running-the-Examples | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								ci.sh
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								ci.sh
									
									
									
									
									
								
							| @@ -3,11 +3,7 @@ | |||||||
| set -euo pipefail | set -euo pipefail | ||||||
|  |  | ||||||
| export RUSTFLAGS=-Dwarnings | export RUSTFLAGS=-Dwarnings | ||||||
| export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info | export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info | ||||||
|  |  | ||||||
| # needed by wifi examples |  | ||||||
| export WIFI_NETWORK=x |  | ||||||
| export WIFI_PASSWORD=x |  | ||||||
|  |  | ||||||
| TARGET=$(rustc -vV | sed -n 's|host: ||p') | TARGET=$(rustc -vV | sed -n 's|host: ||p') | ||||||
|  |  | ||||||
| @@ -23,21 +19,33 @@ cargo batch  \ | |||||||
|     --- 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,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 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 \ | ||||||
|  |     --- 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-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,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 \ | ||||||
|     --- 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,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-ethernet \ | ||||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \ |     --- 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-ipv6,medium-ethernet,nightly \ | ||||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits,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 \ | ||||||
|     --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,unstable-traits \ |  | ||||||
|     --- 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-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,unstable-traits,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,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,nrf52810,gpiote,time-driver-rtc1 \ | ||||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ |     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ | ||||||
| @@ -57,6 +65,7 @@ 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,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 \ | ||||||
|     --- 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,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,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,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 \ | ||||||
| @@ -85,11 +94,13 @@ cargo batch  \ | |||||||
|     --- 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 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,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,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 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 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 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 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,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,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,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,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \ | ||||||
| @@ -119,6 +130,7 @@ cargo batch  \ | |||||||
|     --- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \ |     --- 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/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/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/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/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 \ |     --- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \ | ||||||
| @@ -143,7 +155,7 @@ cargo batch  \ | |||||||
|     --- 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/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/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/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 \ |     --- 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/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/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/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ | ||||||
| @@ -159,6 +171,7 @@ cargo batch  \ | |||||||
|     --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \ |     --- 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/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/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 --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ | ||||||
|     $BUILD_EXTRA |     $BUILD_EXTRA | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,9 +16,7 @@ cargo batch  \ | |||||||
|     --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-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,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 \ | ||||||
|     --- 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-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-ethernet,unstable-traits \ |  | ||||||
|     --- 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 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 nrf52810,gpiote,time-driver-rtc1 \ | ||||||
|     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \ |     --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \ | ||||||
| @@ -36,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,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 --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 \ | ||||||
|  |     --- 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 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 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 \ |     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \ | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -2,4 +2,8 @@ | |||||||
|  |  | ||||||
| Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439 | 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) | 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 | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ use cyw43::SpiBusCyw43; | |||||||
| use embassy_rp::dma::Channel; | use embassy_rp::dma::Channel; | ||||||
| use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate}; | 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::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; | ||||||
| use embassy_rp::relocate::RelocatedProgram; |  | ||||||
| use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef}; | use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef}; | ||||||
| use fixed::FixedU32; | use fixed::FixedU32; | ||||||
| use pio_proc::pio_asm; | use pio_proc::pio_asm; | ||||||
| @@ -88,8 +87,6 @@ where | |||||||
|             ".wrap" |             ".wrap" | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         let relocated = RelocatedProgram::new(&program.program); |  | ||||||
|  |  | ||||||
|         let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio); |         let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio); | ||||||
|         pin_io.set_pull(Pull::None); |         pin_io.set_pull(Pull::None); | ||||||
|         pin_io.set_schmitt(true); |         pin_io.set_schmitt(true); | ||||||
| @@ -102,7 +99,8 @@ where | |||||||
|         pin_clk.set_slew_rate(SlewRate::Fast); |         pin_clk.set_slew_rate(SlewRate::Fast); | ||||||
|  |  | ||||||
|         let mut cfg = Config::default(); |         let mut cfg = Config::default(); | ||||||
|         cfg.use_program(&common.load_program(&relocated), &[&pin_clk]); |         let loaded_program = common.load_program(&program.program); | ||||||
|  |         cfg.use_program(&loaded_program, &[&pin_clk]); | ||||||
|         cfg.set_out_pins(&[&pin_io]); |         cfg.set_out_pins(&[&pin_io]); | ||||||
|         cfg.set_in_pins(&[&pin_io]); |         cfg.set_in_pins(&[&pin_io]); | ||||||
|         cfg.set_set_pins(&[&pin_io]); |         cfg.set_set_pins(&[&pin_io]); | ||||||
| @@ -142,7 +140,7 @@ where | |||||||
|             sm, |             sm, | ||||||
|             irq, |             irq, | ||||||
|             dma: dma.into_ref(), |             dma: dma.into_ref(), | ||||||
|             wrap_target: relocated.wrap().target, |             wrap_target: loaded_program.wrap.target, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ log = ["dep:log"] | |||||||
| firmware-logs = [] | firmware-logs = [] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| embassy-time = { version = "0.1.0", path = "../embassy-time"} | embassy-time = { version = "0.1.3", path = "../embassy-time"} | ||||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync"} | embassy-sync = { version = "0.2.0", path = "../embassy-sync"} | ||||||
| embassy-futures = { version = "0.1.0", path = "../embassy-futures"} | embassy-futures = { version = "0.1.0", path = "../embassy-futures"} | ||||||
| embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} | embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} | ||||||
| @@ -24,11 +24,11 @@ cortex-m = "0.7.6" | |||||||
| cortex-m-rt = "0.7.0" | cortex-m-rt = "0.7.0" | ||||||
| futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } | 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-alpha.10" } | embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-rc.1" } | ||||||
| num_enum = { version = "0.5.7", default-features = false } | num_enum = { version = "0.5.7", default-features = false } | ||||||
|  |  | ||||||
| [package.metadata.embassy_docs] | [package.metadata.embassy_docs] | ||||||
| src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" | 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/" | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" | ||||||
| target = "thumbv6m-none-eabi" | target = "thumbv6m-none-eabi" | ||||||
| features = ["defmt", "firmware-logs"] | features = ["defmt", "firmware-logs"] | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ TODO: | |||||||
| ### Example 2: Create an access point (IP and credentials in the code) | ### Example 2: Create an access point (IP and credentials in the code) | ||||||
| - `cargo run --release --bin wifi_ap_tcp_server` | - `cargo run --release --bin wifi_ap_tcp_server` | ||||||
| ### Example 3: Connect to an existing network and create a server | ### Example 3: Connect to an existing network and create a server | ||||||
| - `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release --bin wifi_tcp_server` | - `cargo run --release --bin wifi_tcp_server` | ||||||
|  |  | ||||||
| After a few seconds, you should see that DHCP picks up an IP address like this | After a few seconds, you should see that DHCP picks up an IP address like this | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ where | |||||||
|         cmd_buf[0] = cmd; |         cmd_buf[0] = cmd; | ||||||
|         cmd_buf[1..][..buf.len()].copy_from_slice(buf); |         cmd_buf[1..][..buf.len()].copy_from_slice(buf); | ||||||
|  |  | ||||||
|         self.status = self.spi.cmd_write(&cmd_buf).await; |         self.status = self.spi.cmd_write(&cmd_buf[..buf.len() + 1]).await; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[allow(unused)] |     #[allow(unused)] | ||||||
|   | |||||||
| @@ -96,6 +96,7 @@ pub(crate) const IOCTL_CMD_UP: u32 = 2; | |||||||
| pub(crate) const IOCTL_CMD_DOWN: u32 = 3; | pub(crate) const IOCTL_CMD_DOWN: u32 = 3; | ||||||
| pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; | pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; | ||||||
| pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; | 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_ANTDIV: u32 = 64; | ||||||
| pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; | pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; | ||||||
| pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; | pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; | ||||||
|   | |||||||
| @@ -124,7 +124,7 @@ impl<'a> Control<'a> { | |||||||
|         Timer::after(Duration::from_millis(100)).await; |         Timer::after(Duration::from_millis(100)).await; | ||||||
|  |  | ||||||
|         // set wifi up |         // set wifi up | ||||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; |         self.up().await; | ||||||
|  |  | ||||||
|         Timer::after(Duration::from_millis(100)).await; |         Timer::after(Duration::from_millis(100)).await; | ||||||
|  |  | ||||||
| @@ -138,6 +138,16 @@ impl<'a> Control<'a> { | |||||||
|         debug!("INIT DONE"); |         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) { |     pub async fn set_power_management(&mut self, mode: PowerManagementMode) { | ||||||
|         // power save mode |         // power save mode | ||||||
|         let mode_num = mode.mode(); |         let mode_num = mode.mode(); | ||||||
| @@ -256,13 +266,13 @@ impl<'a> Control<'a> { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Temporarily set wifi down |         // Temporarily set wifi down | ||||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; |         self.down().await; | ||||||
|  |  | ||||||
|         // Turn off APSTA mode |         // Turn off APSTA mode | ||||||
|         self.set_iovar_u32("apsta", 0).await; |         self.set_iovar_u32("apsta", 0).await; | ||||||
|  |  | ||||||
|         // Set wifi up again |         // Set wifi up again | ||||||
|         self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; |         self.up().await; | ||||||
|  |  | ||||||
|         // Turn on AP mode |         // Turn on AP mode | ||||||
|         self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; |         self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; | ||||||
| @@ -423,6 +433,11 @@ impl<'a> Control<'a> { | |||||||
|             events: &self.events, |             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> { | pub struct Scanner<'a> { | ||||||
|   | |||||||
| @@ -83,14 +83,17 @@ macro_rules! todo { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(not(feature = "defmt"))] | ||||||
| macro_rules! unreachable { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::unreachable!($($x)*); | #[cfg(feature = "defmt")] | ||||||
|         } | macro_rules! unreachable { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         ::defmt::unreachable!($($x)*) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -226,7 +229,8 @@ impl<T, E> Try for Result<T, E> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct Bytes<'a>(pub &'a [u8]); | #[allow(unused)] | ||||||
|  | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||||
|  |  | ||||||
| impl<'a> Debug for Bytes<'a> { | impl<'a> Debug for Bytes<'a> { | ||||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ use ioctl::IoctlState; | |||||||
|  |  | ||||||
| use crate::bus::Bus; | use crate::bus::Bus; | ||||||
| pub use crate::bus::SpiBusCyw43; | pub use crate::bus::SpiBusCyw43; | ||||||
| pub use crate::control::{Control, Error as ControlError}; | pub use crate::control::{Control, Error as ControlError, Scanner}; | ||||||
| pub use crate::runner::Runner; | pub use crate::runner::Runner; | ||||||
| pub use crate::structs::BssInfo; | pub use crate::structs::BssInfo; | ||||||
|  |  | ||||||
| @@ -216,7 +216,7 @@ where | |||||||
|     PWR: OutputPin, |     PWR: OutputPin, | ||||||
|     SPI: SpiBusCyw43, |     SPI: SpiBusCyw43, | ||||||
| { | { | ||||||
|     let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); |     let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); | ||||||
|     let state_ch = ch_runner.state_runner(); |     let state_ch = ch_runner.state_runner(); | ||||||
|  |  | ||||||
|     let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); |     let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); | ||||||
|   | |||||||
| @@ -345,7 +345,9 @@ where | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn rx(&mut self, packet: &mut [u8]) { |     fn rx(&mut self, packet: &mut [u8]) { | ||||||
|         let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { return }; |         let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { | ||||||
|  |             return; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         self.update_credit(&sdpcm_header); |         self.update_credit(&sdpcm_header); | ||||||
|  |  | ||||||
| @@ -353,7 +355,9 @@ where | |||||||
|  |  | ||||||
|         match channel { |         match channel { | ||||||
|             CHANNEL_TYPE_CONTROL => { |             CHANNEL_TYPE_CONTROL => { | ||||||
|                 let Some((cdc_header, response)) = CdcHeader::parse(payload) else { return; }; |                 let Some((cdc_header, response)) = CdcHeader::parse(payload) else { | ||||||
|  |                     return; | ||||||
|  |                 }; | ||||||
|                 trace!("    {:?}", cdc_header); |                 trace!("    {:?}", cdc_header); | ||||||
|  |  | ||||||
|                 if cdc_header.id == self.ioctl_id { |                 if cdc_header.id == self.ioctl_id { | ||||||
| @@ -417,8 +421,12 @@ where | |||||||
|                     let status = event_packet.msg.status; |                     let status = event_packet.msg.status; | ||||||
|                     let event_payload = match evt_type { |                     let event_payload = match evt_type { | ||||||
|                         Event::ESCAN_RESULT if status == EStatus::PARTIAL => { |                         Event::ESCAN_RESULT if status == EStatus::PARTIAL => { | ||||||
|                             let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return }; |                             let Some((_, bss_info)) = ScanResults::parse(evt_data) else { | ||||||
|                             let Some(bss_info) = BssInfo::parse(bss_info) else { return }; |                                 return; | ||||||
|  |                             }; | ||||||
|  |                             let Some(bss_info) = BssInfo::parse(bss_info) else { | ||||||
|  |                                 return; | ||||||
|  |                             }; | ||||||
|                             events::Payload::BssInfo(*bss_info) |                             events::Payload::BssInfo(*bss_info) | ||||||
|                         } |                         } | ||||||
|                         Event::ESCAN_RESULT => events::Payload::None, |                         Event::ESCAN_RESULT => events::Payload::None, | ||||||
| @@ -439,7 +447,9 @@ where | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             CHANNEL_TYPE_DATA => { |             CHANNEL_TYPE_DATA => { | ||||||
|                 let Some((_, packet)) = BdcHeader::parse(payload) else { return }; |                 let Some((_, packet)) = BdcHeader::parse(payload) else { | ||||||
|  |                     return; | ||||||
|  |                 }; | ||||||
|                 trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); |                 trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); | ||||||
|  |  | ||||||
|                 match self.ch.try_rx_buf() { |                 match self.ch.try_rx_buf() { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ version = "0.1.0" | |||||||
| license = "MIT OR Apache-2.0" | license = "MIT OR Apache-2.0" | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| embassy-executor = { version = "0.2.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-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"] } | embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" | |||||||
| cortex-m = "0.7" | cortex-m = "0.7" | ||||||
| cortex-m-rt = "0.7" | cortex-m-rt = "0.7" | ||||||
| embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"]  } | embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"]  } | ||||||
| embassy-executor = { version = "0.2.0", features = ["nightly", "arch-cortex-m", "executor-thread"] } | embassy-executor = { version = "0.3.0", features = ["nightly", "arch-cortex-m", "executor-thread"] } | ||||||
|  |  | ||||||
| defmt = "0.3.0" | defmt = "0.3.0" | ||||||
| defmt-rtt = "0.3.0" | defmt-rtt = "0.3.0" | ||||||
|   | |||||||
| @@ -10,9 +10,9 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAG | |||||||
|  |  | ||||||
| /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||||||
| /// 'mess up' the internal bootloader state | /// 'mess up' the internal bootloader state | ||||||
| pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> { | pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | ||||||
|     dfu: DFU, |     dfu: DFU, | ||||||
|     state: STATE, |     state: FirmwareState<'d, STATE>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(target_os = "none")] | #[cfg(target_os = "none")] | ||||||
| @@ -47,22 +47,12 @@ impl<'a, FLASH: NorFlash> | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<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. |     /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||||||
|     pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self { |     pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             dfu: config.dfu, |             dfu: config.dfu, | ||||||
|             state: config.state, |             state: FirmwareState::new(config.state, aligned), | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Make sure we are running a booted firmware to avoid reverting to a bad state. |  | ||||||
|     async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         if self.get_state(aligned).await? == State::Boot { |  | ||||||
|             Ok(()) |  | ||||||
|         } else { |  | ||||||
|             Err(FirmwareUpdaterError::BadState) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -71,14 +61,8 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|     /// This is useful to check if the bootloader has just done a swap, in order |     /// 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 |     /// to do verifications and self-tests of the new image before calling | ||||||
|     /// `mark_booted`. |     /// `mark_booted`. | ||||||
|     pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> { |     pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||||||
|         self.state.read(0, aligned).await?; |         self.state.get_state().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 |     /// Verify the DFU given a public key. If there is an error then DO NOT | ||||||
| @@ -92,23 +76,16 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|     /// |     /// | ||||||
|     /// If no signature feature is set then this method will always return a |     /// If no signature feature is set then this method will always return a | ||||||
|     /// signature error. |     /// signature error. | ||||||
|     /// |  | ||||||
|     /// # 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. |  | ||||||
|     #[cfg(feature = "_verify")] |     #[cfg(feature = "_verify")] | ||||||
|     pub async fn verify_and_mark_updated( |     pub async fn verify_and_mark_updated( | ||||||
|         &mut self, |         &mut self, | ||||||
|         _public_key: &[u8], |         _public_key: &[u8], | ||||||
|         _signature: &[u8], |         _signature: &[u8], | ||||||
|         _update_len: u32, |         _update_len: u32, | ||||||
|         _aligned: &mut [u8], |  | ||||||
|     ) -> Result<(), FirmwareUpdaterError> { |     ) -> Result<(), FirmwareUpdaterError> { | ||||||
|         assert_eq!(_aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         assert!(_update_len <= self.dfu.capacity() as u32); |         assert!(_update_len <= self.dfu.capacity() as u32); | ||||||
|  |  | ||||||
|         self.verify_booted(_aligned).await?; |         self.state.verify_booted().await?; | ||||||
|  |  | ||||||
|         #[cfg(feature = "ed25519-dalek")] |         #[cfg(feature = "ed25519-dalek")] | ||||||
|         { |         { | ||||||
| @@ -121,8 +98,9 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|             let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; |             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 signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||||||
|  |  | ||||||
|  |             let mut chunk_buf = [0; 2]; | ||||||
|             let mut message = [0; 64]; |             let mut message = [0; 64]; | ||||||
|             self.hash::<Sha512>(_update_len, _aligned, &mut message).await?; |             self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||||||
|  |  | ||||||
|             public_key.verify(&message, &signature).map_err(into_signature_error)? |             public_key.verify(&message, &signature).map_err(into_signature_error)? | ||||||
|         } |         } | ||||||
| @@ -143,7 +121,8 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; |             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||||||
|  |  | ||||||
|             let mut message = [0; 64]; |             let mut message = [0; 64]; | ||||||
|             self.hash::<Sha512>(_update_len, _aligned, &mut message).await?; |             let mut chunk_buf = [0; 2]; | ||||||
|  |             self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||||||
|  |  | ||||||
|             let r = public_key.verify(&message, &signature); |             let r = public_key.verify(&message, &signature); | ||||||
|             trace!( |             trace!( | ||||||
| @@ -156,7 +135,7 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|             r.map_err(into_signature_error)? |             r.map_err(into_signature_error)? | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.set_magic(_aligned, SWAP_MAGIC).await |         self.state.mark_updated().await | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Verify the update in DFU with any digest. |     /// Verify the update in DFU with any digest. | ||||||
| @@ -177,49 +156,14 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Mark to trigger firmware swap on next boot. |     /// Mark to trigger firmware swap on next boot. | ||||||
|     /// |  | ||||||
|     /// # Safety |  | ||||||
|     /// |  | ||||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |  | ||||||
|     #[cfg(not(feature = "_verify"))] |     #[cfg(not(feature = "_verify"))] | ||||||
|     pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |     pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |         self.state.mark_updated().await | ||||||
|         self.set_magic(aligned, SWAP_MAGIC).await |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Mark firmware boot successful and stop rollback on reset. |     /// Mark firmware boot successful and stop rollback on reset. | ||||||
|     /// |     pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||||
|     /// # Safety |         self.state.mark_booted().await | ||||||
|     /// |  | ||||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |  | ||||||
|     pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         self.set_magic(aligned, BOOT_MAGIC).await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         self.state.read(0, aligned).await?; |  | ||||||
|  |  | ||||||
|         if aligned.iter().any(|&b| b != magic) { |  | ||||||
|             // Read progress validity |  | ||||||
|             self.state.read(STATE::WRITE_SIZE as u32, aligned).await?; |  | ||||||
|  |  | ||||||
|             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::WRITE_SIZE as u32, aligned).await?; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Clear magic and progress |  | ||||||
|             self.state.erase(0, self.state.capacity() as u32).await?; |  | ||||||
|  |  | ||||||
|             // Set magic |  | ||||||
|             aligned.fill(magic); |  | ||||||
|             self.state.write(0, aligned).await?; |  | ||||||
|         } |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Write data to a flash page. |     /// Write data to a flash page. | ||||||
| @@ -229,16 +173,10 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|     /// # Safety |     /// # Safety | ||||||
|     /// |     /// | ||||||
|     /// Failing to meet alignment and size requirements may result in a panic. |     /// Failing to meet alignment and size requirements may result in a panic. | ||||||
|     pub async fn write_firmware( |     pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | ||||||
|         &mut self, |  | ||||||
|         aligned: &mut [u8], |  | ||||||
|         offset: usize, |  | ||||||
|         data: &[u8], |  | ||||||
|     ) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         assert!(data.len() >= DFU::ERASE_SIZE); |         assert!(data.len() >= DFU::ERASE_SIZE); | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|  |  | ||||||
|         self.verify_booted(aligned).await?; |         self.state.verify_booted().await?; | ||||||
|  |  | ||||||
|         self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; |         self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; | ||||||
|  |  | ||||||
| @@ -252,20 +190,94 @@ impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | |||||||
|     /// |     /// | ||||||
|     /// Using this instead of `write_firmware` allows for an optimized API in |     /// Using this instead of `write_firmware` allows for an optimized API in | ||||||
|     /// exchange for added complexity. |     /// exchange for added complexity. | ||||||
|     /// |     pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { | ||||||
|     /// # Safety |         self.state.verify_booted().await?; | ||||||
|     /// |  | ||||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |  | ||||||
|     pub async fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { |  | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         self.verify_booted(aligned).await?; |  | ||||||
|  |  | ||||||
|         self.dfu.erase(0, self.dfu.capacity() as u32).await?; |         self.dfu.erase(0, self.dfu.capacity() as u32).await?; | ||||||
|  |  | ||||||
|         Ok(&mut self.dfu) |         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)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use embassy_embedded_hal::flash::partition::Partition; |     use embassy_embedded_hal::flash::partition::Partition; | ||||||
| @@ -288,8 +300,8 @@ mod tests { | |||||||
|         let mut to_write = [0; 4096]; |         let mut to_write = [0; 4096]; | ||||||
|         to_write[..7].copy_from_slice(update.as_slice()); |         to_write[..7].copy_from_slice(update.as_slice()); | ||||||
|  |  | ||||||
|         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); |         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||||||
|         block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap(); |         block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); | ||||||
|         let mut chunk_buf = [0; 2]; |         let mut chunk_buf = [0; 2]; | ||||||
|         let mut hash = [0; 20]; |         let mut hash = [0; 20]; | ||||||
|         block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); |         block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||||||
|   | |||||||
| @@ -10,9 +10,9 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAG | |||||||
|  |  | ||||||
| /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||||||
| /// 'mess up' the internal bootloader state | /// 'mess up' the internal bootloader state | ||||||
| pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> { | pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | ||||||
|     dfu: DFU, |     dfu: DFU, | ||||||
|     state: STATE, |     state: BlockingFirmwareState<'d, STATE>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(target_os = "none")] | #[cfg(target_os = "none")] | ||||||
| @@ -49,22 +49,17 @@ impl<'a, FLASH: NorFlash> | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<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. |     /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||||||
|     pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self { |     /// | ||||||
|  |     /// # 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 { |         Self { | ||||||
|             dfu: config.dfu, |             dfu: config.dfu, | ||||||
|             state: config.state, |             state: BlockingFirmwareState::new(config.state, aligned), | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Make sure we are running a booted firmware to avoid reverting to a bad state. |  | ||||||
|     fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         if self.get_state(aligned)? == State::Boot { |  | ||||||
|             Ok(()) |  | ||||||
|         } else { |  | ||||||
|             Err(FirmwareUpdaterError::BadState) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -73,14 +68,8 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|     /// This is useful to check if the bootloader has just done a swap, in order |     /// 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 |     /// to do verifications and self-tests of the new image before calling | ||||||
|     /// `mark_booted`. |     /// `mark_booted`. | ||||||
|     pub fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> { |     pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||||||
|         self.state.read(0, aligned)?; |         self.state.get_state() | ||||||
|  |  | ||||||
|         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 |     /// Verify the DFU given a public key. If there is an error then DO NOT | ||||||
| @@ -94,23 +83,16 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|     /// |     /// | ||||||
|     /// If no signature feature is set then this method will always return a |     /// If no signature feature is set then this method will always return a | ||||||
|     /// signature error. |     /// signature error. | ||||||
|     /// |  | ||||||
|     /// # 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. |  | ||||||
|     #[cfg(feature = "_verify")] |     #[cfg(feature = "_verify")] | ||||||
|     pub fn verify_and_mark_updated( |     pub fn verify_and_mark_updated( | ||||||
|         &mut self, |         &mut self, | ||||||
|         _public_key: &[u8], |         _public_key: &[u8], | ||||||
|         _signature: &[u8], |         _signature: &[u8], | ||||||
|         _update_len: u32, |         _update_len: u32, | ||||||
|         _aligned: &mut [u8], |  | ||||||
|     ) -> Result<(), FirmwareUpdaterError> { |     ) -> Result<(), FirmwareUpdaterError> { | ||||||
|         assert_eq!(_aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         assert!(_update_len <= self.dfu.capacity() as u32); |         assert!(_update_len <= self.dfu.capacity() as u32); | ||||||
|  |  | ||||||
|         self.verify_booted(_aligned)?; |         self.state.verify_booted()?; | ||||||
|  |  | ||||||
|         #[cfg(feature = "ed25519-dalek")] |         #[cfg(feature = "ed25519-dalek")] | ||||||
|         { |         { | ||||||
| @@ -124,7 +106,8 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|             let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; |             let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||||||
|  |  | ||||||
|             let mut message = [0; 64]; |             let mut message = [0; 64]; | ||||||
|             self.hash::<Sha512>(_update_len, _aligned, &mut message)?; |             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)? |             public_key.verify(&message, &signature).map_err(into_signature_error)? | ||||||
|         } |         } | ||||||
| @@ -145,7 +128,8 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; |             let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||||||
|  |  | ||||||
|             let mut message = [0; 64]; |             let mut message = [0; 64]; | ||||||
|             self.hash::<Sha512>(_update_len, _aligned, &mut message)?; |             let mut chunk_buf = [0; 2]; | ||||||
|  |             self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?; | ||||||
|  |  | ||||||
|             let r = public_key.verify(&message, &signature); |             let r = public_key.verify(&message, &signature); | ||||||
|             trace!( |             trace!( | ||||||
| @@ -158,7 +142,7 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|             r.map_err(into_signature_error)? |             r.map_err(into_signature_error)? | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.set_magic(_aligned, SWAP_MAGIC) |         self.state.mark_updated() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Verify the update in DFU with any digest. |     /// Verify the update in DFU with any digest. | ||||||
| @@ -179,49 +163,14 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Mark to trigger firmware swap on next boot. |     /// Mark to trigger firmware swap on next boot. | ||||||
|     /// |  | ||||||
|     /// # Safety |  | ||||||
|     /// |  | ||||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |  | ||||||
|     #[cfg(not(feature = "_verify"))] |     #[cfg(not(feature = "_verify"))] | ||||||
|     pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |     pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |         self.state.mark_updated() | ||||||
|         self.set_magic(aligned, SWAP_MAGIC) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Mark firmware boot successful and stop rollback on reset. |     /// Mark firmware boot successful and stop rollback on reset. | ||||||
|     /// |     pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||||||
|     /// # Safety |         self.state.mark_booted() | ||||||
|     /// |  | ||||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |  | ||||||
|     pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         self.set_magic(aligned, BOOT_MAGIC) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         self.state.read(0, aligned)?; |  | ||||||
|  |  | ||||||
|         if aligned.iter().any(|&b| b != magic) { |  | ||||||
|             // Read progress validity |  | ||||||
|             self.state.read(STATE::WRITE_SIZE as u32, aligned)?; |  | ||||||
|  |  | ||||||
|             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::WRITE_SIZE as u32, aligned)?; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Clear magic and progress |  | ||||||
|             self.state.erase(0, self.state.capacity() as u32)?; |  | ||||||
|  |  | ||||||
|             // Set magic |  | ||||||
|             aligned.fill(magic); |  | ||||||
|             self.state.write(0, aligned)?; |  | ||||||
|         } |  | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Write data to a flash page. |     /// Write data to a flash page. | ||||||
| @@ -231,15 +180,9 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|     /// # Safety |     /// # Safety | ||||||
|     /// |     /// | ||||||
|     /// Failing to meet alignment and size requirements may result in a panic. |     /// Failing to meet alignment and size requirements may result in a panic. | ||||||
|     pub fn write_firmware( |     pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | ||||||
|         &mut self, |  | ||||||
|         aligned: &mut [u8], |  | ||||||
|         offset: usize, |  | ||||||
|         data: &[u8], |  | ||||||
|     ) -> Result<(), FirmwareUpdaterError> { |  | ||||||
|         assert!(data.len() >= DFU::ERASE_SIZE); |         assert!(data.len() >= DFU::ERASE_SIZE); | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |         self.state.verify_booted()?; | ||||||
|         self.verify_booted(aligned)?; |  | ||||||
|  |  | ||||||
|         self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; |         self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; | ||||||
|  |  | ||||||
| @@ -253,19 +196,94 @@ impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | |||||||
|     /// |     /// | ||||||
|     /// Using this instead of `write_firmware` allows for an optimized API in |     /// Using this instead of `write_firmware` allows for an optimized API in | ||||||
|     /// exchange for added complexity. |     /// exchange for added complexity. | ||||||
|     /// |     pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { | ||||||
|     /// # Safety |         self.state.verify_booted()?; | ||||||
|     /// |  | ||||||
|     /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |  | ||||||
|     pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { |  | ||||||
|         assert_eq!(aligned.len(), STATE::WRITE_SIZE); |  | ||||||
|         self.verify_booted(aligned)?; |  | ||||||
|         self.dfu.erase(0, self.dfu.capacity() as u32)?; |         self.dfu.erase(0, self.dfu.capacity() as u32)?; | ||||||
|  |  | ||||||
|         Ok(&mut self.dfu) |         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)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use core::cell::RefCell; |     use core::cell::RefCell; | ||||||
| @@ -283,14 +301,14 @@ mod tests { | |||||||
|         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); |         let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); | ||||||
|         let state = BlockingPartition::new(&flash, 0, 4096); |         let state = BlockingPartition::new(&flash, 0, 4096); | ||||||
|         let dfu = BlockingPartition::new(&flash, 65536, 65536); |         let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||||||
|  |         let mut aligned = [0; 8]; | ||||||
|  |  | ||||||
|         let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; |         let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||||||
|         let mut to_write = [0; 4096]; |         let mut to_write = [0; 4096]; | ||||||
|         to_write[..7].copy_from_slice(update.as_slice()); |         to_write[..7].copy_from_slice(update.as_slice()); | ||||||
|  |  | ||||||
|         let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); |         let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||||||
|         let mut aligned = [0; 8]; |         updater.write_firmware(0, to_write.as_slice()).unwrap(); | ||||||
|         updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap(); |  | ||||||
|         let mut chunk_buf = [0; 2]; |         let mut chunk_buf = [0; 2]; | ||||||
|         let mut hash = [0; 20]; |         let mut hash = [0; 20]; | ||||||
|         updater |         updater | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ mod asynch; | |||||||
| mod blocking; | mod blocking; | ||||||
|  |  | ||||||
| #[cfg(feature = "nightly")] | #[cfg(feature = "nightly")] | ||||||
| pub use asynch::FirmwareUpdater; | pub use asynch::{FirmwareState, FirmwareUpdater}; | ||||||
| pub use blocking::BlockingFirmwareUpdater; | pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; | ||||||
| use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | ||||||
|  |  | ||||||
| /// Firmware updater flash configuration holding the two flashes used by the updater | /// Firmware updater flash configuration holding the two flashes used by the updater | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -16,9 +16,11 @@ mod test_flash; | |||||||
| // TODO: Use the value provided by NorFlash when available | // TODO: Use the value provided by NorFlash when available | ||||||
| pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; | pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; | ||||||
| pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; | pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; | ||||||
|  | pub use firmware_updater::{ | ||||||
|  |     BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError, | ||||||
|  | }; | ||||||
| #[cfg(feature = "nightly")] | #[cfg(feature = "nightly")] | ||||||
| pub use firmware_updater::FirmwareUpdater; | pub use firmware_updater::{FirmwareState, FirmwareUpdater}; | ||||||
| pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; |  | ||||||
|  |  | ||||||
| pub(crate) const BOOT_MAGIC: u8 = 0xD0; | pub(crate) const BOOT_MAGIC: u8 = 0xD0; | ||||||
| pub(crate) const SWAP_MAGIC: u8 = 0xF0; | pub(crate) const SWAP_MAGIC: u8 = 0xF0; | ||||||
| @@ -118,15 +120,18 @@ mod tests { | |||||||
|         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); |         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||||||
|         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); |         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||||||
|  |  | ||||||
|         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |         let mut updater = FirmwareUpdater::new( | ||||||
|             dfu: flash.dfu(), |             FirmwareUpdaterConfig { | ||||||
|             state: flash.state(), |                 dfu: flash.dfu(), | ||||||
|         }); |                 state: flash.state(), | ||||||
|         block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); |             }, | ||||||
|         block_on(updater.mark_updated(&mut aligned)).unwrap(); |             &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. |         // Writing after marking updated is not allowed until marked as booted. | ||||||
|         let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)); |         let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); | ||||||
|         assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); |         assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); | ||||||
|  |  | ||||||
|         let flash = flash.into_blocking(); |         let flash = flash.into_blocking(); | ||||||
| @@ -158,11 +163,14 @@ mod tests { | |||||||
|  |  | ||||||
|         // Mark as booted |         // Mark as booted | ||||||
|         let flash = flash.into_async(); |         let flash = flash.into_async(); | ||||||
|         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |         let mut updater = FirmwareUpdater::new( | ||||||
|             dfu: flash.dfu(), |             FirmwareUpdaterConfig { | ||||||
|             state: flash.state(), |                 dfu: flash.dfu(), | ||||||
|         }); |                 state: flash.state(), | ||||||
|         block_on(updater.mark_booted(&mut aligned)).unwrap(); |             }, | ||||||
|  |             &mut aligned, | ||||||
|  |         ); | ||||||
|  |         block_on(updater.mark_booted()).unwrap(); | ||||||
|  |  | ||||||
|         let flash = flash.into_blocking(); |         let flash = flash.into_blocking(); | ||||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { |         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||||
| @@ -190,12 +198,15 @@ mod tests { | |||||||
|         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); |         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||||||
|         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); |         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||||||
|  |  | ||||||
|         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |         let mut updater = FirmwareUpdater::new( | ||||||
|             dfu: flash.dfu(), |             FirmwareUpdaterConfig { | ||||||
|             state: flash.state(), |                 dfu: flash.dfu(), | ||||||
|         }); |                 state: flash.state(), | ||||||
|         block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); |             }, | ||||||
|         block_on(updater.mark_updated(&mut aligned)).unwrap(); |             &mut aligned, | ||||||
|  |         ); | ||||||
|  |         block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||||||
|  |         block_on(updater.mark_updated()).unwrap(); | ||||||
|  |  | ||||||
|         let flash = flash.into_blocking(); |         let flash = flash.into_blocking(); | ||||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { |         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||||
| @@ -232,12 +243,15 @@ mod tests { | |||||||
|         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); |         block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||||||
|         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); |         block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||||||
|  |  | ||||||
|         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |         let mut updater = FirmwareUpdater::new( | ||||||
|             dfu: flash.dfu(), |             FirmwareUpdaterConfig { | ||||||
|             state: flash.state(), |                 dfu: flash.dfu(), | ||||||
|         }); |                 state: flash.state(), | ||||||
|         block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); |             }, | ||||||
|         block_on(updater.mark_updated(&mut aligned)).unwrap(); |             &mut aligned, | ||||||
|  |         ); | ||||||
|  |         block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||||||
|  |         block_on(updater.mark_updated()).unwrap(); | ||||||
|  |  | ||||||
|         let flash = flash.into_blocking(); |         let flash = flash.into_blocking(); | ||||||
|         let mut bootloader = BootLoader::new(BootLoaderConfig { |         let mut bootloader = BootLoader::new(BootLoaderConfig { | ||||||
| @@ -293,18 +307,19 @@ mod tests { | |||||||
|  |  | ||||||
|         // On with the test |         // On with the test | ||||||
|         let flash = flash.into_async(); |         let flash = flash.into_async(); | ||||||
|         let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |  | ||||||
|             dfu: flash.dfu(), |  | ||||||
|             state: flash.state(), |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         let mut aligned = [0; 4]; |         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( |         assert!(block_on(updater.verify_and_mark_updated( | ||||||
|             &public_key.to_bytes(), |             &public_key.to_bytes(), | ||||||
|             &signature.to_bytes(), |             &signature.to_bytes(), | ||||||
|             firmware_len as u32, |             firmware_len as u32, | ||||||
|             &mut aligned, |  | ||||||
|         )) |         )) | ||||||
|         .is_ok()); |         .is_ok()); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,37 +3,28 @@ | |||||||
| #![doc = include_str!("../README.md")] | #![doc = include_str!("../README.md")] | ||||||
| mod fmt; | mod fmt; | ||||||
|  |  | ||||||
|  | pub use embassy_boot::{ | ||||||
|  |     AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, | ||||||
|  | }; | ||||||
| #[cfg(feature = "nightly")] | #[cfg(feature = "nightly")] | ||||||
| pub use embassy_boot::FirmwareUpdater; | pub use embassy_boot::{FirmwareState, FirmwareUpdater}; | ||||||
| pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig}; | use embassy_nrf::nvmc::PAGE_SIZE; | ||||||
| use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE}; |  | ||||||
| use embassy_nrf::peripherals::WDT; | use embassy_nrf::peripherals::WDT; | ||||||
| use embassy_nrf::wdt; | use embassy_nrf::wdt; | ||||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||||
|  |  | ||||||
| /// A bootloader for nRF devices. | /// A bootloader for nRF devices. | ||||||
| pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = PAGE_SIZE> { | pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE>; | ||||||
|     boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>, |  | ||||||
|     aligned_buf: AlignedBuffer<BUFFER_SIZE>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||||
|     BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE> |     /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. | ||||||
| { |     pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||||||
|     /// Create a new bootloader instance using the supplied partitions for active, dfu and state. |         config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||||||
|     pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { |     ) -> Self { | ||||||
|         Self { |         let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||||||
|             boot: embassy_boot::BootLoader::new(config), |         let mut boot = embassy_boot::BootLoader::new(config); | ||||||
|             aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), |         boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); | ||||||
|         } |         Self | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping |  | ||||||
|     /// firmware. |  | ||||||
|     pub fn prepare(&mut self) { |  | ||||||
|         self.boot |  | ||||||
|             .prepare_boot(&mut self.aligned_buf.0) |  | ||||||
|             .expect("Boot prepare error"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Boots the application without softdevice mechanisms. |     /// Boots the application without softdevice mechanisms. | ||||||
| @@ -43,8 +34,6 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | |||||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. |     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||||
|     #[cfg(not(feature = "softdevice"))] |     #[cfg(not(feature = "softdevice"))] | ||||||
|     pub unsafe fn load(self, start: u32) -> ! { |     pub unsafe fn load(self, start: u32) -> ! { | ||||||
|         core::mem::drop(self.boot); |  | ||||||
|  |  | ||||||
|         let mut p = cortex_m::Peripherals::steal(); |         let mut p = cortex_m::Peripherals::steal(); | ||||||
|         p.SCB.invalidate_icache(); |         p.SCB.invalidate_icache(); | ||||||
|         p.SCB.vtor.write(start); |         p.SCB.vtor.write(start); | ||||||
| @@ -57,7 +46,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | |||||||
|     /// |     /// | ||||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. |     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||||
|     #[cfg(feature = "softdevice")] |     #[cfg(feature = "softdevice")] | ||||||
|     pub unsafe fn load(&mut self, _app: u32) -> ! { |     pub unsafe fn load(self, _app: u32) -> ! { | ||||||
|         use nrf_softdevice_mbr as mbr; |         use nrf_softdevice_mbr as mbr; | ||||||
|         const NRF_SUCCESS: u32 = 0; |         const NRF_SUCCESS: u32 = 0; | ||||||
|  |  | ||||||
| @@ -104,15 +93,15 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// A flash implementation that wraps NVMC and will pet a watchdog when touching flash. | /// A flash implementation that wraps any flash and will pet a watchdog when touching flash. | ||||||
| pub struct WatchdogFlash<'d> { | pub struct WatchdogFlash<FLASH> { | ||||||
|     flash: Nvmc<'d>, |     flash: FLASH, | ||||||
|     wdt: wdt::WatchdogHandle, |     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 |     /// 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) { |         let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { | ||||||
|             Ok(x) => x, |             Ok(x) => x, | ||||||
|             Err(_) => { |             Err(_) => { | ||||||
| @@ -127,13 +116,13 @@ impl<'d> WatchdogFlash<'d> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'d> ErrorType for WatchdogFlash<'d> { | impl<FLASH: ErrorType> ErrorType for WatchdogFlash<FLASH> { | ||||||
|     type Error = <Nvmc<'d> as ErrorType>::Error; |     type Error = FLASH::Error; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'d> NorFlash for WatchdogFlash<'d> { | impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> { | ||||||
|     const WRITE_SIZE: usize = <Nvmc<'d> as NorFlash>::WRITE_SIZE; |     const WRITE_SIZE: usize = FLASH::WRITE_SIZE; | ||||||
|     const ERASE_SIZE: usize = <Nvmc<'d> as NorFlash>::ERASE_SIZE; |     const ERASE_SIZE: usize = FLASH::ERASE_SIZE; | ||||||
|  |  | ||||||
|     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { |     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||||
|         self.wdt.pet(); |         self.wdt.pet(); | ||||||
| @@ -145,8 +134,8 @@ impl<'d> NorFlash for WatchdogFlash<'d> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'d> ReadNorFlash for WatchdogFlash<'d> { | impl<FLASH: ReadNorFlash> ReadNorFlash for WatchdogFlash<FLASH> { | ||||||
|     const READ_SIZE: usize = <Nvmc<'d> as ReadNorFlash>::READ_SIZE; |     const READ_SIZE: usize = FLASH::READ_SIZE; | ||||||
|     fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { |     fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { | ||||||
|         self.wdt.pet(); |         self.wdt.pet(); | ||||||
|         self.flash.read(offset, data) |         self.flash.read(offset, data) | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,38 +3,29 @@ | |||||||
| #![doc = include_str!("../README.md")] | #![doc = include_str!("../README.md")] | ||||||
| mod fmt; | mod fmt; | ||||||
|  |  | ||||||
|  | pub use embassy_boot::{ | ||||||
|  |     AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State, | ||||||
|  | }; | ||||||
| #[cfg(feature = "nightly")] | #[cfg(feature = "nightly")] | ||||||
| pub use embassy_boot::FirmwareUpdater; | pub use embassy_boot::{FirmwareState, FirmwareUpdater}; | ||||||
| pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; | use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; | ||||||
| use embassy_rp::flash::{Flash, ERASE_SIZE}; |  | ||||||
| use embassy_rp::peripherals::{FLASH, WATCHDOG}; | use embassy_rp::peripherals::{FLASH, WATCHDOG}; | ||||||
| use embassy_rp::watchdog::Watchdog; | use embassy_rp::watchdog::Watchdog; | ||||||
| use embassy_time::Duration; | use embassy_time::Duration; | ||||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||||
|  |  | ||||||
| /// A bootloader for RP2040 devices. | /// A bootloader for RP2040 devices. | ||||||
| pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = ERASE_SIZE> { | pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE>; | ||||||
|     boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>, |  | ||||||
|     aligned_buf: AlignedBuffer<BUFFER_SIZE>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { | ||||||
|     BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE> |     /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware | ||||||
| { |     pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||||||
|     /// Create a new bootloader instance using the supplied partitions for active, dfu and state. |         config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||||||
|     pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { |     ) -> Self { | ||||||
|         Self { |         let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||||||
|             boot: embassy_boot::BootLoader::new(config), |         let mut boot = embassy_boot::BootLoader::new(config); | ||||||
|             aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), |         boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); | ||||||
|         } |         Self | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping |  | ||||||
|     /// firmware. |  | ||||||
|     pub fn prepare(&mut self) { |  | ||||||
|         self.boot |  | ||||||
|             .prepare_boot(self.aligned_buf.as_mut()) |  | ||||||
|             .expect("Boot prepare error"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Boots the application. |     /// Boots the application. | ||||||
| @@ -43,8 +34,6 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | |||||||
|     /// |     /// | ||||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. |     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||||
|     pub unsafe fn load(self, start: u32) -> ! { |     pub unsafe fn load(self, start: u32) -> ! { | ||||||
|         core::mem::drop(self.boot); |  | ||||||
|  |  | ||||||
|         trace!("Loading app at 0x{:x}", start); |         trace!("Loading app at 0x{:x}", start); | ||||||
|         #[allow(unused_mut)] |         #[allow(unused_mut)] | ||||||
|         let mut p = cortex_m::Peripherals::steal(); |         let mut p = cortex_m::Peripherals::steal(); | ||||||
| @@ -58,14 +47,14 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | |||||||
|  |  | ||||||
| /// A flash implementation that will feed a watchdog when touching flash. | /// A flash implementation that will feed a watchdog when touching flash. | ||||||
| pub struct WatchdogFlash<'d, const SIZE: usize> { | pub struct WatchdogFlash<'d, const SIZE: usize> { | ||||||
|     flash: Flash<'d, FLASH, SIZE>, |     flash: Flash<'d, FLASH, Blocking, SIZE>, | ||||||
|     watchdog: Watchdog, |     watchdog: Watchdog, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { | impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { | ||||||
|     /// Start a new watchdog with a given flash and watchdog peripheral and a timeout |     /// Start a new watchdog with a given flash and watchdog peripheral and a timeout | ||||||
|     pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { |     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); |         let mut watchdog = Watchdog::new(watchdog); | ||||||
|         watchdog.start(timeout); |         watchdog.start(timeout); | ||||||
|         Self { flash, watchdog } |         Self { flash, watchdog } | ||||||
| @@ -73,28 +62,28 @@ impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl<'d, const SIZE: usize> ErrorType for 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> { | impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { | ||||||
|     const WRITE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::WRITE_SIZE; |     const WRITE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::WRITE_SIZE; | ||||||
|     const ERASE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::ERASE_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> { |     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||||||
|         self.watchdog.feed(); |         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> { |     fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { | ||||||
|         self.watchdog.feed(); |         self.watchdog.feed(); | ||||||
|         self.flash.write(offset, data) |         self.flash.blocking_write(offset, data) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { | 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> { |     fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { | ||||||
|         self.watchdog.feed(); |         self.watchdog.feed(); | ||||||
|         self.flash.read(offset, data) |         self.flash.blocking_read(offset, data) | ||||||
|     } |     } | ||||||
|     fn capacity(&self) -> usize { |     fn capacity(&self) -> usize { | ||||||
|         self.flash.capacity() |         self.flash.capacity() | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,34 +3,25 @@ | |||||||
| #![doc = include_str!("../README.md")] | #![doc = include_str!("../README.md")] | ||||||
| mod fmt; | mod fmt; | ||||||
|  |  | ||||||
|  | pub use embassy_boot::{ | ||||||
|  |     AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State, | ||||||
|  | }; | ||||||
| #[cfg(feature = "nightly")] | #[cfg(feature = "nightly")] | ||||||
| pub use embassy_boot::FirmwareUpdater; | pub use embassy_boot::{FirmwareState, FirmwareUpdater}; | ||||||
| pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; |  | ||||||
| use embedded_storage::nor_flash::NorFlash; | use embedded_storage::nor_flash::NorFlash; | ||||||
|  |  | ||||||
| /// A bootloader for STM32 devices. | /// A bootloader for STM32 devices. | ||||||
| pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> { | pub struct BootLoader; | ||||||
|     boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>, |  | ||||||
|     aligned_buf: AlignedBuffer<BUFFER_SIZE>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | impl BootLoader { | ||||||
|     BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE> |     /// 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>( | ||||||
|     /// Create a new bootloader instance using the supplied partitions for active, dfu and state. |         config: BootLoaderConfig<ACTIVE, DFU, STATE>, | ||||||
|     pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { |     ) -> Self { | ||||||
|         Self { |         let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | ||||||
|             boot: embassy_boot::BootLoader::new(config), |         let mut boot = embassy_boot::BootLoader::new(config); | ||||||
|             aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), |         boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); | ||||||
|         } |         Self | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Inspect the bootloader state and perform actions required before booting, such as swapping |  | ||||||
|     /// firmware. |  | ||||||
|     pub fn prepare(&mut self) { |  | ||||||
|         self.boot |  | ||||||
|             .prepare_boot(self.aligned_buf.as_mut()) |  | ||||||
|             .expect("Boot prepare error"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Boots the application. |     /// Boots the application. | ||||||
| @@ -39,8 +30,6 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | |||||||
|     /// |     /// | ||||||
|     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. |     /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||||||
|     pub unsafe fn load(self, start: u32) -> ! { |     pub unsafe fn load(self, start: u32) -> ! { | ||||||
|         core::mem::drop(self.boot); |  | ||||||
|  |  | ||||||
|         trace!("Loading app at 0x{:x}", start); |         trace!("Loading app at 0x{:x}", start); | ||||||
|         #[allow(unused_mut)] |         #[allow(unused_mut)] | ||||||
|         let mut p = cortex_m::Peripherals::steal(); |         let mut p = cortex_m::Peripherals::steal(); | ||||||
|   | |||||||
| @@ -15,15 +15,18 @@ target = "x86_64-unknown-linux-gnu" | |||||||
| std = [] | std = [] | ||||||
| # Enable nightly-only features | # Enable nightly-only features | ||||||
| nightly = ["embassy-futures", "embedded-hal-async", "embedded-storage-async"] | nightly = ["embassy-futures", "embedded-hal-async", "embedded-storage-async"] | ||||||
|  | time = ["dep:embassy-time"] | ||||||
|  | default = ["time"] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true } | embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true } | ||||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync" } | embassy-sync = { version = "0.2.0", path = "../embassy-sync" } | ||||||
|  | embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true } | ||||||
| embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ | embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ | ||||||
|     "unproven", |     "unproven", | ||||||
| ] } | ] } | ||||||
| embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } | ||||||
| embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true } | embedded-hal-async = { version = "=1.0.0-rc.1", optional = true } | ||||||
| embedded-storage = "0.3.0" | embedded-storage = "0.3.0" | ||||||
| embedded-storage-async = { version = "0.4.0", optional = true } | embedded-storage-async = { version = "0.4.0", optional = true } | ||||||
| nb = "1.0.0" | nb = "1.0.0" | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| use embedded_hal_02::{blocking, serial}; | use embedded_hal_02::blocking; | ||||||
|  |  | ||||||
| /// Wrapper that implements async traits using blocking implementations. | /// Wrapper that implements async traits using blocking implementations. | ||||||
| /// | /// | ||||||
| @@ -74,7 +74,21 @@ where | |||||||
|     E: embedded_hal_1::spi::Error + 'static, |     E: embedded_hal_1::spi::Error + 'static, | ||||||
|     T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>, |     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 |         // Ensure we write the expected bytes | ||||||
|         for i in 0..core::cmp::min(read.len(), write.len()) { |         for i in 0..core::cmp::min(read.len(), write.len()) { | ||||||
|             read[i] = write[i].clone(); |             read[i] = write[i].clone(); | ||||||
| @@ -83,52 +97,12 @@ where | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> { |     async fn transfer_in_place(&mut self, data: &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> { |  | ||||||
|         self.wrapped.transfer(data)?; |         self.wrapped.transfer(data)?; | ||||||
|         Ok(()) |         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; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// NOR flash wrapper | /// NOR flash wrapper | ||||||
| use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||||||
| use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||||||
|   | |||||||
| @@ -69,54 +69,39 @@ where | |||||||
|     type Error = T::Error; |     type Error = T::Error; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<T> embedded_hal_async::spi::SpiBus<u8> for YieldingAsync<T> | impl<T, Word: 'static + Copy> embedded_hal_async::spi::SpiBus<Word> for YieldingAsync<T> | ||||||
| where | where | ||||||
|     T: embedded_hal_async::spi::SpiBus, |     T: embedded_hal_async::spi::SpiBus<Word>, | ||||||
| { |  | ||||||
|     async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> { |  | ||||||
|         self.wrapped.transfer(read, write).await?; |  | ||||||
|         yield_now().await; |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [u8]) -> Result<(), Self::Error> { |  | ||||||
|         self.wrapped.transfer_in_place(words).await?; |  | ||||||
|         yield_now().await; |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<T> embedded_hal_async::spi::SpiBusFlush for YieldingAsync<T> |  | ||||||
| where |  | ||||||
|     T: embedded_hal_async::spi::SpiBusFlush, |  | ||||||
| { | { | ||||||
|     async fn flush(&mut self) -> Result<(), Self::Error> { |     async fn flush(&mut self) -> Result<(), Self::Error> { | ||||||
|         self.wrapped.flush().await?; |         self.wrapped.flush().await?; | ||||||
|         yield_now().await; |         yield_now().await; | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<T> embedded_hal_async::spi::SpiBusWrite<u8> for YieldingAsync<T> |     async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> { | ||||||
| where |  | ||||||
|     T: embedded_hal_async::spi::SpiBusWrite<u8>, |  | ||||||
| { |  | ||||||
|     async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { |  | ||||||
|         self.wrapped.write(data).await?; |         self.wrapped.write(data).await?; | ||||||
|         yield_now().await; |         yield_now().await; | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<T> embedded_hal_async::spi::SpiBusRead<u8> for YieldingAsync<T> |     async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> { | ||||||
| where |  | ||||||
|     T: embedded_hal_async::spi::SpiBusRead<u8>, |  | ||||||
| { |  | ||||||
|     async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { |  | ||||||
|         self.wrapped.read(data).await?; |         self.wrapped.read(data).await?; | ||||||
|         yield_now().await; |         yield_now().await; | ||||||
|         Ok(()) |         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(()) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// | /// | ||||||
|   | |||||||
| @@ -56,62 +56,6 @@ where | |||||||
|     type Error = SpiDeviceError<BUS::Error, CS::Error>; |     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> | impl<M, BUS, CS> spi::SpiDevice for SpiDevice<'_, M, BUS, CS> | ||||||
| where | where | ||||||
|     M: RawMutex, |     M: RawMutex, | ||||||
| @@ -129,6 +73,12 @@ where | |||||||
|                     Operation::Write(buf) => bus.write(buf).await?, |                     Operation::Write(buf) => bus.write(buf).await?, | ||||||
|                     Operation::Transfer(read, write) => bus.transfer(read, write).await?, |                     Operation::Transfer(read, write) => bus.transfer(read, write).await?, | ||||||
|                     Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).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>; |     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> | impl<M, BUS, CS> spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||||
| where | where | ||||||
|     M: RawMutex, |     M: RawMutex, | ||||||
| @@ -248,6 +140,12 @@ where | |||||||
|                     Operation::Write(buf) => bus.write(buf).await?, |                     Operation::Write(buf) => bus.write(buf).await?, | ||||||
|                     Operation::Transfer(read, write) => bus.transfer(read, write).await?, |                     Operation::Transfer(read, write) => bus.transfer(read, write).await?, | ||||||
|                     Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).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 | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ use core::cell::RefCell; | |||||||
| use embassy_sync::blocking_mutex::raw::RawMutex; | use embassy_sync::blocking_mutex::raw::RawMutex; | ||||||
| use embassy_sync::blocking_mutex::Mutex; | use embassy_sync::blocking_mutex::Mutex; | ||||||
| use embedded_hal_1::digital::OutputPin; | 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::shared_bus::SpiDeviceError; | ||||||
| use crate::SetConfig; | use crate::SetConfig; | ||||||
| @@ -48,58 +48,6 @@ where | |||||||
|     type Error = SpiDeviceError<BUS::Error, CS::Error>; |     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> | impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS> | ||||||
| where | where | ||||||
|     M: RawMutex, |     M: RawMutex, | ||||||
| @@ -116,6 +64,13 @@ where | |||||||
|                 Operation::Write(buf) => bus.write(buf), |                 Operation::Write(buf) => bus.write(buf), | ||||||
|                 Operation::Transfer(read, write) => bus.transfer(read, write), |                 Operation::Transfer(read, write) => bus.transfer(read, write), | ||||||
|                 Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), |                 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. |             // On failure, it's important to still flush and deassert CS. | ||||||
| @@ -199,58 +154,6 @@ where | |||||||
|     type Error = SpiDeviceError<BUS::Error, CS::Error>; |     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> | impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> | ||||||
| where | where | ||||||
|     M: RawMutex, |     M: RawMutex, | ||||||
| @@ -268,6 +171,13 @@ where | |||||||
|                 Operation::Write(buf) => bus.write(buf), |                 Operation::Write(buf) => bus.write(buf), | ||||||
|                 Operation::Transfer(read, write) => bus.transfer(read, write), |                 Operation::Transfer(read, write) => bus.transfer(read, write), | ||||||
|                 Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), |                 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. |             // 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. | /// Error returned by SPI device implementations in this crate. | ||||||
| #[derive(Copy, Clone, Eq, PartialEq, Debug)] | #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||||||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
|  | #[non_exhaustive] | ||||||
| pub enum SpiDeviceError<BUS, CS> { | pub enum SpiDeviceError<BUS, CS> { | ||||||
|     /// An operation on the inner SPI bus failed. |     /// An operation on the inner SPI bus failed. | ||||||
|     Spi(BUS), |     Spi(BUS), | ||||||
|     /// Setting the value of the Chip Select (CS) pin failed. |     /// Setting the value of the Chip Select (CS) pin failed. | ||||||
|     Cs(CS), |     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> | impl<BUS, CS> spi::Error for SpiDeviceError<BUS, CS> | ||||||
| @@ -46,6 +49,7 @@ where | |||||||
|         match self { |         match self { | ||||||
|             Self::Spi(e) => e.kind(), |             Self::Spi(e) => e.kind(), | ||||||
|             Self::Cs(_) => spi::ErrorKind::Other, |             Self::Cs(_) => spi::ErrorKind::Other, | ||||||
|  |             Self::DelayUsNotSupported => spi::ErrorKind::Other, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,6 +5,20 @@ 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/), | 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). | 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 | ## 0.2.0 - 2023-04-27 | ||||||
|  |  | ||||||
| - Replace unnecessary atomics in runqueue | - Replace unnecessary atomics in runqueue | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| [package] | [package] | ||||||
| name = "embassy-executor" | name = "embassy-executor" | ||||||
| version = "0.2.0" | version = "0.3.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| license = "MIT OR Apache-2.0" | license = "MIT OR Apache-2.0" | ||||||
| description = "async/await executor designed for embedded usage" | description = "async/await executor designed for embedded usage" | ||||||
| @@ -14,7 +14,7 @@ categories = [ | |||||||
| [package.metadata.embassy_docs] | [package.metadata.embassy_docs] | ||||||
| src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" | 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/" | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" | ||||||
| features = ["nightly", "defmt", "pender-callback"] | features = ["nightly", "defmt"] | ||||||
| flavors = [ | flavors = [ | ||||||
|     { name = "std",             target = "x86_64-unknown-linux-gnu",     features = ["arch-std", "executor-thread"] }, |     { name = "std",             target = "x86_64-unknown-linux-gnu",     features = ["arch-std", "executor-thread"] }, | ||||||
|     { name = "wasm",            target = "wasm32-unknown-unknown",       features = ["arch-wasm", "executor-thread"] }, |     { name = "wasm",            target = "wasm32-unknown-unknown",       features = ["arch-wasm", "executor-thread"] }, | ||||||
| @@ -25,7 +25,7 @@ flavors = [ | |||||||
| [package.metadata.docs.rs] | [package.metadata.docs.rs] | ||||||
| default-target = "thumbv7em-none-eabi" | default-target = "thumbv7em-none-eabi" | ||||||
| targets = ["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] | [features] | ||||||
|  |  | ||||||
| @@ -37,9 +37,6 @@ arch-xtensa = ["_arch"] | |||||||
| arch-riscv32 = ["_arch"] | arch-riscv32 = ["_arch"] | ||||||
| arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] | 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) | # Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) | ||||||
| executor-thread = [] | executor-thread = [] | ||||||
| # Enable the interrupt-mode executor (available in Cortex-M only) | # Enable the interrupt-mode executor (available in Cortex-M only) | ||||||
| @@ -61,8 +58,8 @@ log = { version = "0.4.14", optional = true } | |||||||
| rtos-trace = { version = "0.1.2", optional = true } | rtos-trace = { version = "0.1.2", optional = true } | ||||||
|  |  | ||||||
| futures-util = { version = "0.3.17", default-features = false } | futures-util = { version = "0.3.17", default-features = false } | ||||||
| embassy-macros  = { version = "0.2.0", path = "../embassy-macros" } | embassy-macros = { version = "0.2.1", path = "../embassy-macros" } | ||||||
| embassy-time  = { version = "0.1.0", path = "../embassy-time", optional = true} | embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true} | ||||||
| atomic-polyfill = "1.0.1" | atomic-polyfill = "1.0.1" | ||||||
| critical-section = "1.1" | critical-section = "1.1" | ||||||
| static_cell = "1.1" | static_cell = "1.1" | ||||||
|   | |||||||
| @@ -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")] | #[cfg(feature = "executor-thread")] | ||||||
| pub use thread::*; | pub use thread::*; | ||||||
| #[cfg(feature = "executor-thread")] | #[cfg(feature = "executor-thread")] | ||||||
| mod thread { | mod thread { | ||||||
|  |     pub(super) const THREAD_PENDER: usize = usize::MAX; | ||||||
|  |  | ||||||
|     use core::arch::asm; |     use core::arch::asm; | ||||||
|     use core::marker::PhantomData; |     use core::marker::PhantomData; | ||||||
|  |  | ||||||
|     #[cfg(feature = "nightly")] |     #[cfg(feature = "nightly")] | ||||||
|     pub use embassy_macros::main_cortex_m as main; |     pub use embassy_macros::main_cortex_m as main; | ||||||
|  |  | ||||||
|     use crate::raw::{Pender, PenderInner}; |  | ||||||
|     use crate::{raw, Spawner}; |     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. |     /// Thread mode executor, using WFE/SEV. | ||||||
|     /// |     /// | ||||||
|     /// This is the simplest and most common kind of executor. It runs on |     /// This is the simplest and most common kind of executor. It runs on | ||||||
| @@ -39,7 +75,7 @@ mod thread { | |||||||
|         /// Create a new Executor. |         /// Create a new Executor. | ||||||
|         pub fn new() -> Self { |         pub fn new() -> Self { | ||||||
|             Self { |             Self { | ||||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), |                 inner: raw::Executor::new(THREAD_PENDER as *mut ()), | ||||||
|                 not_send: PhantomData, |                 not_send: PhantomData, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -86,30 +122,7 @@ mod interrupt { | |||||||
|     use cortex_m::interrupt::InterruptNumber; |     use cortex_m::interrupt::InterruptNumber; | ||||||
|     use cortex_m::peripheral::NVIC; |     use cortex_m::peripheral::NVIC; | ||||||
|  |  | ||||||
|     use crate::raw::{self, Pender, PenderInner}; |     use crate::raw; | ||||||
|  |  | ||||||
|     #[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 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Interrupt mode executor. |     /// Interrupt mode executor. | ||||||
|     /// |     /// | ||||||
| @@ -194,9 +207,7 @@ mod interrupt { | |||||||
|             unsafe { |             unsafe { | ||||||
|                 (&mut *self.executor.get()) |                 (&mut *self.executor.get()) | ||||||
|                     .as_mut_ptr() |                     .as_mut_ptr() | ||||||
|                     .write(raw::Executor::new(Pender(PenderInner::Interrupt(InterruptPender( |                     .write(raw::Executor::new(irq.number() as *mut ())) | ||||||
|                         irq.number(), |  | ||||||
|                     ))))) |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; |             let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; | ||||||
|   | |||||||
| @@ -11,22 +11,16 @@ mod thread { | |||||||
|     #[cfg(feature = "nightly")] |     #[cfg(feature = "nightly")] | ||||||
|     pub use embassy_macros::main_riscv as main; |     pub use embassy_macros::main_riscv as main; | ||||||
|  |  | ||||||
|     use crate::raw::{Pender, PenderInner}; |  | ||||||
|     use crate::{raw, Spawner}; |     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 |     /// 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); |     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 |     /// RISCV32 Executor | ||||||
|     pub struct Executor { |     pub struct Executor { | ||||||
|         inner: raw::Executor, |         inner: raw::Executor, | ||||||
| @@ -37,7 +31,7 @@ mod thread { | |||||||
|         /// Create a new Executor. |         /// Create a new Executor. | ||||||
|         pub fn new() -> Self { |         pub fn new() -> Self { | ||||||
|             Self { |             Self { | ||||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), |                 inner: raw::Executor::new(core::ptr::null_mut()), | ||||||
|                 not_send: PhantomData, |                 not_send: PhantomData, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -11,17 +11,12 @@ mod thread { | |||||||
|     #[cfg(feature = "nightly")] |     #[cfg(feature = "nightly")] | ||||||
|     pub use embassy_macros::main_std as main; |     pub use embassy_macros::main_std as main; | ||||||
|  |  | ||||||
|     use crate::raw::{Pender, PenderInner}; |  | ||||||
|     use crate::{raw, Spawner}; |     use crate::{raw, Spawner}; | ||||||
|  |  | ||||||
|     #[derive(Copy, Clone)] |     #[export_name = "__pender"] | ||||||
|     pub(crate) struct ThreadPender(&'static Signaler); |     fn __pender(context: *mut ()) { | ||||||
|  |         let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; | ||||||
|     impl ThreadPender { |         signaler.signal() | ||||||
|         #[allow(unused)] |  | ||||||
|         pub(crate) fn pend(self) { |  | ||||||
|             self.0.signal() |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Single-threaded std-based executor. |     /// Single-threaded std-based executor. | ||||||
| @@ -34,9 +29,9 @@ mod thread { | |||||||
|     impl Executor { |     impl Executor { | ||||||
|         /// Create a new Executor. |         /// Create a new Executor. | ||||||
|         pub fn new() -> Self { |         pub fn new() -> Self { | ||||||
|             let signaler = &*Box::leak(Box::new(Signaler::new())); |             let signaler = Box::leak(Box::new(Signaler::new())); | ||||||
|             Self { |             Self { | ||||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(signaler)))), |                 inner: raw::Executor::new(signaler as *mut Signaler as *mut ()), | ||||||
|                 not_send: PhantomData, |                 not_send: PhantomData, | ||||||
|                 signaler, |                 signaler, | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -14,14 +14,12 @@ mod thread { | |||||||
|     use wasm_bindgen::prelude::*; |     use wasm_bindgen::prelude::*; | ||||||
|  |  | ||||||
|     use crate::raw::util::UninitCell; |     use crate::raw::util::UninitCell; | ||||||
|     use crate::raw::{Pender, PenderInner}; |  | ||||||
|     use crate::{raw, Spawner}; |     use crate::{raw, Spawner}; | ||||||
|  |  | ||||||
|     /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. |     #[export_name = "__pender"] | ||||||
|     pub struct Executor { |     fn __pender(context: *mut ()) { | ||||||
|         inner: raw::Executor, |         let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; | ||||||
|         ctx: &'static WasmContext, |         let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); | ||||||
|         not_send: PhantomData<*mut ()>, |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) struct WasmContext { |     pub(crate) struct WasmContext { | ||||||
| @@ -29,16 +27,6 @@ mod thread { | |||||||
|         closure: UninitCell<Closure<dyn FnMut(JsValue)>>, |         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 { |     impl WasmContext { | ||||||
|         pub fn new() -> Self { |         pub fn new() -> Self { | ||||||
|             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 { |     impl Executor { | ||||||
|         /// Create a new Executor. |         /// Create a new Executor. | ||||||
|         pub fn new() -> Self { |         pub fn new() -> Self { | ||||||
|             let ctx = &*Box::leak(Box::new(WasmContext::new())); |             let ctx = Box::leak(Box::new(WasmContext::new())); | ||||||
|             Self { |             Self { | ||||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(ctx)))), |                 inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()), | ||||||
|                 not_send: PhantomData, |  | ||||||
|                 ctx, |                 ctx, | ||||||
|  |                 not_send: PhantomData, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,22 +8,16 @@ mod thread { | |||||||
|     use core::marker::PhantomData; |     use core::marker::PhantomData; | ||||||
|     use core::sync::atomic::{AtomicBool, Ordering}; |     use core::sync::atomic::{AtomicBool, Ordering}; | ||||||
|  |  | ||||||
|     use crate::raw::{Pender, PenderInner}; |  | ||||||
|     use crate::{raw, Spawner}; |     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 |     /// 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); |     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 |     /// Xtensa Executor | ||||||
|     pub struct Executor { |     pub struct Executor { | ||||||
|         inner: raw::Executor, |         inner: raw::Executor, | ||||||
| @@ -34,7 +28,7 @@ mod thread { | |||||||
|         /// Create a new Executor. |         /// Create a new Executor. | ||||||
|         pub fn new() -> Self { |         pub fn new() -> Self { | ||||||
|             Self { |             Self { | ||||||
|                 inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), |                 inner: raw::Executor::new(core::ptr::null_mut()), | ||||||
|                 not_send: PhantomData, |                 not_send: PhantomData, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -77,8 +71,8 @@ mod thread { | |||||||
|                         SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); |                         SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); | ||||||
|  |  | ||||||
|                         core::arch::asm!( |                         core::arch::asm!( | ||||||
|                             "wsr.ps {0}", |                         "wsr.ps {0}", | ||||||
|                             "rsync", in(reg) token) |                         "rsync", in(reg) token) | ||||||
|                     } else { |                     } else { | ||||||
|                         // waiti sets the PS.INTLEVEL when slipping into sleep |                         // waiti sets the PS.INTLEVEL when slipping into sleep | ||||||
|                         // because critical sections in Xtensa are implemented via increasing |                         // because critical sections in Xtensa are implemented via increasing | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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> { |     pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { | ||||||
|         let task = AvailableTask::claim(self); |         let task = AvailableTask::claim(self); | ||||||
|         match task { |         match task { | ||||||
|             Some(task) => { |             Some(task) => task.initialize(future), | ||||||
|                 let task = task.initialize(future); |  | ||||||
|                 unsafe { SpawnToken::<F>::new(task) } |  | ||||||
|             } |  | ||||||
|             None => SpawnToken::new_failed(), |             None => SpawnToken::new_failed(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -165,6 +162,9 @@ impl<F: Future + 'static> TaskStorage<F> { | |||||||
|             Poll::Ready(_) => { |             Poll::Ready(_) => { | ||||||
|                 this.future.drop_in_place(); |                 this.future.drop_in_place(); | ||||||
|                 this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); |                 this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); | ||||||
|  |  | ||||||
|  |                 #[cfg(feature = "integrated-timers")] | ||||||
|  |                 this.raw.expires_at.set(Instant::MAX); | ||||||
|             } |             } | ||||||
|             Poll::Pending => {} |             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>, |     task: &'static TaskStorage<F>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<F: Future + 'static> AvailableTask<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 |         task.raw | ||||||
|             .state |             .state | ||||||
|             .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) |             .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 }) |             .map(|_| Self { task }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { |     fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> { | ||||||
|         unsafe { |         unsafe { | ||||||
|             self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll)); |             self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll)); | ||||||
|             self.task.future.write(future()); |             self.task.future.write(future()); | ||||||
|         } |  | ||||||
|         TaskRef::new(self.task) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Raw storage that can hold up to N tasks of the same type. |             let task = TaskRef::new(self.task); | ||||||
| /// |  | ||||||
| /// 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> { |             SpawnToken::new(task) | ||||||
|     /// 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. | ||||||
|     /// |     pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken<F> { | ||||||
|     /// See [`TaskStorage::spawn()`] for details. |         self.initialize_impl::<F>(future) | ||||||
|     /// |  | ||||||
|     /// 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 |     /// Initialize the [`TaskStorage`] to run the given future. | ||||||
|     /// the future is !Send. |  | ||||||
|     /// |     /// | ||||||
|     /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used |     /// # Safety | ||||||
|     /// 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`. |     /// is an `async fn`, NOT a hand-written `Future`. | ||||||
|     #[doc(hidden)] |     #[doc(hidden)] | ||||||
|     pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized> |     pub unsafe fn __initialize_async_fn<FutFn>(self, future: impl FnOnce() -> F) -> SpawnToken<FutFn> { | ||||||
|     where |  | ||||||
|         FutFn: FnOnce() -> F, |  | ||||||
|     { |  | ||||||
|         // When send-spawning a task, we construct the future in this thread, and effectively |         // 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" it to the executor thread by enqueuing it in its queue. Therefore, in theory, | ||||||
|         // send-spawning should require the future `F` to be `Send`. |         // 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 |         // 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>`. |         // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`. | ||||||
|  |         self.initialize_impl::<FutFn>(future) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|         let task = self.pool.iter().find_map(AvailableTask::claim); | /// Raw storage that can hold up to N tasks of the same type. | ||||||
|         match task { | /// | ||||||
|             Some(task) => { | /// This is essentially a `[TaskStorage<F>; N]`. | ||||||
|                 let task = task.initialize(future); | pub struct TaskPool<F: Future + 'static, const N: usize> { | ||||||
|                 unsafe { SpawnToken::<FutFn>::new(task) } |     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(), |             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)] | #[derive(Clone, Copy)] | ||||||
| pub(crate) enum PenderInner { | pub(crate) struct Pender(*mut ()); | ||||||
|     #[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 () }, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| unsafe impl Send for PenderInner {} | unsafe impl Send for Pender {} | ||||||
| unsafe impl Sync for PenderInner {} | unsafe impl Sync for Pender {} | ||||||
|  |  | ||||||
| /// 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); |  | ||||||
|  |  | ||||||
| impl Pender { | impl Pender { | ||||||
|     /// Create a `Pender` that will call an arbitrary function pointer. |     pub(crate) fn pend(self) { | ||||||
|     /// |         extern "Rust" { | ||||||
|     /// # Arguments |             fn __pender(context: *mut ()); | ||||||
|     /// |  | ||||||
|     /// - `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), |  | ||||||
|         } |         } | ||||||
|  |         unsafe { __pender(self.0) }; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -406,7 +386,7 @@ impl SyncExecutor { | |||||||
|         #[allow(clippy::never_loop)] |         #[allow(clippy::never_loop)] | ||||||
|         loop { |         loop { | ||||||
|             #[cfg(feature = "integrated-timers")] |             #[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| { |             self.run_queue.dequeue_all(|p| { | ||||||
|                 let task = p.header(); |                 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 | /// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks | ||||||
| ///   that "want to run"). | ///   that "want to run"). | ||||||
| /// - You must supply a [`Pender`]. The executor will call it to notify you it has work | /// - You must supply a pender function, as shown below. The executor will call it to notify you | ||||||
| ///   to do. You must arrange for `poll()` to be called as soon as possible. | ///   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. | /// level, etc. It may be called synchronously from any `Executor` method call as well. | ||||||
| /// You must deal with this correctly. | /// You must deal with this correctly. | ||||||
| /// | /// | ||||||
| /// In particular, you must NOT call `poll` directly from the pender callback, as this violates | /// 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 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)] | #[repr(transparent)] | ||||||
| pub struct Executor { | pub struct Executor { | ||||||
|     pub(crate) inner: SyncExecutor, |     pub(crate) inner: SyncExecutor, | ||||||
| @@ -492,12 +488,12 @@ impl Executor { | |||||||
|  |  | ||||||
|     /// Create a new 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`. |     /// See [`Executor`] docs for details on the pender. | ||||||
|     pub fn new(pender: Pender) -> Self { |     pub fn new(context: *mut ()) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             inner: SyncExecutor::new(pender), |             inner: SyncExecutor::new(Pender(context)), | ||||||
|             _not_sync: PhantomData, |             _not_sync: PhantomData, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -520,16 +516,16 @@ impl Executor { | |||||||
|     /// This loops over all tasks that are queued to be polled (i.e. they're |     /// 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. |     /// 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 |     /// 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 |     /// to call `poll` even when not requested by the pender, but it wastes | ||||||
|     /// energy. |     /// energy. | ||||||
|     /// |     /// | ||||||
|     /// # Safety |     /// # Safety | ||||||
|     /// |     /// | ||||||
|     /// You must NOT call `poll` reentrantly on the same executor. |     /// You must NOT call `poll` reentrantly on the same executor. | ||||||
|     /// |     /// | ||||||
|     /// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you |     /// 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 |     /// 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 |     /// somehow schedule for `poll()` to be called later, at a time you know for sure there's | ||||||
|     /// no `poll()` already running. |     /// no `poll()` already running. | ||||||
|     pub unsafe fn poll(&'static self) { |     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")] | #[cfg(feature = "integrated-timers")] | ||||||
| struct TimerQueue; | struct TimerQueue; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 { |         Self { | ||||||
|             raw_task: None, |             raw_task: None, | ||||||
|             phantom: PhantomData, |             phantom: PhantomData, | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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,5 @@ | |||||||
| [package] | [package] | ||||||
| name = "embassy-hal-common" | name = "embassy-hal-internal" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| license = "MIT OR Apache-2.0" | license = "MIT OR Apache-2.0" | ||||||
							
								
								
									
										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. | ||||||
							
								
								
									
										258
									
								
								embassy-hal-internal/src/fmt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								embassy-hal-internal/src/fmt.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,258 @@ | |||||||
|  | #![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."); | ||||||
|  |  | ||||||
|  | macro_rules! assert { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::assert!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::assert!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! assert_eq { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::assert_eq!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::assert_eq!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! assert_ne { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::assert_ne!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::assert_ne!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug_assert { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::debug_assert!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug_assert!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug_assert_eq { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::debug_assert_eq!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug_assert_eq!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug_assert_ne { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::debug_assert_ne!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug_assert_ne!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! todo { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::todo!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::todo!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(not(feature = "defmt"))] | ||||||
|  | macro_rules! unreachable { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         ::core::unreachable!($($x)*) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "defmt")] | ||||||
|  | macro_rules! unreachable { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         ::defmt::unreachable!($($x)*) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! panic { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::panic!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::panic!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! trace { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::trace!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::trace!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::debug!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! info { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::info!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::info!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! warn { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::warn!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::warn!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! error { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::error!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::error!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "defmt")] | ||||||
|  | macro_rules! unwrap { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         ::defmt::unwrap!($($x)*) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(not(feature = "defmt"))] | ||||||
|  | macro_rules! unwrap { | ||||||
|  |     ($arg:expr) => { | ||||||
|  |         match $crate::fmt::Try::into_result($arg) { | ||||||
|  |             ::core::result::Result::Ok(t) => t, | ||||||
|  |             ::core::result::Result::Err(e) => { | ||||||
|  |                 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |     ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||||||
|  |         match $crate::fmt::Try::into_result($arg) { | ||||||
|  |             ::core::result::Result::Ok(t) => t, | ||||||
|  |             ::core::result::Result::Err(e) => { | ||||||
|  |                 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||||||
|  | pub struct NoneError; | ||||||
|  |  | ||||||
|  | pub trait Try { | ||||||
|  |     type Ok; | ||||||
|  |     type Error; | ||||||
|  |     fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T> Try for Option<T> { | ||||||
|  |     type Ok = T; | ||||||
|  |     type Error = NoneError; | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn into_result(self) -> Result<T, NoneError> { | ||||||
|  |         self.ok_or(NoneError) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T, E> Try for Result<T, E> { | ||||||
|  |     type Ok = T; | ||||||
|  |     type Error = E; | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn into_result(self) -> Self { | ||||||
|  |         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,6 @@ | |||||||
| #![no_std] | #![no_std] | ||||||
| #![allow(clippy::new_without_default)] | #![allow(clippy::new_without_default)] | ||||||
|  | #![doc = include_str!("../README.md")] | ||||||
| 
 | 
 | ||||||
| // This mod MUST go first, so that the others see its macros.
 | // This mod MUST go first, so that the others see its macros.
 | ||||||
| pub(crate) mod fmt; | pub(crate) mod fmt; | ||||||
| @@ -116,6 +116,7 @@ macro_rules! impl_peripheral { | |||||||
| 
 | 
 | ||||||
|             #[inline] |             #[inline] | ||||||
|             unsafe fn clone_unchecked(&self) -> Self::P { |             unsafe fn clone_unchecked(&self) -> Self::P { | ||||||
|  |                 #[allow(clippy::needless_update)] | ||||||
|                 $type { ..*self } |                 $type { ..*self } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -161,7 +161,7 @@ pub trait Peripheral: Sized { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<'b, T: Deref> Peripheral for T | impl<'b, T: DerefMut> Peripheral for T | ||||||
| where | where | ||||||
|     T::Target: Peripheral, |     T::Target: Peripheral, | ||||||
| { | { | ||||||
| @@ -12,7 +12,7 @@ target = "thumbv7em-none-eabi" | |||||||
|  |  | ||||||
| [features] | [features] | ||||||
| stm32wl = ["dep:embassy-stm32"] | stm32wl = ["dep:embassy-stm32"] | ||||||
| time = [] | time = ["embassy-time", "lorawan-device"] | ||||||
| defmt = ["dep:defmt", "lorawan-device/defmt"] | defmt = ["dep:defmt", "lorawan-device/defmt"] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| @@ -20,15 +20,15 @@ defmt = ["dep:defmt", "lorawan-device/defmt"] | |||||||
| defmt = { version = "0.3", optional = true } | defmt = { version = "0.3", optional = true } | ||||||
| log = { version = "0.4.14", optional = true } | log = { version = "0.4.14", optional = true } | ||||||
|  |  | ||||||
| embassy-time = { version = "0.1.0", path = "../embassy-time" } | embassy-time = { version = "0.1.3", path = "../embassy-time", optional = true } | ||||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync" } | embassy-sync = { version = "0.2.0", path = "../embassy-sync" } | ||||||
| embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true } | embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true } | ||||||
| embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } | embedded-hal-async = { version = "=1.0.0-rc.1" } | ||||||
| embedded-hal-async = { version = "=0.2.0-alpha.1" } |  | ||||||
| embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false } |  | ||||||
| futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } |  | ||||||
| embedded-hal = { version = "0.2", features = ["unproven"] } | embedded-hal = { version = "0.2", features = ["unproven"] } | ||||||
| bit_field = { version = "0.10" } |  | ||||||
|  |  | ||||||
|  | futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } | ||||||
| lora-phy = { version = "1" } | lora-phy = { version = "1" } | ||||||
| lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] } | lorawan-device = { version = "0.10.0", default-features = false, features = ["async"], optional = true } | ||||||
|  |  | ||||||
|  | [patch.crates-io] | ||||||
|  | lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "1323eccc1c470d4259f95f4f315d1be830d572a3"} | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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,6 +1,6 @@ | |||||||
| [package] | [package] | ||||||
| name = "embassy-macros" | name = "embassy-macros" | ||||||
| version = "0.2.0" | version = "0.2.1" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| license = "MIT OR Apache-2.0" | license = "MIT OR Apache-2.0" | ||||||
| description = "macros for creating the entry point and tasks for embassy-executor" | description = "macros for creating the entry point and tasks for embassy-executor" | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								embassy-net-adin1110/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								embassy-net-adin1110/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | [package] | ||||||
|  | name = "embassy-net-adin1110" | ||||||
|  | version = "0.2.0" | ||||||
|  | description = "embassy-net driver for the ADIN1110 ethernet chip" | ||||||
|  | keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet", "async"] | ||||||
|  | categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] | ||||||
|  | license = "MIT OR Apache-2.0" | ||||||
|  | edition = "2021" | ||||||
|  |  | ||||||
|  | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | heapless = "0.7.16" | ||||||
|  | defmt = { version = "0.3", optional = true } | ||||||
|  | log = { version = "0.4", default-features = false, optional = true } | ||||||
|  | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-rc.1" } | ||||||
|  | embedded-hal-async = { version = "=1.0.0-rc.1" } | ||||||
|  | embedded-hal-bus = { version = "=0.1.0-rc.1", features = ["async"] } | ||||||
|  | embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } | ||||||
|  | embassy-time = { version = "0.1.3" } | ||||||
|  | embassy-futures = { version = "0.1.0", path = "../embassy-futures" } | ||||||
|  | bitfield = "0.14.0" | ||||||
|  |  | ||||||
|  | [dev-dependencies] | ||||||
|  | # reenable when https://github.com/dbrgn/embedded-hal-mock/pull/86 is merged. | ||||||
|  | #embedded-hal-mock = { git = "https://github.com/dbrgn/embedded-hal-mock", branch = "1-alpha", features = ["embedded-hal-async", "eh1"] }] } | ||||||
|  | embedded-hal-mock = { git = "https://github.com/newAM/embedded-hal-mock", branch = "eh1-rc.1", features = ["embedded-hal-async", "eh1"] } | ||||||
|  | crc = "3.0.1" | ||||||
|  | env_logger = "0.10" | ||||||
|  | critical-section = { version = "1.1.2", features = ["std"] } | ||||||
|  | futures-test = "0.3.28" | ||||||
|  |  | ||||||
|  | [features] | ||||||
|  | default = [ ] | ||||||
|  | defmt = [ "dep:defmt", "embedded-hal-1/defmt-03" ] | ||||||
|  | log = ["dep:log"] | ||||||
|  |  | ||||||
|  | [package.metadata.embassy_docs] | ||||||
|  | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-adin1110-v$VERSION/embassy-net-adin1110/src/" | ||||||
|  | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-adin1110/src/" | ||||||
|  | target = "thumbv7em-none-eabi" | ||||||
|  | features = ["defmt"] | ||||||
							
								
								
									
										88
									
								
								embassy-net-adin1110/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								embassy-net-adin1110/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | # SPE ADIN1110 `embassy-net` integration | ||||||
|  |  | ||||||
|  | [`embassy-net`](https://crates.io/crates/embassy-net) integration for the `Analog ADIN1110` SPI SPE ethernet chips. | ||||||
|  |  | ||||||
|  | ## What is SPE or Single Pair Ethernet / 10 BASE-T1L | ||||||
|  |  | ||||||
|  | SPE stands for Single Pair Ethernet. As the names implies, SPE uses differential signalling with 2 wires (a twisted-pair) in a cable as the physical medium. | ||||||
|  | SPE is full-duplex - it can transmit and receive ethernet packets at the same time. SPE is still ethernet, only the physical layer is different. | ||||||
|  |  | ||||||
|  | SPE also supports [`PoDL (Power over Data Line)`](https://www.ti.com/lit/an/snla395/snla395.pdf), power delivery from 0.5 up to 50 Watts, similar to [`PoE`](https://en.wikipedia.org/wiki/Power_over_Ethernet), but an additional hardware and handshake protocol are needed. | ||||||
|  |  | ||||||
|  | SPE has many link speeds but only `10 BASE-T1L` is able to reach cable lengths up to 1000 meters in `2.4 Vpp` transmit amplitude. | ||||||
|  | Currently in 2023, none of the standards are compatible with each other. | ||||||
|  | Thus `10 BASE-T1L` won't work with a `10 BASE-T1S`, `100 BASE-T1` or any standard `x BASE-T`. | ||||||
|  |  | ||||||
|  | In the industry SPE is also called [`APL (Advanced Physical Layer)`](https://www.ethernet-apl.org), and is based on the `10 BASE-T1L` standard. | ||||||
|  |  | ||||||
|  | APL can be used in [`intrinsic safety applications/explosion hazardous areas`](https://en.wikipedia.org/wiki/Electrical_equipment_in_hazardous_areas) which has its own name and standard called [`2-WISE (2-wire intrinsically safe ethernet) IEC TS 60079-47:2021`](https://webstore.iec.ch/publication/64292). | ||||||
|  |  | ||||||
|  | `10 BASE-T1L` and `ADIN1110` are designed to support intrinsic safety applications. The power supply energy is fixed and PDoL is not supported. | ||||||
|  |  | ||||||
|  | ## Supported SPI modes | ||||||
|  |  | ||||||
|  | `ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/download/document/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) | ||||||
|  |  | ||||||
|  | Both modes support with and without additional CRC. | ||||||
|  | Currently only `Generic` SPI with or without CRC is supported. | ||||||
|  |  | ||||||
|  | *NOTE:* SPI Mode is selected by the hardware pins `SPI_CFG0` and `SPI_CFG1`. Software can't detect nor change the mode. | ||||||
|  |  | ||||||
|  | ## Hardware | ||||||
|  |  | ||||||
|  | - Tested on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html) with an `STM32L4S5QII3P`, see [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) for an example. | ||||||
|  | - [`SparkFun MicroMod Single Pair Ethernet Function Board`](https://www.sparkfun.com/products/19038) or [`SparkFun MicroMod Single Pair Ethernet Kit (End Of Life)`](https://www.sparkfun.com/products/19628), supporting multiple microcontrollers. **Make sure to check if it's a microcontroller that is supported by Embassy!** | ||||||
|  |  | ||||||
|  | ## Other SPE chips | ||||||
|  |  | ||||||
|  | * [`Analog ADIN2111`](https://www.analog.com/en/products/adin2111.html) 2 Port SPI version. Can work with this driver. | ||||||
|  | * [`Analog ADIN1100`](https://www.analog.com/en/products/adin1100.html) RGMII version. | ||||||
|  |  | ||||||
|  | ## Testing | ||||||
|  |  | ||||||
|  | ADIN1110 library can tested on the host with a mock SPI driver. | ||||||
|  |  | ||||||
|  | $ `cargo test --target x86_64-unknown-linux-gnu` | ||||||
|  |  | ||||||
|  | ## Benchmark | ||||||
|  |  | ||||||
|  | - Benchmarked on [`Analog Devices EVAL-ADIN1110EBZ`](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adin1110.html), with [`spe_adin1110_http_server`](../examples/stm32l4/src/bin/spe_adin1110_http_server.rs) example. | ||||||
|  |  | ||||||
|  | Basic `ping` benchmark | ||||||
|  | ```rust,ignore | ||||||
|  | # ping <IP> -c 60 | ||||||
|  |  | ||||||
|  | 60 packets transmitted, 60 received, 0% packet loss, time 59066ms | ||||||
|  | rtt min/avg/max/mdev = 1.089/1.161/1.237/0.018 ms | ||||||
|  |  | ||||||
|  | # ping <IP> -s 1472 -M do -c 60 | ||||||
|  |  | ||||||
|  | 60 packets transmitted, 60 received, 0% packet loss, time 59066ms | ||||||
|  | rtt min/avg/max/mdev = 5.122/5.162/6.177/0.133 ms | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | HTTP load generator benchmark with [`oha`](https://github.com/hatoo/oha) | ||||||
|  | ```rust,ignore | ||||||
|  | # oha -c 1 http://<IP> -z 60s | ||||||
|  | Summary: | ||||||
|  |   Success rate: 50.00% | ||||||
|  |   Total:        60.0005 secs | ||||||
|  |   Slowest:      0.0055 secs | ||||||
|  |   Fastest:      0.0033 secs | ||||||
|  |   Average:      0.0034 secs | ||||||
|  |   Requests/sec: 362.1971 | ||||||
|  |  | ||||||
|  |   Total data:   2.99 MiB | ||||||
|  |   Size/request: 289 B | ||||||
|  |   Size/sec:     51.11 KiB | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## 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. | ||||||
							
								
								
									
										359
									
								
								embassy-net-adin1110/src/crc32.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								embassy-net-adin1110/src/crc32.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,359 @@ | |||||||
|  | pub const CRC32R_LOOKUP_TABLE: [u32; 256] = [ | ||||||
|  |     0x0000_0000, | ||||||
|  |     0x7707_3096, | ||||||
|  |     0xEE0E_612C, | ||||||
|  |     0x9909_51BA, | ||||||
|  |     0x076D_C419, | ||||||
|  |     0x706A_F48F, | ||||||
|  |     0xE963_A535, | ||||||
|  |     0x9E64_95A3, | ||||||
|  |     0x0EDB_8832, | ||||||
|  |     0x79DC_B8A4, | ||||||
|  |     0xE0D5_E91E, | ||||||
|  |     0x97D2_D988, | ||||||
|  |     0x09B6_4C2B, | ||||||
|  |     0x7EB1_7CBD, | ||||||
|  |     0xE7B8_2D07, | ||||||
|  |     0x90BF_1D91, | ||||||
|  |     0x1DB7_1064, | ||||||
|  |     0x6AB0_20F2, | ||||||
|  |     0xF3B9_7148, | ||||||
|  |     0x84BE_41DE, | ||||||
|  |     0x1ADA_D47D, | ||||||
|  |     0x6DDD_E4EB, | ||||||
|  |     0xF4D4_B551, | ||||||
|  |     0x83D3_85C7, | ||||||
|  |     0x136C_9856, | ||||||
|  |     0x646B_A8C0, | ||||||
|  |     0xFD62_F97A, | ||||||
|  |     0x8A65_C9EC, | ||||||
|  |     0x1401_5C4F, | ||||||
|  |     0x6306_6CD9, | ||||||
|  |     0xFA0F_3D63, | ||||||
|  |     0x8D08_0DF5, | ||||||
|  |     0x3B6E_20C8, | ||||||
|  |     0x4C69_105E, | ||||||
|  |     0xD560_41E4, | ||||||
|  |     0xA267_7172, | ||||||
|  |     0x3C03_E4D1, | ||||||
|  |     0x4B04_D447, | ||||||
|  |     0xD20D_85FD, | ||||||
|  |     0xA50A_B56B, | ||||||
|  |     0x35B5_A8FA, | ||||||
|  |     0x42B2_986C, | ||||||
|  |     0xDBBB_C9D6, | ||||||
|  |     0xACBC_F940, | ||||||
|  |     0x32D8_6CE3, | ||||||
|  |     0x45DF_5C75, | ||||||
|  |     0xDCD6_0DCF, | ||||||
|  |     0xABD1_3D59, | ||||||
|  |     0x26D9_30AC, | ||||||
|  |     0x51DE_003A, | ||||||
|  |     0xC8D7_5180, | ||||||
|  |     0xBFD0_6116, | ||||||
|  |     0x21B4_F4B5, | ||||||
|  |     0x56B3_C423, | ||||||
|  |     0xCFBA_9599, | ||||||
|  |     0xB8BD_A50F, | ||||||
|  |     0x2802_B89E, | ||||||
|  |     0x5F05_8808, | ||||||
|  |     0xC60C_D9B2, | ||||||
|  |     0xB10B_E924, | ||||||
|  |     0x2F6F_7C87, | ||||||
|  |     0x5868_4C11, | ||||||
|  |     0xC161_1DAB, | ||||||
|  |     0xB666_2D3D, | ||||||
|  |     0x76DC_4190, | ||||||
|  |     0x01DB_7106, | ||||||
|  |     0x98D2_20BC, | ||||||
|  |     0xEFD5_102A, | ||||||
|  |     0x71B1_8589, | ||||||
|  |     0x06B6_B51F, | ||||||
|  |     0x9FBF_E4A5, | ||||||
|  |     0xE8B8_D433, | ||||||
|  |     0x7807_C9A2, | ||||||
|  |     0x0F00_F934, | ||||||
|  |     0x9609_A88E, | ||||||
|  |     0xE10E_9818, | ||||||
|  |     0x7F6A_0DBB, | ||||||
|  |     0x086D_3D2D, | ||||||
|  |     0x9164_6C97, | ||||||
|  |     0xE663_5C01, | ||||||
|  |     0x6B6B_51F4, | ||||||
|  |     0x1C6C_6162, | ||||||
|  |     0x8565_30D8, | ||||||
|  |     0xF262_004E, | ||||||
|  |     0x6C06_95ED, | ||||||
|  |     0x1B01_A57B, | ||||||
|  |     0x8208_F4C1, | ||||||
|  |     0xF50F_C457, | ||||||
|  |     0x65B0_D9C6, | ||||||
|  |     0x12B7_E950, | ||||||
|  |     0x8BBE_B8EA, | ||||||
|  |     0xFCB9_887C, | ||||||
|  |     0x62DD_1DDF, | ||||||
|  |     0x15DA_2D49, | ||||||
|  |     0x8CD3_7CF3, | ||||||
|  |     0xFBD4_4C65, | ||||||
|  |     0x4DB2_6158, | ||||||
|  |     0x3AB5_51CE, | ||||||
|  |     0xA3BC_0074, | ||||||
|  |     0xD4BB_30E2, | ||||||
|  |     0x4ADF_A541, | ||||||
|  |     0x3DD8_95D7, | ||||||
|  |     0xA4D1_C46D, | ||||||
|  |     0xD3D6_F4FB, | ||||||
|  |     0x4369_E96A, | ||||||
|  |     0x346E_D9FC, | ||||||
|  |     0xAD67_8846, | ||||||
|  |     0xDA60_B8D0, | ||||||
|  |     0x4404_2D73, | ||||||
|  |     0x3303_1DE5, | ||||||
|  |     0xAA0A_4C5F, | ||||||
|  |     0xDD0D_7CC9, | ||||||
|  |     0x5005_713C, | ||||||
|  |     0x2702_41AA, | ||||||
|  |     0xBE0B_1010, | ||||||
|  |     0xC90C_2086, | ||||||
|  |     0x5768_B525, | ||||||
|  |     0x206F_85B3, | ||||||
|  |     0xB966_D409, | ||||||
|  |     0xCE61_E49F, | ||||||
|  |     0x5EDE_F90E, | ||||||
|  |     0x29D9_C998, | ||||||
|  |     0xB0D0_9822, | ||||||
|  |     0xC7D7_A8B4, | ||||||
|  |     0x59B3_3D17, | ||||||
|  |     0x2EB4_0D81, | ||||||
|  |     0xB7BD_5C3B, | ||||||
|  |     0xC0BA_6CAD, | ||||||
|  |     0xEDB8_8320, | ||||||
|  |     0x9ABF_B3B6, | ||||||
|  |     0x03B6_E20C, | ||||||
|  |     0x74B1_D29A, | ||||||
|  |     0xEAD5_4739, | ||||||
|  |     0x9DD2_77AF, | ||||||
|  |     0x04DB_2615, | ||||||
|  |     0x73DC_1683, | ||||||
|  |     0xE363_0B12, | ||||||
|  |     0x9464_3B84, | ||||||
|  |     0x0D6D_6A3E, | ||||||
|  |     0x7A6A_5AA8, | ||||||
|  |     0xE40E_CF0B, | ||||||
|  |     0x9309_FF9D, | ||||||
|  |     0x0A00_AE27, | ||||||
|  |     0x7D07_9EB1, | ||||||
|  |     0xF00F_9344, | ||||||
|  |     0x8708_A3D2, | ||||||
|  |     0x1E01_F268, | ||||||
|  |     0x6906_C2FE, | ||||||
|  |     0xF762_575D, | ||||||
|  |     0x8065_67CB, | ||||||
|  |     0x196C_3671, | ||||||
|  |     0x6E6B_06E7, | ||||||
|  |     0xFED4_1B76, | ||||||
|  |     0x89D3_2BE0, | ||||||
|  |     0x10DA_7A5A, | ||||||
|  |     0x67DD_4ACC, | ||||||
|  |     0xF9B9_DF6F, | ||||||
|  |     0x8EBE_EFF9, | ||||||
|  |     0x17B7_BE43, | ||||||
|  |     0x60B0_8ED5, | ||||||
|  |     0xD6D6_A3E8, | ||||||
|  |     0xA1D1_937E, | ||||||
|  |     0x38D8_C2C4, | ||||||
|  |     0x4FDF_F252, | ||||||
|  |     0xD1BB_67F1, | ||||||
|  |     0xA6BC_5767, | ||||||
|  |     0x3FB5_06DD, | ||||||
|  |     0x48B2_364B, | ||||||
|  |     0xD80D_2BDA, | ||||||
|  |     0xAF0A_1B4C, | ||||||
|  |     0x3603_4AF6, | ||||||
|  |     0x4104_7A60, | ||||||
|  |     0xDF60_EFC3, | ||||||
|  |     0xA867_DF55, | ||||||
|  |     0x316E_8EEF, | ||||||
|  |     0x4669_BE79, | ||||||
|  |     0xCB61_B38C, | ||||||
|  |     0xBC66_831A, | ||||||
|  |     0x256F_D2A0, | ||||||
|  |     0x5268_E236, | ||||||
|  |     0xCC0C_7795, | ||||||
|  |     0xBB0B_4703, | ||||||
|  |     0x2202_16B9, | ||||||
|  |     0x5505_262F, | ||||||
|  |     0xC5BA_3BBE, | ||||||
|  |     0xB2BD_0B28, | ||||||
|  |     0x2BB4_5A92, | ||||||
|  |     0x5CB3_6A04, | ||||||
|  |     0xC2D7_FFA7, | ||||||
|  |     0xB5D0_CF31, | ||||||
|  |     0x2CD9_9E8B, | ||||||
|  |     0x5BDE_AE1D, | ||||||
|  |     0x9B64_C2B0, | ||||||
|  |     0xEC63_F226, | ||||||
|  |     0x756A_A39C, | ||||||
|  |     0x026D_930A, | ||||||
|  |     0x9C09_06A9, | ||||||
|  |     0xEB0E_363F, | ||||||
|  |     0x7207_6785, | ||||||
|  |     0x0500_5713, | ||||||
|  |     0x95BF_4A82, | ||||||
|  |     0xE2B8_7A14, | ||||||
|  |     0x7BB1_2BAE, | ||||||
|  |     0x0CB6_1B38, | ||||||
|  |     0x92D2_8E9B, | ||||||
|  |     0xE5D5_BE0D, | ||||||
|  |     0x7CDC_EFB7, | ||||||
|  |     0x0BDB_DF21, | ||||||
|  |     0x86D3_D2D4, | ||||||
|  |     0xF1D4_E242, | ||||||
|  |     0x68DD_B3F8, | ||||||
|  |     0x1FDA_836E, | ||||||
|  |     0x81BE_16CD, | ||||||
|  |     0xF6B9_265B, | ||||||
|  |     0x6FB0_77E1, | ||||||
|  |     0x18B7_4777, | ||||||
|  |     0x8808_5AE6, | ||||||
|  |     0xFF0F_6A70, | ||||||
|  |     0x6606_3BCA, | ||||||
|  |     0x1101_0B5C, | ||||||
|  |     0x8F65_9EFF, | ||||||
|  |     0xF862_AE69, | ||||||
|  |     0x616B_FFD3, | ||||||
|  |     0x166C_CF45, | ||||||
|  |     0xA00A_E278, | ||||||
|  |     0xD70D_D2EE, | ||||||
|  |     0x4E04_8354, | ||||||
|  |     0x3903_B3C2, | ||||||
|  |     0xA767_2661, | ||||||
|  |     0xD060_16F7, | ||||||
|  |     0x4969_474D, | ||||||
|  |     0x3E6E_77DB, | ||||||
|  |     0xAED1_6A4A, | ||||||
|  |     0xD9D6_5ADC, | ||||||
|  |     0x40DF_0B66, | ||||||
|  |     0x37D8_3BF0, | ||||||
|  |     0xA9BC_AE53, | ||||||
|  |     0xDEBB_9EC5, | ||||||
|  |     0x47B2_CF7F, | ||||||
|  |     0x30B5_FFE9, | ||||||
|  |     0xBDBD_F21C, | ||||||
|  |     0xCABA_C28A, | ||||||
|  |     0x53B3_9330, | ||||||
|  |     0x24B4_A3A6, | ||||||
|  |     0xBAD0_3605, | ||||||
|  |     0xCDD7_0693, | ||||||
|  |     0x54DE_5729, | ||||||
|  |     0x23D9_67BF, | ||||||
|  |     0xB366_7A2E, | ||||||
|  |     0xC461_4AB8, | ||||||
|  |     0x5D68_1B02, | ||||||
|  |     0x2A6F_2B94, | ||||||
|  |     0xB40B_BE37, | ||||||
|  |     0xC30C_8EA1, | ||||||
|  |     0x5A05_DF1B, | ||||||
|  |     0x2D02_EF8D, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | /// Generate Ethernet Frame Check Sequence | ||||||
|  | #[allow(non_camel_case_types)] | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct ETH_FCS(pub u32); | ||||||
|  |  | ||||||
|  | impl ETH_FCS { | ||||||
|  |     pub const CRC32_OK: u32 = 0x2144_df1c; | ||||||
|  |  | ||||||
|  |     #[must_use] | ||||||
|  |     pub fn new(data: &[u8]) -> Self { | ||||||
|  |         let fcs = data.iter().fold(u32::MAX, |crc, byte| { | ||||||
|  |             let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; | ||||||
|  |             CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) | ||||||
|  |         }) ^ u32::MAX; | ||||||
|  |         Self(fcs) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[must_use] | ||||||
|  |     pub fn update(self, data: &[u8]) -> Self { | ||||||
|  |         let fcs = data.iter().fold(self.0 ^ u32::MAX, |crc, byte| { | ||||||
|  |             let idx = u8::try_from(crc & 0xFF).unwrap() ^ byte; | ||||||
|  |             CRC32R_LOOKUP_TABLE[usize::from(idx)] ^ (crc >> 8) | ||||||
|  |         }) ^ u32::MAX; | ||||||
|  |         Self(fcs) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[must_use] | ||||||
|  |     pub fn crc_ok(&self) -> bool { | ||||||
|  |         self.0 == Self::CRC32_OK | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[must_use] | ||||||
|  |     pub fn hton_bytes(&self) -> [u8; 4] { | ||||||
|  |         self.0.to_le_bytes() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[must_use] | ||||||
|  |     pub fn hton(&self) -> u32 { | ||||||
|  |         self.0.to_le() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use super::*; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn crc32_ethernet_frame() { | ||||||
|  |         let packet_a = &[ | ||||||
|  |             0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xff, 0x06, 0x00, 0x01, 0x08, 0x00, | ||||||
|  |             0x06, 0x04, 0x00, 0x01, 0x00, 0xe0, 0x4c, 0x68, 0x0e, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |             0x00, 0x00, 0xc0, 0xa8, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x65, 0x90, 0x3d, | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         let packet_b = &[ | ||||||
|  |             0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, | ||||||
|  |             0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, | ||||||
|  |             0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Packet A | ||||||
|  |         let own_crc = ETH_FCS::new(&packet_a[0..60]); | ||||||
|  |         let crc_bytes = own_crc.hton_bytes(); | ||||||
|  |         println!("{:08x} {:02x?}", own_crc.0, crc_bytes); | ||||||
|  |         assert_eq!(&crc_bytes, &packet_a[60..64]); | ||||||
|  |  | ||||||
|  |         let own_crc = ETH_FCS::new(packet_a); | ||||||
|  |         println!("{:08x}", own_crc.0); | ||||||
|  |         assert_eq!(own_crc.0, ETH_FCS::CRC32_OK); | ||||||
|  |  | ||||||
|  |         // Packet B | ||||||
|  |         let own_crc = ETH_FCS::new(&packet_b[0..60]); | ||||||
|  |         let crc_bytes = own_crc.hton_bytes(); | ||||||
|  |         println!("{:08x} {:02x?}", own_crc.0, crc_bytes); | ||||||
|  |         assert_eq!(&crc_bytes, &packet_b[60..64]); | ||||||
|  |  | ||||||
|  |         let own_crc = ETH_FCS::new(packet_b); | ||||||
|  |         println!("{:08x}", own_crc.0); | ||||||
|  |         assert_eq!(own_crc.0, ETH_FCS::CRC32_OK); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn crc32_update() { | ||||||
|  |         let full_data = &[ | ||||||
|  |             0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x00, 0xe0, 0x4c, 0x68, 0xee, 0xee, 0xdd, 0x06, 0x00, 0x01, 0x08, 0x00, | ||||||
|  |             0x06, 0x04, 0x00, 0x02, 0x00, 0xe0, 0x4c, 0x68, 0x09, 0xde, 0xc0, 0xa8, 0x01, 0x02, 0x12, 0x34, 0x56, 0x78, | ||||||
|  |             0x9a, 0xbc, 0xc0, 0xa8, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3d, 0x67, 0x7c, | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         let (part_a, part_b) = full_data.split_at(16); | ||||||
|  |         let crc_partially = ETH_FCS::new(part_a).update(part_b); | ||||||
|  |  | ||||||
|  |         let crc_full = ETH_FCS::new(full_data); | ||||||
|  |  | ||||||
|  |         assert_eq!(crc_full.0, crc_partially.0); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								embassy-net-adin1110/src/crc8.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								embassy-net-adin1110/src/crc8.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | /// CRC-8/ITU | ||||||
|  | const CRC8X_TABLE: [u8; 256] = [ | ||||||
|  |     0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, | ||||||
|  |     0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, | ||||||
|  |     0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, | ||||||
|  |     0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, | ||||||
|  |     0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, | ||||||
|  |     0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, | ||||||
|  |     0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, | ||||||
|  |     0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, | ||||||
|  |     0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, | ||||||
|  |     0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, | ||||||
|  |     0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, | ||||||
|  |     0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, | ||||||
|  |     0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, | ||||||
|  |     0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | /// Calculate the crc of a pease of data. | ||||||
|  | pub fn crc8(data: &[u8]) -> u8 { | ||||||
|  |     data.iter().fold(0, |crc, &byte| CRC8X_TABLE[usize::from(byte ^ crc)]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use ::crc::{Crc, CRC_8_SMBUS}; | ||||||
|  |  | ||||||
|  |     use super::crc8; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn spi_header_crc8() { | ||||||
|  |         let data = &[0x80, 0x00]; | ||||||
|  |  | ||||||
|  |         let c = Crc::<u8>::new(&CRC_8_SMBUS); | ||||||
|  |         let mut dig = c.digest(); | ||||||
|  |         dig.update(data); | ||||||
|  |         let sw_crc = dig.finalize(); | ||||||
|  |  | ||||||
|  |         let own_crc = crc8(data); | ||||||
|  |  | ||||||
|  |         assert_eq!(own_crc, sw_crc); | ||||||
|  |         assert_eq!(own_crc, 182); | ||||||
|  |  | ||||||
|  |         let data = &[0x80, 0x01]; | ||||||
|  |         let mut dig = c.digest(); | ||||||
|  |         dig.update(data); | ||||||
|  |         let sw_crc = dig.finalize(); | ||||||
|  |         let own_crc = crc8(data); | ||||||
|  |  | ||||||
|  |         assert_eq!(own_crc, sw_crc); | ||||||
|  |         assert_eq!(own_crc, 177); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
| 
 | 
 | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  | 
 | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | compile_error!("You may not enable both `defmt` and `log` features."); | ||||||
| 
 | 
 | ||||||
| @@ -111,7 +113,7 @@ macro_rules! trace { | |||||||
|             #[cfg(feature = "defmt")] |             #[cfg(feature = "defmt")] | ||||||
|             ::defmt::trace!($s $(, $x)*); |             ::defmt::trace!($s $(, $x)*); | ||||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|             let _ = ($( & $x ),*); |             let _ignored = ($( & $x ),*); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| @@ -124,7 +126,7 @@ macro_rules! debug { | |||||||
|             #[cfg(feature = "defmt")] |             #[cfg(feature = "defmt")] | ||||||
|             ::defmt::debug!($s $(, $x)*); |             ::defmt::debug!($s $(, $x)*); | ||||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|             let _ = ($( & $x ),*); |             let _ignored = ($( & $x ),*); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| @@ -137,7 +139,7 @@ macro_rules! info { | |||||||
|             #[cfg(feature = "defmt")] |             #[cfg(feature = "defmt")] | ||||||
|             ::defmt::info!($s $(, $x)*); |             ::defmt::info!($s $(, $x)*); | ||||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|             let _ = ($( & $x ),*); |             let _ignored = ($( & $x ),*); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| @@ -150,7 +152,7 @@ macro_rules! warn { | |||||||
|             #[cfg(feature = "defmt")] |             #[cfg(feature = "defmt")] | ||||||
|             ::defmt::warn!($s $(, $x)*); |             ::defmt::warn!($s $(, $x)*); | ||||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|             let _ = ($( & $x ),*); |             let _ignored = ($( & $x ),*); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| @@ -163,7 +165,7 @@ macro_rules! error { | |||||||
|             #[cfg(feature = "defmt")] |             #[cfg(feature = "defmt")] | ||||||
|             ::defmt::error!($s $(, $x)*); |             ::defmt::error!($s $(, $x)*); | ||||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|             let _ = ($( & $x ),*); |             let _ignored = ($( & $x ),*); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| @@ -223,3 +225,30 @@ impl<T, E> Try for Result<T, E> { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | pub 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) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1323
									
								
								embassy-net-adin1110/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1323
									
								
								embassy-net-adin1110/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										176
									
								
								embassy-net-adin1110/src/mdio.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								embassy-net-adin1110/src/mdio.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | |||||||
|  | /// PHY Address: (0..=0x1F), 5-bits long. | ||||||
|  | #[allow(dead_code)] | ||||||
|  | type PhyAddr = u8; | ||||||
|  |  | ||||||
|  | /// PHY Register: (0..=0x1F), 5-bits long. | ||||||
|  | #[allow(dead_code)] | ||||||
|  | type RegC22 = u8; | ||||||
|  |  | ||||||
|  | /// PHY Register Clause 45. | ||||||
|  | #[allow(dead_code)] | ||||||
|  | type RegC45 = u16; | ||||||
|  |  | ||||||
|  | /// PHY Register Value | ||||||
|  | #[allow(dead_code)] | ||||||
|  | type RegVal = u16; | ||||||
|  |  | ||||||
|  | #[allow(dead_code)] | ||||||
|  | const REG13: RegC22 = 13; | ||||||
|  | #[allow(dead_code)] | ||||||
|  | const REG14: RegC22 = 14; | ||||||
|  |  | ||||||
|  | #[allow(dead_code)] | ||||||
|  | const PHYADDR_MASK: u8 = 0x1f; | ||||||
|  | #[allow(dead_code)] | ||||||
|  | const DEV_MASK: u8 = 0x1f; | ||||||
|  |  | ||||||
|  | #[allow(dead_code)] | ||||||
|  | #[repr(u16)] | ||||||
|  | enum Reg13Op { | ||||||
|  |     Addr = 0b00 << 14, | ||||||
|  |     Write = 0b01 << 14, | ||||||
|  |     PostReadIncAddr = 0b10 << 14, | ||||||
|  |     Read = 0b11 << 14, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// `MdioBus` trait | ||||||
|  | /// Driver needs to implement the Clause 22 | ||||||
|  | /// Optional Clause 45 is the device supports this. | ||||||
|  | /// | ||||||
|  | /// Clause 45 methodes are bases on <https://www.ieee802.org/3/efm/public/nov02/oam/pannell_oam_1_1102.pdf> | ||||||
|  | pub trait MdioBus { | ||||||
|  |     type Error; | ||||||
|  |  | ||||||
|  |     /// Read, Clause 22 | ||||||
|  |     async fn read_cl22(&mut self, phy_id: PhyAddr, reg: RegC22) -> Result<RegVal, Self::Error>; | ||||||
|  |  | ||||||
|  |     /// Write, Clause 22 | ||||||
|  |     async fn write_cl22(&mut self, phy_id: PhyAddr, reg: RegC22, reg_val: RegVal) -> Result<(), Self::Error>; | ||||||
|  |  | ||||||
|  |     /// Read, Clause 45 | ||||||
|  |     /// This is the default implementation. | ||||||
|  |     /// Many hardware these days support direct Clause 45 operations. | ||||||
|  |     /// Implement this function when your hardware supports it. | ||||||
|  |     async fn read_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45)) -> Result<RegVal, Self::Error> { | ||||||
|  |         // Write FN | ||||||
|  |         let val = (Reg13Op::Addr as RegVal) | RegVal::from(regc45.0 & DEV_MASK); | ||||||
|  |  | ||||||
|  |         self.write_cl22(phy_id, REG13, val).await?; | ||||||
|  |         // Write Addr | ||||||
|  |         self.write_cl22(phy_id, REG14, regc45.1).await?; | ||||||
|  |  | ||||||
|  |         // Write FN | ||||||
|  |         let val = (Reg13Op::Read as RegVal) | RegVal::from(regc45.0 & DEV_MASK); | ||||||
|  |         self.write_cl22(phy_id, REG13, val).await?; | ||||||
|  |         // Write Addr | ||||||
|  |         self.read_cl22(phy_id, REG14).await | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Write, Clause 45 | ||||||
|  |     /// This is the default implementation. | ||||||
|  |     /// Many hardware these days support direct Clause 45 operations. | ||||||
|  |     /// Implement this function when your hardware supports it. | ||||||
|  |     async fn write_cl45(&mut self, phy_id: PhyAddr, regc45: (u8, RegC45), reg_val: RegVal) -> Result<(), Self::Error> { | ||||||
|  |         let dev_addr = RegVal::from(regc45.0 & DEV_MASK); | ||||||
|  |         let reg = regc45.1; | ||||||
|  |  | ||||||
|  |         // Write FN | ||||||
|  |         let val = (Reg13Op::Addr as RegVal) | dev_addr; | ||||||
|  |         self.write_cl22(phy_id, REG13, val).await?; | ||||||
|  |         // Write Addr | ||||||
|  |         self.write_cl22(phy_id, REG14, reg).await?; | ||||||
|  |  | ||||||
|  |         // Write FN | ||||||
|  |         let val = (Reg13Op::Write as RegVal) | dev_addr; | ||||||
|  |         self.write_cl22(phy_id, REG13, val).await?; | ||||||
|  |         // Write Addr | ||||||
|  |         self.write_cl22(phy_id, REG14, reg_val).await | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use core::convert::Infallible; | ||||||
|  |  | ||||||
|  |     use super::{MdioBus, PhyAddr, RegC22, RegVal}; | ||||||
|  |  | ||||||
|  |     #[derive(Debug, PartialEq, Eq)] | ||||||
|  |     enum A { | ||||||
|  |         Read(PhyAddr, RegC22), | ||||||
|  |         Write(PhyAddr, RegC22, RegVal), | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     struct MockMdioBus(Vec<A>); | ||||||
|  |  | ||||||
|  |     impl MockMdioBus { | ||||||
|  |         pub fn clear(&mut self) { | ||||||
|  |             self.0.clear(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl MdioBus for MockMdioBus { | ||||||
|  |         type Error = Infallible; | ||||||
|  |  | ||||||
|  |         async fn write_cl22( | ||||||
|  |             &mut self, | ||||||
|  |             phy_id: super::PhyAddr, | ||||||
|  |             reg: super::RegC22, | ||||||
|  |             reg_val: super::RegVal, | ||||||
|  |         ) -> Result<(), Self::Error> { | ||||||
|  |             self.0.push(A::Write(phy_id, reg, reg_val)); | ||||||
|  |             Ok(()) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         async fn read_cl22( | ||||||
|  |             &mut self, | ||||||
|  |             phy_id: super::PhyAddr, | ||||||
|  |             reg: super::RegC22, | ||||||
|  |         ) -> Result<super::RegVal, Self::Error> { | ||||||
|  |             self.0.push(A::Read(phy_id, reg)); | ||||||
|  |             Ok(0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[futures_test::test] | ||||||
|  |     async fn read_test() { | ||||||
|  |         let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); | ||||||
|  |  | ||||||
|  |         mdiobus.clear(); | ||||||
|  |         mdiobus.read_cl22(0x01, 0x00).await.unwrap(); | ||||||
|  |         assert_eq!(mdiobus.0, vec![A::Read(0x01, 0x00)]); | ||||||
|  |  | ||||||
|  |         mdiobus.clear(); | ||||||
|  |         mdiobus.read_cl45(0x01, (0xBB, 0x1234)).await.unwrap(); | ||||||
|  |         assert_eq!( | ||||||
|  |             mdiobus.0, | ||||||
|  |             vec![ | ||||||
|  |                 #[allow(clippy::identity_op)] | ||||||
|  |                 A::Write(0x01, 13, (0b00 << 14) | 27), | ||||||
|  |                 A::Write(0x01, 14, 0x1234), | ||||||
|  |                 A::Write(0x01, 13, (0b11 << 14) | 27), | ||||||
|  |                 A::Read(0x01, 14) | ||||||
|  |             ] | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[futures_test::test] | ||||||
|  |     async fn write_test() { | ||||||
|  |         let mut mdiobus = MockMdioBus(Vec::with_capacity(20)); | ||||||
|  |  | ||||||
|  |         mdiobus.clear(); | ||||||
|  |         mdiobus.write_cl22(0x01, 0x00, 0xABCD).await.unwrap(); | ||||||
|  |         assert_eq!(mdiobus.0, vec![A::Write(0x01, 0x00, 0xABCD)]); | ||||||
|  |  | ||||||
|  |         mdiobus.clear(); | ||||||
|  |         mdiobus.write_cl45(0x01, (0xBB, 0x1234), 0xABCD).await.unwrap(); | ||||||
|  |         assert_eq!( | ||||||
|  |             mdiobus.0, | ||||||
|  |             vec![ | ||||||
|  |                 A::Write(0x01, 13, 27), | ||||||
|  |                 A::Write(0x01, 14, 0x1234), | ||||||
|  |                 A::Write(0x01, 13, (0b01 << 14) | 27), | ||||||
|  |                 A::Write(0x01, 14, 0xABCD) | ||||||
|  |             ] | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										143
									
								
								embassy-net-adin1110/src/phy.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								embassy-net-adin1110/src/phy.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | use crate::mdio::MdioBus; | ||||||
|  |  | ||||||
|  | #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)] | ||||||
|  | #[repr(u8)] | ||||||
|  | /// Clause 22 Registers | ||||||
|  | pub enum RegsC22 { | ||||||
|  |     /// MII Control Register | ||||||
|  |     CONTROL = 0x00, | ||||||
|  |     /// MII Status Register | ||||||
|  |     STATUS = 0x01, | ||||||
|  |     /// PHY Identifier 1 Register | ||||||
|  |     PHY_ID1 = 0x02, | ||||||
|  |     /// PHY Identifier 2 Register. | ||||||
|  |     PHY_ID2 = 0x03, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Clause 45 Registers | ||||||
|  | #[allow(non_snake_case, dead_code)] | ||||||
|  | pub mod RegsC45 { | ||||||
|  |     /// Device Address: 0x01 | ||||||
|  |     #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||||||
|  |     #[repr(u16)] | ||||||
|  |     pub enum DA1 { | ||||||
|  |         /// PMA/PMD Control 1 Register | ||||||
|  |         PMA_PMD_CNTRL1 = 0x0000, | ||||||
|  |         /// PMA/PMD Status 1 Register | ||||||
|  |         PMA_PMD_STAT1 = 0x0001, | ||||||
|  |         /// MSE Value Register | ||||||
|  |         MSE_VAL = 0x830B, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl DA1 { | ||||||
|  |         #[must_use] | ||||||
|  |         pub fn into(self) -> (u8, u16) { | ||||||
|  |             (0x01, self as u16) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Device Address: 0x03 | ||||||
|  |     #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||||||
|  |     #[repr(u16)] | ||||||
|  |     pub enum DA3 { | ||||||
|  |         /// PCS Control 1 Register | ||||||
|  |         PCS_CNTRL1 = 0x0000, | ||||||
|  |         /// PCS Status 1 Register | ||||||
|  |         PCS_STAT1 = 0x0001, | ||||||
|  |         /// PCS Status 2 Register | ||||||
|  |         PCS_STAT2 = 0x0008, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl DA3 { | ||||||
|  |         #[must_use] | ||||||
|  |         pub fn into(self) -> (u8, u16) { | ||||||
|  |             (0x03, self as u16) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Device Address: 0x07 | ||||||
|  |     #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||||||
|  |     #[repr(u16)] | ||||||
|  |     pub enum DA7 { | ||||||
|  |         /// Extra Autonegotiation Status Register | ||||||
|  |         AN_STATUS_EXTRA = 0x8001, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl DA7 { | ||||||
|  |         #[must_use] | ||||||
|  |         pub fn into(self) -> (u8, u16) { | ||||||
|  |             (0x07, self as u16) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Device Address: 0x1E | ||||||
|  |     #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||||||
|  |     #[repr(u16)] | ||||||
|  |     pub enum DA1E { | ||||||
|  |         /// System Interrupt Status Register | ||||||
|  |         CRSM_IRQ_STATUS = 0x0010, | ||||||
|  |         /// System Interrupt Mask Register | ||||||
|  |         CRSM_IRQ_MASK = 0x0020, | ||||||
|  |         /// Pin Mux Configuration 1 Register | ||||||
|  |         DIGIO_PINMUX = 0x8c56, | ||||||
|  |         /// LED Control Register. | ||||||
|  |         LED_CNTRL = 0x8C82, | ||||||
|  |         /// LED Polarity Register | ||||||
|  |         LED_POLARITY = 0x8C83, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl DA1E { | ||||||
|  |         #[must_use] | ||||||
|  |         pub fn into(self) -> (u8, u16) { | ||||||
|  |             (0x1e, self as u16) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Device Address: 0x1F | ||||||
|  |     #[allow(non_camel_case_types, clippy::upper_case_acronyms)] | ||||||
|  |     #[repr(u16)] | ||||||
|  |     pub enum DA1F { | ||||||
|  |         /// PHY Subsystem Interrupt Status Register | ||||||
|  |         PHY_SYBSYS_IRQ_STATUS = 0x0011, | ||||||
|  |         /// PHY Subsystem Interrupt Mask Register | ||||||
|  |         PHY_SYBSYS_IRQ_MASK = 0x0021, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl DA1F { | ||||||
|  |         #[must_use] | ||||||
|  |         pub fn into(self) -> (u8, u16) { | ||||||
|  |             (0x1f, self as u16) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// 10-BASE-T1x PHY functions. | ||||||
|  | pub struct Phy10BaseT1x(u8); | ||||||
|  |  | ||||||
|  | impl Default for Phy10BaseT1x { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self(0x01) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Phy10BaseT1x { | ||||||
|  |     /// Get the both parts of the PHYID. | ||||||
|  |     pub async fn get_id<MDIOBUS, MDE>(&self, mdiobus: &mut MDIOBUS) -> Result<u32, MDE> | ||||||
|  |     where | ||||||
|  |         MDIOBUS: MdioBus<Error = MDE>, | ||||||
|  |         MDE: core::fmt::Debug, | ||||||
|  |     { | ||||||
|  |         let mut phyid = u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID1 as u8).await?) << 16; | ||||||
|  |         phyid |= u32::from(mdiobus.read_cl22(self.0, RegsC22::PHY_ID2 as u8).await?); | ||||||
|  |         Ok(phyid) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get the Mean Squared Error Value. | ||||||
|  |     pub async fn get_sqi<MDIOBUS, MDE>(&self, mdiobus: &mut MDIOBUS) -> Result<u16, MDE> | ||||||
|  |     where | ||||||
|  |         MDIOBUS: MdioBus<Error = MDE>, | ||||||
|  |         MDE: core::fmt::Debug, | ||||||
|  |     { | ||||||
|  |         mdiobus.read_cl45(self.0, RegsC45::DA1::MSE_VAL.into()).await | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										416
									
								
								embassy-net-adin1110/src/regs.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								embassy-net-adin1110/src/regs.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,416 @@ | |||||||
|  | use core::fmt::{Debug, Display}; | ||||||
|  |  | ||||||
|  | use bitfield::{bitfield, bitfield_bitrange, bitfield_fields}; | ||||||
|  |  | ||||||
|  | #[allow(non_camel_case_types)] | ||||||
|  | #[derive(Debug, Copy, Clone)] | ||||||
|  | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
|  | #[repr(u16)] | ||||||
|  | /// SPI REGISTER DETAILS | ||||||
|  | /// Table 38. | ||||||
|  | pub enum SpiRegisters { | ||||||
|  |     IDVER = 0x00, | ||||||
|  |     PHYID = 0x01, | ||||||
|  |     CAPABILITY = 0x02, | ||||||
|  |     RESET = 0x03, | ||||||
|  |     CONFIG0 = 0x04, | ||||||
|  |     CONFIG2 = 0x06, | ||||||
|  |     STATUS0 = 0x08, | ||||||
|  |     STATUS1 = 0x09, | ||||||
|  |     IMASK0 = 0x0C, | ||||||
|  |     IMASK1 = 0x0D, | ||||||
|  |     MDIO_ACC = 0x20, | ||||||
|  |     TX_FSIZE = 0x30, | ||||||
|  |     TX = 0x31, | ||||||
|  |     TX_SPACE = 0x32, | ||||||
|  |     FIFO_CLR = 0x36, | ||||||
|  |     ADDR_FILT_UPR0 = 0x50, | ||||||
|  |     ADDR_FILT_LWR0 = 0x51, | ||||||
|  |     ADDR_FILT_UPR1 = 0x52, | ||||||
|  |     ADDR_FILT_LWR1 = 0x53, | ||||||
|  |     ADDR_MSK_LWR0 = 0x70, | ||||||
|  |     ADDR_MSK_UPR0 = 0x71, | ||||||
|  |     ADDR_MSK_LWR1 = 0x72, | ||||||
|  |     ADDR_MSK_UPR1 = 0x73, | ||||||
|  |     RX_FSIZE = 0x90, | ||||||
|  |     RX = 0x91, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Display for SpiRegisters { | ||||||
|  |     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||||
|  |         write!(f, "{self:?}") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<SpiRegisters> for u16 { | ||||||
|  |     fn from(val: SpiRegisters) -> Self { | ||||||
|  |         val as u16 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<u16> for SpiRegisters { | ||||||
|  |     fn from(value: u16) -> Self { | ||||||
|  |         match value { | ||||||
|  |             0x00 => Self::IDVER, | ||||||
|  |             0x01 => Self::PHYID, | ||||||
|  |             0x02 => Self::CAPABILITY, | ||||||
|  |             0x03 => Self::RESET, | ||||||
|  |             0x04 => Self::CONFIG0, | ||||||
|  |             0x06 => Self::CONFIG2, | ||||||
|  |             0x08 => Self::STATUS0, | ||||||
|  |             0x09 => Self::STATUS1, | ||||||
|  |             0x0C => Self::IMASK0, | ||||||
|  |             0x0D => Self::IMASK1, | ||||||
|  |             0x20 => Self::MDIO_ACC, | ||||||
|  |             0x30 => Self::TX_FSIZE, | ||||||
|  |             0x31 => Self::TX, | ||||||
|  |             0x32 => Self::TX_SPACE, | ||||||
|  |             0x36 => Self::FIFO_CLR, | ||||||
|  |             0x50 => Self::ADDR_FILT_UPR0, | ||||||
|  |             0x51 => Self::ADDR_FILT_LWR0, | ||||||
|  |             0x52 => Self::ADDR_FILT_UPR1, | ||||||
|  |             0x53 => Self::ADDR_FILT_LWR1, | ||||||
|  |             0x70 => Self::ADDR_MSK_LWR0, | ||||||
|  |             0x71 => Self::ADDR_MSK_UPR0, | ||||||
|  |             0x72 => Self::ADDR_MSK_LWR1, | ||||||
|  |             0x73 => Self::ADDR_MSK_UPR1, | ||||||
|  |             0x90 => Self::RX_FSIZE, | ||||||
|  |             0x91 => Self::RX, | ||||||
|  |             e => panic!("Unknown value {}", e), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Register definitions | ||||||
|  | bitfield! { | ||||||
|  |     /// Status0 Register bits | ||||||
|  |     pub struct Status0(u32); | ||||||
|  |     impl Debug; | ||||||
|  |     u32; | ||||||
|  |     /// Control Data Protection Error | ||||||
|  |     pub cdpe, _ : 12; | ||||||
|  |     /// Transmit Frame Check Squence Error | ||||||
|  |     pub txfcse, _: 11; | ||||||
|  |     /// Transmit Time Stamp Capture Available C | ||||||
|  |     pub ttscac, _ : 10; | ||||||
|  |     /// Transmit Time Stamp Capture Available B | ||||||
|  |     pub ttscab, _ : 9; | ||||||
|  |     /// Transmit Time Stamp Capture Available A | ||||||
|  |     pub ttscaa, _ : 8; | ||||||
|  |     /// PHY Interrupt for Port 1 | ||||||
|  |     pub phyint, _ : 7; | ||||||
|  |     /// Reset Complete | ||||||
|  |     pub resetc, _ : 6; | ||||||
|  |     /// Header error | ||||||
|  |     pub hdre, _ : 5; | ||||||
|  |     /// Loss of Frame Error | ||||||
|  |     pub lofe, _ : 4; | ||||||
|  |     /// Receiver Buffer Overflow Error | ||||||
|  |     pub rxboe, _ : 3; | ||||||
|  |     /// Host Tx FIFO Under Run Error | ||||||
|  |     pub txbue, _ : 2; | ||||||
|  |     /// Host Tx FIFO Overflow | ||||||
|  |     pub txboe, _ : 1; | ||||||
|  |     /// Transmit Protocol Error | ||||||
|  |     pub txpe, _ : 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bitfield! { | ||||||
|  |     /// Status1 Register bits | ||||||
|  |     pub struct Status1(u32); | ||||||
|  |     impl Debug; | ||||||
|  |     u32; | ||||||
|  |     /// ECC Error on Reading the Frame Size from a Tx FIFO | ||||||
|  |     pub tx_ecc_err, set_tx_ecc_err: 12; | ||||||
|  |     /// ECC Error on Reading the Frame Size from an Rx FIFO | ||||||
|  |     pub rx_ecc_err, set_rx_ecc_err : 11; | ||||||
|  |     /// Detected an Error on an SPI Transaction | ||||||
|  |     pub spi_err, set_spi_err: 10; | ||||||
|  |     /// Rx MAC Interframe Gap Error | ||||||
|  |     pub p1_rx_ifg_err, set_p1_rx_ifg_err : 8; | ||||||
|  |     /// Port1 Rx Ready High Priority | ||||||
|  |     pub p1_rx_rdy_hi, set_p1_rx_rdy_hi : 5; | ||||||
|  |     /// Port 1 Rx FIFO Contains Data | ||||||
|  |     pub p1_rx_rdy, set_p1_rx_rdy : 4; | ||||||
|  |     /// Tx Ready | ||||||
|  |     pub tx_rdy, set_tx_rdy : 3; | ||||||
|  |     /// Link Status Changed | ||||||
|  |     pub link_change, set_link_change : 1; | ||||||
|  |     /// Port 1 Link Status | ||||||
|  |     pub p1_link_status, _ : 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bitfield! { | ||||||
|  |     /// Config0 Register bits | ||||||
|  |     pub struct Config0(u32); | ||||||
|  |     impl Debug; | ||||||
|  |     u32; | ||||||
|  |     /// Configuration Synchronization | ||||||
|  |     pub sync, set_sync : 15; | ||||||
|  |     /// Transmit Frame Check Sequence Validation Enable | ||||||
|  |     pub txfcsve, set_txfcsve : 14; | ||||||
|  |     /// !CS Align Receive Frame Enable | ||||||
|  |     pub csarfe, set_csarfe : 13; | ||||||
|  |     /// Zero Align Receive Frame Enable | ||||||
|  |     pub zarfe, set_zarfe : 12; | ||||||
|  |     /// Transmit Credit Threshold | ||||||
|  |     pub tcxthresh, set_tcxthresh : 11, 10; | ||||||
|  |     /// Transmit Cut Through Enable | ||||||
|  |     pub txcte, set_txcte : 9; | ||||||
|  |     /// Receive Cut Through Enable | ||||||
|  |     pub rxcte, set_rxcte : 8; | ||||||
|  |     /// Frame Time Stamp Enable | ||||||
|  |     pub ftse, set_ftse : 7; | ||||||
|  |     /// Receive Frame Time Stamp Select | ||||||
|  |     pub ftss, set_ftss : 6; | ||||||
|  |     /// Enable Control Data Read Write Protection | ||||||
|  |     pub prote, set_prote : 5; | ||||||
|  |     /// Enable TX Data Chunk Sequence and Retry | ||||||
|  |     pub seqe, set_seqe : 4; | ||||||
|  |     /// Chunk Payload Selector (N). | ||||||
|  |     pub cps, set_cps : 2, 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bitfield! { | ||||||
|  |     /// Config2 Register bits | ||||||
|  |     pub struct Config2(u32); | ||||||
|  |     impl Debug; | ||||||
|  |     u32; | ||||||
|  |     /// Assert TX_RDY When the Tx FIFO is Empty | ||||||
|  |     pub tx_rdy_on_empty, set_tx_rdy_on_empty : 8; | ||||||
|  |     /// Determines If the SFD is Detected in the PHY or MAC | ||||||
|  |     pub sdf_detect_src, set_sdf_detect_src : 7; | ||||||
|  |     /// Statistics Clear on Reading | ||||||
|  |     pub stats_clr_on_rd, set_stats_clr_on_rd : 6; | ||||||
|  |     /// Enable SPI CRC | ||||||
|  |     pub crc_append, set_crc_append : 5; | ||||||
|  |     /// Admit Frames with IFG Errors on Port 1 (P1) | ||||||
|  |     pub p1_rcv_ifg_err_frm, set_p1_rcv_ifg_err_frm : 4; | ||||||
|  |     /// Forward Frames Not Matching Any MAC Address to the Host | ||||||
|  |     pub p1_fwd_unk2host, set_p1_fwd_unk2host : 2; | ||||||
|  |     /// SPI to MDIO Bridge MDC Clock Speed | ||||||
|  |     pub mspeed, set_mspeed : 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bitfield! { | ||||||
|  |     /// IMASK0 Register bits | ||||||
|  |     pub struct IMask0(u32); | ||||||
|  |     impl Debug; | ||||||
|  |     u32; | ||||||
|  |     /// Control Data Protection Error Mask | ||||||
|  |     pub cppem, set_cppem : 12; | ||||||
|  |     /// Transmit Frame Check Sequence Error Mask | ||||||
|  |     pub txfcsem, set_txfcsem : 11; | ||||||
|  |     /// Transmit Time Stamp Capture Available C Mask | ||||||
|  |     pub ttscacm, set_ttscacm : 10; | ||||||
|  |     /// Transmit Time Stamp Capture Available B Mask | ||||||
|  |     pub ttscabm, set_ttscabm : 9; | ||||||
|  |     /// Transmit Time Stamp Capture Available A Mask | ||||||
|  |     pub ttscaam, set_ttscaam : 8; | ||||||
|  |     /// Physical Layer Interrupt Mask | ||||||
|  |     pub phyintm, set_phyintm : 7; | ||||||
|  |     /// RESET Complete Mask | ||||||
|  |     pub resetcm, set_resetcm : 6; | ||||||
|  |     /// Header Error Mask | ||||||
|  |     pub hdrem, set_hdrem : 5; | ||||||
|  |     /// Loss of Frame Error Mask | ||||||
|  |     pub lofem, set_lofem : 4; | ||||||
|  |     /// Receive Buffer Overflow Error Mask | ||||||
|  |     pub rxboem, set_rxboem : 3; | ||||||
|  |     /// Transmit Buffer Underflow Error Mask | ||||||
|  |     pub txbuem, set_txbuem : 2; | ||||||
|  |     /// Transmit Buffer Overflow Error Mask | ||||||
|  |     pub txboem, set_txboem : 1; | ||||||
|  |     /// Transmit Protocol Error Mask | ||||||
|  |     pub txpem, set_txpem : 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bitfield! { | ||||||
|  |     /// IMASK1 Register bits | ||||||
|  |     pub struct IMask1(u32); | ||||||
|  |     impl Debug; | ||||||
|  |     u32; | ||||||
|  |     /// Mask Bit for TXF_ECC_ERR | ||||||
|  |     pub tx_ecc_err_mask, set_tx_ecc_err_mask : 12; | ||||||
|  |     /// Mask Bit for RXF_ECC_ERR | ||||||
|  |     pub rx_ecc_err_mask, set_rx_ecc_err_mask : 11; | ||||||
|  |     /// Mask Bit for SPI_ERR | ||||||
|  |     /// This field is only used with the generic SPI protocol | ||||||
|  |     pub spi_err_mask, set_spi_err_mask : 10; | ||||||
|  |     /// Mask Bit for RX_IFG_ERR | ||||||
|  |     pub p1_rx_ifg_err_mask, set_p1_rx_ifg_err_mask : 8; | ||||||
|  |     /// Mask Bit for P1_RX_RDY | ||||||
|  |     /// This field is only used with the generic SPI protocol | ||||||
|  |     pub p1_rx_rdy_mask, set_p1_rx_rdy_mask : 4; | ||||||
|  |     /// Mask Bit for TX_FRM_DONE | ||||||
|  |     /// This field is only used with the generic SPI protocol | ||||||
|  |     pub tx_rdy_mask, set_tx_rdy_mask : 3; | ||||||
|  |     /// Mask Bit for LINK_CHANGE | ||||||
|  |     pub link_change_mask, set_link_change_mask : 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// LED Functions | ||||||
|  | #[repr(u8)] | ||||||
|  | pub enum LedFunc { | ||||||
|  |     LinkupTxRxActicity = 0, | ||||||
|  |     LinkupTxActicity, | ||||||
|  |     LinkupRxActicity, | ||||||
|  |     LinkupOnly, | ||||||
|  |     TxRxActivity, | ||||||
|  |     TxActivity, | ||||||
|  |     RxActivity, | ||||||
|  |     LinkupRxEr, | ||||||
|  |     LinkupRxTxEr, | ||||||
|  |     RxEr, | ||||||
|  |     RxTxEr, | ||||||
|  |     TxSop, | ||||||
|  |     RxSop, | ||||||
|  |     On, | ||||||
|  |     Off, | ||||||
|  |     Blink, | ||||||
|  |     TxLevel2P4, | ||||||
|  |     TxLevel1P0, | ||||||
|  |     Master, | ||||||
|  |     Slave, | ||||||
|  |     IncompatiableLinkCfg, | ||||||
|  |     AnLinkGood, | ||||||
|  |     AnComplete, | ||||||
|  |     TsTimer, | ||||||
|  |     LocRcvrStatus, | ||||||
|  |     RemRcvrStatus, | ||||||
|  |     Clk25Ref, | ||||||
|  |     TxTCLK, | ||||||
|  |     Clk120MHz, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<LedFunc> for u8 { | ||||||
|  |     fn from(val: LedFunc) -> Self { | ||||||
|  |         val as u8 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<u8> for LedFunc { | ||||||
|  |     fn from(value: u8) -> Self { | ||||||
|  |         match value { | ||||||
|  |             0 => LedFunc::LinkupTxRxActicity, | ||||||
|  |             1 => LedFunc::LinkupTxActicity, | ||||||
|  |             2 => LedFunc::LinkupRxActicity, | ||||||
|  |             3 => LedFunc::LinkupOnly, | ||||||
|  |             4 => LedFunc::TxRxActivity, | ||||||
|  |             5 => LedFunc::TxActivity, | ||||||
|  |             6 => LedFunc::RxActivity, | ||||||
|  |             7 => LedFunc::LinkupRxEr, | ||||||
|  |             8 => LedFunc::LinkupRxTxEr, | ||||||
|  |             9 => LedFunc::RxEr, | ||||||
|  |             10 => LedFunc::RxTxEr, | ||||||
|  |             11 => LedFunc::TxSop, | ||||||
|  |             12 => LedFunc::RxSop, | ||||||
|  |             13 => LedFunc::On, | ||||||
|  |             14 => LedFunc::Off, | ||||||
|  |             15 => LedFunc::Blink, | ||||||
|  |             16 => LedFunc::TxLevel2P4, | ||||||
|  |             17 => LedFunc::TxLevel1P0, | ||||||
|  |             18 => LedFunc::Master, | ||||||
|  |             19 => LedFunc::Slave, | ||||||
|  |             20 => LedFunc::IncompatiableLinkCfg, | ||||||
|  |             21 => LedFunc::AnLinkGood, | ||||||
|  |             22 => LedFunc::AnComplete, | ||||||
|  |             23 => LedFunc::TsTimer, | ||||||
|  |             24 => LedFunc::LocRcvrStatus, | ||||||
|  |             25 => LedFunc::RemRcvrStatus, | ||||||
|  |             26 => LedFunc::Clk25Ref, | ||||||
|  |             27 => LedFunc::TxTCLK, | ||||||
|  |             28 => LedFunc::Clk120MHz, | ||||||
|  |             e => panic!("Invalid value {}", e), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// LED Control Register | ||||||
|  | #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||||||
|  | pub struct LedCntrl(pub u16); | ||||||
|  | bitfield_bitrange! {struct LedCntrl(u16)} | ||||||
|  |  | ||||||
|  | impl LedCntrl { | ||||||
|  |     bitfield_fields! { | ||||||
|  |         u8; | ||||||
|  |         /// LED 0 Pin Function | ||||||
|  |         pub from into LedFunc, led0_function, set_led0_function: 4, 0; | ||||||
|  |         /// LED 0 Mode Selection | ||||||
|  |         pub led0_mode, set_led0_mode: 5; | ||||||
|  |         /// Qualify Certain LED 0 Options with Link Status. | ||||||
|  |         pub led0_link_st_qualify, set_led0_link_st_qualify: 6; | ||||||
|  |         /// LED 0 Enable | ||||||
|  |         pub led0_en, set_led0_en: 7; | ||||||
|  |         /// LED 1 Pin Function | ||||||
|  |         pub from into LedFunc, led1_function, set_led1_function: 12, 8; | ||||||
|  |         /// /// LED 1 Mode Selection | ||||||
|  |         pub led1_mode, set_led1_mode: 13; | ||||||
|  |         /// Qualify Certain LED 1 Options with Link Status. | ||||||
|  |         pub led1_link_st_qualify, set_led1_link_st_qualify: 14; | ||||||
|  |         /// LED 1 Enable | ||||||
|  |         pub led1_en, set_led1_en: 15; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         LedCntrl(0) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LED Polarity | ||||||
|  | #[repr(u8)] | ||||||
|  | pub enum LedPol { | ||||||
|  |     AutoSense = 0, | ||||||
|  |     ActiveHigh, | ||||||
|  |     ActiveLow, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<LedPol> for u8 { | ||||||
|  |     fn from(val: LedPol) -> Self { | ||||||
|  |         val as u8 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<u8> for LedPol { | ||||||
|  |     fn from(value: u8) -> Self { | ||||||
|  |         match value { | ||||||
|  |             0 => LedPol::AutoSense, | ||||||
|  |             1 => LedPol::ActiveHigh, | ||||||
|  |             2 => LedPol::ActiveLow, | ||||||
|  |             e => panic!("Invalid value {}", e), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// LED Control Register | ||||||
|  | #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||||||
|  | pub struct LedPolarity(pub u16); | ||||||
|  | bitfield_bitrange! {struct LedPolarity(u16)} | ||||||
|  |  | ||||||
|  | impl LedPolarity { | ||||||
|  |     bitfield_fields! { | ||||||
|  |         u8; | ||||||
|  |         /// LED 1 Polarity | ||||||
|  |         pub from into LedPol, led1_polarity, set_led1_polarity: 3, 2; | ||||||
|  |         /// LED 0 Polarity | ||||||
|  |         pub from into LedPol, led0_polarity, set_led0_polarity: 1, 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// SPI Header | ||||||
|  | #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||||||
|  | pub struct SpiHeader(pub u16); | ||||||
|  | bitfield_bitrange! {struct SpiHeader(u16)} | ||||||
|  |  | ||||||
|  | impl SpiHeader { | ||||||
|  |     bitfield_fields! { | ||||||
|  |         u16; | ||||||
|  |         /// Mask Bit for TXF_ECC_ERR | ||||||
|  |         pub control, set_control : 15; | ||||||
|  |         pub full_duplex, set_full_duplex : 14; | ||||||
|  |         /// Read or Write to register | ||||||
|  |         pub write, set_write : 13; | ||||||
|  |         /// Registers ID/addr | ||||||
|  |         pub from into SpiRegisters, addr, set_addr: 11, 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -76,7 +76,7 @@ These `embassy-net` drivers are implemented using this crate. You can look at th | |||||||
|  |  | ||||||
| - [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W | - [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W | ||||||
| - [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. | - [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. | ||||||
| - [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip. | - [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips. | ||||||
| - [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. | - [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #![macro_use] | #![macro_use] | ||||||
| #![allow(unused_macros)] | #![allow(unused_macros)] | ||||||
|  |  | ||||||
|  | use core::fmt::{Debug, Display, LowerHex}; | ||||||
|  |  | ||||||
| #[cfg(all(feature = "defmt", feature = "log"))] | #[cfg(all(feature = "defmt", feature = "log"))] | ||||||
| compile_error!("You may not enable both `defmt` and `log` features."); | 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 { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         { |         ::core::unreachable!($($x)*) | ||||||
|             #[cfg(not(feature = "defmt"))] |     }; | ||||||
|             ::core::unreachable!($($x)*); | } | ||||||
|             #[cfg(feature = "defmt")] |  | ||||||
|             ::defmt::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 |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -8,11 +8,13 @@ use core::cell::RefCell; | |||||||
| use core::mem::MaybeUninit; | use core::mem::MaybeUninit; | ||||||
| use core::task::{Context, Poll}; | use core::task::{Context, Poll}; | ||||||
|  |  | ||||||
|  | use driver::HardwareAddress; | ||||||
| pub use embassy_net_driver as driver; | pub use embassy_net_driver as driver; | ||||||
| use embassy_net_driver::{Capabilities, LinkState, Medium}; | use embassy_net_driver::{Capabilities, LinkState, Medium}; | ||||||
| use embassy_sync::blocking_mutex::raw::NoopRawMutex; | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||||||
| use embassy_sync::blocking_mutex::Mutex; | use embassy_sync::blocking_mutex::Mutex; | ||||||
| use embassy_sync::waitqueue::WakerRegistration; | use embassy_sync::waitqueue::WakerRegistration; | ||||||
|  | use embassy_sync::zerocopy_channel; | ||||||
|  |  | ||||||
| pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> { | pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> { | ||||||
|     rx: [PacketBuf<MTU>; N_RX], |     rx: [PacketBuf<MTU>; N_RX], | ||||||
| @@ -42,7 +44,7 @@ struct StateInner<'d, const MTU: usize> { | |||||||
| struct Shared { | struct Shared { | ||||||
|     link_state: LinkState, |     link_state: LinkState, | ||||||
|     waker: WakerRegistration, |     waker: WakerRegistration, | ||||||
|     ethernet_address: [u8; 6], |     hardware_address: driver::HardwareAddress, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct Runner<'d, const MTU: usize> { | pub struct Runner<'d, const MTU: usize> { | ||||||
| @@ -73,6 +75,18 @@ impl<'d, const MTU: usize> Runner<'d, MTU> { | |||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn borrow_split(&mut self) -> (StateRunner<'_>, RxRunner<'_, MTU>, TxRunner<'_, MTU>) { | ||||||
|  |         ( | ||||||
|  |             StateRunner { shared: self.shared }, | ||||||
|  |             RxRunner { | ||||||
|  |                 rx_chan: self.rx_chan.borrow(), | ||||||
|  |             }, | ||||||
|  |             TxRunner { | ||||||
|  |                 tx_chan: self.tx_chan.borrow(), | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pub fn state_runner(&self) -> StateRunner<'d> { |     pub fn state_runner(&self) -> StateRunner<'d> { | ||||||
|         StateRunner { shared: self.shared } |         StateRunner { shared: self.shared } | ||||||
|     } |     } | ||||||
| @@ -85,10 +99,10 @@ impl<'d, const MTU: usize> Runner<'d, MTU> { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn set_ethernet_address(&mut self, address: [u8; 6]) { |     pub fn set_hardware_address(&mut self, address: driver::HardwareAddress) { | ||||||
|         self.shared.lock(|s| { |         self.shared.lock(|s| { | ||||||
|             let s = &mut *s.borrow_mut(); |             let s = &mut *s.borrow_mut(); | ||||||
|             s.ethernet_address = address; |             s.hardware_address = address; | ||||||
|             s.waker.wake(); |             s.waker.wake(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -117,24 +131,24 @@ impl<'d, const MTU: usize> Runner<'d, MTU> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub async fn tx_buf(&mut self) -> &mut [u8] { |     pub async fn tx_buf(&mut self) -> &mut [u8] { | ||||||
|         let p = self.tx_chan.recv().await; |         let p = self.tx_chan.receive().await; | ||||||
|         &mut p.buf[..p.len] |         &mut p.buf[..p.len] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { |     pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { | ||||||
|         let p = self.tx_chan.try_recv()?; |         let p = self.tx_chan.try_receive()?; | ||||||
|         Some(&mut p.buf[..p.len]) |         Some(&mut p.buf[..p.len]) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { |     pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { | ||||||
|         match self.tx_chan.poll_recv(cx) { |         match self.tx_chan.poll_receive(cx) { | ||||||
|             Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), |             Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), | ||||||
|             Poll::Pending => Poll::Pending, |             Poll::Pending => Poll::Pending, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn tx_done(&mut self) { |     pub fn tx_done(&mut self) { | ||||||
|         self.tx_chan.recv_done(); |         self.tx_chan.receive_done(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -150,7 +164,15 @@ impl<'d> StateRunner<'d> { | |||||||
|     pub fn set_ethernet_address(&self, address: [u8; 6]) { |     pub fn set_ethernet_address(&self, address: [u8; 6]) { | ||||||
|         self.shared.lock(|s| { |         self.shared.lock(|s| { | ||||||
|             let s = &mut *s.borrow_mut(); |             let s = &mut *s.borrow_mut(); | ||||||
|             s.ethernet_address = address; |             s.hardware_address = driver::HardwareAddress::Ethernet(address); | ||||||
|  |             s.waker.wake(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn set_ieee802154_address(&self, address: [u8; 8]) { | ||||||
|  |         self.shared.lock(|s| { | ||||||
|  |             let s = &mut *s.borrow_mut(); | ||||||
|  |             s.hardware_address = driver::HardwareAddress::Ieee802154(address); | ||||||
|             s.waker.wake(); |             s.waker.wake(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -183,34 +205,38 @@ impl<'d, const MTU: usize> RxRunner<'d, MTU> { | |||||||
|  |  | ||||||
| impl<'d, const MTU: usize> TxRunner<'d, MTU> { | impl<'d, const MTU: usize> TxRunner<'d, MTU> { | ||||||
|     pub async fn tx_buf(&mut self) -> &mut [u8] { |     pub async fn tx_buf(&mut self) -> &mut [u8] { | ||||||
|         let p = self.tx_chan.recv().await; |         let p = self.tx_chan.receive().await; | ||||||
|         &mut p.buf[..p.len] |         &mut p.buf[..p.len] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { |     pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { | ||||||
|         let p = self.tx_chan.try_recv()?; |         let p = self.tx_chan.try_receive()?; | ||||||
|         Some(&mut p.buf[..p.len]) |         Some(&mut p.buf[..p.len]) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { |     pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { | ||||||
|         match self.tx_chan.poll_recv(cx) { |         match self.tx_chan.poll_receive(cx) { | ||||||
|             Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), |             Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), | ||||||
|             Poll::Pending => Poll::Pending, |             Poll::Pending => Poll::Pending, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn tx_done(&mut self) { |     pub fn tx_done(&mut self) { | ||||||
|         self.tx_chan.recv_done(); |         self.tx_chan.receive_done(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( | pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( | ||||||
|     state: &'d mut State<MTU, N_RX, N_TX>, |     state: &'d mut State<MTU, N_RX, N_TX>, | ||||||
|     ethernet_address: [u8; 6], |     hardware_address: driver::HardwareAddress, | ||||||
| ) -> (Runner<'d, MTU>, Device<'d, MTU>) { | ) -> (Runner<'d, MTU>, Device<'d, MTU>) { | ||||||
|     let mut caps = Capabilities::default(); |     let mut caps = Capabilities::default(); | ||||||
|     caps.max_transmission_unit = MTU; |     caps.max_transmission_unit = MTU; | ||||||
|     caps.medium = Medium::Ethernet; |     caps.medium = match &hardware_address { | ||||||
|  |         HardwareAddress::Ethernet(_) => Medium::Ethernet, | ||||||
|  |         HardwareAddress::Ieee802154(_) => Medium::Ieee802154, | ||||||
|  |         HardwareAddress::Ip => Medium::Ip, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     // safety: this is a self-referential struct, however: |     // safety: this is a self-referential struct, however: | ||||||
|     // - it can't move while the `'d` borrow is active. |     // - it can't move while the `'d` borrow is active. | ||||||
| @@ -222,7 +248,7 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( | |||||||
|         tx: zerocopy_channel::Channel::new(&mut state.tx[..]), |         tx: zerocopy_channel::Channel::new(&mut state.tx[..]), | ||||||
|         shared: Mutex::new(RefCell::new(Shared { |         shared: Mutex::new(RefCell::new(Shared { | ||||||
|             link_state: LinkState::Down, |             link_state: LinkState::Down, | ||||||
|             ethernet_address, |             hardware_address, | ||||||
|             waker: WakerRegistration::new(), |             waker: WakerRegistration::new(), | ||||||
|         })), |         })), | ||||||
|     }); |     }); | ||||||
| @@ -268,7 +294,7 @@ impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { | |||||||
|     type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; |     type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; | ||||||
|  |  | ||||||
|     fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { |     fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { | ||||||
|         if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() { |         if self.rx.poll_receive(cx).is_ready() && self.tx.poll_send(cx).is_ready() { | ||||||
|             Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() })) |             Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() })) | ||||||
|         } else { |         } else { | ||||||
|             None |             None | ||||||
| @@ -289,8 +315,8 @@ impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { | |||||||
|         self.caps.clone() |         self.caps.clone() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn ethernet_address(&self) -> [u8; 6] { |     fn hardware_address(&self) -> driver::HardwareAddress { | ||||||
|         self.shared.lock(|s| s.borrow().ethernet_address) |         self.shared.lock(|s| s.borrow().hardware_address) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn link_state(&mut self, cx: &mut Context) -> LinkState { |     fn link_state(&mut self, cx: &mut Context) -> LinkState { | ||||||
| @@ -312,9 +338,9 @@ impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> { | |||||||
|         F: FnOnce(&mut [u8]) -> R, |         F: FnOnce(&mut [u8]) -> R, | ||||||
|     { |     { | ||||||
|         // NOTE(unwrap): we checked the queue wasn't full when creating the token. |         // NOTE(unwrap): we checked the queue wasn't full when creating the token. | ||||||
|         let pkt = unwrap!(self.rx.try_recv()); |         let pkt = unwrap!(self.rx.try_receive()); | ||||||
|         let r = f(&mut pkt.buf[..pkt.len]); |         let r = f(&mut pkt.buf[..pkt.len]); | ||||||
|         self.rx.recv_done(); |         self.rx.receive_done(); | ||||||
|         r |         r | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -336,215 +362,3 @@ impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> { | |||||||
|         r |         r | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| mod zerocopy_channel { |  | ||||||
|     use core::cell::RefCell; |  | ||||||
|     use core::future::poll_fn; |  | ||||||
|     use core::marker::PhantomData; |  | ||||||
|     use core::task::{Context, Poll}; |  | ||||||
|  |  | ||||||
|     use embassy_sync::blocking_mutex::raw::RawMutex; |  | ||||||
|     use embassy_sync::blocking_mutex::Mutex; |  | ||||||
|     use embassy_sync::waitqueue::WakerRegistration; |  | ||||||
|  |  | ||||||
|     pub struct Channel<'a, M: RawMutex, T> { |  | ||||||
|         buf: *mut T, |  | ||||||
|         phantom: PhantomData<&'a mut T>, |  | ||||||
|         state: Mutex<M, RefCell<State>>, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     impl<'a, M: RawMutex, T> Channel<'a, M, T> { |  | ||||||
|         pub fn new(buf: &'a mut [T]) -> Self { |  | ||||||
|             let len = buf.len(); |  | ||||||
|             assert!(len != 0); |  | ||||||
|  |  | ||||||
|             Self { |  | ||||||
|                 buf: buf.as_mut_ptr(), |  | ||||||
|                 phantom: PhantomData, |  | ||||||
|                 state: Mutex::new(RefCell::new(State { |  | ||||||
|                     len, |  | ||||||
|                     front: 0, |  | ||||||
|                     back: 0, |  | ||||||
|                     full: false, |  | ||||||
|                     send_waker: WakerRegistration::new(), |  | ||||||
|                     recv_waker: WakerRegistration::new(), |  | ||||||
|                 })), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { |  | ||||||
|             (Sender { channel: self }, Receiver { channel: self }) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub struct Sender<'a, M: RawMutex, T> { |  | ||||||
|         channel: &'a Channel<'a, M, T>, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     impl<'a, M: RawMutex, T> Sender<'a, M, T> { |  | ||||||
|         pub fn borrow(&mut self) -> Sender<'_, M, T> { |  | ||||||
|             Sender { channel: self.channel } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub fn try_send(&mut self) -> Option<&mut T> { |  | ||||||
|             self.channel.state.lock(|s| { |  | ||||||
|                 let s = &mut *s.borrow_mut(); |  | ||||||
|                 match s.push_index() { |  | ||||||
|                     Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), |  | ||||||
|                     None => None, |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> { |  | ||||||
|             self.channel.state.lock(|s| { |  | ||||||
|                 let s = &mut *s.borrow_mut(); |  | ||||||
|                 match s.push_index() { |  | ||||||
|                     Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), |  | ||||||
|                     None => { |  | ||||||
|                         s.recv_waker.register(cx.waker()); |  | ||||||
|                         Poll::Pending |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub async fn send(&mut self) -> &mut T { |  | ||||||
|             let i = poll_fn(|cx| { |  | ||||||
|                 self.channel.state.lock(|s| { |  | ||||||
|                     let s = &mut *s.borrow_mut(); |  | ||||||
|                     match s.push_index() { |  | ||||||
|                         Some(i) => Poll::Ready(i), |  | ||||||
|                         None => { |  | ||||||
|                             s.recv_waker.register(cx.waker()); |  | ||||||
|                             Poll::Pending |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|             }) |  | ||||||
|             .await; |  | ||||||
|             unsafe { &mut *self.channel.buf.add(i) } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub fn send_done(&mut self) { |  | ||||||
|             self.channel.state.lock(|s| s.borrow_mut().push_done()) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     pub struct Receiver<'a, M: RawMutex, T> { |  | ||||||
|         channel: &'a Channel<'a, M, T>, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     impl<'a, M: RawMutex, T> Receiver<'a, M, T> { |  | ||||||
|         pub fn borrow(&mut self) -> Receiver<'_, M, T> { |  | ||||||
|             Receiver { channel: self.channel } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub fn try_recv(&mut self) -> Option<&mut T> { |  | ||||||
|             self.channel.state.lock(|s| { |  | ||||||
|                 let s = &mut *s.borrow_mut(); |  | ||||||
|                 match s.pop_index() { |  | ||||||
|                     Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), |  | ||||||
|                     None => None, |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> { |  | ||||||
|             self.channel.state.lock(|s| { |  | ||||||
|                 let s = &mut *s.borrow_mut(); |  | ||||||
|                 match s.pop_index() { |  | ||||||
|                     Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), |  | ||||||
|                     None => { |  | ||||||
|                         s.send_waker.register(cx.waker()); |  | ||||||
|                         Poll::Pending |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub async fn recv(&mut self) -> &mut T { |  | ||||||
|             let i = poll_fn(|cx| { |  | ||||||
|                 self.channel.state.lock(|s| { |  | ||||||
|                     let s = &mut *s.borrow_mut(); |  | ||||||
|                     match s.pop_index() { |  | ||||||
|                         Some(i) => Poll::Ready(i), |  | ||||||
|                         None => { |  | ||||||
|                             s.send_waker.register(cx.waker()); |  | ||||||
|                             Poll::Pending |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|             }) |  | ||||||
|             .await; |  | ||||||
|             unsafe { &mut *self.channel.buf.add(i) } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         pub fn recv_done(&mut self) { |  | ||||||
|             self.channel.state.lock(|s| s.borrow_mut().pop_done()) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     struct State { |  | ||||||
|         len: usize, |  | ||||||
|  |  | ||||||
|         /// Front index. Always 0..=(N-1) |  | ||||||
|         front: usize, |  | ||||||
|         /// Back index. Always 0..=(N-1). |  | ||||||
|         back: usize, |  | ||||||
|  |  | ||||||
|         /// Used to distinguish "empty" and "full" cases when `front == back`. |  | ||||||
|         /// May only be `true` if `front == back`, always `false` otherwise. |  | ||||||
|         full: bool, |  | ||||||
|  |  | ||||||
|         send_waker: WakerRegistration, |  | ||||||
|         recv_waker: WakerRegistration, |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     impl State { |  | ||||||
|         fn increment(&self, i: usize) -> usize { |  | ||||||
|             if i + 1 == self.len { |  | ||||||
|                 0 |  | ||||||
|             } else { |  | ||||||
|                 i + 1 |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fn is_full(&self) -> bool { |  | ||||||
|             self.full |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fn is_empty(&self) -> bool { |  | ||||||
|             self.front == self.back && !self.full |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fn push_index(&mut self) -> Option<usize> { |  | ||||||
|             match self.is_full() { |  | ||||||
|                 true => None, |  | ||||||
|                 false => Some(self.back), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fn push_done(&mut self) { |  | ||||||
|             assert!(!self.is_full()); |  | ||||||
|             self.back = self.increment(self.back); |  | ||||||
|             if self.back == self.front { |  | ||||||
|                 self.full = true; |  | ||||||
|             } |  | ||||||
|             self.send_waker.wake(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fn pop_index(&mut self) -> Option<usize> { |  | ||||||
|             match self.is_empty() { |  | ||||||
|                 true => None, |  | ||||||
|                 false => Some(self.front), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fn pop_done(&mut self) { |  | ||||||
|             assert!(!self.is_empty()); |  | ||||||
|             self.front = self.increment(self.front); |  | ||||||
|             self.full = false; |  | ||||||
|             self.recv_waker.wake(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -21,4 +21,4 @@ target = "thumbv7em-none-eabi" | |||||||
| features = ["defmt"] | features = ["defmt"] | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| defmt = { version = "0.3", optional = true } | defmt = { version = "0.3", optional = true } | ||||||
|   | |||||||
| @@ -4,6 +4,18 @@ | |||||||
|  |  | ||||||
| use core::task::Context; | use core::task::Context; | ||||||
|  |  | ||||||
|  | /// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address. | ||||||
|  | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
|  | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
|  | pub enum HardwareAddress { | ||||||
|  |     /// A six-octet Ethernet address | ||||||
|  |     Ethernet([u8; 6]), | ||||||
|  |     /// An eight-octet IEEE802.15.4 address | ||||||
|  |     Ieee802154([u8; 8]), | ||||||
|  |     /// Indicates that a Driver is IP-native, and has no hardware address | ||||||
|  |     Ip, | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Main `embassy-net` driver API. | /// Main `embassy-net` driver API. | ||||||
| /// | /// | ||||||
| /// This is essentially an interface for sending and receiving raw network frames. | /// This is essentially an interface for sending and receiving raw network frames. | ||||||
| @@ -51,8 +63,8 @@ pub trait Driver { | |||||||
|     /// Get a description of device capabilities. |     /// Get a description of device capabilities. | ||||||
|     fn capabilities(&self) -> Capabilities; |     fn capabilities(&self) -> Capabilities; | ||||||
|  |  | ||||||
|     /// Get the device's Ethernet address. |     /// Get the device's hardware address. | ||||||
|     fn ethernet_address(&self) -> [u8; 6]; |     fn hardware_address(&self) -> HardwareAddress; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<T: ?Sized + Driver> Driver for &mut T { | impl<T: ?Sized + Driver> Driver for &mut T { | ||||||
| @@ -75,8 +87,8 @@ impl<T: ?Sized + Driver> Driver for &mut T { | |||||||
|     fn link_state(&mut self, cx: &mut Context) -> LinkState { |     fn link_state(&mut self, cx: &mut Context) -> LinkState { | ||||||
|         T::link_state(self, cx) |         T::link_state(self, cx) | ||||||
|     } |     } | ||||||
|     fn ethernet_address(&self) -> [u8; 6] { |     fn hardware_address(&self) -> HardwareAddress { | ||||||
|         T::ethernet_address(self) |         T::hardware_address(self) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -164,6 +176,9 @@ pub enum Medium { | |||||||
|     /// |     /// | ||||||
|     /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode. |     /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode. | ||||||
|     Ip, |     Ip, | ||||||
|  |  | ||||||
|  |     /// IEEE 802_15_4 medium | ||||||
|  |     Ieee802154, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Medium { | impl Default for Medium { | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								embassy-net-enc28j60/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								embassy-net-enc28j60/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | [package] | ||||||
|  | name = "embassy-net-enc28j60" | ||||||
|  | version = "0.1.0" | ||||||
|  | description = "embassy-net driver for the ENC28J60 ethernet chip" | ||||||
|  | keywords = ["embedded", "enc28j60", "embassy-net", "embedded-hal-async", "ethernet", "async"] | ||||||
|  | categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] | ||||||
|  | license = "MIT OR Apache-2.0" | ||||||
|  | edition = "2021" | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | embedded-hal = { version = "1.0.0-rc.1" } | ||||||
|  | embedded-hal-async = { version = "=1.0.0-rc.1" } | ||||||
|  | embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } | ||||||
|  | embassy-time = { version = "0.1.3", path = "../embassy-time" } | ||||||
|  | embassy-futures = { version = "0.1.0", path = "../embassy-futures" } | ||||||
|  |  | ||||||
|  | defmt = { version = "0.3", optional = true } | ||||||
|  | log = { version = "0.4.14", optional = true } | ||||||
|  |  | ||||||
|  | [package.metadata.embassy_docs] | ||||||
|  | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-enc28j60-v$VERSION/embassy-net-enc28j60/src/" | ||||||
|  | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-enc28j60/src/" | ||||||
|  | target = "thumbv7em-none-eabi" | ||||||
							
								
								
									
										19
									
								
								embassy-net-enc28j60/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								embassy-net-enc28j60/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | # `embassy-net-enc28j60` | ||||||
|  |  | ||||||
|  | [`embassy-net`](https://crates.io/crates/embassy-net) integration for the Microchip ENC28J60 Ethernet chip. | ||||||
|  |  | ||||||
|  | Based on [@japaric](https://github.com/japaric)'s [`enc28j60`](https://github.com/japaric/enc28j60) crate. | ||||||
|  |  | ||||||
|  | ## Interoperability | ||||||
|  |  | ||||||
|  | This crate can run on any executor. | ||||||
|  |  | ||||||
|  | ## 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. | ||||||
							
								
								
									
										69
									
								
								embassy-net-enc28j60/src/bank0.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								embassy-net-enc28j60/src/bank0.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | #[allow(dead_code)] | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub enum Register { | ||||||
|  |     ERDPTL = 0x00, | ||||||
|  |     ERDPTH = 0x01, | ||||||
|  |     EWRPTL = 0x02, | ||||||
|  |     EWRPTH = 0x03, | ||||||
|  |     ETXSTL = 0x04, | ||||||
|  |     ETXSTH = 0x05, | ||||||
|  |     ETXNDL = 0x06, | ||||||
|  |     ETXNDH = 0x07, | ||||||
|  |     ERXSTL = 0x08, | ||||||
|  |     ERXSTH = 0x09, | ||||||
|  |     ERXNDL = 0x0a, | ||||||
|  |     ERXNDH = 0x0b, | ||||||
|  |     ERXRDPTL = 0x0c, | ||||||
|  |     ERXRDPTH = 0x0d, | ||||||
|  |     ERXWRPTL = 0x0e, | ||||||
|  |     ERXWRPTH = 0x0f, | ||||||
|  |     EDMASTL = 0x10, | ||||||
|  |     EDMASTH = 0x11, | ||||||
|  |     EDMANDL = 0x12, | ||||||
|  |     EDMANDH = 0x13, | ||||||
|  |     EDMADSTL = 0x14, | ||||||
|  |     EDMADSTH = 0x15, | ||||||
|  |     EDMACSL = 0x16, | ||||||
|  |     EDMACSH = 0x17, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Register { | ||||||
|  |     pub(crate) fn addr(&self) -> u8 { | ||||||
|  |         *self as u8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn is_eth_register(&self) -> bool { | ||||||
|  |         match *self { | ||||||
|  |             Register::ERDPTL => true, | ||||||
|  |             Register::ERDPTH => true, | ||||||
|  |             Register::EWRPTL => true, | ||||||
|  |             Register::EWRPTH => true, | ||||||
|  |             Register::ETXSTL => true, | ||||||
|  |             Register::ETXSTH => true, | ||||||
|  |             Register::ETXNDL => true, | ||||||
|  |             Register::ETXNDH => true, | ||||||
|  |             Register::ERXSTL => true, | ||||||
|  |             Register::ERXSTH => true, | ||||||
|  |             Register::ERXNDL => true, | ||||||
|  |             Register::ERXNDH => true, | ||||||
|  |             Register::ERXRDPTL => true, | ||||||
|  |             Register::ERXRDPTH => true, | ||||||
|  |             Register::ERXWRPTL => true, | ||||||
|  |             Register::ERXWRPTH => true, | ||||||
|  |             Register::EDMASTL => true, | ||||||
|  |             Register::EDMASTH => true, | ||||||
|  |             Register::EDMANDL => true, | ||||||
|  |             Register::EDMANDH => true, | ||||||
|  |             Register::EDMADSTL => true, | ||||||
|  |             Register::EDMADSTH => true, | ||||||
|  |             Register::EDMACSL => true, | ||||||
|  |             Register::EDMACSH => true, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Into<super::Register> for Register { | ||||||
|  |     fn into(self) -> super::Register { | ||||||
|  |         super::Register::Bank0(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										84
									
								
								embassy-net-enc28j60/src/bank1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								embassy-net-enc28j60/src/bank1.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | #[allow(dead_code)] | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub enum Register { | ||||||
|  |     EHT0 = 0x00, | ||||||
|  |     EHT1 = 0x01, | ||||||
|  |     EHT2 = 0x02, | ||||||
|  |     EHT3 = 0x03, | ||||||
|  |     EHT4 = 0x04, | ||||||
|  |     EHT5 = 0x05, | ||||||
|  |     EHT6 = 0x06, | ||||||
|  |     EHT7 = 0x07, | ||||||
|  |     EPMM0 = 0x08, | ||||||
|  |     EPMM1 = 0x09, | ||||||
|  |     EPMM2 = 0x0a, | ||||||
|  |     EPMM3 = 0x0b, | ||||||
|  |     EPMM4 = 0x0c, | ||||||
|  |     EPMM5 = 0x0d, | ||||||
|  |     EPMM6 = 0x0e, | ||||||
|  |     EPMM7 = 0x0f, | ||||||
|  |     EPMCSL = 0x10, | ||||||
|  |     EPMCSH = 0x11, | ||||||
|  |     EPMOL = 0x14, | ||||||
|  |     EPMOH = 0x15, | ||||||
|  |     ERXFCON = 0x18, | ||||||
|  |     EPKTCNT = 0x19, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Register { | ||||||
|  |     pub(crate) fn addr(&self) -> u8 { | ||||||
|  |         *self as u8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn is_eth_register(&self) -> bool { | ||||||
|  |         match *self { | ||||||
|  |             Register::EHT0 => true, | ||||||
|  |             Register::EHT1 => true, | ||||||
|  |             Register::EHT2 => true, | ||||||
|  |             Register::EHT3 => true, | ||||||
|  |             Register::EHT4 => true, | ||||||
|  |             Register::EHT5 => true, | ||||||
|  |             Register::EHT6 => true, | ||||||
|  |             Register::EHT7 => true, | ||||||
|  |             Register::EPMM0 => true, | ||||||
|  |             Register::EPMM1 => true, | ||||||
|  |             Register::EPMM2 => true, | ||||||
|  |             Register::EPMM3 => true, | ||||||
|  |             Register::EPMM4 => true, | ||||||
|  |             Register::EPMM5 => true, | ||||||
|  |             Register::EPMM6 => true, | ||||||
|  |             Register::EPMM7 => true, | ||||||
|  |             Register::EPMCSL => true, | ||||||
|  |             Register::EPMCSH => true, | ||||||
|  |             Register::EPMOL => true, | ||||||
|  |             Register::EPMOH => true, | ||||||
|  |             Register::ERXFCON => true, | ||||||
|  |             Register::EPKTCNT => true, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Into<super::Register> for Register { | ||||||
|  |     fn into(self) -> super::Register { | ||||||
|  |         super::Register::Bank1(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | register!(ERXFCON, 0b1010_0001, u8, { | ||||||
|  |     #[doc = "Broadcast Filter Enable bit"] | ||||||
|  |     bcen @ 0, | ||||||
|  |     #[doc = "Multicast Filter Enable bit"] | ||||||
|  |     mcen @ 1, | ||||||
|  |     #[doc = "Hash Table Filter Enable bit"] | ||||||
|  |     hten @ 2, | ||||||
|  |     #[doc = "Magic Packet™ Filter Enable bit"] | ||||||
|  |     mpen @ 3, | ||||||
|  |     #[doc = "Pattern Match Filter Enable bit"] | ||||||
|  |     pmen @ 4, | ||||||
|  |     #[doc = "Post-Filter CRC Check Enable bit"] | ||||||
|  |     crcen @ 5, | ||||||
|  |     #[doc = "AND/OR Filter Select bit"] | ||||||
|  |     andor @ 6, | ||||||
|  |     #[doc = "Unicast Filter Enable bit"] | ||||||
|  |     ucen @ 7, | ||||||
|  | }); | ||||||
							
								
								
									
										86
									
								
								embassy-net-enc28j60/src/bank2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								embassy-net-enc28j60/src/bank2.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | #[allow(dead_code)] | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub enum Register { | ||||||
|  |     MACON1 = 0x00, | ||||||
|  |     MACON3 = 0x02, | ||||||
|  |     MACON4 = 0x03, | ||||||
|  |     MABBIPG = 0x04, | ||||||
|  |     MAIPGL = 0x06, | ||||||
|  |     MAIPGH = 0x07, | ||||||
|  |     MACLCON1 = 0x08, | ||||||
|  |     MACLCON2 = 0x09, | ||||||
|  |     MAMXFLL = 0x0a, | ||||||
|  |     MAMXFLH = 0x0b, | ||||||
|  |     MICMD = 0x12, | ||||||
|  |     MIREGADR = 0x14, | ||||||
|  |     MIWRL = 0x16, | ||||||
|  |     MIWRH = 0x17, | ||||||
|  |     MIRDL = 0x18, | ||||||
|  |     MIRDH = 0x19, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Register { | ||||||
|  |     pub(crate) fn addr(&self) -> u8 { | ||||||
|  |         *self as u8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn is_eth_register(&self) -> bool { | ||||||
|  |         match *self { | ||||||
|  |             Register::MACON1 => false, | ||||||
|  |             Register::MACON3 => false, | ||||||
|  |             Register::MACON4 => false, | ||||||
|  |             Register::MABBIPG => false, | ||||||
|  |             Register::MAIPGL => false, | ||||||
|  |             Register::MAIPGH => false, | ||||||
|  |             Register::MACLCON1 => false, | ||||||
|  |             Register::MACLCON2 => false, | ||||||
|  |             Register::MAMXFLL => false, | ||||||
|  |             Register::MAMXFLH => false, | ||||||
|  |             Register::MICMD => false, | ||||||
|  |             Register::MIREGADR => false, | ||||||
|  |             Register::MIWRL => false, | ||||||
|  |             Register::MIWRH => false, | ||||||
|  |             Register::MIRDL => false, | ||||||
|  |             Register::MIRDH => false, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Into<super::Register> for Register { | ||||||
|  |     fn into(self) -> super::Register { | ||||||
|  |         super::Register::Bank2(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | register!(MACON1, 0, u8, { | ||||||
|  |     #[doc = "Enable packets to be received by the MAC"] | ||||||
|  |     marxen @ 0, | ||||||
|  |     #[doc = "Control frames will be discarded after being processed by the MAC"] | ||||||
|  |     passall @ 1, | ||||||
|  |     #[doc = "Inhibit transmissions when pause control frames are received"] | ||||||
|  |     rxpaus @ 2, | ||||||
|  |     #[doc = "Allow the MAC to transmit pause control frames"] | ||||||
|  |     txpaus @ 3, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | register!(MACON3, 0, u8, { | ||||||
|  |     #[doc = "MAC will operate in Full-Duplex mode"] | ||||||
|  |     fuldpx @ 0, | ||||||
|  |     #[doc = "The type/length field of transmitted and received frames will be checked"] | ||||||
|  |     frmlnen @ 1, | ||||||
|  |     #[doc = "Frames bigger than MAMXFL will be aborted when transmitted or received"] | ||||||
|  |     hfrmen @ 2, | ||||||
|  |     #[doc = "No proprietary header is present"] | ||||||
|  |     phdren @ 3, | ||||||
|  |     #[doc = "MAC will append a valid CRC to all frames transmitted regardless of PADCFG bit"] | ||||||
|  |     txcrcen @ 4, | ||||||
|  |     #[doc = "All short frames will be zero-padded to 64 bytes and a valid CRC will then be appended"] | ||||||
|  |     padcfg @ 5..7, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | register!(MICMD, 0, u8, { | ||||||
|  |     #[doc = "MII Read Enable bit"] | ||||||
|  |     miird @ 0, | ||||||
|  |     #[doc = "MII Scan Enable bit"] | ||||||
|  |     miiscan @ 1, | ||||||
|  | }); | ||||||
							
								
								
									
										53
									
								
								embassy-net-enc28j60/src/bank3.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								embassy-net-enc28j60/src/bank3.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | #[allow(dead_code)] | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub enum Register { | ||||||
|  |     MAADR5 = 0x00, | ||||||
|  |     MAADR6 = 0x01, | ||||||
|  |     MAADR3 = 0x02, | ||||||
|  |     MAADR4 = 0x03, | ||||||
|  |     MAADR1 = 0x04, | ||||||
|  |     MAADR2 = 0x05, | ||||||
|  |     EBSTSD = 0x06, | ||||||
|  |     EBSTCON = 0x07, | ||||||
|  |     EBSTCSL = 0x08, | ||||||
|  |     EBSTCSH = 0x09, | ||||||
|  |     MISTAT = 0x0a, | ||||||
|  |     EREVID = 0x12, | ||||||
|  |     ECOCON = 0x15, | ||||||
|  |     EFLOCON = 0x17, | ||||||
|  |     EPAUSL = 0x18, | ||||||
|  |     EPAUSH = 0x19, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Register { | ||||||
|  |     pub(crate) fn addr(&self) -> u8 { | ||||||
|  |         *self as u8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn is_eth_register(&self) -> bool { | ||||||
|  |         match *self { | ||||||
|  |             Register::MAADR5 => false, | ||||||
|  |             Register::MAADR6 => false, | ||||||
|  |             Register::MAADR3 => false, | ||||||
|  |             Register::MAADR4 => false, | ||||||
|  |             Register::MAADR1 => false, | ||||||
|  |             Register::MAADR2 => false, | ||||||
|  |             Register::EBSTSD => true, | ||||||
|  |             Register::EBSTCON => true, | ||||||
|  |             Register::EBSTCSL => true, | ||||||
|  |             Register::EBSTCSH => true, | ||||||
|  |             Register::MISTAT => false, | ||||||
|  |             Register::EREVID => true, | ||||||
|  |             Register::ECOCON => true, | ||||||
|  |             Register::EFLOCON => true, | ||||||
|  |             Register::EPAUSL => true, | ||||||
|  |             Register::EPAUSH => true, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Into<super::Register> for Register { | ||||||
|  |     fn into(self) -> super::Register { | ||||||
|  |         super::Register::Bank3(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										106
									
								
								embassy-net-enc28j60/src/common.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								embassy-net-enc28j60/src/common.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | #[allow(dead_code)] | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub enum Register { | ||||||
|  |     ECON1 = 0x1f, | ||||||
|  |     ECON2 = 0x1e, | ||||||
|  |     EIE = 0x1b, | ||||||
|  |     EIR = 0x1c, | ||||||
|  |     ESTAT = 0x1d, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Register { | ||||||
|  |     pub(crate) fn addr(&self) -> u8 { | ||||||
|  |         *self as u8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn is_eth_register(&self) -> bool { | ||||||
|  |         match *self { | ||||||
|  |             Register::ECON1 => true, | ||||||
|  |             Register::ECON2 => true, | ||||||
|  |             Register::EIE => true, | ||||||
|  |             Register::EIR => true, | ||||||
|  |             Register::ESTAT => true, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Into<super::Register> for Register { | ||||||
|  |     fn into(self) -> super::Register { | ||||||
|  |         super::Register::Common(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | register!(EIE, 0, u8, { | ||||||
|  |     #[doc = "Receive Error Interrupt Enable bit"] | ||||||
|  |     rxerie @ 0, | ||||||
|  |     #[doc = "Transmit Error Interrupt Enable bit"] | ||||||
|  |     txerie @ 1, | ||||||
|  |     #[doc = "Transmit Enable bit"] | ||||||
|  |     txie @ 3, | ||||||
|  |     #[doc = "Link Status Change Interrupt Enable bit"] | ||||||
|  |     linkie @ 4, | ||||||
|  |     #[doc = "DMA Interrupt Enable bit"] | ||||||
|  |     dmaie @ 5, | ||||||
|  |     #[doc = "Receive Packet Pending Interrupt Enable bit"] | ||||||
|  |     pktie @ 6, | ||||||
|  |     #[doc = "Global INT Interrupt Enable bit"] | ||||||
|  |     intie @ 7, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | register!(EIR, 0, u8, { | ||||||
|  |     #[doc = "Receive Error Interrupt Flag bit"] | ||||||
|  |     rxerif @ 0, | ||||||
|  |     #[doc = "Transmit Error Interrupt Flag bit"] | ||||||
|  |     txerif @ 1, | ||||||
|  |     #[doc = "Transmit Interrupt Flag bit"] | ||||||
|  |     txif @ 3, | ||||||
|  |     #[doc = "Link Change Interrupt Flag bit"] | ||||||
|  |     linkif @ 4, | ||||||
|  |     #[doc = "DMA Interrupt Flag bit"] | ||||||
|  |     dmaif @ 5, | ||||||
|  |     #[doc = "Receive Packet Pending Interrupt Flag bit"] | ||||||
|  |     pktif @ 6, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | register!(ESTAT, 0, u8, { | ||||||
|  |     #[doc = "Clock Ready bit"] | ||||||
|  |     clkrdy @ 0, | ||||||
|  |     #[doc = "Transmit Abort Error bit"] | ||||||
|  |     txabrt @ 1, | ||||||
|  |     #[doc = "Receive Busy bit"] | ||||||
|  |     rxbusy @ 2, | ||||||
|  |     #[doc = "Late Collision Error bit"] | ||||||
|  |     latecol @ 4, | ||||||
|  |     #[doc = "Ethernet Buffer Error Status bit"] | ||||||
|  |     bufer @ 6, | ||||||
|  |     #[doc = "INT Interrupt Flag bit"] | ||||||
|  |     int @ 7, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | register!(ECON2, 0b1000_0000, u8, { | ||||||
|  |     #[doc = "Voltage Regulator Power Save Enable bit"] | ||||||
|  |     vrps @ 3, | ||||||
|  |     #[doc = "Power Save Enable bit"] | ||||||
|  |     pwrsv @ 5, | ||||||
|  |     #[doc = "Packet Decrement bit"] | ||||||
|  |     pktdec @ 6, | ||||||
|  |     #[doc = "Automatic Buffer Pointer Increment Enable bit"] | ||||||
|  |     autoinc @ 7, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | register!(ECON1, 0, u8, { | ||||||
|  |     #[doc = "Bank Select bits"] | ||||||
|  |     bsel @ 0..1, | ||||||
|  |     #[doc = "Receive Enable bi"] | ||||||
|  |     rxen @ 2, | ||||||
|  |     #[doc = "Transmit Request to Send bit"] | ||||||
|  |     txrts @ 3, | ||||||
|  |     #[doc = "DMA Checksum Enable bit"] | ||||||
|  |     csumen @ 4, | ||||||
|  |     #[doc = "DMA Start and Busy Status bit"] | ||||||
|  |     dmast @ 5, | ||||||
|  |     #[doc = "Receive Logic Reset bit"] | ||||||
|  |     rxrst @ 6, | ||||||
|  |     #[doc = "Transmit Logic Reset bit"] | ||||||
|  |     txrst @ 7, | ||||||
|  | }); | ||||||
							
								
								
									
										258
									
								
								embassy-net-enc28j60/src/fmt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								embassy-net-enc28j60/src/fmt.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,258 @@ | |||||||
|  | #![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."); | ||||||
|  |  | ||||||
|  | macro_rules! assert { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::assert!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::assert!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! assert_eq { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::assert_eq!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::assert_eq!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! assert_ne { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::assert_ne!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::assert_ne!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug_assert { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::debug_assert!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug_assert!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug_assert_eq { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::debug_assert_eq!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug_assert_eq!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug_assert_ne { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::debug_assert_ne!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug_assert_ne!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! todo { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::todo!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::todo!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(not(feature = "defmt"))] | ||||||
|  | macro_rules! unreachable { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         ::core::unreachable!($($x)*) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "defmt")] | ||||||
|  | macro_rules! unreachable { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         ::defmt::unreachable!($($x)*) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! panic { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(not(feature = "defmt"))] | ||||||
|  |             ::core::panic!($($x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::panic!($($x)*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! trace { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::trace!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::trace!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! debug { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::debug!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::debug!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! info { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::info!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::info!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! warn { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::warn!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::warn!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! error { | ||||||
|  |     ($s:literal $(, $x:expr)* $(,)?) => { | ||||||
|  |         { | ||||||
|  |             #[cfg(feature = "log")] | ||||||
|  |             ::log::error!($s $(, $x)*); | ||||||
|  |             #[cfg(feature = "defmt")] | ||||||
|  |             ::defmt::error!($s $(, $x)*); | ||||||
|  |             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||||
|  |             let _ = ($( & $x ),*); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "defmt")] | ||||||
|  | macro_rules! unwrap { | ||||||
|  |     ($($x:tt)*) => { | ||||||
|  |         ::defmt::unwrap!($($x)*) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(not(feature = "defmt"))] | ||||||
|  | macro_rules! unwrap { | ||||||
|  |     ($arg:expr) => { | ||||||
|  |         match $crate::fmt::Try::into_result($arg) { | ||||||
|  |             ::core::result::Result::Ok(t) => t, | ||||||
|  |             ::core::result::Result::Err(e) => { | ||||||
|  |                 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |     ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||||||
|  |         match $crate::fmt::Try::into_result($arg) { | ||||||
|  |             ::core::result::Result::Ok(t) => t, | ||||||
|  |             ::core::result::Result::Err(e) => { | ||||||
|  |                 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||||||
|  | pub struct NoneError; | ||||||
|  |  | ||||||
|  | pub trait Try { | ||||||
|  |     type Ok; | ||||||
|  |     type Error; | ||||||
|  |     fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T> Try for Option<T> { | ||||||
|  |     type Ok = T; | ||||||
|  |     type Error = NoneError; | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn into_result(self) -> Result<T, NoneError> { | ||||||
|  |         self.ok_or(NoneError) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T, E> Try for Result<T, E> { | ||||||
|  |     type Ok = T; | ||||||
|  |     type Error = E; | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn into_result(self) -> Self { | ||||||
|  |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								embassy-net-enc28j60/src/header.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								embassy-net-enc28j60/src/header.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | register!(RxStatus, 0, u32, { | ||||||
|  |     #[doc = "Indicates length of the received frame"] | ||||||
|  |     byte_count @ 0..15, | ||||||
|  |     #[doc = "Indicates a packet over 50,000 bit times occurred or that a packet was dropped since the last receive"] | ||||||
|  |     long_event @ 16, | ||||||
|  |     #[doc = "Indicates that at some time since the last receive, a carrier event was detected"] | ||||||
|  |     carrier_event @ 18, | ||||||
|  |     #[doc = "Indicates that frame CRC field value does not match the CRC calculated by the MAC"] | ||||||
|  |     crc_error @ 20, | ||||||
|  |     #[doc = "Indicates that frame length field value in the packet does not match the actual data byte length and specifies a valid length"] | ||||||
|  |     length_check_error @ 21, | ||||||
|  |     #[doc = "Indicates that frame type/length field was larger than 1500 bytes (type field)"] | ||||||
|  |     length_out_of_range @ 22, | ||||||
|  |     #[doc = "Indicates that at the packet had a valid CRC and no symbol errors"] | ||||||
|  |     received_ok @ 23, | ||||||
|  |     #[doc = "Indicates packet received had a valid Multicast address"] | ||||||
|  |     multicast @ 24, | ||||||
|  |     #[doc = "Indicates packet received had a valid Broadcast address."] | ||||||
|  |     broadcast @ 25, | ||||||
|  |     #[doc = "Indicates that after the end of this packet, an additional 1 to 7 bits were received"] | ||||||
|  |     dribble_nibble @ 26, | ||||||
|  |     #[doc = "Current frame was recognized as a control frame for having a valid type/length designating it as a control frame"] | ||||||
|  |     receive_control_frame @ 27, | ||||||
|  |     #[doc = "Current frame was recognized as a control frame containing a valid pause frame opcode and a valid destination address"] | ||||||
|  |     receive_pause_control_frame @ 28, | ||||||
|  |     #[doc = "Current frame was recognized as a control frame but it contained an unknown opcode"] | ||||||
|  |     receive_unknown_opcode @ 29, | ||||||
|  |     #[doc = "Current frame was recognized as a VLAN tagged frame"] | ||||||
|  |     receive_vlan_type_detected @ 30, | ||||||
|  | }); | ||||||
							
								
								
									
										717
									
								
								embassy-net-enc28j60/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										717
									
								
								embassy-net-enc28j60/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,717 @@ | |||||||
|  | #![no_std] | ||||||
|  | #![doc = include_str!("../README.md")] | ||||||
|  | #![warn(missing_docs)] | ||||||
|  |  | ||||||
|  | // must go first. | ||||||
|  | mod fmt; | ||||||
|  |  | ||||||
|  | #[macro_use] | ||||||
|  | mod macros; | ||||||
|  | mod bank0; | ||||||
|  | mod bank1; | ||||||
|  | mod bank2; | ||||||
|  | mod bank3; | ||||||
|  | mod common; | ||||||
|  | mod header; | ||||||
|  | mod phy; | ||||||
|  | mod traits; | ||||||
|  |  | ||||||
|  | use core::cmp; | ||||||
|  | use core::convert::TryInto; | ||||||
|  |  | ||||||
|  | use embassy_net_driver::{Capabilities, HardwareAddress, LinkState, Medium}; | ||||||
|  | use embassy_time::Duration; | ||||||
|  | use embedded_hal::digital::OutputPin; | ||||||
|  | use embedded_hal::spi::{Operation, SpiDevice}; | ||||||
|  | use traits::U16Ext; | ||||||
|  |  | ||||||
|  | // Total buffer size (see section 3.2) | ||||||
|  | const BUF_SZ: u16 = 8 * 1024; | ||||||
|  |  | ||||||
|  | // Maximum frame length | ||||||
|  | const MAX_FRAME_LENGTH: u16 = 1518; // value recommended in the data sheet | ||||||
|  |  | ||||||
|  | // Size of the Frame check sequence (32-bit CRC) | ||||||
|  | const CRC_SZ: u16 = 4; | ||||||
|  |  | ||||||
|  | // define the boundaries of the TX and RX buffers | ||||||
|  | // to workaround errata #5 we do the opposite of what section 6.1 of the data sheet | ||||||
|  | // says: we place the RX buffer at address 0 and the TX buffer after it | ||||||
|  | const RXST: u16 = 0x0000; | ||||||
|  | const RXND: u16 = 0x19ff; | ||||||
|  | const TXST: u16 = 0x1a00; | ||||||
|  | const _TXND: u16 = 0x1fff; | ||||||
|  |  | ||||||
|  | const MTU: usize = 1514; // 1500 IP + 14 ethernet header | ||||||
|  |  | ||||||
|  | /// ENC28J60 embassy-net driver | ||||||
|  | pub struct Enc28j60<S, O> { | ||||||
|  |     mac_addr: [u8; 6], | ||||||
|  |  | ||||||
|  |     spi: S, | ||||||
|  |     rst: Option<O>, | ||||||
|  |  | ||||||
|  |     bank: Bank, | ||||||
|  |  | ||||||
|  |     // address of the next packet in buffer memory | ||||||
|  |     next_packet: u16, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<S, O> Enc28j60<S, O> | ||||||
|  | where | ||||||
|  |     S: SpiDevice, | ||||||
|  |     O: OutputPin, | ||||||
|  | { | ||||||
|  |     /// Create a new ENC28J60 driver instance. | ||||||
|  |     /// | ||||||
|  |     /// The RST pin is optional. If None, reset will be done with a SPI | ||||||
|  |     /// soft reset command, instead of via the RST pin. | ||||||
|  |     pub fn new(spi: S, rst: Option<O>, mac_addr: [u8; 6]) -> Self { | ||||||
|  |         let mut res = Self { | ||||||
|  |             mac_addr, | ||||||
|  |             spi, | ||||||
|  |             rst, | ||||||
|  |  | ||||||
|  |             bank: Bank::Bank0, | ||||||
|  |             next_packet: RXST, | ||||||
|  |         }; | ||||||
|  |         res.init(); | ||||||
|  |         res | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn init(&mut self) { | ||||||
|  |         if let Some(rst) = &mut self.rst { | ||||||
|  |             rst.set_low().unwrap(); | ||||||
|  |             embassy_time::block_for(Duration::from_millis(5)); | ||||||
|  |             rst.set_high().unwrap(); | ||||||
|  |             embassy_time::block_for(Duration::from_millis(5)); | ||||||
|  |         } else { | ||||||
|  |             embassy_time::block_for(Duration::from_millis(5)); | ||||||
|  |             self.soft_reset(); | ||||||
|  |             embassy_time::block_for(Duration::from_millis(5)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         debug!( | ||||||
|  |             "enc28j60: erevid {=u8:x}", | ||||||
|  |             self.read_control_register(bank3::Register::EREVID) | ||||||
|  |         ); | ||||||
|  |         debug!("enc28j60: waiting for clk"); | ||||||
|  |         while common::ESTAT(self.read_control_register(common::Register::ESTAT)).clkrdy() == 0 {} | ||||||
|  |         debug!("enc28j60: clk ok"); | ||||||
|  |  | ||||||
|  |         if self.read_control_register(bank3::Register::EREVID) == 0 { | ||||||
|  |             panic!("ErevidIsZero"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // disable CLKOUT output | ||||||
|  |         self.write_control_register(bank3::Register::ECOCON, 0); | ||||||
|  |  | ||||||
|  |         self.init_rx(); | ||||||
|  |  | ||||||
|  |         // TX start | ||||||
|  |         // "It is recommended that an even address be used for ETXST" | ||||||
|  |         debug_assert_eq!(TXST % 2, 0); | ||||||
|  |         self.write_control_register(bank0::Register::ETXSTL, TXST.low()); | ||||||
|  |         self.write_control_register(bank0::Register::ETXSTH, TXST.high()); | ||||||
|  |  | ||||||
|  |         // TX end is set in `transmit` | ||||||
|  |  | ||||||
|  |         // MAC initialization (see section 6.5) | ||||||
|  |         // 1. Set the MARXEN bit in MACON1 to enable the MAC to receive frames. | ||||||
|  |         self.write_control_register( | ||||||
|  |             bank2::Register::MACON1, | ||||||
|  |             bank2::MACON1::default().marxen(1).passall(0).rxpaus(1).txpaus(1).bits(), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         // 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3. | ||||||
|  |         self.write_control_register( | ||||||
|  |             bank2::Register::MACON3, | ||||||
|  |             bank2::MACON3::default().frmlnen(1).txcrcen(1).padcfg(0b001).bits(), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         // 4. Program the MAMXFL registers with the maximum frame length to be permitted to be | ||||||
|  |         // received or transmitted | ||||||
|  |         self.write_control_register(bank2::Register::MAMXFLL, MAX_FRAME_LENGTH.low()); | ||||||
|  |         self.write_control_register(bank2::Register::MAMXFLH, MAX_FRAME_LENGTH.high()); | ||||||
|  |  | ||||||
|  |         // 5. Configure the Back-to-Back Inter-Packet Gap register, MABBIPG. | ||||||
|  |         // Use recommended value of 0x12 | ||||||
|  |         self.write_control_register(bank2::Register::MABBIPG, 0x12); | ||||||
|  |  | ||||||
|  |         // 6. Configure the Non-Back-to-Back Inter-Packet Gap register low byte, MAIPGL. | ||||||
|  |         // Use recommended value of 0x12 | ||||||
|  |         self.write_control_register(bank2::Register::MAIPGL, 0x12); | ||||||
|  |         self.write_control_register(bank2::Register::MAIPGH, 0x0c); | ||||||
|  |  | ||||||
|  |         // 9. Program the local MAC address into the MAADR1:MAADR6 registers | ||||||
|  |         self.write_control_register(bank3::Register::MAADR1, self.mac_addr[0]); | ||||||
|  |         self.write_control_register(bank3::Register::MAADR2, self.mac_addr[1]); | ||||||
|  |         self.write_control_register(bank3::Register::MAADR3, self.mac_addr[2]); | ||||||
|  |         self.write_control_register(bank3::Register::MAADR4, self.mac_addr[3]); | ||||||
|  |         self.write_control_register(bank3::Register::MAADR5, self.mac_addr[4]); | ||||||
|  |         self.write_control_register(bank3::Register::MAADR6, self.mac_addr[5]); | ||||||
|  |  | ||||||
|  |         // Set the PHCON2.HDLDIS bit to prevent automatic loopback of the data which is transmitted | ||||||
|  |         self.write_phy_register(phy::Register::PHCON2, phy::PHCON2::default().hdldis(1).bits()); | ||||||
|  |  | ||||||
|  |         // Globally enable interrupts | ||||||
|  |         //self.bit_field_set(common::Register::EIE, common::EIE::mask().intie()); | ||||||
|  |  | ||||||
|  |         // Set the per packet control byte; we'll always use the value 0 | ||||||
|  |         self.write_buffer_memory(Some(TXST), &mut [0]); | ||||||
|  |  | ||||||
|  |         // Enable reception | ||||||
|  |         self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn init_rx(&mut self) { | ||||||
|  |         // RX start | ||||||
|  |         // "It is recommended that the ERXST Pointer be programmed with an even address" | ||||||
|  |         self.write_control_register(bank0::Register::ERXSTL, RXST.low()); | ||||||
|  |         self.write_control_register(bank0::Register::ERXSTH, RXST.high()); | ||||||
|  |  | ||||||
|  |         // RX read pointer | ||||||
|  |         // NOTE Errata #14 so we are using an *odd* address here instead of ERXST | ||||||
|  |         self.write_control_register(bank0::Register::ERXRDPTL, RXND.low()); | ||||||
|  |         self.write_control_register(bank0::Register::ERXRDPTH, RXND.high()); | ||||||
|  |  | ||||||
|  |         // RX end | ||||||
|  |         self.write_control_register(bank0::Register::ERXNDL, RXND.low()); | ||||||
|  |         self.write_control_register(bank0::Register::ERXNDH, RXND.high()); | ||||||
|  |  | ||||||
|  |         // decrease the packet count to 0 | ||||||
|  |         while self.read_control_register(bank1::Register::EPKTCNT) != 0 { | ||||||
|  |             self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.next_packet = RXST; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn reset_rx(&mut self) { | ||||||
|  |         self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxrst()); | ||||||
|  |         self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().rxrst()); | ||||||
|  |         self.init_rx(); | ||||||
|  |         self.bit_field_set(common::Register::ECON1, common::ECON1::mask().rxen()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Flushes the transmit buffer, ensuring all pending transmissions have completed | ||||||
|  |     /// NOTE: The returned packet *must* be `read` or `ignore`-d, otherwise this method will always | ||||||
|  |     /// return `None` on subsequent invocations | ||||||
|  |     pub fn receive<'a>(&mut self, buf: &'a mut [u8]) -> Option<&'a mut [u8]> { | ||||||
|  |         if self.pending_packets() == 0 { | ||||||
|  |             // Errata #6: we can't rely on PKTIF so we check PKTCNT | ||||||
|  |             return None; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let curr_packet = self.next_packet; | ||||||
|  |  | ||||||
|  |         // read out the first 6 bytes | ||||||
|  |         let mut temp_buf = [0; 6]; | ||||||
|  |         self.read_buffer_memory(Some(curr_packet), &mut temp_buf); | ||||||
|  |  | ||||||
|  |         // next packet pointer | ||||||
|  |         let next_packet = u16::from_parts(temp_buf[0], temp_buf[1]); | ||||||
|  |         // status vector | ||||||
|  |         let status = header::RxStatus(u32::from_le_bytes(temp_buf[2..].try_into().unwrap())); | ||||||
|  |         let len_with_crc = status.byte_count() as u16; | ||||||
|  |  | ||||||
|  |         if len_with_crc < CRC_SZ || len_with_crc > 1600 || next_packet > RXND { | ||||||
|  |             warn!("RX buffer corrupted, resetting RX logic to recover..."); | ||||||
|  |             self.reset_rx(); | ||||||
|  |             return None; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let len = len_with_crc - CRC_SZ; | ||||||
|  |         self.read_buffer_memory(None, &mut buf[..len as usize]); | ||||||
|  |  | ||||||
|  |         // update ERXRDPT | ||||||
|  |         // due to Errata #14 we must write an odd address to ERXRDPT | ||||||
|  |         // we know that ERXST = 0, that ERXND is odd and that next_packet is even | ||||||
|  |         let rxrdpt = if self.next_packet < 1 || self.next_packet > RXND + 1 { | ||||||
|  |             RXND | ||||||
|  |         } else { | ||||||
|  |             self.next_packet - 1 | ||||||
|  |         }; | ||||||
|  |         // "To move ERXRDPT, the host controller must write to ERXRDPTL first." | ||||||
|  |         self.write_control_register(bank0::Register::ERXRDPTL, rxrdpt.low()); | ||||||
|  |         self.write_control_register(bank0::Register::ERXRDPTH, rxrdpt.high()); | ||||||
|  |  | ||||||
|  |         // decrease the packet count | ||||||
|  |         self.bit_field_set(common::Register::ECON2, common::ECON2::mask().pktdec()); | ||||||
|  |  | ||||||
|  |         self.next_packet = next_packet; | ||||||
|  |  | ||||||
|  |         Some(&mut buf[..len as usize]) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn wait_tx_ready(&mut self) { | ||||||
|  |         for _ in 0u32..10000 { | ||||||
|  |             if common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 0 { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // work around errata #12 by resetting the transmit logic before every new | ||||||
|  |         // transmission | ||||||
|  |         self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrst()); | ||||||
|  |         self.bit_field_clear(common::Register::ECON1, common::ECON1::mask().txrst()); | ||||||
|  |         //self.bit_field_clear(common::Register::EIR, { | ||||||
|  |         //    let mask = common::EIR::mask(); | ||||||
|  |         //    mask.txerif() | mask.txif() | ||||||
|  |         //}); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Starts the transmission of `bytes` | ||||||
|  |     /// | ||||||
|  |     /// It's up to the caller to ensure that `bytes` is a valid Ethernet frame. The interface will | ||||||
|  |     /// take care of appending a (4 byte) CRC to the frame and of padding the frame to the minimum | ||||||
|  |     /// size allowed by the Ethernet specification (64 bytes, or 46 bytes of payload). | ||||||
|  |     /// | ||||||
|  |     /// NOTE This method will flush any previous transmission that's in progress | ||||||
|  |     /// | ||||||
|  |     /// # Panics | ||||||
|  |     /// | ||||||
|  |     /// If `bytes` length is greater than 1514, the maximum frame length allowed by the interface, | ||||||
|  |     /// or greater than the transmit buffer | ||||||
|  |     pub fn transmit(&mut self, bytes: &[u8]) { | ||||||
|  |         assert!(bytes.len() <= self.mtu() as usize); | ||||||
|  |  | ||||||
|  |         self.wait_tx_ready(); | ||||||
|  |  | ||||||
|  |         // NOTE the plus one is to not overwrite the per packet control byte | ||||||
|  |         let wrpt = TXST + 1; | ||||||
|  |  | ||||||
|  |         // 1. ETXST was set during initialization | ||||||
|  |  | ||||||
|  |         // 2. write the frame to the IC memory | ||||||
|  |         self.write_buffer_memory(Some(wrpt), bytes); | ||||||
|  |  | ||||||
|  |         let txnd = wrpt + bytes.len() as u16 - 1; | ||||||
|  |  | ||||||
|  |         // 3. Set the end address of the transmit buffer | ||||||
|  |         self.write_control_register(bank0::Register::ETXNDL, txnd.low()); | ||||||
|  |         self.write_control_register(bank0::Register::ETXNDH, txnd.high()); | ||||||
|  |  | ||||||
|  |         // 4. reset interrupt flag | ||||||
|  |         //self.bit_field_clear(common::Register::EIR, common::EIR::mask().txif()); | ||||||
|  |  | ||||||
|  |         // 5. start transmission | ||||||
|  |         self.bit_field_set(common::Register::ECON1, common::ECON1::mask().txrts()); | ||||||
|  |  | ||||||
|  |         // Wait until transmission finishes | ||||||
|  |         //while common::ECON1(self.read_control_register(common::Register::ECON1)).txrts() == 1 {} | ||||||
|  |  | ||||||
|  |         /* | ||||||
|  |         // read the transmit status vector | ||||||
|  |         let mut tx_stat = [0; 7]; | ||||||
|  |         self.read_buffer_memory(None, &mut tx_stat); | ||||||
|  |  | ||||||
|  |         let stat = common::ESTAT(self.read_control_register(common::Register::ESTAT)); | ||||||
|  |  | ||||||
|  |         if stat.txabrt() == 1 { | ||||||
|  |             // work around errata #12 by reading the transmit status vector | ||||||
|  |             if stat.latecol() == 1 || (tx_stat[2] & (1 << 5)) != 0 { | ||||||
|  |                 panic!("LateCollision") | ||||||
|  |             } else { | ||||||
|  |                 panic!("TransmitAbort") | ||||||
|  |             } | ||||||
|  |         }*/ | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get whether the link is up | ||||||
|  |     pub fn is_link_up(&mut self) -> bool { | ||||||
|  |         let bits = self.read_phy_register(phy::Register::PHSTAT2); | ||||||
|  |         phy::PHSTAT2(bits).lstat() == 1 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Returns the interface Maximum Transmission Unit (MTU) | ||||||
|  |     /// | ||||||
|  |     /// The value returned by this function will never exceed 1514 bytes. The actual value depends | ||||||
|  |     /// on the memory assigned to the transmission buffer when initializing the device | ||||||
|  |     pub fn mtu(&self) -> u16 { | ||||||
|  |         cmp::min(BUF_SZ - RXND - 1, MAX_FRAME_LENGTH - CRC_SZ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* Miscellaneous */ | ||||||
|  |     /// Returns the number of packets that have been received but have not been processed yet | ||||||
|  |     pub fn pending_packets(&mut self) -> u8 { | ||||||
|  |         self.read_control_register(bank1::Register::EPKTCNT) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Adjusts the receive filter to *accept* these packet types | ||||||
|  |     pub fn accept(&mut self, packets: &[Packet]) { | ||||||
|  |         let mask = bank1::ERXFCON::mask(); | ||||||
|  |         let mut val = 0; | ||||||
|  |         for packet in packets { | ||||||
|  |             match packet { | ||||||
|  |                 Packet::Broadcast => val |= mask.bcen(), | ||||||
|  |                 Packet::Multicast => val |= mask.mcen(), | ||||||
|  |                 Packet::Unicast => val |= mask.ucen(), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.bit_field_set(bank1::Register::ERXFCON, val) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Adjusts the receive filter to *ignore* these packet types | ||||||
|  |     pub fn ignore(&mut self, packets: &[Packet]) { | ||||||
|  |         let mask = bank1::ERXFCON::mask(); | ||||||
|  |         let mut val = 0; | ||||||
|  |         for packet in packets { | ||||||
|  |             match packet { | ||||||
|  |                 Packet::Broadcast => val |= mask.bcen(), | ||||||
|  |                 Packet::Multicast => val |= mask.mcen(), | ||||||
|  |                 Packet::Unicast => val |= mask.ucen(), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.bit_field_clear(bank1::Register::ERXFCON, val) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* Private */ | ||||||
|  |     /* Read */ | ||||||
|  |     fn read_control_register<R>(&mut self, register: R) -> u8 | ||||||
|  |     where | ||||||
|  |         R: Into<Register>, | ||||||
|  |     { | ||||||
|  |         self._read_control_register(register.into()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn _read_control_register(&mut self, register: Register) -> u8 { | ||||||
|  |         self.change_bank(register); | ||||||
|  |  | ||||||
|  |         if register.is_eth_register() { | ||||||
|  |             let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0]; | ||||||
|  |             self.spi.transfer_in_place(&mut buffer).unwrap(); | ||||||
|  |             buffer[1] | ||||||
|  |         } else { | ||||||
|  |             // MAC, MII regs need a dummy byte. | ||||||
|  |             let mut buffer = [Instruction::RCR.opcode() | register.addr(), 0, 0]; | ||||||
|  |             self.spi.transfer_in_place(&mut buffer).unwrap(); | ||||||
|  |             buffer[2] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn read_phy_register(&mut self, register: phy::Register) -> u16 { | ||||||
|  |         // set PHY register address | ||||||
|  |         self.write_control_register(bank2::Register::MIREGADR, register.addr()); | ||||||
|  |  | ||||||
|  |         // start read operation | ||||||
|  |         self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(1).bits()); | ||||||
|  |  | ||||||
|  |         // wait until the read operation finishes | ||||||
|  |         while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {} | ||||||
|  |  | ||||||
|  |         self.write_control_register(bank2::Register::MICMD, bank2::MICMD::default().miird(0).bits()); | ||||||
|  |  | ||||||
|  |         let l = self.read_control_register(bank2::Register::MIRDL); | ||||||
|  |         let h = self.read_control_register(bank2::Register::MIRDH); | ||||||
|  |         (l as u16) | (h as u16) << 8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* Write */ | ||||||
|  |     fn _write_control_register(&mut self, register: Register, value: u8) { | ||||||
|  |         self.change_bank(register); | ||||||
|  |  | ||||||
|  |         let buffer = [Instruction::WCR.opcode() | register.addr(), value]; | ||||||
|  |         self.spi.write(&buffer).unwrap(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn write_control_register<R>(&mut self, register: R, value: u8) | ||||||
|  |     where | ||||||
|  |         R: Into<Register>, | ||||||
|  |     { | ||||||
|  |         self._write_control_register(register.into(), value) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn write_phy_register(&mut self, register: phy::Register, value: u16) { | ||||||
|  |         // set PHY register address | ||||||
|  |         self.write_control_register(bank2::Register::MIREGADR, register.addr()); | ||||||
|  |  | ||||||
|  |         self.write_control_register(bank2::Register::MIWRL, (value & 0xff) as u8); | ||||||
|  |         // this starts the write operation | ||||||
|  |         self.write_control_register(bank2::Register::MIWRH, (value >> 8) as u8); | ||||||
|  |  | ||||||
|  |         // wait until the write operation finishes | ||||||
|  |         while self.read_control_register(bank3::Register::MISTAT) & 0b1 != 0 {} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* RMW */ | ||||||
|  |     fn modify_control_register<R, F>(&mut self, register: R, f: F) | ||||||
|  |     where | ||||||
|  |         F: FnOnce(u8) -> u8, | ||||||
|  |         R: Into<Register>, | ||||||
|  |     { | ||||||
|  |         self._modify_control_register(register.into(), f) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn _modify_control_register<F>(&mut self, register: Register, f: F) | ||||||
|  |     where | ||||||
|  |         F: FnOnce(u8) -> u8, | ||||||
|  |     { | ||||||
|  |         let r = self._read_control_register(register); | ||||||
|  |         self._write_control_register(register, f(r)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* Auxiliary */ | ||||||
|  |     fn change_bank(&mut self, register: Register) { | ||||||
|  |         let bank = register.bank(); | ||||||
|  |  | ||||||
|  |         if let Some(bank) = bank { | ||||||
|  |             if self.bank == bank { | ||||||
|  |                 // already on the register bank | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // change bank | ||||||
|  |             self.bank = bank; | ||||||
|  |             match bank { | ||||||
|  |                 Bank::Bank0 => self.bit_field_clear(common::Register::ECON1, 0b11), | ||||||
|  |                 Bank::Bank1 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b01), | ||||||
|  |                 Bank::Bank2 => self.modify_control_register(common::Register::ECON1, |r| (r & !0b11) | 0b10), | ||||||
|  |                 Bank::Bank3 => self.bit_field_set(common::Register::ECON1, 0b11), | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // common register | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* Primitive operations */ | ||||||
|  |     fn bit_field_clear<R>(&mut self, register: R, mask: u8) | ||||||
|  |     where | ||||||
|  |         R: Into<Register>, | ||||||
|  |     { | ||||||
|  |         self._bit_field_clear(register.into(), mask) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn _bit_field_clear(&mut self, register: Register, mask: u8) { | ||||||
|  |         debug_assert!(register.is_eth_register()); | ||||||
|  |  | ||||||
|  |         self.change_bank(register); | ||||||
|  |  | ||||||
|  |         self.spi | ||||||
|  |             .write(&[Instruction::BFC.opcode() | register.addr(), mask]) | ||||||
|  |             .unwrap(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn bit_field_set<R>(&mut self, register: R, mask: u8) | ||||||
|  |     where | ||||||
|  |         R: Into<Register>, | ||||||
|  |     { | ||||||
|  |         self._bit_field_set(register.into(), mask) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn _bit_field_set(&mut self, register: Register, mask: u8) { | ||||||
|  |         debug_assert!(register.is_eth_register()); | ||||||
|  |  | ||||||
|  |         self.change_bank(register); | ||||||
|  |  | ||||||
|  |         self.spi | ||||||
|  |             .write(&[Instruction::BFS.opcode() | register.addr(), mask]) | ||||||
|  |             .unwrap(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn read_buffer_memory(&mut self, addr: Option<u16>, buf: &mut [u8]) { | ||||||
|  |         if let Some(addr) = addr { | ||||||
|  |             self.write_control_register(bank0::Register::ERDPTL, addr.low()); | ||||||
|  |             self.write_control_register(bank0::Register::ERDPTH, addr.high()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.spi | ||||||
|  |             .transaction(&mut [Operation::Write(&[Instruction::RBM.opcode()]), Operation::Read(buf)]) | ||||||
|  |             .unwrap(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn soft_reset(&mut self) { | ||||||
|  |         self.spi.write(&[Instruction::SRC.opcode()]).unwrap(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn write_buffer_memory(&mut self, addr: Option<u16>, buffer: &[u8]) { | ||||||
|  |         if let Some(addr) = addr { | ||||||
|  |             self.write_control_register(bank0::Register::EWRPTL, addr.low()); | ||||||
|  |             self.write_control_register(bank0::Register::EWRPTH, addr.high()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self.spi | ||||||
|  |             .transaction(&mut [Operation::Write(&[Instruction::WBM.opcode()]), Operation::Write(buffer)]) | ||||||
|  |             .unwrap(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, PartialEq)] | ||||||
|  | enum Bank { | ||||||
|  |     Bank0, | ||||||
|  |     Bank1, | ||||||
|  |     Bank2, | ||||||
|  |     Bank3, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | enum Instruction { | ||||||
|  |     /// Read Control Register | ||||||
|  |     RCR = 0b000_00000, | ||||||
|  |     /// Read Buffer Memory | ||||||
|  |     RBM = 0b001_11010, | ||||||
|  |     /// Write Control Register | ||||||
|  |     WCR = 0b010_00000, | ||||||
|  |     /// Write Buffer Memory | ||||||
|  |     WBM = 0b011_11010, | ||||||
|  |     /// Bit Field Set | ||||||
|  |     BFS = 0b100_00000, | ||||||
|  |     /// Bit Field Clear | ||||||
|  |     BFC = 0b101_00000, | ||||||
|  |     /// System Reset Command | ||||||
|  |     SRC = 0b111_11111, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Instruction { | ||||||
|  |     fn opcode(&self) -> u8 { | ||||||
|  |         *self as u8 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | enum Register { | ||||||
|  |     Bank0(bank0::Register), | ||||||
|  |     Bank1(bank1::Register), | ||||||
|  |     Bank2(bank2::Register), | ||||||
|  |     Bank3(bank3::Register), | ||||||
|  |     Common(common::Register), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Register { | ||||||
|  |     fn addr(&self) -> u8 { | ||||||
|  |         match *self { | ||||||
|  |             Register::Bank0(r) => r.addr(), | ||||||
|  |             Register::Bank1(r) => r.addr(), | ||||||
|  |             Register::Bank2(r) => r.addr(), | ||||||
|  |             Register::Bank3(r) => r.addr(), | ||||||
|  |             Register::Common(r) => r.addr(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn bank(&self) -> Option<Bank> { | ||||||
|  |         Some(match *self { | ||||||
|  |             Register::Bank0(_) => Bank::Bank0, | ||||||
|  |             Register::Bank1(_) => Bank::Bank1, | ||||||
|  |             Register::Bank2(_) => Bank::Bank2, | ||||||
|  |             Register::Bank3(_) => Bank::Bank3, | ||||||
|  |             Register::Common(_) => return None, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn is_eth_register(&self) -> bool { | ||||||
|  |         match *self { | ||||||
|  |             Register::Bank0(r) => r.is_eth_register(), | ||||||
|  |             Register::Bank1(r) => r.is_eth_register(), | ||||||
|  |             Register::Bank2(r) => r.is_eth_register(), | ||||||
|  |             Register::Bank3(r) => r.is_eth_register(), | ||||||
|  |             Register::Common(r) => r.is_eth_register(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Packet type, used to configure receive filters | ||||||
|  | #[non_exhaustive] | ||||||
|  | #[derive(Clone, Copy, Eq, PartialEq)] | ||||||
|  | pub enum Packet { | ||||||
|  |     /// Broadcast packets | ||||||
|  |     Broadcast, | ||||||
|  |     /// Multicast packets | ||||||
|  |     Multicast, | ||||||
|  |     /// Unicast packets | ||||||
|  |     Unicast, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static mut TX_BUF: [u8; MTU] = [0; MTU]; | ||||||
|  | static mut RX_BUF: [u8; MTU] = [0; MTU]; | ||||||
|  |  | ||||||
|  | impl<S, O> embassy_net_driver::Driver for Enc28j60<S, O> | ||||||
|  | where | ||||||
|  |     S: SpiDevice, | ||||||
|  |     O: OutputPin, | ||||||
|  | { | ||||||
|  |     type RxToken<'a> = RxToken<'a> | ||||||
|  |     where | ||||||
|  |         Self: 'a; | ||||||
|  |  | ||||||
|  |     type TxToken<'a> = TxToken<'a, S, O> | ||||||
|  |     where | ||||||
|  |         Self: 'a; | ||||||
|  |  | ||||||
|  |     fn receive(&mut self, cx: &mut core::task::Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { | ||||||
|  |         let rx_buf = unsafe { &mut RX_BUF }; | ||||||
|  |         let tx_buf = unsafe { &mut TX_BUF }; | ||||||
|  |         if let Some(pkt) = self.receive(rx_buf) { | ||||||
|  |             let n = pkt.len(); | ||||||
|  |             Some((RxToken { buf: &mut pkt[..n] }, TxToken { buf: tx_buf, eth: self })) | ||||||
|  |         } else { | ||||||
|  |             cx.waker().wake_by_ref(); | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn transmit(&mut self, _cx: &mut core::task::Context) -> Option<Self::TxToken<'_>> { | ||||||
|  |         let tx_buf = unsafe { &mut TX_BUF }; | ||||||
|  |         Some(TxToken { buf: tx_buf, eth: self }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn link_state(&mut self, cx: &mut core::task::Context) -> LinkState { | ||||||
|  |         cx.waker().wake_by_ref(); | ||||||
|  |         match self.is_link_up() { | ||||||
|  |             true => LinkState::Up, | ||||||
|  |             false => LinkState::Down, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn capabilities(&self) -> Capabilities { | ||||||
|  |         let mut caps = Capabilities::default(); | ||||||
|  |         caps.max_transmission_unit = MTU; | ||||||
|  |         caps.medium = Medium::Ethernet; | ||||||
|  |         caps | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn hardware_address(&self) -> HardwareAddress { | ||||||
|  |         HardwareAddress::Ethernet(self.mac_addr) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// embassy-net RX token. | ||||||
|  | pub struct RxToken<'a> { | ||||||
|  |     buf: &'a mut [u8], | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> embassy_net_driver::RxToken for RxToken<'a> { | ||||||
|  |     fn consume<R, F>(self, f: F) -> R | ||||||
|  |     where | ||||||
|  |         F: FnOnce(&mut [u8]) -> R, | ||||||
|  |     { | ||||||
|  |         f(self.buf) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// embassy-net TX token. | ||||||
|  | pub struct TxToken<'a, S, O> | ||||||
|  | where | ||||||
|  |     S: SpiDevice, | ||||||
|  |     O: OutputPin, | ||||||
|  | { | ||||||
|  |     eth: &'a mut Enc28j60<S, O>, | ||||||
|  |     buf: &'a mut [u8], | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a, S, O> embassy_net_driver::TxToken for TxToken<'a, S, O> | ||||||
|  | where | ||||||
|  |     S: SpiDevice, | ||||||
|  |     O: OutputPin, | ||||||
|  | { | ||||||
|  |     fn consume<R, F>(self, len: usize, f: F) -> R | ||||||
|  |     where | ||||||
|  |         F: FnOnce(&mut [u8]) -> R, | ||||||
|  |     { | ||||||
|  |         assert!(len <= self.buf.len()); | ||||||
|  |         let r = f(&mut self.buf[..len]); | ||||||
|  |         self.eth.transmit(&self.buf[..len]); | ||||||
|  |         r | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								embassy-net-enc28j60/src/macros.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								embassy-net-enc28j60/src/macros.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | macro_rules! register { | ||||||
|  |     ($REGISTER:ident, $reset_value:expr, $uxx:ty, { | ||||||
|  |         $(#[$($attr:tt)*] $bitfield:ident @ $range:expr,)+ | ||||||
|  |     }) => { | ||||||
|  |         #[derive(Clone, Copy)] | ||||||
|  |         pub(crate) struct $REGISTER<MODE> { | ||||||
|  |             bits: $uxx, | ||||||
|  |             _mode: ::core::marker::PhantomData<MODE>, | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         impl $REGISTER<super::traits::Mask> { | ||||||
|  |             #[allow(dead_code)] | ||||||
|  |             pub(crate) fn mask() -> $REGISTER<super::traits::Mask> { | ||||||
|  |                 $REGISTER { bits: 0, _mode: ::core::marker::PhantomData } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             $( | ||||||
|  |                 #[allow(dead_code)] | ||||||
|  |                 pub(crate) fn $bitfield(&self) -> $uxx { | ||||||
|  |                     use super::traits::OffsetSize; | ||||||
|  |  | ||||||
|  |                     let size = $range.size(); | ||||||
|  |                     let offset = $range.offset(); | ||||||
|  |                     ((1 << size) - 1) << offset | ||||||
|  |                 } | ||||||
|  |             )+ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         impl ::core::default::Default for $REGISTER<super::traits::W> { | ||||||
|  |             fn default() -> Self { | ||||||
|  |                 $REGISTER { bits: $reset_value, _mode: ::core::marker::PhantomData } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #[allow(non_snake_case)] | ||||||
|  |         #[allow(dead_code)] | ||||||
|  |         pub(crate) fn $REGISTER(bits: $uxx) -> $REGISTER<super::traits::R> { | ||||||
|  |             $REGISTER { bits, _mode: ::core::marker::PhantomData } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         impl $REGISTER<super::traits::R> { | ||||||
|  |             #[allow(dead_code)] | ||||||
|  |             pub(crate) fn modify(self) -> $REGISTER<super::traits::W> { | ||||||
|  |                 $REGISTER { bits: self.bits, _mode: ::core::marker::PhantomData } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             $( | ||||||
|  |                 #[$($attr)*] | ||||||
|  |                 #[allow(dead_code)] | ||||||
|  |                 pub(crate) fn $bitfield(&self) -> $uxx { | ||||||
|  |                     use super::traits::OffsetSize; | ||||||
|  |  | ||||||
|  |                     let offset = $range.offset(); | ||||||
|  |                     let size = $range.size(); | ||||||
|  |                     let mask = (1 << size) - 1; | ||||||
|  |  | ||||||
|  |                     (self.bits >> offset) & mask | ||||||
|  |                 } | ||||||
|  |             )+ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         impl $REGISTER<super::traits::W> { | ||||||
|  |             #[allow(dead_code)] | ||||||
|  |             pub(crate) fn bits(self) -> $uxx { | ||||||
|  |                 self.bits | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             $( | ||||||
|  |                 #[$($attr)*] | ||||||
|  |                 #[allow(dead_code)] | ||||||
|  |                 pub(crate) fn $bitfield(&mut self, mut bits: $uxx) -> &mut Self { | ||||||
|  |                     use super::traits::OffsetSize; | ||||||
|  |  | ||||||
|  |                     let offset = $range.offset(); | ||||||
|  |                     let size = $range.size(); | ||||||
|  |                     let mask = (1 << size) - 1; | ||||||
|  |  | ||||||
|  |                     debug_assert!(bits <= mask); | ||||||
|  |                     bits &= mask; | ||||||
|  |  | ||||||
|  |                     self.bits &= !(mask << offset); | ||||||
|  |                     self.bits |= bits << offset; | ||||||
|  |  | ||||||
|  |                     self | ||||||
|  |                 } | ||||||
|  |             )+ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								embassy-net-enc28j60/src/phy.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								embassy-net-enc28j60/src/phy.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | #[allow(dead_code)] | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub enum Register { | ||||||
|  |     PHCON1 = 0x00, | ||||||
|  |     PHSTAT1 = 0x01, | ||||||
|  |     PHID1 = 0x02, | ||||||
|  |     PHID2 = 0x03, | ||||||
|  |     PHCON2 = 0x10, | ||||||
|  |     PHSTAT2 = 0x11, | ||||||
|  |     PHIE = 0x12, | ||||||
|  |     PHIR = 0x13, | ||||||
|  |     PHLCON = 0x14, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Register { | ||||||
|  |     pub(crate) fn addr(&self) -> u8 { | ||||||
|  |         *self as u8 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | register!(PHCON2, 0, u16, { | ||||||
|  |     #[doc = "PHY Half-Duplex Loopback Disable bit"] | ||||||
|  |     hdldis @ 8, | ||||||
|  |     #[doc = "Jabber Correction Disable bit"] | ||||||
|  |     jabber @ 10, | ||||||
|  |     #[doc = "Twisted-Pair Transmitter Disable bit"] | ||||||
|  |     txdis @ 13, | ||||||
|  |     #[doc = "PHY Force Linkup bit"] | ||||||
|  |     frclnk @ 14, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | register!(PHSTAT2, 0, u16, { | ||||||
|  |     #[doc = "Link Status bit"] | ||||||
|  |     lstat @ 10, | ||||||
|  | }); | ||||||
							
								
								
									
										57
									
								
								embassy-net-enc28j60/src/traits.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								embassy-net-enc28j60/src/traits.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | use core::ops::Range; | ||||||
|  |  | ||||||
|  | pub(crate) trait OffsetSize { | ||||||
|  |     fn offset(self) -> u8; | ||||||
|  |     fn size(self) -> u8; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl OffsetSize for u8 { | ||||||
|  |     fn offset(self) -> u8 { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn size(self) -> u8 { | ||||||
|  |         1 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl OffsetSize for Range<u8> { | ||||||
|  |     fn offset(self) -> u8 { | ||||||
|  |         self.start | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn size(self) -> u8 { | ||||||
|  |         self.end - self.start | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub(crate) trait U16Ext { | ||||||
|  |     fn from_parts(low: u8, high: u8) -> Self; | ||||||
|  |  | ||||||
|  |     fn low(self) -> u8; | ||||||
|  |  | ||||||
|  |     fn high(self) -> u8; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl U16Ext for u16 { | ||||||
|  |     fn from_parts(low: u8, high: u8) -> u16 { | ||||||
|  |         ((high as u16) << 8) + low as u16 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn low(self) -> u8 { | ||||||
|  |         (self & 0xff) as u8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn high(self) -> u8 { | ||||||
|  |         (self >> 8) as u8 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub struct Mask; | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub struct R; | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy)] | ||||||
|  | pub struct W; | ||||||
| @@ -7,14 +7,20 @@ edition = "2021" | |||||||
| defmt = { version = "0.3", optional = true } | defmt = { version = "0.3", optional = true } | ||||||
| log = { version = "0.4.14", optional = true } | log = { version = "0.4.14", optional = true } | ||||||
|  |  | ||||||
| embassy-time = { version = "0.1.0", path = "../embassy-time" } | embassy-time = { version = "0.1.3", path = "../embassy-time" } | ||||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync"} | embassy-sync = { version = "0.2.0", path = "../embassy-sync"} | ||||||
| embassy-futures = { version = "0.1.0", path = "../embassy-futures"} | embassy-futures = { version = "0.1.0", path = "../embassy-futures"} | ||||||
| embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} | embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} | ||||||
|  |  | ||||||
| embedded-hal = { version = "1.0.0-alpha.10" } | embedded-hal = { version = "1.0.0-rc.1" } | ||||||
| embedded-hal-async = { version = "=0.2.0-alpha.1" } | embedded-hal-async = { version = "=1.0.0-rc.1" } | ||||||
|  |  | ||||||
| noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] } | noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] } | ||||||
| #noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] } | #noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] } | ||||||
| heapless = "0.7.16" | heapless = "0.7.16" | ||||||
|  |  | ||||||
|  | [package.metadata.embassy_docs] | ||||||
|  | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/" | ||||||
|  | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-esp-hosted/src/" | ||||||
|  | target = "thumbv7em-none-eabi" | ||||||
|  | features = ["defmt"] | ||||||
|   | |||||||
| @@ -1,14 +1,16 @@ | |||||||
| use ch::driver::LinkState; | use ch::driver::LinkState; | ||||||
| use defmt::Debug2Format; |  | ||||||
| use embassy_net_driver_channel as ch; | use embassy_net_driver_channel as ch; | ||||||
| use heapless::String; | use heapless::String; | ||||||
|  |  | ||||||
| use crate::ioctl::Shared; | use crate::ioctl::Shared; | ||||||
| use crate::proto::{self, CtrlMsg}; | use crate::proto::{self, CtrlMsg}; | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Copy, Clone, PartialEq, Eq, Debug)] | ||||||
| pub struct Error { | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
|     pub status: u32, | pub enum Error { | ||||||
|  |     Failed(u32), | ||||||
|  |     Timeout, | ||||||
|  |     Internal, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct Control<'a> { | pub struct Control<'a> { | ||||||
| @@ -17,6 +19,8 @@ pub struct Control<'a> { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[allow(unused)] | #[allow(unused)] | ||||||
|  | #[derive(Copy, Clone, PartialEq, Eq, Debug)] | ||||||
|  | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
| enum WifiMode { | enum WifiMode { | ||||||
|     None = 0, |     None = 0, | ||||||
|     Sta = 1, |     Sta = 1, | ||||||
| @@ -24,92 +28,124 @@ enum WifiMode { | |||||||
|     ApSta = 3, |     ApSta = 3, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub use proto::CtrlWifiSecProt as Security; | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug)] | ||||||
|  | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||||||
|  | pub struct Status { | ||||||
|  |     pub ssid: String<32>, | ||||||
|  |     pub bssid: [u8; 6], | ||||||
|  |     pub rssi: i32, | ||||||
|  |     pub channel: u32, | ||||||
|  |     pub security: Security, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | macro_rules! ioctl { | ||||||
|  |     ($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => { | ||||||
|  |         let mut msg = proto::CtrlMsg { | ||||||
|  |             msg_id: proto::CtrlMsgId::$req_variant as _, | ||||||
|  |             msg_type: proto::CtrlMsgType::Req as _, | ||||||
|  |             payload: Some(proto::CtrlMsgPayload::$req_variant($req)), | ||||||
|  |         }; | ||||||
|  |         $self.ioctl(&mut msg).await?; | ||||||
|  |         #[allow(unused_mut)] | ||||||
|  |         let Some(proto::CtrlMsgPayload::$resp_variant(mut $resp)) = msg.payload | ||||||
|  |         else { | ||||||
|  |             warn!("unexpected response variant"); | ||||||
|  |             return Err(Error::Internal); | ||||||
|  |         }; | ||||||
|  |         if $resp.resp != 0 { | ||||||
|  |             return Err(Error::Failed($resp.resp)); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
| impl<'a> Control<'a> { | impl<'a> Control<'a> { | ||||||
|     pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self { |     pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self { | ||||||
|         Self { state_ch, shared } |         Self { state_ch, shared } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub async fn init(&mut self) { |     pub async fn init(&mut self) -> Result<(), Error> { | ||||||
|         debug!("wait for init event..."); |         debug!("wait for init event..."); | ||||||
|         self.shared.init_wait().await; |         self.shared.init_wait().await; | ||||||
|  |  | ||||||
|         debug!("set wifi mode"); |         debug!("set heartbeat"); | ||||||
|         self.set_wifi_mode(WifiMode::Sta as _).await; |         self.set_heartbeat(10).await?; | ||||||
|  |  | ||||||
|         let mac_addr = self.get_mac_addr().await; |         debug!("set wifi mode"); | ||||||
|  |         self.set_wifi_mode(WifiMode::Sta as _).await?; | ||||||
|  |  | ||||||
|  |         let mac_addr = self.get_mac_addr().await?; | ||||||
|         debug!("mac addr: {:02x}", mac_addr); |         debug!("mac addr: {:02x}", mac_addr); | ||||||
|         self.state_ch.set_ethernet_address(mac_addr); |         self.state_ch.set_ethernet_address(mac_addr); | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub async fn join(&mut self, ssid: &str, password: &str) { |     pub async fn get_status(&mut self) -> Result<Status, Error> { | ||||||
|         let req = proto::CtrlMsg { |         let req = proto::CtrlMsgReqGetApConfig {}; | ||||||
|             msg_id: proto::CtrlMsgId::ReqConnectAp as _, |         ioctl!(self, ReqGetApConfig, RespGetApConfig, req, resp); | ||||||
|             msg_type: proto::CtrlMsgType::Req as _, |         trim_nulls(&mut resp.ssid); | ||||||
|             payload: Some(proto::CtrlMsgPayload::ReqConnectAp(proto::CtrlMsgReqConnectAp { |         Ok(Status { | ||||||
|                 ssid: String::from(ssid), |             ssid: resp.ssid, | ||||||
|                 pwd: String::from(password), |             bssid: parse_mac(&resp.bssid)?, | ||||||
|                 bssid: String::new(), |             rssi: resp.rssi as _, | ||||||
|                 listen_interval: 3, |             channel: resp.chnl, | ||||||
|                 is_wpa3_supported: false, |             security: resp.sec_prot, | ||||||
|             })), |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> { | ||||||
|  |         let req = proto::CtrlMsgReqConnectAp { | ||||||
|  |             ssid: String::from(ssid), | ||||||
|  |             pwd: String::from(password), | ||||||
|  |             bssid: String::new(), | ||||||
|  |             listen_interval: 3, | ||||||
|  |             is_wpa3_supported: false, | ||||||
|         }; |         }; | ||||||
|         let resp = self.ioctl(req).await; |         ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); | ||||||
|         let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; |  | ||||||
|         debug!("======= {:?}", Debug2Format(&resp)); |  | ||||||
|         assert_eq!(resp.resp, 0); |  | ||||||
|         self.state_ch.set_link_state(LinkState::Up); |         self.state_ch.set_link_state(LinkState::Up); | ||||||
|  |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn get_mac_addr(&mut self) -> [u8; 6] { |     pub async fn disconnect(&mut self) -> Result<(), Error> { | ||||||
|         let req = proto::CtrlMsg { |         let req = proto::CtrlMsgReqGetStatus {}; | ||||||
|             msg_id: proto::CtrlMsgId::ReqGetMacAddress as _, |         ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp); | ||||||
|             msg_type: proto::CtrlMsgType::Req as _, |         self.state_ch.set_link_state(LinkState::Down); | ||||||
|             payload: Some(proto::CtrlMsgPayload::ReqGetMacAddress( |         Ok(()) | ||||||
|                 proto::CtrlMsgReqGetMacAddress { |     } | ||||||
|                     mode: WifiMode::Sta as _, |  | ||||||
|                 }, |     /// duration in seconds, clamped to [10, 3600] | ||||||
|             )), |     async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> { | ||||||
|  |         let req = proto::CtrlMsgReqConfigHeartbeat { enable: true, duration }; | ||||||
|  |         ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> { | ||||||
|  |         let req = proto::CtrlMsgReqGetMacAddress { | ||||||
|  |             mode: WifiMode::Sta as _, | ||||||
|         }; |         }; | ||||||
|         let resp = self.ioctl(req).await; |         ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp); | ||||||
|         let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; |         parse_mac(&resp.mac) | ||||||
|         assert_eq!(resp.resp, 0); |  | ||||||
|  |  | ||||||
|         // WHY IS THIS A STRING? WHYYYY |  | ||||||
|         fn nibble_from_hex(b: u8) -> u8 { |  | ||||||
|             match b { |  | ||||||
|                 b'0'..=b'9' => b - b'0', |  | ||||||
|                 b'a'..=b'f' => b + 0xa - b'a', |  | ||||||
|                 b'A'..=b'F' => b + 0xa - b'A', |  | ||||||
|                 _ => panic!("invalid hex digit {}", b), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let mac = resp.mac.as_bytes(); |  | ||||||
|         let mut res = [0; 6]; |  | ||||||
|         assert_eq!(mac.len(), 17); |  | ||||||
|         for (i, b) in res.iter_mut().enumerate() { |  | ||||||
|             *b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1]) |  | ||||||
|         } |  | ||||||
|         res |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn set_wifi_mode(&mut self, mode: u32) { |     async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> { | ||||||
|         let req = proto::CtrlMsg { |         let req = proto::CtrlMsgReqSetMode { mode }; | ||||||
|             msg_id: proto::CtrlMsgId::ReqSetWifiMode as _, |         ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp); | ||||||
|             msg_type: proto::CtrlMsgType::Req as _, |  | ||||||
|             payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })), |         Ok(()) | ||||||
|         }; |  | ||||||
|         let resp = self.ioctl(req).await; |  | ||||||
|         let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; |  | ||||||
|         assert_eq!(resp.resp, 0); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn ioctl(&mut self, req: CtrlMsg) -> CtrlMsg { |     async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> { | ||||||
|         debug!("ioctl req: {:?}", &req); |         debug!("ioctl req: {:?}", &msg); | ||||||
|  |  | ||||||
|         let mut buf = [0u8; 128]; |         let mut buf = [0u8; 128]; | ||||||
|  |  | ||||||
|         let req_len = noproto::write(&req, &mut buf).unwrap(); |         let req_len = noproto::write(msg, &mut buf).map_err(|_| { | ||||||
|  |             warn!("failed to serialize control request"); | ||||||
|  |             Error::Internal | ||||||
|  |         })?; | ||||||
|  |  | ||||||
|         struct CancelOnDrop<'a>(&'a Shared); |         struct CancelOnDrop<'a>(&'a Shared); | ||||||
|  |  | ||||||
| @@ -131,9 +167,44 @@ impl<'a> Control<'a> { | |||||||
|  |  | ||||||
|         ioctl.defuse(); |         ioctl.defuse(); | ||||||
|  |  | ||||||
|         let res = noproto::read(&buf[..resp_len]).unwrap(); |         *msg = noproto::read(&buf[..resp_len]).map_err(|_| { | ||||||
|         debug!("ioctl resp: {:?}", &res); |             warn!("failed to serialize control request"); | ||||||
|  |             Error::Internal | ||||||
|  |         })?; | ||||||
|  |         debug!("ioctl resp: {:?}", msg); | ||||||
|  |  | ||||||
|         res |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WHY IS THIS A STRING? WHYYYY | ||||||
|  | fn parse_mac(mac: &str) -> Result<[u8; 6], Error> { | ||||||
|  |     fn nibble_from_hex(b: u8) -> Result<u8, Error> { | ||||||
|  |         match b { | ||||||
|  |             b'0'..=b'9' => Ok(b - b'0'), | ||||||
|  |             b'a'..=b'f' => Ok(b + 0xa - b'a'), | ||||||
|  |             b'A'..=b'F' => Ok(b + 0xa - b'A'), | ||||||
|  |             _ => { | ||||||
|  |                 warn!("invalid hex digit {}", b); | ||||||
|  |                 Err(Error::Internal) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let mac = mac.as_bytes(); | ||||||
|  |     let mut res = [0; 6]; | ||||||
|  |     if mac.len() != 17 { | ||||||
|  |         warn!("unexpected MAC length"); | ||||||
|  |         return Err(Error::Internal); | ||||||
|  |     } | ||||||
|  |     for (i, b) in res.iter_mut().enumerate() { | ||||||
|  |         *b = (nibble_from_hex(mac[i * 3])? << 4) | nibble_from_hex(mac[i * 3 + 1])? | ||||||
|  |     } | ||||||
|  |     Ok(res) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn trim_nulls<const N: usize>(s: &mut String<N>) { | ||||||
|  |     while s.chars().rev().next() == Some(0 as char) { | ||||||
|  |         s.pop(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -93,7 +93,7 @@ macro_rules! unreachable { | |||||||
| #[cfg(feature = "defmt")] | #[cfg(feature = "defmt")] | ||||||
| macro_rules! unreachable { | macro_rules! unreachable { | ||||||
|     ($($x:tt)*) => { |     ($($x:tt)*) => { | ||||||
|         ::defmt::unreachable!($($x)*); |         ::defmt::unreachable!($($x)*) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -229,7 +229,8 @@ impl<T, E> Try for Result<T, E> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct Bytes<'a>(pub &'a [u8]); | #[allow(unused)] | ||||||
|  | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||||||
|  |  | ||||||
| impl<'a> Debug for Bytes<'a> { | impl<'a> Debug for Bytes<'a> { | ||||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||||
|   | |||||||
| @@ -1,17 +1,15 @@ | |||||||
| #![no_std] | #![no_std] | ||||||
|  |  | ||||||
| use control::Control; | use embassy_futures::select::{select4, Either4}; | ||||||
| use embassy_futures::select::{select3, Either3}; |  | ||||||
| use embassy_net_driver_channel as ch; | use embassy_net_driver_channel as ch; | ||||||
|  | use embassy_net_driver_channel::driver::LinkState; | ||||||
| use embassy_time::{Duration, Instant, Timer}; | use embassy_time::{Duration, Instant, Timer}; | ||||||
| use embedded_hal::digital::{InputPin, OutputPin}; | use embedded_hal::digital::{InputPin, OutputPin}; | ||||||
| use embedded_hal_async::digital::Wait; | use embedded_hal_async::digital::Wait; | ||||||
| use embedded_hal_async::spi::SpiDevice; | use embedded_hal_async::spi::SpiDevice; | ||||||
| use ioctl::Shared; |  | ||||||
| use proto::CtrlMsg; |  | ||||||
|  |  | ||||||
| use crate::ioctl::PendingIoctl; | use crate::ioctl::{PendingIoctl, Shared}; | ||||||
| use crate::proto::CtrlMsgPayload; | use crate::proto::{CtrlMsg, CtrlMsgPayload}; | ||||||
|  |  | ||||||
| mod proto; | mod proto; | ||||||
|  |  | ||||||
| @@ -21,6 +19,8 @@ mod fmt; | |||||||
| mod control; | mod control; | ||||||
| mod ioctl; | mod ioctl; | ||||||
|  |  | ||||||
|  | pub use control::*; | ||||||
|  |  | ||||||
| const MTU: usize = 1514; | const MTU: usize = 1514; | ||||||
|  |  | ||||||
| macro_rules! impl_bytes { | macro_rules! impl_bytes { | ||||||
| @@ -95,6 +95,7 @@ enum InterfaceType { | |||||||
| } | } | ||||||
|  |  | ||||||
| const MAX_SPI_BUFFER_SIZE: usize = 1600; | const MAX_SPI_BUFFER_SIZE: usize = 1600; | ||||||
|  | const HEARTBEAT_MAX_GAP: Duration = Duration::from_secs(20); | ||||||
|  |  | ||||||
| pub struct State { | pub struct State { | ||||||
|     shared: Shared, |     shared: Shared, | ||||||
| @@ -124,17 +125,19 @@ where | |||||||
|     IN: InputPin + Wait, |     IN: InputPin + Wait, | ||||||
|     OUT: OutputPin, |     OUT: OutputPin, | ||||||
| { | { | ||||||
|     let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); |     let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); | ||||||
|     let state_ch = ch_runner.state_runner(); |     let state_ch = ch_runner.state_runner(); | ||||||
|  |  | ||||||
|     let mut runner = Runner { |     let mut runner = Runner { | ||||||
|         ch: ch_runner, |         ch: ch_runner, | ||||||
|  |         state_ch, | ||||||
|         shared: &state.shared, |         shared: &state.shared, | ||||||
|         next_seq: 1, |         next_seq: 1, | ||||||
|         handshake, |         handshake, | ||||||
|         ready, |         ready, | ||||||
|         reset, |         reset, | ||||||
|         spi, |         spi, | ||||||
|  |         heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, | ||||||
|     }; |     }; | ||||||
|     runner.init().await; |     runner.init().await; | ||||||
|  |  | ||||||
| @@ -143,9 +146,11 @@ where | |||||||
|  |  | ||||||
| pub struct Runner<'a, SPI, IN, OUT> { | pub struct Runner<'a, SPI, IN, OUT> { | ||||||
|     ch: ch::Runner<'a, MTU>, |     ch: ch::Runner<'a, MTU>, | ||||||
|  |     state_ch: ch::StateRunner<'a>, | ||||||
|     shared: &'a Shared, |     shared: &'a Shared, | ||||||
|  |  | ||||||
|     next_seq: u16, |     next_seq: u16, | ||||||
|  |     heartbeat_deadline: Instant, | ||||||
|  |  | ||||||
|     spi: SPI, |     spi: SPI, | ||||||
|     handshake: IN, |     handshake: IN, | ||||||
| @@ -177,9 +182,10 @@ where | |||||||
|             let ioctl = self.shared.ioctl_wait_pending(); |             let ioctl = self.shared.ioctl_wait_pending(); | ||||||
|             let tx = self.ch.tx_buf(); |             let tx = self.ch.tx_buf(); | ||||||
|             let ev = async { self.ready.wait_for_high().await.unwrap() }; |             let ev = async { self.ready.wait_for_high().await.unwrap() }; | ||||||
|  |             let hb = Timer::at(self.heartbeat_deadline); | ||||||
|  |  | ||||||
|             match select3(ioctl, tx, ev).await { |             match select4(ioctl, tx, ev, hb).await { | ||||||
|                 Either3::First(PendingIoctl { buf, req_len }) => { |                 Either4::First(PendingIoctl { buf, req_len }) => { | ||||||
|                     tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02"); |                     tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02"); | ||||||
|                     tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes()); |                     tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes()); | ||||||
|                     tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]); |                     tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]); | ||||||
| @@ -198,7 +204,7 @@ where | |||||||
|                     header.checksum = checksum(&tx_buf[..26 + req_len]); |                     header.checksum = checksum(&tx_buf[..26 + req_len]); | ||||||
|                     tx_buf[0..12].copy_from_slice(&header.to_bytes()); |                     tx_buf[0..12].copy_from_slice(&header.to_bytes()); | ||||||
|                 } |                 } | ||||||
|                 Either3::Second(packet) => { |                 Either4::Second(packet) => { | ||||||
|                     tx_buf[12..][..packet.len()].copy_from_slice(packet); |                     tx_buf[12..][..packet.len()].copy_from_slice(packet); | ||||||
|  |  | ||||||
|                     let mut header = PayloadHeader { |                     let mut header = PayloadHeader { | ||||||
| @@ -217,9 +223,12 @@ where | |||||||
|  |  | ||||||
|                     self.ch.tx_done(); |                     self.ch.tx_done(); | ||||||
|                 } |                 } | ||||||
|                 Either3::Third(()) => { |                 Either4::Third(()) => { | ||||||
|                     tx_buf[..PayloadHeader::SIZE].fill(0); |                     tx_buf[..PayloadHeader::SIZE].fill(0); | ||||||
|                 } |                 } | ||||||
|  |                 Either4::Fourth(()) => { | ||||||
|  |                     panic!("heartbeat from esp32 stopped") | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if tx_buf[0] != 0 { |             if tx_buf[0] != 0 { | ||||||
| @@ -308,21 +317,26 @@ where | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn handle_event(&self, data: &[u8]) { |     fn handle_event(&mut self, data: &[u8]) { | ||||||
|         let Ok(event) = noproto::read::<CtrlMsg>(data) else { |         let Ok(event) = noproto::read::<CtrlMsg>(data) else { | ||||||
|             warn!("failed to parse event"); |             warn!("failed to parse event"); | ||||||
|             return |             return; | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         debug!("event: {:?}", &event); |         debug!("event: {:?}", &event); | ||||||
|  |  | ||||||
|         let Some(payload) = &event.payload else { |         let Some(payload) = &event.payload else { | ||||||
|             warn!("event without payload?"); |             warn!("event without payload?"); | ||||||
|             return |             return; | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         match payload { |         match payload { | ||||||
|             CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(), |             CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(), | ||||||
|  |             CtrlMsgPayload::EventHeartbeat(_) => self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP, | ||||||
|  |             CtrlMsgPayload::EventStationDisconnectFromAp(e) => { | ||||||
|  |                 info!("disconnected, code {}", e.resp); | ||||||
|  |                 self.state_ch.set_link_state(LinkState::Down); | ||||||
|  |             } | ||||||
|             _ => {} |             _ => {} | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								embassy-net-ppp/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								embassy-net-ppp/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | [package] | ||||||
|  | name = "embassy-net-ppp" | ||||||
|  | version = "0.1.0" | ||||||
|  | description = "embassy-net driver for PPP over Serial" | ||||||
|  | keywords = ["embedded", "ppp", "embassy-net", "embedded-hal-async", "ethernet", "async"] | ||||||
|  | categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] | ||||||
|  | license = "MIT OR Apache-2.0" | ||||||
|  | edition = "2021" | ||||||
|  |  | ||||||
|  | [features] | ||||||
|  | defmt = ["dep:defmt", "ppproto/defmt"] | ||||||
|  | log = ["dep:log", "ppproto/log"] | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | defmt = { version = "0.3", optional = true } | ||||||
|  | log = { version = "0.4.14", optional = true } | ||||||
|  |  | ||||||
|  | embedded-io-async = { version = "0.5.0" } | ||||||
|  | embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } | ||||||
|  | embassy-futures = { version = "0.1.0", path = "../embassy-futures" } | ||||||
|  | ppproto = { version = "0.1.2"} | ||||||
|  | embassy-sync = { version = "0.2.0", path = "../embassy-sync" } | ||||||
|  |  | ||||||
|  | [package.metadata.embassy_docs] | ||||||
|  | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/" | ||||||
|  | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-ppp/src/" | ||||||
|  | target = "thumbv7em-none-eabi" | ||||||
|  | features = ["defmt"] | ||||||
							
								
								
									
										19
									
								
								embassy-net-ppp/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								embassy-net-ppp/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | # `embassy-net-ppp` | ||||||
|  |  | ||||||
|  | [`embassy-net`](https://crates.io/crates/embassy-net) integration for PPP over Serial. | ||||||
|  |  | ||||||
|  | ## Interoperability | ||||||
|  |  | ||||||
|  | This crate can run on any executor. | ||||||
|  |  | ||||||
|  | It supports any serial port implementing [`embedded-io-async`](https://crates.io/crates/embedded-io-async). | ||||||
|  |  | ||||||
|  | ## 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