├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── lwipopts.h ├── main.c ├── pico_extras_import.cmake ├── pico_sdk_import.cmake ├── pkt_queue.c ├── pkt_queue.h ├── pkt_utils.c ├── pkt_utils.h ├── rmii_ext_clk.pio ├── rmiieth.c ├── rmiieth.code-workspace ├── rmiieth.h ├── rmiieth_md.c └── rmiieth_md.h /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode/.cortex-debug.*.json 3 | 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Pico Debug", 6 | "type":"cortex-debug", 7 | "cwd": "${workspaceRoot}", 8 | "executable": "${command:cmake.launchTargetPath}", 9 | "request": "launch", 10 | "servertype": "external", 11 | // This may need to be arm-none-eabi-gdb depending on your system 12 | "gdbPath" : "gdb-multiarch", 13 | // Connect to an already running OpenOCD instance 14 | "gdbTarget": "localhost:3333", 15 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 16 | "runToMain": true, 17 | // Work around for stopping at main on restart 18 | "postRestartCommands": [ 19 | "break main", 20 | "continue" 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // These settings tweaks to the cmake plugin will ensure 3 | // that you debug using cortex-debug instead of trying to launch 4 | // a Pico binary on the host 5 | "cmake.statusbar.advanced": { 6 | "debug": { 7 | "visibility": "hidden" 8 | }, 9 | "launch": { 10 | "visibility": "hidden" 11 | }, 12 | "build": { 13 | "visibility": "hidden" 14 | }, 15 | "buildTarget": { 16 | "visibility": "hidden" 17 | } 18 | }, 19 | "cmake.buildBeforeRun": true, 20 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" 21 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | include(pico_sdk_import.cmake) 4 | include(pico_extras_import.cmake) 5 | 6 | 7 | project( rmiieth ) 8 | 9 | pico_sdk_init() 10 | 11 | add_executable(rmiieth 12 | main.c 13 | rmiieth.c 14 | rmiieth_md.c 15 | pkt_queue.c 16 | pkt_utils.c 17 | ) 18 | 19 | 20 | pico_generate_pio_header(rmiieth ${CMAKE_CURRENT_LIST_DIR}/rmii_ext_clk.pio) 21 | 22 | target_include_directories(rmiieth PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 23 | 24 | target_link_libraries(rmiieth PRIVATE pico_stdlib hardware_pio hardware_dma pico_lwip pico_lwip_nosys pico_lwip_http) 25 | pico_add_extra_outputs(rmiieth) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, strags 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico-rmiieth 2 | 100Mbit/sec RMII interface for Raspberry Pi Pico 3 | 4 | Should work with most off-the-shelf LAN8720 modules. (Just google "Amazon LAN8720" to find a bunch of near-identical compatible modules). 5 | 6 | 7 | ## Usage 8 | 9 | The code, as provided, contains a **main.c** that initializes the library, and uses LWIP to obtain an IP address via DHCP, and serve up a simple static file. 10 | 11 | 12 | To use the library directly, you need to do the following things: 13 | 14 | 1. Initialize a rmii_config structure: 15 | 16 | ``` 17 | // initial config 18 | PIO pio; // which PIO to use 19 | int pin_mdc; // MD clock pin 20 | int pin_mdio; // MD data pin 21 | int sleep_us; // time to sleep between MD bits 22 | uint32_t phy_addr; // phy_addr - or use rmiieth_probe() to auto-probe 23 | int pin_clk; // refclk input pin 24 | int pin_tx_base; // TX0 (TX1 must be adjacent) 25 | int pin_tx_valid; // TX_EN 26 | int pin_rx_base; // RX0 (RX1 must be adjacent) 27 | int pin_rx_valid; // CRS 28 | int rx_dma_chan; // RX dma channel id 29 | int tx_dma_chan; // TX dma channel id 30 | int rx_lock_id; // spinlock id - leave as -1 to auto-allocate 31 | uint8_t* rx_queue_buffer; // either pass in a buffer, or NULL to have rmiieth_init() malloc one 32 | int32_t rx_queue_buffer_size; // RX queue size 33 | uint8_t* tx_queue_buffer; // either pass in a buffer, or NULL to have rmiieth_init() malloc one 34 | int32_t tx_queue_buffer_size; // TX queue size 35 | int rx_irq; // RX IRQ number 36 | int mtu; // max packet size 37 | ``` 38 | 39 | You can, if you like, call: 40 | 41 | void rmiieth_set_default_config( rmiieth_config* cfg ) 42 | 43 | to set sensible defaults. The library consumes pretty much all the program memory and 3 state machines on a single PIO. 44 | 45 | ### Hardware 46 | 47 | #### GPIO connections 48 | 49 | The RMII module requires 9 pins to be connected to the Pico: 50 | 51 | * MDIO Interface 52 | 53 | These signals can be assigned to any GPIO 54 | 55 | * MD Clock 56 | * MDIO Data 57 | 58 | * Clock 59 | 60 | 50MHz refclk from the RMII module. 61 | Could be any of gpio 20, 22, 12 or 14. 62 | 63 | * TX Pins 64 | 65 | * TX0 (pin_tx_base) 66 | * TX1 67 | * TX_EN (pin_tx_valid) 68 | 69 | The TX0 and TX1 pins must be adjacent and in order (PIN_TX1 = PIN_TX0 + 1). 70 | 71 | * RX Pins 72 | 73 | * RX0 (pin_rx_base) 74 | * RX1 75 | * CRS (pin_rx_valid) 76 | 77 | The RX0 and RX1 pins must be adjacent and in order (PIN_RX1 = PIN_RX0 + 1). 78 | 79 | ### Software configuration 80 | 81 | #### Packet queues 82 | If you wish, you can pass in pre-allocated buffers for the packet queues, or you can leave the xx_queue_buffer pointer set to NULL, and simply specify a size - in order to have the rmiieth code allocate the buffers for you. The size of the queues depends on (a) how much data you expect to receive/transmit, and (b) the period of time between successive calls to ```rmiieth_poll```. 83 | 84 | #### PHY address 85 | The LAN8720 module is capable of being assigned 32 different addresses. The default on my module appears to be 1. However, you can also call ```rmiieth_probe``` to try and auto-discover the address of the attached device (by reading MD status registers). 86 | 87 | 88 | 89 | 90 | 91 | 2. Call ```rmiieth_init``` with the prepared config. 92 | 93 | 94 | 3. Periodically call ```rmiieth_poll``` - this will cause the next queued TX packet to start transmission (assuming one is ready to go, and the TX channel is idle). It will also restart RX requests if the RX channel is not yet started, or has aborted due to a too-large packet. 95 | 96 | 4. To send a packet, do this: 97 | 98 | ``` 99 | uint8_t* pkt_data; 100 | int pkt_size = 128; // sufficient size of packet + 8-byte preamble + 4-byte FSC 101 | if( rmiieth_tx_alloc_packet( cfg, pkt_size, &pkt_data ) ) 102 | { 103 | // ... fill in packet (including 8-byte 55 55 55 ... d5 preamble and frame sequence check) ... 104 | rmiieth_tx_commit_packet( cfg, pkt_size ); // if you want, you can shrink the final packet here 105 | } 106 | ``` 107 | 108 | 5. To check for received packets, do this: 109 | 110 | ``` 111 | if( rmiieth_rx_packet_available( cfg ) ) 112 | { 113 | uint8_t* pkt_data; 114 | int pkt_length; 115 | if( rmiieth_rx_get_packet( cfg, &pkt_data, &pkt_length ) ) 116 | { 117 | // process the packet 118 | rmiieth_rx_consume_packet( cfg ); 119 | } 120 | } 121 | ``` 122 | 123 | ### Notes 124 | 125 | In order to receive and transmit clocked packet data with sufficient accuracy, it's necessary to overclock the Pico to 250MHz. This has been absolutely fine with every Pico I've tested it with, but of course YMMV. 126 | 127 | -------------------------------------------------------------------------------- /lwipopts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2001-2003 Swedish Institute of Computer Science. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, 6 | * are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 19 | * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 21 | * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 24 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 25 | * OF SUCH DAMAGE. 26 | * 27 | * This file is part of the lwIP TCP/IP stack. 28 | * 29 | * Author: Simon Goldschmidt 30 | * 31 | */ 32 | #ifndef __LWIPOPTS_H__ 33 | #define __LWIPOPTS_H__ 34 | 35 | /* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */ 36 | #define NO_SYS 1 37 | #define MEM_ALIGNMENT 4 38 | #define LWIP_RAW 1 39 | #define LWIP_NETCONN 0 40 | #define LWIP_SOCKET 0 41 | #define LWIP_DHCP 1 42 | #define LWIP_ICMP 1 43 | #define LWIP_UDP 1 44 | #define LWIP_TCP 1 45 | #define ETH_PAD_SIZE 0 46 | #define LWIP_IP_ACCEPT_UDP_PORT(p) ((p) == PP_NTOHS(67)) 47 | 48 | #define TCP_MSS (1500 /*mtu*/ - 20 /*iphdr*/ - 20 /*tcphhr*/) 49 | #define TCP_SND_BUF (2 * TCP_MSS) 50 | 51 | #define ETHARP_SUPPORT_STATIC_ENTRIES 1 52 | 53 | #define LWIP_HTTPD_DYNAMIC_HEADERS 1 54 | #define LWIP_HTTPD_CGI 0 55 | #define LWIP_HTTPD_SSI 0 56 | #define LWIP_HTTPD_SSI_INCLUDE_TAG 0 57 | 58 | #define LWIP_ARP 1 59 | #define LWIP_ETHERNET 1 60 | #define LWIP_IPV4 1 61 | #define LWIP_IPV6 0 62 | #define LWIP_ACD 0 63 | #define LWIP_DHCP_DOES_ACD_CHECK 0 64 | 65 | #if 0 66 | #define LWIP_DEBUG 1 67 | #define TCP_DEBUG LWIP_DBG_ON 68 | #define ETHARP_DEBUG LWIP_DBG_ON 69 | #define PBUF_DEBUG LWIP_DBG_ON 70 | #define IP_DEBUG LWIP_DBG_ON 71 | #define TCPIP_DEBUG LWIP_DBG_ON 72 | #define DHCP_DEBUG LWIP_DBG_ON 73 | #define UDP_DEBUG LWIP_DBG_ON 74 | #endif 75 | 76 | 77 | #endif /* __LWIPOPTS_H__ */ 78 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #include "rmiieth.h" 6 | #include "rmiieth_md.h" 7 | #include "pkt_utils.h" 8 | #include "hardware/pio.h" 9 | #include "hardware/dma.h" 10 | #include "hardware/pll.h" 11 | #include "hardware/clocks.h" 12 | #include "hardware/structs/pll.h" 13 | #include "hardware/structs/clocks.h" 14 | #include 15 | #include 16 | #include "lwip/opt.h" 17 | #include "lwip/def.h" 18 | #include "lwip/mem.h" 19 | #include "lwip/pbuf.h" 20 | #include "lwip/stats.h" 21 | #include "lwip/snmp.h" 22 | #include "lwip/ethip6.h" 23 | #include "lwip/etharp.h" 24 | #include "lwip/dhcp.h" 25 | #include "lwip/init.h" 26 | #include "lwip/netif.h" 27 | #include "lwip/netifapi.h" 28 | #include "lwip/ip_addr.h" 29 | #include "lwip/acd.h" 30 | #include "netif/ethernet.h" 31 | #include "lwip/opt.h" 32 | #include "lwip/dhcp.h" 33 | #include "lwip/prot/dhcp.h" 34 | #include "lwip/timeouts.h" 35 | #include "lwip/apps/httpd.h" 36 | #include "pkt_utils.h" 37 | #include 38 | 39 | #define IFNAME0 'b' 40 | #define IFNAME1 'b' 41 | 42 | static uint8_t g_fake_mac[ 6 ] = { 43 | 0xa4,0xdd,0x7b,0xb6,0xf2,0x1d 44 | }; 45 | 46 | 47 | 48 | struct ethernetif { 49 | rmiieth_config* rmiieth_cfg; 50 | }; 51 | 52 | static void ethernetif_input(struct netif *netif); 53 | 54 | static void low_level_init(struct netif *netif) 55 | { 56 | struct ethernetif *ethernetif = netif->state; 57 | struct pbuf *q; 58 | rmiieth_config* cfg = (rmiieth_config*)ethernetif->rmiieth_cfg; 59 | 60 | netif->hwaddr_len = ETHARP_HWADDR_LEN; 61 | for( int i = 0 ; i < 6 ; i++ ) 62 | { 63 | netif->hwaddr[ i ] = g_fake_mac[ i ]; 64 | } 65 | netif->mtu = cfg->mtu; 66 | netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; 67 | } 68 | 69 | static err_t low_level_output(struct netif *netif, struct pbuf *p) 70 | { 71 | struct ethernetif *ethernetif = netif->state; 72 | struct pbuf *q; 73 | rmiieth_config* cfg = (rmiieth_config*)ethernetif->rmiieth_cfg; 74 | 75 | #if ETH_PAD_SIZE 76 | pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */ 77 | #endif 78 | 79 | // 80 | // compute required size 81 | // 82 | 83 | int cc_len = 0; 84 | for( q = p; q != NULL; q = q->next ) 85 | { 86 | cc_len += q->len; 87 | } 88 | cc_len += 8; // preamble 89 | cc_len += 4; 90 | if( cc_len < 72 ) // min length = 8 + 60 + 4 = 72 91 | { 92 | cc_len = 72; 93 | } 94 | 95 | // 96 | // allocate packet 97 | // 98 | 99 | uint8_t* tx_buffer; 100 | int32_t tx_len = 0; 101 | 102 | if( !rmiieth_tx_alloc_packet( cfg, cc_len, &tx_buffer) ) 103 | { 104 | return( ERR_OK ); /// ? 105 | } 106 | 107 | // 108 | // construct TX packet, including preamble and fcs 109 | // 110 | 111 | tx_buffer[ tx_len++ ] = 0x55; tx_buffer[ tx_len++ ] = 0x55; tx_buffer[ tx_len++ ] = 0x55; tx_buffer[ tx_len++ ] = 0x55; 112 | tx_buffer[ tx_len++ ] = 0x55; tx_buffer[ tx_len++ ] = 0x55; tx_buffer[ tx_len++ ] = 0x55; tx_buffer[ tx_len++ ] = 0xd5; 113 | for( q = p; q != NULL; q = q->next ) 114 | { 115 | memcpy( &tx_buffer[ tx_len ], q->payload, q->len ); 116 | tx_len += q->len; 117 | } 118 | while( tx_len < 8 + 64 - 4 ) 119 | { 120 | tx_buffer[ tx_len++ ] = 0x00; 121 | } 122 | uint32_t fcs = pkt_generate_fcs( &tx_buffer[ 8 ], tx_len - 8 ); 123 | tx_buffer[ tx_len++ ] = (uint8_t)( fcs >> 0 ); 124 | tx_buffer[ tx_len++ ] = (uint8_t)( fcs >> 8 ); 125 | tx_buffer[ tx_len++ ] = (uint8_t)( fcs >> 16 ); 126 | tx_buffer[ tx_len++ ] = (uint8_t)( fcs >> 24 ); 127 | 128 | assert( cc_len == tx_len ); 129 | 130 | rmiieth_tx_commit_packet( cfg, tx_len ); 131 | 132 | MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len); 133 | if (((u8_t *)p->payload)[0] & 1) { 134 | /* broadcast or multicast packet*/ 135 | MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts); 136 | } else { 137 | /* unicast packet */ 138 | MIB2_STATS_NETIF_INC(netif, ifoutucastpkts); 139 | } 140 | /* increase ifoutdiscards or ifouterrors on error */ 141 | 142 | #if ETH_PAD_SIZE 143 | pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ 144 | #endif 145 | 146 | LINK_STATS_INC(link.xmit); 147 | 148 | return ERR_OK; 149 | } 150 | 151 | 152 | 153 | static struct pbuf *low_level_input(struct netif *netif) 154 | { 155 | struct ethernetif *ethernetif = netif->state; 156 | rmiieth_config* cfg = ethernetif->rmiieth_cfg; 157 | struct pbuf *p = NULL; 158 | struct pbuf *q; 159 | uint8_t* pkt; 160 | int pkt_len; 161 | 162 | if( !rmiieth_rx_get_packet( cfg, &pkt, &pkt_len ) ) 163 | { 164 | return( NULL ); 165 | } 166 | 167 | if( !pkt_validate( pkt, &pkt_len ) ) 168 | { 169 | LINK_STATS_INC(link.drop); 170 | MIB2_STATS_NETIF_INC(netif, ifindiscards); 171 | rmiieth_rx_consume_packet( cfg ); 172 | return( NULL ); 173 | } 174 | 175 | p = pbuf_alloc(PBUF_RAW, pkt_len, PBUF_POOL); 176 | if( !p ) 177 | { 178 | rmiieth_rx_consume_packet( cfg ); 179 | return( NULL ); 180 | } 181 | 182 | // we're good 183 | int pos = 0; 184 | for( q = p; q != NULL; q = q->next ) 185 | { 186 | memcpy( q->payload, &pkt[ pos ], q->len ); 187 | pos += q->len; 188 | } 189 | 190 | MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len); 191 | if (((u8_t *)p->payload)[0] & 1) { 192 | MIB2_STATS_NETIF_INC(netif, ifinnucastpkts); 193 | } else { 194 | MIB2_STATS_NETIF_INC(netif, ifinucastpkts); 195 | } 196 | LINK_STATS_INC(link.recv); 197 | rmiieth_rx_consume_packet( cfg ); 198 | return p; 199 | } 200 | 201 | static void ethernetif_input(struct netif *netif) 202 | { 203 | struct ethernetif *ethernetif; 204 | struct eth_hdr *ethhdr; 205 | struct pbuf *p; 206 | 207 | ethernetif = netif->state; 208 | 209 | p = low_level_input(netif); 210 | if (p != NULL) { 211 | if (netif->input(p, netif) != ERR_OK) { 212 | LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); 213 | pbuf_free(p); 214 | p = NULL; 215 | } 216 | } 217 | } 218 | 219 | 220 | err_t ethernetif_init(struct netif *netif) 221 | { 222 | struct ethernetif *ethernetif; 223 | 224 | LWIP_ASSERT("netif != NULL", (netif != NULL)); 225 | 226 | #if LWIP_NETIF_HOSTNAME 227 | /* Initialize interface hostname */ 228 | netif->hostname = "lwip"; 229 | #endif /* LWIP_NETIF_HOSTNAME */ 230 | 231 | /* 232 | * Initialize the snmp variables and counters inside the struct netif. 233 | * The last argument should be replaced with your link speed, in units 234 | * of bits per second. 235 | */ 236 | MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS); 237 | 238 | netif->name[0] = IFNAME0; 239 | netif->name[1] = IFNAME1; 240 | /* We directly use etharp_output() here to sethernetifave a function call. 241 | * You can instead declare your own function an call etharp_output() 242 | * from it if you have to do some checks before sending (e.g. if link 243 | * is available...) */ 244 | #if LWIP_IPV4 245 | netif->output = etharp_output; 246 | #endif /* LWIP_IPV4 */ 247 | #if LWIP_IPV6 248 | netif->output_ip6 = ethip6_output; 249 | #endif /* LWIP_IPV6 */ 250 | netif->linkoutput = low_level_output; 251 | 252 | // ethernetif->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]); 253 | 254 | /* initialize the hardware */ 255 | low_level_init(netif); 256 | 257 | return ERR_OK; 258 | } 259 | 260 | 261 | // 262 | // 263 | // 264 | // 265 | // 266 | 267 | 268 | 269 | void rmiieth_lwip_poll( struct netif* netif ) 270 | { 271 | struct ethernetif* ethernetif = netif->state; 272 | rmiieth_config* cfg = ethernetif->rmiieth_cfg; 273 | 274 | rmiieth_poll( cfg ); 275 | while( rmiieth_rx_packet_available( cfg ) ) 276 | { 277 | ethernetif_input( netif ); 278 | } 279 | } 280 | 281 | 282 | 283 | void main_lwip( rmiieth_config* cfg ) 284 | { 285 | int rc; 286 | static struct netif rmiieth_netif; 287 | static struct ethernetif rmiieth_ethernetif; 288 | struct netif* nif; 289 | u8_t prevDHCPState = 0; 290 | 291 | lwip_init(); 292 | 293 | 294 | memset( &rmiieth_ethernetif, 0, sizeof( rmiieth_ethernetif ) ); 295 | rmiieth_ethernetif.rmiieth_cfg = cfg; 296 | rmiieth_netif.state = &rmiieth_ethernetif; 297 | nif = netif_add_noaddr( &rmiieth_netif, &rmiieth_ethernetif, ethernetif_init, ethernet_input ); 298 | 299 | 300 | netif_set_up( &rmiieth_netif ); 301 | netif_set_link_up( &rmiieth_netif ); 302 | rc = dhcp_start( &rmiieth_netif ); 303 | 304 | httpd_init(); 305 | 306 | while( true ) 307 | { 308 | sleep_us( 1 ); 309 | sys_check_timeouts(); 310 | rmiieth_lwip_poll( nif ); 311 | 312 | // show link status periodically 313 | if( false ) 314 | { 315 | static int ctr = 0; 316 | if( !(( ctr++ ) & 255 ) ) 317 | { 318 | uint32_t v = rmiieth_md_readreg( cfg, RMII_REG_BASIC_STATUS ); 319 | printf( "%08x\n", v ); 320 | } 321 | } 322 | 323 | // show DHCP status 324 | struct dhcp* dd = netif_dhcp_data( &rmiieth_netif ); 325 | if( dd->state != prevDHCPState ) 326 | { 327 | printf( "DHCP State goes from %d to %d\n", prevDHCPState, dd->state ); 328 | prevDHCPState = dd->state; 329 | if( dd->state == DHCP_STATE_BOUND ) 330 | { 331 | char tmp[ 256 ]; 332 | printf( " Got address: \n" ); 333 | printf( " IP : %s\n", ipaddr_ntoa_r( &dd->offered_ip_addr, tmp, sizeof( tmp ) ) ); 334 | printf( " Subnet : %s\n", ipaddr_ntoa_r( &dd->offered_sn_mask, tmp, sizeof( tmp ) ) ); 335 | printf( " GW : %s\n", ipaddr_ntoa_r( &dd->offered_gw_addr, tmp, sizeof( tmp ) ) ); 336 | } 337 | } 338 | 339 | } 340 | 341 | } 342 | 343 | 344 | 345 | int main( int argc, char** argv ) 346 | { 347 | rmiieth_config rmii_cfg; 348 | 349 | // 350 | // set system clock 351 | // 352 | 353 | set_sys_clock_khz( 250000, false ); 354 | setup_default_uart(); 355 | printf( "hello world\n" ); 356 | 357 | // 358 | // init rmiieth interface 359 | // 360 | 361 | rmiieth_set_default_config( &rmii_cfg ); 362 | rmiieth_init( &rmii_cfg ); 363 | if( !rmiieth_probe( &rmii_cfg ) ) 364 | { 365 | assert( false ); 366 | } 367 | sleep_ms( 500 ); 368 | rmiieth_md_reset( &rmii_cfg ); 369 | sleep_ms( 500 ); 370 | 371 | // 372 | // select 100Mbit ethernet, duplex, autonegotiation 373 | // 374 | 375 | // basic control: 0011 0011 0000 0000 enable auto-neg, restart auto-neg 376 | // autoneg-advert: 0000 0001 1000 0001 100Mbps, full duplex 377 | 378 | uint32_t basic_control = 0x3300; 379 | rmiieth_md_writereg( &rmii_cfg, RMII_REG_AUTONEG_ADVERT, 0x181 ); 380 | rmiieth_md_writereg( &rmii_cfg, RMII_REG_BASIC_CONTROL, basic_control ); 381 | sleep_ms( 500 ); 382 | 383 | // 384 | // lwip startup 385 | // 386 | 387 | if( true ) 388 | { 389 | main_lwip( &rmii_cfg ); 390 | } 391 | 392 | return( 0 ); 393 | } 394 | 395 | 396 | -------------------------------------------------------------------------------- /pico_extras_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_extras_import.cmake 2 | 3 | # This can be dropped into an external project to help locate pico-extras 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) 7 | set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) 8 | message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) 12 | set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) 13 | message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) 17 | set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | if (NOT PICO_EXTRAS_PATH) 22 | if (PICO_EXTRAS_FETCH_FROM_GIT) 23 | include(FetchContent) 24 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 25 | if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) 26 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 27 | endif () 28 | FetchContent_Declare( 29 | PICO_EXTRAS 30 | GIT_REPOSITORY https://github.com/raspberrypi/pico-extras 31 | GIT_TAG master 32 | ) 33 | if (NOT PICO_EXTRAS) 34 | message("Downloading PICO EXTRAS") 35 | FetchContent_Populate(PICO_EXTRAS) 36 | set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR}) 37 | endif () 38 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 39 | else () 40 | if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") 41 | set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) 42 | message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") 43 | else() 44 | message(FATAL_ERROR 45 | "PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif() 48 | endif () 49 | endif () 50 | 51 | set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") 52 | set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") 53 | set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") 54 | 55 | get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 56 | if (NOT EXISTS ${PICO_EXTRAS_PATH}) 57 | message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") 58 | endif () 59 | 60 | set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) 61 | 62 | add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | # todo document 7 | 8 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 9 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 10 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 11 | endif () 12 | 13 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 14 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 15 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 16 | endif () 17 | 18 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 19 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 20 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 21 | endif () 22 | 23 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") 24 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") 25 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 26 | 27 | if (NOT PICO_SDK_PATH) 28 | if (PICO_SDK_FETCH_FROM_GIT) 29 | include(FetchContent) 30 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 31 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 32 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 33 | endif () 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | ) 39 | if (NOT pico_sdk) 40 | message("Downloading PICO SDK") 41 | FetchContent_Populate(pico_sdk) 42 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 43 | endif () 44 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 45 | else () 46 | message(FATAL_ERROR 47 | "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 48 | ) 49 | endif () 50 | endif () 51 | 52 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 53 | if (NOT EXISTS ${PICO_SDK_PATH}) 54 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 55 | endif () 56 | 57 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 58 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 59 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") 60 | endif () 61 | 62 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) 63 | 64 | include(${PICO_SDK_INIT_CMAKE_FILE}) 65 | -------------------------------------------------------------------------------- /pkt_queue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #include "pkt_queue.h" 6 | #include 7 | #include 8 | #include 9 | #include "pico.h" 10 | 11 | void pkt_queue_init( pkt_queue* pq, uint8_t* data, int32_t size ) 12 | { 13 | pq->data = data; 14 | pq->head = NULL; 15 | pq->tail = NULL; 16 | pq->size = size; 17 | } 18 | 19 | pkt_queue_pkt* __time_critical_func( pkt_queue_reserve_pkt )( pkt_queue* pq, int32_t max_size ) 20 | { 21 | int32_t required_bytes = ( max_size + sizeof( pkt_queue_pkt_hdr ) + 3 ) & ( ~3 ); 22 | pkt_queue_pkt* pkt; 23 | 24 | // empty queue? 25 | if( !pq->tail ) 26 | { 27 | if( required_bytes > pq->size ) 28 | { 29 | return( NULL ); 30 | } 31 | pkt = (pkt_queue_pkt*)pq->data; 32 | pkt->hdr.data_bytes = max_size; 33 | pkt->hdr.mem_bytes = required_bytes; 34 | pq->head = pkt; 35 | pq->tail = pkt; 36 | return( pkt ); 37 | } 38 | 39 | int32_t rpos = ( (uint8_t*)pq->head ) - pq->data; 40 | int32_t wpos = ( (uint8_t*)pq->tail ) - pq->data; 41 | wpos = ( wpos + pq->tail->hdr.mem_bytes ) % pq->size; 42 | 43 | // alloc on RHS? 44 | if( rpos <= wpos ) 45 | { 46 | // need to wrap? 47 | if( required_bytes <= ( pq->size - wpos ) ) 48 | { 49 | pkt = (pkt_queue_pkt*)( &pq->data[ wpos ] ); 50 | pkt->hdr.mem_bytes = required_bytes; 51 | pkt->hdr.data_bytes = max_size; 52 | pq->tail = pkt; 53 | return( pkt ); 54 | } 55 | 56 | // can wrap? 57 | if( required_bytes >= rpos ) 58 | { 59 | return( NULL ); 60 | } 61 | pq->tail->hdr.mem_bytes += ( pq->size - wpos ); 62 | wpos = 0; 63 | } 64 | 65 | // can alloc on LHS? 66 | if( required_bytes > ( rpos - wpos ) ) 67 | { 68 | return( NULL ); 69 | } 70 | 71 | pkt = (pkt_queue_pkt*)( &pq->data[ wpos ] ); 72 | pkt->hdr.mem_bytes = required_bytes; 73 | pkt->hdr.data_bytes = max_size; 74 | pq->tail = pkt; 75 | return( pkt ); 76 | } 77 | 78 | void __time_critical_func(pkt_queue_commit_pkt)( pkt_queue* pq, pkt_queue_pkt* pkt, int32_t actual_size ) 79 | { 80 | assert( pq->tail == pkt ); 81 | assert( actual_size <= pkt->hdr.data_bytes ); 82 | pkt->hdr.mem_bytes -= ( pkt->hdr.data_bytes - actual_size ) & (~3); 83 | pkt->hdr.data_bytes = actual_size; 84 | } 85 | 86 | pkt_queue_pkt* pkt_queue_peek_pkt( pkt_queue* pq ) 87 | { 88 | return( pq->head ); 89 | } 90 | 91 | void pkt_queue_consume_pkt( pkt_queue* pq ) 92 | { 93 | if( !pq->head ) 94 | { 95 | return; 96 | } 97 | if( pq->head == pq->tail ) 98 | { 99 | pq->head = NULL; 100 | pq->tail = NULL; 101 | return; 102 | } 103 | 104 | int32_t rpos = ( (uint8_t*)pq->head ) - pq->data; 105 | rpos = ( rpos + pq->head->hdr.mem_bytes ) % pq->size; 106 | pq->head = (pkt_queue_pkt*)( &pq->data[ rpos ] ); 107 | } 108 | 109 | void pkt_queue_dump( pkt_queue* pq ) 110 | { 111 | pkt_queue_pkt* pkt = pq->head; 112 | 113 | printf( "\nQueue:\n" ); 114 | if( !pkt ) 115 | { 116 | printf( "\n" ); 117 | return; 118 | } 119 | 120 | while( 1 ) 121 | { 122 | int32_t pos = ( (uint8_t*)pkt ) - pq->data; 123 | printf( "%-5d -> %-5d [%-5d] : %d bytes\n", pos, pos + pkt->hdr.mem_bytes, pkt->hdr.mem_bytes, pkt->hdr.data_bytes ); 124 | if( pkt == pq->tail ) 125 | { 126 | break; 127 | } 128 | pos = ( pos + pkt->hdr.mem_bytes ) % pq->size; 129 | pkt = (pkt_queue_pkt*)( &pq->data[ pos ] ); 130 | } 131 | } 132 | 133 | uint8_t g_test_buffer[ 4096 ]; 134 | pkt_queue g_test_queue; 135 | 136 | void pkt_queue_test( void ) 137 | { 138 | pkt_queue* pq = &g_test_queue; 139 | pkt_queue_pkt* pkt; 140 | 141 | pkt_queue_init( pq, g_test_buffer, sizeof( g_test_buffer ) ); 142 | pkt_queue_dump( pq ); 143 | 144 | pkt = pkt_queue_reserve_pkt( pq, 1016 ); 145 | pkt_queue_dump( pq ); 146 | pkt = pkt_queue_reserve_pkt( pq, 1016 ); 147 | pkt_queue_dump( pq ); 148 | pkt = pkt_queue_reserve_pkt( pq, 1016 ); 149 | pkt_queue_dump( pq ); 150 | pkt = pkt_queue_reserve_pkt( pq, 1016 ); 151 | pkt_queue_dump( pq ); 152 | 153 | pkt_queue_commit_pkt( pq, pkt, 500 ); 154 | pkt_queue_dump( pq ); 155 | 156 | pkt = pkt_queue_reserve_pkt( pq, 500 ); 157 | pkt_queue_dump( pq ); 158 | 159 | pkt_queue_consume_pkt( pq ); 160 | pkt_queue_dump( pq ); 161 | 162 | pkt = pkt_queue_reserve_pkt( pq, 500 ); 163 | pkt_queue_dump( pq ); 164 | 165 | 166 | pkt = pkt_queue_reserve_pkt( pq, 1600 ); 167 | pkt_queue_dump( pq ); 168 | 169 | pkt = pkt_queue_reserve_pkt( pq, 1600 ); 170 | pkt_queue_dump( pq ); 171 | pkt_queue_commit_pkt( pq, pkt, 800 ); 172 | 173 | pkt = pkt_queue_reserve_pkt( pq, 1600 ); 174 | pkt_queue_dump( pq ); 175 | 176 | pkt_queue_consume_pkt( pq ); 177 | pkt_queue_dump( pq ); 178 | 179 | pkt = pkt_queue_reserve_pkt( pq, 1500 ); 180 | pkt_queue_dump( pq ); 181 | 182 | pkt = pkt_queue_reserve_pkt( pq, 1600 ); 183 | pkt_queue_dump( pq ); 184 | 185 | pkt = pkt_queue_reserve_pkt( pq, 50 ); 186 | pkt_queue_dump( pq ); 187 | 188 | pkt_queue_consume_pkt( pq ); 189 | pkt_queue_dump( pq ); 190 | pkt_queue_consume_pkt( pq ); 191 | pkt_queue_dump( pq ); 192 | pkt_queue_consume_pkt( pq ); 193 | pkt_queue_dump( pq ); 194 | pkt_queue_consume_pkt( pq ); 195 | pkt_queue_dump( pq ); 196 | 197 | for( int i = 0 ; i < 100000 ; i++ ) 198 | { 199 | int32_t sz = ( rand() & 0x3ff ) + 0x400; 200 | 201 | pkt = pkt_queue_reserve_pkt( pq, sz ); 202 | if( pkt ) 203 | { 204 | pkt_queue_commit_pkt( pq, pkt, sz / 2 ); 205 | } 206 | else 207 | { 208 | pkt_queue_consume_pkt( pq ); 209 | } 210 | 211 | if( !( i % 1000 ) ) 212 | { 213 | pkt_queue_dump( pq ); 214 | } 215 | } 216 | 217 | while( pkt_queue_peek_pkt( pq ) ) 218 | { 219 | pkt_queue_consume_pkt( pq ); 220 | } 221 | 222 | pkt_queue_dump( pq ); 223 | } 224 | 225 | -------------------------------------------------------------------------------- /pkt_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #ifndef PKT_QUEUE_INCLUDED 6 | #define PKT_QUEUE_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | * pkt_queue 14 | * 15 | * This is a circular queue structure that, when you attempt to reserve space that would normally wrap, pads the previous packet, 16 | * and starts allocating from offset 0 - thus guaranteeing that all reserved buffers are contiguous. 17 | * 18 | * It also allows you to truncate the current reservation - so that you can reserve the max packet-size for DMA, and then truncate 19 | * when you know how big the received packet actually is. 20 | * 21 | * It is expected that functions are called in this order: 22 | * 23 | * pkt_queue_init() 24 | * 25 | * Then, on the write side: 26 | * 27 | * pkt_queue_reserve_pkt() - reserves space - returns NULL if none available 28 | * pkt_queue_commit_pkt() - optional, only necessary if you are truncating the current packet 29 | * 30 | * On the read side: 31 | * 32 | * pkt_queue_peek_pkt() - returns the next available packet for reading (or NULL if the queue is empty) 33 | * pkt_queue_consume_pkt() - consumes the current packet, and release the space 34 | * 35 | * Note: If there's one reserved packet in the queue, and you're DMAing into it, pkt_queue_peek_pkt() will still return it. 36 | * If the packet is in use, you need to account for that. 37 | * 38 | */ 39 | 40 | typedef struct pkt_queue_pkt_hdr 41 | { 42 | int32_t data_bytes; 43 | int32_t mem_bytes; 44 | } pkt_queue_pkt_hdr; 45 | 46 | typedef struct pkt_queue_pkt 47 | { 48 | pkt_queue_pkt_hdr hdr; 49 | uint8_t data[ 1 ]; 50 | } pkt_queue_pkt; 51 | 52 | typedef struct pkt_queue 53 | { 54 | pkt_queue_pkt* head; 55 | pkt_queue_pkt* tail; 56 | int32_t size; 57 | uint8_t* data; 58 | } pkt_queue; 59 | 60 | 61 | 62 | void pkt_queue_init( pkt_queue* pq, uint8_t* data, int32_t size ); 63 | pkt_queue_pkt* pkt_queue_reserve_pkt( pkt_queue* pq, int32_t max_size ); 64 | void pkt_queue_commit_pkt( pkt_queue* pq, pkt_queue_pkt* pkt, int32_t actual_size ); 65 | pkt_queue_pkt* pkt_queue_peek_pkt( pkt_queue* pq ); 66 | void pkt_queue_consume_pkt( pkt_queue* pq ); 67 | 68 | 69 | void pkt_queue_dump( pkt_queue* pq ); 70 | void pkt_queue_test( void ); 71 | 72 | 73 | #endif // #ifndef PKT_QUEUE_INCLUDED -------------------------------------------------------------------------------- /pkt_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #include 6 | #include 7 | #include "pkt_utils.h" 8 | 9 | static uint32_t g_grc_table[] = 10 | { 11 | 0x4DBDF21C, 0x500AE278, 0x76D3D2D4, 0x6B64C2B0, 12 | 0x3B61B38C, 0x26D6A3E8, 0x000F9344, 0x1DB88320, 13 | 0xA005713C, 0xBDB26158, 0x9B6B51F4, 0x86DC4190, 14 | 0xD6D930AC, 0xCB6E20C8, 0xEDB71064, 0xF0000000 15 | }; 16 | 17 | bool pkt_remove_preamble( uint8_t* pkt, int* pkt_len_ptr ) 18 | { 19 | int len = *pkt_len_ptr; 20 | uint32_t sync = 0; 21 | 22 | for( int i = 0 ; i < len ; i++ ) 23 | { 24 | uint8_t byte; 25 | byte = pkt[ i ]; 26 | for( int j = 0; j < 8 ; j+=2 ) 27 | { 28 | if( sync == 0xaaaaaaab ) 29 | { 30 | int nl = 0; 31 | for( int k = i ; k < len-1 ; k++ ) 32 | { 33 | pkt[ nl++ ] = ( pkt[ k+1 ] << ( 8 - j ) ) | ( pkt[ k ] >> j ); 34 | } 35 | *pkt_len_ptr = nl; 36 | return( true ); 37 | } 38 | sync <<= 2; 39 | sync |= ( byte & 1 ) << 1; 40 | sync |= ( byte & 2 ) >> 1; 41 | byte >>= 2; 42 | } 43 | } 44 | return( false ); 45 | } 46 | 47 | int pkt_generate_fcs_and_determine_length( uint8_t* data, int max_length ) 48 | { 49 | uint32_t crc = 0; 50 | uint32_t next_bytes; 51 | 52 | next_bytes = ( (uint32_t)data[ 0 ] << 0 ) | 53 | ( (uint32_t)data[ 1 ] << 8 ) | 54 | ( (uint32_t)data[ 2 ] << 16 ) | 55 | ( (uint32_t)data[ 3 ] << 24 ); 56 | 57 | for( uint32_t i = 4 ; i <= max_length ; i++ ) 58 | { 59 | if( crc == next_bytes ) 60 | { 61 | return( i - 4 ); 62 | } 63 | uint8_t nb = next_bytes & 0xff; 64 | crc = (crc >> 4) ^ g_grc_table[ ( crc ^ ( nb >> 0 ) ) & 0x0F ]; 65 | crc = (crc >> 4) ^ g_grc_table[ ( crc ^ ( nb >> 4 ) ) & 0x0F ]; 66 | next_bytes >>= 8; 67 | next_bytes |= ((uint32_t)data[ i ]) << 24; 68 | } 69 | return( -1 ); 70 | } 71 | 72 | uint32_t pkt_generate_fcs( uint8_t* data, int length ) 73 | { 74 | uint32_t crc = 0; 75 | for( uint32_t i = 0 ; i < length ; i++ ) 76 | { 77 | crc = (crc >> 4) ^ g_grc_table[ ( crc ^ ( data[ i ] >> 0 ) ) & 0x0F ]; 78 | crc = (crc >> 4) ^ g_grc_table[ ( crc ^ ( data[ i ] >> 4 ) ) & 0x0F ]; 79 | } 80 | return( crc ); 81 | } 82 | 83 | 84 | bool pkt_validate( uint8_t* pkt, int* pkt_len_ptr ) 85 | { 86 | if( !pkt_remove_preamble( pkt, pkt_len_ptr ) ) 87 | { 88 | #if PKT_DEBUG_PRINTS 89 | printf( "failed to find preamble/sfd\n" ); 90 | pkt_dump( pkt, *pkt_len_ptr, 64 ); 91 | #endif 92 | return( false ); 93 | } 94 | 95 | int len = *pkt_len_ptr; 96 | if( len < 4 ) 97 | { 98 | return( false ); 99 | } 100 | 101 | { 102 | int q = pkt_generate_fcs_and_determine_length( pkt, len ); 103 | if( q < 0 ) 104 | { 105 | #if PKT_DEBUG_PRINTS 106 | printf( "Unable to match FCS\n" ); 107 | pkt_dump( pkt, *pkt_len_ptr, 2048 ); 108 | #endif 109 | return( false ); 110 | } 111 | *pkt_len_ptr = q; 112 | } 113 | 114 | #if PKT_DEBUG_PRINTS || 1 115 | printf( "Received valid packet of %d bytes\n", *pkt_len_ptr ); 116 | #endif 117 | 118 | return( true ); 119 | } 120 | 121 | void pkt_dump( uint8_t* pkt, int len, int max_len ) 122 | { 123 | if( len > max_len ) 124 | { 125 | len = max_len; 126 | } 127 | for( int i = 0 ; i < len ; i++ ) 128 | { 129 | printf( "%02x ", pkt[ i ] ); 130 | } 131 | printf( "\n" ); 132 | } -------------------------------------------------------------------------------- /pkt_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #ifndef PKT_UTILS_H_INCLUDED 6 | #define PKT_UTILS_H_INCLUDED 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define PKT_DEBUG_PRINTS 0 13 | 14 | bool pkt_remove_preamble( uint8_t* pkt, int* pkt_len_ptr ); 15 | int pkt_generate_fcs_and_determine_length( uint8_t* data, int max_length ); 16 | uint32_t pkt_generate_fcs( uint8_t* data, int length ); 17 | bool pkt_validate( uint8_t* pkt, int* pkt_len_ptr ); 18 | void pkt_dump( uint8_t* pkt, int len, int max_len ); 19 | 20 | #endif // #ifndef PKT_UTILS_H_INCLUDED -------------------------------------------------------------------------------- /rmii_ext_clk.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | ;---------------------------------------------------------------------------------------------------- 6 | ; 7 | ; clk receiver 8 | ; 9 | ; just fire an IRQ a (hopefully) fixed number of clocks after a falling edge 10 | ; 11 | ; at 250MHz, each half-cycle should last 2.5 clocks? oof! 12 | ; 13 | ;---------------------------------------------------------------------------------------------------- 14 | 15 | .program eth_clk 16 | .wrap_target 17 | wait 0 pin 0 18 | irq 4 19 | wait 1 pin 0 20 | irq 5 21 | .wrap 22 | 23 | ;---------------------------------------------------------------------------------------------------- 24 | ; 25 | ; rx 26 | ; 27 | ; wait for a data-valid signal, clock in 2 bits at a time until the data-valid signal goes low, 28 | ; then clock some extra bits to ensure we flush the whole packet. 29 | ; 30 | ;---------------------------------------------------------------------------------------------------- 31 | 32 | .program eth_rx 33 | 34 | ; when this finishes, we'll flush 32 more bits of data 35 | ; to hopefully ensure we got everything... 36 | set x, 31 37 | 38 | ; wait for data-valid 39 | ; wait 1 pin 2 ; this only works if rx0, rx1 and rx_valid are contiguous 40 | wait_loop: 41 | jmp pin, rx_loop 42 | jmp wait_loop 43 | 44 | ; main loop 45 | rx_loop: 46 | wait 1 irq 5 47 | in pins, 2 48 | jmp pin rx_loop ; loop as long as rx-valid is set 49 | 50 | post_loop: 51 | wait 1 irq 5 52 | in pins, 2 53 | jmp x--, post_loop 54 | 55 | ; signal the CPU and stop 56 | irq 0 57 | halt: jmp halt 58 | 59 | ;---------------------------------------------------------------------------------------------------- 60 | ; 61 | ; tx 62 | ; 63 | ; raise tx_en, clock out 2 bits until we're done 64 | ; 65 | ;---------------------------------------------------------------------------------------------------- 66 | 67 | .program eth_tx 68 | .side_set 1 opt 69 | .wrap_target 70 | nop side 0 ; ensure TX_EN is low 71 | out x, 32 ; read # of bit-pairs to send 72 | out y, 32 ; read # of padding bytes 73 | 74 | wait 1 irq 5 75 | wait 1 irq 5 76 | 77 | tx_loop: 78 | wait 1 irq 5 79 | out pins, 2 side 1 ; clock out bits, ensure TX_EN is high 80 | jmp x--, tx_loop 81 | 82 | wait 1 irq 5 83 | set pins, 0 side 0 ; ensure TX_EN is low 84 | 85 | ; discard padding bytes 86 | flush_loop: 87 | out x, 8 88 | jmp y-- flush_loop 89 | .wrap 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /rmiieth.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #include "rmiieth.h" 6 | #include "rmiieth_md.h" 7 | #include "rmii_ext_clk.pio.h" 8 | #include "hardware/dma.h" 9 | #include "hardware/irq.h" 10 | #include "pkt_utils.h" 11 | #include 12 | 13 | static void rmiieth_rx_irq_handler( void ); 14 | static void rmiieth_rx_try_start( rmiieth_config* cfg ); 15 | static void rmiieth_start_tx( rmiieth_config* cfg, pkt_queue_pkt* p ); 16 | 17 | static rmiieth_config* g_cfg; 18 | 19 | 20 | void rmiieth_set_default_config( rmiieth_config* cfg ) 21 | { 22 | memset( cfg, 0, sizeof( rmiieth_config ) ); 23 | cfg->pio = pio0; 24 | cfg->pin_mdc = 14; 25 | cfg->pin_mdio = 15; 26 | cfg->pin_clk = 10; 27 | cfg->pin_rx_base = 11; 28 | cfg->pin_rx_valid = 13; 29 | cfg->pin_tx_base = 7; 30 | cfg->pin_tx_valid = 9; 31 | cfg->sleep_us = 1000; 32 | cfg->phy_addr = 0x01; // this happens to be the default 33 | cfg->rx_dma_chan = 0; 34 | cfg->tx_dma_chan = 1; 35 | cfg->rx_irq = 0; 36 | cfg->rx_lock_id = -1; 37 | 38 | cfg->rx_queue_buffer_size = 8192; 39 | cfg->tx_queue_buffer_size = 8192; 40 | cfg->mtu = 1500; 41 | } 42 | 43 | bool rmiieth_probe( rmiieth_config* cfg ) 44 | { 45 | printf( "PROBING\n" ); 46 | for( int i = 0 ; i < 0x20 ; i++ ) 47 | { 48 | cfg->phy_addr = i; 49 | uint32_t vv = rmiieth_md_readreg( cfg, RMII_REG_BASIC_STATUS ); 50 | printf( "%d -> %08x\n", i, vv ); 51 | if( vv != 0xffff ) 52 | { 53 | return( true ); 54 | } 55 | } 56 | return( false ); 57 | } 58 | 59 | void rmiieth_init( rmiieth_config* cfg ) 60 | { 61 | dma_channel_config c; 62 | 63 | g_cfg = cfg; 64 | 65 | // 66 | // init the MD interface 67 | // 68 | 69 | rmiieth_md_init( cfg ); 70 | 71 | // 72 | // init spinlocks 73 | // 74 | 75 | if( cfg->rx_lock_id < 0 ) 76 | { 77 | cfg->rx_lock_id = next_striped_spin_lock_num(); 78 | } 79 | spin_lock_claim( cfg->rx_lock_id ); 80 | cfg->rx_lock = spin_lock_init( cfg->rx_lock_id ); 81 | 82 | // 83 | // init buffers 84 | // 85 | 86 | if( !cfg->rx_queue_buffer ) 87 | { 88 | cfg->rx_queue_buffer = (uint8_t*)malloc( cfg->rx_queue_buffer_size ); 89 | if( !cfg->rx_queue_buffer ) 90 | { 91 | assert( false ); 92 | } 93 | } 94 | pkt_queue_init( &cfg->rx_queue, cfg->rx_queue_buffer, cfg->rx_queue_buffer_size ); 95 | if( !cfg->tx_queue_buffer ) 96 | { 97 | cfg->tx_queue_buffer = (uint8_t*)malloc( cfg->tx_queue_buffer_size ); 98 | if( !cfg->rx_queue_buffer ) 99 | { 100 | assert( false ); 101 | } 102 | } 103 | pkt_queue_init( &cfg->tx_queue, cfg->tx_queue_buffer, cfg->tx_queue_buffer_size ); 104 | 105 | // 106 | // init IRQ - the RX state machine interrupts us at the end of a packet 107 | // 108 | 109 | int pio_irq = cfg->pio == pio0 ? PIO0_IRQ_0 : PIO1_IRQ_0; 110 | pio_irq += cfg->rx_irq; 111 | 112 | irq_set_exclusive_handler( pio_irq, rmiieth_rx_irq_handler ); 113 | irq_set_enabled( pio_irq, true ); 114 | cfg->pio->inte0 = cfg->rx_irq ? PIO_IRQ1_INTE_SM0_BITS : PIO_IRQ0_INTE_SM0_BITS; 115 | // irq_set_exclusive_handler( PIO0_IRQ_0, rmiieth_rx_irq_handler ); 116 | // irq_set_enabled( PIO0_IRQ_0, true ); 117 | // cfg->pio->inte0 = PIO_IRQ0_INTE_SM0_BITS; 118 | 119 | 120 | // 121 | // init the pins 122 | // 123 | 124 | pio_gpio_init( cfg->pio, cfg->pin_clk ); 125 | pio_gpio_init( cfg->pio, cfg->pin_rx_base ); 126 | pio_gpio_init( cfg->pio, cfg->pin_rx_base + 1 ); 127 | pio_gpio_init( cfg->pio, cfg->pin_rx_valid ); 128 | pio_gpio_init( cfg->pio, cfg->pin_tx_base ); 129 | pio_gpio_init( cfg->pio, cfg->pin_tx_base + 1 ); 130 | pio_gpio_init( cfg->pio, cfg->pin_tx_valid ); 131 | 132 | // 133 | // allocate state-machines 134 | // 135 | 136 | if( ( cfg->clk_sm = pio_claim_unused_sm( cfg->pio, false ) ) < 0 ) 137 | { 138 | assert( false ); 139 | } 140 | if( ( cfg->rx_sm = pio_claim_unused_sm( cfg->pio, false ) ) < 0 ) 141 | { 142 | assert( false ); 143 | } 144 | if( ( cfg->tx_sm = pio_claim_unused_sm( cfg->pio, false ) ) < 0 ) 145 | { 146 | assert( false ); 147 | } 148 | 149 | 150 | // 151 | // initialize the clk program 152 | // 153 | 154 | cfg->clk_offset = pio_add_program( cfg->pio, ð_clk_program ); 155 | cfg->clk_config = eth_clk_program_get_default_config( cfg->clk_offset ); 156 | sm_config_set_in_pins( &cfg->clk_config, cfg->pin_clk ); 157 | pio_sm_set_consecutive_pindirs( cfg->pio, cfg->clk_sm, cfg->pin_clk, 1, false ); 158 | pio_sm_init( cfg->pio, cfg->clk_sm, cfg->clk_offset, &cfg->clk_config ); 159 | pio_sm_set_enabled( cfg->pio, cfg->clk_sm, true ); 160 | 161 | // 162 | // init the RX program 163 | // 164 | 165 | cfg->rx_offset = pio_add_program( cfg->pio, ð_rx_program ); 166 | cfg->rx_config = eth_rx_program_get_default_config( cfg->rx_offset ); 167 | sm_config_set_in_pins( &cfg->rx_config, cfg->pin_rx_base ); 168 | sm_config_set_in_shift( &cfg->rx_config, true, true, 32 ); 169 | sm_config_set_jmp_pin( &cfg->rx_config, cfg->pin_rx_valid ); 170 | pio_sm_set_consecutive_pindirs( cfg->pio, cfg->rx_sm, cfg->pin_rx_base, 2, false ); 171 | pio_sm_set_consecutive_pindirs( cfg->pio, cfg->rx_sm, cfg->pin_rx_valid, 1, false ); 172 | pio_sm_init( cfg->pio, cfg->rx_sm, cfg->rx_offset, &cfg->rx_config ); 173 | 174 | // 175 | // init RX DMA 176 | // 177 | 178 | c = dma_channel_get_default_config( cfg->rx_dma_chan ); 179 | channel_config_set_read_increment( &c, false ); 180 | channel_config_set_write_increment( &c, true ); 181 | channel_config_set_dreq( &c, pio_get_dreq( cfg->pio, cfg->rx_sm, false ) ); 182 | channel_config_set_transfer_data_size( &c, DMA_SIZE_32 ); 183 | dma_channel_configure( 184 | cfg->rx_dma_chan, 185 | &c, 186 | NULL, 187 | (void*)((uintptr_t)( &cfg->pio->rxf[ cfg->rx_sm ] ) ), 188 | 1, 189 | false 190 | ); 191 | 192 | // 193 | // init the TX program 194 | // 195 | 196 | cfg->tx_offset = pio_add_program( cfg->pio, ð_tx_program ); 197 | cfg->tx_config = eth_tx_program_get_default_config( cfg->tx_offset ); 198 | sm_config_set_out_pins( &cfg->tx_config, cfg->pin_tx_base, 2 ); 199 | sm_config_set_out_shift( &cfg->tx_config, true, true, 32 ); 200 | sm_config_set_set_pins( &cfg->tx_config, cfg->pin_tx_base, 2 ); 201 | sm_config_set_sideset_pins( &cfg->tx_config, cfg->pin_tx_valid ); 202 | pio_sm_set_consecutive_pindirs( cfg->pio, cfg->tx_sm, cfg->pin_tx_base, 2, true); 203 | pio_sm_set_consecutive_pindirs( cfg->pio, cfg->tx_sm, cfg->pin_tx_valid, 1, true ); 204 | pio_sm_init( cfg->pio, cfg->tx_sm, cfg->tx_offset, &cfg->tx_config ); 205 | // pio_sm_set_clkdiv( cfg->pio, cfg->tx_sm, 100.0 ); 206 | pio_sm_set_enabled( cfg->pio, cfg->tx_sm, true ); 207 | 208 | // 209 | // init TX DMA 210 | // 211 | 212 | c = dma_channel_get_default_config( cfg->tx_dma_chan ); 213 | channel_config_set_read_increment( &c, true ); 214 | channel_config_set_write_increment( &c, false ); 215 | channel_config_set_dreq( &c, pio_get_dreq( cfg->pio, cfg->tx_sm, true ) ); 216 | channel_config_set_transfer_data_size( &c, DMA_SIZE_32 ); 217 | dma_channel_configure( 218 | cfg->tx_dma_chan, 219 | &c, 220 | (void*)((uintptr_t)( &cfg->pio->txf[ cfg->tx_sm ] ) ), 221 | NULL, 222 | 1, 223 | false 224 | ); 225 | 226 | 227 | } 228 | 229 | static void rmiieth_start_tx( rmiieth_config* cfg, pkt_queue_pkt* p ) 230 | { 231 | cfg->tx_current_pkt = p; 232 | 233 | // init SM 234 | // pio_sm_init( cfg->pio, cfg->tx_sm, cfg->tx_offset, &cfg->tx_config ); 235 | 236 | // send bit-pair count 237 | int bit_pair_ct = ( p->hdr.data_bytes << 2 ); 238 | 239 | // send extra padding bytes 240 | int extra_bytes = 4 - ( p->hdr.data_bytes & 3 ); 241 | 242 | // fill in the 8 bytes that we reserved before the packet data.... 243 | uint32_t* pctrs = (uint32_t *)p->data; 244 | pctrs[ 0 ] = bit_pair_ct - 1; 245 | pctrs[ 1 ] = extra_bytes - 1; 246 | 247 | printf( "TX: %d bytes\n", p->hdr.data_bytes ); 248 | #if PKT_DEBUG_PRINTS 249 | pkt_dump( p->data, ( p->hdr.data_bytes + extra_bytes + 8 ), 2048 ); 250 | #endif 251 | 252 | dma_channel_set_read_addr( cfg->tx_dma_chan, p->data, false ); 253 | dma_channel_set_trans_count( cfg->tx_dma_chan, ( p->hdr.data_bytes + extra_bytes + 8 ) >> 2, true ); 254 | } 255 | 256 | void rmiieth_poll( rmiieth_config* cfg ) 257 | { 258 | // if we filled the current packet, the DMA will have halted - fake an interrupt 259 | { 260 | uint32_t ii = spin_lock_blocking( cfg->rx_lock ); 261 | if( cfg->rx_current_pkt && !dma_channel_is_busy( cfg->rx_dma_chan ) ) 262 | { 263 | printf( "*** buffer overrun\n" ); 264 | rmiieth_rx_irq_handler(); 265 | } 266 | spin_unlock( cfg->rx_lock, ii ); 267 | } 268 | 269 | // if the RX process has not yet started, or it stalled out due to lack of space in the RX queue, 270 | // attempt to restart it 271 | if( !cfg->rx_current_pkt ) 272 | { 273 | uint32_t ii = spin_lock_blocking( cfg->rx_lock ); 274 | rmiieth_rx_try_start( cfg ); 275 | spin_unlock( cfg->rx_lock, ii ); 276 | } 277 | 278 | // consider starting a new TX 279 | if( !dma_channel_is_busy( cfg->tx_dma_chan ) ) 280 | { 281 | if( cfg->tx_current_pkt ) 282 | { 283 | pkt_queue_consume_pkt( &cfg->tx_queue ); 284 | cfg->tx_current_pkt = NULL; 285 | } 286 | 287 | pkt_queue_pkt* p; 288 | p = pkt_queue_peek_pkt( &cfg->tx_queue ); 289 | if( p ) 290 | { 291 | rmiieth_start_tx( cfg, p ); 292 | } 293 | } 294 | 295 | 296 | } 297 | 298 | bool rmiieth_rx_packet_available( rmiieth_config* cfg ) 299 | { 300 | pkt_queue_pkt* pkt = pkt_queue_peek_pkt( &cfg->rx_queue ); 301 | return( pkt && pkt != cfg->rx_current_pkt ); 302 | } 303 | 304 | bool rmiieth_rx_get_packet( rmiieth_config* cfg, uint8_t** pkt_data, int* length ) 305 | { 306 | pkt_queue_pkt* pkt = pkt_queue_peek_pkt( &cfg->rx_queue ); 307 | if( !pkt || pkt == cfg->rx_current_pkt ) 308 | { 309 | return( false ); 310 | } 311 | *pkt_data = pkt->data; 312 | *length = pkt->hdr.data_bytes; 313 | return( true ); 314 | } 315 | 316 | void rmiieth_rx_consume_packet( rmiieth_config* cfg ) 317 | { 318 | uint32_t ii = spin_lock_blocking( cfg->rx_lock ); 319 | pkt_queue_consume_pkt( &cfg->rx_queue ); 320 | spin_unlock( cfg->rx_lock, ii ); 321 | } 322 | 323 | bool rmiieth_tx_alloc_packet( rmiieth_config* cfg, int length, uint8_t** data ) 324 | { 325 | assert( !cfg->tx_current_alloc_pkt ); 326 | 327 | // allocate 8 extra bytes at front, for the bit/byte counters 328 | 329 | cfg->tx_current_alloc_pkt = pkt_queue_reserve_pkt( &cfg->tx_queue, length + 8 ); 330 | if( !cfg->tx_current_alloc_pkt ) 331 | { 332 | return( false ); 333 | } 334 | *data = cfg->tx_current_alloc_pkt->data + 8; 335 | return( true ); 336 | } 337 | 338 | bool rmiieth_tx_commit_packet( rmiieth_config* cfg, int length ) 339 | { 340 | assert( cfg->tx_current_alloc_pkt ); 341 | pkt_queue_commit_pkt( &cfg->tx_queue, cfg->tx_current_alloc_pkt, length ); 342 | cfg->tx_current_alloc_pkt = NULL; 343 | } 344 | 345 | static void __time_critical_func(rmiieth_rx_irq_handler)( void ) 346 | { 347 | rmiieth_config* cfg = g_cfg; 348 | 349 | // halt the dma 350 | dma_channel_abort( cfg->rx_dma_chan ); 351 | 352 | // read the write address, compute the # of bytes received 353 | uintptr_t write_addr = dma_channel_hw_addr( cfg->rx_dma_chan )->write_addr; 354 | int32_t bytes = ( write_addr - (uintptr_t)(cfg->rx_current_pkt->data) ); 355 | pkt_queue_commit_pkt( &cfg->rx_queue, cfg->rx_current_pkt, bytes ); 356 | cfg->rx_current_pkt = NULL; 357 | 358 | // clear PIO irq 359 | cfg->pio->irq = 0x01; 360 | 361 | // try to start a new transfer 362 | rmiieth_rx_try_start( cfg ); 363 | } 364 | 365 | // NOTE: must hold rx spinlock on entry to this function 366 | static void __time_critical_func(rmiieth_rx_try_start)( rmiieth_config* cfg ) 367 | { 368 | if( cfg->rx_current_pkt ) 369 | { 370 | return; 371 | } 372 | 373 | cfg->rx_current_pkt = pkt_queue_reserve_pkt( &cfg->rx_queue, ( cfg->mtu + 52 ) & (~3) ); 374 | if( !cfg->rx_current_pkt ) 375 | { 376 | return; 377 | } 378 | 379 | pio_sm_init( cfg->pio, cfg->rx_sm, cfg->rx_offset, &cfg->rx_config ); 380 | 381 | // restart the DMA 382 | dma_channel_set_write_addr( cfg->rx_dma_chan, cfg->rx_current_pkt->data, false ); 383 | dma_channel_set_trans_count( cfg->rx_dma_chan, cfg->rx_current_pkt->hdr.data_bytes >> 2, true ); 384 | 385 | // restart the state machine 386 | pio_sm_set_enabled( cfg->pio, cfg->rx_sm, true ); 387 | } 388 | 389 | -------------------------------------------------------------------------------- /rmiieth.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.associations": { 9 | "irq.h": "c", 10 | "etharp.h": "c", 11 | "sem.h": "c", 12 | "lock_core.h": "c", 13 | "pio.pio.h": "c", 14 | "assert.h": "c", 15 | "dma.h": "c" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /rmiieth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #ifndef RMIIETH_H 6 | #define RMIIETH_H 7 | 8 | #include 9 | #include "pico/stdlib.h" 10 | #include "hardware/pio.h" 11 | #include "hardware/sync.h" 12 | #include "pkt_queue.h" 13 | 14 | typedef struct 15 | { 16 | // initial config 17 | PIO pio; // which PIO to use 18 | int pin_mdc; // MD clock pin 19 | int pin_mdio; // MD data pin 20 | int sleep_us; // time to sleep between MD bits 21 | uint32_t phy_addr; // phy_addr - or use rmiieth_probe() to auto-probe 22 | int pin_clk; // refclk input pin 23 | int pin_tx_base; // TX0 (TX1 must be adjacent) 24 | int pin_tx_valid; // TX_EN 25 | int pin_rx_base; // RX0 (RX1 must be adjacent) 26 | int pin_rx_valid; // CRS 27 | int rx_dma_chan; // RX dma channel id 28 | int tx_dma_chan; // TX dma channel id 29 | int rx_lock_id; // spinlock id - leave as -1 to auto-allocate 30 | uint8_t* rx_queue_buffer; // either pass in a buffer, or NULL to have rmiieth_init() malloc one 31 | int32_t rx_queue_buffer_size; // RX queue size 32 | uint8_t* tx_queue_buffer; // either pass in a buffer, or NULL to have rmiieth_init() malloc one 33 | int32_t tx_queue_buffer_size; // TX queue size 34 | int rx_irq; // RX IRQ number 35 | int mtu; // max packet size 36 | 37 | // state 38 | uint8_t clk_offset; 39 | pio_sm_config clk_config; 40 | int clk_sm; 41 | uint8_t rx_offset; 42 | pio_sm_config rx_config; 43 | int rx_sm; 44 | uint8_t tx_offset; 45 | pio_sm_config tx_config; 46 | int tx_sm; 47 | spin_lock_t* rx_lock; // spinlock for accessing RX queue 48 | pkt_queue rx_queue; // the RX queue 49 | pkt_queue_pkt* rx_current_pkt; // RX packet currently being received (NULL if stalled) 50 | pkt_queue tx_queue; // the TX queue 51 | pkt_queue_pkt* tx_current_pkt; // TX packet currently being transmitted 52 | pkt_queue_pkt* tx_current_alloc_pkt; // TX packet currently allocated 53 | 54 | } rmiieth_config; 55 | 56 | 57 | 58 | extern void rmiieth_set_default_config( rmiieth_config* cfg ); 59 | extern bool rmiieth_probe( rmiieth_config* cfg ); 60 | extern void rmiieth_init( rmiieth_config* cfg ); 61 | extern void rmiieth_poll( rmiieth_config* cfg ); 62 | extern bool rmiieth_rx_packet_available( rmiieth_config* cfg ); 63 | extern bool rmiieth_rx_get_packet( rmiieth_config* cfg, uint8_t** pkt, int* length ); 64 | extern void rmiieth_rx_consume_packet( rmiieth_config* cfg ); 65 | extern bool rmiieth_tx_alloc_packet( rmiieth_config* cfg, int length, uint8_t** data ); 66 | extern bool rmiieth_tx_commit_packet( rmiieth_config* cfg, int length ); 67 | 68 | 69 | 70 | #endif -------------------------------------------------------------------------------- /rmiieth_md.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #include "rmiieth_md.h" 6 | 7 | 8 | #define RMII_ST ( 1 << 30 ) 9 | #define RMII_OP_READ ( 2 << 28 ) 10 | #define RMII_OP_WRITE ( 1 << 28 ) 11 | #define RMII_PHY_SHIFT ( 23) 12 | #define RMII_REG_SHIFT ( 18) 13 | #define RMII_TA_SHIFT ( 16) 14 | #define RMII_TA ( 2 << RMII_TA_SHIFT ) 15 | 16 | 17 | 18 | 19 | 20 | void rmiieth_md_init( rmiieth_config* cfg ) 21 | { 22 | // setup rpio for md interface 23 | gpio_init( cfg->pin_mdc ); 24 | gpio_put( cfg->pin_mdc, 1 ); 25 | gpio_set_dir( cfg->pin_mdc, GPIO_OUT ); 26 | gpio_init( cfg->pin_mdio ); 27 | gpio_set_dir( cfg->pin_mdio, GPIO_IN ); 28 | } 29 | 30 | void RMII_MDIO_startRead( rmiieth_config* cfg ) 31 | { 32 | gpio_set_dir( cfg->pin_mdio, GPIO_IN ); 33 | } 34 | 35 | void RMII_MDIO_startWrite( rmiieth_config* cfg ) 36 | { 37 | gpio_set_dir( cfg->pin_mdio, GPIO_OUT ); 38 | } 39 | 40 | void RMII_MDIO_writeBit( rmiieth_config* cfg, int bit ) 41 | { 42 | gpio_put( cfg->pin_mdc, 0 ); 43 | gpio_put( cfg->pin_mdio, bit ); 44 | sleep_us( cfg->sleep_us ); 45 | gpio_put( cfg->pin_mdc, 1 ); 46 | sleep_us( cfg->sleep_us ); 47 | } 48 | 49 | int RMII_MDIO_readBit( rmiieth_config* cfg ) 50 | { 51 | int bit; 52 | gpio_put( cfg->pin_mdc, 0 ); 53 | sleep_us( cfg->sleep_us ); 54 | bit = gpio_get( cfg->pin_mdio ); 55 | gpio_put( cfg->pin_mdc, 1 ); 56 | sleep_us( cfg->sleep_us ); 57 | return( bit ); 58 | } 59 | 60 | void RMII_MDIO_writeBits( rmiieth_config* cfg, uint32_t v, int bitCt ) 61 | { 62 | for( int i = 0 ; i < bitCt ; i++ ) 63 | { 64 | RMII_MDIO_writeBit( cfg, ( v >> ( bitCt - 1 ) ) & 1 ); 65 | v <<= 1; 66 | } 67 | } 68 | 69 | uint32_t RMII_MDIO_readBits( rmiieth_config* cfg, int bitCt ) 70 | { 71 | uint32_t v = 0; 72 | for( int i = 0 ; i < bitCt ; i++ ) 73 | { 74 | v <<= 1; 75 | v |= RMII_MDIO_readBit( cfg ); 76 | } 77 | return( v ); 78 | } 79 | 80 | uint32_t rmiieth_md_readreg( rmiieth_config* cfg, uint32_t regAddr ) 81 | { 82 | uint32_t v; 83 | RMII_MDIO_startWrite( cfg ); 84 | RMII_MDIO_writeBits( cfg, 0xffffffff, 32 ); 85 | v = RMII_ST | RMII_OP_READ | ( cfg->phy_addr << RMII_PHY_SHIFT ) | ( regAddr << RMII_REG_SHIFT ); 86 | v >>= 18; 87 | RMII_MDIO_writeBits( cfg, v, 14 ); 88 | RMII_MDIO_startRead( cfg ); 89 | RMII_MDIO_readBits( cfg, 2 ); 90 | v = RMII_MDIO_readBits( cfg, 16 ); 91 | return( v ); 92 | } 93 | 94 | void rmiieth_md_writereg( rmiieth_config* cfg, uint32_t regAddr, uint32_t regVal ) 95 | { 96 | uint32_t v; 97 | RMII_MDIO_startWrite( cfg ); 98 | RMII_MDIO_writeBits( cfg, 0xffffffff, 32 ); 99 | v = RMII_ST | RMII_OP_WRITE | ( cfg->phy_addr << RMII_PHY_SHIFT ) | ( regAddr << RMII_REG_SHIFT ) | RMII_TA | regVal; 100 | RMII_MDIO_writeBits( cfg, v, 32 ); 101 | } 102 | 103 | 104 | void rmiieth_md_reset( rmiieth_config* cfg ) 105 | { 106 | uint32_t v = rmiieth_md_readreg( cfg, RMII_REG_BASIC_CONTROL ); 107 | v |= 0x80000000; 108 | rmiieth_md_writereg( cfg, RMII_REG_BASIC_CONTROL, v ); 109 | } 110 | -------------------------------------------------------------------------------- /rmiieth_md.h: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2021 Ben Stragnell 3 | */ 4 | 5 | #ifndef RMIIETH_MD_H 6 | #define RMIIETH_MD_H 7 | 8 | #include "rmiieth.h" 9 | 10 | #define RMII_REG_BASIC_CONTROL ( 0 ) 11 | #define RMII_REG_BASIC_STATUS ( 1 ) 12 | #define RMII_REG_PHY_ID1 ( 2 ) 13 | #define RMII_REG_PHY_ID2 ( 3 ) 14 | #define RMII_REG_AUTONEG_ADVERT ( 4 ) 15 | #define RMII_REG_AUTONEG_LP_ABILITY ( 5 ) 16 | #define RMII_REG_AUTONEG_EXP ( 6 ) 17 | #define RMII_REG_MODE_CS ( 17 ) 18 | #define RMII_REG_SPECIAL_MODES ( 18 ) 19 | #define RMII_REG_SYMBOL_ERR_CTR ( 26 ) 20 | #define RMII_REG_CS_INDICATION ( 27 ) 21 | #define RMII_REG_INT_SRC ( 29 ) 22 | #define RMII_REG_INT_MSK ( 30 ) 23 | #define RMII_REG_PHY_SPECIAL_CS ( 31 ) 24 | 25 | 26 | extern void rmiieth_md_init( rmiieth_config* cfg ); 27 | extern uint32_t rmiieth_md_readreg( rmiieth_config* cfg, uint32_t regAddr ); 28 | extern void rmiieth_md_writereg( rmiieth_config* cfg, uint32_t regAddr, uint32_t regVal ); 29 | extern void rmiieth_md_reset( rmiieth_config* cfg ); 30 | 31 | 32 | #endif 33 | 34 | --------------------------------------------------------------------------------