/* * Copyright (C) 2015, Intel Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "contiki-net.h" #include "dma.h" #include "eth.h" #include "helpers.h" #include "syscalls.h" #include "net/ip/uip.h" #include "pci.h" typedef pci_driver_t quarkX1000_eth_driver_t; /* Refer to Intel Quark SoC X1000 Datasheet, Chapter 15 for more details on * Ethernet device operation. * * This driver puts the Ethernet device into a very simple and space-efficient * mode of operation. It only allocates a single packet descriptor for each of * the transmit and receive directions, computes checksums on the CPU, and * enables store-and-forward mode for both transmit and receive directions. */ /* Transmit descriptor */ typedef struct quarkX1000_eth_tx_desc { /* First word of transmit descriptor */ union { struct { /* Only valid in half-duplex mode. */ uint32_t deferred_bit : 1; uint32_t err_underflow : 1; uint32_t err_excess_defer : 1; uint32_t coll_cnt_slot_num : 4; uint32_t vlan_frm : 1; uint32_t err_excess_coll : 1; uint32_t err_late_coll : 1; uint32_t err_no_carrier : 1; uint32_t err_carrier_loss : 1; uint32_t err_ip_payload : 1; uint32_t err_frm_flushed : 1; uint32_t err_jabber_tout : 1; /* OR of all other error bits. */ uint32_t err_summary : 1; uint32_t err_ip_hdr : 1; uint32_t tx_timestamp_stat : 1; uint32_t vlan_ins_ctrl : 2; uint32_t addr2_chained : 1; uint32_t tx_end_of_ring : 1; uint32_t chksum_ins_ctrl : 2; uint32_t replace_crc : 1; uint32_t tx_timestamp_en : 1; uint32_t dis_pad : 1; uint32_t dis_crc : 1; uint32_t first_seg_in_frm : 1; uint32_t last_seg_in_frm : 1; uint32_t intr_on_complete : 1; /* When set, descriptor is owned by DMA. */ uint32_t own : 1; }; uint32_t tdes0; }; /* Second word of transmit descriptor */ union { struct { uint32_t tx_buf1_sz : 13; uint32_t : 3; uint32_t tx_buf2_sz : 13; uint32_t src_addr_ins_ctrl : 3; }; uint32_t tdes1; }; /* Pointer to frame data buffer */ uint8_t *buf1_ptr; /* Unused, since this driver initializes only a single descriptor for each * direction. */ uint8_t *buf2_ptr; } quarkX1000_eth_tx_desc_t; /* Transmit descriptor */ typedef struct quarkX1000_eth_rx_desc { /* First word of receive descriptor */ union { struct { uint32_t ext_stat : 1; uint32_t err_crc : 1; uint32_t err_dribble_bit : 1; uint32_t err_rx_mii : 1; uint32_t err_rx_wdt : 1; uint32_t frm_type : 1; uint32_t err_late_coll : 1; uint32_t giant_frm : 1; uint32_t last_desc : 1; uint32_t first_desc : 1; uint32_t vlan_tag : 1; uint32_t err_overflow : 1; uint32_t length_err : 1; uint32_t s_addr_filt_fail : 1; uint32_t err_desc : 1; uint32_t err_summary : 1; uint32_t frm_len : 14; uint32_t d_addr_filt_fail : 1; uint32_t own : 1; }; uint32_t rdes0; }; /* Second word of receive descriptor */ union { struct { uint32_t rx_buf1_sz : 13; uint32_t : 1; uint32_t addr2_chained : 1; uint32_t rx_end_of_ring : 1; uint32_t rx_buf2_sz : 13; uint32_t : 2; uint32_t dis_int_compl : 1; }; uint32_t rdes1; }; /* Pointer to frame data buffer */ uint8_t *buf1_ptr; /* Unused, since this driver initializes only a single descriptor for each * direction. */ uint8_t *buf2_ptr; } quarkX1000_eth_rx_desc_t; /* Driver metadata associated with each Ethernet device */ typedef struct quarkX1000_eth_meta { /* Transmit descriptor */ volatile quarkX1000_eth_tx_desc_t tx_desc; /* Transmit DMA packet buffer */ volatile uint8_t tx_buf[ALIGN(UIP_BUFSIZE, 4)]; /* Receive descriptor */ volatile quarkX1000_eth_rx_desc_t rx_desc; /* Receive DMA packet buffer */ volatile uint8_t rx_buf[ALIGN(UIP_BUFSIZE, 4)]; #if X86_CONF_PROT_DOMAINS == X86_CONF_PROT_DOMAINS__PAGING /* Domain-defined metadata must fill an even number of pages, since that is * the minimum granularity of access control supported by paging. However, * using the "aligned(4096)" attribute causes the alignment of the kernel * data section to increase, which causes problems when generating UEFI * binaries, as is described in the linker script. Thus, it is necessary * to manually pad the structure to fill a page. This only works if the * sizes of the actual fields of the structure are collectively less than a * page. */ uint8_t pad[MIN_PAGE_SIZE - (sizeof(quarkX1000_eth_tx_desc_t) + ALIGN(UIP_BUFSIZE, 4) + sizeof(quarkX1000_eth_rx_desc_t) + ALIGN(UIP_BUFSIZE, 4))]; #endif } __attribute__((packed)) quarkX1000_eth_meta_t; #define LOG_PFX "quarkX1000_eth: " #define MMIO_SZ 0x2000 #define MAC_CONF_14_RMII_100M BIT(14) #define MAC_CONF_11_DUPLEX BIT(11) #define MAC_CONF_3_TX_EN BIT(3) #define MAC_CONF_2_RX_EN BIT(2) #define OP_MODE_25_RX_STORE_N_FORWARD BIT(25) #define OP_MODE_21_TX_STORE_N_FORWARD BIT(21) #define OP_MODE_13_START_TX BIT(13) #define OP_MODE_1_START_RX BIT(1) #define REG_ADDR_MAC_CONF 0x0000 #define REG_ADDR_MACADDR_HI 0x0040 #define REG_ADDR_MACADDR_LO 0x0044 #define REG_ADDR_TX_POLL_DEMAND 0x1004 #define REG_ADDR_RX_POLL_DEMAND 0x1008 #define REG_ADDR_RX_DESC_LIST 0x100C #define REG_ADDR_TX_DESC_LIST 0x1010 #define REG_ADDR_DMA_OPERATION 0x1018 PROT_DOMAINS_ALLOC(quarkX1000_eth_driver_t, drv); static quarkX1000_eth_meta_t ATTR_BSS_DMA meta; void quarkX1000_eth_setup(uintptr_t meta_phys_base); /*---------------------------------------------------------------------------*/ SYSCALLS_DEFINE_SINGLETON(quarkX1000_eth_setup, drv, uintptr_t meta_phys_base) { uip_eth_addr mac_addr; uint32_t mac_tmp1, mac_tmp2; quarkX1000_eth_rx_desc_t rx_desc; quarkX1000_eth_tx_desc_t tx_desc; quarkX1000_eth_meta_t ATTR_META_ADDR_SPACE *loc_meta = (quarkX1000_eth_meta_t ATTR_META_ADDR_SPACE *)PROT_DOMAINS_META(drv); prot_domains_enable_mmio(); /* Read the MAC address from the device. */ PCI_MMIO_READL(drv, mac_tmp1, REG_ADDR_MACADDR_HI); PCI_MMIO_READL(drv, mac_tmp2, REG_ADDR_MACADDR_LO); prot_domains_disable_mmio(); /* Convert the data read from the device into the format expected by * Contiki. */ mac_addr.addr[5] = (uint8_t)(mac_tmp1 >> 8); mac_addr.addr[4] = (uint8_t)mac_tmp1; mac_addr.addr[3] = (uint8_t)(mac_tmp2 >> 24); mac_addr.addr[2] = (uint8_t)(mac_tmp2 >> 16); mac_addr.addr[1] = (uint8_t)(mac_tmp2 >> 8); mac_addr.addr[0] = (uint8_t)mac_tmp2; printf(LOG_PFX "MAC address = %02x:%02x:%02x:%02x:%02x:%02x.\n", mac_addr.addr[0], mac_addr.addr[1], mac_addr.addr[2], mac_addr.addr[3], mac_addr.addr[4], mac_addr.addr[5] ); uip_setethaddr(mac_addr); /* Initialize transmit descriptor. */ tx_desc.tdes0 = 0; tx_desc.tdes1 = 0; tx_desc.tx_end_of_ring = 1; tx_desc.first_seg_in_frm = 1; tx_desc.last_seg_in_frm = 1; tx_desc.tx_end_of_ring = 1; META_WRITEL(loc_meta->tx_desc.tdes0, tx_desc.tdes0); META_WRITEL(loc_meta->tx_desc.tdes1, tx_desc.tdes1); META_WRITEL(loc_meta->tx_desc.buf1_ptr, (uint8_t *)PROT_DOMAINS_META_OFF_TO_PHYS( (uintptr_t)&loc_meta->tx_buf, meta_phys_base)); META_WRITEL(loc_meta->tx_desc.buf2_ptr, 0); /* Initialize receive descriptor. */ rx_desc.rdes0 = 0; rx_desc.rdes1 = 0; rx_desc.own = 1; rx_desc.first_desc = 1; rx_desc.last_desc = 1; rx_desc.rx_buf1_sz = UIP_BUFSIZE; rx_desc.rx_end_of_ring = 1; META_WRITEL(loc_meta->rx_desc.rdes0, rx_desc.rdes0); META_WRITEL(loc_meta->rx_desc.rdes1, rx_desc.rdes1); META_WRITEL(loc_meta->rx_desc.buf1_ptr, (uint8_t *)PROT_DOMAINS_META_OFF_TO_PHYS( (uintptr_t)&loc_meta->rx_buf, meta_phys_base)); META_WRITEL(loc_meta->rx_desc.buf2_ptr, 0); prot_domains_enable_mmio(); /* Install transmit and receive descriptors. */ PCI_MMIO_WRITEL(drv, REG_ADDR_RX_DESC_LIST, PROT_DOMAINS_META_OFF_TO_PHYS( (uintptr_t)&loc_meta->rx_desc, meta_phys_base)); PCI_MMIO_WRITEL(drv, REG_ADDR_TX_DESC_LIST, PROT_DOMAINS_META_OFF_TO_PHYS( (uintptr_t)&loc_meta->tx_desc, meta_phys_base)); PCI_MMIO_WRITEL(drv, REG_ADDR_MAC_CONF, /* Set the RMII speed to 100Mbps */ MAC_CONF_14_RMII_100M | /* Enable full-duplex mode */ MAC_CONF_11_DUPLEX | /* Enable transmitter */ MAC_CONF_3_TX_EN | /* Enable receiver */ MAC_CONF_2_RX_EN); PCI_MMIO_WRITEL(drv, REG_ADDR_DMA_OPERATION, /* Enable receive store-and-forward mode for simplicity. */ OP_MODE_25_RX_STORE_N_FORWARD | /* Enable transmit store-and-forward mode for simplicity. */ OP_MODE_21_TX_STORE_N_FORWARD | /* Place the transmitter state machine in the Running state. */ OP_MODE_13_START_TX | /* Place the receiver state machine in the Running state. */ OP_MODE_1_START_RX); prot_domains_disable_mmio(); printf(LOG_PFX "Enabled 100M full-duplex mode.\n"); } /*---------------------------------------------------------------------------*/ /** * \brief Poll for a received Ethernet frame. * \param frame_len Will be set to the size of the received Ethernet frame or * zero if no frame is available. * * If a frame is received, this procedure copies it into the * global uip_buf buffer. */ SYSCALLS_DEFINE_SINGLETON(quarkX1000_eth_poll, drv, uint16_t * frame_len) { uint16_t *loc_frame_len; uint16_t frm_len = 0; quarkX1000_eth_rx_desc_t tmp_desc; quarkX1000_eth_meta_t ATTR_META_ADDR_SPACE *loc_meta = (quarkX1000_eth_meta_t ATTR_META_ADDR_SPACE *)PROT_DOMAINS_META(drv); PROT_DOMAINS_VALIDATE_PTR(loc_frame_len, frame_len, sizeof(*frame_len)); META_READL(tmp_desc.rdes0, loc_meta->rx_desc.rdes0); /* Check whether the RX descriptor is still owned by the device. If not, * process the received frame or an error that may have occurred. */ if(tmp_desc.own == 0) { META_READL(tmp_desc.rdes1, loc_meta->rx_desc.rdes1); if(tmp_desc.err_summary) { fprintf(stderr, LOG_PFX "Error receiving frame: RDES0 = %08x, RDES1 = %08x.\n", tmp_desc.rdes0, tmp_desc.rdes1); assert(0); } frm_len = tmp_desc.frm_len; assert(frm_len <= UIP_BUFSIZE); MEMCPY_FROM_META(uip_buf, loc_meta->rx_buf, frm_len); /* Return ownership of the RX descriptor to the device. */ tmp_desc.own = 1; META_WRITEL(loc_meta->rx_desc.rdes0, tmp_desc.rdes0); prot_domains_enable_mmio(); /* Request that the device check for an available RX descriptor, since * ownership of the descriptor was just transferred to the device. */ PCI_MMIO_WRITEL(drv, REG_ADDR_RX_POLL_DEMAND, 1); prot_domains_disable_mmio(); } *loc_frame_len = frm_len; } /*---------------------------------------------------------------------------*/ /** * \brief Transmit the current Ethernet frame. * * This procedure will block indefinitely until the Ethernet device is * ready to accept a new outgoing frame. It then copies the current * Ethernet frame from the global uip_buf buffer to the device DMA * buffer and signals to the device that a new frame is available to be * transmitted. */ SYSCALLS_DEFINE_SINGLETON(quarkX1000_eth_send, drv) { quarkX1000_eth_tx_desc_t tmp_desc; quarkX1000_eth_meta_t ATTR_META_ADDR_SPACE *loc_meta = (quarkX1000_eth_meta_t ATTR_META_ADDR_SPACE *)PROT_DOMAINS_META(drv); /* Wait until the TX descriptor is no longer owned by the device. */ do { META_READL(tmp_desc.tdes0, loc_meta->tx_desc.tdes0); } while(tmp_desc.own == 1); META_READL(tmp_desc.tdes1, loc_meta->tx_desc.tdes1); /* Check whether an error occurred transmitting the previous frame. */ if(tmp_desc.err_summary) { fprintf(stderr, LOG_PFX "Error transmitting frame: TDES0 = %08x, TDES1 = %08x.\n", tmp_desc.tdes0, tmp_desc.tdes1); assert(0); } /* Transmit the next frame. */ assert(uip_len <= UIP_BUFSIZE); MEMCPY_TO_META(loc_meta->tx_buf, uip_buf, uip_len); tmp_desc.tx_buf1_sz = uip_len; META_WRITEL(loc_meta->tx_desc.tdes1, tmp_desc.tdes1); tmp_desc.own = 1; META_WRITEL(loc_meta->tx_desc.tdes0, tmp_desc.tdes0); prot_domains_enable_mmio(); /* Request that the device check for an available TX descriptor, since * ownership of the descriptor was just transferred to the device. */ PCI_MMIO_WRITEL(drv, REG_ADDR_TX_POLL_DEMAND, 1); prot_domains_disable_mmio(); } /*---------------------------------------------------------------------------*/ /** * \brief Initialize the first Quark X1000 Ethernet MAC. * * This procedure assumes that an MMIO range for the device was * previously assigned, e.g. by firmware. */ void quarkX1000_eth_init(void) { pci_config_addr_t pci_addr = { .raw = 0 }; /* PCI address from section 15.4 of Intel Quark SoC X1000 Datasheet. */ pci_addr.dev = 20; pci_addr.func = 6; /* Activate MMIO and DMA access. */ pci_command_enable(pci_addr, PCI_CMD_2_BUS_MST_EN | PCI_CMD_1_MEM_SPACE_EN); printf(LOG_PFX "Activated MMIO and DMA access.\n"); pci_addr.reg_off = PCI_CONFIG_REG_BAR0; PROT_DOMAINS_INIT_ID(drv); /* Configure the device MMIO range and initialize the driver structure. */ pci_init(&drv, pci_addr, MMIO_SZ, (uintptr_t)&meta, sizeof(quarkX1000_eth_meta_t)); SYSCALLS_INIT(quarkX1000_eth_setup); SYSCALLS_AUTHZ(quarkX1000_eth_setup, drv); SYSCALLS_INIT(quarkX1000_eth_poll); SYSCALLS_AUTHZ(quarkX1000_eth_poll, drv); SYSCALLS_INIT(quarkX1000_eth_send); SYSCALLS_AUTHZ(quarkX1000_eth_send, drv); quarkX1000_eth_setup(prot_domains_lookup_meta_phys_base(&drv)); } /*---------------------------------------------------------------------------*/