1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <inttypes.h>
6#include <stdint.h>
7#include <zircon/listnode.h>
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <threads.h>
13
14#if _KERNEL
15//TODO: proper includes/defines kernel driver
16#else
17// includes and defines for userspace driver
18#include <zircon/types.h>
19#include <zircon/syscalls.h>
20#include <ddk/driver.h>
21typedef int status_t;
22#define nanosleep(x) zx_nanosleep(zx_deadline_after(x));
23#define usleep(x)    nanosleep((x) * 1000)
24#define REG32(addr)  ((volatile uint32_t *)(uintptr_t)(addr))
25#define writel(v, a) (*REG32(eth->iobase + (a)) = (v))
26#define readl(a)     (*REG32(eth->iobase + (a)))
27#endif
28
29#include "ie.h"
30
31void eth_dump_regs(ethdev_t* eth) {
32    printf("STAT %08x CTRL %08x EXT %08x IMS %08x\n",
33           readl(IE_STATUS), readl(IE_CTRL), readl(IE_CTRL_EXT), readl(IE_IMS));
34    printf("RCTL %08x RDLN %08x RDH %08x RDT %08x\n",
35           readl(IE_RCTL), readl(IE_RDLEN), readl(IE_RDH), readl(IE_RDT));
36    printf("RXDC %08x RDTR %08x RBH %08x RBL %08x\n",
37           readl(IE_RXDCTL), readl(IE_RDTR), readl(IE_RDBAH), readl(IE_RDBAL));
38    printf("TCTL %08x TDLN %08x TDH %08x TDT %08x\n",
39           readl(IE_TCTL), readl(IE_TDLEN), readl(IE_TDH), readl(IE_TDT));
40    printf("TXDC %08x TIDV %08x TBH %08x TBL %08x\n",
41           readl(IE_TXDCTL), readl(IE_TIDV), readl(IE_TDBAH), readl(IE_TDBAL));
42}
43
44unsigned eth_handle_irq(ethdev_t* eth) {
45    // clears irqs on read
46    return readl(IE_ICR);
47}
48
49bool eth_status_online(ethdev_t* eth) {
50    return readl(IE_STATUS) & IE_STATUS_LU;
51}
52
53status_t eth_rx(ethdev_t* eth, void** data, size_t* len) {
54    uint32_t n = eth->rx_rd_ptr;
55    uint64_t info = eth->rxd[n].info;
56
57    if (!(info & IE_RXD_DONE)) {
58        return ZX_ERR_SHOULD_WAIT;
59    }
60
61    // copy out packet
62    zx_status_t r = IE_RXD_LEN(info);
63
64    *data = eth->rxb + ETH_RXBUF_SIZE * n;
65    *len = r;
66
67    return ZX_OK;
68}
69
70void eth_rx_ack(ethdev_t* eth) {
71    uint32_t n = eth->rx_rd_ptr;
72
73    // make buffer available to hw
74    eth->rxd[n].info = 0;
75    writel(n, IE_RDT);
76    n = (n + 1) & (ETH_RXBUF_COUNT - 1);
77    eth->rx_rd_ptr = n;
78}
79
80void eth_enable_rx(ethdev_t* eth) {
81    uint32_t rctl = readl(IE_RCTL);
82    writel(rctl | IE_RCTL_EN, IE_RCTL);
83}
84
85void eth_disable_rx(ethdev_t* eth) {
86    uint32_t rctl = readl(IE_RCTL);
87    writel(rctl & ~IE_RCTL_EN, IE_RCTL);
88}
89
90static void reap_tx_buffers(ethdev_t* eth) {
91    uint32_t n = eth->tx_rd_ptr;
92    for (;;) {
93        uint64_t info = eth->txd[n].info;
94        if (!(info & IE_TXD_DONE)) {
95            break;
96        }
97        framebuf_t* frame = list_remove_head_type(&eth->busy_frames, framebuf_t, node);
98        if (frame == NULL) {
99            panic();
100        }
101        // TODO: verify that this is the matching buffer to txd[n] addr?
102        list_add_tail(&eth->free_frames, &frame->node);
103        eth->txd[n].info = 0;
104        n = (n + 1) & (ETH_TXBUF_COUNT - 1);
105    }
106    eth->tx_rd_ptr = n;
107}
108
109status_t eth_tx(ethdev_t* eth, const void* data, size_t len) {
110    if (len > ETH_TXBUF_DSIZE) {
111        printf("intel-eth: unsupported packet length %zu\n", len);
112        return ZX_ERR_INVALID_ARGS;
113    }
114
115    zx_status_t status = ZX_OK;
116
117    mtx_lock(&eth->send_lock);
118
119    reap_tx_buffers(eth);
120
121    // obtain buffer, copy into it, setup descriptor
122    framebuf_t *frame = list_remove_head_type(&eth->free_frames, framebuf_t, node);
123    if (frame == NULL) {
124        status = ZX_ERR_NO_RESOURCES;
125        goto out;
126    }
127
128    uint32_t n = eth->tx_wr_ptr;
129    memcpy(frame->data, data, len);
130    // Pad out short packets.
131    if (len < 60) {
132      memset(frame->data + len, 0, 60 - len);
133      len = 60;
134    }
135    eth->txd[n].addr = frame->phys;
136    eth->txd[n].info = IE_TXD_LEN(len) | IE_TXD_EOP | IE_TXD_IFCS | IE_TXD_RS;
137    list_add_tail(&eth->busy_frames, &frame->node);
138
139    // inform hw of buffer availability
140    n = (n + 1) & (ETH_TXBUF_COUNT - 1);
141    eth->tx_wr_ptr = n;
142    writel(n, IE_TDT);
143
144out:
145    mtx_unlock(&eth->send_lock);
146    return status;
147}
148
149// Returns the number of Tx packets in the hw queue
150size_t eth_tx_queued(ethdev_t* eth) {
151    reap_tx_buffers(eth);
152    return ((eth->tx_wr_ptr + ETH_TXBUF_COUNT) - eth->tx_rd_ptr) & (ETH_TXBUF_COUNT - 1);
153}
154
155void eth_enable_tx(ethdev_t* eth) {
156    uint32_t tctl = readl(IE_TCTL);
157    writel(tctl | IE_TCTL_EN, IE_TCTL);
158}
159
160void eth_disable_tx(ethdev_t* eth) {
161    uint32_t tctl = readl(IE_TCTL);
162    writel(tctl & ~IE_TCTL_EN, IE_TCTL);
163}
164
165void eth_start_promisc(ethdev_t* eth) {
166    uint32_t rctl = readl(IE_RCTL);
167    writel(rctl | IE_RCTL_UPE, IE_RCTL);
168}
169
170void eth_stop_promisc(ethdev_t* eth) {
171    uint32_t rctl = readl(IE_RCTL);
172    writel(rctl & ~IE_RCTL_UPE, IE_RCTL);
173}
174
175static zx_status_t wait_for_mdic(ethdev_t* eth, uint32_t* reg_value) {
176    uint32_t mdic;
177    uint32_t iterations = 0;
178    do {
179        nanosleep(50);
180        mdic = readl(IE_MDIC);
181        if (mdic & IE_MDIC_R) {
182            goto success;
183        }
184        iterations++;
185    } while (!(mdic & IE_MDIC_R) && (iterations < 100));
186    printf("intel-eth: timed out waiting for MDIC to be ready\n");
187    return ZX_ERR_TIMED_OUT;
188
189success:
190    if (reg_value) {
191        *reg_value = mdic;
192    }
193    return ZX_OK;
194}
195
196static zx_status_t phy_read(ethdev_t* eth, uint8_t phyadd, uint8_t regadd, uint16_t* result) {
197    uint32_t mdic = IE_MDIC_PUT_PHYADD(phyadd) |
198                    IE_MDIC_PUT_REGADD(regadd) |
199                    IE_MDIC_OP_READ;
200    writel(mdic, IE_MDIC);
201    zx_status_t status = wait_for_mdic(eth, &mdic);
202    if (status == ZX_OK) {
203        *result = IE_MDIC_GET_DATA(mdic);
204    }
205    return status;
206}
207
208static zx_status_t phy_write(ethdev_t* eth, uint8_t phyadd, uint8_t regadd, uint16_t value) {
209    uint32_t mdic = IE_MDIC_PUT_DATA(value) |
210                    IE_MDIC_PUT_PHYADD(phyadd) |
211                    IE_MDIC_PUT_REGADD(regadd) |
212                    IE_MDIC_OP_WRITE;
213    writel(mdic, IE_MDIC);
214    return wait_for_mdic(eth, NULL);
215}
216
217static zx_status_t get_phy_addr(ethdev_t* eth, uint8_t* phy_addr) {
218    if (eth->phy_addr != 0) {
219        *phy_addr = eth->phy_addr;
220    }
221    for (uint8_t addr = 1; addr <= IE_MAX_PHY_ADDR; addr++) {
222        uint16_t pid;
223        zx_status_t status = phy_read(eth, addr, IE_PHY_PID, &pid);
224        // TODO: Identify the PHY more precisely
225        if (status == ZX_OK && pid != 0) {
226            *phy_addr = pid;
227            return ZX_OK;
228        }
229    }
230    printf("intel-eth: unable to identify valid PHY address\n");
231    return ZX_ERR_NOT_FOUND;
232}
233
234zx_status_t eth_enable_phy(ethdev_t* eth) {
235    uint8_t phy_addr;
236    zx_status_t status = get_phy_addr(eth, &phy_addr);
237    if (status != ZX_OK) {
238        return status;
239    }
240
241    uint16_t phy_ctrl;
242    status = phy_read(eth, phy_addr, IE_PHY_PCTRL, &phy_ctrl);
243    if (status != ZX_OK) {
244        return status;
245    }
246
247    if (phy_ctrl & IE_PHY_PCTRL_POWER_DOWN) {
248        return phy_write(eth, phy_addr, IE_PHY_PCTRL, phy_ctrl & ~IE_PHY_PCTRL_POWER_DOWN);
249    }
250    return ZX_OK;
251}
252
253zx_status_t eth_disable_phy(ethdev_t* eth) {
254    uint8_t phy_addr;
255    zx_status_t status = get_phy_addr(eth, &phy_addr);
256    if (status != ZX_OK) {
257        return status;
258    }
259
260    uint16_t phy_ctrl;
261    status = phy_read(eth, phy_addr, IE_PHY_PCTRL, &phy_ctrl);
262    if (status != ZX_OK) {
263        return status;
264    }
265    return phy_write(eth, phy_addr, IE_PHY_PCTRL, phy_ctrl | IE_PHY_PCTRL_POWER_DOWN);
266}
267
268status_t eth_reset_hw(ethdev_t* eth) {
269    // TODO: don't rely on bootloader having initialized the
270    // controller in order to obtain the mac address
271    uint32_t n;
272    n = readl(IE_RAL(0));
273    memcpy(eth->mac + 0, &n, 4);
274    n = readl(IE_RAH(0));
275    memcpy(eth->mac + 4, &n, 2);
276    printf("eth: mac: %02x:%02x:%02x:%02x:%02x:%02x\n",
277           eth->mac[0],eth->mac[1],eth->mac[2],
278           eth->mac[3],eth->mac[4],eth->mac[5]);
279
280    // disable all interrupts
281    if (eth->pci_did == IE_DID_I211_AT) {
282        writel(0, IE_IAM);
283    }
284    writel(0xffffffff, IE_IMC);
285
286    // disable tx/rx
287    writel(0, IE_RCTL);
288    writel(IE_TCTL_PSP, IE_TCTL);
289
290    // global reset
291    uint32_t reg = readl(IE_CTRL);
292    writel(reg | IE_CTRL_RST, IE_CTRL);
293
294    if (eth->pci_did == IE_DID_I211_AT) {
295        usleep(20);
296        reg = readl(IE_STATUS);
297        if (!(reg & IE_STATUS_PF_RST_DONE)) {
298            printf("eth: reset failed (1)\n");
299            return ZX_ERR_BAD_STATE;
300        }
301        reg = readl(IE_EEC);
302        if (!(reg & IE_EEC_AUTO_RD)) {
303            printf("eth: reset failed (2)\n");
304            return ZX_ERR_BAD_STATE;
305        }
306    } else {
307        usleep(5);
308
309        if (readl(IE_CTRL) & IE_CTRL_RST) {
310            printf("eth: reset failed\n");
311            return ZX_ERR_BAD_STATE;
312        }
313    }
314
315    // disable all interrupts
316    if (eth->pci_did == IE_DID_I211_AT) {
317        writel(0, IE_IAM);
318    }
319    writel(0xffffffff, IE_IMC);
320
321    // clear any pending interrupts
322    readl(IE_ICR);
323
324    return ZX_OK;
325}
326
327void eth_init_hw(ethdev_t* eth) {
328    //TODO: tune RXDCTL and TXDCTL settings
329    //TODO: TCTL COLD should be based on link state
330    //TODO: use address filtering for multicast
331
332    // set link up (Must be set to enable communications between MAC and PHY.)
333    uint32_t reg = readl(IE_CTRL);
334    writel(reg | IE_CTRL_SLU, IE_CTRL);
335
336    usleep(15);
337
338    // setup rx ring
339    eth->rx_rd_ptr = 0;
340    writel(eth->rxd_phys, IE_RDBAL);
341    writel(eth->rxd_phys >> 32, IE_RDBAH);
342    writel(ETH_RXBUF_COUNT * 16, IE_RDLEN);
343
344    reg = IE_RXDCTL_PTHRESH(12) | IE_RXDCTL_HTHRESH(10) | IE_RXDCTL_WTHRESH(1);
345    if (eth->pci_did == IE_DID_I211_AT) {
346        reg |= IE_RXDCTL_ENABLE;
347    } else {
348        reg |= IE_RXDCTL_GRAN;
349    }
350    writel(reg, IE_RXDCTL);
351
352    // wait for enable to complete
353    if (eth->pci_did == IE_DID_I211_AT) {
354        while (!(readl(IE_RXDCTL) & IE_RXDCTL_ENABLE)) {
355        }
356    }
357
358    writel(ETH_RXBUF_COUNT - 1, IE_RDT);
359    writel(IE_RCTL_BSIZE2048 | IE_RCTL_DPF | IE_RCTL_SECRC |
360           IE_RCTL_BAM | IE_RCTL_MPE | IE_RCTL_EN,
361           IE_RCTL);
362
363    // setup tx ring
364    eth->tx_wr_ptr = 0;
365    eth->tx_rd_ptr = 0;
366    writel(eth->txd_phys, IE_TDBAL);
367    writel(eth->txd_phys >> 32, IE_TDBAH);
368    writel(ETH_TXBUF_COUNT * 16, IE_TDLEN);
369
370    reg = IE_TXDCTL_WTHRESH(1);
371    if (eth->pci_did == IE_DID_I211_AT) {
372        reg |= IE_TXDCTL_ENABLE;
373    } else {
374        reg |= IE_TXDCTL_GRAN;
375    }
376    writel(reg, IE_TXDCTL);
377
378    // wait for enable to complete
379    if (eth->pci_did == IE_DID_I211_AT) {
380        while (!(readl(IE_TXDCTL) & IE_TXDCTL_ENABLE)) {
381        }
382    }
383
384    if (eth->pci_did == IE_DID_I211_AT) {
385        reg = IE_TCTL_CT(15) | IE_TCTL_BST(64) | IE_TCTL_PSP | IE_TCTL_EN;
386    } else {
387        reg = readl(IE_TCTL) & IE_TCTL_RESERVED;
388        reg |= IE_TCTL_CT(15) | IE_TCTL_COLD_FD | IE_TCTL_EN;
389    }
390    writel(reg, IE_TCTL);
391
392    // enable interrupts
393    if (eth->pci_did == IE_DID_I211_AT) {
394        // Receiver Descriptor Write Back & Link Status Change interrupts
395        writel(IE_INT_RXDW | IE_INT_LSC, IE_IMS);
396    } else {
397        // enable rx & link status change irqs
398        writel(IE_INT_RXT0 | IE_INT_LSC, IE_IMS);
399    }
400}
401
402void eth_setup_buffers(ethdev_t* eth, void* iomem, zx_paddr_t iophys) {
403    printf("eth: iomem @%p (phys %" PRIxPTR ")\n", iomem, iophys);
404
405    list_initialize(&eth->free_frames);
406    list_initialize(&eth->busy_frames);
407
408    eth->rxd = iomem;
409    eth->rxd_phys = iophys;
410    iomem += ETH_DRING_SIZE;
411    iophys += ETH_DRING_SIZE;
412    memset(eth->rxd, 0, ETH_DRING_SIZE);
413
414    eth->txd = iomem;
415    eth->txd_phys = iophys;
416    iomem += ETH_DRING_SIZE;
417    iophys += ETH_DRING_SIZE;
418    memset(eth->txd, 0, ETH_DRING_SIZE);
419
420    eth->rxb = iomem;
421    eth->rxb_phys = iophys;
422    iomem += ETH_RXBUF_SIZE * ETH_RXBUF_COUNT;
423    iophys += ETH_RXBUF_SIZE * ETH_RXBUF_COUNT;
424
425    for (int n = 0; n < ETH_RXBUF_COUNT; n++) {
426        eth->rxd[n].addr = eth->rxb_phys + ETH_RXBUF_SIZE * n;
427    }
428    for (int n = 0; n < ETH_TXBUF_COUNT - 1; n++) {
429        framebuf_t *txb = iomem;
430        txb->phys = iophys + ETH_TXBUF_HSIZE;
431        txb->size = ETH_TXBUF_SIZE - ETH_TXBUF_HSIZE;
432        txb->data = iomem + ETH_TXBUF_HSIZE;
433        list_add_tail(&eth->free_frames, &txb->node);
434
435        iomem += ETH_TXBUF_SIZE;
436        iophys += ETH_TXBUF_SIZE;
437    }
438}
439