aboutsummaryrefslogtreecommitdiff
path: root/stand/powerpc/ps3/ps3net.c
diff options
context:
space:
mode:
Diffstat (limited to 'stand/powerpc/ps3/ps3net.c')
-rw-r--r--stand/powerpc/ps3/ps3net.c278
1 files changed, 278 insertions, 0 deletions
diff --git a/stand/powerpc/ps3/ps3net.c b/stand/powerpc/ps3/ps3net.c
new file mode 100644
index 000000000000..142eab8243a6
--- /dev/null
+++ b/stand/powerpc/ps3/ps3net.c
@@ -0,0 +1,278 @@
+/*-
+ * Copyright (C) 2010 Nathan Whitehorn
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 TOOLS GMBH 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+
+#define _KERNEL
+#include <machine/cpufunc.h>
+
+#include <stand.h>
+#include <net.h>
+#include <netif.h>
+#include "bootstrap.h"
+#include "lv1call.h"
+#include "ps3.h"
+
+#define GELIC_DESCR_OWNED 0xa0000000
+#define GELIC_CMDSTAT_NOIPSEC 0x00080000
+#define GELIC_CMDSTAT_LAST 0x00040000
+#define GELIC_RXERRORS 0x7def8000
+
+#define GELIC_POLL_PERIOD 100 /* microseconds */
+
+static int ps3net_probe(struct netif *, void *);
+static int ps3net_match(struct netif *, void *);
+static void ps3net_init(struct iodesc *, void *);
+static int ps3net_get(struct iodesc *, void *, size_t, time_t);
+static int ps3net_put(struct iodesc *, void *, size_t);
+static void ps3net_end(struct netif *);
+
+struct netif_stats ps3net_stats[1];
+struct netif_dif ps3net_ifs[] = {{0, 1, ps3net_stats, 0}};
+
+/* XXX: Get from firmware, not hardcoding */
+static int busid = 1;
+static int devid = 0;
+static int vlan;
+static uint64_t dma_base;
+
+struct gelic_dmadesc {
+ uint32_t paddr;
+ uint32_t len;
+ uint32_t next;
+ uint32_t cmd_stat;
+ uint32_t result_size;
+ uint32_t valid_size;
+ uint32_t data_stat;
+ uint32_t rxerror;
+};
+
+struct netif_driver ps3net = {
+ "net",
+ ps3net_match,
+ ps3net_probe,
+ ps3net_init,
+ ps3net_get,
+ ps3net_put,
+ ps3net_end,
+ ps3net_ifs, 1
+};
+
+static int
+ps3net_match(struct netif *nif, void *machdep_hint)
+{
+ return (1);
+}
+
+static int
+ps3net_probe(struct netif *nif, void *machdep_hint)
+{
+ return (0);
+}
+
+static int
+ps3net_put(struct iodesc *desc, void *pkt, size_t len)
+{
+ volatile static struct gelic_dmadesc txdesc __aligned(32);
+ volatile static char txbuf[1536] __aligned(128);
+ size_t sendlen;
+ int err;
+
+#if defined(NETIF_DEBUG)
+ struct ether_header *eh;
+
+ printf("net_put: desc %p, pkt %p, len %d\n", desc, pkt, len);
+ eh = pkt;
+ printf("dst: %s ", ether_sprintf(eh->ether_dhost));
+ printf("src: %s ", ether_sprintf(eh->ether_shost));
+ printf("type: 0x%x\n", eh->ether_type & 0xffff);
+#endif
+
+ while (txdesc.cmd_stat & GELIC_DESCR_OWNED) {
+ printf("Stalled XMIT!\n");
+ delay(10);
+ }
+
+ /*
+ * We must add 4 extra bytes to this packet to store the destination
+ * VLAN.
+ */
+ memcpy(txbuf, pkt, 12);
+ sendlen = 12;
+
+ if (vlan >= 0) {
+ sendlen += 4;
+ ((uint8_t *)txbuf)[12] = 0x81;
+ ((uint8_t *)txbuf)[13] = 0x00;
+ ((uint8_t *)txbuf)[14] = vlan >> 8;
+ ((uint8_t *)txbuf)[15] = vlan & 0xff;
+ }
+ memcpy((void *)txbuf + sendlen, pkt + 12, len - 12);
+ sendlen += len - 12;
+
+ bzero(&txdesc, sizeof(txdesc));
+ txdesc.paddr = dma_base + (uint32_t)txbuf;
+ txdesc.len = sendlen;
+ txdesc.cmd_stat = GELIC_CMDSTAT_NOIPSEC | GELIC_CMDSTAT_LAST |
+ GELIC_DESCR_OWNED;
+
+ powerpc_sync();
+
+ do {
+ err = lv1_net_start_tx_dma(busid, devid,
+ dma_base + (uint32_t)&txdesc, 0);
+ delay(1);
+ if (err != 0)
+ printf("TX Error: %d\n",err);
+ } while (err != 0);
+
+ return (len);
+}
+
+static int
+ps3net_get(struct iodesc *desc, void *pkt, size_t len, time_t timeout)
+{
+ volatile static struct gelic_dmadesc rxdesc __aligned(32);
+ volatile static char rxbuf[1536] __aligned(128);
+ int err = 0;
+
+ if (len == 0)
+ goto restartdma;
+
+ timeout *= 1000000; /* convert to microseconds */
+ while (rxdesc.cmd_stat & GELIC_DESCR_OWNED) {
+ if (timeout < GELIC_POLL_PERIOD)
+ return (ETIMEDOUT);
+ delay(GELIC_POLL_PERIOD);
+ timeout -= GELIC_POLL_PERIOD;
+ }
+
+ delay(200);
+ if (rxdesc.rxerror & GELIC_RXERRORS) {
+ err = -1;
+ goto restartdma;
+ }
+
+ /*
+ * Copy the packet to the receive buffer, leaving out the
+ * 2 byte VLAN header.
+ */
+ len = min(len, rxdesc.valid_size - 2);
+ memcpy(pkt, (u_char *)rxbuf + 2, len);
+ err = len;
+
+#if defined(NETIF_DEBUG)
+{
+ struct ether_header *eh;
+
+ printf("net_get: desc %p, pkt %p, len %d\n", desc, pkt, len);
+ eh = pkt;
+ printf("dst: %s ", ether_sprintf(eh->ether_dhost));
+ printf("src: %s ", ether_sprintf(eh->ether_shost));
+ printf("type: 0x%x\n", eh->ether_type & 0xffff);
+}
+#endif
+
+restartdma:
+ lv1_net_stop_rx_dma(busid, devid, 0);
+ powerpc_sync();
+
+ bzero(&rxdesc, sizeof(rxdesc));
+ rxdesc.paddr = dma_base + (uint32_t)rxbuf;
+ rxdesc.len = sizeof(rxbuf);
+ rxdesc.next = 0;
+ rxdesc.cmd_stat = GELIC_DESCR_OWNED;
+ powerpc_sync();
+
+ lv1_net_start_rx_dma(busid, devid, dma_base + (uint32_t)&rxdesc, 0);
+
+ return (err);
+}
+
+static void
+ps3net_init(struct iodesc *desc, void *machdep_hint)
+{
+ uint64_t mac, val;
+ int i,err;
+
+ err = lv1_open_device(busid, devid, 0);
+
+ lv1_net_stop_tx_dma(busid, devid, 0);
+ lv1_net_stop_rx_dma(busid, devid, 0);
+
+ /*
+ * Wait for link to come up
+ */
+
+ for (i = 0; i < 1000; i++) {
+ lv1_net_control(busid, devid, GELIC_GET_LINK_STATUS, 2, 0,
+ 0, &val);
+ if (val & GELIC_LINK_UP)
+ break;
+ delay(500);
+ }
+
+ /*
+ * Set up DMA IOMMU entries
+ */
+
+ err = lv1_setup_dma(busid, devid, &dma_base);
+
+ /*
+ * Get MAC address and VLAN IDs
+ */
+
+ lv1_net_control(busid, devid, GELIC_GET_MAC_ADDRESS, 0, 0, 0, &mac);
+ bcopy(&((uint8_t *)&mac)[2], desc->myea, sizeof(desc->myea));
+
+ vlan = -1;
+ err = lv1_net_control(busid, devid, GELIC_GET_VLAN_ID, 2, 0,
+ 0, &val);
+ if (err == 0)
+ vlan = val;
+
+ /*
+ * Start RX DMA engine
+ */
+
+ ps3net_get(NULL, NULL, 0, 0);
+}
+
+static void
+ps3net_end(struct netif *nif)
+{
+ lv1_close_device(busid, devid);
+}
+