forked from M-Labs/artiq
DHCP support for core device firmware
DHCP is enabled by setting the `ip` config entry to "use_dhcp". Reusing this config field rather than creating a new one means that there is no ambiguity over which config field takes precedence. Adds a thread to configure the interface based on DHCP events Adds a `Dhcpv4Socket` as a wrapper around smoltcp's version Formalises the storage of the IP addresses so that we can update one in another module. There's also a workaround for the first DHCP discover packet frequently going missing. Signed-off-by: Michael Birtwell <michael.birtwell@oxionics.com>
This commit is contained in:
parent
c60de48a30
commit
6ffb1f83ee
|
@ -42,6 +42,7 @@ Highlights:
|
||||||
* ``PCA9548`` I2C switch class renamed to ``I2CSwitch``, to accomodate support for PCA9547, and
|
* ``PCA9548`` I2C switch class renamed to ``I2CSwitch``, to accomodate support for PCA9547, and
|
||||||
possibly other switches in future. Readback has been removed, and now only one channel per
|
possibly other switches in future. Readback has been removed, and now only one channel per
|
||||||
switch is supported.
|
switch is supported.
|
||||||
|
* The "ip" config option can now be set to "use_dhcp" to use DHCP to obtain an IP address.
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use smoltcp::wire::{EthernetAddress, IpAddress};
|
use smoltcp::wire::{EthernetAddress, IpAddress, Ipv4Address};
|
||||||
|
|
||||||
use config;
|
use config;
|
||||||
#[cfg(soc_platform = "kasli")]
|
#[cfg(soc_platform = "kasli")]
|
||||||
use i2c_eeprom;
|
use i2c_eeprom;
|
||||||
|
|
||||||
|
|
||||||
|
pub const USE_DHCP: IpAddress = IpAddress::Ipv4(Ipv4Address::UNSPECIFIED);
|
||||||
|
|
||||||
|
|
||||||
pub struct NetAddresses {
|
pub struct NetAddresses {
|
||||||
pub hardware_addr: EthernetAddress,
|
pub hardware_addr: EthernetAddress,
|
||||||
pub ipv4_addr: IpAddress,
|
pub ipv4_addr: IpAddress,
|
||||||
|
@ -49,7 +52,13 @@ pub fn get_adresses() -> NetAddresses {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ipv4_addr;
|
let ipv4_addr;
|
||||||
match config::read_str("ip", |r| r.map(|s| s.parse())) {
|
match config::read_str("ip", |r| r.map(|s| {
|
||||||
|
if s == "use_dhcp" {
|
||||||
|
Ok(USE_DHCP)
|
||||||
|
} else {
|
||||||
|
s.parse()
|
||||||
|
}
|
||||||
|
})) {
|
||||||
Ok(Ok(addr)) => ipv4_addr = addr,
|
Ok(Ok(addr)) => ipv4_addr = addr,
|
||||||
_ => {
|
_ => {
|
||||||
#[cfg(soc_platform = "kasli")]
|
#[cfg(soc_platform = "kasli")]
|
||||||
|
|
|
@ -27,9 +27,13 @@ board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp
|
||||||
logger_artiq = { path = "../liblogger_artiq" }
|
logger_artiq = { path = "../liblogger_artiq" }
|
||||||
board_artiq = { path = "../libboard_artiq" }
|
board_artiq = { path = "../libboard_artiq" }
|
||||||
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }
|
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }
|
||||||
smoltcp = { version = "0.8.0", default-features = false, features = ["alloc", "medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] }
|
|
||||||
riscv = { version = "0.6.0", features = ["inline-asm"] }
|
riscv = { version = "0.6.0", features = ["inline-asm"] }
|
||||||
|
|
||||||
|
[dependencies.smoltcp]
|
||||||
|
version = "0.8.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["alloc", "medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp", "socket-dhcpv4"]
|
||||||
|
|
||||||
[dependencies.fringe]
|
[dependencies.fringe]
|
||||||
git = "https://git.m-labs.hk/M-Labs/libfringe.git"
|
git = "https://git.m-labs.hk/M-Labs/libfringe.git"
|
||||||
rev = "3ecbe5"
|
rev = "3ecbe5"
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
use board_misoc::clock;
|
||||||
|
use sched;
|
||||||
|
use sched::Dhcpv4Socket;
|
||||||
|
use smoltcp::socket::Dhcpv4Event;
|
||||||
|
use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
|
||||||
|
|
||||||
|
pub fn dhcp_thread(io: sched::Io) {
|
||||||
|
let mut socket = Dhcpv4Socket::new(&io);
|
||||||
|
let mut last_ip: Option<Ipv4Cidr> = None;
|
||||||
|
let mut done_reset = false;
|
||||||
|
let start_time = clock::get_ms();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// A significant amount of the time our first discover isn't received
|
||||||
|
// by the server. This is likely to be because the ethernet device isn't quite
|
||||||
|
// ready at the point that it is sent. The following makes recovery from
|
||||||
|
// that faster.
|
||||||
|
if !done_reset && last_ip.is_none() && start_time + 1000 < clock::get_ms() {
|
||||||
|
info!("Didn't get initial IP in first second. Assuming packet loss, trying a reset.");
|
||||||
|
socket.reset();
|
||||||
|
done_reset = true;
|
||||||
|
}
|
||||||
|
if let Some(event) = socket.poll() {
|
||||||
|
match event {
|
||||||
|
Dhcpv4Event::Configured(config) => {
|
||||||
|
// Only compare the ip address in the config with previous config because we
|
||||||
|
// ignore the rest of the config i.e. we don't do any DNS or require a default
|
||||||
|
// gateway.
|
||||||
|
let changed = if let Some(last_ip) = last_ip {
|
||||||
|
if config.address != last_ip {
|
||||||
|
info!("IP address changed {} -> {}", last_ip, config.address);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Acquired IP address: None -> {}", config.address);
|
||||||
|
true
|
||||||
|
};
|
||||||
|
if changed {
|
||||||
|
last_ip = Some(config.address);
|
||||||
|
io.set_ipv4_address(&config.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dhcpv4Event::Deconfigured => {
|
||||||
|
if let Some(ip) = last_ip {
|
||||||
|
info!("Lost IP address {} -> None", ip);
|
||||||
|
io.set_ipv4_address(&Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
|
||||||
|
last_ip = None;
|
||||||
|
}
|
||||||
|
// We always get one of these events at the start, ignore that one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We want to poll after every poll of the interface. So we need to
|
||||||
|
// do a minimal yield here.
|
||||||
|
io.relinquish().unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,11 +27,12 @@ extern crate riscv;
|
||||||
|
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
use smoltcp::wire::{IpCidr, HardwareAddress};
|
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr, Ipv4Address};
|
||||||
|
|
||||||
use board_misoc::{csr, ident, clock, spiflash, config, net_settings, pmp, boot};
|
use board_misoc::{csr, ident, clock, spiflash, config, net_settings, pmp, boot};
|
||||||
#[cfg(has_ethmac)]
|
#[cfg(has_ethmac)]
|
||||||
use board_misoc::ethmac;
|
use board_misoc::ethmac;
|
||||||
|
use board_misoc::net_settings::{NetAddresses, USE_DHCP};
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
use board_artiq::drtioaux;
|
use board_artiq::drtioaux;
|
||||||
use board_artiq::drtio_routing;
|
use board_artiq::drtio_routing;
|
||||||
|
@ -58,6 +59,14 @@ mod session;
|
||||||
mod moninj;
|
mod moninj;
|
||||||
#[cfg(has_rtio_analyzer)]
|
#[cfg(has_rtio_analyzer)]
|
||||||
mod analyzer;
|
mod analyzer;
|
||||||
|
mod dhcp;
|
||||||
|
|
||||||
|
// Fixed indexes for the IP addresses so that they can be modified with some
|
||||||
|
// semblance of confidence
|
||||||
|
pub const IPV4_INDEX: usize = 0;
|
||||||
|
pub const IPV6_LL_INDEX: usize = 1;
|
||||||
|
pub const IPV6_INDEX: usize = 2;
|
||||||
|
const IP_ADDRESS_STORAGE_SIZE: usize = 3;
|
||||||
|
|
||||||
#[cfg(has_grabber)]
|
#[cfg(has_grabber)]
|
||||||
fn grabber_thread(io: sched::Io) {
|
fn grabber_thread(io: sched::Io) {
|
||||||
|
@ -87,6 +96,18 @@ fn setup_log_levels() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ip_addrs(net_addresses: &NetAddresses) -> [IpCidr; IP_ADDRESS_STORAGE_SIZE] {
|
||||||
|
let mut storage = [
|
||||||
|
IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0); IP_ADDRESS_STORAGE_SIZE
|
||||||
|
];
|
||||||
|
storage[IPV4_INDEX] = IpCidr::new(net_addresses.ipv4_addr, 0);
|
||||||
|
storage[IPV6_LL_INDEX] = IpCidr::new(net_addresses.ipv6_ll_addr, 0);
|
||||||
|
if let Some(ipv6) = net_addresses.ipv6_addr {
|
||||||
|
storage[IPV6_INDEX] = IpCidr::new(ipv6, 0);
|
||||||
|
}
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
|
||||||
fn startup() {
|
fn startup() {
|
||||||
clock::init();
|
clock::init();
|
||||||
info!("ARTIQ runtime starting...");
|
info!("ARTIQ runtime starting...");
|
||||||
|
@ -141,31 +162,17 @@ fn startup() {
|
||||||
smoltcp::iface::NeighborCache::new(alloc::collections::btree_map::BTreeMap::new());
|
smoltcp::iface::NeighborCache::new(alloc::collections::btree_map::BTreeMap::new());
|
||||||
let net_addresses = net_settings::get_adresses();
|
let net_addresses = net_settings::get_adresses();
|
||||||
info!("network addresses: {}", net_addresses);
|
info!("network addresses: {}", net_addresses);
|
||||||
let interface = match net_addresses.ipv6_addr {
|
let use_dhcp = if net_addresses.ipv4_addr == USE_DHCP {
|
||||||
Some(addr) => {
|
info!("Will try to acquire an IPv4 address with DHCP");
|
||||||
let ip_addrs = [
|
true
|
||||||
IpCidr::new(net_addresses.ipv4_addr, 0),
|
} else {
|
||||||
IpCidr::new(net_addresses.ipv6_ll_addr, 0),
|
false
|
||||||
IpCidr::new(addr, 0)
|
|
||||||
];
|
|
||||||
smoltcp::iface::InterfaceBuilder::new(net_device, vec![])
|
|
||||||
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
|
|
||||||
.ip_addrs(ip_addrs)
|
|
||||||
.neighbor_cache(neighbor_cache)
|
|
||||||
.finalize()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let ip_addrs = [
|
|
||||||
IpCidr::new(net_addresses.ipv4_addr, 0),
|
|
||||||
IpCidr::new(net_addresses.ipv6_ll_addr, 0)
|
|
||||||
];
|
|
||||||
smoltcp::iface::InterfaceBuilder::new(net_device, vec![])
|
|
||||||
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
|
|
||||||
.ip_addrs(ip_addrs)
|
|
||||||
.neighbor_cache(neighbor_cache)
|
|
||||||
.finalize()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
let interface = smoltcp::iface::InterfaceBuilder::new(net_device, vec![])
|
||||||
|
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
|
||||||
|
.ip_addrs(get_ip_addrs(&net_addresses))
|
||||||
|
.neighbor_cache(neighbor_cache)
|
||||||
|
.finalize();
|
||||||
|
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
let drtio_routing_table = urc::Urc::new(RefCell::new(
|
let drtio_routing_table = urc::Urc::new(RefCell::new(
|
||||||
|
@ -182,6 +189,10 @@ fn startup() {
|
||||||
let mut scheduler = sched::Scheduler::new(interface);
|
let mut scheduler = sched::Scheduler::new(interface);
|
||||||
let io = scheduler.io();
|
let io = scheduler.io();
|
||||||
|
|
||||||
|
if use_dhcp {
|
||||||
|
io.spawn(4096, dhcp::dhcp_thread);
|
||||||
|
}
|
||||||
|
|
||||||
rtio_mgt::startup(&io, &aux_mutex, &drtio_routing_table, &up_destinations);
|
rtio_mgt::startup(&io, &aux_mutex, &drtio_routing_table, &up_destinations);
|
||||||
|
|
||||||
io.spawn(4096, mgmt::thread);
|
io.spawn(4096, mgmt::thread);
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use core::result;
|
use core::result;
|
||||||
use core::cell::{Cell, RefCell};
|
use core::cell::{Cell, RefCell, RefMut};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use fringe::OwnedStack;
|
use fringe::OwnedStack;
|
||||||
use fringe::generator::{Generator, Yielder, State as GeneratorState};
|
use fringe::generator::{Generator, Yielder, State as GeneratorState};
|
||||||
use smoltcp::time::Duration;
|
use smoltcp::time::Duration;
|
||||||
use smoltcp::Error as NetworkError;
|
use smoltcp::Error as NetworkError;
|
||||||
use smoltcp::wire::IpEndpoint;
|
use smoltcp::wire::{IpEndpoint, Ipv4Cidr, IpCidr};
|
||||||
use smoltcp::iface::{Interface, SocketHandle};
|
use smoltcp::iface::{Interface, SocketHandle};
|
||||||
|
|
||||||
use io::{Read, Write};
|
use io::{Read, Write};
|
||||||
|
@ -16,6 +16,7 @@ use board_misoc::clock;
|
||||||
use urc::Urc;
|
use urc::Urc;
|
||||||
use board_misoc::ethmac::EthernetDevice;
|
use board_misoc::ethmac::EthernetDevice;
|
||||||
use smoltcp::phy::Tracer;
|
use smoltcp::phy::Tracer;
|
||||||
|
use IPV4_INDEX;
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -273,6 +274,12 @@ impl<'a> Io<'a> {
|
||||||
pub fn join(&self, handle: ThreadHandle) -> Result<(), Error> {
|
pub fn join(&self, handle: ThreadHandle) -> Result<(), Error> {
|
||||||
self.until(move || handle.terminated())
|
self.until(move || handle.terminated())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_ipv4_address(&self, addr: &Ipv4Cidr) {
|
||||||
|
self.network.borrow_mut().update_ip_addrs(|addrs| {
|
||||||
|
addrs.as_mut()[IPV4_INDEX] = IpCidr::Ipv4(*addr);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -563,3 +570,45 @@ impl<'a> Drop for TcpStream<'a> {
|
||||||
self.io.network.borrow_mut().remove_socket(self.handle);
|
self.io.network.borrow_mut().remove_socket(self.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Dhcpv4Socket<'a> {
|
||||||
|
io: &'a Io<'a>,
|
||||||
|
handle: SocketHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Dhcpv4Socket<'a> {
|
||||||
|
|
||||||
|
fn new_lower(io: &'a Io<'a>) -> SocketHandle {
|
||||||
|
let socket = smoltcp::socket::Dhcpv4Socket::new();
|
||||||
|
io.network.borrow_mut().add_socket(socket)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(io: &'a Io<'a>) -> Self {
|
||||||
|
Self {
|
||||||
|
io,
|
||||||
|
handle: Self::new_lower(io)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower(&mut self) -> RefMut<smoltcp::socket::Dhcpv4Socket> {
|
||||||
|
RefMut::map(
|
||||||
|
self.io.network.borrow_mut(),
|
||||||
|
|network| network.get_socket::<smoltcp::socket::Dhcpv4Socket>(self.handle),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll(&mut self) -> Option<smoltcp::socket::Dhcpv4Event> {
|
||||||
|
self.lower().poll()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.lower().reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Drop for Dhcpv4Socket<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut network = self.io.network.borrow_mut();
|
||||||
|
network.remove_socket(self.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -268,6 +268,12 @@ In other cases, install OpenOCD as before, and flash the IP (and, if necessary,
|
||||||
|
|
||||||
For Kasli devices, flashing a MAC address is not necessary as they can obtain it from their EEPROM.
|
For Kasli devices, flashing a MAC address is not necessary as they can obtain it from their EEPROM.
|
||||||
|
|
||||||
|
Alternatively you can set the "ip" config field to "use_dhcp" to have the device use DHCP to obtain an IP address on
|
||||||
|
boot. e.g. ::
|
||||||
|
|
||||||
|
$ artiq_mkfs flash_storage.img -s ip use_dhcp
|
||||||
|
$ artiq_flash -t [board] -V [variant] -f flash_storage.img storage start
|
||||||
|
|
||||||
Check that you can ping the device. If ping fails, check that the Ethernet link LED is ON - on Kasli, it is the LED next to the SFP0 connector. As a next step, look at the messages emitted on the UART during boot. Use a program such as flterm or PuTTY to connect to the device's serial port at 115200bps 8-N-1 and reboot the device. On Kasli, the serial port is on FTDI channel 2 with v1.1 hardware (with channel 0 being JTAG) and on FTDI channel 1 with v1.0 hardware.
|
Check that you can ping the device. If ping fails, check that the Ethernet link LED is ON - on Kasli, it is the LED next to the SFP0 connector. As a next step, look at the messages emitted on the UART during boot. Use a program such as flterm or PuTTY to connect to the device's serial port at 115200bps 8-N-1 and reboot the device. On Kasli, the serial port is on FTDI channel 2 with v1.1 hardware (with channel 0 being JTAG) and on FTDI channel 1 with v1.0 hardware.
|
||||||
|
|
||||||
If you want to use IPv6, the device also has a link-local address that corresponds to its EUI-64, and an additional arbitrary IPv6 address can be defined by using the ``ip6`` configuration key. All IPv4 and IPv6 addresses can be used at the same time.
|
If you want to use IPv6, the device also has a link-local address that corresponds to its EUI-64, and an additional arbitrary IPv6 address can be defined by using the ``ip6`` configuration key. All IPv4 and IPv6 addresses can be used at the same time.
|
||||||
|
|
Loading…
Reference in New Issue