Compare commits
958 Commits
embassy-sy
...
embassy-ne
Author | SHA1 | Date | |
---|---|---|---|
ba43444292 | |||
6eac49186d | |||
4feabb13bf | |||
3f19879f41 | |||
e90f47aba3 | |||
2aa2b843ce | |||
fa2cda81db | |||
837d3bcdbb | |||
9f50f34547 | |||
f0b17675d8 | |||
6eb46c419c | |||
96f1525ffe | |||
01101e3df0 | |||
b95e5a4ea6 | |||
f7ec579c18 | |||
4f0aca481f | |||
ce889900d6 | |||
8a0a7c81b6 | |||
e892014b65 | |||
8cbe5b8e20 | |||
5666c56903 | |||
d5898c11eb | |||
daedfbbd87 | |||
bf7e24e9d7 | |||
02f367f733 | |||
f2e7a23148 | |||
27a89019ad | |||
2eb7a67c70 | |||
59f829c6cc | |||
91c31d5e43 | |||
9b5d7ec061 | |||
ed493be869 | |||
f5ca687e9b | |||
9c81d63155 | |||
60c54107ce | |||
56dd22f0ac | |||
afec1b439b | |||
219ef5b37a | |||
a2d1e7f02c | |||
5e6e18b310 | |||
80407aa930 | |||
a575e40a35 | |||
28fb492c40 | |||
45561f1622 | |||
715bf20c41 | |||
ac2b7928c5 | |||
64cba950e5 | |||
e7bc84dda8 | |||
d8c70c5c3e | |||
12872ce49b | |||
2809e926cf | |||
aa0ab06645 | |||
03e0116a56 | |||
018622f607 | |||
8cafaa1f3c | |||
df944edeef | |||
388d3e273d | |||
49333ce6ad | |||
d43417e97c | |||
91fdd76053 | |||
f23b34951a | |||
29f32ce00e | |||
4dd48099be | |||
915f79c974 | |||
ea04a0277b | |||
6f17286c75 | |||
caf63b9e73 | |||
64ff1a6b75 | |||
558247d8f6 | |||
23c51a1874 | |||
70907d84f1 | |||
8bbfa6827c | |||
6e65282f18 | |||
0d02298ea6 | |||
1ed909ea74 | |||
764b43e82c | |||
082f1ab494 | |||
ec2c095a76 | |||
6c123596b7 | |||
3dbd58f40e | |||
810c6af77a | |||
cd4f8f13a2 | |||
78736328a0 | |||
8d0095c618 | |||
1f2be2dac5 | |||
5247c1c795 | |||
fdb3c3d6ff | |||
2e625138ff | |||
ca21027eea | |||
0a551eb7c6 | |||
5dd0d35021 | |||
0d67ef795e | |||
978e7b5e77 | |||
161d3ce05c | |||
37a1e9f971 | |||
5a075acc6a | |||
0998221478 | |||
428a4ba3f9 | |||
aaad906815 | |||
76659d9003 | |||
990dd5e5db | |||
56ab6d9f14 | |||
88052480b1 | |||
218b102b28 | |||
fe7b72948a | |||
3c70f799a2 | |||
e0747e937f | |||
320e2cf35b | |||
bbc81146ec | |||
faf7aeba27 | |||
558918651e | |||
0122b813d3 | |||
72fd648d92 | |||
b95c0210b8 | |||
f8ee33abb9 | |||
9f63158aad | |||
748d1ea89d | |||
39334f7280 | |||
7177e7ea1a | |||
adaed307b4 | |||
b4f96e192c | |||
ae83e6f536 | |||
443550b353 | |||
6d7d617f40 | |||
c7b0df569b | |||
041a4a4208 | |||
4d2d7d7684 | |||
faa58b9074 | |||
6b5d55eb29 | |||
b0a2f0c4fe | |||
4c9b7befaa | |||
6ef060ca17 | |||
391f0b5d09 | |||
9e8de5f596 | |||
78a2ca8a0e | |||
f5d084552d | |||
e1161dfc80 | |||
ec36225f8a | |||
0ac43d3e7c | |||
6ed36cd9c7 | |||
f6c1108bdf | |||
54fc933932 | |||
af451b5462 | |||
837ebe405f | |||
d236f3dbf9 | |||
d23717904b | |||
837950cd74 | |||
61aa6b5236 | |||
c94ba84892 | |||
64e3310e64 | |||
4601f4e1eb | |||
ae9983324d | |||
bc0734eee5 | |||
c1fc98c313 | |||
c484f0715b | |||
29513074ee | |||
f15a0203e4 | |||
6c13f381c4 | |||
38891c29ea | |||
d82c2a1c26 | |||
055f6afdcc | |||
b55e618175 | |||
0053a8a1a7 | |||
3c98587a88 | |||
9cfcc5b89a | |||
7f63fbbf4a | |||
c822fd46e5 | |||
35db5cf416 | |||
23724b6bf6 | |||
6863786243 | |||
c5a0e8579a | |||
8d5f995a01 | |||
bf32dc5d3a | |||
802416d267 | |||
553c934325 | |||
bb5ceb2d9c | |||
a1b27783a6 | |||
cf83f6820c | |||
2d89cfb18f | |||
2dd5ce83ec | |||
ca8957da43 | |||
ab86b06050 | |||
6653f262d7 | |||
98c821ac39 | |||
dc8e34420f | |||
479ccf17df | |||
d46b2b49c7 | |||
3465452a93 | |||
c0331cdf89 | |||
8ddeaddc67 | |||
0915fb73b2 | |||
2e0bc71c86 | |||
3dde01597a | |||
4716166041 | |||
8a1d3d5c84 | |||
b68cf6c5c8 | |||
8c93805ab5 | |||
f498c689e7 | |||
5c2f02c735 | |||
bce24e8005 | |||
921780e6bf | |||
ce1d72c609 | |||
2455fd4dbe | |||
352f0b6c38 | |||
87ad66f2b4 | |||
2eb08b2dc9 | |||
ca47af6978 | |||
ae1dedc059 | |||
ada7ec2289 | |||
d2a6c5c608 | |||
18578fd15f | |||
d7f674e410 | |||
e871324bde | |||
54bab33c73 | |||
a9fdd468d5 | |||
6701606e4c | |||
79b982c941 | |||
593fc78dd8 | |||
4f03dff577 | |||
162d485304 | |||
70e1b976d8 | |||
adf053a935 | |||
5ee26a5dd1 | |||
44624b2d7a | |||
d690a1717f | |||
f6d75970d8 | |||
a4b8fc420a | |||
4185c10bf8 | |||
ade46489f1 | |||
e83762e979 | |||
6fe853a7d3 | |||
24c4ea71b1 | |||
675499449f | |||
2f269f3256 | |||
f2c2536cf3 | |||
3539dd7d4c | |||
d414f4e4f7 | |||
dd5ce985bd | |||
f901cf57e5 | |||
fa7510968a | |||
d36feb6405 | |||
404aa29289 | |||
2a435e53b7 | |||
c036eab62c | |||
1d8321b821 | |||
d7d66bd74f | |||
e8527bac69 | |||
25f367432d | |||
7fcded5705 | |||
c7e6c7ed18 | |||
046a99aba0 | |||
35083b262b | |||
16bfbd4e99 | |||
98398d31f7 | |||
4f203ae175 | |||
d70994e4a8 | |||
82d765689a | |||
7f0e778145 | |||
3f90620343 | |||
3f35a8876e | |||
b3bbe5eb2d | |||
c327c6cd6f | |||
f5d0d28ac3 | |||
05688934a1 | |||
36bd6c817e | |||
c22d2b5b5b | |||
b703db4c09 | |||
b527cc98af | |||
36e00caf4d | |||
c2aca45b8d | |||
887ecef369 | |||
24dee870a8 | |||
54bbb4400d | |||
c6a984f506 | |||
551f76c700 | |||
b23e40f722 | |||
1cd87f0028 | |||
c5ec453ec1 | |||
5205b5b095 | |||
311236e81e | |||
392ed64f6f | |||
f8d35806dc | |||
1d34078fa1 | |||
020e956f1b | |||
3760bdbb1b | |||
42a5b14724 | |||
08753f74ae | |||
da0be7114f | |||
f8a835be27 | |||
68441a74c2 | |||
1a31b03976 | |||
94046f30ff | |||
aba0f8fd6c | |||
3b38079490 | |||
c844894a6e | |||
642eb1400b | |||
1acbc5b1a9 | |||
421ee4dfbf | |||
46961cfdf7 | |||
cc23129456 | |||
6cb6e57592 | |||
760a32246a | |||
7b2a39a6fb | |||
bab03a3927 | |||
403cbb1dc9 | |||
bd6a1d38d2 | |||
0a136c308e | |||
5d7301e510 | |||
09d52638b5 | |||
37e104a6b3 | |||
7e501855fc | |||
c19967dcf2 | |||
85ce44f78e | |||
6d8f409018 | |||
bea42a78a4 | |||
cb5df138d6 | |||
ef8695cecb | |||
e495473fc3 | |||
62e799da09 | |||
a8b426d0fe | |||
f4736457f5 | |||
f501a1ba2c | |||
ee20fd4c16 | |||
fee89ed7c7 | |||
c5c5b64729 | |||
3081ecf301 | |||
66304a102d | |||
859e539f85 | |||
984cd47b41 | |||
2ccf9f3abd | |||
31b364b9b0 | |||
307f2365da | |||
d82ba4af8a | |||
35d8edbc41 | |||
9115431d35 | |||
e08267df54 | |||
74104aafda | |||
4478d8322b | |||
88543445d8 | |||
41a632a56c | |||
524a89cc72 | |||
8938d928f8 | |||
b50d04336e | |||
ce331b411c | |||
8528455a75 | |||
d28dc08f09 | |||
344e28360f | |||
983f01016b | |||
9eca19b49d | |||
860b519f99 | |||
181c4c5311 | |||
18d14dff48 | |||
b412784a7a | |||
dc28a42fd2 | |||
0d80a95e54 | |||
8073bf22e9 | |||
e764a3d9ca | |||
49a31bd5d8 | |||
7371eefa86 | |||
15636f05f5 | |||
efc71e08c4 | |||
bac8ad565e | |||
525e065474 | |||
44b6494ab7 | |||
6df6239704 | |||
c6ffece410 | |||
34a2804b54 | |||
cdb1447569 | |||
7477785bbb | |||
0e90e98e9b | |||
f616b22159 | |||
c5bf36eebf | |||
abbaaeee37 | |||
387a4fcb8e | |||
cd6256a924 | |||
06f5c309c0 | |||
a58c7b60bc | |||
673396c0e7 | |||
963f3e3059 | |||
373eb97357 | |||
b20427b2ec | |||
c02759ad91 | |||
8b1eaf00a0 | |||
baf1c2efbe | |||
cd8198037f | |||
e65ff85b88 | |||
9370973846 | |||
8b13a7b338 | |||
dfd5603171 | |||
966f0abf48 | |||
1329a387e0 | |||
6804b6c0b4 | |||
ff3a70ed9d | |||
0a26870d36 | |||
b2775fc90c | |||
6efcc9acaa | |||
2a589b7904 | |||
5f10eadb8d | |||
224faccd4c | |||
ce1078994d | |||
b6ba1ea53a | |||
316be179af | |||
e785e1bc22 | |||
3cc0ec654a | |||
a19f8c32ff | |||
87acf5f50f | |||
14e3e72b0f | |||
faf506b300 | |||
879c621394 | |||
627d7f66ef | |||
3ad52f837d | |||
49eaf000b8 | |||
1fdde8f03f | |||
64092169e3 | |||
187551f914 | |||
cd1bf31fed | |||
d54eb1107e | |||
e9121cba2c | |||
059ab358a5 | |||
ab7d129e15 | |||
12720737e1 | |||
18c62aa5b4 | |||
d1dfaa1905 | |||
1f65a4eb6f | |||
eb09d7d671 | |||
7f702fd6f1 | |||
383bef1711 | |||
5e86188c25 | |||
661b1f3373 | |||
d55b9bc6e2 | |||
1ebb742fbf | |||
1be6e53316 | |||
d737e3dcbb | |||
a1cbdd8d29 | |||
8b9306ed5c | |||
df56f901de | |||
9f7392474b | |||
9dff6b9d81 | |||
d736c9205c | |||
464018e12d | |||
55e4a89819 | |||
a521a9b5ce | |||
f43d57846e | |||
4b303949bf | |||
fc746a88b5 | |||
1e029a9e66 | |||
053d5629ba | |||
1b3d9a0aef | |||
f79d8cb2d3 | |||
0d4ab559a7 | |||
1379eb4e70 | |||
f97b591831 | |||
d97a771479 | |||
5bbed31513 | |||
d3494a4bdf | |||
2f2860b096 | |||
e1e87fef25 | |||
908ec5faef | |||
a4772c15c0 | |||
4e9ed223a9 | |||
56f2e0c9a0 | |||
b950d6d72b | |||
ab63f3832f | |||
b0541c01be | |||
56c3a949af | |||
34a0c2172b | |||
0c18a13cc0 | |||
1a87f7477a | |||
14a5d03af2 | |||
a8953b5c66 | |||
d97724cca3 | |||
2bf2e54db9 | |||
288309b9d5 | |||
d07821d851 | |||
62857bdb2d | |||
26d7610554 | |||
6e93d193cf | |||
4567eff78e | |||
2d65373f63 | |||
ae4827587c | |||
a3d6aa5d7d | |||
db907a914c | |||
cdd326284a | |||
3e9d5978c0 | |||
ec7a4fd9cc | |||
977a7906e4 | |||
7cfce05bd2 | |||
f46e0eb5f2 | |||
5fe36b6bb0 | |||
6c1137177f | |||
8800caa216 | |||
82f7e104d9 | |||
2fcdfc4876 | |||
dec75474d5 | |||
3edd81a94e | |||
3810fe6a20 | |||
7f96359804 | |||
bf45b1d83d | |||
d7262f9849 | |||
8a620fd59c | |||
bbd687fcb0 | |||
7b83d53bbf | |||
adefa4f86b | |||
e179e7cf85 | |||
9d971e5b15 | |||
5cfe1a1fb4 | |||
856b944eaf | |||
0e3cd87a32 | |||
6bea078487 | |||
5015c845c5 | |||
c1eaad41f3 | |||
d40589f082 | |||
9d018a0075 | |||
fdc87a8e7f | |||
72b0379125 | |||
14eecf2fc4 | |||
0b4b87e344 | |||
5df263db38 | |||
0584312ef0 | |||
6b5d9642d5 | |||
881e9d07d2 | |||
a7dee5b65c | |||
d3d424dad3 | |||
008b1fd30c | |||
d0703f83db | |||
2910b09cba | |||
59132514cf | |||
1a96eae22c | |||
79c60f4a7d | |||
fb2d5b484a | |||
87795cbca8 | |||
db9b8eb88f | |||
b38d496d51 | |||
374c7513f9 | |||
bdcea84ca1 | |||
8e4d65e163 | |||
2873cb93ee | |||
37b460637d | |||
41ec4170a5 | |||
a9c7263ba0 | |||
5f7ef8bed0 | |||
ed843b519b | |||
09f078a1cc | |||
8ebe6e5f20 | |||
db2bc8783e | |||
705270faae | |||
c37f86ff1c | |||
2dcbe75cca | |||
5158014f3f | |||
4439031d43 | |||
067f1382e4 | |||
1cc61dc68a | |||
0d8d8d3320 | |||
72e36d8997 | |||
91612b7446 | |||
007f452927 | |||
3e728d5e73 | |||
b1ef856242 | |||
91d1fff4ed | |||
629e0ea595 | |||
02d6e0d14d | |||
7750ea65ba | |||
a0b1299890 | |||
733b83e44f | |||
8dbe397f99 | |||
c44c108db5 | |||
77f7830da3 | |||
909a5fe2e5 | |||
0997021a05 | |||
486fe9e59d | |||
906d2b2db7 | |||
79985f0036 | |||
6ad58f428a | |||
4ccb2bc95a | |||
17e78175a6 | |||
62841dd5b9 | |||
1e8da91def | |||
374c92a4f0 | |||
433422b9f2 | |||
a85b34c1fe | |||
1078f6f4e7 | |||
2bb6e93e86 | |||
2afa08c923 | |||
a61701b756 | |||
7a36072a15 | |||
a167c77d39 | |||
8839f3f62a | |||
ac111f40d8 | |||
3229b5e809 | |||
0c07eef3a9 | |||
371a80e1a2 | |||
b2047c4351 | |||
849011b826 | |||
6cec6fa09b | |||
0d224a00e1 | |||
47ae9b7981 | |||
8e22d57447 | |||
0107f83b53 | |||
bab30a7e87 | |||
5f99ccf54c | |||
54e695b1b2 | |||
8fc92fdf62 | |||
c6424fdc11 | |||
3c31236c10 | |||
cd88e39f5f | |||
6096f0cf4b | |||
a1d45303c3 | |||
7601779693 | |||
1806422763 | |||
00cde67abe | |||
96e8a7ddb9 | |||
25864ae4dc | |||
14e0090cb1 | |||
45843034ec | |||
7757405908 | |||
fc268df6f5 | |||
4ea6662e55 | |||
49455792cb | |||
855c0d1423 | |||
6ee45f5ec0 | |||
1296817f7b | |||
534cf7c618 | |||
05c36e05f9 | |||
73cd016885 | |||
0589f2f36e | |||
b612976cc7 | |||
b58b9ff390 | |||
1d5adb8974 | |||
be66e0f7ce | |||
861f49cfd4 | |||
7ab9fe0522 | |||
1c8492bab2 | |||
19588a9e6f | |||
1d2f6667df | |||
ac0ea406f9 | |||
7336b8cd88 | |||
bcbe3040a1 | |||
f4ade6af8b | |||
fa1ec29ae6 | |||
58e727d3b9 | |||
4cd5ed81aa | |||
4618b79b22 | |||
db16b6ff3f | |||
a9074fd09b | |||
f2469776f4 | |||
a10850a6da | |||
03737e4be4 | |||
bc34f3c60f | |||
c70a66fe81 | |||
a186694fdd | |||
af368676ef | |||
ce04b732d1 | |||
099ec7443b | |||
ff6748a0d8 | |||
7646f18836 | |||
5659269c8f | |||
41fe718ea8 | |||
94c6727b3f | |||
b77794c9a7 | |||
ba886b45b8 | |||
76b967a966 | |||
2c5d94493c | |||
2119b8e1ca | |||
49bed094a3 | |||
49ecd8d7c5 | |||
29cc661dca | |||
c19de29847 | |||
f4bfda345d | |||
3e730aa8b0 | |||
28a3454846 | |||
4d551a5865 | |||
91cddd50f6 | |||
9d610c6866 | |||
03d6363d5a | |||
d960bf344a | |||
31b54e0fbd | |||
3ba73b5ff4 | |||
8c733c29cc | |||
278818395e | |||
42a8f1671d | |||
1cf26f0eb3 | |||
d91c37dae3 | |||
759d911b50 | |||
a277deeaa5 | |||
cb00fb18cb | |||
f8b359dc5a | |||
edef790e1a | |||
18af9f304a | |||
deadf40c85 | |||
405649ddc7 | |||
0c8e5f92c7 | |||
e2410cbb6a | |||
52decfb16c | |||
91047c61b9 | |||
054ca17f66 | |||
0a2d6f0be0 | |||
0c7ce80384 | |||
123c110427 | |||
0d82ebea29 | |||
f729d2d060 | |||
9e96655757 | |||
582a15a693 | |||
2d7ba44621 | |||
73f25093c7 | |||
a3f727e2e1 | |||
0dea7b02d6 | |||
a4866ad278 | |||
d78edba0d4 | |||
b283f213d9 | |||
ba47fe9c41 | |||
8285263fc2 | |||
cc5bca8e83 | |||
0a2f7b4661 | |||
6a1a3e6877 | |||
02c86bca52 | |||
fb27594b2e | |||
a73f9474a0 | |||
3bf41e9a06 | |||
0e01b28d5e | |||
8aca324c2d | |||
7ee9e8322c | |||
43c20dbe65 | |||
8cd117fd5d | |||
f64d1131b6 | |||
b153a5b0d7 | |||
9b51c8f4d4 | |||
510ae7e3dc | |||
f67eb84ec7 | |||
5de6bb3adf | |||
54fe50c685 | |||
837cdacd16 | |||
41e90e22e2 | |||
64b80c2e4d | |||
26f4d7d283 | |||
fdd6e08ed6 | |||
7d64de153f | |||
e24421a393 | |||
4de4039417 | |||
f589247c1f | |||
37181c79d9 | |||
a2ac1eed1b | |||
bfa3cbaf30 | |||
216b120f15 | |||
08d9e5981e | |||
8a9136e4e4 | |||
3260f6b2af | |||
2080d8bb6d | |||
a673b9aa29 | |||
38c5b97df0 | |||
21ea98810a | |||
fbd6eeb748 | |||
dbded7a6ce | |||
efc70debb3 | |||
173c65b543 | |||
a86a100879 | |||
bba8b0ded5 | |||
095f5ef279 | |||
1c68c62ebd | |||
f5216624bb | |||
46227bec1e | |||
e63a34ba21 | |||
82dd7a5f8c | |||
0dfa192992 | |||
e14fa11fc3 | |||
df7ef1d98f | |||
9202dbf32a | |||
4044d728a6 | |||
6acc361109 | |||
27ec29e2c5 | |||
90c1422381 | |||
9e1ddeac86 | |||
99dcbf00c4 | |||
776e001b5b | |||
6ba2bb1a7f | |||
a258e15c23 | |||
7a682ec02a | |||
e9ede443bc | |||
bc550cbfda | |||
8da9c07a65 | |||
bd6bb2d248 | |||
81f10e136a | |||
1fdce6e52a | |||
efb67dfc1b | |||
f395ec44e8 | |||
63941432e3 | |||
be0f93ff37 | |||
224eaaf797 | |||
f681b9d4e5 | |||
650589ab3f | |||
b9fc2a6b33 | |||
3002ee0dcf | |||
ce0e1a5db3 | |||
9ca5bcd576 | |||
4be1e4bd44 | |||
a3ecf5caf6 | |||
a7629299f4 | |||
e7ff759f1c | |||
6a6c673c5f | |||
c1d5f86871 | |||
0289630fe4 | |||
1b86570cfd | |||
f51cbebffd | |||
c309797488 | |||
46efce6ea2 | |||
683ad80479 | |||
4d2710ed4d | |||
056b8ab5a2 | |||
eb32d8ebbd | |||
cbbfeb23be | |||
8d7abeb06f | |||
9876571887 | |||
289762c0ef | |||
fd5c7acafc | |||
76ebebd0c5 | |||
608eb9b1fd | |||
6b2aaacf83 | |||
e6e5685f7c | |||
69db1535b2 | |||
d3fb9ddf33 | |||
b2d63d851d | |||
869b337715 | |||
fe57e4d9f8 | |||
781c7f978c | |||
c15f07de4f | |||
983a94a9c5 | |||
e9e2be137f | |||
20ea35fc96 | |||
d918919cb2 | |||
056df98d47 | |||
273e6f5b83 | |||
8926397f45 | |||
07fe37b5ee | |||
1c721cb20e | |||
b58cc2aa23 | |||
a2272dda08 | |||
cde6f0f862 | |||
c7646eb699 | |||
4c52104413 | |||
fd3de78b43 | |||
6f547cf05d | |||
4721381225 | |||
5da6108bec | |||
ed601d439a | |||
cffc3fc795 | |||
bb90bb8c56 | |||
ba9afbc26d | |||
20923080e6 | |||
8b24fe3df0 | |||
369f205962 | |||
33cbc22236 | |||
359b1c7fdb | |||
29494a9296 | |||
f82f931dc2 | |||
3034e8fb45 | |||
b4b8d82980 | |||
0e946dfb20 | |||
b411b7ce63 | |||
ee3d284609 | |||
67743bb122 | |||
1b410d6f3f | |||
a6a2a035d5 | |||
0ff606dfc1 | |||
d57fe0de86 | |||
e33b99e9ec | |||
f6f041b05d | |||
f34829f534 | |||
e3492862e9 | |||
0bcd1b1e10 | |||
5e74926907 | |||
218b44652c | |||
9900ac2c9a | |||
a2bae33d84 | |||
001610f0d0 | |||
072b8ce035 | |||
c1fa46bd36 | |||
871700f05d | |||
2548bbdd65 | |||
1b6799d93f | |||
076ada4c02 | |||
42cc0c6d73 | |||
56b50f8b62 | |||
432240162a | |||
f4c9014fe4 | |||
8a81114baf | |||
27771e60af | |||
86113e199f | |||
9223b67306 | |||
aff265a7f5 | |||
79cee74151 | |||
0d84533bcb | |||
c385bbf07d | |||
62c0b18f10 | |||
a83560c6b1 | |||
cb6d1fc514 | |||
3b04ef265c | |||
7bbd4671d3 | |||
9962db4ecf | |||
79a9a4eb98 | |||
270c4d2476 | |||
6b35f654ba | |||
11387c3b03 | |||
612000aa8f | |||
9aaefa6e71 | |||
281cbcb1e8 | |||
28bf4b7b6d | |||
3ba0b3ef3b | |||
8f21a5b116 | |||
9db9333d05 | |||
483edf694b | |||
e7d30194e3 | |||
520860622b | |||
e727fe8675 | |||
443e275f1f | |||
96214f9db6 | |||
be20512f17 | |||
a19bcb69d1 | |||
92136d27f6 | |||
f0b7f43c41 | |||
fe5229670f | |||
6b90ab8664 | |||
ea0738c485 | |||
29145e5f92 | |||
5c4d6232ae | |||
95f3484b87 | |||
2bd7205c79 | |||
acaa8b3e8b | |||
69e92e5639 | |||
f2ac14b86f | |||
193124bed1 | |||
63806022f3 | |||
3826b4f713 | |||
bb76a29ff1 | |||
9218aff498 | |||
945449b10f | |||
79c7be3fc6 | |||
6b4555a6a7 | |||
f76815d642 | |||
3388b5cecf | |||
ddfbfa0132 | |||
5ef40acd1d | |||
92505f53e2 | |||
726d68a706 | |||
54269a0761 | |||
13c88a9ca3 | |||
4205eef3ec | |||
931e3d7ee0 | |||
7dfdea8797 | |||
31410aa5b7 | |||
ce7353fba4 | |||
f60407feb3 | |||
e1fd7dfc40 | |||
18b11e7417 | |||
d96ad248b3 | |||
3ffdbd2ca3 | |||
30b7800f9a | |||
7ddcacac7b | |||
069a57fcf8 | |||
e560415fde |
16
.github/ci/build-stable.sh
vendored
Executable file
16
.github/ci/build-stable.sh
vendored
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
## on push branch~=gh-readonly-queue/main/.*
|
||||
## on pull_request
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export RUSTUP_HOME=/ci/cache/rustup
|
||||
export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
|
||||
hashtime restore /ci/cache/filetime.json || true
|
||||
hashtime save /ci/cache/filetime.json
|
||||
|
||||
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
|
||||
|
||||
./ci_stable.sh
|
19
.github/ci/build.sh
vendored
Executable file
19
.github/ci/build.sh
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
## on push branch~=gh-readonly-queue/main/.*
|
||||
## on pull_request
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export RUSTUP_HOME=/ci/cache/rustup
|
||||
export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
if [ -f /ci/secrets/teleprobe-token.txt ]; then
|
||||
echo Got teleprobe token!
|
||||
export TELEPROBE_HOST=https://teleprobe.embassy.dev
|
||||
export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt)
|
||||
fi
|
||||
|
||||
hashtime restore /ci/cache/filetime.json || true
|
||||
hashtime save /ci/cache/filetime.json
|
||||
|
||||
./ci.sh
|
44
.github/ci/doc.sh
vendored
Executable file
44
.github/ci/doc.sh
vendored
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
## on push branch=main
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export RUSTUP_HOME=/ci/cache/rustup
|
||||
export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
export BUILDER_THREADS=6
|
||||
export BUILDER_COMPRESS=true
|
||||
|
||||
# force rustup to download the toolchain before starting building.
|
||||
# Otherwise, the docs builder is running multiple instances of cargo rustdoc concurrently.
|
||||
# They all see the toolchain is not installed and try to install it in parallel
|
||||
# which makes rustup very sad
|
||||
rustc --version > /dev/null
|
||||
|
||||
docserver-builder -i ./embassy-stm32 -o crates/embassy-stm32/git.zup
|
||||
docserver-builder -i ./embassy-boot/boot -o crates/embassy-boot/git.zup
|
||||
docserver-builder -i ./embassy-boot/nrf -o crates/embassy-boot-nrf/git.zup
|
||||
docserver-builder -i ./embassy-boot/rp -o crates/embassy-boot-rp/git.zup
|
||||
docserver-builder -i ./embassy-boot/stm32 -o crates/embassy-boot-stm32/git.zup
|
||||
docserver-builder -i ./embassy-embedded-hal -o crates/embassy-embedded-hal/git.zup
|
||||
docserver-builder -i ./embassy-executor -o crates/embassy-executor/git.zup
|
||||
docserver-builder -i ./embassy-futures -o crates/embassy-futures/git.zup
|
||||
docserver-builder -i ./embassy-lora -o crates/embassy-lora/git.zup
|
||||
docserver-builder -i ./embassy-net -o crates/embassy-net/git.zup
|
||||
docserver-builder -i ./embassy-net-driver -o crates/embassy-net-driver/git.zup
|
||||
docserver-builder -i ./embassy-net-driver-channel -o crates/embassy-net-driver-channel/git.zup
|
||||
docserver-builder -i ./embassy-nrf -o crates/embassy-nrf/git.zup
|
||||
docserver-builder -i ./embassy-rp -o crates/embassy-rp/git.zup
|
||||
docserver-builder -i ./embassy-sync -o crates/embassy-sync/git.zup
|
||||
docserver-builder -i ./embassy-time -o crates/embassy-time/git.zup
|
||||
docserver-builder -i ./embassy-usb -o crates/embassy-usb/git.zup
|
||||
docserver-builder -i ./embassy-usb-driver -o crates/embassy-usb-driver/git.zup
|
||||
docserver-builder -i ./embassy-usb-logger -o crates/embassy-usb-logger/git.zup
|
||||
docserver-builder -i ./cyw43 -o crates/cyw43/git.zup
|
||||
docserver-builder -i ./cyw43-pio -o crates/cyw43-pio/git.zup
|
||||
docserver-builder -i ./embassy-net-w5500 -o crates/embassy-net-w5500/git.zup
|
||||
docserver-builder -i ./embassy-stm32-wpan -o crates/embassy-stm32-wpan/git.zup
|
||||
|
||||
export KUBECONFIG=/ci/secrets/kubeconfig.yml
|
||||
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
|
||||
kubectl cp crates $POD:/data
|
30
.github/ci/test.sh
vendored
Executable file
30
.github/ci/test.sh
vendored
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
## on push branch~=gh-readonly-queue/main/.*
|
||||
## on pull_request
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export RUSTUP_HOME=/ci/cache/rustup
|
||||
export CARGO_HOME=/ci/cache/cargo
|
||||
export CARGO_TARGET_DIR=/ci/cache/target
|
||||
|
||||
hashtime restore /ci/cache/filetime.json || true
|
||||
hashtime save /ci/cache/filetime.json
|
||||
|
||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-hal-common/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue
|
||||
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-dalek
|
||||
cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-salty
|
||||
|
||||
cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1,gpiote
|
||||
|
||||
cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features nightly,time-driver
|
||||
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti
|
||||
cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti
|
88
.github/workflows/doc.yml
vendored
88
.github/workflows/doc.yml
vendored
@ -1,88 +0,0 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
BUILDER_THREADS: '1'
|
||||
|
||||
jobs:
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Since stm32 crates take SO LONG to build, we split them
|
||||
# into a separate job. This way it doesn't slow down updating
|
||||
# the rest.
|
||||
strategy:
|
||||
matrix:
|
||||
crates:
|
||||
#- stm32 # runs out of disk space...
|
||||
- rest
|
||||
|
||||
# This will ensure at most one doc build job is running at a time
|
||||
# (for stm32 and non-stm32 independently).
|
||||
# If another job is already running, the new job will wait.
|
||||
# If another job is already waiting, it'll be canceled.
|
||||
# This means some commits will be skipped, but that's fine because
|
||||
# we only care that the latest gets built.
|
||||
concurrency: doc-${{ matrix.crates }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Rust targets
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-gnu
|
||||
rustup target add wasm32-unknown-unknown
|
||||
rustup target add thumbv6m-none-eabi
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add thumbv7em-none-eabi
|
||||
rustup target add thumbv7em-none-eabihf
|
||||
rustup target add thumbv8m.base-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabihf
|
||||
|
||||
- name: Install docserver
|
||||
run: |
|
||||
wget -q -O /usr/local/bin/builder "https://github.com/embassy-rs/docserver/releases/download/v0.4/builder"
|
||||
chmod +x /usr/local/bin/builder
|
||||
|
||||
- name: build-stm32
|
||||
if: ${{ matrix.crates=='stm32' }}
|
||||
run: |
|
||||
mkdir crates
|
||||
builder ./embassy-stm32 crates/embassy-stm32/git.zup
|
||||
|
||||
- name: build-rest
|
||||
if: ${{ matrix.crates=='rest' }}
|
||||
run: |
|
||||
mkdir crates
|
||||
builder ./embassy-boot/boot crates/embassy-boot/git.zup
|
||||
builder ./embassy-boot/nrf crates/embassy-boot-nrf/git.zup
|
||||
builder ./embassy-boot/stm32 crates/embassy-boot-stm32/git.zup
|
||||
builder ./embassy-cortex-m crates/embassy-cortex-m/git.zup
|
||||
builder ./embassy-embedded-hal crates/embassy-embedded-hal/git.zup
|
||||
builder ./embassy-executor crates/embassy-executor/git.zup
|
||||
builder ./embassy-futures crates/embassy-futures/git.zup
|
||||
builder ./embassy-lora crates/embassy-lora/git.zup
|
||||
builder ./embassy-net crates/embassy-net/git.zup
|
||||
builder ./embassy-net-driver crates/embassy-net-driver/git.zup
|
||||
builder ./embassy-net-driver-channel crates/embassy-net-driver-channel/git.zup
|
||||
builder ./embassy-nrf crates/embassy-nrf/git.zup
|
||||
builder ./embassy-rp crates/embassy-rp/git.zup
|
||||
builder ./embassy-sync crates/embassy-sync/git.zup
|
||||
builder ./embassy-time crates/embassy-time/git.zup
|
||||
builder ./embassy-usb crates/embassy-usb/git.zup
|
||||
builder ./embassy-usb-driver crates/embassy-usb-driver/git.zup
|
||||
builder ./embassy-usb-logger crates/embassy-usb-logger/git.zup
|
||||
|
||||
- name: upload
|
||||
run: |
|
||||
mkdir -p ~/.kube
|
||||
echo "${{secrets.KUBECONFIG}}" > ~/.kube/config
|
||||
POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
|
||||
kubectl cp crates $POD:/data
|
||||
|
||||
|
78
.github/workflows/rust.yml
vendored
78
.github/workflows/rust.yml
vendored
@ -1,78 +0,0 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [staging, trying, master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
all:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-nightly, build-stable, test]
|
||||
steps:
|
||||
- name: Done
|
||||
run: exit 0
|
||||
build-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache multiple paths
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target_ci
|
||||
key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
|
||||
- name: build
|
||||
run: |
|
||||
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
|
||||
chmod +x /usr/local/bin/cargo-batch
|
||||
./ci.sh
|
||||
rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*
|
||||
build-stable:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache multiple paths
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target_ci_stable
|
||||
key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }}
|
||||
- name: build
|
||||
run: |
|
||||
curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch
|
||||
chmod +x /usr/local/bin/cargo-batch
|
||||
./ci_stable.sh
|
||||
rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Test boot
|
||||
working-directory: ./embassy-boot/boot
|
||||
run: cargo test && cargo test --features "ed25519-dalek" && cargo test --features "ed25519-salty"
|
||||
|
||||
- name: Test sync
|
||||
working-directory: ./embassy-sync
|
||||
run: cargo test
|
4
.vscode/.gitignore
vendored
Normal file
4
.vscode/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.cortex-debug.*.json
|
||||
launch.json
|
||||
tasks.json
|
||||
*.cfg
|
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@ -6,20 +6,21 @@
|
||||
"rust-analyzer.check.allTargets": false,
|
||||
"rust-analyzer.check.noDefaultFeatures": true,
|
||||
"rust-analyzer.cargo.noDefaultFeatures": true,
|
||||
"rust-analyzer.cargo.target": "thumbv7em-none-eabi",
|
||||
"rust-analyzer.cargo.target": "thumbv7m-none-eabi",
|
||||
//"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
|
||||
"rust-analyzer.cargo.features": [
|
||||
"nightly",
|
||||
///"nightly",
|
||||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
// Declare for the target you wish to develop
|
||||
// "embassy-executor/Cargo.toml",
|
||||
// "embassy-sync/Cargo.toml",
|
||||
"examples/nrf52840/Cargo.toml",
|
||||
"examples/stm32wl/Cargo.toml",
|
||||
// "examples/nrf5340/Cargo.toml",
|
||||
// "examples/nrf-rtos-trace/Cargo.toml",
|
||||
// "examples/rp/Cargo.toml",
|
||||
// "examples/std/Cargo.toml",
|
||||
// "examples/stm32c0/Cargo.toml",
|
||||
// "examples/stm32f0/Cargo.toml",
|
||||
// "examples/stm32f1/Cargo.toml",
|
||||
// "examples/stm32f2/Cargo.toml",
|
||||
@ -28,6 +29,7 @@
|
||||
// "examples/stm32f7/Cargo.toml",
|
||||
// "examples/stm32g0/Cargo.toml",
|
||||
// "examples/stm32g4/Cargo.toml",
|
||||
// "examples/stm32h5/Cargo.toml",
|
||||
// "examples/stm32h7/Cargo.toml",
|
||||
// "examples/stm32l0/Cargo.toml",
|
||||
// "examples/stm32l1/Cargo.toml",
|
||||
@ -35,9 +37,7 @@
|
||||
// "examples/stm32l5/Cargo.toml",
|
||||
// "examples/stm32u5/Cargo.toml",
|
||||
// "examples/stm32wb/Cargo.toml",
|
||||
// "examples/stm32wb55/Cargo.toml",
|
||||
// "examples/stm32wl/Cargo.toml",
|
||||
// "examples/stm32wl55/Cargo.toml",
|
||||
// "examples/wasm/Cargo.toml",
|
||||
],
|
||||
}
|
15
README.md
15
README.md
@ -35,7 +35,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
|
||||
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.
|
||||
|
||||
- **LoRa** -
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers.
|
||||
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking.
|
||||
|
||||
- **USB** -
|
||||
<a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.
|
||||
@ -99,17 +99,10 @@ Examples are found in the `examples/` folder seperated by the chip manufacturer
|
||||
|
||||
### Running examples
|
||||
|
||||
- Setup git submodules (needed for STM32 examples)
|
||||
- Install `probe-rs`.
|
||||
|
||||
```bash
|
||||
git submodule init
|
||||
git submodule update
|
||||
```
|
||||
|
||||
- Install `probe-run` with defmt support.
|
||||
|
||||
```bash
|
||||
cargo install probe-run
|
||||
cargo install probe-rs --features cli
|
||||
```
|
||||
|
||||
- Change directory to the sample's base directory. For example:
|
||||
@ -123,7 +116,7 @@ cd examples/nrf52840
|
||||
For example:
|
||||
|
||||
```bash
|
||||
cargo run --bin blinky
|
||||
cargo run --release --bin blinky
|
||||
```
|
||||
|
||||
## Developing Embassy with Rust Analyzer based editors
|
||||
|
113
ci.sh
113
ci.sh
@ -2,9 +2,12 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export CARGO_TARGET_DIR=$PWD/target_ci
|
||||
export RUSTFLAGS=-Dwarnings
|
||||
export DEFMT_LOG=trace
|
||||
export DEFMT_LOG=trace,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')
|
||||
|
||||
@ -13,7 +16,7 @@ if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then
|
||||
BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std"
|
||||
fi
|
||||
|
||||
find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2018
|
||||
find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2021
|
||||
|
||||
cargo batch \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \
|
||||
@ -22,10 +25,19 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-executor/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-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \
|
||||
--- 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,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,unstable-traits,nightly \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \
|
||||
@ -45,6 +57,20 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
|
||||
--- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,time-driver-any \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \
|
||||
@ -53,6 +79,7 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l422cb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wb15cc,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l072cz,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,unstable-traits \
|
||||
@ -68,16 +95,24 @@ cargo batch \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h503rb,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h562ag,defmt,exti,time-driver-any,unstable-traits \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs' \
|
||||
--- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \
|
||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \
|
||||
--- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,nightly \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,nightly \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4,nightly \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \
|
||||
--- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \
|
||||
--- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \
|
||||
--- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \
|
||||
--- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \
|
||||
@ -98,63 +133,39 @@ cargo batch \
|
||||
--- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \
|
||||
--- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \
|
||||
--- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 --out-dir out/examples/boot/nrf --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns --out-dir out/examples/boot/nrf --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/rp --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f3 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f7 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32h7 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/stm32l0 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/boot/stm32l1 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32l4 --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wl --bin b \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf \
|
||||
--- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf \
|
||||
--- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32h7 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l0 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \
|
||||
--- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/bluepill-stm32f103c8 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/nucleo-stm32f429zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/nucleo-stm32g491re \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/nucleo-stm32g071rb \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/nucleo-stm32c031c6 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/nucleo-stm32h755zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/nucleo-stm32wb55rg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/nucleo-stm32h563zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/iot-stm32u585ai \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \
|
||||
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \
|
||||
--- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \
|
||||
--- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \
|
||||
--- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \
|
||||
$BUILD_EXTRA
|
||||
|
||||
|
||||
function run_elf {
|
||||
echo Running target=$1 elf=$2
|
||||
STATUSCODE=$(
|
||||
curl \
|
||||
-sS \
|
||||
--output /dev/stderr \
|
||||
--write-out "%{http_code}" \
|
||||
-H "Authorization: Bearer $TELEPROBE_TOKEN" \
|
||||
https://teleprobe.embassy.dev/targets/$1/run --data-binary @$2
|
||||
)
|
||||
echo
|
||||
echo HTTP Status code: $STATUSCODE
|
||||
test "$STATUSCODE" -eq 200
|
||||
}
|
||||
|
||||
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
|
||||
if [[ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN-}" ]]; then
|
||||
echo No teleprobe token found, skipping running HIL tests
|
||||
exit
|
||||
fi
|
||||
|
||||
export TELEPROBE_TOKEN=$(curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value')
|
||||
fi
|
||||
|
||||
for board in $(ls out/tests); do
|
||||
echo Running tests for board: $board
|
||||
for elf in $(ls out/tests/$board); do
|
||||
run_elf $board out/tests/$board/$elf
|
||||
done
|
||||
done
|
||||
teleprobe client run -r out/tests
|
||||
|
10
ci_stable.sh
10
ci_stable.sh
@ -2,19 +2,23 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export CARGO_TARGET_DIR=$PWD/target_ci_stable
|
||||
export RUSTFLAGS=-Dwarnings
|
||||
export DEFMT_LOG=trace
|
||||
|
||||
sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml
|
||||
|
||||
cargo batch \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
|
||||
--- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
|
||||
--- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \
|
||||
--- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \
|
||||
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
|
||||
--- build --release --manifest-path embassy-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 nrf52810,gpiote,time-driver-rtc1 \
|
||||
--- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \
|
||||
|
BIN
cyw43-firmware/43439A0.bin
Executable file
BIN
cyw43-firmware/43439A0.bin
Executable file
Binary file not shown.
BIN
cyw43-firmware/43439A0_clm.bin
Executable file
BIN
cyw43-firmware/43439A0_clm.bin
Executable file
Binary file not shown.
49
cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt
Normal file
49
cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt
Normal file
@ -0,0 +1,49 @@
|
||||
Permissive Binary License
|
||||
|
||||
Version 1.0, July 2019
|
||||
|
||||
Redistribution. Redistribution and use in binary form, without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1) Redistributions must reproduce the above copyright notice and the
|
||||
following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
2) Unless to the extent explicitly permitted by law, no reverse
|
||||
engineering, decompilation, or disassembly of this software is
|
||||
permitted.
|
||||
|
||||
3) Redistribution as part of a software development kit must include the
|
||||
accompanying file named <20>DEPENDENCIES<45> and any dependencies listed in
|
||||
that file.
|
||||
|
||||
4) Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
Limited patent license. The copyright holders (and contributors) grant a
|
||||
worldwide, non-exclusive, no-charge, royalty-free patent license to
|
||||
make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer this software, where such license applies only to those patent
|
||||
claims licensable by the copyright holders (and contributors) that are
|
||||
necessarily infringed by this software. This patent license shall not
|
||||
apply to any combinations that include this software. No hardware is
|
||||
licensed hereunder.
|
||||
|
||||
If you institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the software
|
||||
itself infringes your patent(s), then your rights granted under this
|
||||
license shall terminate as of the date such litigation is filed.
|
||||
|
||||
DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
cyw43-firmware/README.md
Normal file
5
cyw43-firmware/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# WiFi firmware
|
||||
|
||||
Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439
|
||||
|
||||
Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
|
22
cyw43-pio/Cargo.toml
Normal file
22
cyw43-pio/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "cyw43-pio"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
# If disabled, SPI runs at 31.25MHz
|
||||
# If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet.
|
||||
overclock = []
|
||||
|
||||
[dependencies]
|
||||
cyw43 = { version = "0.1.0", path = "../cyw43" }
|
||||
embassy-rp = { version = "0.1.0", path = "../embassy-rp" }
|
||||
pio-proc = "0.2"
|
||||
pio = "0.2.1"
|
||||
fixed = "1.23.1"
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/"
|
||||
target = "thumbv6m-none-eabi"
|
229
cyw43-pio/src/lib.rs
Normal file
229
cyw43-pio/src/lib.rs
Normal file
@ -0,0 +1,229 @@
|
||||
#![no_std]
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(async_fn_in_trait)]
|
||||
|
||||
use core::slice;
|
||||
|
||||
use cyw43::SpiBusCyw43;
|
||||
use embassy_rp::dma::Channel;
|
||||
use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate};
|
||||
use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
|
||||
use embassy_rp::relocate::RelocatedProgram;
|
||||
use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef};
|
||||
use fixed::FixedU32;
|
||||
use pio_proc::pio_asm;
|
||||
|
||||
pub struct PioSpi<'d, CS: Pin, PIO: Instance, const SM: usize, DMA> {
|
||||
cs: Output<'d, CS>,
|
||||
sm: StateMachine<'d, PIO, SM>,
|
||||
irq: Irq<'d, PIO, 0>,
|
||||
dma: PeripheralRef<'d, DMA>,
|
||||
wrap_target: u8,
|
||||
}
|
||||
|
||||
impl<'d, CS, PIO, const SM: usize, DMA> PioSpi<'d, CS, PIO, SM, DMA>
|
||||
where
|
||||
DMA: Channel,
|
||||
CS: Pin,
|
||||
PIO: Instance,
|
||||
{
|
||||
pub fn new<DIO, CLK>(
|
||||
common: &mut Common<'d, PIO>,
|
||||
mut sm: StateMachine<'d, PIO, SM>,
|
||||
irq: Irq<'d, PIO, 0>,
|
||||
cs: Output<'d, CS>,
|
||||
dio: DIO,
|
||||
clk: CLK,
|
||||
dma: impl Peripheral<P = DMA> + 'd,
|
||||
) -> Self
|
||||
where
|
||||
DIO: PioPin,
|
||||
CLK: PioPin,
|
||||
{
|
||||
#[cfg(feature = "overclock")]
|
||||
let program = pio_asm!(
|
||||
".side_set 1"
|
||||
|
||||
".wrap_target"
|
||||
// write out x-1 bits
|
||||
"lp:"
|
||||
"out pins, 1 side 0"
|
||||
"jmp x-- lp side 1"
|
||||
// switch directions
|
||||
"set pindirs, 0 side 0"
|
||||
"nop side 1" // necessary for clkdiv=1.
|
||||
"nop side 0"
|
||||
// read in y-1 bits
|
||||
"lp2:"
|
||||
"in pins, 1 side 1"
|
||||
"jmp y-- lp2 side 0"
|
||||
|
||||
// wait for event and irq host
|
||||
"wait 1 pin 0 side 0"
|
||||
"irq 0 side 0"
|
||||
|
||||
".wrap"
|
||||
);
|
||||
#[cfg(not(feature = "overclock"))]
|
||||
let program = pio_asm!(
|
||||
".side_set 1"
|
||||
|
||||
".wrap_target"
|
||||
// write out x-1 bits
|
||||
"lp:"
|
||||
"out pins, 1 side 0"
|
||||
"jmp x-- lp side 1"
|
||||
// switch directions
|
||||
"set pindirs, 0 side 0"
|
||||
"nop side 0"
|
||||
// read in y-1 bits
|
||||
"lp2:"
|
||||
"in pins, 1 side 1"
|
||||
"jmp y-- lp2 side 0"
|
||||
|
||||
// wait for event and irq host
|
||||
"wait 1 pin 0 side 0"
|
||||
"irq 0 side 0"
|
||||
|
||||
".wrap"
|
||||
);
|
||||
|
||||
let relocated = RelocatedProgram::new(&program.program);
|
||||
|
||||
let mut pin_io: embassy_rp::pio::Pin<PIO> = common.make_pio_pin(dio);
|
||||
pin_io.set_pull(Pull::None);
|
||||
pin_io.set_schmitt(true);
|
||||
pin_io.set_input_sync_bypass(true);
|
||||
pin_io.set_drive_strength(Drive::_12mA);
|
||||
pin_io.set_slew_rate(SlewRate::Fast);
|
||||
|
||||
let mut pin_clk = common.make_pio_pin(clk);
|
||||
pin_clk.set_drive_strength(Drive::_12mA);
|
||||
pin_clk.set_slew_rate(SlewRate::Fast);
|
||||
|
||||
let mut cfg = Config::default();
|
||||
cfg.use_program(&common.load_program(&relocated), &[&pin_clk]);
|
||||
cfg.set_out_pins(&[&pin_io]);
|
||||
cfg.set_in_pins(&[&pin_io]);
|
||||
cfg.set_set_pins(&[&pin_io]);
|
||||
cfg.shift_out.direction = ShiftDirection::Left;
|
||||
cfg.shift_out.auto_fill = true;
|
||||
//cfg.shift_out.threshold = 32;
|
||||
cfg.shift_in.direction = ShiftDirection::Left;
|
||||
cfg.shift_in.auto_fill = true;
|
||||
//cfg.shift_in.threshold = 32;
|
||||
|
||||
#[cfg(feature = "overclock")]
|
||||
{
|
||||
// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to
|
||||
// data sheet, but seems to work fine.
|
||||
cfg.clock_divider = FixedU32::from_bits(0x0100);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "overclock"))]
|
||||
{
|
||||
// same speed as pico-sdk, 62.5Mhz
|
||||
// This is actually the fastest we can go without overclocking.
|
||||
// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq.
|
||||
// However, the PIO uses a fractional divider, which works by introducing jitter when
|
||||
// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz
|
||||
// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles
|
||||
// violate the maximum from the data sheet.
|
||||
cfg.clock_divider = FixedU32::from_bits(0x0200);
|
||||
}
|
||||
|
||||
sm.set_config(&cfg);
|
||||
|
||||
sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]);
|
||||
sm.set_pins(Level::Low, &[&pin_clk, &pin_io]);
|
||||
|
||||
Self {
|
||||
cs,
|
||||
sm,
|
||||
irq,
|
||||
dma: dma.into_ref(),
|
||||
wrap_target: relocated.wrap().target,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn write(&mut self, write: &[u32]) -> u32 {
|
||||
self.sm.set_enable(false);
|
||||
let write_bits = write.len() * 32 - 1;
|
||||
let read_bits = 31;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::trace!("write={} read={}", write_bits, read_bits);
|
||||
|
||||
unsafe {
|
||||
pio_instr_util::set_x(&mut self.sm, write_bits as u32);
|
||||
pio_instr_util::set_y(&mut self.sm, read_bits as u32);
|
||||
pio_instr_util::set_pindir(&mut self.sm, 0b1);
|
||||
pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target);
|
||||
}
|
||||
|
||||
self.sm.set_enable(true);
|
||||
|
||||
self.sm.tx().dma_push(self.dma.reborrow(), write).await;
|
||||
|
||||
let mut status = 0;
|
||||
self.sm
|
||||
.rx()
|
||||
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
|
||||
.await;
|
||||
status
|
||||
}
|
||||
|
||||
pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) -> u32 {
|
||||
self.sm.set_enable(false);
|
||||
let write_bits = 31;
|
||||
let read_bits = read.len() * 32 + 32 - 1;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::trace!("write={} read={}", write_bits, read_bits);
|
||||
|
||||
unsafe {
|
||||
pio_instr_util::set_y(&mut self.sm, read_bits as u32);
|
||||
pio_instr_util::set_x(&mut self.sm, write_bits as u32);
|
||||
pio_instr_util::set_pindir(&mut self.sm, 0b1);
|
||||
pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target);
|
||||
}
|
||||
|
||||
// self.cs.set_low();
|
||||
self.sm.set_enable(true);
|
||||
|
||||
self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await;
|
||||
self.sm.rx().dma_pull(self.dma.reborrow(), read).await;
|
||||
|
||||
let mut status = 0;
|
||||
self.sm
|
||||
.rx()
|
||||
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
|
||||
.await;
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, CS, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, CS, PIO, SM, DMA>
|
||||
where
|
||||
CS: Pin,
|
||||
PIO: Instance,
|
||||
DMA: Channel,
|
||||
{
|
||||
async fn cmd_write(&mut self, write: &[u32]) -> u32 {
|
||||
self.cs.set_low();
|
||||
let status = self.write(write).await;
|
||||
self.cs.set_high();
|
||||
status
|
||||
}
|
||||
|
||||
async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 {
|
||||
self.cs.set_low();
|
||||
let status = self.cmd_read(write, read).await;
|
||||
self.cs.set_high();
|
||||
status
|
||||
}
|
||||
|
||||
async fn wait_for_event(&mut self) {
|
||||
self.irq.wait().await;
|
||||
}
|
||||
}
|
34
cyw43/Cargo.toml
Normal file
34
cyw43/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "cyw43"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
defmt = ["dep:defmt"]
|
||||
log = ["dep:log"]
|
||||
|
||||
# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`.
|
||||
firmware-logs = []
|
||||
|
||||
[dependencies]
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time"}
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
|
||||
embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
|
||||
atomic-polyfill = "0.1.5"
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.17", optional = true }
|
||||
|
||||
cortex-m = "0.7.6"
|
||||
cortex-m-rt = "0.7.0"
|
||||
futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] }
|
||||
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" }
|
||||
num_enum = { version = "0.5.7", default-features = false }
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/"
|
||||
target = "thumbv6m-none-eabi"
|
||||
features = ["defmt", "firmware-logs"]
|
57
cyw43/README.md
Normal file
57
cyw43/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# cyw43
|
||||
|
||||
Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
|
||||
|
||||
## Current status
|
||||
|
||||
Working:
|
||||
|
||||
- Station mode (joining an AP).
|
||||
- AP mode (creating an AP)
|
||||
- Scanning
|
||||
- Sending and receiving Ethernet frames.
|
||||
- Using the default MAC address.
|
||||
- [`embassy-net`](https://embassy.dev) integration.
|
||||
- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W.
|
||||
- Using IRQ for device events
|
||||
- GPIO support (for LED on the Pico W)
|
||||
|
||||
TODO:
|
||||
|
||||
- Setting a custom MAC address.
|
||||
- Bus sleep (for power consumption optimization)
|
||||
|
||||
## Running the examples
|
||||
|
||||
- `cargo install probe-rs --features cli`
|
||||
- `cd examples/rp`
|
||||
### Example 1: Scan the wifi stations
|
||||
- `cargo run --release --bin wifi_scan`
|
||||
### Example 2: Create an access point (IP and credentials in the code)
|
||||
- `cargo run --release --bin wifi_ap_tcp_server`
|
||||
### Example 3: Connect to an existing network and create a server
|
||||
- `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release --bin wifi_tcp_server`
|
||||
|
||||
After a few seconds, you should see that DHCP picks up an IP address like this
|
||||
```
|
||||
11.944489 DEBUG Acquired IP configuration:
|
||||
11.944517 DEBUG IP address: 192.168.0.250/24
|
||||
11.944620 DEBUG Default gateway: 192.168.0.33
|
||||
11.944722 DEBUG DNS server 0: 192.168.0.33
|
||||
```
|
||||
This example implements a TCP echo server on port 1234. You can try connecting to it with:
|
||||
```
|
||||
nc 192.168.0.250 1234
|
||||
```
|
||||
Send it some data, you should see it echoed back and printed in the firmware's logs.
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||
|
||||
at your option.
|
||||
|
328
cyw43/src/bus.rs
Normal file
328
cyw43/src/bus.rs
Normal file
@ -0,0 +1,328 @@
|
||||
use embassy_futures::yield_now;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
use futures::FutureExt;
|
||||
|
||||
use crate::consts::*;
|
||||
use crate::slice8_mut;
|
||||
|
||||
/// Custom Spi Trait that _only_ supports the bus operation of the cyw43
|
||||
/// Implementors are expected to hold the CS pin low during an operation.
|
||||
pub trait SpiBusCyw43 {
|
||||
/// Issues a write command on the bus
|
||||
/// First 32 bits of `word` are expected to be a cmd word
|
||||
async fn cmd_write(&mut self, write: &[u32]) -> u32;
|
||||
|
||||
/// Issues a read command on the bus
|
||||
/// `write` is expected to be a 32 bit cmd word
|
||||
/// `read` will contain the response of the device
|
||||
/// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`.
|
||||
/// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long.
|
||||
async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32;
|
||||
|
||||
/// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high.
|
||||
/// The default implementation always reports ready, resulting in active polling of the device.
|
||||
async fn wait_for_event(&mut self) {
|
||||
yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Bus<PWR, SPI> {
|
||||
backplane_window: u32,
|
||||
pwr: PWR,
|
||||
spi: SPI,
|
||||
status: u32,
|
||||
}
|
||||
|
||||
impl<PWR, SPI> Bus<PWR, SPI>
|
||||
where
|
||||
PWR: OutputPin,
|
||||
SPI: SpiBusCyw43,
|
||||
{
|
||||
pub(crate) fn new(pwr: PWR, spi: SPI) -> Self {
|
||||
Self {
|
||||
backplane_window: 0xAAAA_AAAA,
|
||||
pwr,
|
||||
spi,
|
||||
status: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init(&mut self) {
|
||||
// Reset
|
||||
self.pwr.set_low().unwrap();
|
||||
Timer::after(Duration::from_millis(20)).await;
|
||||
self.pwr.set_high().unwrap();
|
||||
Timer::after(Duration::from_millis(250)).await;
|
||||
|
||||
while self
|
||||
.read32_swapped(REG_BUS_TEST_RO)
|
||||
.inspect(|v| trace!("{:#x}", v))
|
||||
.await
|
||||
!= FEEDBEAD
|
||||
{}
|
||||
|
||||
self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await;
|
||||
let val = self.read32_swapped(REG_BUS_TEST_RW).await;
|
||||
trace!("{:#x}", val);
|
||||
assert_eq!(val, TEST_PATTERN);
|
||||
|
||||
let val = self.read32_swapped(REG_BUS_CTRL).await;
|
||||
trace!("{:#010b}", (val & 0xff));
|
||||
|
||||
// 32-bit word length, little endian (which is the default endianess).
|
||||
self.write32_swapped(
|
||||
REG_BUS_CTRL,
|
||||
WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE | INTERRUPT_WITH_STATUS,
|
||||
)
|
||||
.await;
|
||||
|
||||
let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await;
|
||||
trace!("{:#b}", val);
|
||||
|
||||
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await;
|
||||
trace!("{:#x}", val);
|
||||
assert_eq!(val, FEEDBEAD);
|
||||
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await;
|
||||
trace!("{:#x}", val);
|
||||
assert_eq!(val, TEST_PATTERN);
|
||||
}
|
||||
|
||||
pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) {
|
||||
let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8);
|
||||
let len_in_u32 = (len_in_u8 as usize + 3) / 4;
|
||||
|
||||
self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await;
|
||||
}
|
||||
|
||||
pub async fn wlan_write(&mut self, buf: &[u32]) {
|
||||
let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4);
|
||||
//TODO try to remove copy?
|
||||
let mut cmd_buf = [0_u32; 513];
|
||||
cmd_buf[0] = cmd;
|
||||
cmd_buf[1..][..buf.len()].copy_from_slice(buf);
|
||||
|
||||
self.status = self.spi.cmd_write(&cmd_buf).await;
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) {
|
||||
// It seems the HW force-aligns the addr
|
||||
// to 2 if data.len() >= 2
|
||||
// to 4 if data.len() >= 4
|
||||
// To simplify, enforce 4-align for now.
|
||||
assert!(addr % 4 == 0);
|
||||
|
||||
// Backplane read buffer has one extra word for the response delay.
|
||||
let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1];
|
||||
|
||||
while !data.is_empty() {
|
||||
// Ensure transfer doesn't cross a window boundary.
|
||||
let window_offs = addr & BACKPLANE_ADDRESS_MASK;
|
||||
let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize;
|
||||
|
||||
let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining);
|
||||
|
||||
self.backplane_set_window(addr).await;
|
||||
|
||||
let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32);
|
||||
|
||||
// round `buf` to word boundary, add one extra word for the response delay
|
||||
self.status = self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await;
|
||||
|
||||
// when writing out the data, we skip the response-delay byte
|
||||
data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]);
|
||||
|
||||
// Advance ptr.
|
||||
addr += len as u32;
|
||||
data = &mut data[len..];
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) {
|
||||
// It seems the HW force-aligns the addr
|
||||
// to 2 if data.len() >= 2
|
||||
// to 4 if data.len() >= 4
|
||||
// To simplify, enforce 4-align for now.
|
||||
assert!(addr % 4 == 0);
|
||||
|
||||
let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1];
|
||||
|
||||
while !data.is_empty() {
|
||||
// Ensure transfer doesn't cross a window boundary.
|
||||
let window_offs = addr & BACKPLANE_ADDRESS_MASK;
|
||||
let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize;
|
||||
|
||||
let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining);
|
||||
slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]);
|
||||
|
||||
self.backplane_set_window(addr).await;
|
||||
|
||||
let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32);
|
||||
buf[0] = cmd;
|
||||
|
||||
self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await;
|
||||
|
||||
// Advance ptr.
|
||||
addr += len as u32;
|
||||
data = &data[len..];
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bp_read8(&mut self, addr: u32) -> u8 {
|
||||
self.backplane_readn(addr, 1).await as u8
|
||||
}
|
||||
|
||||
pub async fn bp_write8(&mut self, addr: u32, val: u8) {
|
||||
self.backplane_writen(addr, val as u32, 1).await
|
||||
}
|
||||
|
||||
pub async fn bp_read16(&mut self, addr: u32) -> u16 {
|
||||
self.backplane_readn(addr, 2).await as u16
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn bp_write16(&mut self, addr: u32, val: u16) {
|
||||
self.backplane_writen(addr, val as u32, 2).await
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn bp_read32(&mut self, addr: u32) -> u32 {
|
||||
self.backplane_readn(addr, 4).await
|
||||
}
|
||||
|
||||
pub async fn bp_write32(&mut self, addr: u32, val: u32) {
|
||||
self.backplane_writen(addr, val, 4).await
|
||||
}
|
||||
|
||||
async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 {
|
||||
self.backplane_set_window(addr).await;
|
||||
|
||||
let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK;
|
||||
if len == 4 {
|
||||
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG
|
||||
}
|
||||
self.readn(FUNC_BACKPLANE, bus_addr, len).await
|
||||
}
|
||||
|
||||
async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) {
|
||||
self.backplane_set_window(addr).await;
|
||||
|
||||
let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK;
|
||||
if len == 4 {
|
||||
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG
|
||||
}
|
||||
self.writen(FUNC_BACKPLANE, bus_addr, val, len).await
|
||||
}
|
||||
|
||||
async fn backplane_set_window(&mut self, addr: u32) {
|
||||
let new_window = addr & !BACKPLANE_ADDRESS_MASK;
|
||||
|
||||
if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 {
|
||||
self.write8(
|
||||
FUNC_BACKPLANE,
|
||||
REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH,
|
||||
(new_window >> 24) as u8,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 {
|
||||
self.write8(
|
||||
FUNC_BACKPLANE,
|
||||
REG_BACKPLANE_BACKPLANE_ADDRESS_MID,
|
||||
(new_window >> 16) as u8,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 {
|
||||
self.write8(
|
||||
FUNC_BACKPLANE,
|
||||
REG_BACKPLANE_BACKPLANE_ADDRESS_LOW,
|
||||
(new_window >> 8) as u8,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
self.backplane_window = new_window;
|
||||
}
|
||||
|
||||
pub async fn read8(&mut self, func: u32, addr: u32) -> u8 {
|
||||
self.readn(func, addr, 1).await as u8
|
||||
}
|
||||
|
||||
pub async fn write8(&mut self, func: u32, addr: u32, val: u8) {
|
||||
self.writen(func, addr, val as u32, 1).await
|
||||
}
|
||||
|
||||
pub async fn read16(&mut self, func: u32, addr: u32) -> u16 {
|
||||
self.readn(func, addr, 2).await as u16
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn write16(&mut self, func: u32, addr: u32, val: u16) {
|
||||
self.writen(func, addr, val as u32, 2).await
|
||||
}
|
||||
|
||||
pub async fn read32(&mut self, func: u32, addr: u32) -> u32 {
|
||||
self.readn(func, addr, 4).await
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn write32(&mut self, func: u32, addr: u32, val: u32) {
|
||||
self.writen(func, addr, val, 4).await
|
||||
}
|
||||
|
||||
async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 {
|
||||
let cmd = cmd_word(READ, INC_ADDR, func, addr, len);
|
||||
let mut buf = [0; 2];
|
||||
// if we are reading from the backplane, we need an extra word for the response delay
|
||||
let len = if func == FUNC_BACKPLANE { 2 } else { 1 };
|
||||
|
||||
self.status = self.spi.cmd_read(cmd, &mut buf[..len]).await;
|
||||
|
||||
// if we read from the backplane, the result is in the second word, after the response delay
|
||||
if func == FUNC_BACKPLANE {
|
||||
buf[1]
|
||||
} else {
|
||||
buf[0]
|
||||
}
|
||||
}
|
||||
|
||||
async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) {
|
||||
let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len);
|
||||
|
||||
self.status = self.spi.cmd_write(&[cmd, val]).await;
|
||||
}
|
||||
|
||||
async fn read32_swapped(&mut self, addr: u32) -> u32 {
|
||||
let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4);
|
||||
let cmd = swap16(cmd);
|
||||
let mut buf = [0; 1];
|
||||
|
||||
self.status = self.spi.cmd_read(cmd, &mut buf).await;
|
||||
|
||||
swap16(buf[0])
|
||||
}
|
||||
|
||||
async fn write32_swapped(&mut self, addr: u32, val: u32) {
|
||||
let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4);
|
||||
let buf = [swap16(cmd), swap16(val)];
|
||||
|
||||
self.status = self.spi.cmd_write(&buf).await;
|
||||
}
|
||||
|
||||
pub async fn wait_for_event(&mut self) {
|
||||
self.spi.wait_for_event().await;
|
||||
}
|
||||
|
||||
pub fn status(&self) -> u32 {
|
||||
self.status
|
||||
}
|
||||
}
|
||||
|
||||
fn swap16(x: u32) -> u32 {
|
||||
x.rotate_left(16)
|
||||
}
|
||||
|
||||
fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 {
|
||||
(write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF)
|
||||
}
|
318
cyw43/src/consts.rs
Normal file
318
cyw43/src/consts.rs
Normal file
@ -0,0 +1,318 @@
|
||||
#![allow(unused)]
|
||||
|
||||
pub(crate) const FUNC_BUS: u32 = 0;
|
||||
pub(crate) const FUNC_BACKPLANE: u32 = 1;
|
||||
pub(crate) const FUNC_WLAN: u32 = 2;
|
||||
pub(crate) const FUNC_BT: u32 = 3;
|
||||
|
||||
pub(crate) const REG_BUS_CTRL: u32 = 0x0;
|
||||
pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status
|
||||
pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask
|
||||
pub(crate) const REG_BUS_STATUS: u32 = 0x8;
|
||||
pub(crate) const REG_BUS_TEST_RO: u32 = 0x14;
|
||||
pub(crate) const REG_BUS_TEST_RW: u32 = 0x18;
|
||||
pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c;
|
||||
pub(crate) const WORD_LENGTH_32: u32 = 0x1;
|
||||
pub(crate) const HIGH_SPEED: u32 = 0x10;
|
||||
pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5;
|
||||
pub(crate) const WAKE_UP: u32 = 1 << 7;
|
||||
pub(crate) const STATUS_ENABLE: u32 = 1 << 16;
|
||||
pub(crate) const INTERRUPT_WITH_STATUS: u32 = 1 << 17;
|
||||
|
||||
// SPI_STATUS_REGISTER bits
|
||||
pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001;
|
||||
pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002;
|
||||
pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004;
|
||||
pub(crate) const STATUS_F2_INTR: u32 = 0x00000008;
|
||||
pub(crate) const STATUS_F3_INTR: u32 = 0x00000010;
|
||||
pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020;
|
||||
pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040;
|
||||
pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080;
|
||||
pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100;
|
||||
pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00;
|
||||
pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9;
|
||||
pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000;
|
||||
pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000;
|
||||
pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21;
|
||||
|
||||
pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005;
|
||||
pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006;
|
||||
pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007;
|
||||
pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008;
|
||||
pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009;
|
||||
pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A;
|
||||
pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B;
|
||||
pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C;
|
||||
pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D;
|
||||
pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E;
|
||||
pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F;
|
||||
pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B;
|
||||
pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C;
|
||||
pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E;
|
||||
pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F;
|
||||
|
||||
pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000;
|
||||
pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF;
|
||||
pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000;
|
||||
pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64;
|
||||
// Active Low Power (ALP) clock constants
|
||||
pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08;
|
||||
pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40;
|
||||
|
||||
// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect
|
||||
// (AI) pub (crate) constants
|
||||
pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408;
|
||||
pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002;
|
||||
pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001;
|
||||
pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020;
|
||||
|
||||
pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800;
|
||||
pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1;
|
||||
|
||||
pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804;
|
||||
|
||||
pub(crate) const TEST_PATTERN: u32 = 0x12345678;
|
||||
pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD;
|
||||
|
||||
// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits
|
||||
pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1"
|
||||
pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002;
|
||||
pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004;
|
||||
pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1
|
||||
pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1
|
||||
pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020;
|
||||
pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040;
|
||||
pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests
|
||||
pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100;
|
||||
pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200;
|
||||
pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400;
|
||||
pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800;
|
||||
pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000;
|
||||
pub(crate) const IRQ_F1_INTR: u16 = 0x2000;
|
||||
pub(crate) const IRQ_F2_INTR: u16 = 0x4000;
|
||||
pub(crate) const IRQ_F3_INTR: u16 = 0x8000;
|
||||
|
||||
pub(crate) const IOCTL_CMD_UP: u32 = 2;
|
||||
pub(crate) const IOCTL_CMD_DOWN: u32 = 3;
|
||||
pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26;
|
||||
pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30;
|
||||
pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64;
|
||||
pub(crate) const IOCTL_CMD_SET_AP: u32 = 118;
|
||||
pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263;
|
||||
pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262;
|
||||
pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268;
|
||||
|
||||
pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0;
|
||||
pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1;
|
||||
pub(crate) const CHANNEL_TYPE_DATA: u8 = 2;
|
||||
|
||||
// CYW_SPID command structure constants.
|
||||
pub(crate) const WRITE: bool = true;
|
||||
pub(crate) const READ: bool = false;
|
||||
pub(crate) const INC_ADDR: bool = true;
|
||||
pub(crate) const FIXED_ADDR: bool = false;
|
||||
|
||||
pub(crate) const AES_ENABLED: u32 = 0x0004;
|
||||
pub(crate) const WPA2_SECURITY: u32 = 0x00400000;
|
||||
|
||||
pub(crate) const MIN_PSK_LEN: usize = 8;
|
||||
pub(crate) const MAX_PSK_LEN: usize = 64;
|
||||
|
||||
// Security type (authentication and encryption types are combined using bit mask)
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[repr(u32)]
|
||||
pub(crate) enum Security {
|
||||
OPEN = 0,
|
||||
WPA2_AES_PSK = WPA2_SECURITY | AES_ENABLED,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum EStatus {
|
||||
/// operation was successful
|
||||
SUCCESS = 0,
|
||||
/// operation failed
|
||||
FAIL = 1,
|
||||
/// operation timed out
|
||||
TIMEOUT = 2,
|
||||
/// failed due to no matching network found
|
||||
NO_NETWORKS = 3,
|
||||
/// operation was aborted
|
||||
ABORT = 4,
|
||||
/// protocol failure: packet not ack'd
|
||||
NO_ACK = 5,
|
||||
/// AUTH or ASSOC packet was unsolicited
|
||||
UNSOLICITED = 6,
|
||||
/// attempt to assoc to an auto auth configuration
|
||||
ATTEMPT = 7,
|
||||
/// scan results are incomplete
|
||||
PARTIAL = 8,
|
||||
/// scan aborted by another scan
|
||||
NEWSCAN = 9,
|
||||
/// scan aborted due to assoc in progress
|
||||
NEWASSOC = 10,
|
||||
/// 802.11h quiet period started
|
||||
_11HQUIET = 11,
|
||||
/// user disabled scanning (WLC_SET_SCANSUPPRESS)
|
||||
SUPPRESS = 12,
|
||||
/// no allowable channels to scan
|
||||
NOCHANS = 13,
|
||||
/// scan aborted due to CCX fast roam
|
||||
CCXFASTRM = 14,
|
||||
/// abort channel select
|
||||
CS_ABORT = 15,
|
||||
}
|
||||
|
||||
impl PartialEq<EStatus> for u32 {
|
||||
fn eq(&self, other: &EStatus) -> bool {
|
||||
*self == *other as Self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct FormatStatus(pub u32);
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for FormatStatus {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
macro_rules! implm {
|
||||
($($name:ident),*) => {
|
||||
$(
|
||||
if self.0 & $name > 0 {
|
||||
defmt::write!(fmt, " | {}", &stringify!($name)[7..]);
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
implm!(
|
||||
STATUS_DATA_NOT_AVAILABLE,
|
||||
STATUS_UNDERFLOW,
|
||||
STATUS_OVERFLOW,
|
||||
STATUS_F2_INTR,
|
||||
STATUS_F3_INTR,
|
||||
STATUS_F2_RX_READY,
|
||||
STATUS_F3_RX_READY,
|
||||
STATUS_HOST_CMD_DATA_ERR,
|
||||
STATUS_F2_PKT_AVAILABLE,
|
||||
STATUS_F3_PKT_AVAILABLE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
impl core::fmt::Debug for FormatStatus {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
macro_rules! implm {
|
||||
($($name:ident),*) => {
|
||||
$(
|
||||
if self.0 & $name > 0 {
|
||||
core::write!(fmt, " | {}", &stringify!($name)[7..])?;
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
implm!(
|
||||
STATUS_DATA_NOT_AVAILABLE,
|
||||
STATUS_UNDERFLOW,
|
||||
STATUS_OVERFLOW,
|
||||
STATUS_F2_INTR,
|
||||
STATUS_F3_INTR,
|
||||
STATUS_F2_RX_READY,
|
||||
STATUS_F3_RX_READY,
|
||||
STATUS_HOST_CMD_DATA_ERR,
|
||||
STATUS_F2_PKT_AVAILABLE,
|
||||
STATUS_F3_PKT_AVAILABLE
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
impl core::fmt::Display for FormatStatus {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct FormatInterrupt(pub u16);
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for FormatInterrupt {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
macro_rules! implm {
|
||||
($($name:ident),*) => {
|
||||
$(
|
||||
if self.0 & $name > 0 {
|
||||
defmt::write!(fmt, " | {}", &stringify!($name)[4..]);
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
implm!(
|
||||
IRQ_DATA_UNAVAILABLE,
|
||||
IRQ_F2_F3_FIFO_RD_UNDERFLOW,
|
||||
IRQ_F2_F3_FIFO_WR_OVERFLOW,
|
||||
IRQ_COMMAND_ERROR,
|
||||
IRQ_DATA_ERROR,
|
||||
IRQ_F2_PACKET_AVAILABLE,
|
||||
IRQ_F3_PACKET_AVAILABLE,
|
||||
IRQ_F1_OVERFLOW,
|
||||
IRQ_MISC_INTR0,
|
||||
IRQ_MISC_INTR1,
|
||||
IRQ_MISC_INTR2,
|
||||
IRQ_MISC_INTR3,
|
||||
IRQ_MISC_INTR4,
|
||||
IRQ_F1_INTR,
|
||||
IRQ_F2_INTR,
|
||||
IRQ_F3_INTR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
impl core::fmt::Debug for FormatInterrupt {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
macro_rules! implm {
|
||||
($($name:ident),*) => {
|
||||
$(
|
||||
if self.0 & $name > 0 {
|
||||
core::write!(fmt, " | {}", &stringify!($name)[7..])?;
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
implm!(
|
||||
IRQ_DATA_UNAVAILABLE,
|
||||
IRQ_F2_F3_FIFO_RD_UNDERFLOW,
|
||||
IRQ_F2_F3_FIFO_WR_OVERFLOW,
|
||||
IRQ_COMMAND_ERROR,
|
||||
IRQ_DATA_ERROR,
|
||||
IRQ_F2_PACKET_AVAILABLE,
|
||||
IRQ_F3_PACKET_AVAILABLE,
|
||||
IRQ_F1_OVERFLOW,
|
||||
IRQ_MISC_INTR0,
|
||||
IRQ_MISC_INTR1,
|
||||
IRQ_MISC_INTR2,
|
||||
IRQ_MISC_INTR3,
|
||||
IRQ_MISC_INTR4,
|
||||
IRQ_F1_INTR,
|
||||
IRQ_F2_INTR,
|
||||
IRQ_F3_INTR
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
impl core::fmt::Display for FormatInterrupt {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
454
cyw43/src/control.rs
Normal file
454
cyw43/src/control.rs
Normal file
@ -0,0 +1,454 @@
|
||||
use core::cmp::{max, min};
|
||||
|
||||
use ch::driver::LinkState;
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_time::{Duration, Timer};
|
||||
|
||||
pub use crate::bus::SpiBusCyw43;
|
||||
use crate::consts::*;
|
||||
use crate::events::{Event, EventSubscriber, Events};
|
||||
use crate::fmt::Bytes;
|
||||
use crate::ioctl::{IoctlState, IoctlType};
|
||||
use crate::structs::*;
|
||||
use crate::{countries, events, PowerManagementMode};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub status: u32,
|
||||
}
|
||||
|
||||
pub struct Control<'a> {
|
||||
state_ch: ch::StateRunner<'a>,
|
||||
events: &'a Events,
|
||||
ioctl_state: &'a IoctlState,
|
||||
}
|
||||
|
||||
impl<'a> Control<'a> {
|
||||
pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self {
|
||||
Self {
|
||||
state_ch,
|
||||
events: event_sub,
|
||||
ioctl_state,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init(&mut self, clm: &[u8]) {
|
||||
const CHUNK_SIZE: usize = 1024;
|
||||
|
||||
debug!("Downloading CLM...");
|
||||
|
||||
let mut offs = 0;
|
||||
for chunk in clm.chunks(CHUNK_SIZE) {
|
||||
let mut flag = DOWNLOAD_FLAG_HANDLER_VER;
|
||||
if offs == 0 {
|
||||
flag |= DOWNLOAD_FLAG_BEGIN;
|
||||
}
|
||||
offs += chunk.len();
|
||||
if offs == clm.len() {
|
||||
flag |= DOWNLOAD_FLAG_END;
|
||||
}
|
||||
|
||||
let header = DownloadHeader {
|
||||
flag,
|
||||
dload_type: DOWNLOAD_TYPE_CLM,
|
||||
len: chunk.len() as _,
|
||||
crc: 0,
|
||||
};
|
||||
let mut buf = [0; 8 + 12 + CHUNK_SIZE];
|
||||
buf[0..8].copy_from_slice(b"clmload\x00");
|
||||
buf[8..20].copy_from_slice(&header.to_bytes());
|
||||
buf[20..][..chunk.len()].copy_from_slice(&chunk);
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()])
|
||||
.await;
|
||||
}
|
||||
|
||||
// check clmload ok
|
||||
assert_eq!(self.get_iovar_u32("clmload_status").await, 0);
|
||||
|
||||
debug!("Configuring misc stuff...");
|
||||
|
||||
// Disable tx gloming which transfers multiple packets in one request.
|
||||
// 'glom' is short for "conglomerate" which means "gather together into
|
||||
// a compact mass".
|
||||
self.set_iovar_u32("bus:txglom", 0).await;
|
||||
self.set_iovar_u32("apsta", 1).await;
|
||||
|
||||
// read MAC addr.
|
||||
let mut mac_addr = [0; 6];
|
||||
assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6);
|
||||
debug!("mac addr: {:02x}", Bytes(&mac_addr));
|
||||
|
||||
let country = countries::WORLD_WIDE_XX;
|
||||
let country_info = CountryInfo {
|
||||
country_abbrev: [country.code[0], country.code[1], 0, 0],
|
||||
country_code: [country.code[0], country.code[1], 0, 0],
|
||||
rev: if country.rev == 0 { -1 } else { country.rev as _ },
|
||||
};
|
||||
self.set_iovar("country", &country_info.to_bytes()).await;
|
||||
|
||||
// set country takes some time, next ioctls fail if we don't wait.
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
// Set antenna to chip antenna
|
||||
self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await;
|
||||
|
||||
self.set_iovar_u32("bus:txglom", 0).await;
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
//self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...??
|
||||
//Timer::after(Duration::from_millis(100)).await;
|
||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
self.set_iovar_u32("ampdu_mpdu", 4).await;
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
//self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes
|
||||
|
||||
//Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
// evts
|
||||
let mut evts = EventMask {
|
||||
iface: 0,
|
||||
events: [0xFF; 24],
|
||||
};
|
||||
|
||||
// Disable spammy uninteresting events.
|
||||
evts.unset(Event::RADIO);
|
||||
evts.unset(Event::IF);
|
||||
evts.unset(Event::PROBREQ_MSG);
|
||||
evts.unset(Event::PROBREQ_MSG_RX);
|
||||
evts.unset(Event::PROBRESP_MSG);
|
||||
evts.unset(Event::PROBRESP_MSG);
|
||||
evts.unset(Event::ROAM);
|
||||
|
||||
self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await;
|
||||
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
// set wifi up
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
|
||||
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto
|
||||
self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any
|
||||
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
self.state_ch.set_ethernet_address(mac_addr);
|
||||
|
||||
debug!("INIT DONE");
|
||||
}
|
||||
|
||||
pub async fn set_power_management(&mut self, mode: PowerManagementMode) {
|
||||
// power save mode
|
||||
let mode_num = mode.mode();
|
||||
if mode_num == 2 {
|
||||
self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await;
|
||||
self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await;
|
||||
self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await;
|
||||
self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await;
|
||||
}
|
||||
self.ioctl_set_u32(86, 0, mode_num).await;
|
||||
}
|
||||
|
||||
pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> {
|
||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
||||
|
||||
self.ioctl_set_u32(134, 0, 0).await; // wsec = open
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
|
||||
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
|
||||
self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0)
|
||||
|
||||
let mut i = SsidInfo {
|
||||
len: ssid.len() as _,
|
||||
ssid: [0; 32],
|
||||
};
|
||||
i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes());
|
||||
|
||||
self.wait_for_join(i).await
|
||||
}
|
||||
|
||||
pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> {
|
||||
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
|
||||
|
||||
self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await;
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await;
|
||||
self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await;
|
||||
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
let mut pfi = PassphraseInfo {
|
||||
len: passphrase.len() as _,
|
||||
flags: 1,
|
||||
passphrase: [0; 64],
|
||||
};
|
||||
pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes());
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes())
|
||||
.await; // WLC_SET_WSEC_PMK
|
||||
|
||||
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
|
||||
self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open)
|
||||
self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth
|
||||
|
||||
let mut i = SsidInfo {
|
||||
len: ssid.len() as _,
|
||||
ssid: [0; 32],
|
||||
};
|
||||
i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes());
|
||||
|
||||
self.wait_for_join(i).await
|
||||
}
|
||||
|
||||
async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> {
|
||||
self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]);
|
||||
let mut subscriber = self.events.queue.subscriber().unwrap();
|
||||
// the actual join operation starts here
|
||||
// we make sure to enable events before so we don't miss any
|
||||
|
||||
// set_ssid
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes())
|
||||
.await;
|
||||
|
||||
// to complete the join, we wait for a SET_SSID event
|
||||
// we also save the AUTH status for the user, it may be interesting
|
||||
let mut auth_status = 0;
|
||||
let status = loop {
|
||||
let msg = subscriber.next_message_pure().await;
|
||||
if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS {
|
||||
auth_status = msg.header.status;
|
||||
} else if msg.header.event_type == Event::SET_SSID {
|
||||
// join operation ends with SET_SSID event
|
||||
break msg.header.status;
|
||||
}
|
||||
};
|
||||
|
||||
self.events.mask.disable_all();
|
||||
if status == EStatus::SUCCESS {
|
||||
// successful join
|
||||
self.state_ch.set_link_state(LinkState::Up);
|
||||
debug!("JOINED");
|
||||
Ok(())
|
||||
} else {
|
||||
warn!("JOIN failed with status={} auth={}", status, auth_status);
|
||||
Err(Error { status })
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) {
|
||||
assert!(gpio_n < 3);
|
||||
self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 })
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) {
|
||||
self.start_ap(ssid, "", Security::OPEN, channel).await;
|
||||
}
|
||||
|
||||
pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) {
|
||||
self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await;
|
||||
}
|
||||
|
||||
async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) {
|
||||
if security != Security::OPEN
|
||||
&& (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN)
|
||||
{
|
||||
panic!("Passphrase is too short or too long");
|
||||
}
|
||||
|
||||
// Temporarily set wifi down
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
|
||||
|
||||
// Turn off APSTA mode
|
||||
self.set_iovar_u32("apsta", 0).await;
|
||||
|
||||
// Set wifi up again
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
|
||||
|
||||
// Turn on AP mode
|
||||
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await;
|
||||
|
||||
// Set SSID
|
||||
let mut i = SsidInfoWithIndex {
|
||||
index: 0,
|
||||
ssid_info: SsidInfo {
|
||||
len: ssid.as_bytes().len() as _,
|
||||
ssid: [0; 32],
|
||||
},
|
||||
};
|
||||
i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes());
|
||||
self.set_iovar("bsscfg:ssid", &i.to_bytes()).await;
|
||||
|
||||
// Set channel number
|
||||
self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await;
|
||||
|
||||
// Set security
|
||||
self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await;
|
||||
|
||||
if security != Security::OPEN {
|
||||
self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK
|
||||
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
|
||||
// Set passphrase
|
||||
let mut pfi = PassphraseInfo {
|
||||
len: passphrase.as_bytes().len() as _,
|
||||
flags: 1, // WSEC_PASSPHRASE
|
||||
passphrase: [0; 64],
|
||||
};
|
||||
pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes());
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes())
|
||||
.await;
|
||||
}
|
||||
|
||||
// Change mutlicast rate from 1 Mbps to 11 Mbps
|
||||
self.set_iovar_u32("2g_mrate", 11000000 / 500000).await;
|
||||
|
||||
// Start AP
|
||||
self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP
|
||||
}
|
||||
|
||||
async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) {
|
||||
let mut buf = [0; 8];
|
||||
buf[0..4].copy_from_slice(&val1.to_le_bytes());
|
||||
buf[4..8].copy_from_slice(&val2.to_le_bytes());
|
||||
self.set_iovar(name, &buf).await
|
||||
}
|
||||
|
||||
async fn set_iovar_u32(&mut self, name: &str, val: u32) {
|
||||
self.set_iovar(name, &val.to_le_bytes()).await
|
||||
}
|
||||
|
||||
async fn get_iovar_u32(&mut self, name: &str) -> u32 {
|
||||
let mut buf = [0; 4];
|
||||
let len = self.get_iovar(name, &mut buf).await;
|
||||
assert_eq!(len, 4);
|
||||
u32::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
async fn set_iovar(&mut self, name: &str, val: &[u8]) {
|
||||
self.set_iovar_v::<64>(name, val).await
|
||||
}
|
||||
|
||||
async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) {
|
||||
debug!("set {} = {:02x}", name, Bytes(val));
|
||||
|
||||
let mut buf = [0; BUFSIZE];
|
||||
buf[..name.len()].copy_from_slice(name.as_bytes());
|
||||
buf[name.len()] = 0;
|
||||
buf[name.len() + 1..][..val.len()].copy_from_slice(val);
|
||||
|
||||
let total_len = name.len() + 1 + val.len();
|
||||
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len])
|
||||
.await;
|
||||
}
|
||||
|
||||
// TODO this is not really working, it always returns all zeros.
|
||||
async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize {
|
||||
debug!("get {}", name);
|
||||
|
||||
let mut buf = [0; 64];
|
||||
buf[..name.len()].copy_from_slice(name.as_bytes());
|
||||
buf[name.len()] = 0;
|
||||
|
||||
let total_len = max(name.len() + 1, res.len());
|
||||
let res_len = self
|
||||
.ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len])
|
||||
.await;
|
||||
|
||||
let out_len = min(res.len(), res_len);
|
||||
res[..out_len].copy_from_slice(&buf[..out_len]);
|
||||
out_len
|
||||
}
|
||||
|
||||
async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) {
|
||||
let mut buf = val.to_le_bytes();
|
||||
self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await;
|
||||
}
|
||||
|
||||
async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
|
||||
struct CancelOnDrop<'a>(&'a IoctlState);
|
||||
|
||||
impl CancelOnDrop<'_> {
|
||||
fn defuse(self) {
|
||||
core::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CancelOnDrop<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.cancel_ioctl();
|
||||
}
|
||||
}
|
||||
|
||||
let ioctl = CancelOnDrop(self.ioctl_state);
|
||||
let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).await;
|
||||
ioctl.defuse();
|
||||
|
||||
resp_len
|
||||
}
|
||||
|
||||
/// Start a wifi scan
|
||||
///
|
||||
/// Returns a `Stream` of networks found by the device
|
||||
///
|
||||
/// # Note
|
||||
/// Device events are currently implemented using a bounded queue.
|
||||
/// To not miss any events, you should make sure to always await the stream.
|
||||
pub async fn scan(&mut self) -> Scanner<'_> {
|
||||
const SCANTYPE_PASSIVE: u8 = 1;
|
||||
|
||||
let scan_params = ScanParams {
|
||||
version: 1,
|
||||
action: 1,
|
||||
sync_id: 1,
|
||||
ssid_len: 0,
|
||||
ssid: [0; 32],
|
||||
bssid: [0xff; 6],
|
||||
bss_type: 2,
|
||||
scan_type: SCANTYPE_PASSIVE,
|
||||
nprobes: !0,
|
||||
active_time: !0,
|
||||
passive_time: !0,
|
||||
home_time: !0,
|
||||
channel_num: 0,
|
||||
channel_list: [0; 1],
|
||||
};
|
||||
|
||||
self.events.mask.enable(&[Event::ESCAN_RESULT]);
|
||||
let subscriber = self.events.queue.subscriber().unwrap();
|
||||
self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await;
|
||||
|
||||
Scanner {
|
||||
subscriber,
|
||||
events: &self.events,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Scanner<'a> {
|
||||
subscriber: EventSubscriber<'a>,
|
||||
events: &'a Events,
|
||||
}
|
||||
|
||||
impl Scanner<'_> {
|
||||
/// wait for the next found network
|
||||
pub async fn next(&mut self) -> Option<BssInfo> {
|
||||
let event = self.subscriber.next_message_pure().await;
|
||||
if event.header.status != EStatus::PARTIAL {
|
||||
self.events.mask.disable_all();
|
||||
return None;
|
||||
}
|
||||
|
||||
if let events::Payload::BssInfo(bss) = event.payload {
|
||||
Some(bss)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Scanner<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.events.mask.disable_all();
|
||||
}
|
||||
}
|
481
cyw43/src/countries.rs
Normal file
481
cyw43/src/countries.rs
Normal file
@ -0,0 +1,481 @@
|
||||
#![allow(unused)]
|
||||
|
||||
pub struct Country {
|
||||
pub code: [u8; 2],
|
||||
pub rev: u16,
|
||||
}
|
||||
|
||||
/// AF Afghanistan
|
||||
pub const AFGHANISTAN: Country = Country { code: *b"AF", rev: 0 };
|
||||
/// AL Albania
|
||||
pub const ALBANIA: Country = Country { code: *b"AL", rev: 0 };
|
||||
/// DZ Algeria
|
||||
pub const ALGERIA: Country = Country { code: *b"DZ", rev: 0 };
|
||||
/// AS American_Samoa
|
||||
pub const AMERICAN_SAMOA: Country = Country { code: *b"AS", rev: 0 };
|
||||
/// AO Angola
|
||||
pub const ANGOLA: Country = Country { code: *b"AO", rev: 0 };
|
||||
/// AI Anguilla
|
||||
pub const ANGUILLA: Country = Country { code: *b"AI", rev: 0 };
|
||||
/// AG Antigua_and_Barbuda
|
||||
pub const ANTIGUA_AND_BARBUDA: Country = Country { code: *b"AG", rev: 0 };
|
||||
/// AR Argentina
|
||||
pub const ARGENTINA: Country = Country { code: *b"AR", rev: 0 };
|
||||
/// AM Armenia
|
||||
pub const ARMENIA: Country = Country { code: *b"AM", rev: 0 };
|
||||
/// AW Aruba
|
||||
pub const ARUBA: Country = Country { code: *b"AW", rev: 0 };
|
||||
/// AU Australia
|
||||
pub const AUSTRALIA: Country = Country { code: *b"AU", rev: 0 };
|
||||
/// AT Austria
|
||||
pub const AUSTRIA: Country = Country { code: *b"AT", rev: 0 };
|
||||
/// AZ Azerbaijan
|
||||
pub const AZERBAIJAN: Country = Country { code: *b"AZ", rev: 0 };
|
||||
/// BS Bahamas
|
||||
pub const BAHAMAS: Country = Country { code: *b"BS", rev: 0 };
|
||||
/// BH Bahrain
|
||||
pub const BAHRAIN: Country = Country { code: *b"BH", rev: 0 };
|
||||
/// 0B Baker_Island
|
||||
pub const BAKER_ISLAND: Country = Country { code: *b"0B", rev: 0 };
|
||||
/// BD Bangladesh
|
||||
pub const BANGLADESH: Country = Country { code: *b"BD", rev: 0 };
|
||||
/// BB Barbados
|
||||
pub const BARBADOS: Country = Country { code: *b"BB", rev: 0 };
|
||||
/// BY Belarus
|
||||
pub const BELARUS: Country = Country { code: *b"BY", rev: 0 };
|
||||
/// BE Belgium
|
||||
pub const BELGIUM: Country = Country { code: *b"BE", rev: 0 };
|
||||
/// BZ Belize
|
||||
pub const BELIZE: Country = Country { code: *b"BZ", rev: 0 };
|
||||
/// BJ Benin
|
||||
pub const BENIN: Country = Country { code: *b"BJ", rev: 0 };
|
||||
/// BM Bermuda
|
||||
pub const BERMUDA: Country = Country { code: *b"BM", rev: 0 };
|
||||
/// BT Bhutan
|
||||
pub const BHUTAN: Country = Country { code: *b"BT", rev: 0 };
|
||||
/// BO Bolivia
|
||||
pub const BOLIVIA: Country = Country { code: *b"BO", rev: 0 };
|
||||
/// BA Bosnia_and_Herzegovina
|
||||
pub const BOSNIA_AND_HERZEGOVINA: Country = Country { code: *b"BA", rev: 0 };
|
||||
/// BW Botswana
|
||||
pub const BOTSWANA: Country = Country { code: *b"BW", rev: 0 };
|
||||
/// BR Brazil
|
||||
pub const BRAZIL: Country = Country { code: *b"BR", rev: 0 };
|
||||
/// IO British_Indian_Ocean_Territory
|
||||
pub const BRITISH_INDIAN_OCEAN_TERRITORY: Country = Country { code: *b"IO", rev: 0 };
|
||||
/// BN Brunei_Darussalam
|
||||
pub const BRUNEI_DARUSSALAM: Country = Country { code: *b"BN", rev: 0 };
|
||||
/// BG Bulgaria
|
||||
pub const BULGARIA: Country = Country { code: *b"BG", rev: 0 };
|
||||
/// BF Burkina_Faso
|
||||
pub const BURKINA_FASO: Country = Country { code: *b"BF", rev: 0 };
|
||||
/// BI Burundi
|
||||
pub const BURUNDI: Country = Country { code: *b"BI", rev: 0 };
|
||||
/// KH Cambodia
|
||||
pub const CAMBODIA: Country = Country { code: *b"KH", rev: 0 };
|
||||
/// CM Cameroon
|
||||
pub const CAMEROON: Country = Country { code: *b"CM", rev: 0 };
|
||||
/// CA Canada
|
||||
pub const CANADA: Country = Country { code: *b"CA", rev: 0 };
|
||||
/// CA Canada Revision 950
|
||||
pub const CANADA_REV950: Country = Country { code: *b"CA", rev: 950 };
|
||||
/// CV Cape_Verde
|
||||
pub const CAPE_VERDE: Country = Country { code: *b"CV", rev: 0 };
|
||||
/// KY Cayman_Islands
|
||||
pub const CAYMAN_ISLANDS: Country = Country { code: *b"KY", rev: 0 };
|
||||
/// CF Central_African_Republic
|
||||
pub const CENTRAL_AFRICAN_REPUBLIC: Country = Country { code: *b"CF", rev: 0 };
|
||||
/// TD Chad
|
||||
pub const CHAD: Country = Country { code: *b"TD", rev: 0 };
|
||||
/// CL Chile
|
||||
pub const CHILE: Country = Country { code: *b"CL", rev: 0 };
|
||||
/// CN China
|
||||
pub const CHINA: Country = Country { code: *b"CN", rev: 0 };
|
||||
/// CX Christmas_Island
|
||||
pub const CHRISTMAS_ISLAND: Country = Country { code: *b"CX", rev: 0 };
|
||||
/// CO Colombia
|
||||
pub const COLOMBIA: Country = Country { code: *b"CO", rev: 0 };
|
||||
/// KM Comoros
|
||||
pub const COMOROS: Country = Country { code: *b"KM", rev: 0 };
|
||||
/// CG Congo
|
||||
pub const CONGO: Country = Country { code: *b"CG", rev: 0 };
|
||||
/// CD Congo,_The_Democratic_Republic_Of_The
|
||||
pub const CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE: Country = Country { code: *b"CD", rev: 0 };
|
||||
/// CR Costa_Rica
|
||||
pub const COSTA_RICA: Country = Country { code: *b"CR", rev: 0 };
|
||||
/// CI Cote_D'ivoire
|
||||
pub const COTE_DIVOIRE: Country = Country { code: *b"CI", rev: 0 };
|
||||
/// HR Croatia
|
||||
pub const CROATIA: Country = Country { code: *b"HR", rev: 0 };
|
||||
/// CU Cuba
|
||||
pub const CUBA: Country = Country { code: *b"CU", rev: 0 };
|
||||
/// CY Cyprus
|
||||
pub const CYPRUS: Country = Country { code: *b"CY", rev: 0 };
|
||||
/// CZ Czech_Republic
|
||||
pub const CZECH_REPUBLIC: Country = Country { code: *b"CZ", rev: 0 };
|
||||
/// DK Denmark
|
||||
pub const DENMARK: Country = Country { code: *b"DK", rev: 0 };
|
||||
/// DJ Djibouti
|
||||
pub const DJIBOUTI: Country = Country { code: *b"DJ", rev: 0 };
|
||||
/// DM Dominica
|
||||
pub const DOMINICA: Country = Country { code: *b"DM", rev: 0 };
|
||||
/// DO Dominican_Republic
|
||||
pub const DOMINICAN_REPUBLIC: Country = Country { code: *b"DO", rev: 0 };
|
||||
/// AU G'Day mate!
|
||||
pub const DOWN_UNDER: Country = Country { code: *b"AU", rev: 0 };
|
||||
/// EC Ecuador
|
||||
pub const ECUADOR: Country = Country { code: *b"EC", rev: 0 };
|
||||
/// EG Egypt
|
||||
pub const EGYPT: Country = Country { code: *b"EG", rev: 0 };
|
||||
/// SV El_Salvador
|
||||
pub const EL_SALVADOR: Country = Country { code: *b"SV", rev: 0 };
|
||||
/// GQ Equatorial_Guinea
|
||||
pub const EQUATORIAL_GUINEA: Country = Country { code: *b"GQ", rev: 0 };
|
||||
/// ER Eritrea
|
||||
pub const ERITREA: Country = Country { code: *b"ER", rev: 0 };
|
||||
/// EE Estonia
|
||||
pub const ESTONIA: Country = Country { code: *b"EE", rev: 0 };
|
||||
/// ET Ethiopia
|
||||
pub const ETHIOPIA: Country = Country { code: *b"ET", rev: 0 };
|
||||
/// FK Falkland_Islands_(Malvinas)
|
||||
pub const FALKLAND_ISLANDS_MALVINAS: Country = Country { code: *b"FK", rev: 0 };
|
||||
/// FO Faroe_Islands
|
||||
pub const FAROE_ISLANDS: Country = Country { code: *b"FO", rev: 0 };
|
||||
/// FJ Fiji
|
||||
pub const FIJI: Country = Country { code: *b"FJ", rev: 0 };
|
||||
/// FI Finland
|
||||
pub const FINLAND: Country = Country { code: *b"FI", rev: 0 };
|
||||
/// FR France
|
||||
pub const FRANCE: Country = Country { code: *b"FR", rev: 0 };
|
||||
/// GF French_Guina
|
||||
pub const FRENCH_GUINA: Country = Country { code: *b"GF", rev: 0 };
|
||||
/// PF French_Polynesia
|
||||
pub const FRENCH_POLYNESIA: Country = Country { code: *b"PF", rev: 0 };
|
||||
/// TF French_Southern_Territories
|
||||
pub const FRENCH_SOUTHERN_TERRITORIES: Country = Country { code: *b"TF", rev: 0 };
|
||||
/// GA Gabon
|
||||
pub const GABON: Country = Country { code: *b"GA", rev: 0 };
|
||||
/// GM Gambia
|
||||
pub const GAMBIA: Country = Country { code: *b"GM", rev: 0 };
|
||||
/// GE Georgia
|
||||
pub const GEORGIA: Country = Country { code: *b"GE", rev: 0 };
|
||||
/// DE Germany
|
||||
pub const GERMANY: Country = Country { code: *b"DE", rev: 0 };
|
||||
/// E0 European_Wide Revision 895
|
||||
pub const EUROPEAN_WIDE_REV895: Country = Country { code: *b"E0", rev: 895 };
|
||||
/// GH Ghana
|
||||
pub const GHANA: Country = Country { code: *b"GH", rev: 0 };
|
||||
/// GI Gibraltar
|
||||
pub const GIBRALTAR: Country = Country { code: *b"GI", rev: 0 };
|
||||
/// GR Greece
|
||||
pub const GREECE: Country = Country { code: *b"GR", rev: 0 };
|
||||
/// GD Grenada
|
||||
pub const GRENADA: Country = Country { code: *b"GD", rev: 0 };
|
||||
/// GP Guadeloupe
|
||||
pub const GUADELOUPE: Country = Country { code: *b"GP", rev: 0 };
|
||||
/// GU Guam
|
||||
pub const GUAM: Country = Country { code: *b"GU", rev: 0 };
|
||||
/// GT Guatemala
|
||||
pub const GUATEMALA: Country = Country { code: *b"GT", rev: 0 };
|
||||
/// GG Guernsey
|
||||
pub const GUERNSEY: Country = Country { code: *b"GG", rev: 0 };
|
||||
/// GN Guinea
|
||||
pub const GUINEA: Country = Country { code: *b"GN", rev: 0 };
|
||||
/// GW Guinea-bissau
|
||||
pub const GUINEA_BISSAU: Country = Country { code: *b"GW", rev: 0 };
|
||||
/// GY Guyana
|
||||
pub const GUYANA: Country = Country { code: *b"GY", rev: 0 };
|
||||
/// HT Haiti
|
||||
pub const HAITI: Country = Country { code: *b"HT", rev: 0 };
|
||||
/// VA Holy_See_(Vatican_City_State)
|
||||
pub const HOLY_SEE_VATICAN_CITY_STATE: Country = Country { code: *b"VA", rev: 0 };
|
||||
/// HN Honduras
|
||||
pub const HONDURAS: Country = Country { code: *b"HN", rev: 0 };
|
||||
/// HK Hong_Kong
|
||||
pub const HONG_KONG: Country = Country { code: *b"HK", rev: 0 };
|
||||
/// HU Hungary
|
||||
pub const HUNGARY: Country = Country { code: *b"HU", rev: 0 };
|
||||
/// IS Iceland
|
||||
pub const ICELAND: Country = Country { code: *b"IS", rev: 0 };
|
||||
/// IN India
|
||||
pub const INDIA: Country = Country { code: *b"IN", rev: 0 };
|
||||
/// ID Indonesia
|
||||
pub const INDONESIA: Country = Country { code: *b"ID", rev: 0 };
|
||||
/// IR Iran,_Islamic_Republic_Of
|
||||
pub const IRAN_ISLAMIC_REPUBLIC_OF: Country = Country { code: *b"IR", rev: 0 };
|
||||
/// IQ Iraq
|
||||
pub const IRAQ: Country = Country { code: *b"IQ", rev: 0 };
|
||||
/// IE Ireland
|
||||
pub const IRELAND: Country = Country { code: *b"IE", rev: 0 };
|
||||
/// IL Israel
|
||||
pub const ISRAEL: Country = Country { code: *b"IL", rev: 0 };
|
||||
/// IT Italy
|
||||
pub const ITALY: Country = Country { code: *b"IT", rev: 0 };
|
||||
/// JM Jamaica
|
||||
pub const JAMAICA: Country = Country { code: *b"JM", rev: 0 };
|
||||
/// JP Japan
|
||||
pub const JAPAN: Country = Country { code: *b"JP", rev: 0 };
|
||||
/// JE Jersey
|
||||
pub const JERSEY: Country = Country { code: *b"JE", rev: 0 };
|
||||
/// JO Jordan
|
||||
pub const JORDAN: Country = Country { code: *b"JO", rev: 0 };
|
||||
/// KZ Kazakhstan
|
||||
pub const KAZAKHSTAN: Country = Country { code: *b"KZ", rev: 0 };
|
||||
/// KE Kenya
|
||||
pub const KENYA: Country = Country { code: *b"KE", rev: 0 };
|
||||
/// KI Kiribati
|
||||
pub const KIRIBATI: Country = Country { code: *b"KI", rev: 0 };
|
||||
/// KR Korea,_Republic_Of
|
||||
pub const KOREA_REPUBLIC_OF: Country = Country { code: *b"KR", rev: 1 };
|
||||
/// 0A Kosovo
|
||||
pub const KOSOVO: Country = Country { code: *b"0A", rev: 0 };
|
||||
/// KW Kuwait
|
||||
pub const KUWAIT: Country = Country { code: *b"KW", rev: 0 };
|
||||
/// KG Kyrgyzstan
|
||||
pub const KYRGYZSTAN: Country = Country { code: *b"KG", rev: 0 };
|
||||
/// LA Lao_People's_Democratic_Repubic
|
||||
pub const LAO_PEOPLES_DEMOCRATIC_REPUBIC: Country = Country { code: *b"LA", rev: 0 };
|
||||
/// LV Latvia
|
||||
pub const LATVIA: Country = Country { code: *b"LV", rev: 0 };
|
||||
/// LB Lebanon
|
||||
pub const LEBANON: Country = Country { code: *b"LB", rev: 0 };
|
||||
/// LS Lesotho
|
||||
pub const LESOTHO: Country = Country { code: *b"LS", rev: 0 };
|
||||
/// LR Liberia
|
||||
pub const LIBERIA: Country = Country { code: *b"LR", rev: 0 };
|
||||
/// LY Libyan_Arab_Jamahiriya
|
||||
pub const LIBYAN_ARAB_JAMAHIRIYA: Country = Country { code: *b"LY", rev: 0 };
|
||||
/// LI Liechtenstein
|
||||
pub const LIECHTENSTEIN: Country = Country { code: *b"LI", rev: 0 };
|
||||
/// LT Lithuania
|
||||
pub const LITHUANIA: Country = Country { code: *b"LT", rev: 0 };
|
||||
/// LU Luxembourg
|
||||
pub const LUXEMBOURG: Country = Country { code: *b"LU", rev: 0 };
|
||||
/// MO Macao
|
||||
pub const MACAO: Country = Country { code: *b"MO", rev: 0 };
|
||||
/// MK Macedonia,_Former_Yugoslav_Republic_Of
|
||||
pub const MACEDONIA_FORMER_YUGOSLAV_REPUBLIC_OF: Country = Country { code: *b"MK", rev: 0 };
|
||||
/// MG Madagascar
|
||||
pub const MADAGASCAR: Country = Country { code: *b"MG", rev: 0 };
|
||||
/// MW Malawi
|
||||
pub const MALAWI: Country = Country { code: *b"MW", rev: 0 };
|
||||
/// MY Malaysia
|
||||
pub const MALAYSIA: Country = Country { code: *b"MY", rev: 0 };
|
||||
/// MV Maldives
|
||||
pub const MALDIVES: Country = Country { code: *b"MV", rev: 0 };
|
||||
/// ML Mali
|
||||
pub const MALI: Country = Country { code: *b"ML", rev: 0 };
|
||||
/// MT Malta
|
||||
pub const MALTA: Country = Country { code: *b"MT", rev: 0 };
|
||||
/// IM Man,_Isle_Of
|
||||
pub const MAN_ISLE_OF: Country = Country { code: *b"IM", rev: 0 };
|
||||
/// MQ Martinique
|
||||
pub const MARTINIQUE: Country = Country { code: *b"MQ", rev: 0 };
|
||||
/// MR Mauritania
|
||||
pub const MAURITANIA: Country = Country { code: *b"MR", rev: 0 };
|
||||
/// MU Mauritius
|
||||
pub const MAURITIUS: Country = Country { code: *b"MU", rev: 0 };
|
||||
/// YT Mayotte
|
||||
pub const MAYOTTE: Country = Country { code: *b"YT", rev: 0 };
|
||||
/// MX Mexico
|
||||
pub const MEXICO: Country = Country { code: *b"MX", rev: 0 };
|
||||
/// FM Micronesia,_Federated_States_Of
|
||||
pub const MICRONESIA_FEDERATED_STATES_OF: Country = Country { code: *b"FM", rev: 0 };
|
||||
/// MD Moldova,_Republic_Of
|
||||
pub const MOLDOVA_REPUBLIC_OF: Country = Country { code: *b"MD", rev: 0 };
|
||||
/// MC Monaco
|
||||
pub const MONACO: Country = Country { code: *b"MC", rev: 0 };
|
||||
/// MN Mongolia
|
||||
pub const MONGOLIA: Country = Country { code: *b"MN", rev: 0 };
|
||||
/// ME Montenegro
|
||||
pub const MONTENEGRO: Country = Country { code: *b"ME", rev: 0 };
|
||||
/// MS Montserrat
|
||||
pub const MONTSERRAT: Country = Country { code: *b"MS", rev: 0 };
|
||||
/// MA Morocco
|
||||
pub const MOROCCO: Country = Country { code: *b"MA", rev: 0 };
|
||||
/// MZ Mozambique
|
||||
pub const MOZAMBIQUE: Country = Country { code: *b"MZ", rev: 0 };
|
||||
/// MM Myanmar
|
||||
pub const MYANMAR: Country = Country { code: *b"MM", rev: 0 };
|
||||
/// NA Namibia
|
||||
pub const NAMIBIA: Country = Country { code: *b"NA", rev: 0 };
|
||||
/// NR Nauru
|
||||
pub const NAURU: Country = Country { code: *b"NR", rev: 0 };
|
||||
/// NP Nepal
|
||||
pub const NEPAL: Country = Country { code: *b"NP", rev: 0 };
|
||||
/// NL Netherlands
|
||||
pub const NETHERLANDS: Country = Country { code: *b"NL", rev: 0 };
|
||||
/// AN Netherlands_Antilles
|
||||
pub const NETHERLANDS_ANTILLES: Country = Country { code: *b"AN", rev: 0 };
|
||||
/// NC New_Caledonia
|
||||
pub const NEW_CALEDONIA: Country = Country { code: *b"NC", rev: 0 };
|
||||
/// NZ New_Zealand
|
||||
pub const NEW_ZEALAND: Country = Country { code: *b"NZ", rev: 0 };
|
||||
/// NI Nicaragua
|
||||
pub const NICARAGUA: Country = Country { code: *b"NI", rev: 0 };
|
||||
/// NE Niger
|
||||
pub const NIGER: Country = Country { code: *b"NE", rev: 0 };
|
||||
/// NG Nigeria
|
||||
pub const NIGERIA: Country = Country { code: *b"NG", rev: 0 };
|
||||
/// NF Norfolk_Island
|
||||
pub const NORFOLK_ISLAND: Country = Country { code: *b"NF", rev: 0 };
|
||||
/// MP Northern_Mariana_Islands
|
||||
pub const NORTHERN_MARIANA_ISLANDS: Country = Country { code: *b"MP", rev: 0 };
|
||||
/// NO Norway
|
||||
pub const NORWAY: Country = Country { code: *b"NO", rev: 0 };
|
||||
/// OM Oman
|
||||
pub const OMAN: Country = Country { code: *b"OM", rev: 0 };
|
||||
/// PK Pakistan
|
||||
pub const PAKISTAN: Country = Country { code: *b"PK", rev: 0 };
|
||||
/// PW Palau
|
||||
pub const PALAU: Country = Country { code: *b"PW", rev: 0 };
|
||||
/// PA Panama
|
||||
pub const PANAMA: Country = Country { code: *b"PA", rev: 0 };
|
||||
/// PG Papua_New_Guinea
|
||||
pub const PAPUA_NEW_GUINEA: Country = Country { code: *b"PG", rev: 0 };
|
||||
/// PY Paraguay
|
||||
pub const PARAGUAY: Country = Country { code: *b"PY", rev: 0 };
|
||||
/// PE Peru
|
||||
pub const PERU: Country = Country { code: *b"PE", rev: 0 };
|
||||
/// PH Philippines
|
||||
pub const PHILIPPINES: Country = Country { code: *b"PH", rev: 0 };
|
||||
/// PL Poland
|
||||
pub const POLAND: Country = Country { code: *b"PL", rev: 0 };
|
||||
/// PT Portugal
|
||||
pub const PORTUGAL: Country = Country { code: *b"PT", rev: 0 };
|
||||
/// PR Pueto_Rico
|
||||
pub const PUETO_RICO: Country = Country { code: *b"PR", rev: 0 };
|
||||
/// QA Qatar
|
||||
pub const QATAR: Country = Country { code: *b"QA", rev: 0 };
|
||||
/// RE Reunion
|
||||
pub const REUNION: Country = Country { code: *b"RE", rev: 0 };
|
||||
/// RO Romania
|
||||
pub const ROMANIA: Country = Country { code: *b"RO", rev: 0 };
|
||||
/// RU Russian_Federation
|
||||
pub const RUSSIAN_FEDERATION: Country = Country { code: *b"RU", rev: 0 };
|
||||
/// RW Rwanda
|
||||
pub const RWANDA: Country = Country { code: *b"RW", rev: 0 };
|
||||
/// KN Saint_Kitts_and_Nevis
|
||||
pub const SAINT_KITTS_AND_NEVIS: Country = Country { code: *b"KN", rev: 0 };
|
||||
/// LC Saint_Lucia
|
||||
pub const SAINT_LUCIA: Country = Country { code: *b"LC", rev: 0 };
|
||||
/// PM Saint_Pierre_and_Miquelon
|
||||
pub const SAINT_PIERRE_AND_MIQUELON: Country = Country { code: *b"PM", rev: 0 };
|
||||
/// VC Saint_Vincent_and_The_Grenadines
|
||||
pub const SAINT_VINCENT_AND_THE_GRENADINES: Country = Country { code: *b"VC", rev: 0 };
|
||||
/// WS Samoa
|
||||
pub const SAMOA: Country = Country { code: *b"WS", rev: 0 };
|
||||
/// MF Sanit_Martin_/_Sint_Marteen
|
||||
pub const SANIT_MARTIN_SINT_MARTEEN: Country = Country { code: *b"MF", rev: 0 };
|
||||
/// ST Sao_Tome_and_Principe
|
||||
pub const SAO_TOME_AND_PRINCIPE: Country = Country { code: *b"ST", rev: 0 };
|
||||
/// SA Saudi_Arabia
|
||||
pub const SAUDI_ARABIA: Country = Country { code: *b"SA", rev: 0 };
|
||||
/// SN Senegal
|
||||
pub const SENEGAL: Country = Country { code: *b"SN", rev: 0 };
|
||||
/// RS Serbia
|
||||
pub const SERBIA: Country = Country { code: *b"RS", rev: 0 };
|
||||
/// SC Seychelles
|
||||
pub const SEYCHELLES: Country = Country { code: *b"SC", rev: 0 };
|
||||
/// SL Sierra_Leone
|
||||
pub const SIERRA_LEONE: Country = Country { code: *b"SL", rev: 0 };
|
||||
/// SG Singapore
|
||||
pub const SINGAPORE: Country = Country { code: *b"SG", rev: 0 };
|
||||
/// SK Slovakia
|
||||
pub const SLOVAKIA: Country = Country { code: *b"SK", rev: 0 };
|
||||
/// SI Slovenia
|
||||
pub const SLOVENIA: Country = Country { code: *b"SI", rev: 0 };
|
||||
/// SB Solomon_Islands
|
||||
pub const SOLOMON_ISLANDS: Country = Country { code: *b"SB", rev: 0 };
|
||||
/// SO Somalia
|
||||
pub const SOMALIA: Country = Country { code: *b"SO", rev: 0 };
|
||||
/// ZA South_Africa
|
||||
pub const SOUTH_AFRICA: Country = Country { code: *b"ZA", rev: 0 };
|
||||
/// ES Spain
|
||||
pub const SPAIN: Country = Country { code: *b"ES", rev: 0 };
|
||||
/// LK Sri_Lanka
|
||||
pub const SRI_LANKA: Country = Country { code: *b"LK", rev: 0 };
|
||||
/// SR Suriname
|
||||
pub const SURINAME: Country = Country { code: *b"SR", rev: 0 };
|
||||
/// SZ Swaziland
|
||||
pub const SWAZILAND: Country = Country { code: *b"SZ", rev: 0 };
|
||||
/// SE Sweden
|
||||
pub const SWEDEN: Country = Country { code: *b"SE", rev: 0 };
|
||||
/// CH Switzerland
|
||||
pub const SWITZERLAND: Country = Country { code: *b"CH", rev: 0 };
|
||||
/// SY Syrian_Arab_Republic
|
||||
pub const SYRIAN_ARAB_REPUBLIC: Country = Country { code: *b"SY", rev: 0 };
|
||||
/// TW Taiwan,_Province_Of_China
|
||||
pub const TAIWAN_PROVINCE_OF_CHINA: Country = Country { code: *b"TW", rev: 0 };
|
||||
/// TJ Tajikistan
|
||||
pub const TAJIKISTAN: Country = Country { code: *b"TJ", rev: 0 };
|
||||
/// TZ Tanzania,_United_Republic_Of
|
||||
pub const TANZANIA_UNITED_REPUBLIC_OF: Country = Country { code: *b"TZ", rev: 0 };
|
||||
/// TH Thailand
|
||||
pub const THAILAND: Country = Country { code: *b"TH", rev: 0 };
|
||||
/// TG Togo
|
||||
pub const TOGO: Country = Country { code: *b"TG", rev: 0 };
|
||||
/// TO Tonga
|
||||
pub const TONGA: Country = Country { code: *b"TO", rev: 0 };
|
||||
/// TT Trinidad_and_Tobago
|
||||
pub const TRINIDAD_AND_TOBAGO: Country = Country { code: *b"TT", rev: 0 };
|
||||
/// TN Tunisia
|
||||
pub const TUNISIA: Country = Country { code: *b"TN", rev: 0 };
|
||||
/// TR Turkey
|
||||
pub const TURKEY: Country = Country { code: *b"TR", rev: 0 };
|
||||
/// TM Turkmenistan
|
||||
pub const TURKMENISTAN: Country = Country { code: *b"TM", rev: 0 };
|
||||
/// TC Turks_and_Caicos_Islands
|
||||
pub const TURKS_AND_CAICOS_ISLANDS: Country = Country { code: *b"TC", rev: 0 };
|
||||
/// TV Tuvalu
|
||||
pub const TUVALU: Country = Country { code: *b"TV", rev: 0 };
|
||||
/// UG Uganda
|
||||
pub const UGANDA: Country = Country { code: *b"UG", rev: 0 };
|
||||
/// UA Ukraine
|
||||
pub const UKRAINE: Country = Country { code: *b"UA", rev: 0 };
|
||||
/// AE United_Arab_Emirates
|
||||
pub const UNITED_ARAB_EMIRATES: Country = Country { code: *b"AE", rev: 0 };
|
||||
/// GB United_Kingdom
|
||||
pub const UNITED_KINGDOM: Country = Country { code: *b"GB", rev: 0 };
|
||||
/// US United_States
|
||||
pub const UNITED_STATES: Country = Country { code: *b"US", rev: 0 };
|
||||
/// US United_States Revision 4
|
||||
pub const UNITED_STATES_REV4: Country = Country { code: *b"US", rev: 4 };
|
||||
/// Q1 United_States Revision 931
|
||||
pub const UNITED_STATES_REV931: Country = Country { code: *b"Q1", rev: 931 };
|
||||
/// Q2 United_States_(No_DFS)
|
||||
pub const UNITED_STATES_NO_DFS: Country = Country { code: *b"Q2", rev: 0 };
|
||||
/// UM United_States_Minor_Outlying_Islands
|
||||
pub const UNITED_STATES_MINOR_OUTLYING_ISLANDS: Country = Country { code: *b"UM", rev: 0 };
|
||||
/// UY Uruguay
|
||||
pub const URUGUAY: Country = Country { code: *b"UY", rev: 0 };
|
||||
/// UZ Uzbekistan
|
||||
pub const UZBEKISTAN: Country = Country { code: *b"UZ", rev: 0 };
|
||||
/// VU Vanuatu
|
||||
pub const VANUATU: Country = Country { code: *b"VU", rev: 0 };
|
||||
/// VE Venezuela
|
||||
pub const VENEZUELA: Country = Country { code: *b"VE", rev: 0 };
|
||||
/// VN Viet_Nam
|
||||
pub const VIET_NAM: Country = Country { code: *b"VN", rev: 0 };
|
||||
/// VG Virgin_Islands,_British
|
||||
pub const VIRGIN_ISLANDS_BRITISH: Country = Country { code: *b"VG", rev: 0 };
|
||||
/// VI Virgin_Islands,_U.S.
|
||||
pub const VIRGIN_ISLANDS_US: Country = Country { code: *b"VI", rev: 0 };
|
||||
/// WF Wallis_and_Futuna
|
||||
pub const WALLIS_AND_FUTUNA: Country = Country { code: *b"WF", rev: 0 };
|
||||
/// 0C West_Bank
|
||||
pub const WEST_BANK: Country = Country { code: *b"0C", rev: 0 };
|
||||
/// EH Western_Sahara
|
||||
pub const WESTERN_SAHARA: Country = Country { code: *b"EH", rev: 0 };
|
||||
/// Worldwide Locale Revision 983
|
||||
pub const WORLD_WIDE_XV_REV983: Country = Country { code: *b"XV", rev: 983 };
|
||||
/// Worldwide Locale (passive Ch12-14)
|
||||
pub const WORLD_WIDE_XX: Country = Country { code: *b"XX", rev: 0 };
|
||||
/// Worldwide Locale (passive Ch12-14) Revision 17
|
||||
pub const WORLD_WIDE_XX_REV17: Country = Country { code: *b"XX", rev: 17 };
|
||||
/// YE Yemen
|
||||
pub const YEMEN: Country = Country { code: *b"YE", rev: 0 };
|
||||
/// ZM Zambia
|
||||
pub const ZAMBIA: Country = Country { code: *b"ZM", rev: 0 };
|
||||
/// ZW Zimbabwe
|
||||
pub const ZIMBABWE: Country = Country { code: *b"ZW", rev: 0 };
|
400
cyw43/src/events.rs
Normal file
400
cyw43/src/events.rs
Normal file
@ -0,0 +1,400 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use core::cell::RefCell;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::pubsub::{PubSubChannel, Subscriber};
|
||||
|
||||
use crate::structs::BssInfo;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum Event {
|
||||
#[num_enum(default)]
|
||||
Unknown = 0xFF,
|
||||
/// indicates status of set SSID
|
||||
SET_SSID = 0,
|
||||
/// differentiates join IBSS from found (START) IBSS
|
||||
JOIN = 1,
|
||||
/// STA founded an IBSS or AP started a BSS
|
||||
START = 2,
|
||||
/// 802.11 AUTH request
|
||||
AUTH = 3,
|
||||
/// 802.11 AUTH indication
|
||||
AUTH_IND = 4,
|
||||
/// 802.11 DEAUTH request
|
||||
DEAUTH = 5,
|
||||
/// 802.11 DEAUTH indication
|
||||
DEAUTH_IND = 6,
|
||||
/// 802.11 ASSOC request
|
||||
ASSOC = 7,
|
||||
/// 802.11 ASSOC indication
|
||||
ASSOC_IND = 8,
|
||||
/// 802.11 REASSOC request
|
||||
REASSOC = 9,
|
||||
/// 802.11 REASSOC indication
|
||||
REASSOC_IND = 10,
|
||||
/// 802.11 DISASSOC request
|
||||
DISASSOC = 11,
|
||||
/// 802.11 DISASSOC indication
|
||||
DISASSOC_IND = 12,
|
||||
/// 802.11h Quiet period started
|
||||
QUIET_START = 13,
|
||||
/// 802.11h Quiet period ended
|
||||
QUIET_END = 14,
|
||||
/// BEACONS received/lost indication
|
||||
BEACON_RX = 15,
|
||||
/// generic link indication
|
||||
LINK = 16,
|
||||
/// TKIP MIC error occurred
|
||||
MIC_ERROR = 17,
|
||||
/// NDIS style link indication
|
||||
NDIS_LINK = 18,
|
||||
/// roam attempt occurred: indicate status & reason
|
||||
ROAM = 19,
|
||||
/// change in dot11FailedCount (txfail)
|
||||
TXFAIL = 20,
|
||||
/// WPA2 pmkid cache indication
|
||||
PMKID_CACHE = 21,
|
||||
/// current AP's TSF value went backward
|
||||
RETROGRADE_TSF = 22,
|
||||
/// AP was pruned from join list for reason
|
||||
PRUNE = 23,
|
||||
/// report AutoAuth table entry match for join attempt
|
||||
AUTOAUTH = 24,
|
||||
/// Event encapsulating an EAPOL message
|
||||
EAPOL_MSG = 25,
|
||||
/// Scan results are ready or scan was aborted
|
||||
SCAN_COMPLETE = 26,
|
||||
/// indicate to host addts fail/success
|
||||
ADDTS_IND = 27,
|
||||
/// indicate to host delts fail/success
|
||||
DELTS_IND = 28,
|
||||
/// indicate to host of beacon transmit
|
||||
BCNSENT_IND = 29,
|
||||
/// Send the received beacon up to the host
|
||||
BCNRX_MSG = 30,
|
||||
/// indicate to host loss of beacon
|
||||
BCNLOST_MSG = 31,
|
||||
/// before attempting to roam
|
||||
ROAM_PREP = 32,
|
||||
/// PFN network found event
|
||||
PFN_NET_FOUND = 33,
|
||||
/// PFN network lost event
|
||||
PFN_NET_LOST = 34,
|
||||
RESET_COMPLETE = 35,
|
||||
JOIN_START = 36,
|
||||
ROAM_START = 37,
|
||||
ASSOC_START = 38,
|
||||
IBSS_ASSOC = 39,
|
||||
RADIO = 40,
|
||||
/// PSM microcode watchdog fired
|
||||
PSM_WATCHDOG = 41,
|
||||
/// CCX association start
|
||||
CCX_ASSOC_START = 42,
|
||||
/// CCX association abort
|
||||
CCX_ASSOC_ABORT = 43,
|
||||
/// probe request received
|
||||
PROBREQ_MSG = 44,
|
||||
SCAN_CONFIRM_IND = 45,
|
||||
/// WPA Handshake
|
||||
PSK_SUP = 46,
|
||||
COUNTRY_CODE_CHANGED = 47,
|
||||
/// WMMAC excedded medium time
|
||||
EXCEEDED_MEDIUM_TIME = 48,
|
||||
/// WEP ICV error occurred
|
||||
ICV_ERROR = 49,
|
||||
/// Unsupported unicast encrypted frame
|
||||
UNICAST_DECODE_ERROR = 50,
|
||||
/// Unsupported multicast encrypted frame
|
||||
MULTICAST_DECODE_ERROR = 51,
|
||||
TRACE = 52,
|
||||
/// BT-AMP HCI event
|
||||
BTA_HCI_EVENT = 53,
|
||||
/// I/F change (for wlan host notification)
|
||||
IF = 54,
|
||||
/// P2P Discovery listen state expires
|
||||
P2P_DISC_LISTEN_COMPLETE = 55,
|
||||
/// indicate RSSI change based on configured levels
|
||||
RSSI = 56,
|
||||
/// PFN best network batching event
|
||||
PFN_BEST_BATCHING = 57,
|
||||
EXTLOG_MSG = 58,
|
||||
/// Action frame reception
|
||||
ACTION_FRAME = 59,
|
||||
/// Action frame Tx complete
|
||||
ACTION_FRAME_COMPLETE = 60,
|
||||
/// assoc request received
|
||||
PRE_ASSOC_IND = 61,
|
||||
/// re-assoc request received
|
||||
PRE_REASSOC_IND = 62,
|
||||
/// channel adopted (xxx: obsoleted)
|
||||
CHANNEL_ADOPTED = 63,
|
||||
/// AP started
|
||||
AP_STARTED = 64,
|
||||
/// AP stopped due to DFS
|
||||
DFS_AP_STOP = 65,
|
||||
/// AP resumed due to DFS
|
||||
DFS_AP_RESUME = 66,
|
||||
/// WAI stations event
|
||||
WAI_STA_EVENT = 67,
|
||||
/// event encapsulating an WAI message
|
||||
WAI_MSG = 68,
|
||||
/// escan result event
|
||||
ESCAN_RESULT = 69,
|
||||
/// action frame off channel complete
|
||||
ACTION_FRAME_OFF_CHAN_COMPLETE = 70,
|
||||
/// probe response received
|
||||
PROBRESP_MSG = 71,
|
||||
/// P2P Probe request received
|
||||
P2P_PROBREQ_MSG = 72,
|
||||
DCS_REQUEST = 73,
|
||||
/// credits for D11 FIFOs. [AC0,AC1,AC2,AC3,BC_MC,ATIM]
|
||||
FIFO_CREDIT_MAP = 74,
|
||||
/// Received action frame event WITH wl_event_rx_frame_data_t header
|
||||
ACTION_FRAME_RX = 75,
|
||||
/// Wake Event timer fired, used for wake WLAN test mode
|
||||
WAKE_EVENT = 76,
|
||||
/// Radio measurement complete
|
||||
RM_COMPLETE = 77,
|
||||
/// Synchronize TSF with the host
|
||||
HTSFSYNC = 78,
|
||||
/// request an overlay IOCTL/iovar from the host
|
||||
OVERLAY_REQ = 79,
|
||||
CSA_COMPLETE_IND = 80,
|
||||
/// excess PM Wake Event to inform host
|
||||
EXCESS_PM_WAKE_EVENT = 81,
|
||||
/// no PFN networks around
|
||||
PFN_SCAN_NONE = 82,
|
||||
/// last found PFN network gets lost
|
||||
PFN_SCAN_ALLGONE = 83,
|
||||
GTK_PLUMBED = 84,
|
||||
/// 802.11 ASSOC indication for NDIS only
|
||||
ASSOC_IND_NDIS = 85,
|
||||
/// 802.11 REASSOC indication for NDIS only
|
||||
REASSOC_IND_NDIS = 86,
|
||||
ASSOC_REQ_IE = 87,
|
||||
ASSOC_RESP_IE = 88,
|
||||
/// association recreated on resume
|
||||
ASSOC_RECREATED = 89,
|
||||
/// rx action frame event for NDIS only
|
||||
ACTION_FRAME_RX_NDIS = 90,
|
||||
/// authentication request received
|
||||
AUTH_REQ = 91,
|
||||
/// fast assoc recreation failed
|
||||
SPEEDY_RECREATE_FAIL = 93,
|
||||
/// port-specific event and payload (e.g. NDIS)
|
||||
NATIVE = 94,
|
||||
/// event for tx pkt delay suddently jump
|
||||
PKTDELAY_IND = 95,
|
||||
/// AWDL AW period starts
|
||||
AWDL_AW = 96,
|
||||
/// AWDL Master/Slave/NE master role event
|
||||
AWDL_ROLE = 97,
|
||||
/// Generic AWDL event
|
||||
AWDL_EVENT = 98,
|
||||
/// NIC AF txstatus
|
||||
NIC_AF_TXS = 99,
|
||||
/// NAN event
|
||||
NAN = 100,
|
||||
BEACON_FRAME_RX = 101,
|
||||
/// desired service found
|
||||
SERVICE_FOUND = 102,
|
||||
/// GAS fragment received
|
||||
GAS_FRAGMENT_RX = 103,
|
||||
/// GAS sessions all complete
|
||||
GAS_COMPLETE = 104,
|
||||
/// New device found by p2p offload
|
||||
P2PO_ADD_DEVICE = 105,
|
||||
/// device has been removed by p2p offload
|
||||
P2PO_DEL_DEVICE = 106,
|
||||
/// WNM event to notify STA enter sleep mode
|
||||
WNM_STA_SLEEP = 107,
|
||||
/// Indication of MAC tx failures (exhaustion of 802.11 retries) exceeding threshold(s)
|
||||
TXFAIL_THRESH = 108,
|
||||
/// Proximity Detection event
|
||||
PROXD = 109,
|
||||
/// AWDL RX Probe response
|
||||
AWDL_RX_PRB_RESP = 111,
|
||||
/// AWDL RX Action Frames
|
||||
AWDL_RX_ACT_FRAME = 112,
|
||||
/// AWDL Wowl nulls
|
||||
AWDL_WOWL_NULLPKT = 113,
|
||||
/// AWDL Phycal status
|
||||
AWDL_PHYCAL_STATUS = 114,
|
||||
/// AWDL OOB AF status
|
||||
AWDL_OOB_AF_STATUS = 115,
|
||||
/// Interleaved Scan status
|
||||
AWDL_SCAN_STATUS = 116,
|
||||
/// AWDL AW Start
|
||||
AWDL_AW_START = 117,
|
||||
/// AWDL AW End
|
||||
AWDL_AW_END = 118,
|
||||
/// AWDL AW Extensions
|
||||
AWDL_AW_EXT = 119,
|
||||
AWDL_PEER_CACHE_CONTROL = 120,
|
||||
CSA_START_IND = 121,
|
||||
CSA_DONE_IND = 122,
|
||||
CSA_FAILURE_IND = 123,
|
||||
/// CCA based channel quality report
|
||||
CCA_CHAN_QUAL = 124,
|
||||
/// to report change in BSSID while roaming
|
||||
BSSID = 125,
|
||||
/// tx error indication
|
||||
TX_STAT_ERROR = 126,
|
||||
/// credit check for BCMC supported
|
||||
BCMC_CREDIT_SUPPORT = 127,
|
||||
/// psta primary interface indication
|
||||
PSTA_PRIMARY_INTF_IND = 128,
|
||||
/// Handover Request Initiated
|
||||
BT_WIFI_HANDOVER_REQ = 130,
|
||||
/// Southpaw TxInhibit notification
|
||||
SPW_TXINHIBIT = 131,
|
||||
/// FBT Authentication Request Indication
|
||||
FBT_AUTH_REQ_IND = 132,
|
||||
/// Enhancement addition for RSSI
|
||||
RSSI_LQM = 133,
|
||||
/// Full probe/beacon (IEs etc) results
|
||||
PFN_GSCAN_FULL_RESULT = 134,
|
||||
/// Significant change in rssi of bssids being tracked
|
||||
PFN_SWC = 135,
|
||||
/// a STA been authroized for traffic
|
||||
AUTHORIZED = 136,
|
||||
/// probe req with wl_event_rx_frame_data_t header
|
||||
PROBREQ_MSG_RX = 137,
|
||||
/// PFN completed scan of network list
|
||||
PFN_SCAN_COMPLETE = 138,
|
||||
/// RMC Event
|
||||
RMC_EVENT = 139,
|
||||
/// DPSTA interface indication
|
||||
DPSTA_INTF_IND = 140,
|
||||
/// RRM Event
|
||||
RRM = 141,
|
||||
/// ULP entry event
|
||||
ULP = 146,
|
||||
/// TCP Keep Alive Offload Event
|
||||
TKO = 151,
|
||||
/// authentication request received
|
||||
EXT_AUTH_REQ = 187,
|
||||
/// authentication request received
|
||||
EXT_AUTH_FRAME_RX = 188,
|
||||
/// mgmt frame Tx complete
|
||||
MGMT_FRAME_TXSTATUS = 189,
|
||||
/// highest val + 1 for range checking
|
||||
LAST = 190,
|
||||
}
|
||||
|
||||
// TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient.
|
||||
pub type EventQueue = PubSubChannel<NoopRawMutex, Message, 2, 1, 1>;
|
||||
pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>;
|
||||
|
||||
pub struct Events {
|
||||
pub queue: EventQueue,
|
||||
pub mask: SharedEventMask,
|
||||
}
|
||||
|
||||
impl Events {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue: EventQueue::new(),
|
||||
mask: SharedEventMask::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Status {
|
||||
pub event_type: Event,
|
||||
pub status: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Payload {
|
||||
None,
|
||||
BssInfo(BssInfo),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
pub struct Message {
|
||||
pub header: Status,
|
||||
pub payload: Payload,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new(status: Status, payload: Payload) -> Self {
|
||||
Self {
|
||||
header: status,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EventMask {
|
||||
mask: [u32; Self::WORD_COUNT],
|
||||
}
|
||||
|
||||
impl EventMask {
|
||||
const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize;
|
||||
|
||||
fn enable(&mut self, event: Event) {
|
||||
let n = event as u32;
|
||||
let word = n / u32::BITS;
|
||||
let bit = n % u32::BITS;
|
||||
|
||||
self.mask[word as usize] |= 1 << bit;
|
||||
}
|
||||
|
||||
fn disable(&mut self, event: Event) {
|
||||
let n = event as u32;
|
||||
let word = n / u32::BITS;
|
||||
let bit = n % u32::BITS;
|
||||
|
||||
self.mask[word as usize] &= !(1 << bit);
|
||||
}
|
||||
|
||||
fn is_enabled(&self, event: Event) -> bool {
|
||||
let n = event as u32;
|
||||
let word = n / u32::BITS;
|
||||
let bit = n % u32::BITS;
|
||||
|
||||
self.mask[word as usize] & (1 << bit) > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
pub struct SharedEventMask {
|
||||
mask: RefCell<EventMask>,
|
||||
}
|
||||
|
||||
impl SharedEventMask {
|
||||
pub fn enable(&self, events: &[Event]) {
|
||||
let mut mask = self.mask.borrow_mut();
|
||||
for event in events {
|
||||
mask.enable(*event);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn disable(&self, events: &[Event]) {
|
||||
let mut mask = self.mask.borrow_mut();
|
||||
for event in events {
|
||||
mask.disable(*event);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_all(&self) {
|
||||
let mut mask = self.mask.borrow_mut();
|
||||
mask.mask = Default::default();
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self, event: Event) -> bool {
|
||||
let mask = self.mask.borrow();
|
||||
mask.is_enabled(event)
|
||||
}
|
||||
}
|
254
cyw43/src/fmt.rs
Normal file
254
cyw43/src/fmt.rs
Normal file
@ -0,0 +1,254 @@
|
||||
#![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)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
126
cyw43/src/ioctl.rs
Normal file
126
cyw43/src/ioctl.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use core::cell::{Cell, RefCell};
|
||||
use core::future::poll_fn;
|
||||
use core::task::{Poll, Waker};
|
||||
|
||||
use embassy_sync::waitqueue::WakerRegistration;
|
||||
|
||||
use crate::fmt::Bytes;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IoctlType {
|
||||
Get = 0,
|
||||
Set = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PendingIoctl {
|
||||
pub buf: *mut [u8],
|
||||
pub kind: IoctlType,
|
||||
pub cmd: u32,
|
||||
pub iface: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum IoctlStateInner {
|
||||
Pending(PendingIoctl),
|
||||
Sent { buf: *mut [u8] },
|
||||
Done { resp_len: usize },
|
||||
}
|
||||
|
||||
struct Wakers {
|
||||
control: WakerRegistration,
|
||||
runner: WakerRegistration,
|
||||
}
|
||||
|
||||
impl Default for Wakers {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
control: WakerRegistration::new(),
|
||||
runner: WakerRegistration::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IoctlState {
|
||||
state: Cell<IoctlStateInner>,
|
||||
wakers: RefCell<Wakers>,
|
||||
}
|
||||
|
||||
impl IoctlState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: Cell::new(IoctlStateInner::Done { resp_len: 0 }),
|
||||
wakers: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wake_control(&self) {
|
||||
self.wakers.borrow_mut().control.wake();
|
||||
}
|
||||
|
||||
fn register_control(&self, waker: &Waker) {
|
||||
self.wakers.borrow_mut().control.register(waker);
|
||||
}
|
||||
|
||||
fn wake_runner(&self) {
|
||||
self.wakers.borrow_mut().runner.wake();
|
||||
}
|
||||
|
||||
fn register_runner(&self, waker: &Waker) {
|
||||
self.wakers.borrow_mut().runner.register(waker);
|
||||
}
|
||||
|
||||
pub async fn wait_complete(&self) -> usize {
|
||||
poll_fn(|cx| {
|
||||
if let IoctlStateInner::Done { resp_len } = self.state.get() {
|
||||
Poll::Ready(resp_len)
|
||||
} else {
|
||||
self.register_control(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn wait_pending(&self) -> PendingIoctl {
|
||||
let pending = poll_fn(|cx| {
|
||||
if let IoctlStateInner::Pending(pending) = self.state.get() {
|
||||
Poll::Ready(pending)
|
||||
} else {
|
||||
self.register_runner(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
self.state.set(IoctlStateInner::Sent { buf: pending.buf });
|
||||
pending
|
||||
}
|
||||
|
||||
pub fn cancel_ioctl(&self) {
|
||||
self.state.set(IoctlStateInner::Done { resp_len: 0 });
|
||||
}
|
||||
|
||||
pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
|
||||
self.state
|
||||
.set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface }));
|
||||
self.wake_runner();
|
||||
self.wait_complete().await
|
||||
}
|
||||
|
||||
pub fn ioctl_done(&self, response: &[u8]) {
|
||||
if let IoctlStateInner::Sent { buf } = self.state.get() {
|
||||
trace!("IOCTL Response: {:02x}", Bytes(response));
|
||||
|
||||
// TODO fix this
|
||||
(unsafe { &mut *buf }[..response.len()]).copy_from_slice(response);
|
||||
|
||||
self.state.set(IoctlStateInner::Done {
|
||||
resp_len: response.len(),
|
||||
});
|
||||
self.wake_control();
|
||||
} else {
|
||||
warn!("IOCTL Response but no pending Ioctl");
|
||||
}
|
||||
}
|
||||
}
|
236
cyw43/src/lib.rs
Normal file
236
cyw43/src/lib.rs
Normal file
@ -0,0 +1,236 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)]
|
||||
#![deny(unused_must_use)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
mod bus;
|
||||
mod consts;
|
||||
mod countries;
|
||||
mod events;
|
||||
mod ioctl;
|
||||
mod structs;
|
||||
|
||||
mod control;
|
||||
mod nvram;
|
||||
mod runner;
|
||||
|
||||
use core::slice;
|
||||
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
use events::Events;
|
||||
use ioctl::IoctlState;
|
||||
|
||||
use crate::bus::Bus;
|
||||
pub use crate::bus::SpiBusCyw43;
|
||||
pub use crate::control::{Control, Error as ControlError};
|
||||
pub use crate::runner::Runner;
|
||||
pub use crate::structs::BssInfo;
|
||||
|
||||
const MTU: usize = 1514;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Core {
|
||||
WLAN = 0,
|
||||
SOCSRAM = 1,
|
||||
SDIOD = 2,
|
||||
}
|
||||
|
||||
impl Core {
|
||||
fn base_addr(&self) -> u32 {
|
||||
match self {
|
||||
Self::WLAN => CHIP.arm_core_base_address,
|
||||
Self::SOCSRAM => CHIP.socsram_wrapper_base_address,
|
||||
Self::SDIOD => CHIP.sdiod_core_base_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
struct Chip {
|
||||
arm_core_base_address: u32,
|
||||
socsram_base_address: u32,
|
||||
socsram_wrapper_base_address: u32,
|
||||
sdiod_core_base_address: u32,
|
||||
pmu_base_address: u32,
|
||||
chip_ram_size: u32,
|
||||
atcm_ram_base_address: u32,
|
||||
socram_srmem_size: u32,
|
||||
chanspec_band_mask: u32,
|
||||
chanspec_band_2g: u32,
|
||||
chanspec_band_5g: u32,
|
||||
chanspec_band_shift: u32,
|
||||
chanspec_bw_10: u32,
|
||||
chanspec_bw_20: u32,
|
||||
chanspec_bw_40: u32,
|
||||
chanspec_bw_mask: u32,
|
||||
chanspec_bw_shift: u32,
|
||||
chanspec_ctl_sb_lower: u32,
|
||||
chanspec_ctl_sb_upper: u32,
|
||||
chanspec_ctl_sb_none: u32,
|
||||
chanspec_ctl_sb_mask: u32,
|
||||
}
|
||||
|
||||
const WRAPPER_REGISTER_OFFSET: u32 = 0x100000;
|
||||
|
||||
// Data for CYW43439
|
||||
const CHIP: Chip = Chip {
|
||||
arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET,
|
||||
socsram_base_address: 0x18004000,
|
||||
socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET,
|
||||
sdiod_core_base_address: 0x18002000,
|
||||
pmu_base_address: 0x18000000,
|
||||
chip_ram_size: 512 * 1024,
|
||||
atcm_ram_base_address: 0,
|
||||
socram_srmem_size: 64 * 1024,
|
||||
chanspec_band_mask: 0xc000,
|
||||
chanspec_band_2g: 0x0000,
|
||||
chanspec_band_5g: 0xc000,
|
||||
chanspec_band_shift: 14,
|
||||
chanspec_bw_10: 0x0800,
|
||||
chanspec_bw_20: 0x1000,
|
||||
chanspec_bw_40: 0x1800,
|
||||
chanspec_bw_mask: 0x3800,
|
||||
chanspec_bw_shift: 11,
|
||||
chanspec_ctl_sb_lower: 0x0000,
|
||||
chanspec_ctl_sb_upper: 0x0100,
|
||||
chanspec_ctl_sb_none: 0x0000,
|
||||
chanspec_ctl_sb_mask: 0x0700,
|
||||
};
|
||||
|
||||
pub struct State {
|
||||
ioctl_state: IoctlState,
|
||||
ch: ch::State<MTU, 4, 4>,
|
||||
events: Events,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ioctl_state: IoctlState::new(),
|
||||
ch: ch::State::new(),
|
||||
events: Events::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PowerManagementMode {
|
||||
/// Custom, officially unsupported mode. Use at your own risk.
|
||||
/// All power-saving features set to their max at only a marginal decrease in power consumption
|
||||
/// as oppposed to `Aggressive`.
|
||||
SuperSave,
|
||||
|
||||
/// Aggressive power saving mode.
|
||||
Aggressive,
|
||||
|
||||
/// The default mode.
|
||||
PowerSave,
|
||||
|
||||
/// Performance is prefered over power consumption but still some power is conserved as opposed to
|
||||
/// `None`.
|
||||
Performance,
|
||||
|
||||
/// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of
|
||||
/// a much lower throughput.
|
||||
ThroughputThrottling,
|
||||
|
||||
/// No power management is configured. This consumes the most power.
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for PowerManagementMode {
|
||||
fn default() -> Self {
|
||||
Self::PowerSave
|
||||
}
|
||||
}
|
||||
|
||||
impl PowerManagementMode {
|
||||
fn sleep_ret_ms(&self) -> u16 {
|
||||
match self {
|
||||
PowerManagementMode::SuperSave => 2000,
|
||||
PowerManagementMode::Aggressive => 2000,
|
||||
PowerManagementMode::PowerSave => 200,
|
||||
PowerManagementMode::Performance => 20,
|
||||
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
|
||||
PowerManagementMode::None => 0, // value doesn't matter
|
||||
}
|
||||
}
|
||||
|
||||
fn beacon_period(&self) -> u8 {
|
||||
match self {
|
||||
PowerManagementMode::SuperSave => 255,
|
||||
PowerManagementMode::Aggressive => 1,
|
||||
PowerManagementMode::PowerSave => 1,
|
||||
PowerManagementMode::Performance => 1,
|
||||
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
|
||||
PowerManagementMode::None => 0, // value doesn't matter
|
||||
}
|
||||
}
|
||||
|
||||
fn dtim_period(&self) -> u8 {
|
||||
match self {
|
||||
PowerManagementMode::SuperSave => 255,
|
||||
PowerManagementMode::Aggressive => 1,
|
||||
PowerManagementMode::PowerSave => 1,
|
||||
PowerManagementMode::Performance => 1,
|
||||
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
|
||||
PowerManagementMode::None => 0, // value doesn't matter
|
||||
}
|
||||
}
|
||||
|
||||
fn assoc(&self) -> u8 {
|
||||
match self {
|
||||
PowerManagementMode::SuperSave => 255,
|
||||
PowerManagementMode::Aggressive => 10,
|
||||
PowerManagementMode::PowerSave => 10,
|
||||
PowerManagementMode::Performance => 1,
|
||||
PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter
|
||||
PowerManagementMode::None => 0, // value doesn't matter
|
||||
}
|
||||
}
|
||||
|
||||
fn mode(&self) -> u32 {
|
||||
match self {
|
||||
PowerManagementMode::ThroughputThrottling => 1,
|
||||
PowerManagementMode::None => 0,
|
||||
_ => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type NetDriver<'a> = ch::Device<'a, MTU>;
|
||||
|
||||
pub async fn new<'a, PWR, SPI>(
|
||||
state: &'a mut State,
|
||||
pwr: PWR,
|
||||
spi: SPI,
|
||||
firmware: &[u8],
|
||||
) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>)
|
||||
where
|
||||
PWR: OutputPin,
|
||||
SPI: SpiBusCyw43,
|
||||
{
|
||||
let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]);
|
||||
let state_ch = ch_runner.state_runner();
|
||||
|
||||
let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events);
|
||||
|
||||
runner.init(firmware).await;
|
||||
|
||||
(
|
||||
device,
|
||||
Control::new(state_ch, &state.events, &state.ioctl_state),
|
||||
runner,
|
||||
)
|
||||
}
|
||||
|
||||
fn slice8_mut(x: &mut [u32]) -> &mut [u8] {
|
||||
let len = x.len() * 4;
|
||||
unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) }
|
||||
}
|
54
cyw43/src/nvram.rs
Normal file
54
cyw43/src/nvram.rs
Normal file
@ -0,0 +1,54 @@
|
||||
macro_rules! nvram {
|
||||
($($s:literal,)*) => {
|
||||
concat_bytes!($($s, b"\x00",)* b"\x00\x00")
|
||||
};
|
||||
}
|
||||
|
||||
pub static NVRAM: &'static [u8] = &*nvram!(
|
||||
b"NVRAMRev=$Rev$",
|
||||
b"manfid=0x2d0",
|
||||
b"prodid=0x0727",
|
||||
b"vendid=0x14e4",
|
||||
b"devid=0x43e2",
|
||||
b"boardtype=0x0887",
|
||||
b"boardrev=0x1100",
|
||||
b"boardnum=22",
|
||||
b"macaddr=00:A0:50:b5:59:5e",
|
||||
b"sromrev=11",
|
||||
b"boardflags=0x00404001",
|
||||
b"boardflags3=0x04000000",
|
||||
b"xtalfreq=37400",
|
||||
b"nocrc=1",
|
||||
b"ag0=255",
|
||||
b"aa2g=1",
|
||||
b"ccode=ALL",
|
||||
b"pa0itssit=0x20",
|
||||
b"extpagain2g=0",
|
||||
b"pa2ga0=-168,6649,-778",
|
||||
b"AvVmid_c0=0x0,0xc8",
|
||||
b"cckpwroffset0=5",
|
||||
b"maxp2ga0=84",
|
||||
b"txpwrbckof=6",
|
||||
b"cckbw202gpo=0",
|
||||
b"legofdmbw202gpo=0x66111111",
|
||||
b"mcsbw202gpo=0x77711111",
|
||||
b"propbw202gpo=0xdd",
|
||||
b"ofdmdigfilttype=18",
|
||||
b"ofdmdigfilttypebe=18",
|
||||
b"papdmode=1",
|
||||
b"papdvalidtest=1",
|
||||
b"pacalidx2g=45",
|
||||
b"papdepsoffset=-30",
|
||||
b"papdendidx=58",
|
||||
b"ltecxmux=0",
|
||||
b"ltecxpadnum=0x0102",
|
||||
b"ltecxfnsel=0x44",
|
||||
b"ltecxgcigpio=0x01",
|
||||
b"il0macaddr=00:90:4c:c5:12:38",
|
||||
b"wl0id=0x431b",
|
||||
b"deadman_to=0xffffffff",
|
||||
b"muxenab=0x100",
|
||||
b"spurconfig=0x3",
|
||||
b"glitch_based_crsmin=1",
|
||||
b"btc_mode=1",
|
||||
);
|
575
cyw43/src/runner.rs
Normal file
575
cyw43/src/runner.rs
Normal file
@ -0,0 +1,575 @@
|
||||
use embassy_futures::select::{select3, Either3};
|
||||
use embassy_net_driver_channel as ch;
|
||||
use embassy_sync::pubsub::PubSubBehavior;
|
||||
use embassy_time::{block_for, Duration, Timer};
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
|
||||
use crate::bus::Bus;
|
||||
pub use crate::bus::SpiBusCyw43;
|
||||
use crate::consts::*;
|
||||
use crate::events::{Event, Events, Status};
|
||||
use crate::fmt::Bytes;
|
||||
use crate::ioctl::{IoctlState, IoctlType, PendingIoctl};
|
||||
use crate::nvram::NVRAM;
|
||||
use crate::structs::*;
|
||||
use crate::{events, slice8_mut, Core, CHIP, MTU};
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
struct LogState {
|
||||
addr: u32,
|
||||
last_idx: usize,
|
||||
buf: [u8; 256],
|
||||
buf_count: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
impl Default for LogState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
addr: Default::default(),
|
||||
last_idx: Default::default(),
|
||||
buf: [0; 256],
|
||||
buf_count: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Runner<'a, PWR, SPI> {
|
||||
ch: ch::Runner<'a, MTU>,
|
||||
bus: Bus<PWR, SPI>,
|
||||
|
||||
ioctl_state: &'a IoctlState,
|
||||
ioctl_id: u16,
|
||||
sdpcm_seq: u8,
|
||||
sdpcm_seq_max: u8,
|
||||
|
||||
events: &'a Events,
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
log: LogState,
|
||||
}
|
||||
|
||||
impl<'a, PWR, SPI> Runner<'a, PWR, SPI>
|
||||
where
|
||||
PWR: OutputPin,
|
||||
SPI: SpiBusCyw43,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
ch: ch::Runner<'a, MTU>,
|
||||
bus: Bus<PWR, SPI>,
|
||||
ioctl_state: &'a IoctlState,
|
||||
events: &'a Events,
|
||||
) -> Self {
|
||||
Self {
|
||||
ch,
|
||||
bus,
|
||||
ioctl_state,
|
||||
ioctl_id: 0,
|
||||
sdpcm_seq: 0,
|
||||
sdpcm_seq_max: 1,
|
||||
events,
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
log: LogState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn init(&mut self, firmware: &[u8]) {
|
||||
self.bus.init().await;
|
||||
|
||||
// Init ALP (Active Low Power) clock
|
||||
self.bus
|
||||
.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ)
|
||||
.await;
|
||||
debug!("waiting for clock...");
|
||||
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {}
|
||||
debug!("clock ok");
|
||||
|
||||
let chip_id = self.bus.bp_read16(0x1800_0000).await;
|
||||
debug!("chip ID: {}", chip_id);
|
||||
|
||||
// Upload firmware.
|
||||
self.core_disable(Core::WLAN).await;
|
||||
self.core_reset(Core::SOCSRAM).await;
|
||||
self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await;
|
||||
self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await;
|
||||
|
||||
let ram_addr = CHIP.atcm_ram_base_address;
|
||||
|
||||
debug!("loading fw");
|
||||
self.bus.bp_write(ram_addr, firmware).await;
|
||||
|
||||
debug!("loading nvram");
|
||||
// Round up to 4 bytes.
|
||||
let nvram_len = (NVRAM.len() + 3) / 4 * 4;
|
||||
self.bus
|
||||
.bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM)
|
||||
.await;
|
||||
|
||||
let nvram_len_words = nvram_len as u32 / 4;
|
||||
let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words;
|
||||
self.bus
|
||||
.bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic)
|
||||
.await;
|
||||
|
||||
// Start core!
|
||||
debug!("starting up core...");
|
||||
self.core_reset(Core::WLAN).await;
|
||||
assert!(self.core_is_up(Core::WLAN).await);
|
||||
|
||||
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {}
|
||||
|
||||
// "Set up the interrupt mask and enable interrupts"
|
||||
// self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await;
|
||||
|
||||
self.bus
|
||||
.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE)
|
||||
.await;
|
||||
|
||||
// "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped."
|
||||
// Sounds scary...
|
||||
self.bus
|
||||
.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32)
|
||||
.await;
|
||||
|
||||
// wait for wifi startup
|
||||
debug!("waiting for wifi init...");
|
||||
while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {}
|
||||
|
||||
// Some random configs related to sleep.
|
||||
// These aren't needed if we don't want to sleep the bus.
|
||||
// TODO do we need to sleep the bus to read the irq line, due to
|
||||
// being on the same pin as MOSI/MISO?
|
||||
|
||||
/*
|
||||
let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await;
|
||||
val |= 0x02; // WAKE_TILL_HT_AVAIL
|
||||
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await;
|
||||
self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1
|
||||
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT
|
||||
|
||||
let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await;
|
||||
val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON
|
||||
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await;
|
||||
*/
|
||||
|
||||
// clear pulls
|
||||
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await;
|
||||
let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await;
|
||||
|
||||
// start HT clock
|
||||
//self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await;
|
||||
//debug!("waiting for HT clock...");
|
||||
//while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {}
|
||||
//debug!("clock ok");
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
self.log_init().await;
|
||||
|
||||
debug!("wifi init done");
|
||||
}
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
async fn log_init(&mut self) {
|
||||
// Initialize shared memory for logging.
|
||||
|
||||
let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size;
|
||||
let shared_addr = self.bus.bp_read32(addr).await;
|
||||
debug!("shared_addr {:08x}", shared_addr);
|
||||
|
||||
let mut shared = [0; SharedMemData::SIZE];
|
||||
self.bus.bp_read(shared_addr, &mut shared).await;
|
||||
let shared = SharedMemData::from_bytes(&shared);
|
||||
|
||||
self.log.addr = shared.console_addr + 8;
|
||||
}
|
||||
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
async fn log_read(&mut self) {
|
||||
// Read log struct
|
||||
let mut log = [0; SharedMemLog::SIZE];
|
||||
self.bus.bp_read(self.log.addr, &mut log).await;
|
||||
let log = SharedMemLog::from_bytes(&log);
|
||||
|
||||
let idx = log.idx as usize;
|
||||
|
||||
// If pointer hasn't moved, no need to do anything.
|
||||
if idx == self.log.last_idx {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read entire buf for now. We could read only what we need, but then we
|
||||
// run into annoying alignment issues in `bp_read`.
|
||||
let mut buf = [0; 0x400];
|
||||
self.bus.bp_read(log.buf, &mut buf).await;
|
||||
|
||||
while self.log.last_idx != idx as usize {
|
||||
let b = buf[self.log.last_idx];
|
||||
if b == b'\r' || b == b'\n' {
|
||||
if self.log.buf_count != 0 {
|
||||
let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) };
|
||||
debug!("LOGS: {}", s);
|
||||
self.log.buf_count = 0;
|
||||
}
|
||||
} else if self.log.buf_count < self.log.buf.len() {
|
||||
self.log.buf[self.log.buf_count] = b;
|
||||
self.log.buf_count += 1;
|
||||
}
|
||||
|
||||
self.log.last_idx += 1;
|
||||
if self.log.last_idx == 0x400 {
|
||||
self.log.last_idx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(mut self) -> ! {
|
||||
let mut buf = [0; 512];
|
||||
loop {
|
||||
#[cfg(feature = "firmware-logs")]
|
||||
self.log_read().await;
|
||||
|
||||
if self.has_credit() {
|
||||
let ioctl = self.ioctl_state.wait_pending();
|
||||
let tx = self.ch.tx_buf();
|
||||
let ev = self.bus.wait_for_event();
|
||||
|
||||
match select3(ioctl, tx, ev).await {
|
||||
Either3::First(PendingIoctl {
|
||||
buf: iobuf,
|
||||
kind,
|
||||
cmd,
|
||||
iface,
|
||||
}) => {
|
||||
self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await;
|
||||
self.check_status(&mut buf).await;
|
||||
}
|
||||
Either3::Second(packet) => {
|
||||
trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
|
||||
|
||||
let mut buf = [0; 512];
|
||||
let buf8 = slice8_mut(&mut buf);
|
||||
|
||||
// There MUST be 2 bytes of padding between the SDPCM and BDC headers.
|
||||
// And ONLY for data packets!
|
||||
// No idea why, but the firmware will append two zero bytes to the tx'd packets
|
||||
// otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it
|
||||
// be oversized and get dropped.
|
||||
// WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90
|
||||
// and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597
|
||||
// ¯\_(ツ)_/¯
|
||||
const PADDING_SIZE: usize = 2;
|
||||
let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len();
|
||||
|
||||
let seq = self.sdpcm_seq;
|
||||
self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1);
|
||||
|
||||
let sdpcm_header = SdpcmHeader {
|
||||
len: total_len as u16, // TODO does this len need to be rounded up to u32?
|
||||
len_inv: !total_len as u16,
|
||||
sequence: seq,
|
||||
channel_and_flags: CHANNEL_TYPE_DATA,
|
||||
next_length: 0,
|
||||
header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _,
|
||||
wireless_flow_control: 0,
|
||||
bus_data_credit: 0,
|
||||
reserved: [0, 0],
|
||||
};
|
||||
|
||||
let bdc_header = BdcHeader {
|
||||
flags: BDC_VERSION << BDC_VERSION_SHIFT,
|
||||
priority: 0,
|
||||
flags2: 0,
|
||||
data_offset: 0,
|
||||
};
|
||||
trace!("tx {:?}", sdpcm_header);
|
||||
trace!(" {:?}", bdc_header);
|
||||
|
||||
buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes());
|
||||
buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE]
|
||||
.copy_from_slice(&bdc_header.to_bytes());
|
||||
buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()]
|
||||
.copy_from_slice(packet);
|
||||
|
||||
let total_len = (total_len + 3) & !3; // round up to 4byte
|
||||
|
||||
trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)]));
|
||||
|
||||
self.bus.wlan_write(&buf[..(total_len / 4)]).await;
|
||||
self.ch.tx_done();
|
||||
self.check_status(&mut buf).await;
|
||||
}
|
||||
Either3::Third(()) => {
|
||||
self.handle_irq(&mut buf).await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("TX stalled");
|
||||
self.bus.wait_for_event().await;
|
||||
self.handle_irq(&mut buf).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for IRQ on F2 packet available
|
||||
async fn handle_irq(&mut self, buf: &mut [u32; 512]) {
|
||||
// Receive stuff
|
||||
let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await;
|
||||
trace!("irq{}", FormatInterrupt(irq));
|
||||
|
||||
if irq & IRQ_F2_PACKET_AVAILABLE != 0 {
|
||||
self.check_status(buf).await;
|
||||
}
|
||||
|
||||
if irq & IRQ_DATA_UNAVAILABLE != 0 {
|
||||
// TODO what should we do here?
|
||||
warn!("IRQ DATA_UNAVAILABLE, clearing...");
|
||||
self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle F2 events while status register is set
|
||||
async fn check_status(&mut self, buf: &mut [u32; 512]) {
|
||||
loop {
|
||||
let status = self.bus.status();
|
||||
trace!("check status{}", FormatStatus(status));
|
||||
|
||||
if status & STATUS_F2_PKT_AVAILABLE != 0 {
|
||||
let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT;
|
||||
self.bus.wlan_read(buf, len).await;
|
||||
trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)]));
|
||||
self.rx(&mut slice8_mut(buf)[..len as usize]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rx(&mut self, packet: &mut [u8]) {
|
||||
let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { return };
|
||||
|
||||
self.update_credit(&sdpcm_header);
|
||||
|
||||
let channel = sdpcm_header.channel_and_flags & 0x0f;
|
||||
|
||||
match channel {
|
||||
CHANNEL_TYPE_CONTROL => {
|
||||
let Some((cdc_header, response)) = CdcHeader::parse(payload) else { return; };
|
||||
trace!(" {:?}", cdc_header);
|
||||
|
||||
if cdc_header.id == self.ioctl_id {
|
||||
if cdc_header.status != 0 {
|
||||
// TODO: propagate error instead
|
||||
panic!("IOCTL error {}", cdc_header.status as i32);
|
||||
}
|
||||
|
||||
self.ioctl_state.ioctl_done(response);
|
||||
}
|
||||
}
|
||||
CHANNEL_TYPE_EVENT => {
|
||||
let Some((_, bdc_packet)) = BdcHeader::parse(payload) else {
|
||||
warn!("BDC event, incomplete header");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else {
|
||||
warn!("BDC event, incomplete data");
|
||||
return;
|
||||
};
|
||||
|
||||
const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h
|
||||
if event_packet.eth.ether_type != ETH_P_LINK_CTL {
|
||||
warn!(
|
||||
"unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}",
|
||||
event_packet.eth.ether_type, ETH_P_LINK_CTL
|
||||
);
|
||||
return;
|
||||
}
|
||||
const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18];
|
||||
if event_packet.hdr.oui != BROADCOM_OUI {
|
||||
warn!(
|
||||
"unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}",
|
||||
Bytes(&event_packet.hdr.oui),
|
||||
Bytes(BROADCOM_OUI)
|
||||
);
|
||||
return;
|
||||
}
|
||||
const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769;
|
||||
if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG {
|
||||
warn!("unexpected subtype {}", event_packet.hdr.subtype);
|
||||
return;
|
||||
}
|
||||
|
||||
const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1;
|
||||
if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT {
|
||||
warn!("unexpected user_subtype {}", event_packet.hdr.subtype);
|
||||
return;
|
||||
}
|
||||
|
||||
let evt_type = events::Event::from(event_packet.msg.event_type as u8);
|
||||
debug!(
|
||||
"=== EVENT {:?}: {:?} {:02x}",
|
||||
evt_type,
|
||||
event_packet.msg,
|
||||
Bytes(evt_data)
|
||||
);
|
||||
|
||||
if self.events.mask.is_enabled(evt_type) {
|
||||
let status = event_packet.msg.status;
|
||||
let event_payload = match evt_type {
|
||||
Event::ESCAN_RESULT if status == EStatus::PARTIAL => {
|
||||
let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return };
|
||||
let Some(bss_info) = BssInfo::parse(bss_info) else { return };
|
||||
events::Payload::BssInfo(*bss_info)
|
||||
}
|
||||
Event::ESCAN_RESULT => events::Payload::None,
|
||||
_ => events::Payload::None,
|
||||
};
|
||||
|
||||
// this intentionally uses the non-blocking publish immediate
|
||||
// publish() is a deadlock risk in the current design as awaiting here prevents ioctls
|
||||
// The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event
|
||||
// (if they are actively awaiting the queue)
|
||||
self.events.queue.publish_immediate(events::Message::new(
|
||||
Status {
|
||||
event_type: evt_type,
|
||||
status,
|
||||
},
|
||||
event_payload,
|
||||
));
|
||||
}
|
||||
}
|
||||
CHANNEL_TYPE_DATA => {
|
||||
let Some((_, packet)) = BdcHeader::parse(payload) else { return };
|
||||
trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
|
||||
|
||||
match self.ch.try_rx_buf() {
|
||||
Some(buf) => {
|
||||
buf[..packet.len()].copy_from_slice(packet);
|
||||
self.ch.rx_done(packet.len())
|
||||
}
|
||||
None => warn!("failed to push rxd packet to the channel."),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) {
|
||||
if sdpcm_header.channel_and_flags & 0xf < 3 {
|
||||
let mut sdpcm_seq_max = sdpcm_header.bus_data_credit;
|
||||
if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 {
|
||||
sdpcm_seq_max = self.sdpcm_seq + 2;
|
||||
}
|
||||
self.sdpcm_seq_max = sdpcm_seq_max;
|
||||
}
|
||||
}
|
||||
|
||||
fn has_credit(&self) -> bool {
|
||||
self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0
|
||||
}
|
||||
|
||||
async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) {
|
||||
let mut buf = [0; 512];
|
||||
let buf8 = slice8_mut(&mut buf);
|
||||
|
||||
let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len();
|
||||
|
||||
let sdpcm_seq = self.sdpcm_seq;
|
||||
self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1);
|
||||
self.ioctl_id = self.ioctl_id.wrapping_add(1);
|
||||
|
||||
let sdpcm_header = SdpcmHeader {
|
||||
len: total_len as u16, // TODO does this len need to be rounded up to u32?
|
||||
len_inv: !total_len as u16,
|
||||
sequence: sdpcm_seq,
|
||||
channel_and_flags: CHANNEL_TYPE_CONTROL,
|
||||
next_length: 0,
|
||||
header_length: SdpcmHeader::SIZE as _,
|
||||
wireless_flow_control: 0,
|
||||
bus_data_credit: 0,
|
||||
reserved: [0, 0],
|
||||
};
|
||||
|
||||
let cdc_header = CdcHeader {
|
||||
cmd: cmd,
|
||||
len: data.len() as _,
|
||||
flags: kind as u16 | (iface as u16) << 12,
|
||||
id: self.ioctl_id,
|
||||
status: 0,
|
||||
};
|
||||
trace!("tx {:?}", sdpcm_header);
|
||||
trace!(" {:?}", cdc_header);
|
||||
|
||||
buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes());
|
||||
buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes());
|
||||
buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data);
|
||||
|
||||
let total_len = (total_len + 3) & !3; // round up to 4byte
|
||||
|
||||
trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)]));
|
||||
|
||||
self.bus.wlan_write(&buf[..total_len / 4]).await;
|
||||
}
|
||||
|
||||
async fn core_disable(&mut self, core: Core) {
|
||||
let base = core.base_addr();
|
||||
|
||||
// Dummy read?
|
||||
let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
|
||||
|
||||
// Check it isn't already reset
|
||||
let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
|
||||
if r & AI_RESETCTRL_BIT_RESET != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await;
|
||||
let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
|
||||
|
||||
block_for(Duration::from_millis(1));
|
||||
|
||||
self.bus
|
||||
.bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET)
|
||||
.await;
|
||||
let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
|
||||
}
|
||||
|
||||
async fn core_reset(&mut self, core: Core) {
|
||||
self.core_disable(core).await;
|
||||
|
||||
let base = core.base_addr();
|
||||
self.bus
|
||||
.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN)
|
||||
.await;
|
||||
let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
|
||||
|
||||
self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await;
|
||||
|
||||
Timer::after(Duration::from_millis(1)).await;
|
||||
|
||||
self.bus
|
||||
.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN)
|
||||
.await;
|
||||
let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
|
||||
|
||||
Timer::after(Duration::from_millis(1)).await;
|
||||
}
|
||||
|
||||
async fn core_is_up(&mut self, core: Core) -> bool {
|
||||
let base = core.base_addr();
|
||||
|
||||
let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
|
||||
if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN {
|
||||
debug!("core_is_up: returning false due to bad ioctrl {:02x}", io);
|
||||
return false;
|
||||
}
|
||||
|
||||
let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
|
||||
if r & (AI_RESETCTRL_BIT_RESET) != 0 {
|
||||
debug!("core_is_up: returning false due to bad resetctrl {:02x}", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
496
cyw43/src/structs.rs
Normal file
496
cyw43/src/structs.rs
Normal file
@ -0,0 +1,496 @@
|
||||
use crate::events::Event;
|
||||
use crate::fmt::Bytes;
|
||||
|
||||
macro_rules! impl_bytes {
|
||||
($t:ident) => {
|
||||
impl $t {
|
||||
pub const SIZE: usize = core::mem::size_of::<Self>();
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
|
||||
unsafe { core::mem::transmute(*self) }
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self {
|
||||
let alignment = core::mem::align_of::<Self>();
|
||||
assert_eq!(
|
||||
bytes.as_ptr().align_offset(alignment),
|
||||
0,
|
||||
"{} is not aligned",
|
||||
core::any::type_name::<Self>()
|
||||
);
|
||||
unsafe { core::mem::transmute(bytes) }
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self {
|
||||
let alignment = core::mem::align_of::<Self>();
|
||||
assert_eq!(
|
||||
bytes.as_ptr().align_offset(alignment),
|
||||
0,
|
||||
"{} is not aligned",
|
||||
core::any::type_name::<Self>()
|
||||
);
|
||||
|
||||
unsafe { core::mem::transmute(bytes) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct SharedMemData {
|
||||
pub flags: u32,
|
||||
pub trap_addr: u32,
|
||||
pub assert_exp_addr: u32,
|
||||
pub assert_file_addr: u32,
|
||||
pub assert_line: u32,
|
||||
pub console_addr: u32,
|
||||
pub msgtrace_addr: u32,
|
||||
pub fwid: u32,
|
||||
}
|
||||
impl_bytes!(SharedMemData);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct SharedMemLog {
|
||||
pub buf: u32,
|
||||
pub buf_size: u32,
|
||||
pub idx: u32,
|
||||
pub out_idx: u32,
|
||||
}
|
||||
impl_bytes!(SharedMemLog);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct SdpcmHeader {
|
||||
pub len: u16,
|
||||
pub len_inv: u16,
|
||||
/// Rx/Tx sequence number
|
||||
pub sequence: u8,
|
||||
/// 4 MSB Channel number, 4 LSB arbitrary flag
|
||||
pub channel_and_flags: u8,
|
||||
/// Length of next data frame, reserved for Tx
|
||||
pub next_length: u8,
|
||||
/// Data offset
|
||||
pub header_length: u8,
|
||||
/// Flow control bits, reserved for Tx
|
||||
pub wireless_flow_control: u8,
|
||||
/// Maximum Sequence number allowed by firmware for Tx
|
||||
pub bus_data_credit: u8,
|
||||
/// Reserved
|
||||
pub reserved: [u8; 2],
|
||||
}
|
||||
impl_bytes!(SdpcmHeader);
|
||||
|
||||
impl SdpcmHeader {
|
||||
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
|
||||
let packet_len = packet.len();
|
||||
if packet_len < Self::SIZE {
|
||||
warn!("packet too short, len={}", packet.len());
|
||||
return None;
|
||||
}
|
||||
let (sdpcm_header, sdpcm_packet) = packet.split_at_mut(Self::SIZE);
|
||||
let sdpcm_header = Self::from_bytes_mut(sdpcm_header.try_into().unwrap());
|
||||
trace!("rx {:?}", sdpcm_header);
|
||||
|
||||
if sdpcm_header.len != !sdpcm_header.len_inv {
|
||||
warn!("len inv mismatch");
|
||||
return None;
|
||||
}
|
||||
|
||||
if sdpcm_header.len as usize != packet_len {
|
||||
warn!("len from header doesn't match len from spi");
|
||||
return None;
|
||||
}
|
||||
|
||||
let sdpcm_packet = &mut sdpcm_packet[(sdpcm_header.header_length as usize - Self::SIZE)..];
|
||||
Some((sdpcm_header, sdpcm_packet))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C, packed(2))]
|
||||
pub struct CdcHeader {
|
||||
pub cmd: u32,
|
||||
pub len: u32,
|
||||
pub flags: u16,
|
||||
pub id: u16,
|
||||
pub status: u32,
|
||||
}
|
||||
impl_bytes!(CdcHeader);
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for CdcHeader {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
fn copy<T: Copy>(t: T) -> T {
|
||||
t
|
||||
}
|
||||
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"CdcHeader{{cmd: {=u32:08x}, len: {=u32:08x}, flags: {=u16:04x}, id: {=u16:04x}, status: {=u32:08x}}}",
|
||||
copy(self.cmd),
|
||||
copy(self.len),
|
||||
copy(self.flags),
|
||||
copy(self.id),
|
||||
copy(self.status),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl CdcHeader {
|
||||
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
|
||||
if packet.len() < Self::SIZE {
|
||||
warn!("payload too short, len={}", packet.len());
|
||||
return None;
|
||||
}
|
||||
|
||||
let (cdc_header, payload) = packet.split_at_mut(Self::SIZE);
|
||||
let cdc_header = Self::from_bytes_mut(cdc_header.try_into().unwrap());
|
||||
|
||||
let payload = &mut payload[..cdc_header.len as usize];
|
||||
Some((cdc_header, payload))
|
||||
}
|
||||
}
|
||||
|
||||
pub const BDC_VERSION: u8 = 2;
|
||||
pub const BDC_VERSION_SHIFT: u8 = 4;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct BdcHeader {
|
||||
pub flags: u8,
|
||||
/// 802.1d Priority (low 3 bits)
|
||||
pub priority: u8,
|
||||
pub flags2: u8,
|
||||
/// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers.
|
||||
pub data_offset: u8,
|
||||
}
|
||||
impl_bytes!(BdcHeader);
|
||||
|
||||
impl BdcHeader {
|
||||
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
|
||||
if packet.len() < Self::SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (bdc_header, bdc_packet) = packet.split_at_mut(Self::SIZE);
|
||||
let bdc_header = Self::from_bytes_mut(bdc_header.try_into().unwrap());
|
||||
trace!(" {:?}", bdc_header);
|
||||
|
||||
let packet_start = 4 * bdc_header.data_offset as usize;
|
||||
|
||||
let bdc_packet = bdc_packet.get_mut(packet_start..)?;
|
||||
trace!(" {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)]));
|
||||
|
||||
Some((bdc_header, bdc_packet))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct EthernetHeader {
|
||||
pub destination_mac: [u8; 6],
|
||||
pub source_mac: [u8; 6],
|
||||
pub ether_type: u16,
|
||||
}
|
||||
|
||||
impl EthernetHeader {
|
||||
pub fn byteswap(&mut self) {
|
||||
self.ether_type = self.ether_type.to_be();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct EventHeader {
|
||||
pub subtype: u16,
|
||||
pub length: u16,
|
||||
pub version: u8,
|
||||
pub oui: [u8; 3],
|
||||
pub user_subtype: u16,
|
||||
}
|
||||
|
||||
impl EventHeader {
|
||||
pub fn byteswap(&mut self) {
|
||||
self.subtype = self.subtype.to_be();
|
||||
self.length = self.length.to_be();
|
||||
self.user_subtype = self.user_subtype.to_be();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
// #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C, packed(2))]
|
||||
pub struct EventMessage {
|
||||
/// version
|
||||
pub version: u16,
|
||||
/// see flags below
|
||||
pub flags: u16,
|
||||
/// Message (see below)
|
||||
pub event_type: u32,
|
||||
/// Status code (see below)
|
||||
pub status: u32,
|
||||
/// Reason code (if applicable)
|
||||
pub reason: u32,
|
||||
/// WLC_E_AUTH
|
||||
pub auth_type: u32,
|
||||
/// data buf
|
||||
pub datalen: u32,
|
||||
/// Station address (if applicable)
|
||||
pub addr: [u8; 6],
|
||||
/// name of the incoming packet interface
|
||||
pub ifname: [u8; 16],
|
||||
/// destination OS i/f index
|
||||
pub ifidx: u8,
|
||||
/// source bsscfg index
|
||||
pub bsscfgidx: u8,
|
||||
}
|
||||
impl_bytes!(EventMessage);
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for EventMessage {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
let event_type = self.event_type;
|
||||
let status = self.status;
|
||||
let reason = self.reason;
|
||||
let auth_type = self.auth_type;
|
||||
let datalen = self.datalen;
|
||||
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"EventMessage {{ \
|
||||
version: {=u16}, \
|
||||
flags: {=u16}, \
|
||||
event_type: {=u32}, \
|
||||
status: {=u32}, \
|
||||
reason: {=u32}, \
|
||||
auth_type: {=u32}, \
|
||||
datalen: {=u32}, \
|
||||
addr: {=[u8; 6]:x}, \
|
||||
ifname: {=[u8; 16]:x}, \
|
||||
ifidx: {=u8}, \
|
||||
bsscfgidx: {=u8}, \
|
||||
}} ",
|
||||
self.version,
|
||||
self.flags,
|
||||
event_type,
|
||||
status,
|
||||
reason,
|
||||
auth_type,
|
||||
datalen,
|
||||
self.addr,
|
||||
self.ifname,
|
||||
self.ifidx,
|
||||
self.bsscfgidx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl EventMessage {
|
||||
pub fn byteswap(&mut self) {
|
||||
self.version = self.version.to_be();
|
||||
self.flags = self.flags.to_be();
|
||||
self.event_type = self.event_type.to_be();
|
||||
self.status = self.status.to_be();
|
||||
self.reason = self.reason.to_be();
|
||||
self.auth_type = self.auth_type.to_be();
|
||||
self.datalen = self.datalen.to_be();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C, packed(2))]
|
||||
pub struct EventPacket {
|
||||
pub eth: EthernetHeader,
|
||||
pub hdr: EventHeader,
|
||||
pub msg: EventMessage,
|
||||
}
|
||||
impl_bytes!(EventPacket);
|
||||
|
||||
impl EventPacket {
|
||||
pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
|
||||
if packet.len() < Self::SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (event_header, event_packet) = packet.split_at_mut(Self::SIZE);
|
||||
let event_header = Self::from_bytes_mut(event_header.try_into().unwrap());
|
||||
// warn!("event_header {:x}", event_header as *const _);
|
||||
event_header.byteswap();
|
||||
|
||||
let event_packet = event_packet.get_mut(..event_header.msg.datalen as usize)?;
|
||||
|
||||
Some((event_header, event_packet))
|
||||
}
|
||||
|
||||
pub fn byteswap(&mut self) {
|
||||
self.eth.byteswap();
|
||||
self.hdr.byteswap();
|
||||
self.msg.byteswap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct DownloadHeader {
|
||||
pub flag: u16, //
|
||||
pub dload_type: u16,
|
||||
pub len: u32,
|
||||
pub crc: u32,
|
||||
}
|
||||
impl_bytes!(DownloadHeader);
|
||||
|
||||
#[allow(unused)]
|
||||
pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001;
|
||||
pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002;
|
||||
pub const DOWNLOAD_FLAG_END: u16 = 0x0004;
|
||||
pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000;
|
||||
|
||||
// Country Locale Matrix (CLM)
|
||||
pub const DOWNLOAD_TYPE_CLM: u16 = 2;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct CountryInfo {
|
||||
pub country_abbrev: [u8; 4],
|
||||
pub rev: i32,
|
||||
pub country_code: [u8; 4],
|
||||
}
|
||||
impl_bytes!(CountryInfo);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct SsidInfo {
|
||||
pub len: u32,
|
||||
pub ssid: [u8; 32],
|
||||
}
|
||||
impl_bytes!(SsidInfo);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct PassphraseInfo {
|
||||
pub len: u16,
|
||||
pub flags: u16,
|
||||
pub passphrase: [u8; 64],
|
||||
}
|
||||
impl_bytes!(PassphraseInfo);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct SsidInfoWithIndex {
|
||||
pub index: u32,
|
||||
pub ssid_info: SsidInfo,
|
||||
}
|
||||
impl_bytes!(SsidInfoWithIndex);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct EventMask {
|
||||
pub iface: u32,
|
||||
pub events: [u8; 24],
|
||||
}
|
||||
impl_bytes!(EventMask);
|
||||
|
||||
impl EventMask {
|
||||
pub fn unset(&mut self, evt: Event) {
|
||||
let evt = evt as u8 as usize;
|
||||
self.events[evt / 8] &= !(1 << (evt % 8));
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for a wifi scan
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C)]
|
||||
pub struct ScanParams {
|
||||
pub version: u32,
|
||||
pub action: u16,
|
||||
pub sync_id: u16,
|
||||
pub ssid_len: u32,
|
||||
pub ssid: [u8; 32],
|
||||
pub bssid: [u8; 6],
|
||||
pub bss_type: u8,
|
||||
pub scan_type: u8,
|
||||
pub nprobes: u32,
|
||||
pub active_time: u32,
|
||||
pub passive_time: u32,
|
||||
pub home_time: u32,
|
||||
pub channel_num: u32,
|
||||
pub channel_list: [u16; 1],
|
||||
}
|
||||
impl_bytes!(ScanParams);
|
||||
|
||||
/// Wifi Scan Results Header, followed by `bss_count` `BssInfo`
|
||||
#[derive(Clone, Copy)]
|
||||
// #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C, packed(2))]
|
||||
pub struct ScanResults {
|
||||
pub buflen: u32,
|
||||
pub version: u32,
|
||||
pub sync_id: u16,
|
||||
pub bss_count: u16,
|
||||
}
|
||||
impl_bytes!(ScanResults);
|
||||
|
||||
impl ScanResults {
|
||||
pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> {
|
||||
if packet.len() < ScanResults::SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE);
|
||||
let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap());
|
||||
|
||||
if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE {
|
||||
warn!("Scan result, incomplete BssInfo");
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((scan_results, bssinfo))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wifi Scan Result
|
||||
#[derive(Clone, Copy)]
|
||||
// #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(C, packed(2))]
|
||||
#[non_exhaustive]
|
||||
pub struct BssInfo {
|
||||
pub version: u32,
|
||||
pub length: u32,
|
||||
pub bssid: [u8; 6],
|
||||
pub beacon_period: u16,
|
||||
pub capability: u16,
|
||||
pub ssid_len: u8,
|
||||
pub ssid: [u8; 32],
|
||||
// there will be more stuff here
|
||||
}
|
||||
impl_bytes!(BssInfo);
|
||||
|
||||
impl BssInfo {
|
||||
pub fn parse(packet: &mut [u8]) -> Option<&mut Self> {
|
||||
if packet.len() < BssInfo::SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(BssInfo::from_bytes_mut(
|
||||
packet[..BssInfo::SIZE].as_mut().try_into().unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] }
|
||||
embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] }
|
||||
|
||||
|
@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0"
|
||||
[dependencies]
|
||||
cortex-m = "0.7"
|
||||
cortex-m-rt = "0.7"
|
||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"], default-features = false }
|
||||
embassy-executor = { version = "0.1.0", default-features = false, features = ["nightly", "arch-cortex-m", "executor-thread"] }
|
||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] }
|
||||
embassy-executor = { version = "0.2.0", features = ["nightly", "arch-cortex-m", "executor-thread"] }
|
||||
|
||||
defmt = "0.3.0"
|
||||
defmt-rtt = "0.3.0"
|
||||
|
@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
|
||||
[dependencies]
|
||||
cortex-m = "0.7"
|
||||
cortex-m-rt = "0.7"
|
||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"], default-features = false }
|
||||
embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] }
|
||||
|
||||
defmt = "0.3.0"
|
||||
defmt-rtt = "0.3.0"
|
||||
|
@ -20,13 +20,13 @@ fn main() -> ! {
|
||||
let led = Output::new(p.PB14, Level::Low, Speed::Low);
|
||||
let mut button = Input::new(p.PC13, Pull::Up);
|
||||
|
||||
cortex_m::interrupt::free(|cs| unsafe {
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
enable_interrupt(&mut button);
|
||||
|
||||
LED.borrow(cs).borrow_mut().replace(led);
|
||||
BUTTON.borrow(cs).borrow_mut().replace(button);
|
||||
|
||||
NVIC::unmask(pac::Interrupt::EXTI15_10);
|
||||
unsafe { NVIC::unmask(pac::Interrupt::EXTI15_10) };
|
||||
});
|
||||
|
||||
loop {
|
||||
@ -64,25 +64,21 @@ const PORT: u8 = 2;
|
||||
const PIN: usize = 13;
|
||||
fn check_interrupt<P: Pin>(_pin: &mut Input<'static, P>) -> bool {
|
||||
let exti = pac::EXTI;
|
||||
unsafe {
|
||||
let pin = PIN;
|
||||
let lines = exti.pr(0).read();
|
||||
lines.line(pin)
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_interrupt<P: Pin>(_pin: &mut Input<'static, P>) {
|
||||
let exti = pac::EXTI;
|
||||
unsafe {
|
||||
let pin = PIN;
|
||||
let mut lines = exti.pr(0).read();
|
||||
lines.set_line(pin, true);
|
||||
exti.pr(0).write_value(lines);
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_interrupt<P: Pin>(_pin: &mut Input<'static, P>) {
|
||||
cortex_m::interrupt::free(|_| unsafe {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
let rcc = pac::RCC;
|
||||
rcc.apb2enr().modify(|w| w.set_syscfgen(true));
|
||||
|
||||
|
@ -49,7 +49,7 @@ cd examples/nrf52840
|
||||
cargo run --bin blinky --release
|
||||
----
|
||||
|
||||
== Whats next?
|
||||
== What's next?
|
||||
|
||||
Congratulations, you have your first Embassy application running! Here are some alternatives on where to go from here:
|
||||
|
||||
|
@ -27,9 +27,10 @@ defmt = { version = "0.3", optional = true }
|
||||
digest = "0.10"
|
||||
log = { version = "0.4", optional = true }
|
||||
ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true }
|
||||
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
|
||||
embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true }
|
||||
signature = { version = "1.6.4", default-features = false }
|
||||
|
||||
@ -39,6 +40,7 @@ env_logger = "0.9"
|
||||
rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version
|
||||
futures = { version = "0.3", features = ["executor"] }
|
||||
sha1 = "0.10.5"
|
||||
critical-section = { version = "1.1.1", features = ["std"] }
|
||||
|
||||
[dev-dependencies.ed25519-dalek]
|
||||
default_features = false
|
||||
@ -48,5 +50,7 @@ features = ["rand", "std", "u32_backend"]
|
||||
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
|
||||
ed25519-salty = ["dep:salty", "_verify"]
|
||||
|
||||
nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"]
|
||||
|
||||
#Internal features
|
||||
_verify = []
|
||||
|
@ -13,11 +13,12 @@ By design, the bootloader does not provide any network capabilities. Networking
|
||||
The bootloader supports different hardware in separate crates:
|
||||
|
||||
* `embassy-boot-nrf` - for the nRF microcontrollers.
|
||||
* `embassy-boot-rp` - for the RP2040 microcontrollers.
|
||||
* `embassy-boot-stm32` - for the STM32 microcontrollers.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
|
||||
use core::cell::RefCell;
|
||||
|
||||
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
|
||||
use embassy_embedded_hal::flash::partition::BlockingPartition;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
|
||||
|
||||
use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
||||
|
||||
/// Errors returned by bootloader
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
@ -30,63 +35,96 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait defining the flash handles used for active and DFU partition.
|
||||
pub trait FlashConfig {
|
||||
/// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value.
|
||||
const STATE_ERASE_VALUE: u8 = 0xFF;
|
||||
/// Bootloader flash configuration holding the three flashes used by the bootloader
|
||||
///
|
||||
/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use.
|
||||
/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition
|
||||
/// the provided flash according to symbols defined in the linkerfile.
|
||||
pub struct BootLoaderConfig<ACTIVE, DFU, STATE> {
|
||||
/// Flash type used for the active partition - the partition which will be booted from.
|
||||
pub active: ACTIVE,
|
||||
/// Flash type used for the dfu partition - the partition which will be swapped in when requested.
|
||||
pub dfu: DFU,
|
||||
/// Flash type used for the state partition.
|
||||
type STATE: NorFlash;
|
||||
/// Flash type used for the active partition.
|
||||
type ACTIVE: NorFlash;
|
||||
/// Flash type used for the dfu partition.
|
||||
type DFU: NorFlash;
|
||||
|
||||
/// Return flash instance used to write/read to/from active partition.
|
||||
fn active(&mut self) -> &mut Self::ACTIVE;
|
||||
/// Return flash instance used to write/read to/from dfu partition.
|
||||
fn dfu(&mut self) -> &mut Self::DFU;
|
||||
/// Return flash instance used to write/read to/from bootloader state.
|
||||
fn state(&mut self) -> &mut Self::STATE;
|
||||
pub state: STATE,
|
||||
}
|
||||
|
||||
trait FlashConfigEx {
|
||||
fn page_size() -> u32;
|
||||
impl<'a, FLASH: NorFlash>
|
||||
BootLoaderConfig<
|
||||
BlockingPartition<'a, NoopRawMutex, FLASH>,
|
||||
BlockingPartition<'a, NoopRawMutex, FLASH>,
|
||||
BlockingPartition<'a, NoopRawMutex, FLASH>,
|
||||
>
|
||||
{
|
||||
/// Create a bootloader config from the flash and address symbols defined in the linkerfile
|
||||
// #[cfg(target_os = "none")]
|
||||
pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_active_start: u32;
|
||||
static __bootloader_active_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
impl<T: FlashConfig> FlashConfigEx for T {
|
||||
/// Get the page size which is the "unit of operation" within the bootloader.
|
||||
fn page_size() -> u32 {
|
||||
core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32
|
||||
let active = unsafe {
|
||||
let start = &__bootloader_active_start as *const u32 as u32;
|
||||
let end = &__bootloader_active_end as *const u32 as u32;
|
||||
trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end);
|
||||
|
||||
BlockingPartition::new(flash, start, end - start)
|
||||
};
|
||||
let dfu = unsafe {
|
||||
let start = &__bootloader_dfu_start as *const u32 as u32;
|
||||
let end = &__bootloader_dfu_end as *const u32 as u32;
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
|
||||
|
||||
BlockingPartition::new(flash, start, end - start)
|
||||
};
|
||||
let state = unsafe {
|
||||
let start = &__bootloader_state_start as *const u32 as u32;
|
||||
let end = &__bootloader_state_end as *const u32 as u32;
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
|
||||
|
||||
BlockingPartition::new(flash, start, end - start)
|
||||
};
|
||||
|
||||
Self { active, dfu, state }
|
||||
}
|
||||
}
|
||||
|
||||
/// BootLoader works with any flash implementing embedded_storage.
|
||||
pub struct BootLoader {
|
||||
// Page with current state of bootloader. The state partition has the following format:
|
||||
// All ranges are in multiples of WRITE_SIZE bytes.
|
||||
// | Range | Description |
|
||||
// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
|
||||
// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
|
||||
// | 2..2 + N | Progress index used while swapping or reverting |
|
||||
state: Partition,
|
||||
// Location of the partition which will be booted from
|
||||
active: Partition,
|
||||
// Location of the partition which will be swapped in when requested
|
||||
dfu: Partition,
|
||||
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> {
|
||||
active: ACTIVE,
|
||||
dfu: DFU,
|
||||
/// The state partition has the following format:
|
||||
/// All ranges are in multiples of WRITE_SIZE bytes.
|
||||
/// | Range | Description |
|
||||
/// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
|
||||
/// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
|
||||
/// | 2..2 + N | Progress index used while swapping or reverting
|
||||
state: STATE,
|
||||
}
|
||||
|
||||
impl BootLoader {
|
||||
/// Create a new instance of a bootloader with the given partitions.
|
||||
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, STATE> {
|
||||
/// Get the page size which is the "unit of operation" within the bootloader.
|
||||
const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE {
|
||||
ACTIVE::ERASE_SIZE as u32
|
||||
} else {
|
||||
DFU::ERASE_SIZE as u32
|
||||
};
|
||||
|
||||
/// Create a new instance of a bootloader with the flash partitions.
|
||||
///
|
||||
/// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
|
||||
/// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
Self { active, dfu, state }
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
|
||||
Self {
|
||||
active: config.active,
|
||||
dfu: config.dfu,
|
||||
state: config.state,
|
||||
}
|
||||
|
||||
/// Return the offset of the active partition into the active flash.
|
||||
pub fn boot_address(&self) -> usize {
|
||||
self.active.from as usize
|
||||
}
|
||||
|
||||
/// Perform necessary boot preparations like swapping images.
|
||||
@ -175,195 +213,174 @@ impl BootLoader {
|
||||
/// | DFU | 3 | 3 | 2 | 1 | 3 |
|
||||
/// +-----------+--------------+--------+--------+--------+--------+
|
||||
///
|
||||
pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
|
||||
pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
|
||||
// Ensure we have enough progress pages to store copy progress
|
||||
assert_eq!(0, P::page_size() % aligned_buf.len() as u32);
|
||||
assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32);
|
||||
assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32);
|
||||
assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32);
|
||||
assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32);
|
||||
assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
|
||||
assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE);
|
||||
assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE);
|
||||
assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE);
|
||||
assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32);
|
||||
assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32);
|
||||
assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
|
||||
assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
|
||||
assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
|
||||
|
||||
assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE);
|
||||
|
||||
// Copy contents from partition N to active
|
||||
let state = self.read_state(p, aligned_buf)?;
|
||||
let state = self.read_state(aligned_buf)?;
|
||||
if state == State::Swap {
|
||||
//
|
||||
// Check if we already swapped. If we're in the swap state, this means we should revert
|
||||
// since the app has failed to mark boot as successful
|
||||
//
|
||||
if !self.is_swapped(p, aligned_buf)? {
|
||||
if !self.is_swapped(aligned_buf)? {
|
||||
trace!("Swapping");
|
||||
self.swap(p, aligned_buf)?;
|
||||
self.swap(aligned_buf)?;
|
||||
trace!("Swapping done");
|
||||
} else {
|
||||
trace!("Reverting");
|
||||
self.revert(p, aligned_buf)?;
|
||||
self.revert(aligned_buf)?;
|
||||
|
||||
let state_flash = p.state();
|
||||
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
|
||||
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
|
||||
|
||||
// Invalidate progress
|
||||
state_word.fill(!P::STATE_ERASE_VALUE);
|
||||
self.state
|
||||
.write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
|
||||
state_word.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(STATE::WRITE_SIZE as u32, state_word)?;
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.wipe_blocking(state_flash)?;
|
||||
self.state.erase(0, self.state.capacity() as u32)?;
|
||||
|
||||
// Set magic
|
||||
state_word.fill(BOOT_MAGIC);
|
||||
self.state.write_blocking(state_flash, 0, state_word)?;
|
||||
self.state.write(0, state_word)?;
|
||||
}
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
|
||||
let page_count = (self.active.size() / P::page_size()) as usize;
|
||||
let progress = self.current_progress(p, aligned_buf)?;
|
||||
fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
|
||||
let page_count = self.active.capacity() / Self::PAGE_SIZE as usize;
|
||||
let progress = self.current_progress(aligned_buf)?;
|
||||
|
||||
Ok(progress >= page_count * 2)
|
||||
}
|
||||
|
||||
fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
|
||||
let write_size = P::STATE::WRITE_SIZE as u32;
|
||||
let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize;
|
||||
let state_flash = config.state();
|
||||
fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
|
||||
let write_size = STATE::WRITE_SIZE as u32;
|
||||
let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2;
|
||||
let state_word = &mut aligned_buf[..write_size as usize];
|
||||
|
||||
self.state.read_blocking(state_flash, write_size, state_word)?;
|
||||
if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
|
||||
self.state.read(write_size, state_word)?;
|
||||
if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// Progress is invalid
|
||||
return Ok(max_index);
|
||||
}
|
||||
|
||||
for index in 0..max_index {
|
||||
self.state
|
||||
.read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?;
|
||||
self.state.read((2 + index) as u32 * write_size, state_word)?;
|
||||
|
||||
if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
|
||||
if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) {
|
||||
return Ok(index);
|
||||
}
|
||||
}
|
||||
Ok(max_index)
|
||||
}
|
||||
|
||||
fn update_progress<P: FlashConfig>(
|
||||
&mut self,
|
||||
progress_index: usize,
|
||||
p: &mut P,
|
||||
aligned_buf: &mut [u8],
|
||||
) -> Result<(), BootError> {
|
||||
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
|
||||
state_word.fill(!P::STATE_ERASE_VALUE);
|
||||
self.state.write_blocking(
|
||||
p.state(),
|
||||
(2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32,
|
||||
state_word,
|
||||
)?;
|
||||
fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> {
|
||||
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
|
||||
state_word.fill(!STATE_ERASE_VALUE);
|
||||
self.state
|
||||
.write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_page_once_to_active<P: FlashConfig>(
|
||||
fn copy_page_once_to_active(
|
||||
&mut self,
|
||||
progress_index: usize,
|
||||
from_offset: u32,
|
||||
to_offset: u32,
|
||||
p: &mut P,
|
||||
aligned_buf: &mut [u8],
|
||||
) -> Result<(), BootError> {
|
||||
if self.current_progress(p, aligned_buf)? <= progress_index {
|
||||
let page_size = P::page_size() as u32;
|
||||
if self.current_progress(aligned_buf)? <= progress_index {
|
||||
let page_size = Self::PAGE_SIZE as u32;
|
||||
|
||||
self.active
|
||||
.erase_blocking(p.active(), to_offset, to_offset + page_size)?;
|
||||
self.active.erase(to_offset, to_offset + page_size)?;
|
||||
|
||||
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
|
||||
self.dfu
|
||||
.read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
|
||||
self.active
|
||||
.write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
|
||||
self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?;
|
||||
self.active.write(to_offset + offset_in_page as u32, aligned_buf)?;
|
||||
}
|
||||
|
||||
self.update_progress(progress_index, p, aligned_buf)?;
|
||||
self.update_progress(progress_index, aligned_buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_page_once_to_dfu<P: FlashConfig>(
|
||||
fn copy_page_once_to_dfu(
|
||||
&mut self,
|
||||
progress_index: usize,
|
||||
from_offset: u32,
|
||||
to_offset: u32,
|
||||
p: &mut P,
|
||||
aligned_buf: &mut [u8],
|
||||
) -> Result<(), BootError> {
|
||||
if self.current_progress(p, aligned_buf)? <= progress_index {
|
||||
let page_size = P::page_size() as u32;
|
||||
if self.current_progress(aligned_buf)? <= progress_index {
|
||||
let page_size = Self::PAGE_SIZE as u32;
|
||||
|
||||
self.dfu
|
||||
.erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
|
||||
self.dfu.erase(to_offset as u32, to_offset + page_size)?;
|
||||
|
||||
for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
|
||||
self.active
|
||||
.read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
|
||||
self.dfu
|
||||
.write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
|
||||
self.active.read(from_offset + offset_in_page as u32, aligned_buf)?;
|
||||
self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?;
|
||||
}
|
||||
|
||||
self.update_progress(progress_index, p, aligned_buf)?;
|
||||
self.update_progress(progress_index, aligned_buf)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
|
||||
let page_size = P::page_size();
|
||||
let page_count = self.active.size() / page_size;
|
||||
fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
|
||||
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
|
||||
for page_num in 0..page_count {
|
||||
let progress_index = (page_num * 2) as usize;
|
||||
|
||||
// Copy active page to the 'next' DFU page.
|
||||
let active_from_offset = (page_count - 1 - page_num) * page_size;
|
||||
let dfu_to_offset = (page_count - page_num) * page_size;
|
||||
let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
|
||||
let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE;
|
||||
//trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
|
||||
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
|
||||
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
|
||||
|
||||
// Copy DFU page to the active page
|
||||
let active_to_offset = (page_count - 1 - page_num) * page_size;
|
||||
let dfu_from_offset = (page_count - 1 - page_num) * page_size;
|
||||
let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
|
||||
let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
|
||||
//trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
|
||||
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
|
||||
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
|
||||
let page_size = P::page_size();
|
||||
let page_count = self.active.size() / page_size;
|
||||
fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
|
||||
let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
|
||||
for page_num in 0..page_count {
|
||||
let progress_index = (page_count * 2 + page_num * 2) as usize;
|
||||
|
||||
// Copy the bad active page to the DFU page
|
||||
let active_from_offset = page_num * page_size;
|
||||
let dfu_to_offset = page_num * page_size;
|
||||
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
|
||||
let active_from_offset = page_num * Self::PAGE_SIZE;
|
||||
let dfu_to_offset = page_num * Self::PAGE_SIZE;
|
||||
self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
|
||||
|
||||
// Copy the DFU page back to the active page
|
||||
let active_to_offset = page_num * page_size;
|
||||
let dfu_from_offset = (page_num + 1) * page_size;
|
||||
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
|
||||
let active_to_offset = page_num * Self::PAGE_SIZE;
|
||||
let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE;
|
||||
self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
|
||||
let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
|
||||
self.state.read_blocking(config.state(), 0, state_word)?;
|
||||
fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
|
||||
let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
|
||||
self.state.read(0, state_word)?;
|
||||
|
||||
if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
@ -373,161 +390,32 @@ impl BootLoader {
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) {
|
||||
assert_eq!(active.size() % page_size, 0);
|
||||
assert_eq!(dfu.size() % page_size, 0);
|
||||
assert!(dfu.size() - active.size() >= page_size);
|
||||
assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32);
|
||||
}
|
||||
|
||||
/// A flash wrapper implementing the Flash and embedded_storage traits.
|
||||
pub struct BootFlash<F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
flash: F,
|
||||
}
|
||||
|
||||
impl<F> BootFlash<F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
/// Create a new instance of a bootable flash
|
||||
pub fn new(flash: F) -> Self {
|
||||
Self { flash }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ErrorType for BootFlash<F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
type Error = F::Error;
|
||||
}
|
||||
|
||||
impl<F> NorFlash for BootFlash<F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
const WRITE_SIZE: usize = F::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = F::ERASE_SIZE;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
F::erase(&mut self.flash, from, to)
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
F::write(&mut self.flash, offset, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ReadNorFlash for BootFlash<F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
const READ_SIZE: usize = F::READ_SIZE;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
F::read(&mut self.flash, offset, bytes)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
F::capacity(&self.flash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience provider that uses a single flash for all partitions.
|
||||
pub struct SingleFlashConfig<'a, F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
flash: &'a mut F,
|
||||
}
|
||||
|
||||
impl<'a, F> SingleFlashConfig<'a, F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
/// Create a provider for a single flash.
|
||||
pub fn new(flash: &'a mut F) -> Self {
|
||||
Self { flash }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
|
||||
where
|
||||
F: NorFlash,
|
||||
{
|
||||
type STATE = F;
|
||||
type ACTIVE = F;
|
||||
type DFU = F;
|
||||
|
||||
fn active(&mut self) -> &mut Self::STATE {
|
||||
self.flash
|
||||
}
|
||||
fn dfu(&mut self) -> &mut Self::ACTIVE {
|
||||
self.flash
|
||||
}
|
||||
fn state(&mut self) -> &mut Self::DFU {
|
||||
self.flash
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience flash provider that uses separate flash instances for each partition.
|
||||
pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
|
||||
where
|
||||
ACTIVE: NorFlash,
|
||||
STATE: NorFlash,
|
||||
DFU: NorFlash,
|
||||
{
|
||||
active: &'a mut ACTIVE,
|
||||
state: &'a mut STATE,
|
||||
dfu: &'a mut DFU,
|
||||
}
|
||||
|
||||
impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
|
||||
where
|
||||
ACTIVE: NorFlash,
|
||||
STATE: NorFlash,
|
||||
DFU: NorFlash,
|
||||
{
|
||||
/// Create a new flash provider with separate configuration for all three partitions.
|
||||
pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
|
||||
Self { active, state, dfu }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
|
||||
where
|
||||
ACTIVE: NorFlash,
|
||||
STATE: NorFlash,
|
||||
DFU: NorFlash,
|
||||
{
|
||||
type STATE = STATE;
|
||||
type ACTIVE = ACTIVE;
|
||||
type DFU = DFU;
|
||||
|
||||
fn active(&mut self) -> &mut Self::ACTIVE {
|
||||
self.active
|
||||
}
|
||||
fn dfu(&mut self) -> &mut Self::DFU {
|
||||
self.dfu
|
||||
}
|
||||
fn state(&mut self) -> &mut Self::STATE {
|
||||
self.state
|
||||
}
|
||||
fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
|
||||
active: &ACTIVE,
|
||||
dfu: &DFU,
|
||||
state: &STATE,
|
||||
page_size: u32,
|
||||
) {
|
||||
assert_eq!(active.capacity() as u32 % page_size, 0);
|
||||
assert_eq!(dfu.capacity() as u32 % page_size, 0);
|
||||
assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size);
|
||||
assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_range_asserts() {
|
||||
const ACTIVE: Partition = Partition::new(4096, 4194304);
|
||||
const DFU: Partition = Partition::new(4194304, 2 * 4194304);
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
|
||||
const ACTIVE_SIZE: usize = 4194304 - 4096;
|
||||
const DFU_SIZE: usize = 4194304;
|
||||
const STATE_SIZE: usize = 4096;
|
||||
static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF);
|
||||
static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF);
|
||||
static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF);
|
||||
assert_partitions(&ACTIVE, &DFU, &STATE, 4096);
|
||||
}
|
||||
}
|
||||
|
@ -1,534 +0,0 @@
|
||||
use digest::Digest;
|
||||
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
|
||||
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
|
||||
|
||||
use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
|
||||
|
||||
/// Errors returned by FirmwareUpdater
|
||||
#[derive(Debug)]
|
||||
pub enum FirmwareUpdaterError {
|
||||
/// Error from flash.
|
||||
Flash(NorFlashErrorKind),
|
||||
/// Signature errors.
|
||||
Signature(signature::Error),
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for FirmwareUpdaterError {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
match self {
|
||||
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
|
||||
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for FirmwareUpdaterError
|
||||
where
|
||||
E: NorFlashError,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
FirmwareUpdaterError::Flash(error.kind())
|
||||
}
|
||||
}
|
||||
|
||||
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
||||
/// 'mess up' the internal bootloader state
|
||||
pub struct FirmwareUpdater {
|
||||
state: Partition,
|
||||
dfu: Partition,
|
||||
}
|
||||
|
||||
impl Default for FirmwareUpdater {
|
||||
fn default() -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let dfu = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_dfu_start as *const u32 as u32,
|
||||
&__bootloader_dfu_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
let state = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_state_start as *const u32 as u32,
|
||||
&__bootloader_state_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
|
||||
FirmwareUpdater::new(dfu, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl FirmwareUpdater {
|
||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
||||
pub const fn new(dfu: Partition, state: Partition) -> Self {
|
||||
Self { dfu, state }
|
||||
}
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub async fn get_state<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
aligned: &mut [u8],
|
||||
) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(state_flash, 0, aligned).await?;
|
||||
|
||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
||||
/// proceed with updating the firmware as it must be signed with a
|
||||
/// corresponding private key (otherwise it could be malicious firmware).
|
||||
///
|
||||
/// Mark to trigger firmware swap on next boot if verify suceeds.
|
||||
///
|
||||
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
|
||||
/// been generated from a SHA-512 digest of the firmware bytes.
|
||||
///
|
||||
/// If no signature feature is set then this method will always return a
|
||||
/// signature error.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub async fn verify_and_mark_updated<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
_state_and_dfu_flash: &mut F,
|
||||
_public_key: &[u8],
|
||||
_signature: &[u8],
|
||||
_update_len: u32,
|
||||
_aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(_aligned.len(), F::WRITE_SIZE);
|
||||
assert!(_update_len <= self.dfu.size());
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
||||
|
||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
||||
|
||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
||||
|
||||
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
|
||||
.await?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
|
||||
use salty::{PublicKey, Signature};
|
||||
|
||||
use crate::digest_adapters::salty::Sha512;
|
||||
|
||||
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
|
||||
FirmwareUpdaterError::Signature(signature::Error::default())
|
||||
}
|
||||
|
||||
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
|
||||
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
|
||||
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)
|
||||
.await?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
"Verifying with public key {}, signature {} and message {} yields ok: {}",
|
||||
public_key.to_bytes(),
|
||||
signature.to_bytes(),
|
||||
message,
|
||||
r.is_ok()
|
||||
);
|
||||
r.map_err(into_signature_error)?
|
||||
}
|
||||
|
||||
self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
pub async fn hash<F: AsyncNorFlash, D: Digest>(
|
||||
&mut self,
|
||||
dfu_flash: &mut F,
|
||||
update_len: u32,
|
||||
chunk_buf: &mut [u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
let mut digest = D::new();
|
||||
for offset in (0..update_len).step_by(chunk_buf.len()) {
|
||||
self.dfu.read(dfu_flash, offset, chunk_buf).await?;
|
||||
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
|
||||
digest.update(&chunk_buf[..len]);
|
||||
}
|
||||
output.copy_from_slice(digest.finalize().as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
pub async fn mark_updated<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
||||
self.set_magic(aligned, SWAP_MAGIC, state_flash).await
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
pub async fn mark_booted<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
||||
self.set_magic(aligned, BOOT_MAGIC, state_flash).await
|
||||
}
|
||||
|
||||
async fn set_magic<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
aligned: &mut [u8],
|
||||
magic: u8,
|
||||
state_flash: &mut F,
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read(state_flash, 0, aligned).await?;
|
||||
|
||||
if aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
|
||||
|
||||
// FIXME: Do not make this assumption.
|
||||
const STATE_ERASE_VALUE: u8 = 0xFF;
|
||||
|
||||
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.wipe(state_flash).await?;
|
||||
|
||||
// Set magic
|
||||
aligned.fill(magic);
|
||||
self.state.write(state_flash, 0, aligned).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write data to a flash page.
|
||||
///
|
||||
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Failing to meet alignment and size requirements may result in a panic.
|
||||
pub async fn write_firmware<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
offset: usize,
|
||||
data: &[u8],
|
||||
dfu_flash: &mut F,
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(data.len() >= F::ERASE_SIZE);
|
||||
|
||||
self.dfu
|
||||
.erase(dfu_flash, offset as u32, (offset + data.len()) as u32)
|
||||
.await?;
|
||||
|
||||
self.dfu.write(dfu_flash, offset as u32, data).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare for an incoming DFU update by erasing the entire DFU area and
|
||||
/// returning its `Partition`.
|
||||
///
|
||||
/// Using this instead of `write_firmware` allows for an optimized API in
|
||||
/// exchange for added complexity.
|
||||
pub async fn prepare_update<F: AsyncNorFlash>(
|
||||
&mut self,
|
||||
dfu_flash: &mut F,
|
||||
) -> Result<Partition, FirmwareUpdaterError> {
|
||||
self.dfu.wipe(dfu_flash).await?;
|
||||
|
||||
Ok(self.dfu)
|
||||
}
|
||||
|
||||
//
|
||||
// Blocking API
|
||||
//
|
||||
|
||||
/// Obtain the current state.
|
||||
///
|
||||
/// This is useful to check if the bootloader has just done a swap, in order
|
||||
/// to do verifications and self-tests of the new image before calling
|
||||
/// `mark_booted`.
|
||||
pub fn get_state_blocking<F: NorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
aligned: &mut [u8],
|
||||
) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read_blocking(state_flash, 0, aligned)?;
|
||||
|
||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
||||
/// proceed with updating the firmware as it must be signed with a
|
||||
/// corresponding private key (otherwise it could be malicious firmware).
|
||||
///
|
||||
/// Mark to trigger firmware swap on next boot if verify suceeds.
|
||||
///
|
||||
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
|
||||
/// been generated from a SHA-512 digest of the firmware bytes.
|
||||
///
|
||||
/// If no signature feature is set then this method will always return a
|
||||
/// signature error.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub fn verify_and_mark_updated_blocking<F: NorFlash>(
|
||||
&mut self,
|
||||
_state_and_dfu_flash: &mut F,
|
||||
_public_key: &[u8],
|
||||
_signature: &[u8],
|
||||
_update_len: u32,
|
||||
_aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(_aligned.len(), F::WRITE_SIZE);
|
||||
assert!(_update_len <= self.dfu.size());
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
||||
|
||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
||||
|
||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
||||
|
||||
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
|
||||
use salty::{PublicKey, Signature};
|
||||
|
||||
use crate::digest_adapters::salty::Sha512;
|
||||
|
||||
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
|
||||
FirmwareUpdaterError::Signature(signature::Error::default())
|
||||
}
|
||||
|
||||
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
|
||||
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
|
||||
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
"Verifying with public key {}, signature {} and message {} yields ok: {}",
|
||||
public_key.to_bytes(),
|
||||
signature.to_bytes(),
|
||||
message,
|
||||
r.is_ok()
|
||||
);
|
||||
r.map_err(into_signature_error)?
|
||||
}
|
||||
|
||||
self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash)
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
pub fn hash_blocking<F: NorFlash, D: Digest>(
|
||||
&mut self,
|
||||
dfu_flash: &mut F,
|
||||
update_len: u32,
|
||||
chunk_buf: &mut [u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
let mut digest = D::new();
|
||||
for offset in (0..update_len).step_by(chunk_buf.len()) {
|
||||
self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?;
|
||||
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
|
||||
digest.update(&chunk_buf[..len]);
|
||||
}
|
||||
output.copy_from_slice(digest.finalize().as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
pub fn mark_updated_blocking<F: NorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
||||
self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash)
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
|
||||
pub fn mark_booted_blocking<F: NorFlash>(
|
||||
&mut self,
|
||||
state_flash: &mut F,
|
||||
aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), F::WRITE_SIZE);
|
||||
self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash)
|
||||
}
|
||||
|
||||
fn set_magic_blocking<F: NorFlash>(
|
||||
&mut self,
|
||||
aligned: &mut [u8],
|
||||
magic: u8,
|
||||
state_flash: &mut F,
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
self.state.read_blocking(state_flash, 0, aligned)?;
|
||||
|
||||
if aligned.iter().any(|&b| b != magic) {
|
||||
// Read progress validity
|
||||
self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
|
||||
|
||||
// FIXME: Do not make this assumption.
|
||||
const STATE_ERASE_VALUE: u8 = 0xFF;
|
||||
|
||||
if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
|
||||
// The current progress validity marker is invalid
|
||||
} else {
|
||||
// Invalidate progress
|
||||
aligned.fill(!STATE_ERASE_VALUE);
|
||||
self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
|
||||
}
|
||||
|
||||
// Clear magic and progress
|
||||
self.state.wipe_blocking(state_flash)?;
|
||||
|
||||
// Set magic
|
||||
aligned.fill(magic);
|
||||
self.state.write_blocking(state_flash, 0, aligned)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write data to a flash page.
|
||||
///
|
||||
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Failing to meet alignment and size requirements may result in a panic.
|
||||
pub fn write_firmware_blocking<F: NorFlash>(
|
||||
&mut self,
|
||||
offset: usize,
|
||||
data: &[u8],
|
||||
dfu_flash: &mut F,
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(data.len() >= F::ERASE_SIZE);
|
||||
|
||||
self.dfu
|
||||
.erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?;
|
||||
|
||||
self.dfu.write_blocking(dfu_flash, offset as u32, data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare for an incoming DFU update by erasing the entire DFU area and
|
||||
/// returning its `Partition`.
|
||||
///
|
||||
/// Using this instead of `write_firmware_blocking` allows for an optimized
|
||||
/// API in exchange for added complexity.
|
||||
pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> {
|
||||
self.dfu.wipe_blocking(flash)?;
|
||||
|
||||
Ok(self.dfu)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::executor::block_on;
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
use super::*;
|
||||
use crate::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_verify_sha1() {
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const DFU: Partition = Partition::new(65536, 131072);
|
||||
|
||||
let mut flash = MemFlash::<131072, 4096, 8>::default();
|
||||
|
||||
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
||||
let mut to_write = [0; 4096];
|
||||
to_write[..7].copy_from_slice(update.as_slice());
|
||||
|
||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||
block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap();
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut hash = [0; 20];
|
||||
block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
|
||||
|
||||
assert_eq!(Sha1::digest(update).as_slice(), hash);
|
||||
}
|
||||
}
|
299
embassy-boot/boot/src/firmware_updater/asynch.rs
Normal file
299
embassy-boot/boot/src/firmware_updater/asynch.rs
Normal file
@ -0,0 +1,299 @@
|
||||
use digest::Digest;
|
||||
#[cfg(target_os = "none")]
|
||||
use embassy_embedded_hal::flash::partition::Partition;
|
||||
#[cfg(target_os = "none")]
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embedded_storage_async::nor_flash::NorFlash;
|
||||
|
||||
use super::FirmwareUpdaterConfig;
|
||||
use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
||||
|
||||
/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
||||
/// 'mess up' the internal bootloader state
|
||||
pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
|
||||
dfu: DFU,
|
||||
state: STATE,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "none")]
|
||||
impl<'a, FLASH: NorFlash>
|
||||
FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>>
|
||||
{
|
||||
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
|
||||
pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let dfu = unsafe {
|
||||
let start = &__bootloader_dfu_start as *const u32 as u32;
|
||||
let end = &__bootloader_dfu_end as *const u32 as u32;
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
|
||||
|
||||
Partition::new(flash, start, end - start)
|
||||
};
|
||||
let state = unsafe {
|
||||
let start = &__bootloader_state_start as *const u32 as u32;
|
||||
let end = &__bootloader_state_end as *const u32 as u32;
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
|
||||
|
||||
Partition::new(flash, start, end - start)
|
||||
};
|
||||
|
||||
Self { dfu, state }
|
||||
}
|
||||
}
|
||||
|
||||
impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> {
|
||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
|
||||
Self {
|
||||
dfu: config.dfu,
|
||||
state: config.state,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, aligned).await?;
|
||||
|
||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
||||
/// proceed with updating the firmware as it must be signed with a
|
||||
/// corresponding private key (otherwise it could be malicious firmware).
|
||||
///
|
||||
/// Mark to trigger firmware swap on next boot if verify suceeds.
|
||||
///
|
||||
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
|
||||
/// been generated from a SHA-512 digest of the firmware bytes.
|
||||
///
|
||||
/// If no signature feature is set then this method will always return a
|
||||
/// signature error.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub async fn verify_and_mark_updated(
|
||||
&mut self,
|
||||
_public_key: &[u8],
|
||||
_signature: &[u8],
|
||||
_update_len: u32,
|
||||
_aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
|
||||
assert!(_update_len <= self.dfu.capacity() as u32);
|
||||
|
||||
self.verify_booted(_aligned).await?;
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
||||
|
||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
||||
|
||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
||||
|
||||
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
|
||||
use salty::{PublicKey, Signature};
|
||||
|
||||
use crate::digest_adapters::salty::Sha512;
|
||||
|
||||
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
|
||||
FirmwareUpdaterError::Signature(signature::Error::default())
|
||||
}
|
||||
|
||||
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
|
||||
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
|
||||
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
"Verifying with public key {}, signature {} and message {} yields ok: {}",
|
||||
public_key.to_bytes(),
|
||||
signature.to_bytes(),
|
||||
message,
|
||||
r.is_ok()
|
||||
);
|
||||
r.map_err(into_signature_error)?
|
||||
}
|
||||
|
||||
self.set_magic(_aligned, SWAP_MAGIC).await
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
pub async fn hash<D: Digest>(
|
||||
&mut self,
|
||||
update_len: u32,
|
||||
chunk_buf: &mut [u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
let mut digest = D::new();
|
||||
for offset in (0..update_len).step_by(chunk_buf.len()) {
|
||||
self.dfu.read(offset, chunk_buf).await?;
|
||||
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
|
||||
digest.update(&chunk_buf[..len]);
|
||||
}
|
||||
output.copy_from_slice(digest.finalize().as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
///
|
||||
/// # 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"))]
|
||||
pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.set_magic(aligned, SWAP_MAGIC).await
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Failing to meet alignment and size requirements may result in a panic.
|
||||
pub async fn write_firmware(
|
||||
&mut self,
|
||||
aligned: &mut [u8],
|
||||
offset: usize,
|
||||
data: &[u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(data.len() >= DFU::ERASE_SIZE);
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
|
||||
self.verify_booted(aligned).await?;
|
||||
|
||||
self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
|
||||
|
||||
self.dfu.write(offset as u32, data).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare for an incoming DFU update by erasing the entire DFU area and
|
||||
/// returning its `Partition`.
|
||||
///
|
||||
/// Using this instead of `write_firmware` allows for an optimized API in
|
||||
/// exchange for added complexity.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// 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?;
|
||||
|
||||
Ok(&mut self.dfu)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_embedded_hal::flash::partition::Partition;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use futures::executor::block_on;
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
use super::*;
|
||||
use crate::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_verify_sha1() {
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
|
||||
let state = Partition::new(&flash, 0, 4096);
|
||||
let dfu = Partition::new(&flash, 65536, 65536);
|
||||
let mut aligned = [0; 8];
|
||||
|
||||
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
||||
let mut to_write = [0; 4096];
|
||||
to_write[..7].copy_from_slice(update.as_slice());
|
||||
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
|
||||
block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap();
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut hash = [0; 20];
|
||||
block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
|
||||
|
||||
assert_eq!(Sha1::digest(update).as_slice(), hash);
|
||||
}
|
||||
}
|
302
embassy-boot/boot/src/firmware_updater/blocking.rs
Normal file
302
embassy-boot/boot/src/firmware_updater/blocking.rs
Normal file
@ -0,0 +1,302 @@
|
||||
use digest::Digest;
|
||||
#[cfg(target_os = "none")]
|
||||
use embassy_embedded_hal::flash::partition::BlockingPartition;
|
||||
#[cfg(target_os = "none")]
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embedded_storage::nor_flash::NorFlash;
|
||||
|
||||
use super::FirmwareUpdaterConfig;
|
||||
use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
|
||||
|
||||
/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
|
||||
/// 'mess up' the internal bootloader state
|
||||
pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
|
||||
dfu: DFU,
|
||||
state: STATE,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "none")]
|
||||
impl<'a, FLASH: NorFlash>
|
||||
FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>>
|
||||
{
|
||||
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
|
||||
pub fn from_linkerfile_blocking(
|
||||
flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>,
|
||||
) -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let dfu = unsafe {
|
||||
let start = &__bootloader_dfu_start as *const u32 as u32;
|
||||
let end = &__bootloader_dfu_end as *const u32 as u32;
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
|
||||
|
||||
BlockingPartition::new(flash, start, end - start)
|
||||
};
|
||||
let state = unsafe {
|
||||
let start = &__bootloader_state_start as *const u32 as u32;
|
||||
let end = &__bootloader_state_end as *const u32 as u32;
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
|
||||
|
||||
BlockingPartition::new(flash, start, end - start)
|
||||
};
|
||||
|
||||
Self { dfu, state }
|
||||
}
|
||||
}
|
||||
|
||||
impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
|
||||
/// Create a firmware updater instance with partition ranges for the update and state partitions.
|
||||
pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
|
||||
Self {
|
||||
dfu: config.dfu,
|
||||
state: config.state,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
|
||||
self.state.read(0, aligned)?;
|
||||
|
||||
if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
|
||||
Ok(State::Swap)
|
||||
} else {
|
||||
Ok(State::Boot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the DFU given a public key. If there is an error then DO NOT
|
||||
/// proceed with updating the firmware as it must be signed with a
|
||||
/// corresponding private key (otherwise it could be malicious firmware).
|
||||
///
|
||||
/// Mark to trigger firmware swap on next boot if verify suceeds.
|
||||
///
|
||||
/// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
|
||||
/// been generated from a SHA-512 digest of the firmware bytes.
|
||||
///
|
||||
/// If no signature feature is set then this method will always return a
|
||||
/// signature error.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
|
||||
/// and written to.
|
||||
#[cfg(feature = "_verify")]
|
||||
pub fn verify_and_mark_updated(
|
||||
&mut self,
|
||||
_public_key: &[u8],
|
||||
_signature: &[u8],
|
||||
_update_len: u32,
|
||||
_aligned: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
|
||||
assert!(_update_len <= self.dfu.capacity() as u32);
|
||||
|
||||
self.verify_booted(_aligned)?;
|
||||
|
||||
#[cfg(feature = "ed25519-dalek")]
|
||||
{
|
||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
||||
|
||||
use crate::digest_adapters::ed25519_dalek::Sha512;
|
||||
|
||||
let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
|
||||
|
||||
let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
|
||||
let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
|
||||
|
||||
public_key.verify(&message, &signature).map_err(into_signature_error)?
|
||||
}
|
||||
#[cfg(feature = "ed25519-salty")]
|
||||
{
|
||||
use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
|
||||
use salty::{PublicKey, Signature};
|
||||
|
||||
use crate::digest_adapters::salty::Sha512;
|
||||
|
||||
fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
|
||||
FirmwareUpdaterError::Signature(signature::Error::default())
|
||||
}
|
||||
|
||||
let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
|
||||
let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
|
||||
let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
|
||||
let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
|
||||
|
||||
let mut message = [0; 64];
|
||||
self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
|
||||
|
||||
let r = public_key.verify(&message, &signature);
|
||||
trace!(
|
||||
"Verifying with public key {}, signature {} and message {} yields ok: {}",
|
||||
public_key.to_bytes(),
|
||||
signature.to_bytes(),
|
||||
message,
|
||||
r.is_ok()
|
||||
);
|
||||
r.map_err(into_signature_error)?
|
||||
}
|
||||
|
||||
self.set_magic(_aligned, SWAP_MAGIC)
|
||||
}
|
||||
|
||||
/// Verify the update in DFU with any digest.
|
||||
pub fn hash<D: Digest>(
|
||||
&mut self,
|
||||
update_len: u32,
|
||||
chunk_buf: &mut [u8],
|
||||
output: &mut [u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
let mut digest = D::new();
|
||||
for offset in (0..update_len).step_by(chunk_buf.len()) {
|
||||
self.dfu.read(offset, chunk_buf)?;
|
||||
let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
|
||||
digest.update(&chunk_buf[..len]);
|
||||
}
|
||||
output.copy_from_slice(digest.finalize().as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark to trigger firmware swap on next boot.
|
||||
///
|
||||
/// # 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"))]
|
||||
pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.set_magic(aligned, SWAP_MAGIC)
|
||||
}
|
||||
|
||||
/// Mark firmware boot successful and stop rollback on reset.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Failing to meet alignment and size requirements may result in a panic.
|
||||
pub fn write_firmware(
|
||||
&mut self,
|
||||
aligned: &mut [u8],
|
||||
offset: usize,
|
||||
data: &[u8],
|
||||
) -> Result<(), FirmwareUpdaterError> {
|
||||
assert!(data.len() >= DFU::ERASE_SIZE);
|
||||
assert_eq!(aligned.len(), STATE::WRITE_SIZE);
|
||||
self.verify_booted(aligned)?;
|
||||
|
||||
self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
|
||||
|
||||
self.dfu.write(offset as u32, data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare for an incoming DFU update by erasing the entire DFU area and
|
||||
/// returning its `Partition`.
|
||||
///
|
||||
/// Using this instead of `write_firmware` allows for an optimized API in
|
||||
/// exchange for added complexity.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// 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)?;
|
||||
|
||||
Ok(&mut self.dfu)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::cell::RefCell;
|
||||
|
||||
use embassy_embedded_hal::flash::partition::BlockingPartition;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
use super::*;
|
||||
use crate::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_verify_sha1() {
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
|
||||
let state = BlockingPartition::new(&flash, 0, 4096);
|
||||
let dfu = BlockingPartition::new(&flash, 65536, 65536);
|
||||
|
||||
let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
||||
let mut to_write = [0; 4096];
|
||||
to_write[..7].copy_from_slice(update.as_slice());
|
||||
|
||||
let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
|
||||
let mut aligned = [0; 8];
|
||||
updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap();
|
||||
let mut chunk_buf = [0; 2];
|
||||
let mut hash = [0; 20];
|
||||
updater
|
||||
.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Sha1::digest(update).as_slice(), hash);
|
||||
}
|
||||
}
|
51
embassy-boot/boot/src/firmware_updater/mod.rs
Normal file
51
embassy-boot/boot/src/firmware_updater/mod.rs
Normal file
@ -0,0 +1,51 @@
|
||||
#[cfg(feature = "nightly")]
|
||||
mod asynch;
|
||||
mod blocking;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use asynch::FirmwareUpdater;
|
||||
pub use blocking::BlockingFirmwareUpdater;
|
||||
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
||||
|
||||
/// Firmware updater flash configuration holding the two flashes used by the updater
|
||||
///
|
||||
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
|
||||
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
|
||||
/// the provided flash according to symbols defined in the linkerfile.
|
||||
pub struct FirmwareUpdaterConfig<DFU, STATE> {
|
||||
/// The dfu flash partition
|
||||
pub dfu: DFU,
|
||||
/// The state flash partition
|
||||
pub state: STATE,
|
||||
}
|
||||
|
||||
/// Errors returned by FirmwareUpdater
|
||||
#[derive(Debug)]
|
||||
pub enum FirmwareUpdaterError {
|
||||
/// Error from flash.
|
||||
Flash(NorFlashErrorKind),
|
||||
/// Signature errors.
|
||||
Signature(signature::Error),
|
||||
/// Bad state.
|
||||
BadState,
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for FirmwareUpdaterError {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
match self {
|
||||
FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
|
||||
FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
|
||||
FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for FirmwareUpdaterError
|
||||
where
|
||||
E: NorFlashError,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
FirmwareUpdaterError::Flash(error.kind())
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
#![feature(async_fn_in_trait)]
|
||||
#![allow(incomplete_features)]
|
||||
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
|
||||
#![no_std]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
@ -8,12 +7,18 @@ mod fmt;
|
||||
mod boot_loader;
|
||||
mod digest_adapters;
|
||||
mod firmware_updater;
|
||||
#[cfg(test)]
|
||||
mod mem_flash;
|
||||
mod partition;
|
||||
#[cfg(test)]
|
||||
mod test_flash;
|
||||
|
||||
pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig};
|
||||
pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError};
|
||||
pub use partition::Partition;
|
||||
// The expected value of the flash after an erase
|
||||
// TODO: Use the value provided by NorFlash when available
|
||||
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
|
||||
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use firmware_updater::FirmwareUpdater;
|
||||
pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError};
|
||||
|
||||
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
|
||||
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
|
||||
@ -46,10 +51,20 @@ impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
|
||||
use futures::executor::block_on;
|
||||
|
||||
use super::*;
|
||||
use crate::boot_loader::BootLoaderConfig;
|
||||
use crate::firmware_updater::FirmwareUpdaterConfig;
|
||||
use crate::mem_flash::MemFlash;
|
||||
#[cfg(feature = "nightly")]
|
||||
use crate::test_flash::AsyncTestFlash;
|
||||
use crate::test_flash::BlockingTestFlash;
|
||||
|
||||
/*
|
||||
#[test]
|
||||
@ -68,151 +83,181 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_boot_state() {
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 61440);
|
||||
const DFU: Partition = Partition::new(61440, 122880);
|
||||
let flash = BlockingTestFlash::new(BootLoaderConfig {
|
||||
active: MemFlash::<57344, 4096, 4>::default(),
|
||||
dfu: MemFlash::<61440, 4096, 4>::default(),
|
||||
state: MemFlash::<4096, 4096, 4>::default(),
|
||||
});
|
||||
|
||||
let mut flash = MemFlash::<131072, 4096, 4>::default();
|
||||
flash.mem[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
|
||||
let mut flash = SingleFlashConfig::new(&mut flash);
|
||||
flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap();
|
||||
|
||||
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
|
||||
let mut page = [0; 4096];
|
||||
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap());
|
||||
assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
fn test_swap_state() {
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 61440);
|
||||
const DFU: Partition = Partition::new(61440, 122880);
|
||||
let mut flash = MemFlash::<131072, 4096, 4>::random();
|
||||
const FIRMWARE_SIZE: usize = 57344;
|
||||
let flash = AsyncTestFlash::new(BootLoaderConfig {
|
||||
active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(),
|
||||
dfu: MemFlash::<61440, 4096, 4>::default(),
|
||||
state: MemFlash::<4096, 4096, 4>::default(),
|
||||
});
|
||||
|
||||
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
|
||||
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
|
||||
let mut aligned = [0; 4];
|
||||
|
||||
flash.program(ACTIVE.from, &original).unwrap();
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
|
||||
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
|
||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||
block_on(updater.write_firmware(0, &update, &mut flash)).unwrap();
|
||||
block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated(&mut aligned)).unwrap();
|
||||
|
||||
// Writing after marking updated is not allowed until marked as booted.
|
||||
let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE));
|
||||
assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState)));
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
|
||||
let mut page = [0; 1024];
|
||||
assert_eq!(
|
||||
State::Swap,
|
||||
bootloader
|
||||
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
|
||||
flash.assert_eq(ACTIVE.from, &update);
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(UPDATE, read_buf);
|
||||
// First DFU page is untouched
|
||||
flash.assert_eq(DFU.from + 4096, &original);
|
||||
flash.dfu().read(4096, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
|
||||
// Running again should cause a revert
|
||||
assert_eq!(
|
||||
State::Swap,
|
||||
bootloader
|
||||
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
|
||||
flash.assert_eq(ACTIVE.from, &original);
|
||||
// Last page is untouched
|
||||
flash.assert_eq(DFU.from, &update);
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
// Last DFU page is untouched
|
||||
flash.dfu().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(UPDATE, read_buf);
|
||||
|
||||
// Mark as booted
|
||||
block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap();
|
||||
assert_eq!(
|
||||
State::Boot,
|
||||
bootloader
|
||||
.prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page)
|
||||
.unwrap()
|
||||
);
|
||||
let flash = flash.into_async();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.mark_booted(&mut aligned)).unwrap();
|
||||
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
fn test_separate_flash_active_page_biggest() {
|
||||
const STATE: Partition = Partition::new(2048, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 16384);
|
||||
const DFU: Partition = Partition::new(0, 16384);
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
fn test_swap_state_active_page_biggest() {
|
||||
const FIRMWARE_SIZE: usize = 12288;
|
||||
let flash = AsyncTestFlash::new(BootLoaderConfig {
|
||||
active: MemFlash::<12288, 4096, 8>::random(),
|
||||
dfu: MemFlash::<16384, 2048, 8>::random(),
|
||||
state: MemFlash::<2048, 128, 4>::random(),
|
||||
});
|
||||
|
||||
let mut active = MemFlash::<16384, 4096, 8>::random();
|
||||
let mut dfu = MemFlash::<16384, 2048, 8>::random();
|
||||
let mut state = MemFlash::<4096, 128, 4>::random();
|
||||
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
|
||||
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
|
||||
let mut aligned = [0; 4];
|
||||
|
||||
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
|
||||
active.program(ACTIVE.from, &original).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated(&mut aligned)).unwrap();
|
||||
|
||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
|
||||
block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
|
||||
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
|
||||
|
||||
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
|
||||
let mut page = [0; 4096];
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
|
||||
assert_eq!(
|
||||
State::Swap,
|
||||
bootloader
|
||||
.prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
active.assert_eq(ACTIVE.from, &update);
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(UPDATE, read_buf);
|
||||
// First DFU page is untouched
|
||||
dfu.assert_eq(DFU.from + 4096, &original);
|
||||
flash.dfu().read(4096, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "_verify"))]
|
||||
fn test_separate_flash_dfu_page_biggest() {
|
||||
const STATE: Partition = Partition::new(2048, 4096);
|
||||
const ACTIVE: Partition = Partition::new(4096, 16384);
|
||||
const DFU: Partition = Partition::new(0, 16384);
|
||||
#[cfg(all(feature = "nightly", not(feature = "_verify")))]
|
||||
fn test_swap_state_dfu_page_biggest() {
|
||||
const FIRMWARE_SIZE: usize = 12288;
|
||||
let flash = AsyncTestFlash::new(BootLoaderConfig {
|
||||
active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(),
|
||||
dfu: MemFlash::<16384, 4096, 8>::random(),
|
||||
state: MemFlash::<2048, 128, 4>::random(),
|
||||
});
|
||||
|
||||
const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
|
||||
const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
|
||||
let mut aligned = [0; 4];
|
||||
let mut active = MemFlash::<16384, 2048, 4>::random();
|
||||
let mut dfu = MemFlash::<16384, 4096, 8>::random();
|
||||
let mut state = MemFlash::<4096, 128, 4>::random();
|
||||
|
||||
let original = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
let update = [rand::random::<u8>(); ACTIVE.size() as usize];
|
||||
block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
|
||||
block_on(flash.active().write(0, &ORIGINAL)).unwrap();
|
||||
|
||||
active.program(ACTIVE.from, &original).unwrap();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
|
||||
block_on(updater.mark_updated(&mut aligned)).unwrap();
|
||||
|
||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||
|
||||
block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap();
|
||||
block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
|
||||
|
||||
let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
|
||||
let flash = flash.into_blocking();
|
||||
let mut bootloader = BootLoader::new(BootLoaderConfig {
|
||||
active: flash.active(),
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
let mut page = [0; 4096];
|
||||
assert_eq!(
|
||||
State::Swap,
|
||||
bootloader
|
||||
.prepare_boot(
|
||||
&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,),
|
||||
&mut page
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
|
||||
|
||||
active.assert_eq(ACTIVE.from, &update);
|
||||
let mut read_buf = [0; FIRMWARE_SIZE];
|
||||
flash.active().read(0, &mut read_buf).unwrap();
|
||||
assert_eq!(UPDATE, read_buf);
|
||||
// First DFU page is untouched
|
||||
dfu.assert_eq(DFU.from + 4096, &original);
|
||||
flash.dfu().read(4096, &mut read_buf).unwrap();
|
||||
assert_eq!(ORIGINAL, read_buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "_verify")]
|
||||
#[cfg(all(feature = "nightly", feature = "_verify"))]
|
||||
fn test_verify() {
|
||||
// The following key setup is based on:
|
||||
// https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
|
||||
@ -234,25 +279,28 @@ mod tests {
|
||||
let public_key: PublicKey = keypair.public;
|
||||
|
||||
// Setup flash
|
||||
|
||||
const STATE: Partition = Partition::new(0, 4096);
|
||||
const DFU: Partition = Partition::new(4096, 8192);
|
||||
let mut flash = MemFlash::<8192, 4096, 4>::default();
|
||||
let flash = BlockingTestFlash::new(BootLoaderConfig {
|
||||
active: MemFlash::<0, 0, 0>::default(),
|
||||
dfu: MemFlash::<4096, 4096, 4>::default(),
|
||||
state: MemFlash::<4096, 4096, 4>::default(),
|
||||
});
|
||||
|
||||
let firmware_len = firmware.len();
|
||||
|
||||
let mut write_buf = [0; 4096];
|
||||
write_buf[0..firmware_len].copy_from_slice(firmware);
|
||||
DFU.write_blocking(&mut flash, 0, &write_buf).unwrap();
|
||||
flash.dfu().write(0, &write_buf).unwrap();
|
||||
|
||||
// On with the test
|
||||
|
||||
let mut updater = FirmwareUpdater::new(DFU, STATE);
|
||||
let flash = flash.into_async();
|
||||
let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
|
||||
dfu: flash.dfu(),
|
||||
state: flash.state(),
|
||||
});
|
||||
|
||||
let mut aligned = [0; 4];
|
||||
|
||||
assert!(block_on(updater.verify_and_mark_updated(
|
||||
&mut flash,
|
||||
&public_key.to_bytes(),
|
||||
&signature.to_bytes(),
|
||||
firmware_len as u32,
|
||||
|
@ -3,6 +3,7 @@
|
||||
use core::ops::{Bound, Range, RangeBounds};
|
||||
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
|
||||
@ -33,6 +34,52 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> {
|
||||
let len = bytes.len();
|
||||
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
|
||||
let offset = offset as usize;
|
||||
assert!(bytes.len() % WRITE_SIZE == 0);
|
||||
assert!(offset % WRITE_SIZE == 0);
|
||||
assert!(offset + bytes.len() <= SIZE);
|
||||
|
||||
if let Some(pending_successes) = self.pending_write_successes {
|
||||
if pending_successes > 0 {
|
||||
self.pending_write_successes = Some(pending_successes - 1);
|
||||
} else {
|
||||
return Err(MemFlashError);
|
||||
}
|
||||
}
|
||||
|
||||
for ((offset, mem_byte), new_byte) in self
|
||||
.mem
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.skip(offset)
|
||||
.take(bytes.len())
|
||||
.zip(bytes)
|
||||
{
|
||||
assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
|
||||
*mem_byte = *new_byte;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> {
|
||||
let from = from as usize;
|
||||
let to = to as usize;
|
||||
assert!(from % ERASE_SIZE == 0);
|
||||
assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
|
||||
for i in from..to {
|
||||
self.mem[i] = 0xFF;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
|
||||
let offset = offset as usize;
|
||||
assert!(bytes.len() % WRITE_SIZE == 0);
|
||||
@ -43,12 +90,6 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn assert_eq(&self, offset: u32, expectation: &[u8]) {
|
||||
for i in 0..expectation.len() {
|
||||
assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
|
||||
@ -77,9 +118,7 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNo
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
let len = bytes.len();
|
||||
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
|
||||
Ok(())
|
||||
self.read(offset, bytes)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
@ -93,72 +132,42 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFla
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
let from = from as usize;
|
||||
let to = to as usize;
|
||||
assert!(from % ERASE_SIZE == 0);
|
||||
assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
|
||||
for i in from..to {
|
||||
self.mem[i] = 0xFF;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
let offset = offset as usize;
|
||||
assert!(bytes.len() % WRITE_SIZE == 0);
|
||||
assert!(offset % WRITE_SIZE == 0);
|
||||
assert!(offset + bytes.len() <= SIZE);
|
||||
self.write(offset, bytes)
|
||||
}
|
||||
|
||||
if let Some(pending_successes) = self.pending_write_successes {
|
||||
if pending_successes > 0 {
|
||||
self.pending_write_successes = Some(pending_successes - 1);
|
||||
} else {
|
||||
return Err(MemFlashError);
|
||||
}
|
||||
}
|
||||
|
||||
for ((offset, mem_byte), new_byte) in self
|
||||
.mem
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.skip(offset)
|
||||
.take(bytes.len())
|
||||
.zip(bytes)
|
||||
{
|
||||
assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
|
||||
*mem_byte = *new_byte;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
<Self as ReadNorFlash>::read(self, offset, bytes)
|
||||
self.read(offset, bytes)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
<Self as ReadNorFlash>::capacity(self)
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
<Self as NorFlash>::erase(self, from, to)
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes)
|
||||
}
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
<Self as NorFlash>::write(self, offset, bytes)
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to)
|
||||
}
|
||||
}
|
||||
|
@ -1,139 +0,0 @@
|
||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
/// A region in flash used by the bootloader.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Partition {
|
||||
/// The offset into the flash where the partition starts.
|
||||
pub from: u32,
|
||||
/// The offset into the flash where the partition ends.
|
||||
pub to: u32,
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
/// Create a new partition with the provided range
|
||||
pub const fn new(from: u32, to: u32) -> Self {
|
||||
Self { from, to }
|
||||
}
|
||||
|
||||
/// Return the size of the partition
|
||||
pub const fn size(&self) -> u32 {
|
||||
self.to - self.from
|
||||
}
|
||||
|
||||
/// Read from the partition on the provided flash
|
||||
pub async fn read<F: AsyncReadNorFlash>(
|
||||
&self,
|
||||
flash: &mut F,
|
||||
offset: u32,
|
||||
bytes: &mut [u8],
|
||||
) -> Result<(), F::Error> {
|
||||
let offset = self.from as u32 + offset;
|
||||
flash.read(offset, bytes).await
|
||||
}
|
||||
|
||||
/// Write to the partition on the provided flash
|
||||
pub async fn write<F: AsyncNorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
|
||||
let offset = self.from as u32 + offset;
|
||||
flash.write(offset, bytes).await?;
|
||||
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erase part of the partition on the provided flash
|
||||
pub async fn erase<F: AsyncNorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
|
||||
let from = self.from as u32 + from;
|
||||
let to = self.from as u32 + to;
|
||||
flash.erase(from, to).await?;
|
||||
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erase the entire partition
|
||||
pub(crate) async fn wipe<F: AsyncNorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
|
||||
let from = self.from as u32;
|
||||
let to = self.to as u32;
|
||||
flash.erase(from, to).await?;
|
||||
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read from the partition on the provided flash
|
||||
pub fn read_blocking<F: ReadNorFlash>(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> {
|
||||
let offset = self.from as u32 + offset;
|
||||
flash.read(offset, bytes)
|
||||
}
|
||||
|
||||
/// Write to the partition on the provided flash
|
||||
pub fn write_blocking<F: NorFlash>(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> {
|
||||
let offset = self.from as u32 + offset;
|
||||
flash.write(offset, bytes)?;
|
||||
trace!("Wrote from 0x{:x} len {}", offset, bytes.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erase part of the partition on the provided flash
|
||||
pub fn erase_blocking<F: NorFlash>(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> {
|
||||
let from = self.from as u32 + from;
|
||||
let to = self.from as u32 + to;
|
||||
flash.erase(from, to)?;
|
||||
trace!("Erased from 0x{:x} to 0x{:x}", from, to);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erase the entire partition
|
||||
pub(crate) fn wipe_blocking<F: NorFlash>(&self, flash: &mut F) -> Result<(), F::Error> {
|
||||
let from = self.from as u32;
|
||||
let to = self.to as u32;
|
||||
flash.erase(from, to)?;
|
||||
trace!("Wiped from 0x{:x} to 0x{:x}", from, to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::mem_flash::MemFlash;
|
||||
use crate::Partition;
|
||||
|
||||
#[test]
|
||||
fn can_erase() {
|
||||
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
|
||||
let partition = Partition::new(256, 512);
|
||||
|
||||
partition.erase_blocking(&mut flash, 64, 192).unwrap();
|
||||
|
||||
for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) {
|
||||
assert_eq!(0x00, byte, "Index {}", index);
|
||||
}
|
||||
|
||||
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) {
|
||||
assert_eq!(0xFF, byte, "Index {}", index);
|
||||
}
|
||||
|
||||
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) {
|
||||
assert_eq!(0x00, byte, "Index {}", index);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_wipe() {
|
||||
let mut flash = MemFlash::<1024, 64, 4>::new(0x00);
|
||||
let partition = Partition::new(256, 512);
|
||||
|
||||
partition.wipe_blocking(&mut flash).unwrap();
|
||||
|
||||
for (index, byte) in flash.mem.iter().copied().enumerate().take(256) {
|
||||
assert_eq!(0x00, byte, "Index {}", index);
|
||||
}
|
||||
|
||||
for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) {
|
||||
assert_eq!(0xFF, byte, "Index {}", index);
|
||||
}
|
||||
|
||||
for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) {
|
||||
assert_eq!(0x00, byte, "Index {}", index);
|
||||
}
|
||||
}
|
||||
}
|
64
embassy-boot/boot/src/test_flash/asynch.rs
Normal file
64
embassy-boot/boot/src/test_flash/asynch.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use embassy_embedded_hal::flash::partition::Partition;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embedded_storage_async::nor_flash::NorFlash;
|
||||
|
||||
use crate::BootLoaderConfig;
|
||||
|
||||
pub struct AsyncTestFlash<ACTIVE, DFU, STATE>
|
||||
where
|
||||
ACTIVE: NorFlash,
|
||||
DFU: NorFlash,
|
||||
STATE: NorFlash,
|
||||
{
|
||||
active: Mutex<NoopRawMutex, ACTIVE>,
|
||||
dfu: Mutex<NoopRawMutex, DFU>,
|
||||
state: Mutex<NoopRawMutex, STATE>,
|
||||
}
|
||||
|
||||
impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
|
||||
where
|
||||
ACTIVE: NorFlash,
|
||||
DFU: NorFlash,
|
||||
STATE: NorFlash,
|
||||
{
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
|
||||
Self {
|
||||
active: Mutex::new(config.active),
|
||||
dfu: Mutex::new(config.dfu),
|
||||
state: Mutex::new(config.state),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active(&self) -> Partition<NoopRawMutex, ACTIVE> {
|
||||
Self::create_partition(&self.active)
|
||||
}
|
||||
|
||||
pub fn dfu(&self) -> Partition<NoopRawMutex, DFU> {
|
||||
Self::create_partition(&self.dfu)
|
||||
}
|
||||
|
||||
pub fn state(&self) -> Partition<NoopRawMutex, STATE> {
|
||||
Self::create_partition(&self.state)
|
||||
}
|
||||
|
||||
fn create_partition<T: NorFlash>(mutex: &Mutex<NoopRawMutex, T>) -> Partition<NoopRawMutex, T> {
|
||||
Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
|
||||
where
|
||||
ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash,
|
||||
DFU: NorFlash + embedded_storage::nor_flash::NorFlash,
|
||||
STATE: NorFlash + embedded_storage::nor_flash::NorFlash,
|
||||
{
|
||||
pub fn into_blocking(self) -> super::BlockingTestFlash<ACTIVE, DFU, STATE> {
|
||||
let config = BootLoaderConfig {
|
||||
active: self.active.into_inner(),
|
||||
dfu: self.dfu.into_inner(),
|
||||
state: self.state.into_inner(),
|
||||
};
|
||||
super::BlockingTestFlash::new(config)
|
||||
}
|
||||
}
|
69
embassy-boot/boot/src/test_flash/blocking.rs
Normal file
69
embassy-boot/boot/src/test_flash/blocking.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use core::cell::RefCell;
|
||||
|
||||
use embassy_embedded_hal::flash::partition::BlockingPartition;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_storage::nor_flash::NorFlash;
|
||||
|
||||
use crate::BootLoaderConfig;
|
||||
|
||||
pub struct BlockingTestFlash<ACTIVE, DFU, STATE>
|
||||
where
|
||||
ACTIVE: NorFlash,
|
||||
DFU: NorFlash,
|
||||
STATE: NorFlash,
|
||||
{
|
||||
active: Mutex<NoopRawMutex, RefCell<ACTIVE>>,
|
||||
dfu: Mutex<NoopRawMutex, RefCell<DFU>>,
|
||||
state: Mutex<NoopRawMutex, RefCell<STATE>>,
|
||||
}
|
||||
|
||||
impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
|
||||
where
|
||||
ACTIVE: NorFlash,
|
||||
DFU: NorFlash,
|
||||
STATE: NorFlash,
|
||||
{
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
|
||||
Self {
|
||||
active: Mutex::new(RefCell::new(config.active)),
|
||||
dfu: Mutex::new(RefCell::new(config.dfu)),
|
||||
state: Mutex::new(RefCell::new(config.state)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active(&self) -> BlockingPartition<NoopRawMutex, ACTIVE> {
|
||||
Self::create_partition(&self.active)
|
||||
}
|
||||
|
||||
pub fn dfu(&self) -> BlockingPartition<NoopRawMutex, DFU> {
|
||||
Self::create_partition(&self.dfu)
|
||||
}
|
||||
|
||||
pub fn state(&self) -> BlockingPartition<NoopRawMutex, STATE> {
|
||||
Self::create_partition(&self.state)
|
||||
}
|
||||
|
||||
pub fn create_partition<T: NorFlash>(
|
||||
mutex: &Mutex<NoopRawMutex, RefCell<T>>,
|
||||
) -> BlockingPartition<NoopRawMutex, T> {
|
||||
BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
|
||||
where
|
||||
ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
|
||||
DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash,
|
||||
STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
|
||||
{
|
||||
pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> {
|
||||
let config = BootLoaderConfig {
|
||||
active: self.active.into_inner().into_inner(),
|
||||
dfu: self.dfu.into_inner().into_inner(),
|
||||
state: self.state.into_inner().into_inner(),
|
||||
};
|
||||
super::AsyncTestFlash::new(config)
|
||||
}
|
||||
}
|
7
embassy-boot/boot/src/test_flash/mod.rs
Normal file
7
embassy-boot/boot/src/test_flash/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[cfg(feature = "nightly")]
|
||||
mod asynch;
|
||||
mod blocking;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub(crate) use asynch::AsyncTestFlash;
|
||||
pub(crate) use blocking::BlockingTestFlash;
|
@ -17,12 +17,12 @@ target = "thumbv7em-none-eabi"
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] }
|
||||
embassy-nrf = { path = "../../embassy-nrf" }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true }
|
||||
@ -36,3 +36,8 @@ defmt = [
|
||||
softdevice = [
|
||||
"nrf-softdevice-mbr",
|
||||
]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-nrf/nightly"
|
||||
]
|
||||
|
@ -6,14 +6,14 @@ An adaptation of `embassy-boot` for nRF.
|
||||
|
||||
## Features
|
||||
|
||||
* Load applications with our without the softdevice.
|
||||
* Load applications with or without the softdevice.
|
||||
* Configure bootloader partitions based on linker script.
|
||||
* Using watchdog timer to detect application failure.
|
||||
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot-nrf` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,76 +1,39 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_boot::FirmwareUpdater;
|
||||
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig};
|
||||
use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE};
|
||||
use embassy_nrf::peripherals::WDT;
|
||||
use embassy_nrf::wdt;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
|
||||
/// A bootloader for nRF devices.
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE> {
|
||||
boot: embassy_boot::BootLoader,
|
||||
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = PAGE_SIZE> {
|
||||
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
|
||||
impl Default for BootLoader<PAGE_SIZE> {
|
||||
/// Create a new bootloader instance using parameters from linker script
|
||||
fn default() -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_active_start: u32;
|
||||
static __bootloader_active_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let active = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_active_start as *const u32 as u32,
|
||||
&__bootloader_active_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
let dfu = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_dfu_start as *const u32 as u32,
|
||||
&__bootloader_dfu_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
let state = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_state_start as *const u32 as u32,
|
||||
&__bootloader_state_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
|
||||
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
|
||||
|
||||
Self::new(active, dfu, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
|
||||
{
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
|
||||
Self {
|
||||
boot: embassy_boot::BootLoader::new(active, dfu, state),
|
||||
boot: embassy_boot::BootLoader::new(config),
|
||||
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping
|
||||
/// firmware.
|
||||
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
|
||||
match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) {
|
||||
Ok(_) => self.boot.boot_address(),
|
||||
Err(_) => panic!("boot prepare error!"),
|
||||
}
|
||||
pub fn prepare(&mut self) {
|
||||
self.boot
|
||||
.prepare_boot(&mut self.aligned_buf.0)
|
||||
.expect("Boot prepare error");
|
||||
}
|
||||
|
||||
/// Boots the application without softdevice mechanisms.
|
||||
@ -79,10 +42,12 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
///
|
||||
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
|
||||
#[cfg(not(feature = "softdevice"))]
|
||||
pub unsafe fn load(&mut self, start: usize) -> ! {
|
||||
pub unsafe fn load(self, start: u32) -> ! {
|
||||
core::mem::drop(self.boot);
|
||||
|
||||
let mut p = cortex_m::Peripherals::steal();
|
||||
p.SCB.invalidate_icache();
|
||||
p.SCB.vtor.write(start as u32);
|
||||
p.SCB.vtor.write(start);
|
||||
cortex_m::asm::bootload(start as *const u32)
|
||||
}
|
||||
|
||||
@ -92,7 +57,7 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
///
|
||||
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
|
||||
#[cfg(feature = "softdevice")]
|
||||
pub unsafe fn load(&mut self, _app: usize) -> ! {
|
||||
pub unsafe fn load(&mut self, _app: u32) -> ! {
|
||||
use nrf_softdevice_mbr as mbr;
|
||||
const NRF_SUCCESS: u32 = 0;
|
||||
|
||||
|
@ -18,14 +18,14 @@ defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-rp = { path = "../../embassy-rp", default-features = false, features = ["nightly"] }
|
||||
embassy-rp = { path = "../../embassy-rp", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
embassy-time = { path = "../../embassy-time", features = ["nightly"] }
|
||||
embassy-time = { path = "../../embassy-time" }
|
||||
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[features]
|
||||
@ -40,6 +40,12 @@ log = [
|
||||
"embassy-rp/log",
|
||||
]
|
||||
debug = ["defmt-rtt"]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-rp/nightly",
|
||||
"embassy-time/nightly"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
debug = 2
|
||||
|
@ -13,7 +13,7 @@ NOTE: The applications using this bootloader should not link with the `link-rp.x
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot-rp` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_boot::FirmwareUpdater;
|
||||
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
|
||||
use embassy_rp::flash::{Flash, ERASE_SIZE};
|
||||
use embassy_rp::peripherals::{FLASH, WATCHDOG};
|
||||
use embassy_rp::watchdog::Watchdog;
|
||||
@ -12,27 +13,28 @@ use embassy_time::Duration;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
|
||||
/// A bootloader for RP2040 devices.
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE> {
|
||||
boot: embassy_boot::BootLoader,
|
||||
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = ERASE_SIZE> {
|
||||
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
|
||||
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
|
||||
{
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
|
||||
Self {
|
||||
boot: embassy_boot::BootLoader::new(active, dfu, state),
|
||||
boot: embassy_boot::BootLoader::new(config),
|
||||
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping
|
||||
/// firmware.
|
||||
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
|
||||
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
|
||||
Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(),
|
||||
Err(_) => panic!("boot prepare error!"),
|
||||
}
|
||||
pub fn prepare(&mut self) {
|
||||
self.boot
|
||||
.prepare_boot(self.aligned_buf.as_mut())
|
||||
.expect("Boot prepare error");
|
||||
}
|
||||
|
||||
/// Boots the application.
|
||||
@ -40,57 +42,20 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
/// # Safety
|
||||
///
|
||||
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
|
||||
pub unsafe fn load(&mut self, start: usize) -> ! {
|
||||
pub unsafe fn load(self, start: u32) -> ! {
|
||||
core::mem::drop(self.boot);
|
||||
|
||||
trace!("Loading app at 0x{:x}", start);
|
||||
#[allow(unused_mut)]
|
||||
let mut p = cortex_m::Peripherals::steal();
|
||||
#[cfg(not(armv6m))]
|
||||
p.SCB.invalidate_icache();
|
||||
p.SCB.vtor.write(start as u32);
|
||||
p.SCB.vtor.write(start);
|
||||
|
||||
cortex_m::asm::bootload(start as *const u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BootLoader<ERASE_SIZE> {
|
||||
/// Create a new bootloader instance using parameters from linker script
|
||||
fn default() -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_active_start: u32;
|
||||
static __bootloader_active_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let active = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_active_start as *const u32 as u32,
|
||||
&__bootloader_active_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
let dfu = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_dfu_start as *const u32 as u32,
|
||||
&__bootloader_dfu_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
let state = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_state_start as *const u32 as u32,
|
||||
&__bootloader_state_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
|
||||
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
|
||||
|
||||
Self::new(active, dfu, state)
|
||||
}
|
||||
}
|
||||
|
||||
/// A flash implementation that will feed a watchdog when touching flash.
|
||||
pub struct WatchdogFlash<'d, const SIZE: usize> {
|
||||
flash: Flash<'d, FLASH, SIZE>,
|
||||
|
@ -19,12 +19,12 @@ defmt-rtt = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
embassy-sync = { path = "../../embassy-sync" }
|
||||
embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] }
|
||||
embassy-stm32 = { path = "../../embassy-stm32", default-features = false }
|
||||
embassy-boot = { path = "../boot", default-features = false }
|
||||
cortex-m = { version = "0.7.6" }
|
||||
cortex-m-rt = { version = "0.7" }
|
||||
embedded-storage = "0.3.0"
|
||||
embedded-storage-async = "0.4.0"
|
||||
embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
[features]
|
||||
@ -39,6 +39,11 @@ log = [
|
||||
"embassy-stm32/log",
|
||||
]
|
||||
debug = ["defmt-rtt"]
|
||||
nightly = [
|
||||
"dep:embedded-storage-async",
|
||||
"embassy-boot/nightly",
|
||||
"embassy-stm32/nightly"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
debug = 2
|
||||
|
@ -11,7 +11,7 @@ An adaptation of `embassy-boot` for STM32.
|
||||
|
||||
## Minimum supported Rust version (MSRV)
|
||||
|
||||
`embassy-boot-stm32` requires Rust nightly to compile as it relies on async traits for interacting with the flash peripherals.
|
||||
`embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,33 +1,36 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
mod fmt;
|
||||
|
||||
pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State};
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use embassy_boot::FirmwareUpdater;
|
||||
pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State};
|
||||
use embedded_storage::nor_flash::NorFlash;
|
||||
|
||||
/// A bootloader for STM32 devices.
|
||||
pub struct BootLoader<const BUFFER_SIZE: usize> {
|
||||
boot: embassy_boot::BootLoader,
|
||||
pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> {
|
||||
boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>,
|
||||
aligned_buf: AlignedBuffer<BUFFER_SIZE>,
|
||||
}
|
||||
|
||||
impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>
|
||||
BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE>
|
||||
{
|
||||
/// Create a new bootloader instance using the supplied partitions for active, dfu and state.
|
||||
pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
|
||||
pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self {
|
||||
Self {
|
||||
boot: embassy_boot::BootLoader::new(active, dfu, state),
|
||||
boot: embassy_boot::BootLoader::new(config),
|
||||
aligned_buf: AlignedBuffer([0; BUFFER_SIZE]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspect the bootloader state and perform actions required before booting, such as swapping
|
||||
/// firmware.
|
||||
pub fn prepare<F: FlashConfig>(&mut self, flash: &mut F) -> usize {
|
||||
match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) {
|
||||
Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(),
|
||||
Err(_) => panic!("boot prepare error!"),
|
||||
}
|
||||
pub fn prepare(&mut self) {
|
||||
self.boot
|
||||
.prepare_boot(self.aligned_buf.as_mut())
|
||||
.expect("Boot prepare error");
|
||||
}
|
||||
|
||||
/// Boots the application.
|
||||
@ -35,53 +38,16 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
|
||||
/// # Safety
|
||||
///
|
||||
/// This modifies the stack pointer and reset vector and will run code placed in the active partition.
|
||||
pub unsafe fn load(&mut self, start: usize) -> ! {
|
||||
pub unsafe fn load(self, start: u32) -> ! {
|
||||
core::mem::drop(self.boot);
|
||||
|
||||
trace!("Loading app at 0x{:x}", start);
|
||||
#[allow(unused_mut)]
|
||||
let mut p = cortex_m::Peripherals::steal();
|
||||
#[cfg(not(armv6m))]
|
||||
p.SCB.invalidate_icache();
|
||||
p.SCB.vtor.write(start as u32);
|
||||
p.SCB.vtor.write(start);
|
||||
|
||||
cortex_m::asm::bootload(start as *const u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const BUFFER_SIZE: usize> Default for BootLoader<BUFFER_SIZE> {
|
||||
/// Create a new bootloader instance using parameters from linker script
|
||||
fn default() -> Self {
|
||||
extern "C" {
|
||||
static __bootloader_state_start: u32;
|
||||
static __bootloader_state_end: u32;
|
||||
static __bootloader_active_start: u32;
|
||||
static __bootloader_active_end: u32;
|
||||
static __bootloader_dfu_start: u32;
|
||||
static __bootloader_dfu_end: u32;
|
||||
}
|
||||
|
||||
let active = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_active_start as *const u32 as u32,
|
||||
&__bootloader_active_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
let dfu = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_dfu_start as *const u32 as u32,
|
||||
&__bootloader_dfu_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
let state = unsafe {
|
||||
Partition::new(
|
||||
&__bootloader_state_start as *const u32 as u32,
|
||||
&__bootloader_state_end as *const u32 as u32,
|
||||
)
|
||||
};
|
||||
|
||||
trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
|
||||
trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
|
||||
trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
|
||||
|
||||
Self::new(active, dfu, state)
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
[package]
|
||||
name = "embassy-cortex-m"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-cortex-m-v$VERSION/embassy-cortex-m/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-cortex-m/src/"
|
||||
features = ["prio-bits-3"]
|
||||
flavors = [
|
||||
{ name = "thumbv6m-none-eabi", target = "thumbv6m-none-eabi", features = [] },
|
||||
{ name = "thumbv7m-none-eabi", target = "thumbv7m-none-eabi", features = [] },
|
||||
{ name = "thumbv7em-none-eabi", target = "thumbv7em-none-eabi", features = [] },
|
||||
{ name = "thumbv7em-none-eabihf", target = "thumbv7em-none-eabihf", features = [] },
|
||||
{ name = "thumbv8m.base-none-eabi", target = "thumbv8m.base-none-eabi", features = [] },
|
||||
{ name = "thumbv8m.main-none-eabi", target = "thumbv8m.main-none-eabi", features = [] },
|
||||
{ name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] },
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Define the number of NVIC priority bits.
|
||||
prio-bits-0 = []
|
||||
prio-bits-1 = []
|
||||
prio-bits-2 = []
|
||||
prio-bits-3 = []
|
||||
prio-bits-4 = []
|
||||
prio-bits-5 = []
|
||||
prio-bits-6 = []
|
||||
prio-bits-7 = []
|
||||
prio-bits-8 = []
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embassy-executor = { version = "0.1.0", path = "../embassy-executor"}
|
||||
embassy-macros = { version = "0.1.0", path = "../embassy-macros"}
|
||||
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common"}
|
||||
atomic-polyfill = "1.0.1"
|
||||
critical-section = "1.1"
|
||||
cfg-if = "1.0.0"
|
||||
cortex-m = "0.7.6"
|
||||
|
@ -1,10 +0,0 @@
|
||||
//! Embassy executor and interrupt handling specific to cortex-m devices.
|
||||
#![no_std]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
pub use embassy_executor as executor;
|
||||
pub mod interrupt;
|
||||
pub mod peripheral;
|
@ -1,144 +0,0 @@
|
||||
//! Peripheral interrupt handling specific to cortex-m devices.
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use cortex_m::peripheral::scb::VectActive;
|
||||
use cortex_m::peripheral::{NVIC, SCB};
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
|
||||
use crate::interrupt::{Interrupt, InterruptExt, Priority};
|
||||
|
||||
/// A type which can be used as state with `PeripheralMutex`.
|
||||
///
|
||||
/// It needs to be `Send` because `&mut` references are sent back and forth between the 'thread' which owns the `PeripheralMutex` and the interrupt,
|
||||
/// and `&mut T` is only `Send` where `T: Send`.
|
||||
pub trait PeripheralState: Send {
|
||||
/// The interrupt that is used for this peripheral.
|
||||
type Interrupt: Interrupt;
|
||||
|
||||
/// The interrupt handler that should be invoked for the peripheral. Implementations need to clear the appropriate interrupt flags to ensure the handle will not be called again.
|
||||
fn on_interrupt(&mut self);
|
||||
}
|
||||
|
||||
/// A type for storing the state of a peripheral that can be stored in a static.
|
||||
pub struct StateStorage<S>(MaybeUninit<S>);
|
||||
|
||||
impl<S> StateStorage<S> {
|
||||
/// Create a new instance for storing peripheral state.
|
||||
pub const fn new() -> Self {
|
||||
Self(MaybeUninit::uninit())
|
||||
}
|
||||
}
|
||||
|
||||
/// A type for a peripheral that keeps the state of a peripheral that can be accessed from thread mode and an interrupt handler in
|
||||
/// a safe way.
|
||||
pub struct PeripheralMutex<'a, S: PeripheralState> {
|
||||
state: *mut S,
|
||||
irq: PeripheralRef<'a, S::Interrupt>,
|
||||
}
|
||||
|
||||
/// Whether `irq` can be preempted by the current interrupt.
|
||||
pub(crate) fn can_be_preempted(irq: &impl Interrupt) -> bool {
|
||||
match SCB::vect_active() {
|
||||
// Thread mode can't preempt anything.
|
||||
VectActive::ThreadMode => false,
|
||||
// Exceptions don't always preempt interrupts,
|
||||
// but there isn't much of a good reason to be keeping a `PeripheralMutex` in an exception anyway.
|
||||
VectActive::Exception(_) => true,
|
||||
VectActive::Interrupt { irqn } => {
|
||||
#[derive(Clone, Copy)]
|
||||
struct NrWrap(u16);
|
||||
unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap {
|
||||
fn number(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
NVIC::get_priority(NrWrap(irqn.into())) < irq.get_priority().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: PeripheralState> PeripheralMutex<'a, S> {
|
||||
/// Create a new `PeripheralMutex` wrapping `irq`, with `init` initializing the initial state.
|
||||
///
|
||||
/// Registers `on_interrupt` as the `irq`'s handler, and enables it.
|
||||
pub fn new(
|
||||
irq: impl Peripheral<P = S::Interrupt> + 'a,
|
||||
storage: &'a mut StateStorage<S>,
|
||||
init: impl FnOnce() -> S,
|
||||
) -> Self {
|
||||
into_ref!(irq);
|
||||
|
||||
if can_be_preempted(&*irq) {
|
||||
panic!(
|
||||
"`PeripheralMutex` cannot be created in an interrupt with higher priority than the interrupt it wraps"
|
||||
);
|
||||
}
|
||||
|
||||
let state_ptr = storage.0.as_mut_ptr();
|
||||
|
||||
// Safety: The pointer is valid and not used by anyone else
|
||||
// because we have the `&mut StateStorage`.
|
||||
unsafe { state_ptr.write(init()) };
|
||||
|
||||
irq.disable();
|
||||
irq.set_handler(|p| unsafe {
|
||||
// Safety: it's OK to get a &mut to the state, since
|
||||
// - We checked that the thread owning the `PeripheralMutex` can't preempt us in `new`.
|
||||
// Interrupts' priorities can only be changed with raw embassy `Interrupts`,
|
||||
// which can't safely store a `PeripheralMutex` across invocations.
|
||||
// - We can't have preempted a with() call because the irq is disabled during it.
|
||||
let state = &mut *(p as *mut S);
|
||||
state.on_interrupt();
|
||||
});
|
||||
irq.set_handler_context(state_ptr as *mut ());
|
||||
irq.enable();
|
||||
|
||||
Self { irq, state: state_ptr }
|
||||
}
|
||||
|
||||
/// Access the peripheral state ensuring interrupts are disabled so that the state can be
|
||||
/// safely accessed.
|
||||
pub fn with<R>(&mut self, f: impl FnOnce(&mut S) -> R) -> R {
|
||||
self.irq.disable();
|
||||
|
||||
// Safety: it's OK to get a &mut to the state, since the irq is disabled.
|
||||
let state = unsafe { &mut *self.state };
|
||||
let r = f(state);
|
||||
|
||||
self.irq.enable();
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
/// Returns whether the wrapped interrupt is currently in a pending state.
|
||||
pub fn is_pending(&self) -> bool {
|
||||
self.irq.is_pending()
|
||||
}
|
||||
|
||||
/// Forces the wrapped interrupt into a pending state.
|
||||
pub fn pend(&self) {
|
||||
self.irq.pend()
|
||||
}
|
||||
|
||||
/// Forces the wrapped interrupt out of a pending state.
|
||||
pub fn unpend(&self) {
|
||||
self.irq.unpend()
|
||||
}
|
||||
|
||||
/// Gets the priority of the wrapped interrupt.
|
||||
pub fn priority(&self) -> Priority {
|
||||
self.irq.get_priority()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: PeripheralState> Drop for PeripheralMutex<'a, S> {
|
||||
fn drop(&mut self) {
|
||||
self.irq.disable();
|
||||
self.irq.remove_handler();
|
||||
|
||||
// safety:
|
||||
// - we initialized the state in `new`, so we know it's initialized.
|
||||
// - the irq is disabled, so it won't preempt us while dropping.
|
||||
unsafe { self.state.drop_in_place() }
|
||||
}
|
||||
}
|
@ -14,11 +14,14 @@ target = "x86_64-unknown-linux-gnu"
|
||||
[features]
|
||||
std = []
|
||||
# Enable nightly-only features
|
||||
nightly = ["embedded-hal-async", "embedded-storage-async"]
|
||||
nightly = ["embassy-futures", "embedded-hal-async", "embedded-storage-async"]
|
||||
|
||||
[dependencies]
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true }
|
||||
embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
||||
"unproven",
|
||||
] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
|
||||
embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true }
|
||||
embedded-storage = "0.3.0"
|
||||
@ -26,3 +29,7 @@ embedded-storage-async = { version = "0.4.0", optional = true }
|
||||
nb = "1.0.0"
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
critical-section = { version = "1.1.1", features = ["std"] }
|
||||
futures-test = "0.3.17"
|
||||
|
@ -1,5 +1,3 @@
|
||||
//! Adapters between embedded-hal traits.
|
||||
|
||||
use embedded_hal_02::{blocking, serial};
|
||||
|
||||
/// Wrapper that implements async traits using blocking implementations.
|
||||
@ -131,48 +129,6 @@ where
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
#[cfg(feature = "_todo_embedded_hal_serial")]
|
||||
impl<T, E> embedded_hal_async::serial::Read for BlockingAsync<T>
|
||||
where
|
||||
T: serial::Read<u8, Error = E>,
|
||||
E: embedded_hal_1::serial::Error + 'static,
|
||||
{
|
||||
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
|
||||
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
|
||||
async move {
|
||||
let mut pos = 0;
|
||||
while pos < buf.len() {
|
||||
match self.wrapped.read() {
|
||||
Err(nb::Error::WouldBlock) => {}
|
||||
Err(nb::Error::Other(e)) => return Err(e),
|
||||
Ok(b) => {
|
||||
buf[pos] = b;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "_todo_embedded_hal_serial")]
|
||||
impl<T, E> embedded_hal_async::serial::Write for BlockingAsync<T>
|
||||
where
|
||||
T: blocking::serial::Write<u8, Error = E> + serial::Read<u8, Error = E>,
|
||||
E: embedded_hal_1::serial::Error + 'static,
|
||||
{
|
||||
type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
|
||||
fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
|
||||
async move { self.wrapped.bwrite_all(buf) }
|
||||
}
|
||||
|
||||
type FlushFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where T: 'a;
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
async move { self.wrapped.bflush() }
|
||||
}
|
||||
}
|
||||
|
||||
/// NOR flash wrapper
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
7
embassy-embedded-hal/src/adapter/mod.rs
Normal file
7
embassy-embedded-hal/src/adapter/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! Adapters between embedded-hal traits.
|
||||
|
||||
mod blocking_async;
|
||||
mod yielding_async;
|
||||
|
||||
pub use blocking_async::BlockingAsync;
|
||||
pub use yielding_async::YieldingAsync;
|
184
embassy-embedded-hal/src/adapter/yielding_async.rs
Normal file
184
embassy-embedded-hal/src/adapter/yielding_async.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use embassy_futures::yield_now;
|
||||
|
||||
/// Wrapper that yields for each operation to the wrapped instance
|
||||
///
|
||||
/// This can be used in combination with BlockingAsync<T> to enforce yields
|
||||
/// between long running blocking operations.
|
||||
pub struct YieldingAsync<T> {
|
||||
wrapped: T,
|
||||
}
|
||||
|
||||
impl<T> YieldingAsync<T> {
|
||||
/// Create a new instance of a wrapper that yields after each operation.
|
||||
pub fn new(wrapped: T) -> Self {
|
||||
Self { wrapped }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// I2C implementations
|
||||
//
|
||||
impl<T> embedded_hal_1::i2c::ErrorType for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_1::i2c::ErrorType,
|
||||
{
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::i2c::I2c for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::i2c::I2c,
|
||||
{
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(address, read).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(address, write).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write_read(address, write, read).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
self.wrapped.transaction(address, operations).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SPI implementations
|
||||
//
|
||||
|
||||
impl<T> embedded_hal_async::spi::ErrorType for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::ErrorType,
|
||||
{
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::spi::SpiBus<u8> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBus,
|
||||
{
|
||||
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> {
|
||||
self.wrapped.flush().await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::spi::SpiBusWrite<u8> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBusWrite<u8>,
|
||||
{
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::spi::SpiBusRead<u8> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBusRead<u8>,
|
||||
{
|
||||
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// NOR flash implementations
|
||||
///
|
||||
impl<T: embedded_storage::nor_flash::ErrorType> embedded_storage::nor_flash::ErrorType for YieldingAsync<T> {
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T: embedded_storage_async::nor_flash::ReadNorFlash> embedded_storage_async::nor_flash::ReadNorFlash
|
||||
for YieldingAsync<T>
|
||||
{
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(offset, bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.wrapped.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: embedded_storage_async::nor_flash::NorFlash> embedded_storage_async::nor_flash::NorFlash for YieldingAsync<T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(offset, bytes).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
// Yield between each actual erase
|
||||
for from in (from..to).step_by(T::ERASE_SIZE) {
|
||||
let to = core::cmp::min(from + T::ERASE_SIZE as u32, to);
|
||||
self.wrapped.erase(from, to).await?;
|
||||
yield_now().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embedded_storage_async::nor_flash::NorFlash;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
let mut yielding = YieldingAsync::new(flash);
|
||||
|
||||
yielding.erase(0, 256).await.unwrap();
|
||||
|
||||
let flash = yielding.wrapped;
|
||||
assert_eq!(2, flash.erases.len());
|
||||
assert_eq!((0, 128), flash.erases[0]);
|
||||
assert_eq!((128, 256), flash.erases[1]);
|
||||
}
|
||||
}
|
228
embassy-embedded-hal/src/flash/concat_flash.rs
Normal file
228
embassy-embedded-hal/src/flash/concat_flash.rs
Normal file
@ -0,0 +1,228 @@
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
/// Convenience helper for concatenating two consecutive flashes into one.
|
||||
/// This is especially useful if used with "flash regions", where one may
|
||||
/// want to concatenate multiple regions into one larger region.
|
||||
pub struct ConcatFlash<First, Second>(First, Second);
|
||||
|
||||
impl<First, Second> ConcatFlash<First, Second> {
|
||||
/// Create a new flash that concatenates two consecutive flashes.
|
||||
pub fn new(first: First, second: Second) -> Self {
|
||||
Self(first, second)
|
||||
}
|
||||
}
|
||||
|
||||
const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize {
|
||||
if first_read_size != second_read_size {
|
||||
panic!("The read size for the concatenated flashes must be the same");
|
||||
}
|
||||
first_read_size
|
||||
}
|
||||
|
||||
const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize {
|
||||
if first_write_size != second_write_size {
|
||||
panic!("The write size for the concatenated flashes must be the same");
|
||||
}
|
||||
first_write_size
|
||||
}
|
||||
|
||||
const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize {
|
||||
let max_erase_size = if first_erase_size > second_erase_size {
|
||||
first_erase_size
|
||||
} else {
|
||||
second_erase_size
|
||||
};
|
||||
if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 {
|
||||
panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size");
|
||||
}
|
||||
max_erase_size
|
||||
}
|
||||
|
||||
impl<First, Second, E> ErrorType for ConcatFlash<First, Second>
|
||||
where
|
||||
First: ErrorType<Error = E>,
|
||||
Second: ErrorType<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
impl<First, Second, E> ReadNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: ReadNorFlash<Error = E>,
|
||||
Second: ReadNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
|
||||
|
||||
fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.read(offset, &mut bytes[..len])?;
|
||||
offset += len as u32;
|
||||
bytes = &mut bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.read(offset - self.0.capacity() as u32, bytes)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.0.capacity() + self.1.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<First, Second, E> NorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: NorFlash<Error = E>,
|
||||
Second: NorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
|
||||
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
|
||||
|
||||
fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.write(offset, &bytes[..len])?;
|
||||
offset += len as u32;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.write(offset - self.0.capacity() as u32, bytes)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
|
||||
if from < self.0.capacity() as u32 {
|
||||
let to = core::cmp::min(self.0.capacity() as u32, to);
|
||||
self.0.erase(from, to)?;
|
||||
from = self.0.capacity() as u32;
|
||||
}
|
||||
|
||||
if from < to {
|
||||
self.1
|
||||
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<First, Second, E> AsyncReadNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: AsyncReadNorFlash<Error = E>,
|
||||
Second: AsyncReadNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
|
||||
|
||||
async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.read(offset, &mut bytes[..len]).await?;
|
||||
offset += len as u32;
|
||||
bytes = &mut bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.read(offset - self.0.capacity() as u32, bytes).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.0.capacity() + self.1.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<First, Second, E> AsyncNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: AsyncNorFlash<Error = E>,
|
||||
Second: AsyncNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
|
||||
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
|
||||
|
||||
async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.write(offset, &bytes[..len]).await?;
|
||||
offset += len as u32;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.write(offset - self.0.capacity() as u32, bytes).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
|
||||
if from < self.0.capacity() as u32 {
|
||||
let to = core::cmp::min(self.0.capacity() as u32, to);
|
||||
self.0.erase(from, to).await?;
|
||||
from = self.0.capacity() as u32;
|
||||
}
|
||||
|
||||
if from < to {
|
||||
self.1
|
||||
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
||||
|
||||
use super::ConcatFlash;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_write_and_read_across_flashes() {
|
||||
let first = MemFlash::<64, 16, 4>::default();
|
||||
let second = MemFlash::<64, 64, 4>::default();
|
||||
let mut f = ConcatFlash::new(first, second);
|
||||
|
||||
f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap();
|
||||
|
||||
assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]);
|
||||
assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
f.read(60, &mut read_buf).unwrap();
|
||||
|
||||
assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_erase_across_flashes() {
|
||||
let first = MemFlash::<128, 16, 4>::new(0x00);
|
||||
let second = MemFlash::<128, 64, 4>::new(0x00);
|
||||
let mut f = ConcatFlash::new(first, second);
|
||||
|
||||
f.erase(64, 192).unwrap();
|
||||
|
||||
assert_eq!(&[0x00; 64], &f.0.mem[0..64]);
|
||||
assert_eq!(&[0xff; 64], &f.0.mem[64..128]);
|
||||
assert_eq!(&[0xff; 64], &f.1.mem[0..64]);
|
||||
assert_eq!(&[0x00; 64], &f.1.mem[64..128]);
|
||||
}
|
||||
}
|
128
embassy-embedded-hal/src/flash/mem_flash.rs
Normal file
128
embassy-embedded-hal/src/flash/mem_flash.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
#[cfg(feature = "nightly")]
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub(crate) struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
|
||||
pub mem: [u8; SIZE],
|
||||
pub writes: Vec<(u32, usize)>,
|
||||
pub erases: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> {
|
||||
#[allow(unused)]
|
||||
pub const fn new(fill: u8) -> Self {
|
||||
Self {
|
||||
mem: [fill; SIZE],
|
||||
writes: Vec::new(),
|
||||
erases: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) {
|
||||
let len = bytes.len();
|
||||
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) {
|
||||
self.writes.push((offset, bytes.len()));
|
||||
let offset = offset as usize;
|
||||
assert_eq!(0, bytes.len() % WRITE_SIZE);
|
||||
assert_eq!(0, offset % WRITE_SIZE);
|
||||
assert!(offset + bytes.len() <= SIZE);
|
||||
|
||||
self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) {
|
||||
self.erases.push((from, to));
|
||||
let from = from as usize;
|
||||
let to = to as usize;
|
||||
assert_eq!(0, from % ERASE_SIZE);
|
||||
assert_eq!(0, to % ERASE_SIZE);
|
||||
self.mem[from..to].fill(0xff);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(0xff)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
type Error = core::convert::Infallible;
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
8
embassy-embedded-hal/src/flash/mod.rs
Normal file
8
embassy-embedded-hal/src/flash/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
//! Utilities related to flash.
|
||||
|
||||
mod concat_flash;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod mem_flash;
|
||||
pub mod partition;
|
||||
|
||||
pub use concat_flash::ConcatFlash;
|
139
embassy-embedded-hal/src/flash/partition/asynch.rs
Normal file
139
embassy-embedded-hal/src/flash/partition/asynch.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embedded_storage::nor_flash::ErrorType;
|
||||
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// A logical partition of an underlying shared flash
|
||||
///
|
||||
/// A partition holds an offset and a size of the flash,
|
||||
/// and is restricted to operate with that range.
|
||||
/// There is no guarantee that muliple partitions on the same flash
|
||||
/// operate on mutually exclusive ranges - such a separation is up to
|
||||
/// the user to guarantee.
|
||||
pub struct Partition<'a, M: RawMutex, T: NorFlash> {
|
||||
flash: &'a Mutex<M, T>,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> {
|
||||
/// Create a new partition
|
||||
pub const fn new(flash: &'a Mutex<M, T>, offset: u32, size: u32) -> Self {
|
||||
if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0
|
||||
{
|
||||
panic!("Partition offset must be a multiple of read, write and erase size");
|
||||
}
|
||||
if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 {
|
||||
panic!("Partition size must be a multiple of read, write and erase size");
|
||||
}
|
||||
Self { flash, offset, size }
|
||||
}
|
||||
|
||||
/// Get the partition offset within the flash
|
||||
pub const fn offset(&self) -> u32 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
/// Get the partition size
|
||||
pub const fn size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ErrorType for Partition<'_, M, T> {
|
||||
type Error = Error<T::Error>;
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ReadNorFlash for Partition<'_, M, T> {
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash.read(self.offset + offset, bytes).await.map_err(Error::Flash)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.size as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> NorFlash for Partition<'_, M, T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash.write(self.offset + offset, bytes).await.map_err(Error::Flash)
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
if to > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash
|
||||
.erase(self.offset + from, self.offset + to)
|
||||
.await
|
||||
.map_err(Error::Flash)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_read() {
|
||||
let mut flash = MemFlash::<1024, 128, 4>::default();
|
||||
flash.mem[132..132 + 8].fill(0xAA);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
partition.read(4, &mut read_buf).await.unwrap();
|
||||
|
||||
assert!(read_buf.iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_write() {
|
||||
let flash = MemFlash::<1024, 128, 4>::default();
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
let write_buf = [0xAA; 8];
|
||||
partition.write(4, &write_buf).await.unwrap();
|
||||
|
||||
let flash = flash.try_lock().unwrap();
|
||||
assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
partition.erase(0, 128).await.unwrap();
|
||||
|
||||
let flash = flash.try_lock().unwrap();
|
||||
assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none());
|
||||
}
|
||||
}
|
149
embassy-embedded-hal/src/flash/partition/blocking.rs
Normal file
149
embassy-embedded-hal/src/flash/partition/blocking.rs
Normal file
@ -0,0 +1,149 @@
|
||||
use core::cell::RefCell;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// A logical partition of an underlying shared flash
|
||||
///
|
||||
/// A partition holds an offset and a size of the flash,
|
||||
/// and is restricted to operate with that range.
|
||||
/// There is no guarantee that muliple partitions on the same flash
|
||||
/// operate on mutually exclusive ranges - such a separation is up to
|
||||
/// the user to guarantee.
|
||||
pub struct BlockingPartition<'a, M: RawMutex, T: NorFlash> {
|
||||
flash: &'a Mutex<M, RefCell<T>>,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> {
|
||||
/// Create a new partition
|
||||
pub const fn new(flash: &'a Mutex<M, RefCell<T>>, offset: u32, size: u32) -> Self {
|
||||
if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0
|
||||
{
|
||||
panic!("Partition offset must be a multiple of read, write and erase size");
|
||||
}
|
||||
if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 {
|
||||
panic!("Partition size must be a multiple of read, write and erase size");
|
||||
}
|
||||
Self { flash, offset, size }
|
||||
}
|
||||
|
||||
/// Get the partition offset within the flash
|
||||
pub const fn offset(&self) -> u32 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
/// Get the partition size
|
||||
pub const fn size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ErrorType for BlockingPartition<'_, M, T> {
|
||||
type Error = Error<T::Error>;
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ReadNorFlash for BlockingPartition<'_, M, T> {
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
self.flash.lock(|flash| {
|
||||
flash
|
||||
.borrow_mut()
|
||||
.read(self.offset + offset, bytes)
|
||||
.map_err(Error::Flash)
|
||||
})
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.size as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> NorFlash for BlockingPartition<'_, M, T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
self.flash.lock(|flash| {
|
||||
flash
|
||||
.borrow_mut()
|
||||
.write(self.offset + offset, bytes)
|
||||
.map_err(Error::Flash)
|
||||
})
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
if to > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
self.flash.lock(|flash| {
|
||||
flash
|
||||
.borrow_mut()
|
||||
.erase(self.offset + from, self.offset + to)
|
||||
.map_err(Error::Flash)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_read() {
|
||||
let mut flash = MemFlash::<1024, 128, 4>::default();
|
||||
flash.mem[132..132 + 8].fill(0xAA);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
|
||||
let mut partition = BlockingPartition::new(&flash, 128, 256);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
partition.read(4, &mut read_buf).unwrap();
|
||||
|
||||
assert!(read_buf.iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_write() {
|
||||
let flash = MemFlash::<1024, 128, 4>::default();
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
|
||||
let mut partition = BlockingPartition::new(&flash, 128, 256);
|
||||
|
||||
let write_buf = [0xAA; 8];
|
||||
partition.write(4, &write_buf).unwrap();
|
||||
|
||||
let flash = flash.into_inner().take();
|
||||
assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
|
||||
let mut partition = BlockingPartition::new(&flash, 128, 256);
|
||||
|
||||
partition.erase(0, 128).unwrap();
|
||||
|
||||
let flash = flash.into_inner().take();
|
||||
assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none());
|
||||
}
|
||||
}
|
30
embassy-embedded-hal/src/flash/partition/mod.rs
Normal file
30
embassy-embedded-hal/src/flash/partition/mod.rs
Normal file
@ -0,0 +1,30 @@
|
||||
//! Flash Partition utilities
|
||||
|
||||
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
mod asynch;
|
||||
mod blocking;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use asynch::Partition;
|
||||
pub use blocking::BlockingPartition;
|
||||
|
||||
/// Partition error
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error<T> {
|
||||
/// The requested flash area is outside the partition
|
||||
OutOfBounds,
|
||||
/// Underlying flash error
|
||||
Flash(T),
|
||||
}
|
||||
|
||||
impl<T: NorFlashError> NorFlashError for Error<T> {
|
||||
fn kind(&self) -> NorFlashErrorKind {
|
||||
match self {
|
||||
Error::OutOfBounds => NorFlashErrorKind::OutOfBounds,
|
||||
Error::Flash(f) => f.kind(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(
|
||||
feature = "nightly",
|
||||
feature(type_alias_impl_trait, async_fn_in_trait, impl_trait_projections, try_blocks)
|
||||
)]
|
||||
#![cfg_attr(feature = "nightly", allow(incomplete_features))]
|
||||
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections, try_blocks))]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Utilities to use `embedded-hal` traits with Embassy.
|
||||
@ -11,6 +7,8 @@
|
||||
#[cfg(feature = "nightly")]
|
||||
pub mod adapter;
|
||||
|
||||
pub mod flash;
|
||||
|
||||
pub mod shared_bus;
|
||||
|
||||
/// Set the configuration of a peripheral driver.
|
||||
|
@ -84,9 +84,11 @@ where
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_async::i2c::Operation<'_>],
|
||||
) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let _ = address;
|
||||
let _ = operations;
|
||||
todo!()
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.transaction(address, operations)
|
||||
.await
|
||||
.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,8 +152,11 @@ where
|
||||
}
|
||||
|
||||
async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> {
|
||||
let _ = address;
|
||||
let _ = operations;
|
||||
todo!()
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config);
|
||||
bus.transaction(address, operations)
|
||||
.await
|
||||
.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,12 @@
|
||||
//!
|
||||
//! # Example (nrf52)
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```rust,ignore
|
||||
//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
||||
//!
|
||||
//! static I2C_BUS: StaticCell<NoopMutex<RefCell<Twim<TWISPI0>>>> = StaticCell::new();
|
||||
//! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0);
|
||||
//! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, Config::default());
|
||||
//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default());
|
||||
//! let i2c_bus = NoopMutex::new(RefCell::new(i2c));
|
||||
//! let i2c_bus = I2C_BUS.init(i2c_bus);
|
||||
//!
|
||||
|
@ -2,13 +2,12 @@
|
||||
//!
|
||||
//! # Example (nrf52)
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```rust,ignore
|
||||
//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
||||
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
||||
//!
|
||||
//! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new();
|
||||
//! let irq = interrupt::take!(SPIM3);
|
||||
//! let spi = Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, Config::default());
|
||||
//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default());
|
||||
//! let spi_bus = NoopMutex::new(RefCell::new(spi));
|
||||
//! let spi_bus = SPI_BUS.init(spi_bus);
|
||||
//!
|
||||
|
23
embassy-executor/CHANGELOG.md
Normal file
23
embassy-executor/CHANGELOG.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.2.0 - 2023-04-27
|
||||
|
||||
- Replace unnecessary atomics in runqueue
|
||||
- add Pender, rework Cargo features.
|
||||
- add support for turbo-wakers.
|
||||
- Allow TaskStorage to auto-implement `Sync`
|
||||
- Use AtomicPtr for signal_ctx, removes 1 unsafe.
|
||||
- Replace unsound critical sections with atomics
|
||||
|
||||
## 0.1.1 - 2022-11-23
|
||||
|
||||
- Fix features for documentation
|
||||
|
||||
## 0.1.0 - 2022-11-23
|
||||
|
||||
- First release
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-executor"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "async/await executor designed for embedded usage"
|
||||
@ -61,11 +61,11 @@ log = { version = "0.4.14", optional = true }
|
||||
rtos-trace = { version = "0.1.2", optional = true }
|
||||
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
embassy-macros = { version = "0.1.0", path = "../embassy-macros" }
|
||||
embassy-macros = { version = "0.2.0", path = "../embassy-macros" }
|
||||
embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true}
|
||||
atomic-polyfill = "1.0.1"
|
||||
critical-section = "1.1"
|
||||
static_cell = "1.0"
|
||||
static_cell = "1.1"
|
||||
|
||||
# arch-cortex-m dependencies
|
||||
cortex-m = { version = "0.7.6", optional = true }
|
||||
|
@ -205,5 +205,20 @@ mod interrupt {
|
||||
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
|
||||
/// Get a SendSpawner for this executor
|
||||
///
|
||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on this
|
||||
/// executor.
|
||||
///
|
||||
/// This MUST only be called on an executor that has already been spawned.
|
||||
/// The function will panic otherwise.
|
||||
pub fn spawner(&'static self) -> crate::SendSpawner {
|
||||
if !self.started.load(Ordering::Acquire) {
|
||||
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
|
||||
}
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,13 +63,22 @@ mod thread {
|
||||
loop {
|
||||
unsafe {
|
||||
self.inner.poll();
|
||||
|
||||
// Manual critical section implementation that only masks interrupts handlers.
|
||||
// We must not acquire the cross-core on dual-core systems because that would
|
||||
// prevent the other core from doing useful work while this core is sleeping.
|
||||
let token: critical_section::RawRestoreState;
|
||||
core::arch::asm!("rsil {0}, 5", out(reg) token);
|
||||
|
||||
// we do not care about race conditions between the load and store operations, interrupts
|
||||
// will only set this value to true.
|
||||
// if there is work to do, loop back to polling
|
||||
// TODO can we relax this?
|
||||
critical_section::with(|_| {
|
||||
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
|
||||
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
|
||||
|
||||
core::arch::asm!(
|
||||
"wsr.ps {0}",
|
||||
"rsync", in(reg) token)
|
||||
} else {
|
||||
// waiti sets the PS.INTLEVEL when slipping into sleep
|
||||
// because critical sections in Xtensa are implemented via increasing
|
||||
@ -77,7 +86,6 @@ mod thread {
|
||||
// take care not add code after `waiti` if it needs to be inside the CS
|
||||
core::arch::asm!("waiti 0"); // critical section ends here
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,16 @@ use core::ptr::NonNull;
|
||||
use atomic_polyfill::{AtomicPtr, Ordering};
|
||||
|
||||
use super::{TaskHeader, TaskRef};
|
||||
use crate::raw::util::SyncUnsafeCell;
|
||||
|
||||
pub(crate) struct RunQueueItem {
|
||||
next: AtomicPtr<TaskHeader>,
|
||||
next: SyncUnsafeCell<Option<TaskRef>>,
|
||||
}
|
||||
|
||||
impl RunQueueItem {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next: AtomicPtr::new(ptr::null_mut()),
|
||||
next: SyncUnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,7 +52,12 @@ impl RunQueue {
|
||||
self.head
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
|
||||
was_empty = prev.is_null();
|
||||
task.header().run_queue_item.next.store(prev, Ordering::Relaxed);
|
||||
unsafe {
|
||||
// safety: the pointer is either null or valid
|
||||
let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr()));
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
task.header().run_queue_item.next.set(prev);
|
||||
}
|
||||
Some(task.as_ptr() as *mut _)
|
||||
})
|
||||
.ok();
|
||||
@ -64,18 +70,19 @@ impl RunQueue {
|
||||
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
|
||||
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
|
||||
// Atomically empty the queue.
|
||||
let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
|
||||
// safety: the pointer is either null or valid
|
||||
let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) };
|
||||
|
||||
// Iterate the linked list of tasks that were previously in the queue.
|
||||
while let Some(task) = NonNull::new(ptr) {
|
||||
let task = unsafe { TaskRef::from_ptr(task.as_ptr()) };
|
||||
while let Some(task) = next {
|
||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||
// Therefore, first read the next pointer, and only then process the task.
|
||||
let next = task.header().run_queue_item.next.load(Ordering::Relaxed);
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
next = unsafe { task.header().run_queue_item.next.get() };
|
||||
|
||||
on_task(task);
|
||||
|
||||
ptr = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,3 +31,15 @@ pub fn block_on<F: Future>(mut fut: F) -> F::Output {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll a future once.
|
||||
pub fn poll_once<F: Future>(mut fut: F) -> Poll<F::Output> {
|
||||
// safety: we don't move the future after this line.
|
||||
let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
|
||||
|
||||
let raw_waker = RawWaker::new(ptr::null(), &VTABLE);
|
||||
let waker = unsafe { Waker::from_raw(raw_waker) };
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
fut.as_mut().poll(&mut cx)
|
||||
}
|
||||
|
@ -195,9 +195,6 @@ macro_rules! unwrap {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt-timestamp-uptime")]
|
||||
defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() }
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
|
@ -6,8 +6,24 @@ license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
|
||||
# Define the number of NVIC priority bits.
|
||||
prio-bits-0 = []
|
||||
prio-bits-1 = []
|
||||
prio-bits-2 = []
|
||||
prio-bits-3 = []
|
||||
prio-bits-4 = []
|
||||
prio-bits-5 = []
|
||||
prio-bits-6 = []
|
||||
prio-bits-7 = []
|
||||
prio-bits-8 = []
|
||||
|
||||
cortex-m = ["dep:cortex-m", "dep:critical-section"]
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
||||
num-traits = { version = "0.2.14", default-features = false }
|
||||
|
||||
cortex-m = { version = "0.7.6", optional = true }
|
||||
critical-section = { version = "1", optional = true }
|
@ -133,7 +133,7 @@ impl<'a> Writer<'a> {
|
||||
|
||||
/// Push one data byte.
|
||||
///
|
||||
/// Returns true if pushed succesfully.
|
||||
/// Returns true if pushed successfully.
|
||||
pub fn push_one(&mut self, val: u8) -> bool {
|
||||
let n = self.push(|f| match f {
|
||||
[] => 0,
|
||||
@ -265,7 +265,7 @@ impl<'a> Reader<'a> {
|
||||
|
||||
/// Pop one data byte.
|
||||
///
|
||||
/// Returns true if popped succesfully.
|
||||
/// Returns true if popped successfully.
|
||||
pub fn pop_one(&mut self) -> Option<u8> {
|
||||
let mut res = None;
|
||||
self.pop(|f| match f {
|
||||
@ -458,8 +458,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn push_slices() {
|
||||
init();
|
||||
|
||||
let mut b = [0; 4];
|
||||
let rb = RingBuffer::new();
|
||||
unsafe {
|
||||
|
@ -1,18 +1,109 @@
|
||||
//! Interrupt handling for cortex-m devices.
|
||||
use core::{mem, ptr};
|
||||
use core::mem;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
|
||||
use atomic_polyfill::{compiler_fence, AtomicPtr, Ordering};
|
||||
use cortex_m::interrupt::InterruptNumber;
|
||||
use cortex_m::peripheral::NVIC;
|
||||
use embassy_hal_common::Peripheral;
|
||||
pub use embassy_macros::cortex_m_interrupt_take as take;
|
||||
|
||||
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
||||
#[doc(hidden)]
|
||||
pub mod _export {
|
||||
pub use atomic_polyfill as atomic;
|
||||
pub use embassy_macros::{cortex_m_interrupt as interrupt, cortex_m_interrupt_declare as declare};
|
||||
/// Generate a standard `mod interrupt` for a HAL.
|
||||
#[macro_export]
|
||||
macro_rules! interrupt_mod {
|
||||
($($irqs:ident),* $(,)?) => {
|
||||
#[cfg(feature = "rt")]
|
||||
pub use cortex_m_rt::interrupt;
|
||||
|
||||
/// Interrupt definitions.
|
||||
pub mod interrupt {
|
||||
pub use $crate::interrupt::{InterruptExt, Priority};
|
||||
pub use crate::pac::Interrupt::*;
|
||||
pub use crate::pac::Interrupt;
|
||||
|
||||
/// Type-level interrupt infrastructure.
|
||||
///
|
||||
/// This module contains one *type* per interrupt. This is used for checking at compile time that
|
||||
/// the interrupts are correctly bound to HAL drivers.
|
||||
///
|
||||
/// As an end user, you shouldn't need to use this module directly. Use the [`crate::bind_interrupts!`] macro
|
||||
/// to bind interrupts, and the [`crate::interrupt`] module to manually register interrupt handlers and manipulate
|
||||
/// interrupts directly (pending/unpending, enabling/disabling, setting the priority, etc...)
|
||||
pub mod typelevel {
|
||||
use super::InterruptExt;
|
||||
|
||||
mod sealed {
|
||||
pub trait Interrupt {}
|
||||
}
|
||||
|
||||
/// Type-level interrupt.
|
||||
///
|
||||
/// This trait is implemented for all typelevel interrupt types in this module.
|
||||
pub trait Interrupt: sealed::Interrupt {
|
||||
|
||||
/// Interrupt enum variant.
|
||||
///
|
||||
/// This allows going from typelevel interrupts (one type per interrupt) to
|
||||
/// non-typelevel interrupts (a single `Interrupt` enum type, with one variant per interrupt).
|
||||
const IRQ: super::Interrupt;
|
||||
|
||||
/// Enable the interrupt.
|
||||
#[inline]
|
||||
unsafe fn enable() {
|
||||
Self::IRQ.enable()
|
||||
}
|
||||
|
||||
/// Disable the interrupt.
|
||||
#[inline]
|
||||
fn disable() {
|
||||
Self::IRQ.disable()
|
||||
}
|
||||
|
||||
/// Check if interrupt is enabled.
|
||||
#[inline]
|
||||
fn is_enabled() -> bool {
|
||||
Self::IRQ.is_enabled()
|
||||
}
|
||||
|
||||
/// Check if interrupt is pending.
|
||||
#[inline]
|
||||
fn is_pending() -> bool {
|
||||
Self::IRQ.is_pending()
|
||||
}
|
||||
|
||||
/// Set interrupt pending.
|
||||
#[inline]
|
||||
fn pend() {
|
||||
Self::IRQ.pend()
|
||||
}
|
||||
|
||||
/// Unset interrupt pending.
|
||||
#[inline]
|
||||
fn unpend() {
|
||||
Self::IRQ.unpend()
|
||||
}
|
||||
|
||||
/// Get the priority of the interrupt.
|
||||
#[inline]
|
||||
fn get_priority() -> crate::interrupt::Priority {
|
||||
Self::IRQ.get_priority()
|
||||
}
|
||||
|
||||
/// Set the interrupt priority.
|
||||
#[inline]
|
||||
fn set_priority(prio: crate::interrupt::Priority) {
|
||||
Self::IRQ.set_priority(prio)
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
#[allow(non_camel_case_types)]
|
||||
#[doc=stringify!($irqs)]
|
||||
#[doc=" typelevel interrupt."]
|
||||
pub enum $irqs {}
|
||||
impl sealed::Interrupt for $irqs{}
|
||||
impl Interrupt for $irqs {
|
||||
const IRQ: super::Interrupt = super::Interrupt::$irqs;
|
||||
}
|
||||
)*
|
||||
|
||||
/// Interrupt handler trait.
|
||||
///
|
||||
/// Drivers that need to handle interrupts implement this trait.
|
||||
@ -42,174 +133,77 @@ pub trait Handler<I: Interrupt> {
|
||||
///
|
||||
/// This allows drivers to check bindings at compile-time.
|
||||
pub unsafe trait Binding<I: Interrupt, H: Handler<I>> {}
|
||||
|
||||
/// Implementation detail, do not use outside embassy crates.
|
||||
#[doc(hidden)]
|
||||
pub struct DynHandler {
|
||||
pub func: AtomicPtr<()>,
|
||||
pub ctx: AtomicPtr<()>,
|
||||
}
|
||||
|
||||
impl DynHandler {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
func: AtomicPtr::new(ptr::null_mut()),
|
||||
ctx: AtomicPtr::new(ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct NrWrap(pub(crate) u16);
|
||||
unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap {
|
||||
fn number(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Represents an interrupt type that can be configured by embassy to handle
|
||||
/// interrupts.
|
||||
pub unsafe trait Interrupt: Peripheral<P = Self> {
|
||||
/// Return the NVIC interrupt number for this interrupt.
|
||||
fn number(&self) -> u16;
|
||||
/// Steal an instance of this interrupt
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This may panic if the interrupt has already been stolen and configured.
|
||||
unsafe fn steal() -> Self;
|
||||
|
||||
/// Implementation detail, do not use outside embassy crates.
|
||||
#[doc(hidden)]
|
||||
unsafe fn __handler(&self) -> &'static DynHandler;
|
||||
pub unsafe trait InterruptExt: InterruptNumber + Copy {
|
||||
/// Enable the interrupt.
|
||||
#[inline]
|
||||
unsafe fn enable(self) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
NVIC::unmask(self)
|
||||
}
|
||||
|
||||
/// Represents additional behavior for all interrupts.
|
||||
pub trait InterruptExt: Interrupt {
|
||||
/// Configure the interrupt handler for this interrupt.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It is the responsibility of the caller to ensure the handler
|
||||
/// points to a valid handler as long as interrupts are enabled.
|
||||
fn set_handler(&self, func: unsafe fn(*mut ()));
|
||||
|
||||
/// Remove the interrupt handler for this interrupt.
|
||||
fn remove_handler(&self);
|
||||
|
||||
/// Set point to a context that is passed to the interrupt handler when
|
||||
/// an interrupt is pending.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It is the responsibility of the caller to ensure the context
|
||||
/// points to a valid handler as long as interrupts are enabled.
|
||||
fn set_handler_context(&self, ctx: *mut ());
|
||||
|
||||
/// Enable the interrupt. Once enabled, the interrupt handler may
|
||||
/// be called "any time".
|
||||
fn enable(&self);
|
||||
|
||||
/// Disable the interrupt.
|
||||
fn disable(&self);
|
||||
#[inline]
|
||||
fn disable(self) {
|
||||
NVIC::mask(self);
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Check if interrupt is being handled.
|
||||
#[inline]
|
||||
#[cfg(not(armv6m))]
|
||||
fn is_active(&self) -> bool;
|
||||
fn is_active(self) -> bool {
|
||||
NVIC::is_active(self)
|
||||
}
|
||||
|
||||
/// Check if interrupt is enabled.
|
||||
fn is_enabled(&self) -> bool;
|
||||
#[inline]
|
||||
fn is_enabled(self) -> bool {
|
||||
NVIC::is_enabled(self)
|
||||
}
|
||||
|
||||
/// Check if interrupt is pending.
|
||||
fn is_pending(&self) -> bool;
|
||||
#[inline]
|
||||
fn is_pending(self) -> bool {
|
||||
NVIC::is_pending(self)
|
||||
}
|
||||
|
||||
/// Set interrupt pending.
|
||||
fn pend(&self);
|
||||
#[inline]
|
||||
fn pend(self) {
|
||||
NVIC::pend(self)
|
||||
}
|
||||
|
||||
/// Unset interrupt pending.
|
||||
fn unpend(&self);
|
||||
#[inline]
|
||||
fn unpend(self) {
|
||||
NVIC::unpend(self)
|
||||
}
|
||||
|
||||
/// Get the priority of the interrupt.
|
||||
fn get_priority(&self) -> Priority;
|
||||
#[inline]
|
||||
fn get_priority(self) -> Priority {
|
||||
Priority::from(NVIC::get_priority(self))
|
||||
}
|
||||
|
||||
/// Set the interrupt priority.
|
||||
fn set_priority(&self, prio: Priority);
|
||||
}
|
||||
|
||||
impl<T: Interrupt + ?Sized> InterruptExt for T {
|
||||
fn set_handler(&self, func: unsafe fn(*mut ())) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
let handler = unsafe { self.__handler() };
|
||||
handler.func.store(func as *mut (), Ordering::Relaxed);
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn remove_handler(&self) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
let handler = unsafe { self.__handler() };
|
||||
handler.func.store(ptr::null_mut(), Ordering::Relaxed);
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn set_handler_context(&self, ctx: *mut ()) {
|
||||
let handler = unsafe { self.__handler() };
|
||||
handler.ctx.store(ctx, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn enable(&self) {
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
unsafe {
|
||||
NVIC::unmask(NrWrap(self.number()));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn disable(&self) {
|
||||
NVIC::mask(NrWrap(self.number()));
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(armv6m))]
|
||||
fn is_active(&self) -> bool {
|
||||
NVIC::is_active(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_enabled(&self) -> bool {
|
||||
NVIC::is_enabled(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_pending(&self) -> bool {
|
||||
NVIC::is_pending(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pend(&self) {
|
||||
NVIC::pend(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unpend(&self) {
|
||||
NVIC::unpend(NrWrap(self.number()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_priority(&self) -> Priority {
|
||||
Priority::from(NVIC::get_priority(NrWrap(self.number())))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_priority(&self, prio: Priority) {
|
||||
unsafe {
|
||||
fn set_priority(self, prio: Priority) {
|
||||
critical_section::with(|_| unsafe {
|
||||
let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(());
|
||||
nvic.set_priority(NrWrap(self.number()), prio.into())
|
||||
}
|
||||
nvic.set_priority(self, prio.into())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: InterruptNumber + Copy> InterruptExt for T {}
|
||||
|
||||
impl From<u8> for Priority {
|
||||
fn from(priority: u8) -> Self {
|
||||
unsafe { mem::transmute(priority & PRIO_MASK) }
|
@ -11,3 +11,6 @@ mod peripheral;
|
||||
pub mod ratio;
|
||||
pub mod ring_buffer;
|
||||
pub use peripheral::{Peripheral, PeripheralRef};
|
||||
|
||||
#[cfg(feature = "cortex-m")]
|
||||
pub mod interrupt;
|
||||
|
@ -1,5 +1,5 @@
|
||||
#[macro_export]
|
||||
macro_rules! peripherals {
|
||||
macro_rules! peripherals_definition {
|
||||
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
|
||||
/// Types for the peripheral singletons.
|
||||
pub mod peripherals {
|
||||
@ -26,7 +26,12 @@ macro_rules! peripherals {
|
||||
$crate::impl_peripheral!($name);
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! peripherals_struct {
|
||||
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
|
||||
/// Struct containing all the peripheral singletons.
|
||||
///
|
||||
/// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`].
|
||||
@ -76,6 +81,24 @@ macro_rules! peripherals {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! peripherals {
|
||||
($($(#[$cfg:meta])? $name:ident),*$(,)?) => {
|
||||
$crate::peripherals_definition!(
|
||||
$(
|
||||
$(#[$cfg])?
|
||||
$name,
|
||||
)*
|
||||
);
|
||||
$crate::peripherals_struct!(
|
||||
$(
|
||||
$(#[$cfg])?
|
||||
$name,
|
||||
)*
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! into_ref {
|
||||
($($name:ident),*) => {
|
||||
|
@ -7,21 +7,13 @@ license = "MIT OR Apache-2.0"
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
|
||||
features = ["time", "defmt"]
|
||||
flavors = [
|
||||
{ name = "sx126x", target = "thumbv7em-none-eabihf", features = ["sx126x"] },
|
||||
{ name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x"] },
|
||||
{ name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/time-driver-any"] },
|
||||
]
|
||||
|
||||
[lib]
|
||||
features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/unstable-pac", "time", "defmt"]
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[features]
|
||||
sx126x = []
|
||||
sx127x = []
|
||||
stm32wl = ["dep:embassy-stm32"]
|
||||
time = []
|
||||
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
|
||||
defmt = ["dep:defmt", "lorawan-device/defmt"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -38,5 +30,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
|
||||
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||
bit_field = { version = "0.10" }
|
||||
|
||||
lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] }
|
||||
lorawan = { version = "0.7.1", default-features = false }
|
||||
lora-phy = { version = "1" }
|
||||
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
|
||||
|
325
embassy-lora/src/iv.rs
Normal file
325
embassy-lora/src/iv.rs
Normal file
@ -0,0 +1,325 @@
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_stm32::interrupt;
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_stm32::interrupt::InterruptExt;
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_stm32::pac;
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
#[cfg(feature = "stm32wl")]
|
||||
use embassy_sync::signal::Signal;
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::delay::DelayUs;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use lora_phy::mod_params::RadioError::*;
|
||||
use lora_phy::mod_params::{BoardType, RadioError};
|
||||
use lora_phy::mod_traits::InterfaceVariant;
|
||||
|
||||
/// Interrupt handler.
|
||||
#[cfg(feature = "stm32wl")]
|
||||
pub struct InterruptHandler {}
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
impl interrupt::typelevel::Handler<interrupt::typelevel::SUBGHZ_RADIO> for InterruptHandler {
|
||||
unsafe fn on_interrupt() {
|
||||
interrupt::SUBGHZ_RADIO.disable();
|
||||
IRQ_SIGNAL.signal(());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
static IRQ_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
/// Base for the InterfaceVariant implementation for an stm32wl/sx1262 combination
|
||||
pub struct Stm32wlInterfaceVariant<CTRL> {
|
||||
board_type: BoardType,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
impl<'a, CTRL> Stm32wlInterfaceVariant<CTRL>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
{
|
||||
/// Create an InterfaceVariant instance for an stm32wl/sx1262 combination
|
||||
pub fn new(
|
||||
_irq: impl interrupt::typelevel::Binding<interrupt::typelevel::SUBGHZ_RADIO, InterruptHandler>,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
) -> Result<Self, RadioError> {
|
||||
interrupt::SUBGHZ_RADIO.disable();
|
||||
Ok(Self {
|
||||
board_type: BoardType::Stm32wlSx1262, // updated when associated with a specific LoRa board
|
||||
rf_switch_rx,
|
||||
rf_switch_tx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
impl<CTRL> InterfaceVariant for Stm32wlInterfaceVariant<CTRL>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
{
|
||||
fn set_board_type(&mut self, board_type: BoardType) {
|
||||
self.board_type = board_type;
|
||||
}
|
||||
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
|
||||
let pwr = pac::PWR;
|
||||
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW));
|
||||
Ok(())
|
||||
}
|
||||
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
|
||||
let pwr = pac::PWR;
|
||||
pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH));
|
||||
Ok(())
|
||||
}
|
||||
async fn reset(&mut self, _delay: &mut impl DelayUs) -> Result<(), RadioError> {
|
||||
let rcc = pac::RCC;
|
||||
rcc.csr().modify(|w| w.set_rfrst(true));
|
||||
rcc.csr().modify(|w| w.set_rfrst(false));
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
|
||||
let pwr = pac::PWR;
|
||||
while pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY {}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn await_irq(&mut self) -> Result<(), RadioError> {
|
||||
unsafe { interrupt::SUBGHZ_RADIO.enable() };
|
||||
IRQ_SIGNAL.wait().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base for the InterfaceVariant implementation for an stm32l0/sx1276 combination
|
||||
pub struct Stm32l0InterfaceVariant<CTRL, WAIT> {
|
||||
board_type: BoardType,
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
irq: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> Stm32l0InterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
/// Create an InterfaceVariant instance for an stm32l0/sx1276 combination
|
||||
pub fn new(
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
irq: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
) -> Result<Self, RadioError> {
|
||||
Ok(Self {
|
||||
board_type: BoardType::Stm32l0Sx1276, // updated when associated with a specific LoRa board
|
||||
nss,
|
||||
reset,
|
||||
irq,
|
||||
rf_switch_rx,
|
||||
rf_switch_tx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> InterfaceVariant for Stm32l0InterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
fn set_board_type(&mut self, board_type: BoardType) {
|
||||
self.board_type = board_type;
|
||||
}
|
||||
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_low().map_err(|_| NSS)
|
||||
}
|
||||
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_high().map_err(|_| NSS)
|
||||
}
|
||||
async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
|
||||
delay.delay_ms(10).await;
|
||||
self.reset.set_low().map_err(|_| Reset)?;
|
||||
delay.delay_ms(10).await;
|
||||
self.reset.set_high().map_err(|_| Reset)?;
|
||||
delay.delay_ms(10).await;
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
|
||||
Ok(())
|
||||
}
|
||||
async fn await_irq(&mut self) -> Result<(), RadioError> {
|
||||
self.irq.wait_for_high().await.map_err(|_| Irq)
|
||||
}
|
||||
|
||||
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base for the InterfaceVariant implementation for a generic Sx126x LoRa board
|
||||
pub struct GenericSx126xInterfaceVariant<CTRL, WAIT> {
|
||||
board_type: BoardType,
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> GenericSx126xInterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
/// Create an InterfaceVariant instance for an nrf52840/sx1262 combination
|
||||
pub fn new(
|
||||
nss: CTRL,
|
||||
reset: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
rf_switch_rx: Option<CTRL>,
|
||||
rf_switch_tx: Option<CTRL>,
|
||||
) -> Result<Self, RadioError> {
|
||||
Ok(Self {
|
||||
board_type: BoardType::Rak4631Sx1262, // updated when associated with a specific LoRa board
|
||||
nss,
|
||||
reset,
|
||||
dio1,
|
||||
busy,
|
||||
rf_switch_rx,
|
||||
rf_switch_tx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<CTRL, WAIT> InterfaceVariant for GenericSx126xInterfaceVariant<CTRL, WAIT>
|
||||
where
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
fn set_board_type(&mut self, board_type: BoardType) {
|
||||
self.board_type = board_type;
|
||||
}
|
||||
async fn set_nss_low(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_low().map_err(|_| NSS)
|
||||
}
|
||||
async fn set_nss_high(&mut self) -> Result<(), RadioError> {
|
||||
self.nss.set_high().map_err(|_| NSS)
|
||||
}
|
||||
async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
|
||||
delay.delay_ms(10).await;
|
||||
self.reset.set_low().map_err(|_| Reset)?;
|
||||
delay.delay_ms(20).await;
|
||||
self.reset.set_high().map_err(|_| Reset)?;
|
||||
delay.delay_ms(10).await;
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
|
||||
self.busy.wait_for_low().await.map_err(|_| Busy)
|
||||
}
|
||||
async fn await_irq(&mut self) -> Result<(), RadioError> {
|
||||
if self.board_type != BoardType::RpPicoWaveshareSx1262 {
|
||||
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
|
||||
} else {
|
||||
self.dio1.wait_for_rising_edge().await.map_err(|_| DIO1)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
|
||||
match &mut self.rf_switch_rx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
|
||||
None => (),
|
||||
};
|
||||
match &mut self.rf_switch_tx {
|
||||
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,11 @@
|
||||
#![no_std]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
|
||||
//! crate's async LoRaWAN MAC implementation.
|
||||
#![feature(async_fn_in_trait, impl_trait_projections)]
|
||||
//! embassy-lora holds LoRa-specific functionality.
|
||||
|
||||
pub(crate) mod fmt;
|
||||
|
||||
#[cfg(feature = "stm32wl")]
|
||||
pub mod stm32wl;
|
||||
#[cfg(feature = "sx126x")]
|
||||
pub mod sx126x;
|
||||
#[cfg(feature = "sx127x")]
|
||||
pub mod sx127x;
|
||||
/// interface variants required by the external lora physical layer crate (lora-phy)
|
||||
pub mod iv;
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
@ -34,13 +29,11 @@ impl lorawan_device::async_device::radio::Timer for LoraTimer {
|
||||
self.start = Instant::now();
|
||||
}
|
||||
|
||||
type AtFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
||||
fn at<'m>(&'m mut self, millis: u64) -> Self::AtFuture<'m> {
|
||||
Timer::at(self.start + Duration::from_millis(millis))
|
||||
async fn at(&mut self, millis: u64) {
|
||||
Timer::at(self.start + Duration::from_millis(millis)).await
|
||||
}
|
||||
|
||||
type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
||||
fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
|
||||
Timer::after(Duration::from_millis(millis))
|
||||
async fn delay_ms(&mut self, millis: u64) {
|
||||
Timer::after(Duration::from_millis(millis)).await
|
||||
}
|
||||
}
|
||||
|
@ -1,292 +0,0 @@
|
||||
//! A radio driver integration for the radio found on STM32WL family devices.
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_stm32::dma::NoDma;
|
||||
use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
|
||||
use embassy_stm32::subghz::{
|
||||
CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, HseTrim, Irq, LoRaBandwidth, LoRaModParams,
|
||||
LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PacketType, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
|
||||
Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
|
||||
};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
|
||||
use lorawan_device::async_device::Timings;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum State {
|
||||
Idle,
|
||||
Txing,
|
||||
Rxing,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RadioError;
|
||||
|
||||
static IRQ_WAKER: AtomicWaker = AtomicWaker::new();
|
||||
|
||||
/// The radio peripheral keeping the radio state and owning the radio IRQ.
|
||||
pub struct SubGhzRadio<'d, RS> {
|
||||
radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RS,
|
||||
irq: PeripheralRef<'d, SUBGHZ_RADIO>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct SubGhzRadioConfig {
|
||||
pub reg_mode: RegMode,
|
||||
pub calibrate_image: CalibrateImage,
|
||||
pub pa_config: PaConfig,
|
||||
pub tx_params: TxParams,
|
||||
}
|
||||
|
||||
impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
|
||||
/// Create a new instance of a SubGhz radio for LoRaWAN.
|
||||
pub fn new(
|
||||
mut radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RS,
|
||||
irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
|
||||
config: SubGhzRadioConfig,
|
||||
) -> Result<Self, RadioError> {
|
||||
into_ref!(irq);
|
||||
|
||||
radio.reset();
|
||||
|
||||
irq.disable();
|
||||
irq.set_handler(|_| {
|
||||
IRQ_WAKER.wake();
|
||||
unsafe { SUBGHZ_RADIO::steal().disable() };
|
||||
});
|
||||
|
||||
configure_radio(&mut radio, config)?;
|
||||
|
||||
Ok(Self { radio, switch, irq })
|
||||
}
|
||||
|
||||
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
|
||||
/// the upcoming RX window start.
|
||||
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
|
||||
trace!("TX request: {:?}", config);
|
||||
self.switch.set_tx();
|
||||
|
||||
self.radio
|
||||
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
|
||||
|
||||
self.set_lora_mod_params(config.rf)?;
|
||||
|
||||
let packet_params = LoRaPacketParams::new()
|
||||
.set_preamble_len(8)
|
||||
.set_header_type(HeaderType::Variable)
|
||||
.set_payload_len(buf.len() as u8)
|
||||
.set_crc_en(true)
|
||||
.set_invert_iq(false);
|
||||
|
||||
self.radio.set_lora_packet_params(&packet_params)?;
|
||||
|
||||
let irq_cfg = CfgIrq::new().irq_enable_all(Irq::TxDone).irq_enable_all(Irq::Timeout);
|
||||
self.radio.set_irq_cfg(&irq_cfg)?;
|
||||
|
||||
self.radio.set_buffer_base_address(0, 0)?;
|
||||
self.radio.write_buffer(0, buf)?;
|
||||
|
||||
// The maximum airtime for any LoRaWAN package is 2793.5ms.
|
||||
// The value of 4000ms is copied from C driver and gives us a good safety margin.
|
||||
self.radio.set_tx(Timeout::from_millis_sat(4000))?;
|
||||
trace!("TX started");
|
||||
|
||||
loop {
|
||||
let (_status, irq_status) = self.irq_wait().await;
|
||||
|
||||
if irq_status & Irq::TxDone.mask() != 0 {
|
||||
trace!("TX done");
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if irq_status & Irq::Timeout.mask() != 0 {
|
||||
return Err(RadioError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
|
||||
let mod_params = LoRaModParams::new()
|
||||
.set_sf(convert_spreading_factor(&config.spreading_factor))
|
||||
.set_bw(convert_bandwidth(&config.bandwidth))
|
||||
.set_cr(CodingRate::Cr45)
|
||||
.set_ldro_en(matches!(
|
||||
(config.spreading_factor, config.bandwidth),
|
||||
(SpreadingFactor::_12, Bandwidth::_125KHz)
|
||||
| (SpreadingFactor::_12, Bandwidth::_250KHz)
|
||||
| (SpreadingFactor::_11, Bandwidth::_125KHz)
|
||||
));
|
||||
self.radio.set_lora_mod_params(&mod_params)
|
||||
}
|
||||
|
||||
/// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
|
||||
/// be able to hold a single LoRaWAN packet.
|
||||
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
|
||||
assert!(buf.len() >= 255);
|
||||
trace!("RX request: {:?}", config);
|
||||
self.switch.set_rx();
|
||||
|
||||
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
|
||||
|
||||
self.set_lora_mod_params(config)?;
|
||||
|
||||
let packet_params = LoRaPacketParams::new()
|
||||
.set_preamble_len(8)
|
||||
.set_header_type(HeaderType::Variable)
|
||||
.set_payload_len(0xFF)
|
||||
.set_crc_en(false)
|
||||
.set_invert_iq(true);
|
||||
self.radio.set_lora_packet_params(&packet_params)?;
|
||||
|
||||
let irq_cfg = CfgIrq::new()
|
||||
.irq_enable_all(Irq::RxDone)
|
||||
.irq_enable_all(Irq::PreambleDetected)
|
||||
.irq_enable_all(Irq::HeaderValid)
|
||||
.irq_enable_all(Irq::HeaderErr)
|
||||
.irq_enable_all(Irq::Err)
|
||||
.irq_enable_all(Irq::Timeout);
|
||||
self.radio.set_irq_cfg(&irq_cfg)?;
|
||||
|
||||
self.radio.set_buffer_base_address(0, 0)?;
|
||||
|
||||
// NOTE: Upper layer handles timeout by cancelling the future
|
||||
self.radio.set_rx(Timeout::DISABLED)?;
|
||||
|
||||
trace!("RX started");
|
||||
|
||||
loop {
|
||||
let (_status, irq_status) = self.irq_wait().await;
|
||||
|
||||
if irq_status & Irq::RxDone.mask() != 0 {
|
||||
let (_status, len, ptr) = self.radio.rx_buffer_status()?;
|
||||
let packet_status = self.radio.lora_packet_status()?;
|
||||
let rssi = packet_status.rssi_pkt().to_integer();
|
||||
let snr = packet_status.snr_pkt().to_integer();
|
||||
self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
|
||||
self.radio.set_standby(StandbyClk::Rc)?;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
trace!("RX done: {:02x?}", &mut buf[..len as usize]);
|
||||
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
|
||||
}
|
||||
|
||||
if irq_status & Irq::Timeout.mask() != 0 {
|
||||
return Err(RadioError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn irq_wait(&mut self) -> (Status, u16) {
|
||||
poll_fn(|cx| {
|
||||
self.irq.unpend();
|
||||
self.irq.enable();
|
||||
IRQ_WAKER.register(cx.waker());
|
||||
|
||||
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
|
||||
self.radio
|
||||
.clear_irq_status(irq_status)
|
||||
.expect("error clearing irq status");
|
||||
|
||||
trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status);
|
||||
|
||||
if irq_status == 0 {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready((status, irq_status))
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConfig) -> Result<(), RadioError> {
|
||||
trace!("Configuring STM32WL SUBGHZ radio");
|
||||
|
||||
radio.set_regulator_mode(config.reg_mode)?;
|
||||
radio.set_standby(StandbyClk::Rc)?;
|
||||
|
||||
let tcxo_mode = TcxoMode::new()
|
||||
.set_txco_trim(TcxoTrim::Volts1pt7)
|
||||
.set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(100)));
|
||||
radio.set_tcxo_mode(&tcxo_mode)?;
|
||||
// Reduce input capacitance as shown in Reference Manual "Figure 23. HSE32 TCXO control".
|
||||
// The STM32CUBE C driver also does this.
|
||||
radio.set_hse_in_trim(HseTrim::MIN)?;
|
||||
|
||||
// Re-calibrate everything after setting the TXCO config.
|
||||
radio.calibrate(0x7F)?;
|
||||
radio.calibrate_image(config.calibrate_image)?;
|
||||
|
||||
radio.set_pa_config(&config.pa_config)?;
|
||||
radio.set_tx_params(&config.tx_params)?;
|
||||
radio.set_pa_ocp(Ocp::Max140m)?;
|
||||
|
||||
radio.set_packet_type(PacketType::LoRa)?;
|
||||
radio.set_lora_sync_word(LoRaSyncWord::Public)?;
|
||||
|
||||
trace!("Done initializing STM32WL SUBGHZ radio");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'d, RS: RadioSwitch> PhyRxTx for SubGhzRadio<'d, RS> {
|
||||
type PhyError = RadioError;
|
||||
|
||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm where Self: 'm;
|
||||
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||
async move { self.do_tx(config, buf).await }
|
||||
}
|
||||
|
||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm where Self: 'm;
|
||||
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||
async move { self.do_rx(config, buf).await }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<embassy_stm32::spi::Error> for RadioError {
|
||||
fn from(_: embassy_stm32::spi::Error) -> Self {
|
||||
RadioError
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
|
||||
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||
-3
|
||||
}
|
||||
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||
1003
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RadioSwitch {
|
||||
fn set_rx(&mut self);
|
||||
fn set_tx(&mut self);
|
||||
}
|
||||
|
||||
fn convert_spreading_factor(sf: &SpreadingFactor) -> SF {
|
||||
match sf {
|
||||
SpreadingFactor::_7 => SF::Sf7,
|
||||
SpreadingFactor::_8 => SF::Sf8,
|
||||
SpreadingFactor::_9 => SF::Sf9,
|
||||
SpreadingFactor::_10 => SF::Sf10,
|
||||
SpreadingFactor::_11 => SF::Sf11,
|
||||
SpreadingFactor::_12 => SF::Sf12,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_bandwidth(bw: &Bandwidth) -> LoRaBandwidth {
|
||||
match bw {
|
||||
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
|
||||
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
|
||||
Bandwidth::_500KHz => LoRaBandwidth::Bw500,
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
use core::future::Future;
|
||||
|
||||
use defmt::Format;
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::*;
|
||||
use lorawan_device::async_device::radio::{PhyRxTx, RfConfig, RxQuality, TxConfig};
|
||||
use lorawan_device::async_device::Timings;
|
||||
|
||||
mod sx126x_lora;
|
||||
use sx126x_lora::LoRa;
|
||||
|
||||
use self::sx126x_lora::mod_params::RadioError;
|
||||
|
||||
/// Semtech Sx126x LoRa peripheral
|
||||
pub struct Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
pub lora: LoRa<SPI, CTRL, WAIT>,
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
pub async fn new(
|
||||
spi: SPI,
|
||||
cs: CTRL,
|
||||
reset: CTRL,
|
||||
antenna_rx: CTRL,
|
||||
antenna_tx: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
enable_public_network: bool,
|
||||
) -> Result<Self, RadioError<BUS>> {
|
||||
let mut lora = LoRa::new(spi, cs, reset, antenna_rx, antenna_tx, dio1, busy);
|
||||
lora.init().await?;
|
||||
lora.set_lora_modem(enable_public_network).await?;
|
||||
Ok(Self { lora })
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> Timings for Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||
-50
|
||||
}
|
||||
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||
1050
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> PhyRxTx for Sx126xRadio<SPI, CTRL, WAIT, BUS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS> + 'static,
|
||||
CTRL: OutputPin + 'static,
|
||||
WAIT: Wait + 'static,
|
||||
BUS: Error + Format + 'static,
|
||||
{
|
||||
type PhyError = RadioError<BUS>;
|
||||
|
||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CTRL: 'm,
|
||||
WAIT: 'm,
|
||||
BUS: 'm;
|
||||
|
||||
fn tx<'m>(&'m mut self, config: TxConfig, buffer: &'m [u8]) -> Self::TxFuture<'m> {
|
||||
trace!("TX START");
|
||||
async move {
|
||||
self.lora
|
||||
.set_tx_config(
|
||||
config.pw,
|
||||
config.rf.spreading_factor.into(),
|
||||
config.rf.bandwidth.into(),
|
||||
config.rf.coding_rate.into(),
|
||||
8,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
self.lora.set_max_payload_length(buffer.len() as u8).await?;
|
||||
self.lora.set_channel(config.rf.frequency).await?;
|
||||
self.lora.send(buffer, 0xffffff).await?;
|
||||
self.lora.process_irq(None, None, None).await?;
|
||||
trace!("TX DONE");
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CTRL: 'm,
|
||||
WAIT: 'm,
|
||||
BUS: 'm;
|
||||
|
||||
fn rx<'m>(&'m mut self, config: RfConfig, receiving_buffer: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||
trace!("RX START");
|
||||
async move {
|
||||
self.lora
|
||||
.set_rx_config(
|
||||
config.spreading_factor.into(),
|
||||
config.bandwidth.into(),
|
||||
config.coding_rate.into(),
|
||||
8,
|
||||
4,
|
||||
false,
|
||||
0u8,
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?;
|
||||
self.lora.set_channel(config.frequency).await?;
|
||||
self.lora.rx(90 * 1000).await?;
|
||||
let mut received_len = 0u8;
|
||||
self.lora
|
||||
.process_irq(Some(receiving_buffer), Some(&mut received_len), None)
|
||||
.await?;
|
||||
trace!("RX DONE");
|
||||
|
||||
let packet_status = self.lora.get_latest_packet_status();
|
||||
let mut rssi = 0i16;
|
||||
let mut snr = 0i8;
|
||||
if packet_status.is_some() {
|
||||
rssi = packet_status.unwrap().rssi as i16;
|
||||
snr = packet_status.unwrap().snr;
|
||||
}
|
||||
|
||||
Ok((received_len as usize, RxQuality::new(rssi, snr)))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
use super::mod_params::RadioError::*;
|
||||
use super::mod_params::*;
|
||||
use super::LoRa;
|
||||
|
||||
// Defines the time required for the TCXO to wakeup [ms].
|
||||
const BRD_TCXO_WAKEUP_TIME: u32 = 10;
|
||||
|
||||
// Provides board-specific functionality for Semtech SX126x-based boards.
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS>,
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
// De-initialize the radio I/Os pins interface. Useful when going into MCU low power modes.
|
||||
pub(super) async fn brd_io_deinit(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
Ok(()) // no operation currently
|
||||
}
|
||||
|
||||
// Initialize the TCXO power pin
|
||||
pub(super) async fn brd_io_tcxo_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
let timeout = self.brd_get_board_tcxo_wakeup_time() << 6;
|
||||
self.sub_set_dio3_as_tcxo_ctrl(TcxoCtrlVoltage::Ctrl1V7, timeout)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Initialize RF switch control pins
|
||||
pub(super) async fn brd_io_rf_switch_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio2_as_rf_switch_ctrl(true).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Initialize the radio debug pins
|
||||
pub(super) async fn brd_io_dbg_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
Ok(()) // no operation currently
|
||||
}
|
||||
|
||||
// Hardware reset of the radio
|
||||
pub(super) async fn brd_reset(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
self.reset.set_low().map_err(|_| Reset)?;
|
||||
Timer::after(Duration::from_millis(20)).await;
|
||||
self.reset.set_high().map_err(|_| Reset)?;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Wait while the busy pin is high
|
||||
pub(super) async fn brd_wait_on_busy(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.busy.wait_for_low().await.map_err(|_| Busy)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Wake up the radio
|
||||
pub(super) async fn brd_wakeup(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::GetStatus.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(&[0x00]).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Send a command that writes data to the radio
|
||||
pub(super) async fn brd_write_command(&mut self, op_code: OpCode, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
if op_code != OpCode::SetSleep {
|
||||
self.brd_wait_on_busy().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Send a command that reads data from the radio, filling the provided buffer and returning a status
|
||||
pub(super) async fn brd_read_command(&mut self, op_code: OpCode, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
|
||||
let mut status = [0u8];
|
||||
let mut input = [0u8];
|
||||
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
|
||||
self.spi.transfer(&mut status, &[0x00]).await.map_err(SPI)?;
|
||||
for i in 0..buffer.len() {
|
||||
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
|
||||
buffer[i] = input[0];
|
||||
}
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
|
||||
Ok(status[0])
|
||||
}
|
||||
|
||||
// Write one or more bytes of data to the radio memory
|
||||
pub(super) async fn brd_write_registers(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &[u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::WriteRegister.value()]).await.map_err(SPI)?;
|
||||
self.spi
|
||||
.write(&[
|
||||
((start_register.addr() & 0xFF00) >> 8) as u8,
|
||||
(start_register.addr() & 0x00FF) as u8,
|
||||
])
|
||||
.await
|
||||
.map_err(SPI)?;
|
||||
self.spi.write(buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Read one or more bytes of data from the radio memory
|
||||
pub(super) async fn brd_read_registers(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut input = [0u8];
|
||||
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::ReadRegister.value()]).await.map_err(SPI)?;
|
||||
self.spi
|
||||
.write(&[
|
||||
((start_register.addr() & 0xFF00) >> 8) as u8,
|
||||
(start_register.addr() & 0x00FF) as u8,
|
||||
0x00u8,
|
||||
])
|
||||
.await
|
||||
.map_err(SPI)?;
|
||||
for i in 0..buffer.len() {
|
||||
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
|
||||
buffer[i] = input[0];
|
||||
}
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Write data to the buffer holding the payload in the radio
|
||||
pub(super) async fn brd_write_buffer(&mut self, offset: u8, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::WriteBuffer.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(&[offset]).await.map_err(SPI)?;
|
||||
self.spi.write(buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Read data from the buffer holding the payload in the radio
|
||||
pub(super) async fn brd_read_buffer(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), RadioError<BUS>> {
|
||||
let mut input = [0u8];
|
||||
|
||||
self.sub_check_device_ready().await?;
|
||||
|
||||
self.cs.set_low().map_err(|_| CS)?;
|
||||
self.spi.write(&[OpCode::ReadBuffer.value()]).await.map_err(SPI)?;
|
||||
self.spi.write(&[offset]).await.map_err(SPI)?;
|
||||
self.spi.write(&[0x00]).await.map_err(SPI)?;
|
||||
for i in 0..buffer.len() {
|
||||
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
|
||||
buffer[i] = input[0];
|
||||
}
|
||||
self.cs.set_high().map_err(|_| CS)?;
|
||||
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio output power
|
||||
pub(super) async fn brd_set_rf_tx_power(&mut self, power: i8) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_tx_params(power, RampTime::Ramp40Us).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the radio type
|
||||
pub(super) fn brd_get_radio_type(&mut self) -> RadioType {
|
||||
RadioType::SX1262
|
||||
}
|
||||
|
||||
// Quiesce the antenna(s).
|
||||
pub(super) fn brd_ant_sleep(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.antenna_tx.set_low().map_err(|_| AntTx)?;
|
||||
self.antenna_rx.set_low().map_err(|_| AntRx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Prepare the antenna(s) for a receive operation
|
||||
pub(super) fn brd_ant_set_rx(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.antenna_tx.set_low().map_err(|_| AntTx)?;
|
||||
self.antenna_rx.set_high().map_err(|_| AntRx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Prepare the antenna(s) for a send operation
|
||||
pub(super) fn brd_ant_set_tx(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.antenna_rx.set_low().map_err(|_| AntRx)?;
|
||||
self.antenna_tx.set_high().map_err(|_| AntTx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Check if the given RF frequency is supported by the hardware
|
||||
pub(super) async fn brd_check_rf_frequency(&mut self, _frequency: u32) -> Result<bool, RadioError<BUS>> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
// Get the duration required for the TCXO to wakeup [ms].
|
||||
pub(super) fn brd_get_board_tcxo_wakeup_time(&mut self) -> u32 {
|
||||
BRD_TCXO_WAKEUP_TIME
|
||||
}
|
||||
|
||||
/* Get current state of the DIO1 pin - not currently needed if waiting on DIO1 instead of using an IRQ process
|
||||
pub(super) async fn brd_get_dio1_pin_state(
|
||||
&mut self,
|
||||
) -> Result<u32, RadioError<BUS>> {
|
||||
Ok(0)
|
||||
}
|
||||
*/
|
||||
|
||||
// Get the current radio operatiing mode
|
||||
pub(super) fn brd_get_operating_mode(&mut self) -> RadioMode {
|
||||
self.operating_mode
|
||||
}
|
||||
|
||||
// Set/Update the current radio operating mode This function is only required to reflect the current radio operating mode when processing interrupts.
|
||||
pub(super) fn brd_set_operating_mode(&mut self, mode: RadioMode) {
|
||||
self.operating_mode = mode;
|
||||
}
|
||||
}
|
@ -1,732 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
mod board_specific;
|
||||
pub mod mod_params;
|
||||
mod subroutine;
|
||||
|
||||
use mod_params::RadioError::*;
|
||||
use mod_params::*;
|
||||
|
||||
// Syncwords for public and private networks
|
||||
const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444;
|
||||
const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424;
|
||||
|
||||
// Maximum number of registers that can be added to the retention list
|
||||
const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4;
|
||||
|
||||
// Possible LoRa bandwidths
|
||||
const LORA_BANDWIDTHS: [Bandwidth; 3] = [Bandwidth::_125KHz, Bandwidth::_250KHz, Bandwidth::_500KHz];
|
||||
|
||||
// Radio complete wakeup time with margin for temperature compensation [ms]
|
||||
const RADIO_WAKEUP_TIME: u32 = 3;
|
||||
|
||||
/// Provides high-level access to Semtech SX126x-based boards
|
||||
pub struct LoRa<SPI, CTRL, WAIT> {
|
||||
spi: SPI,
|
||||
cs: CTRL,
|
||||
reset: CTRL,
|
||||
antenna_rx: CTRL,
|
||||
antenna_tx: CTRL,
|
||||
dio1: WAIT,
|
||||
busy: WAIT,
|
||||
operating_mode: RadioMode,
|
||||
rx_continuous: bool,
|
||||
max_payload_length: u8,
|
||||
modulation_params: Option<ModulationParams>,
|
||||
packet_type: PacketType,
|
||||
packet_params: Option<PacketParams>,
|
||||
packet_status: Option<PacketStatus>,
|
||||
image_calibrated: bool,
|
||||
frequency_error: u32,
|
||||
}
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS>,
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time ()
|
||||
pub fn new(spi: SPI, cs: CTRL, reset: CTRL, antenna_rx: CTRL, antenna_tx: CTRL, dio1: WAIT, busy: WAIT) -> Self {
|
||||
Self {
|
||||
spi,
|
||||
cs,
|
||||
reset,
|
||||
antenna_rx,
|
||||
antenna_tx,
|
||||
dio1,
|
||||
busy,
|
||||
operating_mode: RadioMode::Sleep,
|
||||
rx_continuous: false,
|
||||
max_payload_length: 0xFFu8,
|
||||
modulation_params: None,
|
||||
packet_type: PacketType::LoRa,
|
||||
packet_params: None,
|
||||
packet_status: None,
|
||||
image_calibrated: false,
|
||||
frequency_error: 0u32, // where is volatile FrequencyError modified ???
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the radio
|
||||
pub async fn init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_init().await?;
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
self.sub_set_regulator_mode(RegulatorMode::UseDCDC).await?;
|
||||
self.sub_set_buffer_base_address(0x00u8, 0x00u8).await?;
|
||||
self.sub_set_tx_params(0i8, RampTime::Ramp200Us).await?;
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::All.value(),
|
||||
IrqMask::All.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
self.add_register_to_retention_list(Register::RxGain.addr()).await?;
|
||||
self.add_register_to_retention_list(Register::TxModulation.addr())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return current radio state
|
||||
pub fn get_status(&mut self) -> RadioState {
|
||||
match self.brd_get_operating_mode() {
|
||||
RadioMode::Transmit => RadioState::TxRunning,
|
||||
RadioMode::Receive => RadioState::RxRunning,
|
||||
RadioMode::ChannelActivityDetection => RadioState::ChannelActivityDetecting,
|
||||
_ => RadioState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure the radio for LoRa (FSK support should be provided in a separate driver, if desired)
|
||||
pub async fn set_lora_modem(&mut self, enable_public_network: bool) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_packet_type(PacketType::LoRa).await?;
|
||||
if enable_public_network {
|
||||
self.brd_write_registers(
|
||||
Register::LoRaSyncword,
|
||||
&[
|
||||
((LORA_MAC_PUBLIC_SYNCWORD >> 8) & 0xFF) as u8,
|
||||
(LORA_MAC_PUBLIC_SYNCWORD & 0xFF) as u8,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
self.brd_write_registers(
|
||||
Register::LoRaSyncword,
|
||||
&[
|
||||
((LORA_MAC_PRIVATE_SYNCWORD >> 8) & 0xFF) as u8,
|
||||
(LORA_MAC_PRIVATE_SYNCWORD & 0xFF) as u8,
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the channel frequency
|
||||
pub async fn set_channel(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_rf_frequency(frequency).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/* Checks if the channel is free for the given time. This is currently not implemented until a substitute
|
||||
for switching to the FSK modem is found.
|
||||
|
||||
pub async fn is_channel_free(&mut self, frequency: u32, rxBandwidth: u32, rssiThresh: i16, maxCarrierSenseTime: u32) -> bool;
|
||||
*/
|
||||
|
||||
/// Generate a 32 bit random value based on the RSSI readings, after disabling all interrupts. Ensure set_lora_modem() is called befrorehand.
|
||||
/// After calling this function either set_rx_config() or set_tx_config() must be called.
|
||||
pub async fn get_random_value(&mut self) -> Result<u32, RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let result = self.sub_get_random().await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Set the reception parameters for the LoRa modem (only). Ensure set_lora_modem() is called befrorehand.
|
||||
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
|
||||
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
|
||||
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
|
||||
/// preamble_length length in symbols (the hardware adds 4 more symbols)
|
||||
/// symb_timeout RxSingle timeout value in symbols
|
||||
/// fixed_len fixed length packets [0: variable, 1: fixed]
|
||||
/// payload_len payload length when fixed length is used
|
||||
/// crc_on [0: OFF, 1: ON]
|
||||
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
|
||||
/// hop_period number of symbols between each hop
|
||||
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
|
||||
/// rx_continuous reception mode [false: single mode, true: continuous mode]
|
||||
pub async fn set_rx_config(
|
||||
&mut self,
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
symb_timeout: u16,
|
||||
fixed_len: bool,
|
||||
payload_len: u8,
|
||||
crc_on: bool,
|
||||
_freq_hop_on: bool,
|
||||
_hop_period: u8,
|
||||
iq_inverted: bool,
|
||||
rx_continuous: bool,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut symb_timeout_final = symb_timeout;
|
||||
|
||||
self.rx_continuous = rx_continuous;
|
||||
if self.rx_continuous {
|
||||
symb_timeout_final = 0;
|
||||
}
|
||||
if fixed_len {
|
||||
self.max_payload_length = payload_len;
|
||||
} else {
|
||||
self.max_payload_length = 0xFFu8;
|
||||
}
|
||||
|
||||
self.sub_set_stop_rx_timer_on_preamble_detect(false).await?;
|
||||
|
||||
let mut low_data_rate_optimize = 0x00u8;
|
||||
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
|
||||
&& (bandwidth == Bandwidth::_125KHz))
|
||||
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
|
||||
{
|
||||
low_data_rate_optimize = 0x01u8;
|
||||
}
|
||||
|
||||
let modulation_params = ModulationParams {
|
||||
spreading_factor: spreading_factor,
|
||||
bandwidth: bandwidth,
|
||||
coding_rate: coding_rate,
|
||||
low_data_rate_optimize: low_data_rate_optimize,
|
||||
};
|
||||
|
||||
let mut preamble_length_final = preamble_length;
|
||||
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
|
||||
&& (preamble_length < 12)
|
||||
{
|
||||
preamble_length_final = 12;
|
||||
}
|
||||
|
||||
let packet_params = PacketParams {
|
||||
preamble_length: preamble_length_final,
|
||||
implicit_header: fixed_len,
|
||||
payload_length: self.max_payload_length,
|
||||
crc_on: crc_on,
|
||||
iq_inverted: iq_inverted,
|
||||
};
|
||||
|
||||
self.modulation_params = Some(modulation_params);
|
||||
self.packet_params = Some(packet_params);
|
||||
|
||||
self.standby().await?;
|
||||
self.sub_set_modulation_params().await?;
|
||||
self.sub_set_packet_params().await?;
|
||||
self.sub_set_lora_symb_num_timeout(symb_timeout_final).await?;
|
||||
|
||||
// Optimize the Inverted IQ Operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
|
||||
let mut iq_polarity = [0x00u8];
|
||||
self.brd_read_registers(Register::IQPolarity, &mut iq_polarity).await?;
|
||||
if iq_inverted {
|
||||
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] & (!(1 << 2))])
|
||||
.await?;
|
||||
} else {
|
||||
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] | (1 << 2)])
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the transmission parameters for the LoRa modem (only).
|
||||
/// power output power [dBm]
|
||||
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
|
||||
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
|
||||
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
|
||||
/// preamble_length length in symbols (the hardware adds 4 more symbols)
|
||||
/// fixed_len fixed length packets [0: variable, 1: fixed]
|
||||
/// crc_on [0: OFF, 1: ON]
|
||||
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
|
||||
/// hop_period number of symbols between each hop
|
||||
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
|
||||
pub async fn set_tx_config(
|
||||
&mut self,
|
||||
power: i8,
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
fixed_len: bool,
|
||||
crc_on: bool,
|
||||
_freq_hop_on: bool,
|
||||
_hop_period: u8,
|
||||
iq_inverted: bool,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut low_data_rate_optimize = 0x00u8;
|
||||
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
|
||||
&& (bandwidth == Bandwidth::_125KHz))
|
||||
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
|
||||
{
|
||||
low_data_rate_optimize = 0x01u8;
|
||||
}
|
||||
|
||||
let modulation_params = ModulationParams {
|
||||
spreading_factor: spreading_factor,
|
||||
bandwidth: bandwidth,
|
||||
coding_rate: coding_rate,
|
||||
low_data_rate_optimize: low_data_rate_optimize,
|
||||
};
|
||||
|
||||
let mut preamble_length_final = preamble_length;
|
||||
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
|
||||
&& (preamble_length < 12)
|
||||
{
|
||||
preamble_length_final = 12;
|
||||
}
|
||||
|
||||
let packet_params = PacketParams {
|
||||
preamble_length: preamble_length_final,
|
||||
implicit_header: fixed_len,
|
||||
payload_length: self.max_payload_length,
|
||||
crc_on: crc_on,
|
||||
iq_inverted: iq_inverted,
|
||||
};
|
||||
|
||||
self.modulation_params = Some(modulation_params);
|
||||
self.packet_params = Some(packet_params);
|
||||
|
||||
self.standby().await?;
|
||||
self.sub_set_modulation_params().await?;
|
||||
self.sub_set_packet_params().await?;
|
||||
|
||||
// Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
|
||||
|
||||
let mut tx_modulation = [0x00u8];
|
||||
self.brd_read_registers(Register::TxModulation, &mut tx_modulation)
|
||||
.await?;
|
||||
if bandwidth == Bandwidth::_500KHz {
|
||||
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] & (!(1 << 2))])
|
||||
.await?;
|
||||
} else {
|
||||
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] | (1 << 2)])
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.brd_set_rf_tx_power(power).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the given RF frequency is supported by the hardware [true: supported, false: unsupported]
|
||||
pub async fn check_rf_frequency(&mut self, frequency: u32) -> Result<bool, RadioError<BUS>> {
|
||||
Ok(self.brd_check_rf_frequency(frequency).await?)
|
||||
}
|
||||
|
||||
/// Computes the packet time on air in ms for the given payload for a LoRa modem (can only be called once set_rx_config or set_tx_config have been called)
|
||||
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
|
||||
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
|
||||
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
|
||||
/// preamble_length length in symbols (the hardware adds 4 more symbols)
|
||||
/// fixed_len fixed length packets [0: variable, 1: fixed]
|
||||
/// payload_len sets payload length when fixed length is used
|
||||
/// crc_on [0: OFF, 1: ON]
|
||||
pub fn get_time_on_air(
|
||||
&mut self,
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
fixed_len: bool,
|
||||
payload_len: u8,
|
||||
crc_on: bool,
|
||||
) -> Result<u32, RadioError<BUS>> {
|
||||
let numerator = 1000
|
||||
* Self::get_lora_time_on_air_numerator(
|
||||
spreading_factor,
|
||||
bandwidth,
|
||||
coding_rate,
|
||||
preamble_length,
|
||||
fixed_len,
|
||||
payload_len,
|
||||
crc_on,
|
||||
);
|
||||
let denominator = bandwidth.value_in_hz();
|
||||
if denominator == 0 {
|
||||
Err(RadioError::InvalidBandwidth)
|
||||
} else {
|
||||
Ok((numerator + denominator - 1) / denominator)
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the buffer of the given size. Prepares the packet to be sent and sets the radio in transmission [timeout in ms]
|
||||
pub async fn send(&mut self, buffer: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
|
||||
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut packet_params = self.packet_params.as_mut().unwrap();
|
||||
packet_params.payload_length = buffer.len() as u8;
|
||||
self.sub_set_packet_params().await?;
|
||||
self.sub_send_payload(buffer, timeout).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the radio in sleep mode
|
||||
pub async fn sleep(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_sleep(SleepParams {
|
||||
wakeup_rtc: false,
|
||||
reset: false,
|
||||
warm_start: true,
|
||||
})
|
||||
.await?;
|
||||
Timer::after(Duration::from_millis(2)).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the radio in standby mode
|
||||
pub async fn standby(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the radio in reception mode for the given duration [0: continuous, others: timeout (ms)]
|
||||
pub async fn rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::All.value(),
|
||||
IrqMask::All.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if self.rx_continuous {
|
||||
self.sub_set_rx(0xFFFFFF).await?;
|
||||
} else {
|
||||
self.sub_set_rx(timeout << 6).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start a Channel Activity Detection
|
||||
pub async fn start_cad(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
|
||||
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
self.sub_set_cad().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the radio in continuous wave transmission mode
|
||||
/// frequency channel RF frequency
|
||||
/// power output power [dBm]
|
||||
/// timeout transmission mode timeout [s]
|
||||
pub async fn set_tx_continuous_wave(
|
||||
&mut self,
|
||||
frequency: u32,
|
||||
power: i8,
|
||||
_timeout: u16,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_rf_frequency(frequency).await?;
|
||||
self.brd_set_rf_tx_power(power).await?;
|
||||
self.sub_set_tx_continuous_wave().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the current RSSI value for the LoRa modem (only) [dBm]
|
||||
pub async fn get_rssi(&mut self) -> Result<i16, RadioError<BUS>> {
|
||||
let value = self.sub_get_rssi_inst().await?;
|
||||
Ok(value as i16)
|
||||
}
|
||||
|
||||
/// Write one or more radio registers with a buffer of a given size, starting at the first register address
|
||||
pub async fn write_registers_from_buffer(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &[u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_registers(start_register, buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read one or more radio registers into a buffer of a given size, starting at the first register address
|
||||
pub async fn read_registers_into_buffer(
|
||||
&mut self,
|
||||
start_register: Register,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_read_registers(start_register, buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the maximum payload length (in bytes) for a LoRa modem (only).
|
||||
pub async fn set_max_payload_length(&mut self, max: u8) -> Result<(), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
let packet_params = self.packet_params.as_mut().unwrap();
|
||||
self.max_payload_length = max;
|
||||
packet_params.payload_length = max;
|
||||
self.sub_set_packet_params().await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the time required for the board plus radio to get out of sleep [ms]
|
||||
pub fn get_wakeup_time(&mut self) -> u32 {
|
||||
self.brd_get_board_tcxo_wakeup_time() + RADIO_WAKEUP_TIME
|
||||
}
|
||||
|
||||
/// Process the radio irq
|
||||
pub async fn process_irq(
|
||||
&mut self,
|
||||
receiving_buffer: Option<&mut [u8]>,
|
||||
received_len: Option<&mut u8>,
|
||||
cad_activity_detected: Option<&mut bool>,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
loop {
|
||||
trace!("process_irq loop entered");
|
||||
|
||||
let de = self.sub_get_device_errors().await?;
|
||||
trace!("device_errors: rc_64khz_calibration = {}, rc_13mhz_calibration = {}, pll_calibration = {}, adc_calibration = {}, image_calibration = {}, xosc_start = {}, pll_lock = {}, pa_ramp = {}",
|
||||
de.rc_64khz_calibration, de.rc_13mhz_calibration, de.pll_calibration, de.adc_calibration, de.image_calibration, de.xosc_start, de.pll_lock, de.pa_ramp);
|
||||
let st = self.sub_get_status().await?;
|
||||
trace!(
|
||||
"radio status: cmd_status: {:x}, chip_mode: {:x}",
|
||||
st.cmd_status,
|
||||
st.chip_mode
|
||||
);
|
||||
|
||||
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
|
||||
let operating_mode = self.brd_get_operating_mode();
|
||||
let irq_flags = self.sub_get_irq_status().await?;
|
||||
self.sub_clear_irq_status(irq_flags).await?;
|
||||
trace!("process_irq DIO1 satisfied: irq_flags = {:x}", irq_flags);
|
||||
|
||||
// check for errors and unexpected interrupt masks (based on operation mode)
|
||||
if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() {
|
||||
if !self.rx_continuous {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
}
|
||||
return Err(RadioError::HeaderError);
|
||||
} else if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() {
|
||||
if operating_mode == RadioMode::Receive {
|
||||
if !self.rx_continuous {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
}
|
||||
return Err(RadioError::CRCErrorOnReceive);
|
||||
} else {
|
||||
return Err(RadioError::CRCErrorUnexpected);
|
||||
}
|
||||
} else if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
|
||||
if operating_mode == RadioMode::Transmit {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Err(RadioError::TransmitTimeout);
|
||||
} else if operating_mode == RadioMode::Receive {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Err(RadioError::ReceiveTimeout);
|
||||
} else {
|
||||
return Err(RadioError::TimeoutUnexpected);
|
||||
}
|
||||
} else if ((irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value())
|
||||
&& (operating_mode != RadioMode::Transmit)
|
||||
{
|
||||
return Err(RadioError::TransmitDoneUnexpected);
|
||||
} else if ((irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value())
|
||||
&& (operating_mode != RadioMode::Receive)
|
||||
{
|
||||
return Err(RadioError::ReceiveDoneUnexpected);
|
||||
} else if (((irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value())
|
||||
|| ((irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value()))
|
||||
&& (operating_mode != RadioMode::ChannelActivityDetection)
|
||||
{
|
||||
return Err(RadioError::CADUnexpected);
|
||||
}
|
||||
|
||||
if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() {
|
||||
trace!("HeaderValid");
|
||||
} else if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() {
|
||||
trace!("PreambleDetected");
|
||||
} else if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() {
|
||||
trace!("SyncwordValid");
|
||||
}
|
||||
|
||||
// handle completions
|
||||
if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Ok(());
|
||||
} else if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
|
||||
if !self.rx_continuous {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
|
||||
// implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3)
|
||||
self.brd_write_registers(Register::RTCCtrl, &[0x00]).await?;
|
||||
let mut evt_clr = [0x00u8];
|
||||
self.brd_read_registers(Register::EvtClr, &mut evt_clr).await?;
|
||||
evt_clr[0] |= 1 << 1;
|
||||
self.brd_write_registers(Register::EvtClr, &evt_clr).await?;
|
||||
}
|
||||
|
||||
if receiving_buffer.is_some() && received_len.is_some() {
|
||||
*(received_len.unwrap()) = self.sub_get_payload(receiving_buffer.unwrap()).await?;
|
||||
}
|
||||
self.packet_status = self.sub_get_packet_status().await?.into();
|
||||
return Ok(());
|
||||
} else if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
|
||||
if cad_activity_detected.is_some() {
|
||||
*(cad_activity_detected.unwrap()) =
|
||||
(irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
|
||||
}
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// if DIO1 was driven high for reasons other than an error or operation completion (currently, PreambleDetected, SyncwordValid, and HeaderValid
|
||||
// are in that category), loop to wait again
|
||||
}
|
||||
}
|
||||
|
||||
// SX126x-specific functions
|
||||
|
||||
/// Set the radio in reception mode with Max LNA gain for the given time (SX126x radios only) [0: continuous, others timeout in ms]
|
||||
pub async fn set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_dio_irq_params(
|
||||
IrqMask::All.value(),
|
||||
IrqMask::All.value(),
|
||||
IrqMask::None.value(),
|
||||
IrqMask::None.value(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if self.rx_continuous {
|
||||
self.sub_set_rx_boosted(0xFFFFFF).await?; // Rx continuous
|
||||
} else {
|
||||
self.sub_set_rx_boosted(timeout << 6).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the Rx duty cycle management parameters (SX126x radios only)
|
||||
/// rx_time structure describing reception timeout value
|
||||
/// sleep_time structure describing sleep timeout value
|
||||
pub async fn set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_rx_duty_cycle(rx_time, sleep_time).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_latest_packet_status(&mut self) -> Option<PacketStatus> {
|
||||
self.packet_status
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
async fn add_register_to_retention_list(&mut self, register_address: u16) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize];
|
||||
|
||||
// Read the address and registers already added to the list
|
||||
self.brd_read_registers(Register::RetentionList, &mut buffer).await?;
|
||||
|
||||
let number_of_registers = buffer[0];
|
||||
for i in 0..number_of_registers {
|
||||
if register_address
|
||||
== ((buffer[(1 + (2 * i)) as usize] as u16) << 8) | (buffer[(2 + (2 * i)) as usize] as u16)
|
||||
{
|
||||
return Ok(()); // register already in list
|
||||
}
|
||||
}
|
||||
|
||||
if number_of_registers < MAX_NUMBER_REGS_IN_RETENTION {
|
||||
buffer[0] += 1; // increment number of registers
|
||||
|
||||
buffer[(1 + (2 * number_of_registers)) as usize] = ((register_address >> 8) & 0xFF) as u8;
|
||||
buffer[(2 + (2 * number_of_registers)) as usize] = (register_address & 0xFF) as u8;
|
||||
self.brd_write_registers(Register::RetentionList, &buffer).await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::RetentionListExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_lora_time_on_air_numerator(
|
||||
spreading_factor: SpreadingFactor,
|
||||
bandwidth: Bandwidth,
|
||||
coding_rate: CodingRate,
|
||||
preamble_length: u16,
|
||||
fixed_len: bool,
|
||||
payload_len: u8,
|
||||
crc_on: bool,
|
||||
) -> u32 {
|
||||
let cell_denominator;
|
||||
let cr_denominator = (coding_rate.value() as i32) + 4;
|
||||
|
||||
// Ensure that the preamble length is at least 12 symbols when using SF5 or SF6
|
||||
let mut preamble_length_final = preamble_length;
|
||||
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
|
||||
&& (preamble_length < 12)
|
||||
{
|
||||
preamble_length_final = 12;
|
||||
}
|
||||
|
||||
let mut low_data_rate_optimize = false;
|
||||
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
|
||||
&& (bandwidth == Bandwidth::_125KHz))
|
||||
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
|
||||
{
|
||||
low_data_rate_optimize = true;
|
||||
}
|
||||
|
||||
let mut cell_numerator = ((payload_len as i32) << 3) + (if crc_on { 16 } else { 0 })
|
||||
- (4 * spreading_factor.value() as i32)
|
||||
+ (if fixed_len { 0 } else { 20 });
|
||||
|
||||
if spreading_factor.value() <= 6 {
|
||||
cell_denominator = 4 * (spreading_factor.value() as i32);
|
||||
} else {
|
||||
cell_numerator += 8;
|
||||
if low_data_rate_optimize {
|
||||
cell_denominator = 4 * ((spreading_factor.value() as i32) - 2);
|
||||
} else {
|
||||
cell_denominator = 4 * (spreading_factor.value() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
if cell_numerator < 0 {
|
||||
cell_numerator = 0;
|
||||
}
|
||||
|
||||
let mut intermediate: i32 = (((cell_numerator + cell_denominator - 1) / cell_denominator) * cr_denominator)
|
||||
+ (preamble_length_final as i32)
|
||||
+ 12;
|
||||
|
||||
if spreading_factor.value() <= 6 {
|
||||
intermediate = intermediate + 2;
|
||||
}
|
||||
|
||||
(((4 * intermediate) + 1) * (1 << (spreading_factor.value() - 2))) as u32
|
||||
}
|
||||
}
|
@ -1,469 +0,0 @@
|
||||
use core::fmt::Debug;
|
||||
|
||||
use lorawan_device::async_device::radio as device;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum RadioError<BUS> {
|
||||
SPI(BUS),
|
||||
CS,
|
||||
Reset,
|
||||
AntRx,
|
||||
AntTx,
|
||||
Busy,
|
||||
DIO1,
|
||||
PayloadSizeMismatch(usize, usize),
|
||||
RetentionListExceeded,
|
||||
InvalidBandwidth,
|
||||
ModulationParamsMissing,
|
||||
PacketParamsMissing,
|
||||
HeaderError,
|
||||
CRCErrorUnexpected,
|
||||
CRCErrorOnReceive,
|
||||
TransmitTimeout,
|
||||
ReceiveTimeout,
|
||||
TimeoutUnexpected,
|
||||
TransmitDoneUnexpected,
|
||||
ReceiveDoneUnexpected,
|
||||
CADUnexpected,
|
||||
}
|
||||
|
||||
pub struct RadioSystemError {
|
||||
pub rc_64khz_calibration: bool,
|
||||
pub rc_13mhz_calibration: bool,
|
||||
pub pll_calibration: bool,
|
||||
pub adc_calibration: bool,
|
||||
pub image_calibration: bool,
|
||||
pub xosc_start: bool,
|
||||
pub pll_lock: bool,
|
||||
pub pa_ramp: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum PacketType {
|
||||
GFSK = 0x00,
|
||||
LoRa = 0x01,
|
||||
None = 0x0F,
|
||||
}
|
||||
|
||||
impl PacketType {
|
||||
pub const fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
pub fn to_enum(value: u8) -> Self {
|
||||
if value == 0x00 {
|
||||
PacketType::GFSK
|
||||
} else if value == 0x01 {
|
||||
PacketType::LoRa
|
||||
} else {
|
||||
PacketType::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PacketStatus {
|
||||
pub rssi: i8,
|
||||
pub snr: i8,
|
||||
pub signal_rssi: i8,
|
||||
pub freq_error: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum RadioType {
|
||||
SX1261,
|
||||
SX1262,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum RadioMode {
|
||||
Sleep = 0x00, // sleep mode
|
||||
StandbyRC = 0x01, // standby mode with RC oscillator
|
||||
StandbyXOSC = 0x02, // standby mode with XOSC oscillator
|
||||
FrequencySynthesis = 0x03, // frequency synthesis mode
|
||||
Transmit = 0x04, // transmit mode
|
||||
Receive = 0x05, // receive mode
|
||||
ReceiveDutyCycle = 0x06, // receive duty cycle mode
|
||||
ChannelActivityDetection = 0x07, // channel activity detection mode
|
||||
}
|
||||
|
||||
impl RadioMode {
|
||||
/// Returns the value of the mode.
|
||||
pub const fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
pub fn to_enum(value: u8) -> Self {
|
||||
if value == 0x00 {
|
||||
RadioMode::Sleep
|
||||
} else if value == 0x01 {
|
||||
RadioMode::StandbyRC
|
||||
} else if value == 0x02 {
|
||||
RadioMode::StandbyXOSC
|
||||
} else if value == 0x03 {
|
||||
RadioMode::FrequencySynthesis
|
||||
} else if value == 0x04 {
|
||||
RadioMode::Transmit
|
||||
} else if value == 0x05 {
|
||||
RadioMode::Receive
|
||||
} else if value == 0x06 {
|
||||
RadioMode::ReceiveDutyCycle
|
||||
} else if value == 0x07 {
|
||||
RadioMode::ChannelActivityDetection
|
||||
} else {
|
||||
RadioMode::Sleep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RadioState {
|
||||
Idle = 0x00,
|
||||
RxRunning = 0x01,
|
||||
TxRunning = 0x02,
|
||||
ChannelActivityDetecting = 0x03,
|
||||
}
|
||||
|
||||
impl RadioState {
|
||||
/// Returns the value of the state.
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RadioStatus {
|
||||
pub cmd_status: u8,
|
||||
pub chip_mode: u8,
|
||||
}
|
||||
|
||||
impl RadioStatus {
|
||||
pub fn value(self) -> u8 {
|
||||
(self.chip_mode << 4) | (self.cmd_status << 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IrqMask {
|
||||
None = 0x0000,
|
||||
TxDone = 0x0001,
|
||||
RxDone = 0x0002,
|
||||
PreambleDetected = 0x0004,
|
||||
SyncwordValid = 0x0008,
|
||||
HeaderValid = 0x0010,
|
||||
HeaderError = 0x0020,
|
||||
CRCError = 0x0040,
|
||||
CADDone = 0x0080,
|
||||
CADActivityDetected = 0x0100,
|
||||
RxTxTimeout = 0x0200,
|
||||
All = 0xFFFF,
|
||||
}
|
||||
|
||||
impl IrqMask {
|
||||
pub fn value(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Register {
|
||||
PacketParams = 0x0704, // packet configuration
|
||||
PayloadLength = 0x0702, // payload size
|
||||
SynchTimeout = 0x0706, // recalculated number of symbols
|
||||
Syncword = 0x06C0, // Syncword values
|
||||
LoRaSyncword = 0x0740, // LoRa Syncword value
|
||||
GeneratedRandomNumber = 0x0819, //32-bit generated random number
|
||||
AnaLNA = 0x08E2, // disable the LNA
|
||||
AnaMixer = 0x08E5, // disable the mixer
|
||||
RxGain = 0x08AC, // RX gain (0x94: power saving, 0x96: rx boosted)
|
||||
XTATrim = 0x0911, // device internal trimming capacitor
|
||||
OCP = 0x08E7, // over current protection max value
|
||||
RetentionList = 0x029F, // retention list
|
||||
IQPolarity = 0x0736, // optimize the inverted IQ operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
|
||||
TxModulation = 0x0889, // modulation quality with 500 kHz LoRa Bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
|
||||
TxClampCfg = 0x08D8, // better resistance to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
|
||||
RTCCtrl = 0x0902, // RTC control
|
||||
EvtClr = 0x0944, // event clear
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub fn addr(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum OpCode {
|
||||
GetStatus = 0xC0,
|
||||
WriteRegister = 0x0D,
|
||||
ReadRegister = 0x1D,
|
||||
WriteBuffer = 0x0E,
|
||||
ReadBuffer = 0x1E,
|
||||
SetSleep = 0x84,
|
||||
SetStandby = 0x80,
|
||||
SetFS = 0xC1,
|
||||
SetTx = 0x83,
|
||||
SetRx = 0x82,
|
||||
SetRxDutyCycle = 0x94,
|
||||
SetCAD = 0xC5,
|
||||
SetTxContinuousWave = 0xD1,
|
||||
SetTxContinuousPremable = 0xD2,
|
||||
SetPacketType = 0x8A,
|
||||
GetPacketType = 0x11,
|
||||
SetRFFrequency = 0x86,
|
||||
SetTxParams = 0x8E,
|
||||
SetPAConfig = 0x95,
|
||||
SetCADParams = 0x88,
|
||||
SetBufferBaseAddress = 0x8F,
|
||||
SetModulationParams = 0x8B,
|
||||
SetPacketParams = 0x8C,
|
||||
GetRxBufferStatus = 0x13,
|
||||
GetPacketStatus = 0x14,
|
||||
GetRSSIInst = 0x15,
|
||||
GetStats = 0x10,
|
||||
ResetStats = 0x00,
|
||||
CfgDIOIrq = 0x08,
|
||||
GetIrqStatus = 0x12,
|
||||
ClrIrqStatus = 0x02,
|
||||
Calibrate = 0x89,
|
||||
CalibrateImage = 0x98,
|
||||
SetRegulatorMode = 0x96,
|
||||
GetErrors = 0x17,
|
||||
ClrErrors = 0x07,
|
||||
SetTCXOMode = 0x97,
|
||||
SetTxFallbackMode = 0x93,
|
||||
SetRFSwitchMode = 0x9D,
|
||||
SetStopRxTimerOnPreamble = 0x9F,
|
||||
SetLoRaSymbTimeout = 0xA0,
|
||||
}
|
||||
|
||||
impl OpCode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SleepParams {
|
||||
pub wakeup_rtc: bool, // get out of sleep mode if wakeup signal received from RTC
|
||||
pub reset: bool,
|
||||
pub warm_start: bool,
|
||||
}
|
||||
|
||||
impl SleepParams {
|
||||
pub fn value(self) -> u8 {
|
||||
((self.warm_start as u8) << 2) | ((self.reset as u8) << 1) | (self.wakeup_rtc as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum StandbyMode {
|
||||
RC = 0x00,
|
||||
XOSC = 0x01,
|
||||
}
|
||||
|
||||
impl StandbyMode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RegulatorMode {
|
||||
UseLDO = 0x00,
|
||||
UseDCDC = 0x01,
|
||||
}
|
||||
|
||||
impl RegulatorMode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CalibrationParams {
|
||||
pub rc64k_enable: bool, // calibrate RC64K clock
|
||||
pub rc13m_enable: bool, // calibrate RC13M clock
|
||||
pub pll_enable: bool, // calibrate PLL
|
||||
pub adc_pulse_enable: bool, // calibrate ADC Pulse
|
||||
pub adc_bulkn_enable: bool, // calibrate ADC bulkN
|
||||
pub adc_bulkp_enable: bool, // calibrate ADC bulkP
|
||||
pub img_enable: bool,
|
||||
}
|
||||
|
||||
impl CalibrationParams {
|
||||
pub fn value(self) -> u8 {
|
||||
((self.img_enable as u8) << 6)
|
||||
| ((self.adc_bulkp_enable as u8) << 5)
|
||||
| ((self.adc_bulkn_enable as u8) << 4)
|
||||
| ((self.adc_pulse_enable as u8) << 3)
|
||||
| ((self.pll_enable as u8) << 2)
|
||||
| ((self.rc13m_enable as u8) << 1)
|
||||
| ((self.rc64k_enable as u8) << 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TcxoCtrlVoltage {
|
||||
Ctrl1V6 = 0x00,
|
||||
Ctrl1V7 = 0x01,
|
||||
Ctrl1V8 = 0x02,
|
||||
Ctrl2V2 = 0x03,
|
||||
Ctrl2V4 = 0x04,
|
||||
Ctrl2V7 = 0x05,
|
||||
Ctrl3V0 = 0x06,
|
||||
Ctrl3V3 = 0x07,
|
||||
}
|
||||
|
||||
impl TcxoCtrlVoltage {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RampTime {
|
||||
Ramp10Us = 0x00,
|
||||
Ramp20Us = 0x01,
|
||||
Ramp40Us = 0x02,
|
||||
Ramp80Us = 0x03,
|
||||
Ramp200Us = 0x04,
|
||||
Ramp800Us = 0x05,
|
||||
Ramp1700Us = 0x06,
|
||||
Ramp3400Us = 0x07,
|
||||
}
|
||||
|
||||
impl RampTime {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum SpreadingFactor {
|
||||
_5 = 0x05,
|
||||
_6 = 0x06,
|
||||
_7 = 0x07,
|
||||
_8 = 0x08,
|
||||
_9 = 0x09,
|
||||
_10 = 0x0A,
|
||||
_11 = 0x0B,
|
||||
_12 = 0x0C,
|
||||
}
|
||||
|
||||
impl SpreadingFactor {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<device::SpreadingFactor> for SpreadingFactor {
|
||||
fn from(sf: device::SpreadingFactor) -> Self {
|
||||
match sf {
|
||||
device::SpreadingFactor::_7 => SpreadingFactor::_7,
|
||||
device::SpreadingFactor::_8 => SpreadingFactor::_8,
|
||||
device::SpreadingFactor::_9 => SpreadingFactor::_9,
|
||||
device::SpreadingFactor::_10 => SpreadingFactor::_10,
|
||||
device::SpreadingFactor::_11 => SpreadingFactor::_11,
|
||||
device::SpreadingFactor::_12 => SpreadingFactor::_12,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Bandwidth {
|
||||
_500KHz = 0x06,
|
||||
_250KHz = 0x05,
|
||||
_125KHz = 0x04,
|
||||
}
|
||||
|
||||
impl Bandwidth {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
pub fn value_in_hz(self) -> u32 {
|
||||
match self {
|
||||
Bandwidth::_125KHz => 125000u32,
|
||||
Bandwidth::_250KHz => 250000u32,
|
||||
Bandwidth::_500KHz => 500000u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<device::Bandwidth> for Bandwidth {
|
||||
fn from(bw: device::Bandwidth) -> Self {
|
||||
match bw {
|
||||
device::Bandwidth::_500KHz => Bandwidth::_500KHz,
|
||||
device::Bandwidth::_250KHz => Bandwidth::_250KHz,
|
||||
device::Bandwidth::_125KHz => Bandwidth::_125KHz,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CodingRate {
|
||||
_4_5 = 0x01,
|
||||
_4_6 = 0x02,
|
||||
_4_7 = 0x03,
|
||||
_4_8 = 0x04,
|
||||
}
|
||||
|
||||
impl CodingRate {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<device::CodingRate> for CodingRate {
|
||||
fn from(cr: device::CodingRate) -> Self {
|
||||
match cr {
|
||||
device::CodingRate::_4_5 => CodingRate::_4_5,
|
||||
device::CodingRate::_4_6 => CodingRate::_4_6,
|
||||
device::CodingRate::_4_7 => CodingRate::_4_7,
|
||||
device::CodingRate::_4_8 => CodingRate::_4_8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ModulationParams {
|
||||
pub spreading_factor: SpreadingFactor,
|
||||
pub bandwidth: Bandwidth,
|
||||
pub coding_rate: CodingRate,
|
||||
pub low_data_rate_optimize: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PacketParams {
|
||||
pub preamble_length: u16, // number of LoRa symbols in the preamble
|
||||
pub implicit_header: bool, // if the header is explicit, it will be transmitted in the LoRa packet, but is not transmitted if the header is implicit (known fixed length)
|
||||
pub payload_length: u8,
|
||||
pub crc_on: bool,
|
||||
pub iq_inverted: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CADSymbols {
|
||||
_1 = 0x00,
|
||||
_2 = 0x01,
|
||||
_4 = 0x02,
|
||||
_8 = 0x03,
|
||||
_16 = 0x04,
|
||||
}
|
||||
|
||||
impl CADSymbols {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CADExitMode {
|
||||
CADOnly = 0x00,
|
||||
CADRx = 0x01,
|
||||
CADLBT = 0x10,
|
||||
}
|
||||
|
||||
impl CADExitMode {
|
||||
pub fn value(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
@ -1,674 +0,0 @@
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
use super::mod_params::*;
|
||||
use super::LoRa;
|
||||
|
||||
// Internal frequency of the radio
|
||||
const SX126X_XTAL_FREQ: u32 = 32000000;
|
||||
|
||||
// Scaling factor used to perform fixed-point operations
|
||||
const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14;
|
||||
|
||||
// PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT
|
||||
const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT);
|
||||
|
||||
// Maximum value for parameter symbNum
|
||||
const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248;
|
||||
|
||||
// Provides board-specific functionality for Semtech SX126x-based boards
|
||||
|
||||
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = BUS>,
|
||||
CTRL: OutputPin,
|
||||
WAIT: Wait,
|
||||
{
|
||||
// Initialize the radio driver
|
||||
pub(super) async fn sub_init(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_reset().await?;
|
||||
self.brd_wakeup().await?;
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
self.brd_io_tcxo_init().await?;
|
||||
self.brd_io_rf_switch_init().await?;
|
||||
self.image_calibrated = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Wakeup the radio if it is in Sleep mode and check that Busy is low
|
||||
pub(super) async fn sub_check_device_ready(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
let operating_mode = self.brd_get_operating_mode();
|
||||
if operating_mode == RadioMode::Sleep || operating_mode == RadioMode::ReceiveDutyCycle {
|
||||
self.brd_wakeup().await?;
|
||||
}
|
||||
self.brd_wait_on_busy().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Save the payload to be sent in the radio buffer
|
||||
pub(super) async fn sub_set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_buffer(0x00, payload).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Read the payload received.
|
||||
pub(super) async fn sub_get_payload(&mut self, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
|
||||
let (size, offset) = self.sub_get_rx_buffer_status().await?;
|
||||
if (size as usize) > buffer.len() {
|
||||
Err(RadioError::PayloadSizeMismatch(size as usize, buffer.len()))
|
||||
} else {
|
||||
self.brd_read_buffer(offset, buffer).await?;
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a payload
|
||||
pub(super) async fn sub_send_payload(&mut self, payload: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
self.sub_set_payload(payload).await?;
|
||||
self.sub_set_tx(timeout).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get a 32-bit random value generated by the radio. A valid packet type must have been configured before using this command.
|
||||
//
|
||||
// The radio must be in reception mode before executing this function. This code can potentially result in interrupt generation. It is the responsibility of
|
||||
// the calling code to disable radio interrupts before calling this function, and re-enable them afterwards if necessary, or be certain that any interrupts
|
||||
// generated during this process will not cause undesired side-effects in the software.
|
||||
//
|
||||
// The random numbers produced by the generator do not have a uniform or Gaussian distribution. If uniformity is needed, perform appropriate software post-processing.
|
||||
pub(super) async fn sub_get_random(&mut self) -> Result<u32, RadioError<BUS>> {
|
||||
let mut reg_ana_lna_buffer_original = [0x00u8];
|
||||
let mut reg_ana_mixer_buffer_original = [0x00u8];
|
||||
let mut reg_ana_lna_buffer = [0x00u8];
|
||||
let mut reg_ana_mixer_buffer = [0x00u8];
|
||||
let mut number_buffer = [0x00u8, 0x00u8, 0x00u8, 0x00u8];
|
||||
|
||||
self.brd_read_registers(Register::AnaLNA, &mut reg_ana_lna_buffer_original)
|
||||
.await?;
|
||||
reg_ana_lna_buffer[0] = reg_ana_lna_buffer_original[0] & (!(1 << 0));
|
||||
self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer).await?;
|
||||
|
||||
self.brd_read_registers(Register::AnaMixer, &mut reg_ana_mixer_buffer_original)
|
||||
.await?;
|
||||
reg_ana_mixer_buffer[0] = reg_ana_mixer_buffer_original[0] & (!(1 << 7));
|
||||
self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer)
|
||||
.await?;
|
||||
|
||||
// Set radio in continuous reception
|
||||
self.sub_set_rx(0xFFFFFFu32).await?;
|
||||
|
||||
self.brd_read_registers(Register::GeneratedRandomNumber, &mut number_buffer)
|
||||
.await?;
|
||||
|
||||
self.sub_set_standby(StandbyMode::RC).await?;
|
||||
|
||||
self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer_original)
|
||||
.await?;
|
||||
self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer_original)
|
||||
.await?;
|
||||
|
||||
Ok(Self::convert_u8_buffer_to_u32(&number_buffer))
|
||||
}
|
||||
|
||||
// Set the radio in sleep mode
|
||||
pub(super) async fn sub_set_sleep(&mut self, sleep_config: SleepParams) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_sleep()?;
|
||||
|
||||
if !sleep_config.warm_start {
|
||||
self.image_calibrated = false;
|
||||
}
|
||||
|
||||
self.brd_write_command(OpCode::SetSleep, &[sleep_config.value()])
|
||||
.await?;
|
||||
self.brd_set_operating_mode(RadioMode::Sleep);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in configuration mode
|
||||
pub(super) async fn sub_set_standby(&mut self, mode: StandbyMode) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetStandby, &[mode.value()]).await?;
|
||||
if mode == StandbyMode::RC {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyRC);
|
||||
} else {
|
||||
self.brd_set_operating_mode(RadioMode::StandbyXOSC);
|
||||
}
|
||||
|
||||
self.brd_ant_sleep()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in FS mode
|
||||
pub(super) async fn sub_set_fs(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
// antenna settings ???
|
||||
self.brd_write_command(OpCode::SetFS, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::FrequencySynthesis);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in transmission mode with timeout specified
|
||||
pub(super) async fn sub_set_tx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
|
||||
self.brd_ant_set_tx()?;
|
||||
|
||||
self.brd_set_operating_mode(RadioMode::Transmit);
|
||||
self.brd_write_command(OpCode::SetTx, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in reception mode with timeout specified
|
||||
pub(super) async fn sub_set_rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
|
||||
self.brd_ant_set_rx()?;
|
||||
|
||||
self.brd_set_operating_mode(RadioMode::Receive);
|
||||
self.brd_write_registers(Register::RxGain, &[0x94u8]).await?;
|
||||
self.brd_write_command(OpCode::SetRx, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in reception mode with Boosted LNA gain and timeout specified
|
||||
pub(super) async fn sub_set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
|
||||
self.brd_ant_set_rx()?;
|
||||
|
||||
self.brd_set_operating_mode(RadioMode::Receive);
|
||||
// set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity
|
||||
self.brd_write_registers(Register::RxGain, &[0x96u8]).await?;
|
||||
self.brd_write_command(OpCode::SetRx, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the Rx duty cycle management parameters
|
||||
pub(super) async fn sub_set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
((rx_time >> 16) & 0xFF) as u8,
|
||||
((rx_time >> 8) & 0xFF) as u8,
|
||||
(rx_time & 0xFF) as u8,
|
||||
((sleep_time >> 16) & 0xFF) as u8,
|
||||
((sleep_time >> 8) & 0xFF) as u8,
|
||||
(sleep_time & 0xFF) as u8,
|
||||
];
|
||||
|
||||
// antenna settings ???
|
||||
|
||||
self.brd_write_command(OpCode::SetRxDutyCycle, &buffer).await?;
|
||||
self.brd_set_operating_mode(RadioMode::ReceiveDutyCycle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in CAD mode
|
||||
pub(super) async fn sub_set_cad(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_set_rx()?;
|
||||
|
||||
self.brd_write_command(OpCode::SetCAD, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in continuous wave transmission mode
|
||||
pub(super) async fn sub_set_tx_continuous_wave(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_set_tx()?;
|
||||
|
||||
self.brd_write_command(OpCode::SetTxContinuousWave, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::Transmit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio in continuous preamble transmission mode
|
||||
pub(super) async fn sub_set_tx_infinite_preamble(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_ant_set_tx()?;
|
||||
|
||||
self.brd_write_command(OpCode::SetTxContinuousPremable, &[]).await?;
|
||||
self.brd_set_operating_mode(RadioMode::Transmit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Decide which interrupt will stop the internal radio rx timer.
|
||||
// false timer stop after header/syncword detection
|
||||
// true timer stop after preamble detection
|
||||
pub(super) async fn sub_set_stop_rx_timer_on_preamble_detect(
|
||||
&mut self,
|
||||
enable: bool,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetStopRxTimerOnPreamble, &[enable as u8])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the number of symbols the radio will wait to validate a reception
|
||||
pub(super) async fn sub_set_lora_symb_num_timeout(&mut self, symb_num: u16) -> Result<(), RadioError<BUS>> {
|
||||
let mut exp = 0u8;
|
||||
let mut reg;
|
||||
let mut mant = ((core::cmp::min(symb_num, SX126X_MAX_LORA_SYMB_NUM_TIMEOUT as u16) as u8) + 1) >> 1;
|
||||
while mant > 31 {
|
||||
mant = (mant + 3) >> 2;
|
||||
exp += 1;
|
||||
}
|
||||
reg = mant << ((2 * exp) + 1);
|
||||
|
||||
self.brd_write_command(OpCode::SetLoRaSymbTimeout, &[reg]).await?;
|
||||
|
||||
if symb_num != 0 {
|
||||
reg = exp + (mant << 3);
|
||||
self.brd_write_registers(Register::SynchTimeout, &[reg]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the power regulators operating mode (LDO or DC_DC). Using only LDO implies that the Rx or Tx current is doubled
|
||||
pub(super) async fn sub_set_regulator_mode(&mut self, mode: RegulatorMode) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetRegulatorMode, &[mode.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Calibrate the given radio block
|
||||
pub(super) async fn sub_calibrate(&mut self, calibrate_params: CalibrationParams) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::Calibrate, &[calibrate_params.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Calibrate the image rejection based on the given frequency
|
||||
pub(super) async fn sub_calibrate_image(&mut self, freq: u32) -> Result<(), RadioError<BUS>> {
|
||||
let mut cal_freq = [0x00u8, 0x00u8];
|
||||
|
||||
if freq > 900000000 {
|
||||
cal_freq[0] = 0xE1;
|
||||
cal_freq[1] = 0xE9;
|
||||
} else if freq > 850000000 {
|
||||
cal_freq[0] = 0xD7;
|
||||
cal_freq[1] = 0xDB;
|
||||
} else if freq > 770000000 {
|
||||
cal_freq[0] = 0xC1;
|
||||
cal_freq[1] = 0xC5;
|
||||
} else if freq > 460000000 {
|
||||
cal_freq[0] = 0x75;
|
||||
cal_freq[1] = 0x81;
|
||||
} else if freq > 425000000 {
|
||||
cal_freq[0] = 0x6B;
|
||||
cal_freq[1] = 0x6F;
|
||||
}
|
||||
self.brd_write_command(OpCode::CalibrateImage, &cal_freq).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Activate the extention of the timeout when a long preamble is used
|
||||
pub(super) async fn sub_set_long_preamble(&mut self, _enable: u8) -> Result<(), RadioError<BUS>> {
|
||||
Ok(()) // no operation currently
|
||||
}
|
||||
|
||||
// Set the transmission parameters
|
||||
// hp_max 0 for sx1261, 7 for sx1262
|
||||
// device_sel 1 for sx1261, 0 for sx1262
|
||||
// pa_lut 0 for 14dBm LUT, 1 for 22dBm LUT
|
||||
pub(super) async fn sub_set_pa_config(
|
||||
&mut self,
|
||||
pa_duty_cycle: u8,
|
||||
hp_max: u8,
|
||||
device_sel: u8,
|
||||
pa_lut: u8,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetPAConfig, &[pa_duty_cycle, hp_max, device_sel, pa_lut])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Define into which mode the chip goes after a TX / RX done
|
||||
pub(super) async fn sub_set_rx_tx_fallback_mode(&mut self, fallback_mode: u8) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetTxFallbackMode, &[fallback_mode])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the IRQ mask and DIO masks
|
||||
pub(super) async fn sub_set_dio_irq_params(
|
||||
&mut self,
|
||||
irq_mask: u16,
|
||||
dio1_mask: u16,
|
||||
dio2_mask: u16,
|
||||
dio3_mask: u16,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; 8];
|
||||
|
||||
buffer[0] = ((irq_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[1] = (irq_mask & 0x00FF) as u8;
|
||||
buffer[2] = ((dio1_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[3] = (dio1_mask & 0x00FF) as u8;
|
||||
buffer[4] = ((dio2_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[5] = (dio2_mask & 0x00FF) as u8;
|
||||
buffer[6] = ((dio3_mask >> 8) & 0x00FF) as u8;
|
||||
buffer[7] = (dio3_mask & 0x00FF) as u8;
|
||||
self.brd_write_command(OpCode::CfgDIOIrq, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Return the current IRQ status
|
||||
pub(super) async fn sub_get_irq_status(&mut self) -> Result<u16, RadioError<BUS>> {
|
||||
let mut irq_status = [0x00u8, 0x00u8];
|
||||
self.brd_read_command(OpCode::GetIrqStatus, &mut irq_status).await?;
|
||||
Ok(((irq_status[0] as u16) << 8) | (irq_status[1] as u16))
|
||||
}
|
||||
|
||||
// Indicate if DIO2 is used to control an RF Switch
|
||||
pub(super) async fn sub_set_dio2_as_rf_switch_ctrl(&mut self, enable: bool) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetRFSwitchMode, &[enable as u8]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Indicate if the radio main clock is supplied from a TCXO
|
||||
// tcxo_voltage voltage used to control the TCXO on/off from DIO3
|
||||
// timeout duration given to the TCXO to go to 32MHz
|
||||
pub(super) async fn sub_set_dio3_as_tcxo_ctrl(
|
||||
&mut self,
|
||||
tcxo_voltage: TcxoCtrlVoltage,
|
||||
timeout: u32,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let buffer = [
|
||||
tcxo_voltage.value() & 0x07,
|
||||
Self::timeout_1(timeout),
|
||||
Self::timeout_2(timeout),
|
||||
Self::timeout_3(timeout),
|
||||
];
|
||||
self.brd_write_command(OpCode::SetTCXOMode, &buffer).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the RF frequency (Hz)
|
||||
pub(super) async fn sub_set_rf_frequency(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; 4];
|
||||
|
||||
if !self.image_calibrated {
|
||||
self.sub_calibrate_image(frequency).await?;
|
||||
self.image_calibrated = true;
|
||||
}
|
||||
|
||||
let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency);
|
||||
|
||||
buffer[0] = ((freq_in_pll_steps >> 24) & 0xFF) as u8;
|
||||
buffer[1] = ((freq_in_pll_steps >> 16) & 0xFF) as u8;
|
||||
buffer[2] = ((freq_in_pll_steps >> 8) & 0xFF) as u8;
|
||||
buffer[3] = (freq_in_pll_steps & 0xFF) as u8;
|
||||
self.brd_write_command(OpCode::SetRFFrequency, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the radio for the given protocol (LoRa or GFSK). This method has to be called before setting RF frequency, modulation paramaters, and packet paramaters.
|
||||
pub(super) async fn sub_set_packet_type(&mut self, packet_type: PacketType) -> Result<(), RadioError<BUS>> {
|
||||
self.packet_type = packet_type;
|
||||
self.brd_write_command(OpCode::SetPacketType, &[packet_type.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the current radio protocol (LoRa or GFSK)
|
||||
pub(super) fn sub_get_packet_type(&mut self) -> PacketType {
|
||||
self.packet_type
|
||||
}
|
||||
|
||||
// Set the transmission parameters
|
||||
// power RF output power [-18..13] dBm
|
||||
// ramp_time transmission ramp up time
|
||||
pub(super) async fn sub_set_tx_params(
|
||||
&mut self,
|
||||
mut power: i8,
|
||||
ramp_time: RampTime,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
if self.brd_get_radio_type() == RadioType::SX1261 {
|
||||
if power == 15 {
|
||||
self.sub_set_pa_config(0x06, 0x00, 0x01, 0x01).await?;
|
||||
} else {
|
||||
self.sub_set_pa_config(0x04, 0x00, 0x01, 0x01).await?;
|
||||
}
|
||||
|
||||
if power >= 14 {
|
||||
power = 14;
|
||||
} else if power < -17 {
|
||||
power = -17;
|
||||
}
|
||||
} else {
|
||||
// Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
|
||||
let mut tx_clamp_cfg = [0x00u8];
|
||||
self.brd_read_registers(Register::TxClampCfg, &mut tx_clamp_cfg).await?;
|
||||
tx_clamp_cfg[0] = tx_clamp_cfg[0] | (0x0F << 1);
|
||||
self.brd_write_registers(Register::TxClampCfg, &tx_clamp_cfg).await?;
|
||||
|
||||
self.sub_set_pa_config(0x04, 0x07, 0x00, 0x01).await?;
|
||||
|
||||
if power > 22 {
|
||||
power = 22;
|
||||
} else if power < -9 {
|
||||
power = -9;
|
||||
}
|
||||
}
|
||||
|
||||
// power conversion of negative number from i8 to u8 ???
|
||||
self.brd_write_command(OpCode::SetTxParams, &[power as u8, ramp_time.value()])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the modulation parameters
|
||||
pub(super) async fn sub_set_modulation_params(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
if self.modulation_params.is_some() {
|
||||
let mut buffer = [0x00u8; 4];
|
||||
|
||||
// Since this driver only supports LoRa, ensure the packet type is set accordingly
|
||||
self.sub_set_packet_type(PacketType::LoRa).await?;
|
||||
|
||||
let modulation_params = self.modulation_params.unwrap();
|
||||
buffer[0] = modulation_params.spreading_factor.value();
|
||||
buffer[1] = modulation_params.bandwidth.value();
|
||||
buffer[2] = modulation_params.coding_rate.value();
|
||||
buffer[3] = modulation_params.low_data_rate_optimize;
|
||||
|
||||
self.brd_write_command(OpCode::SetModulationParams, &buffer).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::ModulationParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the packet parameters
|
||||
pub(super) async fn sub_set_packet_params(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
let mut buffer = [0x00u8; 6];
|
||||
|
||||
// Since this driver only supports LoRa, ensure the packet type is set accordingly
|
||||
self.sub_set_packet_type(PacketType::LoRa).await?;
|
||||
|
||||
let packet_params = self.packet_params.unwrap();
|
||||
buffer[0] = ((packet_params.preamble_length >> 8) & 0xFF) as u8;
|
||||
buffer[1] = (packet_params.preamble_length & 0xFF) as u8;
|
||||
buffer[2] = packet_params.implicit_header as u8;
|
||||
buffer[3] = packet_params.payload_length;
|
||||
buffer[4] = packet_params.crc_on as u8;
|
||||
buffer[5] = packet_params.iq_inverted as u8;
|
||||
|
||||
self.brd_write_command(OpCode::SetPacketParams, &buffer).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the channel activity detection (CAD) parameters
|
||||
// symbols number of symbols to use for CAD operations
|
||||
// det_peak limit for detection of SNR peak used in the CAD
|
||||
// det_min minimum symbol recognition for CAD
|
||||
// exit_mode operation to be done at the end of CAD action
|
||||
// timeout timeout value to abort the CAD activity
|
||||
|
||||
pub(super) async fn sub_set_cad_params(
|
||||
&mut self,
|
||||
symbols: CADSymbols,
|
||||
det_peak: u8,
|
||||
det_min: u8,
|
||||
exit_mode: CADExitMode,
|
||||
timeout: u32,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8; 7];
|
||||
|
||||
buffer[0] = symbols.value();
|
||||
buffer[1] = det_peak;
|
||||
buffer[2] = det_min;
|
||||
buffer[3] = exit_mode.value();
|
||||
buffer[4] = Self::timeout_1(timeout);
|
||||
buffer[5] = Self::timeout_2(timeout);
|
||||
buffer[6] = Self::timeout_3(timeout);
|
||||
|
||||
self.brd_write_command(OpCode::SetCADParams, &buffer).await?;
|
||||
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set the data buffer base address for transmission and reception
|
||||
pub(super) async fn sub_set_buffer_base_address(
|
||||
&mut self,
|
||||
tx_base_address: u8,
|
||||
rx_base_address: u8,
|
||||
) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::SetBufferBaseAddress, &[tx_base_address, rx_base_address])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Get the current radio status
|
||||
pub(super) async fn sub_get_status(&mut self) -> Result<RadioStatus, RadioError<BUS>> {
|
||||
let status = self.brd_read_command(OpCode::GetStatus, &mut []).await?;
|
||||
Ok(RadioStatus {
|
||||
cmd_status: (status & (0x07 << 1)) >> 1,
|
||||
chip_mode: (status & (0x07 << 4)) >> 4,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the instantaneous RSSI value for the last packet received
|
||||
pub(super) async fn sub_get_rssi_inst(&mut self) -> Result<i8, RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8];
|
||||
self.brd_read_command(OpCode::GetRSSIInst, &mut buffer).await?;
|
||||
let rssi: i8 = ((-(buffer[0] as i32)) >> 1) as i8; // check this ???
|
||||
Ok(rssi)
|
||||
}
|
||||
|
||||
// Get the last received packet buffer status
|
||||
pub(super) async fn sub_get_rx_buffer_status(&mut self) -> Result<(u8, u8), RadioError<BUS>> {
|
||||
if self.packet_params.is_some() {
|
||||
let mut status = [0x00u8; 2];
|
||||
let mut payload_length_buffer = [0x00u8];
|
||||
|
||||
self.brd_read_command(OpCode::GetRxBufferStatus, &mut status).await?;
|
||||
if (self.sub_get_packet_type() == PacketType::LoRa) && self.packet_params.unwrap().implicit_header {
|
||||
self.brd_read_registers(Register::PayloadLength, &mut payload_length_buffer)
|
||||
.await?;
|
||||
} else {
|
||||
payload_length_buffer[0] = status[0];
|
||||
}
|
||||
|
||||
let payload_length = payload_length_buffer[0];
|
||||
let offset = status[1];
|
||||
|
||||
Ok((payload_length, offset))
|
||||
} else {
|
||||
Err(RadioError::PacketParamsMissing)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the last received packet payload status
|
||||
pub(super) async fn sub_get_packet_status(&mut self) -> Result<PacketStatus, RadioError<BUS>> {
|
||||
let mut status = [0x00u8; 3];
|
||||
self.brd_read_command(OpCode::GetPacketStatus, &mut status).await?;
|
||||
|
||||
// check this ???
|
||||
let rssi = ((-(status[0] as i32)) >> 1) as i8;
|
||||
let snr = ((status[1] as i8) + 2) >> 2;
|
||||
let signal_rssi = ((-(status[2] as i32)) >> 1) as i8;
|
||||
let freq_error = self.frequency_error;
|
||||
|
||||
Ok(PacketStatus {
|
||||
rssi,
|
||||
snr,
|
||||
signal_rssi,
|
||||
freq_error,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the possible system errors
|
||||
pub(super) async fn sub_get_device_errors(&mut self) -> Result<RadioSystemError, RadioError<BUS>> {
|
||||
let mut errors = [0x00u8; 2];
|
||||
self.brd_read_command(OpCode::GetErrors, &mut errors).await?;
|
||||
|
||||
Ok(RadioSystemError {
|
||||
rc_64khz_calibration: (errors[1] & (1 << 0)) != 0,
|
||||
rc_13mhz_calibration: (errors[1] & (1 << 1)) != 0,
|
||||
pll_calibration: (errors[1] & (1 << 2)) != 0,
|
||||
adc_calibration: (errors[1] & (1 << 3)) != 0,
|
||||
image_calibration: (errors[1] & (1 << 4)) != 0,
|
||||
xosc_start: (errors[1] & (1 << 5)) != 0,
|
||||
pll_lock: (errors[1] & (1 << 6)) != 0,
|
||||
pa_ramp: (errors[0] & (1 << 0)) != 0,
|
||||
})
|
||||
}
|
||||
|
||||
// Clear all the errors in the device
|
||||
pub(super) async fn sub_clear_device_errors(&mut self) -> Result<(), RadioError<BUS>> {
|
||||
self.brd_write_command(OpCode::ClrErrors, &[0x00u8, 0x00u8]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Clear the IRQs
|
||||
pub(super) async fn sub_clear_irq_status(&mut self, irq: u16) -> Result<(), RadioError<BUS>> {
|
||||
let mut buffer = [0x00u8, 0x00u8];
|
||||
buffer[0] = ((irq >> 8) & 0xFF) as u8;
|
||||
buffer[1] = (irq & 0xFF) as u8;
|
||||
self.brd_write_command(OpCode::ClrIrqStatus, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
fn timeout_1(timeout: u32) -> u8 {
|
||||
((timeout >> 16) & 0xFF) as u8
|
||||
}
|
||||
fn timeout_2(timeout: u32) -> u8 {
|
||||
((timeout >> 8) & 0xFF) as u8
|
||||
}
|
||||
fn timeout_3(timeout: u32) -> u8 {
|
||||
(timeout & 0xFF) as u8
|
||||
}
|
||||
|
||||
// check this ???
|
||||
fn convert_u8_buffer_to_u32(buffer: &[u8; 4]) -> u32 {
|
||||
let b0 = buffer[0] as u32;
|
||||
let b1 = buffer[1] as u32;
|
||||
let b2 = buffer[2] as u32;
|
||||
let b3 = buffer[3] as u32;
|
||||
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
|
||||
}
|
||||
|
||||
fn convert_freq_in_hz_to_pll_step(freq_in_hz: u32) -> u32 {
|
||||
// Get integer and fractional parts of the frequency computed with a PLL step scaled value
|
||||
let steps_int = freq_in_hz / SX126X_PLL_STEP_SCALED;
|
||||
let steps_frac = freq_in_hz - (steps_int * SX126X_PLL_STEP_SCALED);
|
||||
|
||||
(steps_int << SX126X_PLL_STEP_SHIFT_AMOUNT)
|
||||
+ (((steps_frac << SX126X_PLL_STEP_SHIFT_AMOUNT) + (SX126X_PLL_STEP_SCALED >> 1)) / SX126X_PLL_STEP_SCALED)
|
||||
}
|
||||
}
|
@ -1,217 +0,0 @@
|
||||
use core::future::Future;
|
||||
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::digital::Wait;
|
||||
use embedded_hal_async::spi::*;
|
||||
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
|
||||
use lorawan_device::async_device::Timings;
|
||||
|
||||
mod sx127x_lora;
|
||||
use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ};
|
||||
|
||||
/// Trait representing a radio switch for boards using the Sx127x radio. One some
|
||||
/// boards, this will be a dummy implementation that does nothing.
|
||||
pub trait RadioSwitch {
|
||||
fn set_tx(&mut self);
|
||||
fn set_rx(&mut self);
|
||||
}
|
||||
|
||||
/// Semtech Sx127x radio peripheral
|
||||
pub struct Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
E: 'static,
|
||||
CS: OutputPin + 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
{
|
||||
radio: LoRa<SPI, CS, RESET>,
|
||||
rfs: RFS,
|
||||
irq: I,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum State {
|
||||
Idle,
|
||||
Txing,
|
||||
Rxing,
|
||||
}
|
||||
|
||||
impl<SPI, CS, RESET, E, I, RFS> Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
CS: OutputPin + 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
E: 'static,
|
||||
{
|
||||
pub async fn new(
|
||||
spi: SPI,
|
||||
cs: CS,
|
||||
reset: RESET,
|
||||
irq: I,
|
||||
rfs: RFS,
|
||||
) -> Result<Self, RadioError<E, CS::Error, RESET::Error>> {
|
||||
let mut radio = LoRa::new(spi, cs, reset);
|
||||
radio.reset().await?;
|
||||
Ok(Self { radio, irq, rfs })
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, RESET, E, I, RFS> Timings for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
CS: OutputPin + 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
{
|
||||
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||
-3
|
||||
}
|
||||
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||
1003
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, RESET, E, I, RFS> PhyRxTx for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E> + 'static,
|
||||
CS: OutputPin + 'static,
|
||||
E: 'static,
|
||||
RESET: OutputPin + 'static,
|
||||
I: Wait + 'static,
|
||||
RFS: RadioSwitch + 'static,
|
||||
{
|
||||
type PhyError = Sx127xError;
|
||||
|
||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CS: 'm,
|
||||
RESET: 'm,
|
||||
E: 'm,
|
||||
I: 'm,
|
||||
RFS: 'm;
|
||||
|
||||
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||
trace!("TX START");
|
||||
async move {
|
||||
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
|
||||
self.rfs.set_tx();
|
||||
self.radio.set_tx_power(14, 0).await?;
|
||||
self.radio.set_frequency(config.rf.frequency).await?;
|
||||
// TODO: Modify radio to support other coding rates
|
||||
self.radio.set_coding_rate_4(5).await?;
|
||||
self.radio
|
||||
.set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth))
|
||||
.await?;
|
||||
self.radio
|
||||
.set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor))
|
||||
.await?;
|
||||
|
||||
self.radio.set_preamble_length(8).await?;
|
||||
self.radio.set_lora_pa_ramp().await?;
|
||||
self.radio.set_lora_sync_word().await?;
|
||||
self.radio.set_invert_iq(false).await?;
|
||||
self.radio.set_crc(true).await?;
|
||||
|
||||
self.radio.set_dio0_tx_done().await?;
|
||||
|
||||
self.radio.transmit_start(buf).await?;
|
||||
|
||||
loop {
|
||||
self.irq.wait_for_rising_edge().await.unwrap();
|
||||
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
|
||||
let irq = self.radio.clear_irq().await.ok().unwrap();
|
||||
if (irq & IRQ::IrqTxDoneMask.addr()) != 0 {
|
||||
trace!("TX DONE");
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm
|
||||
where
|
||||
SPI: 'm,
|
||||
CS: 'm,
|
||||
RESET: 'm,
|
||||
E: 'm,
|
||||
I: 'm,
|
||||
RFS: 'm;
|
||||
|
||||
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||
trace!("RX START");
|
||||
async move {
|
||||
self.rfs.set_rx();
|
||||
self.radio.reset_payload_length().await?;
|
||||
self.radio.set_frequency(config.frequency).await?;
|
||||
// TODO: Modify radio to support other coding rates
|
||||
self.radio.set_coding_rate_4(5).await?;
|
||||
self.radio
|
||||
.set_signal_bandwidth(bandwidth_to_i64(config.bandwidth))
|
||||
.await?;
|
||||
self.radio
|
||||
.set_spreading_factor(spreading_factor_to_u8(config.spreading_factor))
|
||||
.await?;
|
||||
|
||||
self.radio.set_preamble_length(8).await?;
|
||||
self.radio.set_lora_sync_word().await?;
|
||||
self.radio.set_invert_iq(true).await?;
|
||||
self.radio.set_crc(true).await?;
|
||||
|
||||
self.radio.set_dio0_rx_done().await?;
|
||||
self.radio.set_mode(RadioMode::RxContinuous).await?;
|
||||
|
||||
loop {
|
||||
self.irq.wait_for_rising_edge().await.unwrap();
|
||||
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
|
||||
let irq = self.radio.clear_irq().await.ok().unwrap();
|
||||
if (irq & IRQ::IrqRxDoneMask.addr()) != 0 {
|
||||
let rssi = self.radio.get_packet_rssi().await.unwrap_or(0) as i16;
|
||||
let snr = self.radio.get_packet_snr().await.unwrap_or(0.0) as i8;
|
||||
let response = if let Ok(size) = self.radio.read_packet_size().await {
|
||||
self.radio.read_packet(buf).await?;
|
||||
Ok((size, RxQuality::new(rssi, snr)))
|
||||
} else {
|
||||
Ok((0, RxQuality::new(rssi, snr)))
|
||||
};
|
||||
trace!("RX DONE");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Sx127xError;
|
||||
|
||||
impl<A, B, C> From<sx127x_lora::Error<A, B, C>> for Sx127xError {
|
||||
fn from(_: sx127x_lora::Error<A, B, C>) -> Self {
|
||||
Sx127xError
|
||||
}
|
||||
}
|
||||
|
||||
fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 {
|
||||
match sf {
|
||||
SpreadingFactor::_7 => 7,
|
||||
SpreadingFactor::_8 => 8,
|
||||
SpreadingFactor::_9 => 9,
|
||||
SpreadingFactor::_10 => 10,
|
||||
SpreadingFactor::_11 => 11,
|
||||
SpreadingFactor::_12 => 12,
|
||||
}
|
||||
}
|
||||
|
||||
fn bandwidth_to_i64(bw: Bandwidth) -> i64 {
|
||||
match bw {
|
||||
Bandwidth::_125KHz => 125_000,
|
||||
Bandwidth::_250KHz => 250_000,
|
||||
Bandwidth::_500KHz => 500_000,
|
||||
}
|
||||
}
|
@ -1,539 +0,0 @@
|
||||
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
|
||||
// license
|
||||
//
|
||||
// Modifications made to make the driver work with the rust-lorawan link layer.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use bit_field::BitField;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
|
||||
mod register;
|
||||
pub use self::register::IRQ;
|
||||
use self::register::{PaConfig, Register};
|
||||
|
||||
/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi
|
||||
pub struct LoRa<SPI, CS, RESET> {
|
||||
spi: SPI,
|
||||
cs: CS,
|
||||
reset: RESET,
|
||||
pub explicit_header: bool,
|
||||
pub mode: RadioMode,
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error<SPI, CS, RESET> {
|
||||
Uninformative,
|
||||
VersionMismatch(u8),
|
||||
CS(CS),
|
||||
Reset(RESET),
|
||||
SPI(SPI),
|
||||
Transmitting,
|
||||
}
|
||||
|
||||
use Error::*;
|
||||
|
||||
use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown};
|
||||
|
||||
#[cfg(not(feature = "version_0x09"))]
|
||||
const VERSION_CHECK: u8 = 0x12;
|
||||
|
||||
#[cfg(feature = "version_0x09")]
|
||||
const VERSION_CHECK: u8 = 0x09;
|
||||
|
||||
impl<SPI, CS, RESET, E> LoRa<SPI, CS, RESET>
|
||||
where
|
||||
SPI: SpiBus<u8, Error = E>,
|
||||
CS: OutputPin,
|
||||
RESET: OutputPin,
|
||||
{
|
||||
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time.
|
||||
/// This also preforms a hardware reset of the module and then puts it in standby.
|
||||
pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self {
|
||||
Self {
|
||||
spi,
|
||||
cs,
|
||||
reset,
|
||||
explicit_header: true,
|
||||
mode: RadioMode::Sleep,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reset(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.reset.set_low().map_err(Reset)?;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
self.reset.set_high().map_err(Reset)?;
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
let version = self.read_register(Register::RegVersion.addr()).await?;
|
||||
if version == VERSION_CHECK {
|
||||
self.set_mode(RadioMode::Sleep).await?;
|
||||
self.write_register(Register::RegFifoTxBaseAddr.addr(), 0).await?;
|
||||
self.write_register(Register::RegFifoRxBaseAddr.addr(), 0).await?;
|
||||
let lna = self.read_register(Register::RegLna.addr()).await?;
|
||||
self.write_register(Register::RegLna.addr(), lna | 0x03).await?;
|
||||
self.write_register(Register::RegModemConfig3.addr(), 0x04).await?;
|
||||
self.set_tcxo(true).await?;
|
||||
self.set_mode(RadioMode::Stdby).await?;
|
||||
self.cs.set_high().map_err(CS)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::VersionMismatch(version))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_dio0_tx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111)
|
||||
.await?;
|
||||
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
|
||||
self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_dio0_rx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111)
|
||||
.await?;
|
||||
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
|
||||
self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn transmit_start(&mut self, buffer: &[u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
assert!(buffer.len() < 255);
|
||||
if self.transmitting().await? {
|
||||
//trace!("ALREADY TRANSMNITTING");
|
||||
Err(Transmitting)
|
||||
} else {
|
||||
self.set_mode(RadioMode::Stdby).await?;
|
||||
if self.explicit_header {
|
||||
self.set_explicit_header_mode().await?;
|
||||
} else {
|
||||
self.set_implicit_header_mode().await?;
|
||||
}
|
||||
|
||||
self.write_register(Register::RegIrqFlags.addr(), 0).await?;
|
||||
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
|
||||
self.write_register(Register::RegPayloadLength.addr(), 0).await?;
|
||||
for byte in buffer.iter() {
|
||||
self.write_register(Register::RegFifo.addr(), *byte).await?;
|
||||
}
|
||||
self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8)
|
||||
.await?;
|
||||
self.set_mode(RadioMode::Tx).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn packet_ready(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegIrqFlags.addr()).await?.get_bit(6))
|
||||
}
|
||||
|
||||
pub async fn irq_flags_mask(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegIrqFlagsMask.addr()).await? as u8)
|
||||
}
|
||||
|
||||
pub async fn irq_flags(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegIrqFlags.addr()).await? as u8)
|
||||
}
|
||||
|
||||
pub async fn read_packet_size(&mut self) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
|
||||
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
|
||||
Ok(size as usize)
|
||||
}
|
||||
|
||||
/// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a
|
||||
/// new packet ready to be read.
|
||||
pub async fn read_packet(&mut self, buffer: &mut [u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.clear_irq().await?;
|
||||
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
|
||||
assert!(size as usize <= buffer.len());
|
||||
let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr()).await?;
|
||||
self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr).await?;
|
||||
for i in 0..size {
|
||||
let byte = self.read_register(Register::RegFifo.addr()).await?;
|
||||
buffer[i as usize] = byte;
|
||||
}
|
||||
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if the radio is currently transmitting a packet.
|
||||
pub async fn transmitting(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
|
||||
if (self.read_register(Register::RegOpMode.addr()).await?) & RadioMode::Tx.addr() == RadioMode::Tx.addr() {
|
||||
Ok(true)
|
||||
} else {
|
||||
if (self.read_register(Register::RegIrqFlags.addr()).await? & IRQ::IrqTxDoneMask.addr()) == 1 {
|
||||
self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr())
|
||||
.await?;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the radio's IRQ registers.
|
||||
pub async fn clear_irq(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
let irq_flags = self.read_register(Register::RegIrqFlags.addr()).await?;
|
||||
self.write_register(Register::RegIrqFlags.addr(), 0xFF).await?;
|
||||
Ok(irq_flags)
|
||||
}
|
||||
|
||||
/// Sets the transmit power and pin. Levels can range from 0-14 when the output
|
||||
/// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB.
|
||||
/// Default value is `17`.
|
||||
pub async fn set_tx_power(
|
||||
&mut self,
|
||||
mut level: i32,
|
||||
output_pin: u8,
|
||||
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if PaConfig::PaOutputRfoPin.addr() == output_pin {
|
||||
// RFO
|
||||
if level < 0 {
|
||||
level = 0;
|
||||
} else if level > 14 {
|
||||
level = 14;
|
||||
}
|
||||
self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8)
|
||||
.await
|
||||
} else {
|
||||
// PA BOOST
|
||||
if level > 17 {
|
||||
if level > 20 {
|
||||
level = 20;
|
||||
}
|
||||
// subtract 3 from level, so 18 - 20 maps to 15 - 17
|
||||
level -= 3;
|
||||
|
||||
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
|
||||
self.write_register(Register::RegPaDac.addr(), 0x87).await?;
|
||||
self.set_ocp(140).await?;
|
||||
} else {
|
||||
if level < 2 {
|
||||
level = 2;
|
||||
}
|
||||
//Default value PA_HF/LF or +17dBm
|
||||
self.write_register(Register::RegPaDac.addr(), 0x84).await?;
|
||||
self.set_ocp(100).await?;
|
||||
}
|
||||
level -= 2;
|
||||
self.write_register(Register::RegPaConfig.addr(), PaConfig::PaBoost.addr() | level as u8)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_modem_stat(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegModemStat.addr()).await? as u8)
|
||||
}
|
||||
|
||||
/// Sets the over current protection on the radio(mA).
|
||||
pub async fn set_ocp(&mut self, ma: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let mut ocp_trim: u8 = 27;
|
||||
|
||||
if ma <= 120 {
|
||||
ocp_trim = (ma - 45) / 5;
|
||||
} else if ma <= 240 {
|
||||
ocp_trim = (ma + 30) / 10;
|
||||
}
|
||||
self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the state of the radio. Default mode after initiation is `Standby`.
|
||||
pub async fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if self.explicit_header {
|
||||
self.set_explicit_header_mode().await?;
|
||||
} else {
|
||||
self.set_implicit_header_mode().await?;
|
||||
}
|
||||
self.write_register(
|
||||
Register::RegOpMode.addr(),
|
||||
RadioMode::LongRangeMode.addr() | mode.addr(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.mode = mode;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reset_payload_length(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegPayloadLength.addr(), 0xFF).await
|
||||
}
|
||||
|
||||
/// Sets the frequency of the radio. Values are in megahertz.
|
||||
/// I.E. 915 MHz must be used for North America. Check regulation for your area.
|
||||
pub async fn set_frequency(&mut self, freq: u32) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
const FREQ_STEP: f64 = 61.03515625;
|
||||
// calculate register values
|
||||
let frf = (freq as f64 / FREQ_STEP) as u32;
|
||||
// write registers
|
||||
self.write_register(Register::RegFrfMsb.addr(), ((frf & 0x00FF_0000) >> 16) as u8)
|
||||
.await?;
|
||||
self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8)
|
||||
.await?;
|
||||
self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the radio to use an explicit header. Default state is `ON`.
|
||||
async fn set_explicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe)
|
||||
.await?;
|
||||
self.explicit_header = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the radio to use an implicit header. Default state is `OFF`.
|
||||
async fn set_implicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01)
|
||||
.await?;
|
||||
self.explicit_header = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the spreading factor of the radio. Supported values are between 6 and 12.
|
||||
/// If a spreading factor of 6 is set, implicit header mode must be used to transmit
|
||||
/// and receive packets. Default value is `7`.
|
||||
pub async fn set_spreading_factor(&mut self, mut sf: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if sf < 6 {
|
||||
sf = 6;
|
||||
} else if sf > 12 {
|
||||
sf = 12;
|
||||
}
|
||||
|
||||
if sf == 6 {
|
||||
self.write_register(Register::RegDetectionOptimize.addr(), 0xc5).await?;
|
||||
self.write_register(Register::RegDetectionThreshold.addr(), 0x0c)
|
||||
.await?;
|
||||
} else {
|
||||
self.write_register(Register::RegDetectionOptimize.addr(), 0xc3).await?;
|
||||
self.write_register(Register::RegDetectionThreshold.addr(), 0x0a)
|
||||
.await?;
|
||||
}
|
||||
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
|
||||
self.write_register(
|
||||
Register::RegModemConfig2.addr(),
|
||||
(modem_config_2 & 0x0f) | ((sf << 4) & 0xf0),
|
||||
)
|
||||
.await?;
|
||||
self.set_ldo_flag().await?;
|
||||
|
||||
self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_tcxo(&mut self, external: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if external {
|
||||
self.write_register(Register::RegTcxo.addr(), 0x10).await
|
||||
} else {
|
||||
self.write_register(Register::RegTcxo.addr(), 0x00).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`,
|
||||
/// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz`
|
||||
/// Default value is `125000 Hz`
|
||||
pub async fn set_signal_bandwidth(&mut self, sbw: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let bw: i64 = match sbw {
|
||||
7_800 => 0,
|
||||
10_400 => 1,
|
||||
15_600 => 2,
|
||||
20_800 => 3,
|
||||
31_250 => 4,
|
||||
41_700 => 5,
|
||||
62_500 => 6,
|
||||
125_000 => 7,
|
||||
250_000 => 8,
|
||||
_ => 9,
|
||||
};
|
||||
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(
|
||||
Register::RegModemConfig1.addr(),
|
||||
(modem_config_1 & 0x0f) | ((bw << 4) as u8),
|
||||
)
|
||||
.await?;
|
||||
self.set_ldo_flag().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the coding rate of the radio with the numerator fixed at 4. Supported values
|
||||
/// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`.
|
||||
/// Default value is `5`.
|
||||
pub async fn set_coding_rate_4(&mut self, mut denominator: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if denominator < 5 {
|
||||
denominator = 5;
|
||||
} else if denominator > 8 {
|
||||
denominator = 8;
|
||||
}
|
||||
let cr = denominator - 4;
|
||||
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
|
||||
self.write_register(Register::RegModemConfig1.addr(), (modem_config_1 & 0xf1) | (cr << 1))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the preamble length of the radio. Values are between 6 and 65535.
|
||||
/// Default value is `8`.
|
||||
pub async fn set_preamble_length(&mut self, length: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8)
|
||||
.await?;
|
||||
self.write_register(Register::RegPreambleLsb.addr(), length as u8).await
|
||||
}
|
||||
|
||||
/// Enables are disables the radio's CRC check. Default value is `false`.
|
||||
pub async fn set_crc(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
|
||||
if value {
|
||||
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04)
|
||||
.await
|
||||
} else {
|
||||
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Inverts the radio's IQ signals. Default value is `false`.
|
||||
pub async fn set_invert_iq(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
if value {
|
||||
self.write_register(Register::RegInvertiq.addr(), 0x66).await?;
|
||||
self.write_register(Register::RegInvertiq2.addr(), 0x19).await
|
||||
} else {
|
||||
self.write_register(Register::RegInvertiq.addr(), 0x27).await?;
|
||||
self.write_register(Register::RegInvertiq2.addr(), 0x1d).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the spreading factor of the radio.
|
||||
pub async fn get_spreading_factor(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(self.read_register(Register::RegModemConfig2.addr()).await? >> 4)
|
||||
}
|
||||
|
||||
/// Returns the signal bandwidth of the radio.
|
||||
pub async fn get_signal_bandwidth(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
|
||||
let bw = self.read_register(Register::RegModemConfig1.addr()).await? >> 4;
|
||||
let bw = match bw {
|
||||
0 => 7_800,
|
||||
1 => 10_400,
|
||||
2 => 15_600,
|
||||
3 => 20_800,
|
||||
4 => 31_250,
|
||||
5 => 41_700,
|
||||
6 => 62_500,
|
||||
7 => 125_000,
|
||||
8 => 250_000,
|
||||
9 => 500_000,
|
||||
_ => -1,
|
||||
};
|
||||
Ok(bw)
|
||||
}
|
||||
|
||||
/// Returns the RSSI of the last received packet.
|
||||
pub async fn get_packet_rssi(&mut self) -> Result<i32, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr()).await?) - 157)
|
||||
}
|
||||
|
||||
/// Returns the signal to noise radio of the the last received packet.
|
||||
pub async fn get_packet_snr(&mut self) -> Result<f64, Error<E, CS::Error, RESET::Error>> {
|
||||
Ok(f64::from(self.read_register(Register::RegPktSnrValue.addr()).await?))
|
||||
}
|
||||
|
||||
/// Returns the frequency error of the last received packet in Hz.
|
||||
pub async fn get_packet_frequency_error(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
|
||||
let mut freq_error: i32;
|
||||
freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr()).await? & 0x7);
|
||||
freq_error <<= 8i64;
|
||||
freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr()).await?);
|
||||
freq_error <<= 8i64;
|
||||
freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr()).await?);
|
||||
|
||||
let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
|
||||
let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal))
|
||||
* (self.get_signal_bandwidth().await? as f64 / 500_000.0f64); // p. 37
|
||||
Ok(f_error as i64)
|
||||
}
|
||||
|
||||
async fn set_ldo_flag(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let sw = self.get_signal_bandwidth().await?;
|
||||
// Section 4.1.1.5
|
||||
let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor().await?));
|
||||
|
||||
// Section 4.1.1.6
|
||||
let ldo_on = symbol_duration > 16;
|
||||
|
||||
let mut config_3 = self.read_register(Register::RegModemConfig3.addr()).await?;
|
||||
config_3.set_bit(3, ldo_on);
|
||||
//config_3.set_bit(2, true);
|
||||
self.write_register(Register::RegModemConfig3.addr(), config_3).await
|
||||
}
|
||||
|
||||
async fn read_register(&mut self, reg: u8) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||
let mut buffer = [reg & 0x7f, 0];
|
||||
self.cs.set_low().map_err(CS)?;
|
||||
|
||||
let _ = self.spi.transfer(&mut buffer, &[reg & 0x7f, 0]).await.map_err(SPI)?;
|
||||
|
||||
self.cs.set_high().map_err(CS)?;
|
||||
Ok(buffer[1])
|
||||
}
|
||||
|
||||
async fn write_register(&mut self, reg: u8, byte: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.cs.set_low().map_err(CS)?;
|
||||
let buffer = [reg | 0x80, byte];
|
||||
self.spi.write(&buffer).await.map_err(SPI)?;
|
||||
self.cs.set_high().map_err(CS)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn put_in_fsk_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
// Put in FSK mode
|
||||
let mut op_mode = 0;
|
||||
op_mode
|
||||
.set_bit(7, false) // FSK mode
|
||||
.set_bits(5..6, 0x00) // FSK modulation
|
||||
.set_bit(3, false) //Low freq registers
|
||||
.set_bits(0..2, 0b011); // Mode
|
||||
|
||||
self.write_register(Register::RegOpMode as u8, op_mode).await
|
||||
}
|
||||
|
||||
pub async fn set_fsk_pa_ramp(
|
||||
&mut self,
|
||||
modulation_shaping: FskDataModulationShaping,
|
||||
ramp: FskRampUpRamDown,
|
||||
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
let mut pa_ramp = 0;
|
||||
pa_ramp
|
||||
.set_bits(5..6, modulation_shaping as u8)
|
||||
.set_bits(0..3, ramp as u8);
|
||||
|
||||
self.write_register(Register::RegPaRamp as u8, pa_ramp).await
|
||||
}
|
||||
|
||||
pub async fn set_lora_pa_ramp(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegPaRamp as u8, 0b1000).await
|
||||
}
|
||||
|
||||
pub async fn set_lora_sync_word(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||
self.write_register(Register::RegSyncWord as u8, 0x34).await
|
||||
}
|
||||
}
|
||||
/// Modes of the radio and their corresponding register values.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RadioMode {
|
||||
LongRangeMode = 0x80,
|
||||
Sleep = 0x00,
|
||||
Stdby = 0x01,
|
||||
Tx = 0x03,
|
||||
RxContinuous = 0x05,
|
||||
RxSingle = 0x06,
|
||||
}
|
||||
|
||||
impl RadioMode {
|
||||
/// Returns the address of the mode.
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
|
||||
// license
|
||||
//
|
||||
// Modifications made to make the driver work with the rust-lorawan link layer.
|
||||
#![allow(dead_code, clippy::enum_variant_names)]
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Register {
|
||||
RegFifo = 0x00,
|
||||
RegOpMode = 0x01,
|
||||
RegFrfMsb = 0x06,
|
||||
RegFrfMid = 0x07,
|
||||
RegFrfLsb = 0x08,
|
||||
RegPaConfig = 0x09,
|
||||
RegPaRamp = 0x0a,
|
||||
RegOcp = 0x0b,
|
||||
RegLna = 0x0c,
|
||||
RegFifoAddrPtr = 0x0d,
|
||||
RegFifoTxBaseAddr = 0x0e,
|
||||
RegFifoRxBaseAddr = 0x0f,
|
||||
RegFifoRxCurrentAddr = 0x10,
|
||||
RegIrqFlagsMask = 0x11,
|
||||
RegIrqFlags = 0x12,
|
||||
RegRxNbBytes = 0x13,
|
||||
RegPktSnrValue = 0x19,
|
||||
RegModemStat = 0x18,
|
||||
RegPktRssiValue = 0x1a,
|
||||
RegModemConfig1 = 0x1d,
|
||||
RegModemConfig2 = 0x1e,
|
||||
RegSymbTimeoutLsb = 0x1f,
|
||||
RegPreambleMsb = 0x20,
|
||||
RegPreambleLsb = 0x21,
|
||||
RegPayloadLength = 0x22,
|
||||
RegMaxPayloadLength = 0x23,
|
||||
RegModemConfig3 = 0x26,
|
||||
RegFreqErrorMsb = 0x28,
|
||||
RegFreqErrorMid = 0x29,
|
||||
RegFreqErrorLsb = 0x2a,
|
||||
RegRssiWideband = 0x2c,
|
||||
RegDetectionOptimize = 0x31,
|
||||
RegInvertiq = 0x33,
|
||||
RegDetectionThreshold = 0x37,
|
||||
RegSyncWord = 0x39,
|
||||
RegInvertiq2 = 0x3b,
|
||||
RegDioMapping1 = 0x40,
|
||||
RegVersion = 0x42,
|
||||
RegTcxo = 0x4b,
|
||||
RegPaDac = 0x4d,
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PaConfig {
|
||||
PaBoost = 0x80,
|
||||
PaOutputRfoPin = 0,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IRQ {
|
||||
IrqTxDoneMask = 0x08,
|
||||
IrqPayloadCrcErrorMask = 0x20,
|
||||
IrqRxDoneMask = 0x40,
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl PaConfig {
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl IRQ {
|
||||
pub fn addr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FskDataModulationShaping {
|
||||
None = 1,
|
||||
GaussianBt1d0 = 2,
|
||||
GaussianBt0d5 = 10,
|
||||
GaussianBt0d3 = 11,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FskRampUpRamDown {
|
||||
_3d4ms = 0b000,
|
||||
_2ms = 0b0001,
|
||||
_1ms = 0b0010,
|
||||
_500us = 0b0011,
|
||||
_250us = 0b0100,
|
||||
_125us = 0b0101,
|
||||
_100us = 0b0110,
|
||||
_62us = 0b0111,
|
||||
_50us = 0b1000,
|
||||
_40us = 0b1001,
|
||||
_31us = 0b1010,
|
||||
_25us = 0b1011,
|
||||
_20us = 0b1100,
|
||||
_15us = 0b1101,
|
||||
_12us = 0b1110,
|
||||
_10us = 0b1111,
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "embassy-macros"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "macros for creating the entry point and tasks for embassy-executor"
|
||||
@ -12,9 +12,9 @@ categories = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0.76", features = ["full", "extra-traits"] }
|
||||
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
|
||||
quote = "1.0.9"
|
||||
darling = "0.13.0"
|
||||
darling = "0.20.1"
|
||||
proc-macro2 = "1.0.29"
|
||||
|
||||
[lib]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user