diff -r 08aa7305b9ba -r 3bedfc5ecd74 devices/ccat/netdev.c --- a/devices/ccat/netdev.c Fri Dec 18 12:30:45 2015 +0100 +++ b/devices/ccat/netdev.c Tue Feb 16 15:18:34 2016 +0100 @@ -1,6 +1,6 @@ /** Network Driver for Beckhoff CCAT communication controller - Copyright (C) 2014 Beckhoff Automation GmbH + Copyright (C) 2014 - 2015 Beckhoff Automation GmbH Author: Patrick Bruenn This program is free software; you can redistribute it and/or modify @@ -23,8 +23,14 @@ #include #include +#ifdef CONFIG_PCI +#include +#else +#define free_dma(X) +#define request_dma(X, Y) ((int)(-EINVAL)) +#endif + #include "module.h" -#include "netdev.h" /** * EtherCAT frame to enable forwarding on EtherCAT Terminals @@ -44,23 +50,253 @@ }; #define FIFO_LENGTH 64 -#define POLL_TIME ktime_set(0, 100 * NSEC_PER_USEC) - -/** - * Helper to check if frame in tx dma memory was already marked as sent by CCAT - */ -static inline bool ccat_eth_frame_sent(const struct ccat_eth_frame *const frame) -{ - return le32_to_cpu(frame->tx_flags) & CCAT_FRAME_SENT; -} - -/** - * Helper to check if frame in tx dma memory was already marked as sent by CCAT - */ -static inline bool ccat_eth_frame_received(const struct ccat_eth_frame *const - frame) -{ - return le32_to_cpu(frame->rx_flags) & CCAT_FRAME_RECEIVED; +#define POLL_TIME ktime_set(0, 50 * NSEC_PER_USEC) +#define CCAT_ALIGNMENT ((size_t)(128 * 1024)) +#define CCAT_ALIGN_CHANNEL(x, c) ((typeof(x))(ALIGN((size_t)((x) + ((c) * CCAT_ALIGNMENT)), CCAT_ALIGNMENT))) + +struct ccat_dma_frame_hdr { + __le32 reserved1; + __le32 rx_flags; +#define CCAT_FRAME_RECEIVED 0x1 + __le16 length; + __le16 reserved3; + __le32 tx_flags; +#define CCAT_FRAME_SENT 0x1 + __le64 timestamp; +}; + +struct ccat_eim_frame_hdr { + __le16 length; + __le16 reserved3; + __le32 tx_flags; + __le64 timestamp; +}; + +struct ccat_eth_frame { + u8 placeholder[0x800]; +}; + +struct ccat_dma_frame { + struct ccat_dma_frame_hdr hdr; + u8 data[sizeof(struct ccat_eth_frame) - + sizeof(struct ccat_dma_frame_hdr)]; +}; + +struct ccat_eim_frame { + struct ccat_eim_frame_hdr hdr; + u8 data[sizeof(struct ccat_eth_frame) - + sizeof(struct ccat_eim_frame_hdr)]; +}; + +#define MAX_PAYLOAD_SIZE \ + (sizeof(struct ccat_eth_frame) - max(sizeof(struct ccat_dma_frame_hdr), sizeof(struct ccat_eim_frame_hdr))) + +/** + * struct ccat_eth_register - CCAT register addresses in the PCI BAR + * @mii: address of the CCAT management interface register + * @mac: address of the CCAT media access control register + * @rx_mem: address of the CCAT register holding the RX DMA address + * @tx_mem: address of the CCAT register holding the TX DMA address + * @misc: address of a CCAT register holding miscellaneous information + */ +struct ccat_eth_register { + void __iomem *mii; + void __iomem *mac; + void __iomem *rx_mem; + void __iomem *tx_mem; + void __iomem *misc; +}; + +/** + * struct ccat_dma_mem - CCAT DMA channel configuration + * @size: number of bytes in the associated DMA memory + * @phys: device-viewed address(physical) of the associated DMA memory + * @channel: CCAT DMA channel number + * @dev: valid struct device pointer + * @base: CPU-viewed address(virtual) of the associated DMA memory + */ +struct ccat_dma_mem { + size_t size; + dma_addr_t phys; + size_t channel; + struct device *dev; + void *base; +}; + +/** + * struct ccat_dma/eim/mem + * @next: pointer to the next frame in fifo ring buffer + * @start: aligned CPU-viewed address(virtual) of the associated memory + */ +struct ccat_dma { + struct ccat_dma_frame *next; + void *start; +}; + +struct ccat_eim { + struct ccat_eim_frame __iomem *next; + void __iomem *start; +}; + +struct ccat_mem { + struct ccat_eth_frame *next; + void *start; +}; + +/** + * struct ccat_eth_fifo - CCAT RX or TX fifo + * @ops: function pointer table for dma/eim and rx/tx specific fifo functions + * @reg: PCI register address of this fifo + * @rx_bytes: number of bytes processed -> reported with ndo_get_stats64() + * @rx_dropped: number of dropped frames -> reported with ndo_get_stats64() + * @mem/dma/eim: information about the associated memory + */ +struct ccat_eth_fifo { + const struct ccat_eth_fifo_operations *ops; + const struct ccat_eth_frame *end; + void __iomem *reg; + atomic64_t bytes; + atomic64_t dropped; + union { + struct ccat_mem mem; + struct ccat_dma dma; + struct ccat_eim eim; + }; +}; + +/** + * struct ccat_eth_fifo_operations + * @ready: callback used to test the next frames ready bit + * @add: callback used to add a frame to this fifo + * @copy_to_skb: callback used to copy from rx fifos to skbs + * @skb: callback used to queue skbs into tx fifos + */ +struct ccat_eth_fifo_operations { + size_t(*ready) (struct ccat_eth_fifo *); + void (*add) (struct ccat_eth_fifo *); + union { + void (*copy_to_skb) (struct ccat_eth_fifo *, struct sk_buff *, + size_t); + void (*skb) (struct ccat_eth_fifo *, struct sk_buff *); + } queue; +}; + +/** + * same as: typedef struct _CCatInfoBlockOffs from CCatDefinitions.h + */ +struct ccat_mac_infoblock { + u32 reserved; + u32 mii; + u32 tx_fifo; + u32 mac; + u32 rx_mem; + u32 tx_mem; + u32 misc; +}; + +/** + * struct ccat_eth_priv - CCAT Ethernet/EtherCAT Master function (netdev) + * @func: pointer to the parent struct ccat_function + * @netdev: the net_device structure used by the kernel networking stack + * @reg: register addresses in PCI config space of the Ethernet/EtherCAT Master function + * @rx_fifo: fifo used for RX descriptors + * @tx_fifo: fifo used for TX descriptors + * @poll_timer: interval timer used to poll CCAT for events like link changed, rx done, tx done + */ +struct ccat_eth_priv { + struct ccat_function *func; + struct net_device *netdev; + struct ccat_eth_register reg; + struct ccat_eth_fifo rx_fifo; + struct ccat_eth_fifo tx_fifo; + struct hrtimer poll_timer; + struct ccat_dma_mem dma_mem; + ec_device_t *ecdev; + void (*carrier_off) (struct net_device * netdev); + bool(*carrier_ok) (const struct net_device * netdev); + void (*carrier_on) (struct net_device * netdev); + void (*kfree_skb_any) (struct sk_buff * skb); + void (*receive) (struct ccat_eth_priv *, size_t); + void (*start_queue) (struct net_device * netdev); + void (*stop_queue) (struct net_device * netdev); + void (*unregister) (struct net_device * netdev); +}; + +struct ccat_mac_register { + /** MAC error register @+0x0 */ + u8 frame_len_err; + u8 rx_err; + u8 crc_err; + u8 link_lost_err; + u32 reserved1; + /** Buffer overflow errors @+0x8 */ + u8 rx_mem_full; + u8 reserved2[7]; + /** MAC frame counter @+0x10 */ + u32 tx_frames; + u32 rx_frames; + u64 reserved3; + /** MAC fifo level @+0x20 */ + u8 tx_fifo_level:7; + u8 reserved4:1; + u8 reserved5[7]; + /** TX memory full error @+0x28 */ + u8 tx_mem_full; + u8 reserved6[7]; + u64 reserved8[9]; + /** Connection @+0x78 */ + u8 mii_connected; +}; + +static void fifo_set_end(struct ccat_eth_fifo *const fifo, size_t size) +{ + fifo->end = fifo->mem.start + size - sizeof(struct ccat_eth_frame); +} + +static void ccat_dma_free(struct ccat_eth_priv *const priv) +{ + if (priv->dma_mem.base) { + const struct ccat_dma_mem tmp = priv->dma_mem; + + memset(&priv->dma_mem, 0, sizeof(priv->dma_mem)); + dma_free_coherent(tmp.dev, tmp.size, tmp.base, tmp.phys); + free_dma(priv->func->info.tx_dma_chan); + free_dma(priv->func->info.rx_dma_chan); + } +} + +/** + * ccat_dma_init() - Initialize CCAT and host memory for DMA transfer + * @dma object for management data which will be initialized + * @channel number of the DMA channel + * @ioaddr of the pci bar2 configspace used to calculate the address of the pci dma configuration + * @dev which should be configured for DMA + */ +static int ccat_dma_init(struct ccat_dma_mem *const dma, size_t channel, + void __iomem * const bar2, + struct ccat_eth_fifo *const fifo) +{ + void __iomem *const ioaddr = bar2 + 0x1000 + (sizeof(u64) * channel); + const dma_addr_t phys = CCAT_ALIGN_CHANNEL(dma->phys, channel); + const u32 phys_hi = (sizeof(phys) > sizeof(u32)) ? phys >> 32 : 0; + fifo->dma.start = CCAT_ALIGN_CHANNEL(dma->base, channel); + + fifo_set_end(fifo, CCAT_ALIGNMENT); + if (request_dma(channel, KBUILD_MODNAME)) { + pr_info("request dma channel %llu failed\n", (u64) channel); + return -EINVAL; + } + + /** bit 0 enables 64 bit mode on ccat */ + iowrite32((u32) phys | ((phys_hi) > 0), ioaddr); + iowrite32(phys_hi, ioaddr + 4); + + pr_debug + ("DMA%llu mem initialized\n base: 0x%p\n start: 0x%p\n phys: 0x%09llx\n pci addr: 0x%01x%08x\n size: %llu |%llx bytes.\n", + (u64) channel, dma->base, fifo->dma.start, (u64) dma->phys, + ioread32(ioaddr + 4), ioread32(ioaddr), + (u64) dma->size, (u64) dma->size); + return 0; } static void ecdev_kfree_skb_any(struct sk_buff *skb) @@ -91,6 +327,16 @@ /* dummy called if nothing has to be done in EtherCAT operation mode */ } +static void ecdev_receive_dma(struct ccat_eth_priv *const priv, size_t len) +{ + ecdev_receive(priv->ecdev, priv->rx_fifo.dma.next->data, len); +} + +static void ecdev_receive_eim(struct ccat_eth_priv *const priv, size_t len) +{ + ecdev_receive(priv->ecdev, priv->rx_fifo.eim.next->data, len); +} + static void unregister_ecdev(struct net_device *const netdev) { struct ccat_eth_priv *const priv = netdev_priv(netdev); @@ -98,172 +344,308 @@ ecdev_withdraw(priv->ecdev); } -static void ccat_eth_fifo_inc(struct ccat_eth_dma_fifo *fifo) -{ - if (++fifo->next >= fifo->end) - fifo->next = fifo->dma.virt; -} - -typedef void (*fifo_add_function) (struct ccat_eth_dma_fifo *, - struct ccat_eth_frame *); - -static void ccat_eth_rx_fifo_add(struct ccat_eth_dma_fifo *fifo, - struct ccat_eth_frame *frame) -{ - const size_t offset = ((void *)(frame) - fifo->dma.virt); +static inline size_t fifo_eim_tx_ready(struct ccat_eth_fifo *const fifo) +{ + struct ccat_eth_priv *const priv = + container_of(fifo, struct ccat_eth_priv, tx_fifo); + static const size_t TX_FIFO_LEVEL_OFFSET = 0x20; + static const u8 TX_FIFO_LEVEL_MASK = 0x3F; + void __iomem *addr = priv->reg.mac + TX_FIFO_LEVEL_OFFSET; + + return !(ioread8(addr) & TX_FIFO_LEVEL_MASK); +} + +static inline size_t fifo_eim_rx_ready(struct ccat_eth_fifo *const fifo) +{ + static const size_t OVERHEAD = sizeof(struct ccat_eim_frame_hdr); + const size_t len = ioread16(&fifo->eim.next->hdr.length); + + return (len < OVERHEAD) ? 0 : len - OVERHEAD; +} + +static void ccat_eth_fifo_inc(struct ccat_eth_fifo *fifo) +{ + if (++fifo->mem.next > fifo->end) + fifo->mem.next = fifo->mem.start; +} + +static void fifo_eim_rx_add(struct ccat_eth_fifo *const fifo) +{ + struct ccat_eim_frame __iomem *frame = fifo->eim.next; + iowrite16(0, frame); + wmb(); +} + +static void fifo_eim_tx_add(struct ccat_eth_fifo *const fifo) +{ +} + +#define memcpy_from_ccat(DEST, SRC, LEN) memcpy(DEST,(__force void*)(SRC), LEN) +#define memcpy_to_ccat(DEST, SRC, LEN) memcpy((__force void*)(DEST),SRC, LEN) +static void fifo_eim_copy_to_linear_skb(struct ccat_eth_fifo *const fifo, + struct sk_buff *skb, const size_t len) +{ + memcpy_from_ccat(skb->data, fifo->eim.next->data, len); +} + +static void fifo_eim_queue_skb(struct ccat_eth_fifo *const fifo, + struct sk_buff *skb) +{ + struct ccat_eim_frame __iomem *frame = fifo->eim.next; + const u32 addr_and_length = + (void __iomem *)frame - (void __iomem *)fifo->eim.start; + + const __le16 length = cpu_to_le16(skb->len); + memcpy_to_ccat(&frame->hdr.length, &length, sizeof(length)); + memcpy_to_ccat(frame->data, skb->data, skb->len); + iowrite32(addr_and_length, fifo->reg); +} + +static void ccat_eth_fifo_hw_reset(struct ccat_eth_fifo *const fifo) +{ + if (fifo->reg) { + iowrite32(0, fifo->reg + 0x8); + wmb(); + } +} + +static void ccat_eth_fifo_reset(struct ccat_eth_fifo *const fifo) +{ + ccat_eth_fifo_hw_reset(fifo); + + if (fifo->ops->add) { + fifo->mem.next = fifo->mem.start; + do { + fifo->ops->add(fifo); + ccat_eth_fifo_inc(fifo); + } while (fifo->mem.next != fifo->mem.start); + } +} + +static inline size_t fifo_dma_tx_ready(struct ccat_eth_fifo *const fifo) +{ + const struct ccat_dma_frame *frame = fifo->dma.next; + return le32_to_cpu(frame->hdr.tx_flags) & CCAT_FRAME_SENT; +} + +static inline size_t fifo_dma_rx_ready(struct ccat_eth_fifo *const fifo) +{ + static const size_t OVERHEAD = + offsetof(struct ccat_dma_frame_hdr, rx_flags); + const struct ccat_dma_frame *const frame = fifo->dma.next; + + if (le32_to_cpu(frame->hdr.rx_flags) & CCAT_FRAME_RECEIVED) { + const size_t len = le16_to_cpu(frame->hdr.length); + return (len < OVERHEAD) ? 0 : len - OVERHEAD; + } + return 0; +} + +static void ccat_eth_rx_fifo_dma_add(struct ccat_eth_fifo *const fifo) +{ + struct ccat_dma_frame *const frame = fifo->dma.next; + const size_t offset = (void *)frame - fifo->dma.start; const u32 addr_and_length = (1 << 31) | offset; - frame->rx_flags = cpu_to_le32(0); + frame->hdr.rx_flags = cpu_to_le32(0); iowrite32(addr_and_length, fifo->reg); } -static void ccat_eth_tx_fifo_add_free(struct ccat_eth_dma_fifo *fifo, - struct ccat_eth_frame *frame) +static void ccat_eth_tx_fifo_dma_add_free(struct ccat_eth_fifo *const fifo) { /* mark frame as ready to use for tx */ - frame->tx_flags = cpu_to_le32(CCAT_FRAME_SENT); -} - -static void ccat_eth_dma_fifo_reset(struct ccat_eth_dma_fifo *fifo) -{ - /* reset hw fifo */ - iowrite32(0, fifo->reg + 0x8); - wmb(); - - if (fifo->add) { - fifo->next = fifo->dma.virt; - do { - fifo->add(fifo, fifo->next); - ccat_eth_fifo_inc(fifo); - } while (fifo->next != fifo->dma.virt); - } -} - -static int ccat_eth_dma_fifo_init(struct ccat_eth_dma_fifo *fifo, - void __iomem * const fifo_reg, - fifo_add_function add, size_t channel, - struct ccat_eth_priv *const priv) -{ - if (0 != - ccat_dma_init(&fifo->dma, channel, priv->ccatdev->bar[2].ioaddr, - &priv->ccatdev->pdev->dev)) { - pr_info("init DMA%llu memory failed.\n", (u64) channel); - return -1; - } - fifo->add = add; - fifo->end = ((struct ccat_eth_frame *)fifo->dma.virt) + FIFO_LENGTH; - fifo->reg = fifo_reg; - return 0; -} - -/** - * Stop both (Rx/Tx) DMA fifo's and free related management structures - */ -static void ccat_eth_priv_free_dma(struct ccat_eth_priv *priv) + fifo->dma.next->hdr.tx_flags = cpu_to_le32(CCAT_FRAME_SENT); +} + +static void fifo_dma_copy_to_linear_skb(struct ccat_eth_fifo *const fifo, + struct sk_buff *skb, const size_t len) +{ + skb_copy_to_linear_data(skb, fifo->dma.next->data, len); +} + +static void fifo_dma_queue_skb(struct ccat_eth_fifo *const fifo, + struct sk_buff *skb) +{ + struct ccat_dma_frame *frame = fifo->dma.next; + u32 addr_and_length; + + frame->hdr.tx_flags = cpu_to_le32(0); + frame->hdr.length = cpu_to_le16(skb->len); + + memcpy(frame->data, skb->data, skb->len); + + /* Queue frame into CCAT TX-FIFO, CCAT ignores the first 8 bytes of the tx descriptor */ + addr_and_length = offsetof(struct ccat_dma_frame_hdr, length); + addr_and_length += ((void *)frame - fifo->dma.start); + addr_and_length += + ((skb->len + sizeof(struct ccat_dma_frame_hdr)) / 8) << 24; + iowrite32(addr_and_length, fifo->reg); +} + +static const struct ccat_eth_fifo_operations dma_rx_fifo_ops = { + .add = ccat_eth_rx_fifo_dma_add, + .ready = fifo_dma_rx_ready, + .queue.copy_to_skb = fifo_dma_copy_to_linear_skb, +}; + +static const struct ccat_eth_fifo_operations dma_tx_fifo_ops = { + .add = ccat_eth_tx_fifo_dma_add_free, + .ready = fifo_dma_tx_ready, + .queue.skb = fifo_dma_queue_skb, +}; + +static const struct ccat_eth_fifo_operations eim_rx_fifo_ops = { + .add = fifo_eim_rx_add, + .queue.copy_to_skb = fifo_eim_copy_to_linear_skb, + .ready = fifo_eim_rx_ready, +}; + +static const struct ccat_eth_fifo_operations eim_tx_fifo_ops = { + .add = fifo_eim_tx_add, + .queue.skb = fifo_eim_queue_skb, + .ready = fifo_eim_tx_ready, +}; + +static void ccat_eth_priv_free(struct ccat_eth_priv *priv) { /* reset hw fifo's */ - iowrite32(0, priv->rx_fifo.reg + 0x8); - iowrite32(0, priv->tx_fifo.reg + 0x8); - wmb(); + ccat_eth_fifo_hw_reset(&priv->rx_fifo); + ccat_eth_fifo_hw_reset(&priv->tx_fifo); /* release dma */ - ccat_dma_free(&priv->rx_fifo.dma); - ccat_dma_free(&priv->tx_fifo.dma); -} - -/** - * Initalizes both (Rx/Tx) DMA fifo's and related management structures - */ -static int ccat_eth_priv_init_dma(struct ccat_eth_priv *priv) -{ - if (ccat_eth_dma_fifo_init - (&priv->rx_fifo, priv->reg.rx_fifo, ccat_eth_rx_fifo_add, - priv->info.rx_dma_chan, priv)) { - pr_warn("init Rx DMA fifo failed.\n"); - return -1; - } - - if (ccat_eth_dma_fifo_init - (&priv->tx_fifo, priv->reg.tx_fifo, ccat_eth_tx_fifo_add_free, - priv->info.tx_dma_chan, priv)) { - pr_warn("init Tx DMA fifo failed.\n"); - ccat_dma_free(&priv->rx_fifo.dma); - return -1; - } - - /* disable MAC filter */ + ccat_dma_free(priv); +} + +static int ccat_hw_disable_mac_filter(struct ccat_eth_priv *priv) +{ iowrite8(0, priv->reg.mii + 0x8 + 6); wmb(); return 0; } /** - * Initializes the CCat... members of the ccat_eth_priv structure. - * Call this function only if info and ioaddr are already initialized! - */ -static void ccat_eth_priv_init_mappings(struct ccat_eth_priv *priv) + * Initalizes both (Rx/Tx) DMA fifo's and related management structures + */ +static int ccat_eth_priv_init_dma(struct ccat_eth_priv *priv) +{ + struct ccat_dma_mem *const dma = &priv->dma_mem; + struct pci_dev *const pdev = priv->func->ccat->pdev; + void __iomem *const bar_2 = priv->func->ccat->bar_2; + const u8 rx_chan = priv->func->info.rx_dma_chan; + const u8 tx_chan = priv->func->info.tx_dma_chan; + int status = 0; + + dma->dev = &pdev->dev; + dma->size = CCAT_ALIGNMENT * 3; + dma->base = + dma_zalloc_coherent(dma->dev, dma->size, &dma->phys, GFP_KERNEL); + if (!dma->base || !dma->phys) { + pr_err("init DMA memory failed.\n"); + return -ENOMEM; + } + + priv->rx_fifo.ops = &dma_rx_fifo_ops; + status = ccat_dma_init(dma, rx_chan, bar_2, &priv->rx_fifo); + if (status) { + pr_info("init RX DMA memory failed.\n"); + ccat_dma_free(priv); + return status; + } + + priv->tx_fifo.ops = &dma_tx_fifo_ops; + status = ccat_dma_init(dma, tx_chan, bar_2, &priv->tx_fifo); + if (status) { + pr_info("init TX DMA memory failed.\n"); + ccat_dma_free(priv); + return status; + } + return ccat_hw_disable_mac_filter(priv); +} + +static int ccat_eth_priv_init_eim(struct ccat_eth_priv *priv) +{ + priv->rx_fifo.eim.start = priv->reg.rx_mem; + priv->rx_fifo.ops = &eim_rx_fifo_ops; + fifo_set_end(&priv->rx_fifo, sizeof(struct ccat_eth_frame)); + + priv->tx_fifo.eim.start = priv->reg.tx_mem; + priv->tx_fifo.ops = &eim_tx_fifo_ops; + fifo_set_end(&priv->tx_fifo, priv->func->info.tx_size); + + return ccat_hw_disable_mac_filter(priv); +} + +/** + * Initializes a struct ccat_eth_register with data from a corresponding + * CCAT function. + */ +static void ccat_eth_priv_init_reg(struct ccat_eth_priv *const priv) { struct ccat_mac_infoblock offsets; - void __iomem *const func_base = - priv->ccatdev->bar[0].ioaddr + priv->info.addr; + struct ccat_eth_register *const reg = &priv->reg; + const struct ccat_function *const func = priv->func; + void __iomem *const func_base = func->ccat->bar_0 + func->info.addr; + + /* struct ccat_eth_fifo contains a union of ccat_dma, ccat_eim and ccat_mem + * the members next and start have to overlay the exact same memory, + * to support 'polymorphic' usage of them */ + BUILD_BUG_ON(offsetof(struct ccat_dma, next) != + offsetof(struct ccat_mem, next)); + BUILD_BUG_ON(offsetof(struct ccat_dma, start) != + offsetof(struct ccat_mem, start)); + BUILD_BUG_ON(offsetof(struct ccat_dma, next) != + offsetof(struct ccat_eim, next)); + BUILD_BUG_ON(offsetof(struct ccat_dma, start) != + offsetof(struct ccat_eim, start)); memcpy_fromio(&offsets, func_base, sizeof(offsets)); - priv->reg.mii = func_base + offsets.mii; - priv->reg.tx_fifo = func_base + offsets.tx_fifo; - priv->reg.rx_fifo = func_base + offsets.tx_fifo + 0x10; - priv->reg.mac = func_base + offsets.mac; - priv->reg.rx_mem = func_base + offsets.rx_mem; - priv->reg.tx_mem = func_base + offsets.tx_mem; - priv->reg.misc = func_base + offsets.misc; + reg->mii = func_base + offsets.mii; + priv->tx_fifo.reg = func_base + offsets.tx_fifo; + priv->rx_fifo.reg = func_base + offsets.tx_fifo + 0x10; + reg->mac = func_base + offsets.mac; + reg->rx_mem = func_base + offsets.rx_mem; + reg->tx_mem = func_base + offsets.tx_mem; + reg->misc = func_base + offsets.misc; } static netdev_tx_t ccat_eth_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct ccat_eth_priv *const priv = netdev_priv(dev); - struct ccat_eth_dma_fifo *const fifo = &priv->tx_fifo; - u32 addr_and_length; + struct ccat_eth_fifo *const fifo = &priv->tx_fifo; if (skb_is_nonlinear(skb)) { pr_warn("Non linear skb not supported -> drop frame.\n"); - atomic64_inc(&priv->tx_dropped); + atomic64_inc(&fifo->dropped); priv->kfree_skb_any(skb); return NETDEV_TX_OK; } - if (skb->len > sizeof(fifo->next->data)) { + if (skb->len > MAX_PAYLOAD_SIZE) { pr_warn("skb.len %llu exceeds dma buffer %llu -> drop frame.\n", - (u64) skb->len, (u64) sizeof(fifo->next->data)); - atomic64_inc(&priv->tx_dropped); + (u64) skb->len, (u64) MAX_PAYLOAD_SIZE); + atomic64_inc(&fifo->dropped); priv->kfree_skb_any(skb); return NETDEV_TX_OK; } - if (!ccat_eth_frame_sent(fifo->next)) { + if (!fifo->ops->ready(fifo)) { netdev_err(dev, "BUG! Tx Ring full when queue awake!\n"); priv->stop_queue(priv->netdev); return NETDEV_TX_BUSY; } /* prepare frame in DMA memory */ - fifo->next->tx_flags = cpu_to_le32(0); - fifo->next->length = cpu_to_le16(skb->len); - memcpy(fifo->next->data, skb->data, skb->len); - - /* Queue frame into CCAT TX-FIFO, CCAT ignores the first 8 bytes of the tx descriptor */ - addr_and_length = offsetof(struct ccat_eth_frame, length); - addr_and_length += ((void *)fifo->next - fifo->dma.virt); - addr_and_length += ((skb->len + CCAT_ETH_FRAME_HEAD_LEN) / 8) << 24; - iowrite32(addr_and_length, priv->reg.tx_fifo); + fifo->ops->queue.skb(fifo, skb); /* update stats */ - atomic64_add(skb->len, &priv->tx_bytes); + atomic64_add(skb->len, &fifo->bytes); priv->kfree_skb_any(skb); ccat_eth_fifo_inc(fifo); /* stop queue if tx ring is full */ - if (!ccat_eth_frame_sent(fifo->next)) { + if (!fifo->ops->ready(fifo)) { priv->stop_queue(priv->netdev); } return NETDEV_TX_OK; @@ -286,24 +668,24 @@ ccat_eth_start_xmit(skb, dev); } -static void ccat_eth_receive(struct net_device *const dev, - const void *const data, const size_t len) +static void ccat_eth_receive(struct ccat_eth_priv *const priv, const size_t len) { struct sk_buff *const skb = dev_alloc_skb(len + NET_IP_ALIGN); - struct ccat_eth_priv *const priv = netdev_priv(dev); + struct ccat_eth_fifo *const fifo = &priv->rx_fifo; + struct net_device *const dev = priv->netdev; if (!skb) { pr_info("%s() out of memory :-(\n", __FUNCTION__); - atomic64_inc(&priv->rx_dropped); + atomic64_inc(&fifo->dropped); return; } skb->dev = dev; skb_reserve(skb, NET_IP_ALIGN); - skb_copy_to_linear_data(skb, data, len); + fifo->ops->queue.copy_to_skb(fifo, skb, len); skb_put(skb, len); skb->protocol = eth_type_trans(skb, dev); skb->ip_summed = CHECKSUM_UNNECESSARY; - atomic64_add(len, &priv->rx_bytes); + atomic64_add(len, &fifo->bytes); netif_rx(skb); } @@ -325,8 +707,8 @@ speed == SPEED_100 ? 100 : 10, cmd.duplex == DUPLEX_FULL ? "Full" : "Half"); */ - ccat_eth_dma_fifo_reset(&priv->rx_fifo); - ccat_eth_dma_fifo_reset(&priv->tx_fifo); + ccat_eth_fifo_reset(&priv->rx_fifo); + ccat_eth_fifo_reset(&priv->tx_fifo); /* TODO reset CCAT MAC register */ @@ -343,7 +725,7 @@ inline static size_t ccat_eth_priv_read_link_state(const struct ccat_eth_priv *const priv) { - return (1 << 24) == (ioread32(priv->reg.mii + 0x8 + 4) & (1 << 24)); + return ! !(ioread32(priv->reg.mii + 0x8 + 4) & (1 << 24)); } /** @@ -366,25 +748,20 @@ */ static void poll_rx(struct ccat_eth_priv *const priv) { - static const size_t overhead = CCAT_ETH_FRAME_HEAD_LEN - 4; - struct ccat_eth_dma_fifo *const fifo = &priv->rx_fifo; - - /* TODO omit possible deadlock in situations with heavy traffic */ - while (ccat_eth_frame_received(fifo->next)) { - const size_t len = le16_to_cpu(fifo->next->length) - overhead; - if (priv->ecdev) { - ecdev_receive(priv->ecdev, fifo->next->data, len); - } else { - ccat_eth_receive(priv->netdev, fifo->next->data, len); - } - ccat_eth_rx_fifo_add(fifo, fifo->next); + struct ccat_eth_fifo *const fifo = &priv->rx_fifo; + const size_t len = fifo->ops->ready(fifo); + + if (len) { + priv->receive(priv, len); + fifo->ops->add(fifo); ccat_eth_fifo_inc(fifo); } } -static void ec_poll_rx(struct net_device *dev) +static void ec_poll(struct net_device *dev) { struct ccat_eth_priv *const priv = netdev_priv(dev); + poll_link(priv); poll_rx(priv); } @@ -393,7 +770,7 @@ */ static void poll_tx(struct ccat_eth_priv *const priv) { - if (ccat_eth_frame_sent(priv->tx_fifo.next)) { + if (priv->tx_fifo.ops->ready(&priv->tx_fifo)) { netif_wake_queue(priv->netdev); } } @@ -408,10 +785,8 @@ container_of(timer, struct ccat_eth_priv, poll_timer); poll_link(priv); - if(!priv->ecdev) { - poll_rx(priv); - poll_tx(priv); - } + poll_rx(priv); + poll_tx(priv); hrtimer_forward_now(timer, POLL_TIME); return HRTIMER_RESTART; } @@ -421,15 +796,16 @@ { struct ccat_eth_priv *const priv = netdev_priv(dev); struct ccat_mac_register mac; + memcpy_fromio(&mac, priv->reg.mac, sizeof(mac)); storage->rx_packets = mac.rx_frames; /* total packets received */ storage->tx_packets = mac.tx_frames; /* total packets transmitted */ - storage->rx_bytes = atomic64_read(&priv->rx_bytes); /* total bytes received */ - storage->tx_bytes = atomic64_read(&priv->tx_bytes); /* total bytes transmitted */ + storage->rx_bytes = atomic64_read(&priv->rx_fifo.bytes); /* total bytes received */ + storage->tx_bytes = atomic64_read(&priv->tx_fifo.bytes); /* total bytes transmitted */ storage->rx_errors = mac.frame_len_err + mac.rx_mem_full + mac.crc_err + mac.rx_err; /* bad packets received */ storage->tx_errors = mac.tx_mem_full; /* packet transmit problems */ - storage->rx_dropped = atomic64_read(&priv->rx_dropped); /* no space in linux buffers */ - storage->tx_dropped = atomic64_read(&priv->tx_dropped); /* no space available in linux */ + storage->rx_dropped = atomic64_read(&priv->rx_fifo.dropped); /* no space in linux buffers */ + storage->tx_dropped = atomic64_read(&priv->tx_fifo.dropped); /* no space available in linux */ //TODO __u64 multicast; /* multicast packets received */ //TODO __u64 collisions; @@ -458,9 +834,11 @@ { struct ccat_eth_priv *const priv = netdev_priv(dev); - hrtimer_init(&priv->poll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - priv->poll_timer.function = poll_timer_callback; - hrtimer_start(&priv->poll_timer, POLL_TIME, HRTIMER_MODE_REL); + if (!priv->ecdev) { + hrtimer_init(&priv->poll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + priv->poll_timer.function = poll_timer_callback; + hrtimer_start(&priv->poll_timer, POLL_TIME, HRTIMER_MODE_REL); + } return 0; } @@ -469,7 +847,9 @@ struct ccat_eth_priv *const priv = netdev_priv(dev); priv->stop_queue(dev); - hrtimer_cancel(&priv->poll_timer); + if (!priv->ecdev) { + hrtimer_cancel(&priv->poll_timer); + } return 0; } @@ -480,50 +860,60 @@ .ndo_stop = ccat_eth_stop, }; -struct ccat_eth_priv *ccat_eth_init(const struct ccat_device *const ccatdev, - const void __iomem * const addr) -{ - struct ccat_eth_priv *priv; +static struct ccat_eth_priv *ccat_eth_alloc_netdev(struct ccat_function *func) +{ + struct ccat_eth_priv *priv = NULL; struct net_device *const netdev = alloc_etherdev(sizeof(*priv)); - priv = netdev_priv(netdev); - priv->netdev = netdev; - priv->ccatdev = ccatdev; - - /* ccat register mappings */ - memcpy_fromio(&priv->info, addr, sizeof(priv->info)); - ccat_eth_priv_init_mappings(priv); - - if (ccat_eth_priv_init_dma(priv)) { - pr_warn("%s(): DMA initialization failed.\n", __FUNCTION__); - free_netdev(netdev); - return NULL; - } + if (netdev) { + priv = netdev_priv(netdev); + memset(priv, 0, sizeof(*priv)); + priv->netdev = netdev; + priv->func = func; + ccat_eth_priv_init_reg(priv); + } + return priv; +} + +static int ccat_eth_init_netdev(struct ccat_eth_priv *priv) +{ + int status; /* init netdev with MAC and stack callbacks */ - memcpy_fromio(netdev->dev_addr, priv->reg.mii + 8, netdev->addr_len); - netdev->netdev_ops = &ccat_eth_netdev_ops; + memcpy_fromio(priv->netdev->dev_addr, priv->reg.mii + 8, + priv->netdev->addr_len); + priv->netdev->netdev_ops = &ccat_eth_netdev_ops; /* use as EtherCAT device? */ - priv->ecdev = ecdev_offer(netdev, ec_poll_rx, THIS_MODULE); + priv->carrier_off = ecdev_carrier_off; + priv->carrier_ok = ecdev_carrier_ok; + priv->carrier_on = ecdev_carrier_on; + priv->kfree_skb_any = ecdev_kfree_skb_any; + + /* It would be more intuitive to check for: + * if (priv->func->drv->type == CCATINFO_ETHERCAT_MASTER_DMA) { + * unfortunately priv->func->drv is not initialized until probe() returns. + * So we check if there is a rx dma fifo registered to determine dma/io mode */ + if (&dma_rx_fifo_ops == priv->rx_fifo.ops) { + priv->receive = ecdev_receive_dma; + } else { + priv->receive = ecdev_receive_eim; + } + priv->start_queue = ecdev_nop; + priv->stop_queue = ecdev_nop; + priv->unregister = unregister_ecdev; + priv->ecdev = ecdev_offer(priv->netdev, ec_poll, THIS_MODULE); if (priv->ecdev) { - priv->carrier_off = ecdev_carrier_off; - priv->carrier_ok = ecdev_carrier_ok; - priv->carrier_on = ecdev_carrier_on; - priv->kfree_skb_any = ecdev_kfree_skb_any; - priv->start_queue = ecdev_nop; - priv->stop_queue = ecdev_nop; - priv->unregister = unregister_ecdev; - - priv->carrier_off(netdev); + priv->carrier_off(priv->netdev); if (ecdev_open(priv->ecdev)) { pr_info("unable to register network device.\n"); ecdev_withdraw(priv->ecdev); - ccat_eth_priv_free_dma(priv); - free_netdev(netdev); - return NULL; + ccat_eth_priv_free(priv); + free_netdev(priv->netdev); + return -1; // TODO return better error code } - return priv; + priv->func->private_data = priv; + return 0; } /* EtherCAT disabled -> prepare normal ethernet mode */ @@ -531,24 +921,82 @@ priv->carrier_ok = netif_carrier_ok; priv->carrier_on = netif_carrier_on; priv->kfree_skb_any = dev_kfree_skb_any; + priv->receive = ccat_eth_receive; priv->start_queue = netif_start_queue; priv->stop_queue = netif_stop_queue; priv->unregister = unregister_netdev; - - priv->carrier_off(netdev); - if (register_netdev(netdev)) { + priv->carrier_off(priv->netdev); + + status = register_netdev(priv->netdev); + if (status) { pr_info("unable to register network device.\n"); - ccat_eth_priv_free_dma(priv); - free_netdev(netdev); - return NULL; - } - pr_info("registered %s as network device.\n", netdev->name); - return priv; -} - -void ccat_eth_remove(struct ccat_eth_priv *const priv) -{ - priv->unregister(priv->netdev); - ccat_eth_priv_free_dma(priv); - free_netdev(priv->netdev); -} + ccat_eth_priv_free(priv); + free_netdev(priv->netdev); + return status; + } + pr_info("registered %s as network device.\n", priv->netdev->name); + priv->func->private_data = priv; + return 0; +} + +static int ccat_eth_dma_probe(struct ccat_function *func) +{ + struct ccat_eth_priv *priv = ccat_eth_alloc_netdev(func); + int status; + + if (!priv) + return -ENOMEM; + + status = ccat_eth_priv_init_dma(priv); + if (status) { + pr_warn("%s(): DMA initialization failed.\n", __FUNCTION__); + free_netdev(priv->netdev); + return status; + } + return ccat_eth_init_netdev(priv); +} + +static void ccat_eth_dma_remove(struct ccat_function *func) +{ + struct ccat_eth_priv *const eth = func->private_data; + eth->unregister(eth->netdev); + ccat_eth_priv_free(eth); + free_netdev(eth->netdev); +} + +const struct ccat_driver eth_dma_driver = { + .type = CCATINFO_ETHERCAT_MASTER_DMA, + .probe = ccat_eth_dma_probe, + .remove = ccat_eth_dma_remove, +}; + +static int ccat_eth_eim_probe(struct ccat_function *func) +{ + struct ccat_eth_priv *priv = ccat_eth_alloc_netdev(func); + int status; + + if (!priv) + return -ENOMEM; + + status = ccat_eth_priv_init_eim(priv); + if (status) { + pr_warn("%s(): memory initialization failed.\n", __FUNCTION__); + free_netdev(priv->netdev); + return status; + } + return ccat_eth_init_netdev(priv); +} + +static void ccat_eth_eim_remove(struct ccat_function *func) +{ + struct ccat_eth_priv *const eth = func->private_data; + eth->unregister(eth->netdev); + ccat_eth_priv_free(eth); + free_netdev(eth->netdev); +} + +const struct ccat_driver eth_eim_driver = { + .type = CCATINFO_ETHERCAT_NODMA, + .probe = ccat_eth_eim_probe, + .remove = ccat_eth_eim_remove, +};