From dff03ecfc74d6af716637888338ebfa99ab7a027 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 2 Jun 2021 01:30:07 +0200 Subject: Move examples to a subdirectory --- examples/std/src/bin/net.rs | 103 ++++++++++++++++++ examples/std/src/bin/serial.rs | 59 +++++++++++ examples/std/src/bin/tick.rs | 31 ++++++ examples/std/src/serial_port.rs | 71 +++++++++++++ examples/std/src/tuntap.rs | 225 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 489 insertions(+) create mode 100644 examples/std/src/bin/net.rs create mode 100644 examples/std/src/bin/serial.rs create mode 100644 examples/std/src/bin/tick.rs create mode 100644 examples/std/src/serial_port.rs create mode 100644 examples/std/src/tuntap.rs (limited to 'examples/std/src') diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs new file mode 100644 index 000000000..5a726e5d2 --- /dev/null +++ b/examples/std/src/bin/net.rs @@ -0,0 +1,103 @@ +#![feature(type_alias_impl_trait)] +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![allow(incomplete_features)] + +use clap::{AppSettings, Clap}; +use embassy::executor::Spawner; +use embassy::io::AsyncWriteExt; +use embassy::util::Forever; +use embassy_net::*; +use embassy_std::Executor; +use heapless::Vec; +use log::*; + +#[path = "../tuntap.rs"] +mod tuntap; + +use crate::tuntap::TunTapDevice; + +static DEVICE: Forever = Forever::new(); +static CONFIG: Forever = Forever::new(); + +#[derive(Clap)] +#[clap(version = "1.0")] +#[clap(setting = AppSettings::ColoredHelp)] +struct Opts { + /// TAP device name + #[clap(long, default_value = "tap0")] + tap: String, +} + +#[embassy::task] +async fn net_task() { + embassy_net::run().await +} + +#[embassy::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Init network device + let device = TunTapDevice::new(&opts.tap).unwrap(); + + // Static IP configuration + let config = StaticConfigurator::new(Config { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + dns_servers: Vec::new(), + gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + }); + + // DHCP configruation + let config = DhcpConfigurator::new(); + + // Init network stack + embassy_net::init(DEVICE.put(device), CONFIG.put(config)); + + // Launch network task + spawner.spawn(net_task()).unwrap(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(&mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(192, 168, 69, 74), 8000); + info!("connecting to {:?}...", remote_endpoint); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + warn!("connect error: {:?}", e); + return; + } + info!("connected!"); + loop { + let r = socket.write_all(b"Hello!\n").await; + if let Err(e) = r { + warn!("write error: {:?}", e); + return; + } + } +} + +#[no_mangle] +fn _embassy_rand(buf: &mut [u8]) { + use rand_core::{OsRng, RngCore}; + OsRng.fill_bytes(buf); +} + +static EXECUTOR: Forever = Forever::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.put(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} diff --git a/examples/std/src/bin/serial.rs b/examples/std/src/bin/serial.rs new file mode 100644 index 000000000..1b22dc0de --- /dev/null +++ b/examples/std/src/bin/serial.rs @@ -0,0 +1,59 @@ +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] +#![allow(incomplete_features)] + +#[path = "../serial_port.rs"] +mod serial_port; + +use async_io::Async; +use embassy::io::AsyncBufReadExt; +use embassy::util::Forever; +use embassy_std::Executor; +use log::*; +use nix::sys::termios; + +use self::serial_port::SerialPort; + +#[embassy::task] +async fn run() { + // Open the serial port. + let baudrate = termios::BaudRate::B115200; + let port = SerialPort::new("/dev/ttyACM0", baudrate).unwrap(); + //let port = Spy::new(port); + + // Use async_io's reactor for async IO. + // This demonstrates how embassy's executor can drive futures from another IO library. + // Essentially, async_io::Async converts from AsRawFd+Read+Write to futures's AsyncRead+AsyncWrite + let port = Async::new(port).unwrap(); + + // This implements futures's AsyncBufRead based on futures's AsyncRead + let port = futures::io::BufReader::new(port); + + // We can then use FromStdIo to convert from futures's AsyncBufRead+AsyncWrite + // to embassy's AsyncBufRead+AsyncWrite + let mut port = embassy::io::FromStdIo::new(port); + + info!("Serial opened!"); + + loop { + let mut buf = [0u8; 256]; + let n = port.read(&mut buf).await.unwrap(); + info!("read {:?}", &buf[..n]); + } +} + +static EXECUTOR: Forever = Forever::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.put(Executor::new()); + executor.run(|spawner| { + spawner.spawn(run()).unwrap(); + }); +} diff --git a/examples/std/src/bin/tick.rs b/examples/std/src/bin/tick.rs new file mode 100644 index 000000000..6f30edb34 --- /dev/null +++ b/examples/std/src/bin/tick.rs @@ -0,0 +1,31 @@ +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] +#![allow(incomplete_features)] + +use embassy::time::{Duration, Timer}; +use embassy::util::Forever; +use embassy_std::Executor; +use log::*; + +#[embassy::task] +async fn run() { + loop { + info!("tick"); + Timer::after(Duration::from_secs(1)).await; + } +} + +static EXECUTOR: Forever = Forever::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.put(Executor::new()); + executor.run(|spawner| { + spawner.spawn(run()).unwrap(); + }); +} diff --git a/examples/std/src/serial_port.rs b/examples/std/src/serial_port.rs new file mode 100644 index 000000000..7ac1b1edb --- /dev/null +++ b/examples/std/src/serial_port.rs @@ -0,0 +1,71 @@ +use nix::fcntl::OFlag; +use nix::sys::termios; +use nix::Error; +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; + +pub struct SerialPort { + fd: RawFd, +} + +impl SerialPort { + pub fn new<'a, P: ?Sized + nix::NixPath>( + path: &P, + baudrate: termios::BaudRate, + ) -> io::Result { + let fd = nix::fcntl::open( + path, + OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, + nix::sys::stat::Mode::empty(), + ) + .map_err(to_io_error)?; + + let mut cfg = termios::tcgetattr(fd).map_err(to_io_error)?; + cfg.input_flags = termios::InputFlags::empty(); + cfg.output_flags = termios::OutputFlags::empty(); + cfg.control_flags = termios::ControlFlags::empty(); + cfg.local_flags = termios::LocalFlags::empty(); + termios::cfmakeraw(&mut cfg); + cfg.input_flags |= termios::InputFlags::IGNBRK; + cfg.control_flags |= termios::ControlFlags::CREAD; + //cfg.control_flags |= termios::ControlFlags::CRTSCTS; + termios::cfsetospeed(&mut cfg, baudrate).map_err(to_io_error)?; + termios::cfsetispeed(&mut cfg, baudrate).map_err(to_io_error)?; + termios::cfsetspeed(&mut cfg, baudrate).map_err(to_io_error)?; + // Set VMIN = 1 to block until at least one character is received. + cfg.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; + termios::tcsetattr(fd, termios::SetArg::TCSANOW, &cfg).map_err(to_io_error)?; + termios::tcflush(fd, termios::FlushArg::TCIOFLUSH).map_err(to_io_error)?; + + Ok(Self { fd }) + } +} + +impl AsRawFd for SerialPort { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl io::Read for SerialPort { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + nix::unistd::read(self.fd, buf).map_err(to_io_error) + } +} + +impl io::Write for SerialPort { + fn write(&mut self, buf: &[u8]) -> io::Result { + nix::unistd::write(self.fd, buf).map_err(to_io_error) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +fn to_io_error(e: Error) -> io::Error { + match e { + Error::Sys(errno) => errno.into(), + e => io::Error::new(io::ErrorKind::InvalidInput, e), + } +} diff --git a/examples/std/src/tuntap.rs b/examples/std/src/tuntap.rs new file mode 100644 index 000000000..dd453deb3 --- /dev/null +++ b/examples/std/src/tuntap.rs @@ -0,0 +1,225 @@ +use async_io::Async; +use libc; +use log::*; +use smoltcp::wire::EthernetFrame; +use std::io; +use std::io::{Read, Write}; +use std::os::unix::io::{AsRawFd, RawFd}; + +pub const SIOCGIFMTU: libc::c_ulong = 0x8921; +pub const SIOCGIFINDEX: libc::c_ulong = 0x8933; +pub const ETH_P_ALL: libc::c_short = 0x0003; +pub const TUNSETIFF: libc::c_ulong = 0x400454CA; +pub const IFF_TUN: libc::c_int = 0x0001; +pub const IFF_TAP: libc::c_int = 0x0002; +pub const IFF_NO_PI: libc::c_int = 0x1000; + +#[repr(C)] +#[derive(Debug)] +struct ifreq { + ifr_name: [libc::c_char; libc::IF_NAMESIZE], + ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */ +} + +fn ifreq_for(name: &str) -> ifreq { + let mut ifreq = ifreq { + ifr_name: [0; libc::IF_NAMESIZE], + ifr_data: 0, + }; + for (i, byte) in name.as_bytes().iter().enumerate() { + ifreq.ifr_name[i] = *byte as libc::c_char + } + ifreq +} + +fn ifreq_ioctl( + lower: libc::c_int, + ifreq: &mut ifreq, + cmd: libc::c_ulong, +) -> io::Result { + unsafe { + let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq); + if res == -1 { + return Err(io::Error::last_os_error()); + } + } + + Ok(ifreq.ifr_data) +} + +#[derive(Debug)] +pub struct TunTap { + fd: libc::c_int, + ifreq: ifreq, + mtu: usize, +} + +impl AsRawFd for TunTap { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl TunTap { + pub fn new(name: &str) -> io::Result { + unsafe { + let fd = libc::open( + "/dev/net/tun\0".as_ptr() as *const libc::c_char, + libc::O_RDWR | libc::O_NONBLOCK, + ); + if fd == -1 { + return Err(io::Error::last_os_error()); + } + + let mut ifreq = ifreq_for(name); + ifreq.ifr_data = IFF_TAP | IFF_NO_PI; + ifreq_ioctl(fd, &mut ifreq, TUNSETIFF)?; + + let socket = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP); + if socket == -1 { + return Err(io::Error::last_os_error()); + } + + let ip_mtu = ifreq_ioctl(socket, &mut ifreq, SIOCGIFMTU); + libc::close(socket); + let ip_mtu = ip_mtu? as usize; + + // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.) + // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it. + let mtu = ip_mtu + EthernetFrame::<&[u8]>::header_len(); + + Ok(TunTap { fd, mtu, ifreq }) + } + } +} + +impl Drop for TunTap { + fn drop(&mut self) { + unsafe { + libc::close(self.fd); + } + } +} + +impl io::Read for TunTap { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }; + if len == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(len as usize) + } + } +} + +impl io::Write for TunTap { + fn write(&mut self, buf: &[u8]) -> io::Result { + let len = unsafe { libc::write(self.fd, buf.as_ptr() as *mut libc::c_void, buf.len()) }; + if len == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(len as usize) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub struct TunTapDevice { + device: Async, + waker: Option, +} + +impl TunTapDevice { + pub fn new(name: &str) -> io::Result { + Ok(Self { + device: Async::new(TunTap::new(name)?)?, + waker: None, + }) + } +} + +use core::task::Waker; +use embassy_net::{DeviceCapabilities, LinkState, Packet, PacketBox, PacketBoxExt, PacketBuf}; +use std::task::Context; + +impl crate::Device for TunTapDevice { + fn is_transmit_ready(&mut self) -> bool { + true + } + + fn transmit(&mut self, pkt: PacketBuf) { + // todo handle WouldBlock + match self.device.get_mut().write(&pkt) { + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + info!("transmit WouldBlock"); + } + Err(e) => panic!("transmit error: {:?}", e), + } + } + + fn receive(&mut self) -> Option { + let mut pkt = PacketBox::new(Packet::new()).unwrap(); + loop { + match self.device.get_mut().read(&mut pkt[..]) { + Ok(n) => { + return Some(pkt.slice(0..n)); + } + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + let ready = if let Some(w) = self.waker.as_ref() { + let mut cx = Context::from_waker(w); + let ready = self.device.poll_readable(&mut cx).is_ready(); + ready + } else { + false + }; + if !ready { + return None; + } + } + Err(e) => panic!("read error: {:?}", e), + } + } + } + + fn register_waker(&mut self, w: &Waker) { + match self.waker { + // Optimization: If both the old and new Wakers wake the same task, we can simply + // keep the old waker, skipping the clone. (In most executor implementations, + // cloning a waker is somewhat expensive, comparable to cloning an Arc). + Some(ref w2) if (w2.will_wake(w)) => {} + _ => { + // clone the new waker and store it + if let Some(old_waker) = core::mem::replace(&mut self.waker, Some(w.clone())) { + // We had a waker registered for another task. Wake it, so the other task can + // reregister itself if it's still interested. + // + // If two tasks are waiting on the same thing concurrently, this will cause them + // to wake each other in a loop fighting over this WakerRegistration. This wastes + // CPU but things will still work. + // + // If the user wants to have two tasks waiting on the same thing they should use + // a more appropriate primitive that can store multiple wakers. + old_waker.wake() + } + } + } + } + + fn capabilities(&mut self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = self.device.get_ref().mtu; + caps + } + + fn link_state(&mut self) -> LinkState { + LinkState::Up + } + + fn ethernet_address(&mut self) -> [u8; 6] { + [0x02, 0x03, 0x04, 0x05, 0x06, 0x07] + } +} -- cgit