| /* |
| * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: GPL-2.0-only |
| */ |
| |
| #include <autoconf.h> |
| #include <stdbool.h> |
| #include <string.h> |
| |
| #include <camkes.h> |
| #include <camkes/dma.h> |
| #include <camkes/io.h> |
| #include <camkes/irq.h> |
| |
| #include <platsupport/io.h> |
| #include <platsupport/irq.h> |
| #include <ethdrivers/raw.h> |
| #include <ethdrivers/intel.h> |
| #include <sel4utils/sel4_zf_logif.h> |
| |
| #define RX_BUFS 256 |
| |
| #define CLIENT_RX_BUFS 128 |
| #define CLIENT_TX_BUFS 128 |
| |
| #define BUF_SIZE 2048 |
| |
| |
| static struct eth_driver *eth_driver; |
| |
| |
| /* |
| *Struct eth_buf contains a virtual address (buf) to use for memory operations |
| *at the picoserver level and a physical address to be passed down to the |
| *driver. |
| */ |
| typedef struct eth_buf { |
| void *buf; |
| uintptr_t phys; |
| } eth_buf_t; |
| |
| typedef struct rx_frame { |
| eth_buf_t *buf; // Clients share a pool of RX frames |
| int len; |
| int client; |
| } rx_frame_t; |
| |
| typedef struct tx_frame { |
| eth_buf_t buf; // Each client has a pool of TX frames |
| int len; |
| int client; |
| } tx_frame_t; |
| |
| typedef struct client { |
| /* this flag indicates whether we or not we need to notify the client |
| * if new data is received. We only notify once the client observes |
| * the last packet */ |
| int should_notify; |
| |
| /* keeps track of the head of the queue */ |
| int pending_rx_head; |
| /* keeps track of the tail of the queue */ |
| int pending_rx_tail; |
| /* |
| * this is a cyclic queue of RX buffers pending to be read by a client, |
| * the head represents the first buffer in the queue, and the tail the last |
| */ |
| rx_frame_t pending_rx[CLIENT_RX_BUFS]; |
| |
| /* keeps track of how many TX buffers are in use */ |
| int num_tx; |
| /* the allocated TX buffers for the client */ |
| tx_frame_t tx_mem[CLIENT_TX_BUFS]; |
| /* |
| * this represents the pool of buffers that can be used for TX, |
| * this array is a sliding array in that num_tx acts a pointer to |
| * separate between buffers that are in use and buffers that are |
| * not in use. E.g. 'o' = free, 'x' = in use |
| * ------------------------------------- |
| * | o | o | o | o | o | o | x | x | x | |
| * ------------------------------------- |
| * ^ |
| * num_tx |
| */ |
| tx_frame_t *pending_tx[CLIENT_TX_BUFS]; |
| |
| /* mac address for this client */ |
| uint8_t mac[6]; |
| |
| /* Badge for this client */ |
| seL4_Word client_id; |
| |
| /* dataport for this client */ |
| void *dataport; |
| } client_t; |
| |
| static int num_clients = 0; |
| static client_t *clients = NULL; |
| |
| static int num_rx_bufs; |
| static eth_buf_t rx_bufs[RX_BUFS]; |
| static eth_buf_t *rx_buf_pool[RX_BUFS]; |
| |
| static int done_init = 0; |
| |
| /* Functions provided by the Ethdriver template */ |
| void client_emit(unsigned int client_id); |
| unsigned int client_get_sender_id(void); |
| unsigned int client_num_badges(void); |
| unsigned int client_enumerate_badge(unsigned int i); |
| void *client_buf(unsigned int client_id); |
| bool client_has_mac(unsigned int client_id); |
| void client_get_mac(unsigned int client_id, uint8_t *mac); |
| |
| |
| static void eth_tx_complete(void *iface, void *cookie) |
| { |
| tx_frame_t *buf = (tx_frame_t *)cookie; |
| client_t *client = &clients[buf->client]; |
| client->pending_tx[client->num_tx] = buf; |
| client->num_tx++; |
| } |
| |
| static uintptr_t eth_allocate_rx_buf(void *iface, size_t buf_size, void **cookie) |
| { |
| if (buf_size > BUF_SIZE) { |
| return 0; |
| } |
| if (num_rx_bufs == 0) { |
| return 0; |
| } |
| num_rx_bufs--; |
| *cookie = rx_buf_pool[num_rx_bufs]; |
| return rx_buf_pool[num_rx_bufs]->phys; |
| } |
| |
| static client_t *detect_client(void *buf, unsigned int len) |
| { |
| if (len < 6) { |
| return NULL; |
| } |
| for (int i = 0; i < num_clients; i++) { |
| if (memcmp(clients[i].mac, buf, 6) == 0) { |
| return &clients[i]; |
| } |
| } |
| return NULL; |
| } |
| |
| static int is_broadcast(void *buf, unsigned int len) |
| { |
| static uint8_t broadcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
| if (len < 6) { |
| return 0; |
| } |
| if (memcmp(buf, broadcast, 6) == 0) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int is_multicast(void *buf, unsigned int len) |
| { |
| // the dest address is in the IP header (16 bytes in), which is located after the |
| // ethernet header. the dest address itself is a standard 4byte IP address |
| const int eth_header_len = 14; |
| const int ip_hdr_dest_offset = 16; |
| if (len < eth_header_len + ip_hdr_dest_offset + 4) { |
| return 0; |
| } |
| // read out a copy of the IP address so that it is correctly aligned |
| uint32_t addr; |
| // TODO Find out why ARM memcpy faults on unaligned addresses |
| //memcpy(&addr, ((uintptr_t)buf) + eth_header_len + ip_hdr_dest_offset, 4); |
| for (int i = 0; i < 4; i++) { |
| ((char *)&addr)[i] = ((char *)(buf + eth_header_len + ip_hdr_dest_offset))[i]; |
| } |
| // multicast addresses start with bit pattern 1110, which after accounting for |
| // network byte ordering is 0xe0 |
| if ((addr & 0xf0) == 0xe0) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void give_client_buf(client_t *client, eth_buf_t *buf, unsigned int len) |
| { |
| client->pending_rx[client->pending_rx_head] = (rx_frame_t) { |
| buf, len, 0 |
| }; |
| client->pending_rx_head = (client->pending_rx_head + 1) % CLIENT_RX_BUFS; |
| if (client->should_notify) { |
| client_emit(client->client_id); |
| client->should_notify = 0; |
| } |
| } |
| |
| static void eth_rx_complete(void *iface, unsigned int num_bufs, void **cookies, unsigned int *lens) |
| { |
| /* insert filtering here. currently everything just goes to one client */ |
| if (num_bufs != 1) { |
| goto error; |
| } |
| eth_buf_t *curr_buf = cookies[0]; |
| client_t *client = detect_client(curr_buf->buf, lens[0]); |
| if (!client) { |
| if (is_broadcast(curr_buf->buf, lens[0]) || is_multicast(curr_buf->buf, lens[0])) { |
| /* in a broadcast duplicate this buffer for every other client, we will fallthrough |
| * to give the buffer to client 0 at the end */ |
| for (int i = 1; i < num_clients; i++) { |
| client = &clients[i]; |
| if ((client->pending_rx_head + 1) % CLIENT_RX_BUFS != client->pending_rx_tail) { |
| void *cookie; |
| uintptr_t phys = eth_allocate_rx_buf(iface, lens[0], &cookie); |
| eth_buf_t *new_buf = cookie; |
| if (phys) { |
| memcpy(new_buf->buf, curr_buf->buf, lens[0]); |
| give_client_buf(client, new_buf, lens[0]); |
| } |
| } |
| } |
| } else { |
| goto error; |
| } |
| client = &clients[0]; |
| } |
| if ((client->pending_rx_head + 1) % CLIENT_RX_BUFS == client->pending_rx_tail) { |
| goto error; |
| } |
| give_client_buf(client, curr_buf, lens[0]); |
| return; |
| error: |
| /* abort and put all the bufs back */ |
| for (int i = 0; i < num_bufs; i++) { |
| eth_buf_t *returned_buf = cookies[i]; |
| rx_buf_pool[num_rx_bufs] = returned_buf; |
| num_rx_bufs++; |
| } |
| } |
| |
| static struct raw_iface_callbacks ethdriver_callbacks = { |
| .tx_complete = eth_tx_complete, |
| .rx_complete = eth_rx_complete, |
| .allocate_rx_buf = eth_allocate_rx_buf |
| }; |
| |
| /** If eth frames have been received by the driver, copy a single frame from |
| * the driver's buffer (rx_bufs), into the dataport of the caller of this |
| * function. |
| * |
| * @param[out] len The size in bytes of the eth frame. |
| * @return If there are no frames available to be consumed, returns negative. |
| * If there was an error or the component hasn't been initialized yet, |
| * returns negative. |
| * If there was only one frame available to be consumed, returns 0. |
| * If there are other frames to be consumed even after the one that |
| * will be returned by the current invocation, returns 1 (i.e, "there |
| * is more data."). |
| */ |
| int client_rx(int *len) |
| { |
| if (!done_init) { |
| return -1; |
| } |
| int ret; |
| int id = client_get_sender_id(); |
| client_t *client = NULL; |
| for (int i = 0; i < num_clients; i++) { |
| if (clients[i].client_id == id) { |
| client = &clients[i]; |
| } |
| } |
| assert(client); |
| void *packet = client->dataport; |
| if (client->pending_rx_head == client->pending_rx_tail) { |
| client->should_notify = 1; |
| return -1; |
| } |
| rx_frame_t rx = client->pending_rx[client->pending_rx_tail]; |
| client->pending_rx_tail = (client->pending_rx_tail + 1) % CLIENT_RX_BUFS; |
| memcpy(packet, rx.buf->buf, rx.len); |
| *len = rx.len; |
| if (client->pending_rx_tail == client->pending_rx_head) { |
| client->should_notify = 1; |
| ret = 0; |
| } else { |
| ret = 1; |
| } |
| rx_buf_pool[num_rx_bufs] = rx.buf; |
| num_rx_bufs++; |
| return ret; |
| } |
| |
| int client_tx(int len) |
| { |
| if (!done_init) { |
| return -1; |
| } |
| if (len > BUF_SIZE) { |
| len = BUF_SIZE; |
| } |
| if (len < 12) { |
| return -1; |
| } |
| int err = ETHIF_TX_COMPLETE; |
| int id = client_get_sender_id(); |
| client_t *client = NULL; |
| for (int i = 0; i < num_clients; i++) { |
| if (clients[i].client_id == id) { |
| client = &clients[i]; |
| } |
| } |
| assert(client); |
| void *packet = client->dataport; |
| /* silently drop packets */ |
| if (client->num_tx != 0) { |
| client->num_tx --; |
| tx_frame_t *tx_buf = client->pending_tx[client->num_tx]; |
| |
| /* copy the packet over */ |
| memcpy(tx_buf->buf.buf, packet, len); |
| |
| /* On ARM memory mapped as uncached does not support unaligned access, copy it in manually. |
| * For more information read compiler flags -munaligned-access -mno-unaligned-access. |
| */ |
| for (int i = 0; i < 6; i++) { |
| ((char *)(tx_buf->buf.buf + 6))[i] = ((char *)client->mac)[i]; |
| } |
| |
| /* queue up transmit */ |
| err = eth_driver->i_fn.raw_tx(eth_driver, 1, (uintptr_t *) & (tx_buf->buf.phys), |
| (unsigned int *)&len, tx_buf); |
| if (err != ETHIF_TX_ENQUEUED) { |
| /* Free the internal tx buffer in case tx fails. Up to the client to retry the trasmission */ |
| client->num_tx++; |
| } |
| } |
| |
| return err; |
| } |
| |
| void client_mac(uint8_t *b1, uint8_t *b2, uint8_t *b3, uint8_t *b4, uint8_t *b5, uint8_t *b6) |
| { |
| int id = client_get_sender_id(); |
| client_t *client = NULL; |
| for (int i = 0; i < num_clients; i++) { |
| if (clients[i].client_id == id) { |
| client = &clients[i]; |
| } |
| } |
| assert(client); |
| assert(done_init); |
| *b1 = client->mac[0]; |
| *b2 = client->mac[1]; |
| *b3 = client->mac[2]; |
| *b4 = client->mac[3]; |
| *b5 = client->mac[4]; |
| *b6 = client->mac[5]; |
| } |
| |
| |
| static int hardware_interface_searcher(void *cookie, void *interface_instance, char **properties) |
| { |
| |
| eth_driver = interface_instance; |
| return PS_INTERFACE_FOUND_MATCH; |
| } |
| |
| |
| int server_init(ps_io_ops_t *io_ops) |
| { |
| |
| int error = ps_interface_find(&io_ops->interface_registration_ops, |
| PS_ETHERNET_INTERFACE, hardware_interface_searcher, NULL); |
| if (error) { |
| ZF_LOGF("Unable to find an ethernet device"); |
| } |
| |
| |
| eth_driver->cb_cookie = NULL; |
| eth_driver->i_cb = ethdriver_callbacks; |
| |
| |
| /* preallocate buffers */ |
| for (int i = 0; i < RX_BUFS; i++) { |
| void *buf = ps_dma_alloc(&io_ops->dma_manager, BUF_SIZE, 4, 1, PS_MEM_NORMAL); |
| assert(buf); |
| memset(buf, 0, BUF_SIZE); |
| uintptr_t phys = ps_dma_pin(&io_ops->dma_manager, buf, BUF_SIZE); |
| rx_bufs[num_rx_bufs] = (eth_buf_t) { |
| .buf = buf, .phys = phys |
| }; |
| rx_buf_pool[num_rx_bufs] = &(rx_bufs[num_rx_bufs]); |
| num_rx_bufs++; |
| } |
| num_clients = client_num_badges(); |
| clients = calloc(num_clients, sizeof(client_t)); |
| for (int client = 0; client < num_clients; client++) { |
| clients[client].should_notify = 1; |
| clients[client].client_id = client_enumerate_badge(client); |
| clients[client].dataport = client_buf(clients[client].client_id); |
| for (int i = 0; i < CLIENT_TX_BUFS; i++) { |
| void *buf = ps_dma_alloc(&io_ops->dma_manager, BUF_SIZE, 4, 1, PS_MEM_NORMAL); |
| assert(buf); |
| memset(buf, 0, BUF_SIZE); |
| uintptr_t phys = ps_dma_pin(&io_ops->dma_manager, buf, BUF_SIZE); |
| tx_frame_t *tx_buf = &clients[client].tx_mem[clients[client].num_tx]; |
| *tx_buf = (tx_frame_t) { |
| .len = BUF_SIZE, .client = client |
| }; |
| tx_buf->buf = (eth_buf_t) { |
| .buf = buf, .phys = phys |
| }; |
| clients[client].pending_tx[clients[client].num_tx] = tx_buf; |
| clients[client].num_tx++; |
| } |
| } |
| |
| |
| uint8_t hw_mac[6]; |
| eth_driver->i_fn.get_mac(eth_driver, hw_mac); |
| eth_driver->i_fn.raw_poll(eth_driver); |
| |
| int num_defaults = 0; |
| for (int client = 0; client < num_clients; client++) { |
| if (client_has_mac(clients[client].client_id)) { |
| client_get_mac(clients[client].client_id, clients[client].mac); |
| } else { |
| ++num_defaults; |
| memcpy(clients[client].mac, hw_mac, 6); |
| ZF_LOGF_IF((num_defaults > 1), "Should not have 2 clients with the same MAC address"); |
| } |
| } |
| |
| done_init = 1; |
| return 0; |
| } |
| |
| CAMKES_POST_INIT_MODULE_DEFINE(ethdriver_run, server_init); |