From 7a988bee2204af455baf0c9676a4177830809064 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Mon, 3 Jul 2023 17:43:19 +0200 Subject: [PATCH] core: Add initial/minimal DHCP support to hardware stack. Ex of use in a SoC that issues a DHCP request per second: from liteeth.core.dhcp import LiteEthDHCP from migen.genlib.misc import WaitTimer # Signals. ip_address = Signal(32) mac_address = Signal(48, reset=0x10e2d5000001) # Request Timer. self.dhcp_timer = dhcp_timer = WaitTimer(int(sys_clk_freq/2)) self.comb += self.dhcp_timer.wait.eq(~self.dhcp_timer.done) # DHCP. dhcp_port = self.ethcore_etherbone.udp.crossbar.get_port(68, dw=32, cd="sys") self.dhcp = dhcp = LiteEthDHCP(udp_port=dhcp_port, sys_clk_freq=sys_clk_freq) self.comb += [ dhcp.start.eq(self.dhcp_timer.done), dhcp.mac_address.eq(mac_address), ] self.sync += [ If(dhcp.done, ip_address.eq(dhcp.offered_ip_address) ) ] --- liteeth/core/dhcp.py | 621 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 liteeth/core/dhcp.py diff --git a/liteeth/core/dhcp.py b/liteeth/core/dhcp.py new file mode 100644 index 0000000..d5bd648 --- /dev/null +++ b/liteeth/core/dhcp.py @@ -0,0 +1,621 @@ +# +# This file is part of LiteEth. +# +# Copyright (c) 2023 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +""" +DHCP + +Minimal DHCP (IPV4) support for LiteEth. + +Limitations/TODOs: +- No lease time parsing/support, user logic should consider it short (or known from server) and +issue a DHCP request regularly. Limitations is due to 32-bit data-path and parsing. Switching to a +8-bit data-path for DHCP options would allow supporting it more easily. +- Additional checks could be made on RX (see FIXMEs, but cost logic on FPGA). +- Define more DHCP constants and use them in the code. +""" + +from migen import * +from migen.genlib.misc import WaitTimer + +from litex.gen import * + +from liteeth.common import * + +# DHCP Constants ----------------------------------------------------------------------------------- + +DHCP_SERVER_PORT = 67 +DHCP_CLIENT_PORT = 68 + +DHCP_FIXED_HEADER_LENGTH = 236 +DHCP_SERVER_NAME_LENGTH = 64 +DHCP_BOOT_FILE_NAME_LENGTH = 128 + +DHCP_TX_DISCOVER = 0b0 +DHCP_TX_REQUEST = 0b1 + +DHCP_RX_OFFER = 0b0 +DHCP_RX_ACK = 0b1 + +# DHCP TX ------------------------------------------------------------------------------------------ + +class LiteEthDHCPTX(LiteXModule): + def __init__(self, udp_port): + # Control/Status. + self.start = Signal() # i + self.done = Signal() # o + self.type = Signal() # i + + # Parameters + self.transaction_id = Signal(32) # i + self.mac_address = Signal(48) # i + self.server_ip_address = Signal(32) # o (Only for Request). + self.offered_ip_address = Signal(48) # o (Only for Request). + + # # # + + # Signals. + # -------- + count = Signal(8) + length = Signal(8) + self.comb += Case(self.type, { + DHCP_TX_DISCOVER : length.eq(24), + DHCP_TX_REQUEST : length.eq(36), + }) + + # Static Assign. + # -------------- + self.comb += [ + udp_port.sink.src_port.eq(DHCP_CLIENT_PORT), + udp_port.sink.dst_port.eq(DHCP_SERVER_PORT), + udp_port.sink.ip_address.eq(convert_ip("255.255.255.255")), + udp_port.sink.length.eq(DHCP_FIXED_HEADER_LENGTH + length), + udp_port.sink.last_be.eq(0b1000), # 32-bit. + ] + + # Common FSM. + # ----------- + self.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + If(self.start, + NextState("HEADER") + ) + ) + fsm.act("HEADER", + udp_port.sink.valid.eq(1), + udp_port.sink.data[ 0: 8].eq(0x01), # Message Type: Boot Request (1). + udp_port.sink.data[ 8:16].eq(0x01), # Hardware Type: Ethernet (1). + udp_port.sink.data[16:24].eq(0x06), # Hardware Address Length: 6 bytes. + udp_port.sink.data[24:32].eq(0x00), # Hops: 0. + If(udp_port.sink.ready, + NextState("TRANSACTION-ID") + ) + ) + fsm.act("TRANSACTION-ID", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(self.transaction_id), # Transaction ID. + If(udp_port.sink.ready, + NextState("SECONDS-FLAGS") + ) + ) + fsm.act("SECONDS-FLAGS", + udp_port.sink.valid.eq(1), + udp_port.sink.data[ 0:16].eq(0x0000), # Seconds Elapsed: 0 + udp_port.sink.data[16:24].eq(0x8000), # Flags: Broadcast (0x8000) + If(udp_port.sink.ready, + NextState("CLIENT-IP-ADDRESS") + ) + ) + fsm.act("CLIENT-IP-ADDRESS", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(0x00000000), # Client IP: 0.0.0.0 + If(udp_port.sink.ready, + NextState("YOUR-IP-ADDRESS") + ) + ) + fsm.act("YOUR-IP-ADDRESS", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(0x00000000), # Your IP: 0.0.0.0 + If(udp_port.sink.ready, + NextState("SERVER-IP-ADDRESS") + ) + ) + fsm.act("SERVER-IP-ADDRESS", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(0x00000000), # Server IP: 0.0.0.0 + If(udp_port.sink.ready, + NextState("GATEWAY-IP-ADDRESS") + ) + ) + fsm.act("GATEWAY-IP-ADDRESS", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(0x00000000), # Gateway IP: 0.0.0.0 + If(udp_port.sink.ready, + NextState("CLIENT-MAC-ADDRESS-MSB") + ) + ) + fsm.act("CLIENT-MAC-ADDRESS-MSB", # Client MAC address MSBs. + udp_port.sink.valid.eq(1), + udp_port.sink.data[ 0: 8].eq(self.mac_address[40:48]), + udp_port.sink.data[ 8:16].eq(self.mac_address[32:40]), + udp_port.sink.data[16:24].eq(self.mac_address[24:32]), + udp_port.sink.data[24:32].eq(self.mac_address[16:24]), + If(udp_port.sink.ready, + NextState("CLIENT-MAC-ADDRESS-LSB") + ) + ) + fsm.act("CLIENT-MAC-ADDRESS-LSB", # Client MAC address LSBs. + udp_port.sink.valid.eq(1), + udp_port.sink.data[ 0: 8].eq(self.mac_address[ 8:16]), + udp_port.sink.data[ 8:16].eq(self.mac_address[ 0: 8]), + udp_port.sink.data[16:24].eq(0x00), + udp_port.sink.data[24:32].eq(0x00), + If(udp_port.sink.ready, + NextValue(count, 0), + NextState("CLIENT-MAC-ADDRESS-PADDING") + ) + ) + fsm.act("CLIENT-MAC-ADDRESS-PADDING", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(0x00000000), + If(udp_port.sink.ready, + NextValue(count, count + 1), + If(count == (8//4-1), + NextValue(count, 0), + NextState("SERVER-NAME") + ) + ) + ) + fsm.act("SERVER-NAME", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(0x00000000), + If(udp_port.sink.ready, + NextValue(count, count + 1), + If(count == (DHCP_SERVER_NAME_LENGTH//4 - 1), + NextValue(count, 0), + NextState("BOOT-FILE-NAME") + ) + ) + ) + fsm.act("BOOT-FILE-NAME", + udp_port.sink.valid.eq(1), + udp_port.sink.data.eq(0x00000000), + If(udp_port.sink.ready, + NextValue(count, count + 1), + If(count == (DHCP_BOOT_FILE_NAME_LENGTH//4 - 1), + NextValue(count, 0), + NextState("MAGIC-COOKIE") + ) + ) + ) + fsm.act("MAGIC-COOKIE", + udp_port.sink.valid.eq(1), + udp_port.sink.data[ 0: 8].eq(0x63), + udp_port.sink.data[ 8:16].eq(0x82), + udp_port.sink.data[16:24].eq(0x53), + udp_port.sink.data[24:32].eq(0x63), + If(udp_port.sink.ready, + If(self.type == DHCP_TX_DISCOVER, + NextState("DISCOVER-OPTIONS-0") + ).Elif(self.type == DHCP_TX_REQUEST, + NextState("REQUEST-OPTIONS-0") + ) + ) + ) + # Discover Options. + # ----------------- + fsm.act("DISCOVER-OPTIONS-0", + udp_port.sink.valid.eq(1), + # DHCP Message Type: Discover + udp_port.sink.data[ 0: 8].eq(0x35), + udp_port.sink.data[ 8:16].eq(0x01), + udp_port.sink.data[16:24].eq(0x01), + # Client Identifier + udp_port.sink.data[24:32].eq(0x3d), + If(udp_port.sink.ready, + NextState("DISCOVER-OPTIONS-1") + ) + ) + fsm.act("DISCOVER-OPTIONS-1", + udp_port.sink.valid.eq(1), + # Client Identifier + udp_port.sink.data[ 0: 8].eq(0x06), + udp_port.sink.data[ 8:16].eq(self.mac_address[40:48]), + udp_port.sink.data[16:24].eq(self.mac_address[32:40]), + udp_port.sink.data[24:32].eq(self.mac_address[24:32]), + If(udp_port.sink.ready, + NextState("DISCOVER-OPTIONS-2") + ) + ) + fsm.act("DISCOVER-OPTIONS-2", + udp_port.sink.valid.eq(1), + # Client Identifier + udp_port.sink.data[ 0: 8].eq(self.mac_address[16:24]), + udp_port.sink.data[ 8:16].eq(self.mac_address[ 8:16]), + udp_port.sink.data[16:24].eq(self.mac_address[ 0: 8]), + # Parameter Request List: Subnet Mask, Router, Domain Name Server + udp_port.sink.data[24:32].eq(0x37), + If(udp_port.sink.ready, + NextState("DISCOVER-OPTIONS-3") + ) + ) + fsm.act("DISCOVER-OPTIONS-3", + udp_port.sink.valid.eq(1), + # Parameter Request List: Subnet Mask, Router, Domain Name Server + udp_port.sink.data[ 0: 8].eq(0x03), + udp_port.sink.data[ 8:16].eq(0x03), + udp_port.sink.data[16:24].eq(0x01), + udp_port.sink.data[24:32].eq(0x06), + If(udp_port.sink.ready, + NextState("DISCOVER-OPTIONS-4") + ) + ) + fsm.act("DISCOVER-OPTIONS-4", + udp_port.sink.valid.eq(1), + udp_port.sink.last.eq(1), + # End Option. + udp_port.sink.data[ 0: 8].eq(0xff), + udp_port.sink.data[ 8:16].eq(0x00), + udp_port.sink.data[16:24].eq(0x00), + udp_port.sink.data[24:32].eq(0x00), + If(udp_port.sink.ready, + NextState("DONE") + ) + ) + # Request Options. + # ---------------- + fsm.act("REQUEST-OPTIONS-0", + udp_port.sink.valid.eq(1), + # DHCP Message Type: Request + udp_port.sink.data[ 0: 8].eq(0x35), + udp_port.sink.data[ 8:16].eq(0x01), + udp_port.sink.data[16:24].eq(0x03), + # Requested IP Address + udp_port.sink.data[24:32].eq(0x32), + If(udp_port.sink.ready, + NextState("REQUEST-OPTIONS-1") + ) + ) + fsm.act("REQUEST-OPTIONS-1", + udp_port.sink.valid.eq(1), + # Requested IP Address + udp_port.sink.data[ 0: 8].eq(0x04), + udp_port.sink.data[ 8:16].eq(self.offered_ip_address[24:32]), + udp_port.sink.data[16:24].eq(self.offered_ip_address[16:24]), + udp_port.sink.data[24:32].eq(self.offered_ip_address[ 8:16]), + If(udp_port.sink.ready, + NextState("REQUEST-OPTIONS-2") + ) + ) + fsm.act("REQUEST-OPTIONS-2", + udp_port.sink.valid.eq(1), + # Requested IP Address + udp_port.sink.data[ 0: 8].eq(self.offered_ip_address[0:8]), + # Server IP Address + udp_port.sink.data[ 8:16].eq(0x36), + udp_port.sink.data[16:24].eq(0x04), + udp_port.sink.data[24:32].eq(self.server_ip_address[24:32]), + If(udp_port.sink.ready, + NextState("REQUEST-OPTIONS-3") + ) + ) + fsm.act("REQUEST-OPTIONS-3", + udp_port.sink.valid.eq(1), + # Server IP Address + udp_port.sink.data[ 0: 8].eq(self.server_ip_address[16:24]), + udp_port.sink.data[ 8:16].eq(self.server_ip_address[ 8:16]), + udp_port.sink.data[16:24].eq(self.server_ip_address[ 0: 8]), + # Client Identifier + udp_port.sink.data[24:32].eq(0x3d), + If(udp_port.sink.ready, + NextState("REQUEST-OPTIONS-4") + ) + ) + fsm.act("REQUEST-OPTIONS-4", + udp_port.sink.valid.eq(1), + # Client Identifier + udp_port.sink.data[ 0: 8].eq(0x06), + udp_port.sink.data[ 8:16].eq(self.mac_address[40:48]), + udp_port.sink.data[16:24].eq(self.mac_address[32:40]), + udp_port.sink.data[24:32].eq(self.mac_address[24:32]), + If(udp_port.sink.ready, + NextState("REQUEST-OPTIONS-5") + ) + ) + fsm.act("REQUEST-OPTIONS-5", + udp_port.sink.valid.eq(1), + # Client Identifier + udp_port.sink.data[ 0: 8].eq(self.mac_address[16:24]), + udp_port.sink.data[ 8:16].eq(self.mac_address[ 8:16]), + udp_port.sink.data[16:24].eq(self.mac_address[ 0: 8]), + # Parameter Request List: Subnet Mask, Router, Domain Name Server + udp_port.sink.data[24:32].eq(0x37), + If(udp_port.sink.ready, + NextState("REQUEST-OPTIONS-6") + ) + ) + fsm.act("REQUEST-OPTIONS-6", + udp_port.sink.valid.eq(1), + # Parameter Request List: Subnet Mask, Router, Domain Name Server + udp_port.sink.data[ 0: 8].eq(0x03), + udp_port.sink.data[ 8:16].eq(0x03), + udp_port.sink.data[16:24].eq(0x01), + udp_port.sink.data[24:32].eq(0x06), + If(udp_port.sink.ready, + NextState("REQUEST-OPTIONS-7") + ) + ) + fsm.act("REQUEST-OPTIONS-7", + udp_port.sink.valid.eq(1), + udp_port.sink.last.eq(1), + # End Option. + udp_port.sink.data[ 0: 8].eq(0xff), + udp_port.sink.data[ 8:16].eq(0x00), + udp_port.sink.data[16:24].eq(0x00), + udp_port.sink.data[24:32].eq(0x00), + If(udp_port.sink.ready, + NextState("DONE") + ) + ) + # Done. + # ----- + fsm.act("DONE", + self.done.eq(1), + NextState("IDLE") + ) + +# DHCP Response/ACK -------------------------------------------------------------------------------- + +class LiteEthDHCPRX(LiteXModule): + def __init__(self, udp_port): + # Control/Status. + self.present = Signal() # o + self.ack = Signal() # i + self.type = Signal() # o + self.error = Signal() # o + + # Parameters + self.transaction_id = Signal(32) # i + self.mac_address = Signal(48) # i + self.server_ip_address = Signal(32) # o + self.offered_ip_address = Signal(48) # o + + # # # + + # Common FSM. + # ----------- + self.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + If((udp_port.source.dst_port == DHCP_CLIENT_PORT) & + (udp_port.source.src_port == DHCP_SERVER_PORT) & + # Fixed header + magic_cookie + message_type. + (udp_port.source.length > DHCP_FIXED_HEADER_LENGTH + 4 + 4), + udp_port.source.ready.eq(0), + NextState("HEADER") + ).Else( + NextState("DROP") + ) + ) + ) + fsm.act("HEADER", + # FIXME: Add Check? + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + NextState("TRANSACTION-ID") + ) + ) + fsm.act("TRANSACTION-ID", + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + If(udp_port.source.data == self.transaction_id, + NextState("SECONDS-FLAGS") + ).Else( + NextState("DROP") + ) + ) + ) + fsm.act("SECONDS-FLAGS", + # FIXME: Add Check? + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + NextState("CLIENT-IP-ADDRESS") + ) + ) + fsm.act("CLIENT-IP-ADDRESS", + # FIXME: Add Check? + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + NextState("YOUR-IP-ADDRESS") + ) + ) + fsm.act("YOUR-IP-ADDRESS", + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + NextValue(self.offered_ip_address[24:32], udp_port.source.data[ 0: 8]), + NextValue(self.offered_ip_address[16:24], udp_port.source.data[ 8:16]), + NextValue(self.offered_ip_address[ 8:16], udp_port.source.data[16:24]), + NextValue(self.offered_ip_address[ 0: 8], udp_port.source.data[24:32]), + NextState("SERVER-IP-ADDRESS") + ), + ) + fsm.act("SERVER-IP-ADDRESS", + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + NextValue(self.server_ip_address[24:32], udp_port.source.data[ 0: 8]), + NextValue(self.server_ip_address[16:24], udp_port.source.data[ 8:16]), + NextValue(self.server_ip_address[ 8:16], udp_port.source.data[16:24]), + NextValue(self.server_ip_address[ 0: 8], udp_port.source.data[24:32]), + NextState("GATEWAY-IP-ADDRESS") + ) + ) + fsm.act("GATEWAY-IP-ADDRESS", + # FIXME: Add Check? + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + NextState("CLIENT-MAC-ADDRESS-MSB") + ) + ) + fsm.act("CLIENT-MAC-ADDRESS-MSB", # Client MAC address MSBs. + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + If((udp_port.source.data[ 0: 8] == self.mac_address[40:48]) & + (udp_port.source.data[ 8:16] == self.mac_address[32:40]) & + (udp_port.source.data[16:24] == self.mac_address[24:32]) & + (udp_port.source.data[24:32] == self.mac_address[16:24]), + NextState("CLIENT-MAC-ADDRESS-LSB"), + ).Else( + NextState("DROP") + ) + ) + ) + fsm.act("CLIENT-MAC-ADDRESS-LSB", # Client MAC address LSBs. + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + If((udp_port.source.data[ 0: 8] == self.mac_address[ 8:16]) & + (udp_port.source.data[ 8:16] == self.mac_address[ 0: 8]), + NextState("MAGIC-COOKIE"), + ).Else( + NextState("DROP") + ) + ) + ) + fsm.act("MAGIC-COOKIE", + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + If((udp_port.source.data[ 0: 8] == 0x63) & + (udp_port.source.data[ 8:16] == 0x82) & + (udp_port.source.data[16:24] == 0x53) & + (udp_port.source.data[24:32] == 0x63), + NextState("MESSAGE-TYPE") + ) + ) + ) + fsm.act("MESSAGE-TYPE", + udp_port.source.ready.eq(1), + If(udp_port.source.valid, + # DHCP Message Type. + If((udp_port.source.data[ 0: 8] == 53) & + (udp_port.source.data[ 8:16] == 1), + # DHCP Offer. + If(udp_port.source.data[16:24] == 2, + NextValue(self.type, DHCP_RX_OFFER), + NextState("END") + # DHCP Ack. + ).Elif(udp_port.source.data[16:24] == 5, + NextValue(self.type, DHCP_RX_ACK), + NextState("END") + ).Else( + NextState("DROP") + ) + ) + ) + ) + fsm.act("END", + udp_port.source.ready.eq(1), + If(udp_port.source.valid & udp_port.source.last, + NextState("PRESENT") + ) + ) + fsm.act("DROP", + udp_port.source.ready.eq(1), + If(udp_port.source.valid & udp_port.source.last, + NextValue(self.error, 1), + NextState("PRESENT") + ) + ) + fsm.act("PRESENT", + self.present.eq(1), + If(self.ack, + NextValue(self.error, 0), + NextState("IDLE") + ) + ) + +# DHCP --------------------------------------------------------------------------------------------- + +class LiteEthDHCP(LiteXModule): + def __init__(self, udp_port, sys_clk_freq, timeout=1e0): + assert len(udp_port.sink.data) == 32 # Only supports 32-bit UDP port. + + # Control/Status. + self.start = Signal() # i + self.done = Signal() # o + self.timeout = Signal() # o + + # Parameters + self.mac_address = Signal(48) # i + self.offered_ip_address = Signal(48) # o + + # # # + + # Signals. + transaction_id = Signal(32) + offered_ip_address = Signal(32) + server_ip_address = Signal(32) + + # DHCP TX. + self.tx = tx = ResetInserter()(LiteEthDHCPTX(udp_port)) + self.comb += [ + tx.mac_address.eq(self.mac_address), + tx.transaction_id.eq(transaction_id), + ] + + # DHCP RX. + self.rx = rx = ResetInserter()(LiteEthDHCPRX(udp_port)) + self.comb += [ + rx.mac_address.eq(self.mac_address), + rx.transaction_id.eq(transaction_id), + ] + + # DHCP Timeout. + self.timeout_timer = timeout_timer = WaitTimer(int(timeout*sys_clk_freq)) + self.comb += [ + timeout_timer.wait.eq(~self.done), + self.timeout.eq(timeout_timer.done), + ] + + # DHCP FSM. + self.fsm = fsm = ResetInserter()(FSM(reset_state="IDLE")) + self.comb += fsm.reset.eq(self.timeout) + fsm.act("IDLE", + self.done.eq(1), + If(self.start, + NextValue(transaction_id, transaction_id + 1), + NextState("SEND-DISCOVER") + ) + ) + fsm.act("SEND-DISCOVER", + tx.start.eq(1), + tx.type.eq(DHCP_TX_DISCOVER), + If(tx.done, + NextState("RECEIVE-OFFER") + ) + ) + fsm.act("RECEIVE-OFFER", + rx.ack.eq(1), + If(rx.present & (rx.type == DHCP_RX_OFFER), + NextValue(offered_ip_address, rx.offered_ip_address), + NextValue(server_ip_address, rx.server_ip_address), + NextState("SEND-REQUEST") + ) + ) + fsm.act("SEND-REQUEST", + tx.start.eq(1), + tx.type.eq(DHCP_TX_REQUEST), + tx.offered_ip_address.eq(offered_ip_address), + tx.server_ip_address.eq(server_ip_address), + If(tx.done, + NextState("RECEIVE-ACK") + ) + ) + fsm.act("RECEIVE-ACK", + rx.ack.eq(1), + If(rx.present & (rx.type == DHCP_RX_ACK), + NextValue(self.offered_ip_address, offered_ip_address), + NextState("IDLE") + ) + )