From abeed93da59cc6d182d1852eb03a174331963f4a Mon Sep 17 00:00:00 2001 From: Michael LeMay Date: Tue, 7 Jul 2015 16:07:00 -0700 Subject: [PATCH] x86: Add driver for Intel Quark X1000 built-in Ethernet This patch adds a simple, space-efficient driver for the Ethernet interface built into the Intel Quark X1000. 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. --- cpu/x86/Makefile.x86_quarkX1000 | 2 +- cpu/x86/drivers/legacy_pc/pci.c | 2 +- cpu/x86/drivers/quarkX1000/eth.c | 372 +++++++++++++++++++++++++++++++ cpu/x86/drivers/quarkX1000/eth.h | 40 ++++ 4 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 cpu/x86/drivers/quarkX1000/eth.c create mode 100644 cpu/x86/drivers/quarkX1000/eth.h diff --git a/cpu/x86/Makefile.x86_quarkX1000 b/cpu/x86/Makefile.x86_quarkX1000 index 8e78fd95e..192fd302b 100644 --- a/cpu/x86/Makefile.x86_quarkX1000 +++ b/cpu/x86/Makefile.x86_quarkX1000 @@ -2,7 +2,7 @@ include $(CONTIKI)/cpu/x86/Makefile.x86_common CONTIKI_CPU_DIRS += drivers/legacy_pc drivers/quarkX1000 init/legacy_pc -CONTIKI_SOURCEFILES += bootstrap_quarkX1000.S rtc.c pit.c pic.c irq.c nmi.c pci.c uart-16x50.c uart.c gpio.c i2c.c +CONTIKI_SOURCEFILES += bootstrap_quarkX1000.S rtc.c pit.c pic.c irq.c nmi.c pci.c uart-16x50.c uart.c gpio.c i2c.c eth.c CFLAGS += -m32 -march=i586 -mtune=i586 LDFLAGS += -m32 -Xlinker -T -Xlinker $(CONTIKI)/cpu/x86/quarkX1000.ld diff --git a/cpu/x86/drivers/legacy_pc/pci.c b/cpu/x86/drivers/legacy_pc/pci.c index 2af9f4883..0507ad231 100644 --- a/cpu/x86/drivers/legacy_pc/pci.c +++ b/cpu/x86/drivers/legacy_pc/pci.c @@ -236,7 +236,7 @@ pci_pirq_set_irq(PIRQ pirq, uint8_t irq, uint8_t route_to_legacy) void pci_init(pci_driver_t *c_this, pci_config_addr_t pci_addr, uintptr_t meta) { - /* The reg_off (BAR) value is masked to clear non-address bits. */ + /* The BAR value is masked to clear non-address bits. */ c_this->mmio = pci_config_read(pci_addr) & ~0xFFF; c_this->meta = meta; } diff --git a/cpu/x86/drivers/quarkX1000/eth.c b/cpu/x86/drivers/quarkX1000/eth.c new file mode 100644 index 000000000..2f02213ea --- /dev/null +++ b/cpu/x86/drivers/quarkX1000/eth.c @@ -0,0 +1,372 @@ +/* + * 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 "eth.h" +#include "helpers.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[UIP_BUFSIZE]; + /* Receive descriptor */ + volatile quarkX1000_eth_rx_desc_t rx_desc; + /* Receive DMA packet buffer */ + volatile uint8_t rx_buf[UIP_BUFSIZE]; +} 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 + +static quarkX1000_eth_driver_t drv; +static quarkX1000_eth_meta_t meta; + +/*---------------------------------------------------------------------------*/ +/** + * \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 }; + uip_eth_addr mac_addr; + uint32_t mac_tmp1, mac_tmp2; + + /* 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; + + /* Configure the device MMIO range and initialize the driver structure. */ + pci_init(&drv, pci_addr, (uintptr_t)&meta); + + /* 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); + + /* 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. */ + meta.tx_desc.tdes0 = 0; + meta.tx_desc.tdes1 = 0; + + meta.tx_desc.buf1_ptr = (uint8_t *)meta.tx_buf; + meta.tx_desc.tx_end_of_ring = 1; + meta.tx_desc.first_seg_in_frm = 1; + meta.tx_desc.last_seg_in_frm = 1; + meta.tx_desc.tx_end_of_ring = 1; + + /* Initialize receive descriptor. */ + meta.rx_desc.rdes0 = 0; + meta.rx_desc.rdes1 = 0; + + meta.rx_desc.buf1_ptr = (uint8_t *)meta.rx_buf; + meta.rx_desc.own = 1; + meta.rx_desc.first_desc = 1; + meta.rx_desc.last_desc = 1; + meta.rx_desc.rx_buf1_sz = UIP_BUFSIZE; + meta.rx_desc.rx_end_of_ring = 1; + + /* Install transmit and receive descriptors. */ + PCI_MMIO_WRITEL(drv, REG_ADDR_RX_DESC_LIST, (uint32_t)&meta.rx_desc); + PCI_MMIO_WRITEL(drv, REG_ADDR_TX_DESC_LIST, (uint32_t)&meta.tx_desc); + + 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); + + 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. + */ +void +quarkX1000_eth_poll(uint16_t *frame_len) +{ + uint16_t frm_len = 0; + + /* 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(meta.rx_desc.own == 0) { + if(meta.rx_desc.err_summary) { + fprintf(stderr, + LOG_PFX "Error receiving frame: RDES0 = %08x, RDES1 = %08x.\n", + meta.rx_desc.rdes0, meta.rx_desc.rdes1); + assert(0); + } + + frm_len = meta.rx_desc.frm_len; + assert(frm_len <= UIP_BUFSIZE); + memcpy(uip_buf, (void *)meta.rx_buf, frm_len); + + /* Return ownership of the RX descriptor to the device. */ + meta.rx_desc.own = 1; + + /* 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); + } + + *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. + */ +void +quarkX1000_eth_send(void) +{ + /* Wait until the TX descriptor is no longer owned by the device. */ + while(meta.tx_desc.own == 1); + + /* Check whether an error occurred transmitting the previous frame. */ + if(meta.tx_desc.err_summary) { + fprintf(stderr, + LOG_PFX "Error transmitting frame: TDES0 = %08x, TDES1 = %08x.\n", + meta.tx_desc.tdes0, meta.tx_desc.tdes1); + assert(0); + } + + /* Transmit the next frame. */ + assert(uip_len <= UIP_BUFSIZE); + memcpy((void *)meta.tx_buf, uip_buf, uip_len); + + meta.tx_desc.tx_buf1_sz = uip_len; + + meta.tx_desc.own = 1; + + /* 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); +} +/*---------------------------------------------------------------------------*/ diff --git a/cpu/x86/drivers/quarkX1000/eth.h b/cpu/x86/drivers/quarkX1000/eth.h new file mode 100644 index 000000000..e94267466 --- /dev/null +++ b/cpu/x86/drivers/quarkX1000/eth.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef CPU_X86_DRIVERS_QUARKX1000_ETH_H_ +#define CPU_X86_DRIVERS_QUARKX1000_ETH_H_ + +#include + +void quarkX1000_eth_init(void); +void quarkX1000_eth_poll(uint16_t *frame_len); +void quarkX1000_eth_send(void); + +#endif /* CPU_X86_DRIVERS_QUARKX1000_ETH_H_ */