aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2021-02-03 05:09:37 +0100
committerDario Nieuwenhuis <[email protected]>2021-02-03 05:09:37 +0100
commitcb5931d583d283dda3a1b5ed2014c086bb8f98ae (patch)
tree19a669238e0d562bf74616fe38485388ec40b02a
:rainbow:
-rw-r--r--.gitignore3
-rw-r--r--Cargo.toml48
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT25
-rw-r--r--README.md34
-rw-r--r--embassy-net-examples/Cargo.toml17
-rw-r--r--embassy-net-examples/src/main.rs79
-rw-r--r--embassy-net-examples/src/tuntap.rs200
-rw-r--r--embassy-net/Cargo.toml46
-rw-r--r--embassy-net/src/config/dhcp.rs80
-rw-r--r--embassy-net/src/config/mod.rs34
-rw-r--r--embassy-net/src/config/statik.rs26
-rw-r--r--embassy-net/src/device.rs103
-rw-r--r--embassy-net/src/fmt.rs118
-rw-r--r--embassy-net/src/lib.rs31
-rw-r--r--embassy-net/src/packet_pool.rs88
-rw-r--r--embassy-net/src/pool.rs245
-rw-r--r--embassy-net/src/stack.rs212
-rw-r--r--embassy-net/src/tcp_socket.rs178
-rwxr-xr-xtest-build.sh25
20 files changed, 1793 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..25d18d5aa
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
1/target
2Cargo.lock
3third_party
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 000000000..1b3012829
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,48 @@
1
2[workspace]
3members = [
4 "embassy-net",
5 "embassy-net-examples",
6]
7
8exclude = [
9 "third_party"
10]
11
12[profile.dev]
13codegen-units = 1
14debug = 2
15debug-assertions = true
16incremental = false
17opt-level = 3
18overflow-checks = true
19
20[profile.release]
21codegen-units = 1
22debug = 2
23debug-assertions = false
24incremental = false
25lto = "fat"
26opt-level = 's'
27overflow-checks = false
28
29# do not optimize proc-macro crates = faster builds from scratch
30[profile.dev.build-override]
31codegen-units = 8
32debug = false
33debug-assertions = false
34opt-level = 0
35overflow-checks = false
36
37[profile.release.build-override]
38codegen-units = 8
39debug = false
40debug-assertions = false
41opt-level = 0
42overflow-checks = false
43
44[patch.crates-io]
45embassy = { git = "https://github.com/akiles/embassy" }
46embassy-std = { git = "https://github.com/akiles/embassy" }
47embassy-macros = { git = "https://github.com/akiles/embassy" }
48smoltcp = { git = "https://github.com/akiles/smoltcp" } \ No newline at end of file
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
1 Apache License
2 Version 2.0, January 2004
3 http://www.apache.org/licenses/
4
5TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
71. Definitions.
8
9 "License" shall mean the terms and conditions for use, reproduction,
10 and distribution as defined by Sections 1 through 9 of this document.
11
12 "Licensor" shall mean the copyright owner or entity authorized by
13 the copyright owner that is granting the License.
14
15 "Legal Entity" shall mean the union of the acting entity and all
16 other entities that control, are controlled by, or are under common
17 control with that entity. For the purposes of this definition,
18 "control" means (i) the power, direct or indirect, to cause the
19 direction or management of such entity, whether by contract or
20 otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 outstanding shares, or (iii) beneficial ownership of such entity.
22
23 "You" (or "Your") shall mean an individual or Legal Entity
24 exercising permissions granted by this License.
25
26 "Source" form shall mean the preferred form for making modifications,
27 including but not limited to software source code, documentation
28 source, and configuration files.
29
30 "Object" form shall mean any form resulting from mechanical
31 transformation or translation of a Source form, including but
32 not limited to compiled object code, generated documentation,
33 and conversions to other media types.
34
35 "Work" shall mean the work of authorship, whether in Source or
36 Object form, made available under the License, as indicated by a
37 copyright notice that is included in or attached to the work
38 (an example is provided in the Appendix below).
39
40 "Derivative Works" shall mean any work, whether in Source or Object
41 form, that is based on (or derived from) the Work and for which the
42 editorial revisions, annotations, elaborations, or other modifications
43 represent, as a whole, an original work of authorship. For the purposes
44 of this License, Derivative Works shall not include works that remain
45 separable from, or merely link (or bind by name) to the interfaces of,
46 the Work and Derivative Works thereof.
47
48 "Contribution" shall mean any work of authorship, including
49 the original version of the Work and any modifications or additions
50 to that Work or Derivative Works thereof, that is intentionally
51 submitted to Licensor for inclusion in the Work by the copyright owner
52 or by an individual or Legal Entity authorized to submit on behalf of
53 the copyright owner. For the purposes of this definition, "submitted"
54 means any form of electronic, verbal, or written communication sent
55 to the Licensor or its representatives, including but not limited to
56 communication on electronic mailing lists, source code control systems,
57 and issue tracking systems that are managed by, or on behalf of, the
58 Licensor for the purpose of discussing and improving the Work, but
59 excluding communication that is conspicuously marked or otherwise
60 designated in writing by the copyright owner as "Not a Contribution."
61
62 "Contributor" shall mean Licensor and any individual or Legal Entity
63 on behalf of whom a Contribution has been received by Licensor and
64 subsequently incorporated within the Work.
65
662. Grant of Copyright License. Subject to the terms and conditions of
67 this License, each Contributor hereby grants to You a perpetual,
68 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 copyright license to reproduce, prepare Derivative Works of,
70 publicly display, publicly perform, sublicense, and distribute the
71 Work and such Derivative Works in Source or Object form.
72
733. Grant of Patent License. Subject to the terms and conditions of
74 this License, each Contributor hereby grants to You a perpetual,
75 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 (except as stated in this section) patent license to make, have made,
77 use, offer to sell, sell, import, and otherwise transfer the Work,
78 where such license applies only to those patent claims licensable
79 by such Contributor that are necessarily infringed by their
80 Contribution(s) alone or by combination of their Contribution(s)
81 with the Work to which such Contribution(s) was submitted. If You
82 institute patent litigation against any entity (including a
83 cross-claim or counterclaim in a lawsuit) alleging that the Work
84 or a Contribution incorporated within the Work constitutes direct
85 or contributory patent infringement, then any patent licenses
86 granted to You under this License for that Work shall terminate
87 as of the date such litigation is filed.
88
894. Redistribution. You may reproduce and distribute copies of the
90 Work or Derivative Works thereof in any medium, with or without
91 modifications, and in Source or Object form, provided that You
92 meet the following conditions:
93
94 (a) You must give any other recipients of the Work or
95 Derivative Works a copy of this License; and
96
97 (b) You must cause any modified files to carry prominent notices
98 stating that You changed the files; and
99
100 (c) You must retain, in the Source form of any Derivative Works
101 that You distribute, all copyright, patent, trademark, and
102 attribution notices from the Source form of the Work,
103 excluding those notices that do not pertain to any part of
104 the Derivative Works; and
105
106 (d) If the Work includes a "NOTICE" text file as part of its
107 distribution, then any Derivative Works that You distribute must
108 include a readable copy of the attribution notices contained
109 within such NOTICE file, excluding those notices that do not
110 pertain to any part of the Derivative Works, in at least one
111 of the following places: within a NOTICE text file distributed
112 as part of the Derivative Works; within the Source form or
113 documentation, if provided along with the Derivative Works; or,
114 within a display generated by the Derivative Works, if and
115 wherever such third-party notices normally appear. The contents
116 of the NOTICE file are for informational purposes only and
117 do not modify the License. You may add Your own attribution
118 notices within Derivative Works that You distribute, alongside
119 or as an addendum to the NOTICE text from the Work, provided
120 that such additional attribution notices cannot be construed
121 as modifying the License.
122
123 You may add Your own copyright statement to Your modifications and
124 may provide additional or different license terms and conditions
125 for use, reproduction, or distribution of Your modifications, or
126 for any such Derivative Works as a whole, provided Your use,
127 reproduction, and distribution of the Work otherwise complies with
128 the conditions stated in this License.
129
1305. Submission of Contributions. Unless You explicitly state otherwise,
131 any Contribution intentionally submitted for inclusion in the Work
132 by You to the Licensor shall be under the terms and conditions of
133 this License, without any additional terms or conditions.
134 Notwithstanding the above, nothing herein shall supersede or modify
135 the terms of any separate license agreement you may have executed
136 with Licensor regarding such Contributions.
137
1386. Trademarks. This License does not grant permission to use the trade
139 names, trademarks, service marks, or product names of the Licensor,
140 except as required for reasonable and customary use in describing the
141 origin of the Work and reproducing the content of the NOTICE file.
142
1437. Disclaimer of Warranty. Unless required by applicable law or
144 agreed to in writing, Licensor provides the Work (and each
145 Contributor provides its Contributions) on an "AS IS" BASIS,
146 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 implied, including, without limitation, any warranties or conditions
148 of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 PARTICULAR PURPOSE. You are solely responsible for determining the
150 appropriateness of using or redistributing the Work and assume any
151 risks associated with Your exercise of permissions under this License.
152
1538. Limitation of Liability. In no event and under no legal theory,
154 whether in tort (including negligence), contract, or otherwise,
155 unless required by applicable law (such as deliberate and grossly
156 negligent acts) or agreed to in writing, shall any Contributor be
157 liable to You for damages, including any direct, indirect, special,
158 incidental, or consequential damages of any character arising as a
159 result of this License or out of the use or inability to use the
160 Work (including but not limited to damages for loss of goodwill,
161 work stoppage, computer failure or malfunction, or any and all
162 other commercial damages or losses), even if such Contributor
163 has been advised of the possibility of such damages.
164
1659. Accepting Warranty or Additional Liability. While redistributing
166 the Work or Derivative Works thereof, You may choose to offer,
167 and charge a fee for, acceptance of support, warranty, indemnity,
168 or other liability obligations and/or rights consistent with this
169 License. However, in accepting such obligations, You may act only
170 on Your own behalf and on Your sole responsibility, not on behalf
171 of any other Contributor, and only if You agree to indemnify,
172 defend, and hold each Contributor harmless for any liability
173 incurred by, or claims asserted against, such Contributor by reason
174 of your accepting any such warranty or additional liability.
175
176END OF TERMS AND CONDITIONS
177
178APPENDIX: How to apply the Apache License to your work.
179
180 To apply the Apache License to your work, attach the following
181 boilerplate notice, with the fields enclosed by brackets "[]"
182 replaced with your own identifying information. (Don't include
183 the brackets!) The text should be enclosed in the appropriate
184 comment syntax for the file format. We also recommend that a
185 file or class name and description of purpose be included on the
186 same "printed page" as the copyright notice for easier
187 identification within third-party archives.
188
189Copyright [yyyy] [name of copyright owner]
190
191Licensed under the Apache License, Version 2.0 (the "License");
192you may not use this file except in compliance with the License.
193You may obtain a copy of the License at
194
195 http://www.apache.org/licenses/LICENSE-2.0
196
197Unless required by applicable law or agreed to in writing, software
198distributed under the License is distributed on an "AS IS" BASIS,
199WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200See the License for the specific language governing permissions and
201limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 000000000..dacc57b2b
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,25 @@
1Copyright (c) 2020 Dario Nieuwenhuis
2
3Permission is hereby granted, free of charge, to any
4person obtaining a copy of this software and associated
5documentation files (the "Software"), to deal in the
6Software without restriction, including without
7limitation the rights to use, copy, modify, merge,
8publish, distribute, sublicense, and/or sell copies of
9the Software, and to permit persons to whom the Software
10is furnished to do so, subject to the following
11conditions:
12
13The above copyright notice and this permission notice
14shall be included in all copies or substantial portions
15of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..64f656709
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
1# embassy-net
2
3embassy-net contains an async network API based on smoltcp and embassy, designed
4for embedded systems.
5
6## Running the example
7
8First, create the tap0 interface. You only need to do this once.
9
10```sh
11sudo ip tuntap add name tap0 mode tap user $USER
12sudo ip link set tap0 up
13sudo ip addr add 192.168.69.100/24 dev tap0
14sudo ip -6 addr add fe80::100/64 dev tap0
15sudo ip -6 addr add fdaa::100/64 dev tap0
16sudo ip -6 route add fe80::/64 dev tap0
17sudo ip -6 route add fdaa::/64 dev tap0
18```
19
20Then, run it
21
22```sh
23cargo run --bin embassy-net-examples
24```
25
26## License
27
28This work is licensed under either of
29
30- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
31 http://www.apache.org/licenses/LICENSE-2.0)
32- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
33
34at your option.
diff --git a/embassy-net-examples/Cargo.toml b/embassy-net-examples/Cargo.toml
new file mode 100644
index 000000000..944e91914
--- /dev/null
+++ b/embassy-net-examples/Cargo.toml
@@ -0,0 +1,17 @@
1[package]
2name = "embassy-net-examples"
3version = "0.1.0"
4authors = ["Dario Nieuwenhuis <[email protected]>"]
5edition = "2018"
6
7[dependencies]
8heapless = { version = "0.5.6", default-features = false }
9embassy = { version = "0.1.0", features=["std", "log"] }
10embassy-std = { version = "0.1.0" }
11embassy-net = { version = "0.1.0", path = "../embassy-net", features=["std", "log"] }
12env_logger = "0.8.2"
13log = "0.4.11"
14futures = "0.3.8"
15libc = "0.2.81"
16async-io = "1.3.1"
17smoltcp = { version = "0.6.0", default-features = false }
diff --git a/embassy-net-examples/src/main.rs b/embassy-net-examples/src/main.rs
new file mode 100644
index 000000000..bc413f1a2
--- /dev/null
+++ b/embassy-net-examples/src/main.rs
@@ -0,0 +1,79 @@
1#![feature(type_alias_impl_trait)]
2
3use embassy::executor::{Spawner, task};
4use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
5use embassy::time::{Duration, Timer};
6use embassy::util::Forever;
7use embassy_net::*;
8use embassy_std::Executor;
9use heapless::Vec;
10use log::*;
11
12mod tuntap;
13
14use crate::tuntap::TunTapDevice;
15
16static DEVICE: Forever<TunTapDevice> = Forever::new();
17static CONFIG: Forever<StaticConfigurator> = Forever::new();
18
19#[task]
20async fn net_task() {
21 embassy_net::run().await
22}
23
24#[task]
25async fn main_task(spawner: Spawner) {
26 // Init network device
27 let device = TunTapDevice::new("tap0").unwrap();
28
29 // Static IP configuration
30 let config = StaticConfigurator::new(UpConfig {
31 address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 1), 24),
32 dns_servers: Vec::new(),
33 gateway: Ipv4Address::new(192, 168, 69, 100),
34 });
35
36 // Init network stack
37 embassy_net::init(DEVICE.put(device), CONFIG.put(config));
38
39 // Launch network task
40 spawner.spawn(net_task()).unwrap();
41
42 // Then we can use it!
43 let mut rx_buffer = [0; 4096];
44 let mut tx_buffer = [0; 4096];
45 let mut socket = TcpSocket::new(&mut rx_buffer, &mut tx_buffer);
46
47 socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10)));
48
49 let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 8000);
50 info!("connecting to {:?}...", remote_endpoint);
51 let r = socket.connect(remote_endpoint).await;
52 if let Err(e) = r {
53 warn!("connect error: {:?}", e);
54 return;
55 }
56 info!("connected!");
57 loop {
58 let r = socket.write_all(b"Hello!\n").await;
59 if let Err(e) = r {
60 warn!("write error: {:?}", e);
61 return;
62 }
63 }
64}
65
66static EXECUTOR: Forever<Executor> = Forever::new();
67
68fn main() {
69 env_logger::builder()
70 .filter_level(log::LevelFilter::Debug)
71 .filter_module("async_io", log::LevelFilter::Info)
72 .format_timestamp_nanos()
73 .init();
74
75 let executor = EXECUTOR.put(Executor::new());
76 executor.run(|spawner| {
77 spawner.spawn(main_task(spawner)).unwrap();
78 });
79}
diff --git a/embassy-net-examples/src/tuntap.rs b/embassy-net-examples/src/tuntap.rs
new file mode 100644
index 000000000..5c138c069
--- /dev/null
+++ b/embassy-net-examples/src/tuntap.rs
@@ -0,0 +1,200 @@
1use async_io::Async;
2use embassy::util::WakerRegistration;
3use libc;
4use smoltcp::wire::EthernetFrame;
5use std::io;
6use std::io::{Read, Write};
7use std::os::unix::io::{AsRawFd, RawFd};
8use log::*;
9
10pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
11pub const SIOCGIFINDEX: libc::c_ulong = 0x8933;
12pub const ETH_P_ALL: libc::c_short = 0x0003;
13pub const TUNSETIFF: libc::c_ulong = 0x400454CA;
14pub const IFF_TUN: libc::c_int = 0x0001;
15pub const IFF_TAP: libc::c_int = 0x0002;
16pub const IFF_NO_PI: libc::c_int = 0x1000;
17
18#[repr(C)]
19#[derive(Debug)]
20struct ifreq {
21 ifr_name: [libc::c_char; libc::IF_NAMESIZE],
22 ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
23}
24
25fn ifreq_for(name: &str) -> ifreq {
26 let mut ifreq = ifreq {
27 ifr_name: [0; libc::IF_NAMESIZE],
28 ifr_data: 0,
29 };
30 for (i, byte) in name.as_bytes().iter().enumerate() {
31 ifreq.ifr_name[i] = *byte as libc::c_char
32 }
33 ifreq
34}
35
36fn ifreq_ioctl(
37 lower: libc::c_int,
38 ifreq: &mut ifreq,
39 cmd: libc::c_ulong,
40) -> io::Result<libc::c_int> {
41 unsafe {
42 let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq);
43 if res == -1 {
44 return Err(io::Error::last_os_error());
45 }
46 }
47
48 Ok(ifreq.ifr_data)
49}
50
51#[derive(Debug)]
52pub struct TunTap {
53 fd: libc::c_int,
54 ifreq: ifreq,
55 mtu: usize,
56}
57
58impl AsRawFd for TunTap {
59 fn as_raw_fd(&self) -> RawFd {
60 self.fd
61 }
62}
63
64impl TunTap {
65 pub fn new(name: &str) -> io::Result<TunTap> {
66 unsafe {
67 let fd = libc::open(
68 "/dev/net/tun\0".as_ptr() as *const libc::c_char,
69 libc::O_RDWR | libc::O_NONBLOCK,
70 );
71 if fd == -1 {
72 return Err(io::Error::last_os_error());
73 }
74
75 let mut ifreq = ifreq_for(name);
76 ifreq.ifr_data = IFF_TAP | IFF_NO_PI;
77 ifreq_ioctl(fd, &mut ifreq, TUNSETIFF)?;
78
79 let socket = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
80 if socket == -1 {
81 return Err(io::Error::last_os_error());
82 }
83
84 let ip_mtu = ifreq_ioctl(socket, &mut ifreq, SIOCGIFMTU);
85 libc::close(socket);
86 let ip_mtu = ip_mtu? as usize;
87
88 // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
89 // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
90 let mtu = ip_mtu + EthernetFrame::<&[u8]>::header_len();
91
92 Ok(TunTap { fd, mtu, ifreq })
93 }
94 }
95}
96
97impl Drop for TunTap {
98 fn drop(&mut self) {
99 unsafe {
100 libc::close(self.fd);
101 }
102 }
103}
104
105impl io::Read for TunTap {
106 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
107 let len = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
108 if len == -1 {
109 Err(io::Error::last_os_error())
110 } else {
111 Ok(len as usize)
112 }
113 }
114}
115
116impl io::Write for TunTap {
117 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
118 let len = unsafe { libc::write(self.fd, buf.as_ptr() as *mut libc::c_void, buf.len()) };
119 if len == -1 {
120 Err(io::Error::last_os_error())
121 } else {
122 Ok(len as usize)
123 }
124 }
125
126 fn flush(&mut self) -> io::Result<()> {
127 Ok(())
128 }
129}
130
131pub struct TunTapDevice {
132 device: Async<TunTap>,
133 waker: WakerRegistration,
134}
135
136impl TunTapDevice {
137 pub fn new(name: &str) -> io::Result<TunTapDevice> {
138 Ok(Self {
139 device: Async::new(TunTap::new(name)?)?,
140 waker: WakerRegistration::new(),
141 })
142 }
143}
144
145use embassy_net::{LinkState, DeviceCapabilities, Packet, PacketBox, PacketBuf};
146use core::task::Waker;
147
148impl crate::Device for TunTapDevice {
149 fn is_transmit_ready(&mut self) -> bool {
150 true
151 }
152
153 fn transmit(&mut self, pkt: PacketBuf) {
154 // todo handle WouldBlock
155 match self.device.get_mut().write(&pkt) {
156 Ok(_) => {}
157 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
158 info!("transmit WouldBlock");
159 }
160 Err(e) => panic!("transmit error: {:?}", e),
161 }
162 }
163
164 fn receive(&mut self) -> Option<PacketBuf> {
165 let mut pkt = PacketBox::new(Packet::new()).unwrap();
166 loop {
167 match self.device.get_mut().read(&mut pkt[..]) {
168 Ok(n) => {
169 return Some(pkt.slice(0..n));
170 }
171 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
172 let ready = if let Some(mut cx) = self.waker.context() {
173 let ready = self.device.poll_readable(&mut cx).is_ready();
174 ready
175 } else {
176 false
177 };
178 if !ready {
179 return None;
180 }
181 }
182 Err(e) => panic!("read error: {:?}", e),
183 }
184 }
185 }
186
187 fn register_waker(&mut self, waker: &Waker) {
188 self.waker.register(waker)
189 }
190
191 fn capabilities(&mut self) -> DeviceCapabilities {
192 let mut caps = DeviceCapabilities::default();
193 caps.max_transmission_unit = self.device.get_ref().mtu;
194 caps
195 }
196
197 fn link_state(&mut self) -> LinkState {
198 LinkState::Up
199 }
200}
diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml
new file mode 100644
index 000000000..aec6b7965
--- /dev/null
+++ b/embassy-net/Cargo.toml
@@ -0,0 +1,46 @@
1[package]
2name = "embassy-net"
3version = "0.1.0"
4authors = ["Dario Nieuwenhuis <[email protected]>"]
5edition = "2018"
6
7[features]
8std = []
9defmt-trace = []
10defmt-debug = []
11defmt-info = []
12defmt-warn = []
13defmt-error = []
14
15[dependencies]
16
17defmt = { version = "0.1.3", optional = true }
18log = { version = "0.4.11", optional = true }
19
20embassy = { version = "0.1.0" }
21
22managed = { version = "0.8.0", default-features = false, features = [ "map" ]}
23heapless = { version = "0.5.6", default-features = false }
24as-slice = { version = "0.1.4" }
25generic-array = { version = "0.14.4", default-features = false }
26stable_deref_trait = { version = "1.2.0", default-features = false }
27futures = { version = "0.3.5", default-features = false, features = [ "async-await" ]}
28
29[dependencies.smoltcp]
30version = "0.6.0"
31#git = "https://github.com/akiles/smoltcp"
32#rev = "00952e2c5cdf5667a1dfb6142258055f58d3851c"
33default-features = false
34features = [
35 "medium-ethernet",
36 "medium-ip",
37 "proto-ipv4",
38 "proto-dhcpv4",
39 #"proto-igmp",
40 #"proto-ipv6",
41 #"socket-raw",
42 #"socket-icmp",
43 #"socket-udp",
44 "socket-tcp",
45 "async",
46]
diff --git a/embassy-net/src/config/dhcp.rs b/embassy-net/src/config/dhcp.rs
new file mode 100644
index 000000000..f5d598bdf
--- /dev/null
+++ b/embassy-net/src/config/dhcp.rs
@@ -0,0 +1,80 @@
1use embassy::util::Forever;
2use heapless::consts::*;
3use heapless::Vec;
4use smoltcp::dhcp::Dhcpv4Client;
5use smoltcp::socket::{RawPacketMetadata, RawSocketBuffer};
6use smoltcp::time::Instant;
7use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
8
9use super::*;
10use crate::{device::LinkState, fmt::*};
11use crate::{Interface, SocketSet};
12
13pub struct DhcpResources {
14 rx_buffer: [u8; 900],
15 tx_buffer: [u8; 600],
16 rx_meta: [RawPacketMetadata; 1],
17 tx_meta: [RawPacketMetadata; 1],
18}
19
20pub struct DhcpConfigurator {
21 client: Option<Dhcpv4Client>,
22}
23
24impl DhcpConfigurator {
25 pub fn new() -> Self {
26 Self { client: None }
27 }
28}
29
30static DHCP_RESOURCES: Forever<DhcpResources> = Forever::new();
31
32impl Configurator for DhcpConfigurator {
33 fn poll(
34 &mut self,
35 iface: &mut Interface,
36 sockets: &mut SocketSet,
37 timestamp: Instant,
38 ) -> Option<Config> {
39 if self.client.is_none() {
40 let res = DHCP_RESOURCES.put(DhcpResources {
41 rx_buffer: [0; 900],
42 tx_buffer: [0; 600],
43 rx_meta: [RawPacketMetadata::EMPTY; 1],
44 tx_meta: [RawPacketMetadata::EMPTY; 1],
45 });
46 let rx_buffer = RawSocketBuffer::new(&mut res.rx_meta[..], &mut res.rx_buffer[..]);
47 let tx_buffer = RawSocketBuffer::new(&mut res.tx_meta[..], &mut res.tx_buffer[..]);
48 let dhcp = Dhcpv4Client::new(sockets, rx_buffer, tx_buffer, timestamp);
49 info!("created dhcp");
50 self.client = Some(dhcp)
51 }
52
53 let client = self.client.as_mut().unwrap();
54
55 let link_up = iface.device_mut().device.link_state() == LinkState::Up;
56 if !link_up {
57 client.reset(timestamp);
58 return Some(Config::Down);
59 }
60
61 let config = client.poll(iface, sockets, timestamp).unwrap_or(None)?;
62
63 if config.address.is_none() {
64 return Some(Config::Down);
65 }
66
67 let mut dns_servers = Vec::new();
68 for s in &config.dns_servers {
69 if let Some(addr) = s {
70 dns_servers.push(addr.clone()).unwrap();
71 }
72 }
73
74 return Some(Config::Up(UpConfig {
75 address: config.address.unwrap(),
76 gateway: config.router.unwrap_or(Ipv4Address::UNSPECIFIED),
77 dns_servers,
78 }));
79 }
80}
diff --git a/embassy-net/src/config/mod.rs b/embassy-net/src/config/mod.rs
new file mode 100644
index 000000000..596374f9e
--- /dev/null
+++ b/embassy-net/src/config/mod.rs
@@ -0,0 +1,34 @@
1use heapless::consts::*;
2use heapless::Vec;
3use smoltcp::time::Instant;
4use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
5
6use crate::fmt::*;
7use crate::{Interface, SocketSet};
8
9mod dhcp;
10mod statik;
11pub use dhcp::DhcpConfigurator;
12pub use statik::StaticConfigurator;
13
14#[derive(Debug, Clone)]
15pub enum Config {
16 Down,
17 Up(UpConfig),
18}
19
20#[derive(Debug, Clone)]
21pub struct UpConfig {
22 pub address: Ipv4Cidr,
23 pub gateway: Ipv4Address,
24 pub dns_servers: Vec<Ipv4Address, U3>,
25}
26
27pub trait Configurator {
28 fn poll(
29 &mut self,
30 iface: &mut Interface,
31 sockets: &mut SocketSet,
32 timestamp: Instant,
33 ) -> Option<Config>;
34}
diff --git a/embassy-net/src/config/statik.rs b/embassy-net/src/config/statik.rs
new file mode 100644
index 000000000..52196f48a
--- /dev/null
+++ b/embassy-net/src/config/statik.rs
@@ -0,0 +1,26 @@
1use smoltcp::time::Instant;
2
3use super::*;
4use crate::fmt::*;
5use crate::{Interface, SocketSet};
6
7pub struct StaticConfigurator {
8 config: UpConfig,
9}
10
11impl StaticConfigurator {
12 pub fn new(config: UpConfig) -> Self {
13 Self { config }
14 }
15}
16
17impl Configurator for StaticConfigurator {
18 fn poll(
19 &mut self,
20 _iface: &mut Interface,
21 _sockets: &mut SocketSet,
22 _timestamp: Instant,
23 ) -> Option<Config> {
24 Some(Config::Up(self.config.clone()))
25 }
26}
diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs
new file mode 100644
index 000000000..95a62e792
--- /dev/null
+++ b/embassy-net/src/device.rs
@@ -0,0 +1,103 @@
1use core::task::{Poll, Waker};
2use smoltcp::phy::Device as SmolDevice;
3use smoltcp::phy::DeviceCapabilities;
4use smoltcp::time::Instant as SmolInstant;
5use smoltcp::Result;
6
7use crate::fmt::*;
8use crate::{Packet, PacketBox, PacketBuf};
9
10#[derive(PartialEq, Eq, Clone, Copy)]
11pub enum LinkState {
12 Down,
13 Up,
14}
15
16pub trait Device {
17 fn is_transmit_ready(&mut self) -> bool;
18 fn transmit(&mut self, pkt: PacketBuf);
19 fn receive(&mut self) -> Option<PacketBuf>;
20
21 fn register_waker(&mut self, waker: &Waker);
22 fn capabilities(&mut self) -> DeviceCapabilities;
23 fn link_state(&mut self) -> LinkState;
24}
25
26pub struct DeviceAdapter {
27 pub device: &'static mut dyn Device,
28 caps: DeviceCapabilities,
29}
30
31impl DeviceAdapter {
32 pub(crate) fn new(device: &'static mut dyn Device) -> Self {
33 Self {
34 caps: device.capabilities(),
35 device,
36 }
37 }
38}
39
40impl<'a> SmolDevice<'a> for DeviceAdapter {
41 type RxToken = RxToken;
42 type TxToken = TxToken<'a>;
43
44 fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
45 let rx_pkt = self.device.receive()?;
46 let tx_pkt = PacketBox::new(Packet::new()).unwrap(); // TODO: not sure about unwrap
47 let rx_token = RxToken { pkt: rx_pkt };
48 let tx_token = TxToken {
49 device: self.device,
50 pkt: tx_pkt,
51 };
52
53 Some((rx_token, tx_token))
54 }
55
56 /// Construct a transmit token.
57 fn transmit(&'a mut self) -> Option<Self::TxToken> {
58 if !self.device.is_transmit_ready() {
59 return None;
60 }
61
62 let tx_pkt = PacketBox::new(Packet::new())?;
63 Some(TxToken {
64 device: self.device,
65 pkt: tx_pkt,
66 })
67 }
68
69 /// Get a description of device capabilities.
70 fn capabilities(&self) -> DeviceCapabilities {
71 self.caps.clone()
72 }
73}
74
75pub struct RxToken {
76 pkt: PacketBuf,
77}
78
79impl smoltcp::phy::RxToken for RxToken {
80 fn consume<R, F>(mut self, _timestamp: SmolInstant, f: F) -> Result<R>
81 where
82 F: FnOnce(&mut [u8]) -> Result<R>,
83 {
84 f(&mut self.pkt)
85 }
86}
87
88pub struct TxToken<'a> {
89 device: &'a mut dyn Device,
90 pkt: PacketBox,
91}
92
93impl<'a> smoltcp::phy::TxToken for TxToken<'a> {
94 fn consume<R, F>(mut self, _timestamp: SmolInstant, len: usize, f: F) -> Result<R>
95 where
96 F: FnOnce(&mut [u8]) -> Result<R>,
97 {
98 let mut buf = self.pkt.slice(0..len);
99 let r = f(&mut buf)?;
100 self.device.transmit(buf);
101 Ok(r)
102 }
103}
diff --git a/embassy-net/src/fmt.rs b/embassy-net/src/fmt.rs
new file mode 100644
index 000000000..4da69766c
--- /dev/null
+++ b/embassy-net/src/fmt.rs
@@ -0,0 +1,118 @@
1#![macro_use]
2
3#[cfg(all(feature = "defmt", feature = "log"))]
4compile_error!("You may not enable both `defmt` and `log` features.");
5
6pub use fmt::*;
7
8#[cfg(feature = "defmt")]
9mod fmt {
10 pub use defmt::{
11 assert, assert_eq, assert_ne, debug, debug_assert, debug_assert_eq, debug_assert_ne, error,
12 info, panic, todo, trace, unreachable, unwrap, warn,
13 };
14}
15
16#[cfg(feature = "log")]
17mod fmt {
18 pub use core::{
19 assert, assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, panic, todo,
20 unreachable,
21 };
22 pub use log::{debug, error, info, trace, warn};
23}
24
25#[cfg(not(any(feature = "defmt", feature = "log")))]
26mod fmt {
27 #![macro_use]
28
29 pub use core::{
30 assert, assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, panic, todo,
31 unreachable,
32 };
33
34 #[macro_export]
35 macro_rules! trace {
36 ($($msg:expr),+ $(,)?) => {
37 ()
38 };
39 }
40
41 #[macro_export]
42 macro_rules! debug {
43 ($($msg:expr),+ $(,)?) => {
44 ()
45 };
46 }
47
48 #[macro_export]
49 macro_rules! info {
50 ($($msg:expr),+ $(,)?) => {
51 ()
52 };
53 }
54
55 #[macro_export]
56 macro_rules! warn {
57 ($($msg:expr),+ $(,)?) => {
58 ()
59 };
60 }
61
62 #[macro_export]
63 macro_rules! error {
64 ($($msg:expr),+ $(,)?) => {
65 ()
66 };
67 }
68}
69
70#[cfg(not(feature = "defmt"))]
71#[macro_export]
72macro_rules! unwrap {
73 ($arg:expr) => {
74 match $crate::fmt::Try::into_result($arg) {
75 ::core::result::Result::Ok(t) => t,
76 ::core::result::Result::Err(e) => {
77 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
78 }
79 }
80 };
81 ($arg:expr, $($msg:expr),+ $(,)? ) => {
82 match $crate::fmt::Try::into_result($arg) {
83 ::core::result::Result::Ok(t) => t,
84 ::core::result::Result::Err(e) => {
85 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
86 }
87 }
88 }
89}
90
91#[derive(Debug, Copy, Clone, Eq, PartialEq)]
92pub struct NoneError;
93
94pub trait Try {
95 type Ok;
96 type Error;
97 fn into_result(self) -> Result<Self::Ok, Self::Error>;
98}
99
100impl<T> Try for Option<T> {
101 type Ok = T;
102 type Error = NoneError;
103
104 #[inline]
105 fn into_result(self) -> Result<T, NoneError> {
106 self.ok_or(NoneError)
107 }
108}
109
110impl<T, E> Try for Result<T, E> {
111 type Ok = T;
112 type Error = E;
113
114 #[inline]
115 fn into_result(self) -> Self {
116 self
117 }
118}
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs
new file mode 100644
index 000000000..a2a320adf
--- /dev/null
+++ b/embassy-net/src/lib.rs
@@ -0,0 +1,31 @@
1#![cfg_attr(not(feature = "std"), no_std)]
2#![feature(const_fn)]
3#![feature(const_in_array_repeat_expressions)]
4#![feature(const_generics)]
5#![feature(const_evaluatable_checked)]
6#![allow(incomplete_features)]
7
8// This mod MUST go first, so that the others see its macros.
9pub(crate) mod fmt;
10
11mod pool; // TODO extract to embassy, or to own crate
12
13mod config;
14mod device;
15mod packet_pool;
16mod stack;
17mod tcp_socket;
18
19pub use config::{Config, Configurator, DhcpConfigurator, StaticConfigurator, UpConfig};
20pub use device::{Device, LinkState};
21pub use packet_pool::{Packet, PacketBox, PacketBuf};
22pub use stack::{init, is_init, run};
23pub use tcp_socket::TcpSocket;
24
25// smoltcp reexports
26pub use smoltcp::phy::{DeviceCapabilities, Medium};
27pub use smoltcp::time::Duration as SmolDuration;
28pub use smoltcp::time::Instant as SmolInstant;
29pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
30pub type Interface = smoltcp::iface::Interface<'static, device::DeviceAdapter>;
31pub type SocketSet = smoltcp::socket::SocketSet<'static>;
diff --git a/embassy-net/src/packet_pool.rs b/embassy-net/src/packet_pool.rs
new file mode 100644
index 000000000..246356431
--- /dev/null
+++ b/embassy-net/src/packet_pool.rs
@@ -0,0 +1,88 @@
1use as_slice::{AsMutSlice, AsSlice};
2use core::ops::{Deref, DerefMut, Range};
3
4use super::pool::{BitPool, Box, StaticPool};
5
6pub const MTU: usize = 1514;
7pub const PACKET_POOL_SIZE: usize = 4;
8
9pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]);
10pub type PacketBox = Box<PacketPool>;
11
12pub struct Packet(pub [u8; MTU]);
13
14impl Packet {
15 pub const fn new() -> Self {
16 Self([0; MTU])
17 }
18}
19
20impl Box<PacketPool> {
21 pub fn slice(self, range: Range<usize>) -> PacketBuf {
22 PacketBuf {
23 packet: self,
24 range,
25 }
26 }
27}
28
29impl AsSlice for Packet {
30 type Element = u8;
31
32 fn as_slice(&self) -> &[Self::Element] {
33 &self.deref()[..]
34 }
35}
36
37impl AsMutSlice for Packet {
38 fn as_mut_slice(&mut self) -> &mut [Self::Element] {
39 &mut self.deref_mut()[..]
40 }
41}
42
43impl Deref for Packet {
44 type Target = [u8; MTU];
45
46 fn deref(&self) -> &[u8; MTU] {
47 &self.0
48 }
49}
50
51impl DerefMut for Packet {
52 fn deref_mut(&mut self) -> &mut [u8; MTU] {
53 &mut self.0
54 }
55}
56
57pub struct PacketBuf {
58 packet: PacketBox,
59 range: Range<usize>,
60}
61
62impl AsSlice for PacketBuf {
63 type Element = u8;
64
65 fn as_slice(&self) -> &[Self::Element] {
66 &self.packet[self.range.clone()]
67 }
68}
69
70impl AsMutSlice for PacketBuf {
71 fn as_mut_slice(&mut self) -> &mut [Self::Element] {
72 &mut self.packet[self.range.clone()]
73 }
74}
75
76impl Deref for PacketBuf {
77 type Target = [u8];
78
79 fn deref(&self) -> &[u8] {
80 &self.packet[self.range.clone()]
81 }
82}
83
84impl DerefMut for PacketBuf {
85 fn deref_mut(&mut self) -> &mut [u8] {
86 &mut self.packet[self.range.clone()]
87 }
88}
diff --git a/embassy-net/src/pool.rs b/embassy-net/src/pool.rs
new file mode 100644
index 000000000..3ab36e4cc
--- /dev/null
+++ b/embassy-net/src/pool.rs
@@ -0,0 +1,245 @@
1#![macro_use]
2
3use as_slice::{AsMutSlice, AsSlice};
4use core::cmp;
5use core::fmt;
6use core::hash::{Hash, Hasher};
7use core::mem::MaybeUninit;
8use core::ops::{Deref, DerefMut};
9use core::sync::atomic::{AtomicU32, Ordering};
10
11use crate::fmt::{assert, *};
12
13struct AtomicBitset<const N: usize>
14where
15 [AtomicU32; (N + 31) / 32]: Sized,
16{
17 used: [AtomicU32; (N + 31) / 32],
18}
19
20impl<const N: usize> AtomicBitset<N>
21where
22 [AtomicU32; (N + 31) / 32]: Sized,
23{
24 const fn new() -> Self {
25 const Z: AtomicU32 = AtomicU32::new(0);
26 Self {
27 used: [Z; (N + 31) / 32],
28 }
29 }
30
31 fn alloc(&self) -> Option<usize> {
32 for (i, val) in self.used.iter().enumerate() {
33 let res = val.fetch_update(Ordering::AcqRel, Ordering::Acquire, |val| {
34 let n = val.trailing_ones() as usize + i * 32;
35 if n >= N {
36 None
37 } else {
38 Some(val | (1 << n))
39 }
40 });
41 if let Ok(val) = res {
42 let n = val.trailing_ones() as usize + i * 32;
43 return Some(n);
44 }
45 }
46 None
47 }
48 fn free(&self, i: usize) {
49 assert!(i < N);
50 self.used[i / 32].fetch_and(!(1 << ((i % 32) as u32)), Ordering::AcqRel);
51 }
52}
53
54pub trait Pool<T> {
55 fn alloc(&self) -> Option<*mut T>;
56 unsafe fn free(&self, p: *mut T);
57}
58
59pub struct BitPool<T, const N: usize>
60where
61 [AtomicU32; (N + 31) / 32]: Sized,
62{
63 used: AtomicBitset<N>,
64 data: MaybeUninit<[T; N]>,
65}
66
67impl<T, const N: usize> BitPool<T, N>
68where
69 [AtomicU32; (N + 31) / 32]: Sized,
70{
71 pub const fn new() -> Self {
72 Self {
73 used: AtomicBitset::new(),
74 data: MaybeUninit::uninit(),
75 }
76 }
77}
78
79impl<T, const N: usize> Pool<T> for BitPool<T, N>
80where
81 [AtomicU32; (N + 31) / 32]: Sized,
82{
83 fn alloc(&self) -> Option<*mut T> {
84 let n = self.used.alloc()?;
85 let origin = self.data.as_ptr() as *mut T;
86 Some(unsafe { origin.add(n) })
87 }
88
89 /// safety: p must be a pointer obtained from self.alloc that hasn't been freed yet.
90 unsafe fn free(&self, p: *mut T) {
91 let origin = self.data.as_ptr() as *mut T;
92 let n = p.offset_from(origin);
93 assert!(n >= 0);
94 assert!((n as usize) < N);
95 self.used.free(n as usize);
96 }
97}
98
99pub trait StaticPool: 'static {
100 type Item: 'static;
101 type Pool: Pool<Self::Item>;
102 fn get() -> &'static Self::Pool;
103}
104
105pub struct Box<P: StaticPool> {
106 ptr: *mut P::Item,
107}
108
109impl<P: StaticPool> Box<P> {
110 pub fn new(item: P::Item) -> Option<Self> {
111 let p = match P::get().alloc() {
112 Some(p) => p,
113 None => {
114 warn!("alloc failed!");
115 return None;
116 }
117 };
118 //trace!("allocated {:u32}", p as u32);
119 unsafe { p.write(item) };
120 Some(Self { ptr: p })
121 }
122}
123
124impl<P: StaticPool> Drop for Box<P> {
125 fn drop(&mut self) {
126 unsafe {
127 //trace!("dropping {:u32}", self.ptr as u32);
128 self.ptr.drop_in_place();
129 P::get().free(self.ptr);
130 };
131 }
132}
133
134unsafe impl<P: StaticPool> Send for Box<P> where P::Item: Send {}
135
136unsafe impl<P: StaticPool> Sync for Box<P> where P::Item: Sync {}
137
138unsafe impl<P: StaticPool> stable_deref_trait::StableDeref for Box<P> {}
139
140impl<P: StaticPool> AsSlice for Box<P>
141where
142 P::Item: AsSlice,
143{
144 type Element = <P::Item as AsSlice>::Element;
145
146 fn as_slice(&self) -> &[Self::Element] {
147 self.deref().as_slice()
148 }
149}
150
151impl<P: StaticPool> AsMutSlice for Box<P>
152where
153 P::Item: AsMutSlice,
154{
155 fn as_mut_slice(&mut self) -> &mut [Self::Element] {
156 self.deref_mut().as_mut_slice()
157 }
158}
159
160impl<P: StaticPool> Deref for Box<P> {
161 type Target = P::Item;
162
163 fn deref(&self) -> &P::Item {
164 unsafe { &*self.ptr }
165 }
166}
167
168impl<P: StaticPool> DerefMut for Box<P> {
169 fn deref_mut(&mut self) -> &mut P::Item {
170 unsafe { &mut *self.ptr }
171 }
172}
173
174impl<P: StaticPool> fmt::Debug for Box<P>
175where
176 P::Item: fmt::Debug,
177{
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 <P::Item as fmt::Debug>::fmt(self, f)
180 }
181}
182
183impl<P: StaticPool> fmt::Display for Box<P>
184where
185 P::Item: fmt::Display,
186{
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 <P::Item as fmt::Display>::fmt(self, f)
189 }
190}
191
192impl<P: StaticPool> PartialEq for Box<P>
193where
194 P::Item: PartialEq,
195{
196 fn eq(&self, rhs: &Box<P>) -> bool {
197 <P::Item as PartialEq>::eq(self, rhs)
198 }
199}
200
201impl<P: StaticPool> Eq for Box<P> where P::Item: Eq {}
202
203impl<P: StaticPool> PartialOrd for Box<P>
204where
205 P::Item: PartialOrd,
206{
207 fn partial_cmp(&self, rhs: &Box<P>) -> Option<cmp::Ordering> {
208 <P::Item as PartialOrd>::partial_cmp(self, rhs)
209 }
210}
211
212impl<P: StaticPool> Ord for Box<P>
213where
214 P::Item: Ord,
215{
216 fn cmp(&self, rhs: &Box<P>) -> cmp::Ordering {
217 <P::Item as Ord>::cmp(self, rhs)
218 }
219}
220
221impl<P: StaticPool> Hash for Box<P>
222where
223 P::Item: Hash,
224{
225 fn hash<H>(&self, state: &mut H)
226 where
227 H: Hasher,
228 {
229 <P::Item as Hash>::hash(self, state)
230 }
231}
232
233macro_rules! pool {
234 ($vis:vis $name:ident: [$ty:ty; $size:expr]) => {
235 $vis struct $name;
236 impl StaticPool for $name {
237 type Item = $ty;
238 type Pool = BitPool<$ty, $size>;
239 fn get() -> &'static Self::Pool {
240 static POOL: BitPool<$ty, $size> = BitPool::new();
241 &POOL
242 }
243 }
244 };
245}
diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs
new file mode 100644
index 000000000..c353f1bb1
--- /dev/null
+++ b/embassy-net/src/stack.rs
@@ -0,0 +1,212 @@
1use core::future::Future;
2use core::task::Context;
3use core::task::Poll;
4use core::{cell::RefCell, future};
5use embassy::time::{Instant, Timer};
6use embassy::util::ThreadModeMutex;
7use embassy::util::{Forever, WakerRegistration};
8use futures::pin_mut;
9use smoltcp::iface::{InterfaceBuilder, Neighbor, NeighborCache, Route, Routes};
10use smoltcp::phy::Device as _;
11use smoltcp::phy::Medium;
12use smoltcp::socket::SocketSetItem;
13use smoltcp::time::Instant as SmolInstant;
14use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
15
16use crate::device::{Device, DeviceAdapter};
17use crate::fmt::*;
18use crate::{
19 config::{Config, Configurator},
20 device::LinkState,
21};
22use crate::{Interface, SocketSet};
23
24const ADDRESSES_LEN: usize = 1;
25const NEIGHBOR_CACHE_LEN: usize = 8;
26const SOCKETS_LEN: usize = 2;
27const LOCAL_PORT_MIN: u16 = 1025;
28const LOCAL_PORT_MAX: u16 = 65535;
29
30struct StackResources {
31 addresses: [IpCidr; ADDRESSES_LEN],
32 neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR_CACHE_LEN],
33 sockets: [Option<SocketSetItem<'static>>; SOCKETS_LEN],
34 routes: [Option<(IpCidr, Route)>; 1],
35}
36
37static STACK_RESOURCES: Forever<StackResources> = Forever::new();
38static STACK: ThreadModeMutex<RefCell<Option<Stack>>> = ThreadModeMutex::new(RefCell::new(None));
39
40pub(crate) struct Stack {
41 iface: Interface,
42 pub sockets: SocketSet,
43 link_up: bool,
44 next_local_port: u16,
45 configurator: &'static mut dyn Configurator,
46 waker: WakerRegistration,
47}
48
49impl Stack {
50 pub(crate) fn with<R>(f: impl FnOnce(&mut Stack) -> R) -> R {
51 let mut stack = STACK.borrow().borrow_mut();
52 let stack = stack.as_mut().unwrap();
53 f(stack)
54 }
55
56 pub fn get_local_port(&mut self) -> u16 {
57 let res = self.next_local_port;
58 self.next_local_port = if res >= LOCAL_PORT_MAX {
59 LOCAL_PORT_MIN
60 } else {
61 res + 1
62 };
63 res
64 }
65
66 pub(crate) fn wake(&mut self) {
67 self.waker.wake()
68 }
69
70 fn poll_configurator(&mut self, timestamp: SmolInstant) {
71 if let Some(config) = self
72 .configurator
73 .poll(&mut self.iface, &mut self.sockets, timestamp)
74 {
75 let medium = self.iface.device().capabilities().medium;
76
77 let (addr, gateway) = match config {
78 Config::Up(config) => (config.address.into(), Some(config.gateway)),
79 Config::Down => (IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32), None),
80 };
81
82 self.iface.update_ip_addrs(|addrs| {
83 let curr_addr = &mut addrs[0];
84 if *curr_addr != addr {
85 info!("IPv4 address: {:?} -> {:?}", *curr_addr, addr);
86 *curr_addr = addr;
87 }
88 });
89
90 if medium == Medium::Ethernet {
91 self.iface.routes_mut().update(|r| {
92 let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0);
93 let curr_gateway = r.get(&cidr).map(|r| r.via_router);
94
95 if curr_gateway != gateway.map(|a| a.into()) {
96 info!("IPv4 gateway: {:?} -> {:?}", curr_gateway, gateway);
97 if let Some(gateway) = gateway {
98 r.insert(cidr, Route::new_ipv4_gateway(gateway)).unwrap();
99 } else {
100 r.remove(&cidr);
101 }
102 }
103 });
104 }
105 }
106 }
107
108 fn poll(&mut self, cx: &mut Context<'_>) {
109 self.iface.device_mut().device.register_waker(cx.waker());
110 self.waker.register(cx.waker());
111
112 let timestamp = instant_to_smoltcp(Instant::now());
113 if let Err(e) = self.iface.poll(&mut self.sockets, timestamp) {
114 // If poll() returns error, it may not be done yet, so poll again later.
115 cx.waker().wake_by_ref();
116 return;
117 }
118
119 // Update link up
120 let old_link_up = self.link_up;
121 self.link_up = self.iface.device_mut().device.link_state() == LinkState::Up;
122
123 // Print when changed
124 if old_link_up != self.link_up {
125 if self.link_up {
126 info!("Link up!");
127 } else {
128 info!("Link down!");
129 }
130 }
131
132 if old_link_up || self.link_up {
133 self.poll_configurator(timestamp)
134 }
135
136 if let Some(poll_at) = self.iface.poll_at(&mut self.sockets, timestamp) {
137 let t = Timer::at(instant_from_smoltcp(poll_at));
138 pin_mut!(t);
139 if t.poll(cx).is_ready() {
140 cx.waker().wake_by_ref();
141 }
142 }
143 }
144}
145
146/// Initialize embassy_net.
147/// This function must be called from thread mode.
148pub fn init(device: &'static mut dyn Device, configurator: &'static mut dyn Configurator) {
149 let res = STACK_RESOURCES.put(StackResources {
150 addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32)],
151 neighbor_cache: [None; NEIGHBOR_CACHE_LEN],
152 sockets: [None; SOCKETS_LEN],
153 routes: [None; 1],
154 });
155
156 let ethernet_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]);
157
158 let medium = device.capabilities().medium;
159
160 let mut b = InterfaceBuilder::new(DeviceAdapter::new(device));
161 b = b.ip_addrs(&mut res.addresses[..]);
162
163 if medium == Medium::Ethernet {
164 b = b.ethernet_addr(ethernet_addr);
165 b = b.neighbor_cache(NeighborCache::new(&mut res.neighbor_cache[..]));
166 b = b.routes(Routes::new(&mut res.routes[..]));
167 }
168
169 let iface = b.finalize();
170
171 let sockets = SocketSet::new(&mut res.sockets[..]);
172
173 let local_port = loop {
174 let mut res = [0u8; 2];
175 embassy::rand::rand(&mut res);
176 let port = u16::from_le_bytes(res);
177 if port >= LOCAL_PORT_MIN && port <= LOCAL_PORT_MAX {
178 break port;
179 }
180 };
181
182 let stack = Stack {
183 iface,
184 sockets,
185 link_up: false,
186 configurator,
187 next_local_port: local_port,
188 waker: WakerRegistration::new(),
189 };
190
191 *STACK.borrow().borrow_mut() = Some(stack);
192}
193
194pub fn is_init() -> bool {
195 STACK.borrow().borrow().is_some()
196}
197
198pub async fn run() {
199 futures::future::poll_fn(|cx| {
200 Stack::with(|stack| stack.poll(cx));
201 Poll::<()>::Pending
202 })
203 .await
204}
205
206fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
207 SmolInstant::from_millis(instant.as_millis() as i64)
208}
209
210fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
211 Instant::from_millis(instant.total_millis() as u64)
212}
diff --git a/embassy-net/src/tcp_socket.rs b/embassy-net/src/tcp_socket.rs
new file mode 100644
index 000000000..7f4eb014c
--- /dev/null
+++ b/embassy-net/src/tcp_socket.rs
@@ -0,0 +1,178 @@
1use core::marker::PhantomData;
2use core::mem;
3use core::pin::Pin;
4use core::task::{Context, Poll};
5use embassy::io;
6use embassy::io::{AsyncBufRead, AsyncWrite};
7use smoltcp::socket::SocketHandle;
8use smoltcp::socket::TcpSocket as SyncTcpSocket;
9use smoltcp::socket::{TcpSocketBuffer, TcpState};
10use smoltcp::time::Duration;
11use smoltcp::wire::IpEndpoint;
12use smoltcp::{Error, Result};
13
14use super::stack::Stack;
15use crate::fmt::*;
16
17pub struct TcpSocket<'a> {
18 handle: SocketHandle,
19 ghost: PhantomData<&'a mut [u8]>,
20}
21
22impl<'a> Unpin for TcpSocket<'a> {}
23
24impl<'a> TcpSocket<'a> {
25 pub fn new(rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
26 let handle = Stack::with(|stack| {
27 let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
28 let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
29 stack.sockets.add(SyncTcpSocket::new(
30 TcpSocketBuffer::new(rx_buffer),
31 TcpSocketBuffer::new(tx_buffer),
32 ))
33 });
34
35 Self {
36 handle,
37 ghost: PhantomData,
38 }
39 }
40
41 pub async fn connect<T>(&mut self, remote_endpoint: T) -> Result<()>
42 where
43 T: Into<IpEndpoint>,
44 {
45 let local_port = Stack::with(|stack| stack.get_local_port());
46 self.with(|s| s.connect(remote_endpoint, local_port))?;
47
48 futures::future::poll_fn(|cx| {
49 self.with(|s| match s.state() {
50 TcpState::Closed | TcpState::TimeWait => Poll::Ready(Err(Error::Unaddressable)),
51 TcpState::Listen => Poll::Ready(Err(Error::Illegal)),
52 TcpState::SynSent | TcpState::SynReceived => {
53 s.register_send_waker(cx.waker());
54 Poll::Pending
55 }
56 _ => Poll::Ready(Ok(())),
57 })
58 })
59 .await
60 }
61
62 pub fn set_timeout(&mut self, duration: Option<Duration>) {
63 self.with(|s| s.set_timeout(duration))
64 }
65
66 pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
67 self.with(|s| s.set_keep_alive(interval))
68 }
69
70 pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
71 self.with(|s| s.set_hop_limit(hop_limit))
72 }
73
74 pub fn local_endpoint(&self) -> IpEndpoint {
75 self.with(|s| s.local_endpoint())
76 }
77
78 pub fn remote_endpoint(&self) -> IpEndpoint {
79 self.with(|s| s.remote_endpoint())
80 }
81
82 pub fn state(&self) -> TcpState {
83 self.with(|s| s.state())
84 }
85
86 pub fn close(&mut self) {
87 self.with(|s| s.close())
88 }
89
90 pub fn abort(&mut self) {
91 self.with(|s| s.abort())
92 }
93
94 pub fn may_send(&self) -> bool {
95 self.with(|s| s.may_send())
96 }
97
98 pub fn may_recv(&self) -> bool {
99 self.with(|s| s.may_recv())
100 }
101
102 fn with<R>(&self, f: impl FnOnce(&mut SyncTcpSocket) -> R) -> R {
103 Stack::with(|stack| {
104 let res = {
105 let mut s = stack.sockets.get::<SyncTcpSocket>(self.handle);
106 f(&mut *s)
107 };
108 stack.wake();
109 res
110 })
111 }
112}
113
114fn to_ioerr(e: Error) -> io::Error {
115 warn!("smoltcp err: {:?}", e);
116 // todo
117 io::Error::Other
118}
119
120impl<'a> Drop for TcpSocket<'a> {
121 fn drop(&mut self) {
122 Stack::with(|stack| {
123 stack.sockets.remove(self.handle);
124 })
125 }
126}
127
128impl<'a> AsyncBufRead for TcpSocket<'a> {
129 fn poll_fill_buf<'z>(
130 self: Pin<&'z mut Self>,
131 cx: &mut Context<'_>,
132 ) -> Poll<io::Result<&'z [u8]>> {
133 self.with(|socket| match socket.peek(1 << 30) {
134 // No data ready
135 Ok(buf) if buf.len() == 0 => {
136 socket.register_recv_waker(cx.waker());
137 Poll::Pending
138 }
139 // Data ready!
140 Ok(buf) => {
141 // Safety:
142 // - User can't touch the inner TcpSocket directly at all.
143 // - The socket itself won't touch these bytes until consume() is called, which
144 // requires the user to release this borrow.
145 let buf: &'z [u8] = unsafe { core::mem::transmute(&*buf) };
146 Poll::Ready(Ok(buf))
147 }
148 // EOF
149 Err(Error::Finished) => Poll::Ready(Ok(&[][..])),
150 // Error
151 Err(e) => Poll::Ready(Err(to_ioerr(e))),
152 })
153 }
154
155 fn consume(self: Pin<&mut Self>, amt: usize) {
156 self.with(|s| s.recv(|_| (amt, ()))).unwrap()
157 }
158}
159
160impl<'a> AsyncWrite for TcpSocket<'a> {
161 fn poll_write(
162 self: Pin<&mut Self>,
163 cx: &mut Context<'_>,
164 buf: &[u8],
165 ) -> Poll<io::Result<usize>> {
166 self.with(|s| match s.send_slice(buf) {
167 // Not ready to send (no space in the tx buffer)
168 Ok(0) => {
169 s.register_send_waker(cx.waker());
170 Poll::Pending
171 }
172 // Some data sent
173 Ok(n) => Poll::Ready(Ok(n)),
174 // Error
175 Err(e) => Poll::Ready(Err(to_ioerr(e))),
176 })
177 }
178}
diff --git a/test-build.sh b/test-build.sh
new file mode 100755
index 000000000..f67cc5b2b
--- /dev/null
+++ b/test-build.sh
@@ -0,0 +1,25 @@
1#!/bin/bash
2
3set -euxo pipefail
4
5# embassy std
6(cd embassy; cargo build --features log,std)
7
8# embassy embedded
9(cd embassy; cargo build --target thumbv7em-none-eabi)
10(cd embassy; cargo build --target thumbv7em-none-eabi --features log)
11(cd embassy; cargo build --target thumbv7em-none-eabi --features defmt)
12
13# embassy-nrf
14
15(cd embassy-nrf-examples; cargo build --target thumbv7em-none-eabi --bins)
16
17(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52810)
18#(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52811) # nrf52811-hal doesn't exist yet
19(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52832)
20(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52833)
21(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52840)
22
23(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52840,log)
24(cd embassy-nrf; cargo build --target thumbv7em-none-eabi --features 52840,defmt)
25