blob: 066945b143d59e08671ca8ae9453cfd60202fe52 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2011 IBM Corporation
* All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/bsd-license.php
*
* Contributors:
* IBM Corporation - initial implementation
*****************************************************************************/
#include <stdint.h>
#include "netdriver_int.h"
#include "libhvcall.h"
static snk_kernel_t *snk_kernel_interface;
static snk_module_t *snk_module_interface;
static unsigned int g_reg;
#define printk(fmt...) do { snk_kernel_interface->print(fmt); } while(0)
#define malloc(args...) snk_kernel_interface->k_malloc(args)
#define malloc_aligned(args...) snk_kernel_interface->k_malloc_aligned(args)
#define free(args...) do { snk_kernel_interface->k_free(args); } while(0)
#define dprintk(fmt...)
//#define dprintk(fmt...) printk(fmt)
/* *** WARNING: We pass our addresses as-is as DMA addresses,
* we -do- rely on the forth code to have enabled TCE bypass
* on our device !
*/
#define vaddr_to_dma(vaddr) ((uint64_t)vaddr)
struct ibmveth_buf_desc_fields {
uint32_t flags_len;
#define IBMVETH_BUF_VALID 0x80000000
#define IBMVETH_BUF_TOGGLE 0x40000000
#define IBMVETH_BUF_NO_CSUM 0x02000000
#define IBMVETH_BUF_CSUM_GOOD 0x01000000
#define IBMVETH_BUF_LEN_MASK 0x00FFFFFF
uint32_t address;
};
union ibmveth_buf_desc {
uint64_t desc;
struct ibmveth_buf_desc_fields fields;
};
struct ibmveth_rx_q_entry {
uint32_t flags_off;
#define IBMVETH_RXQ_TOGGLE 0x80000000
#define IBMVETH_RXQ_TOGGLE_SHIFT 31
#define IBMVETH_RXQ_VALID 0x40000000
#define IBMVETH_RXQ_NO_CSUM 0x02000000
#define IBMVETH_RXQ_CSUM_GOOD 0x01000000
#define IBMVETH_RXQ_OFF_MASK 0x0000FFFF
uint32_t length;
uint64_t correlator;
};
static void *buffer_list;
static void *filter_list;
static uint64_t *rx_bufs;
static uint64_t *rx_bufs_aligned;
static uint32_t cur_rx_toggle;
static uint32_t cur_rx_index;
#define RX_QUEUE_SIZE 16
#define RX_BUF_SIZE 2048
#define RX_BUF_MULT (RX_BUF_SIZE >> 3)
static struct ibmveth_rx_q_entry *rx_queue;
static char * memcpy( char *dest, const char *src, size_t n )
{
char *ret = dest;
while( n-- ) {
*dest++ = *src++;
}
return( ret );
}
static inline uint64_t *veth_get_rx_buf(unsigned int i)
{
return &rx_bufs_aligned[i * RX_BUF_MULT];
}
static int veth_init(void)
{
char *mac_addr = snk_module_interface->mac_addr;
union ibmveth_buf_desc rxq_desc;
unsigned long rx_queue_len = sizeof(struct ibmveth_rx_q_entry) *
RX_QUEUE_SIZE;
unsigned int i;
long rc;
dprintk("veth_init(%02x:%02x:%02x:%02x:%02x:%02x)\n",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
if (snk_module_interface->running != 0)
return 0;
cur_rx_toggle = IBMVETH_RXQ_TOGGLE;
cur_rx_index = 0;
buffer_list = malloc_aligned(8192, 4096);
filter_list = buffer_list + 4096;
rx_queue = malloc_aligned(rx_queue_len, 16);
rx_bufs = malloc(2048 * RX_QUEUE_SIZE + 4);
if (!buffer_list || !filter_list || !rx_queue || !rx_bufs) {
printk("veth: Failed to allocate memory !\n");
goto fail;
}
rx_bufs_aligned = (uint64_t *)(((uint64_t)rx_bufs | 3) + 1);
rxq_desc.fields.address = vaddr_to_dma(rx_queue);
rxq_desc.fields.flags_len = IBMVETH_BUF_VALID | rx_queue_len;
rc = h_register_logical_lan(g_reg,
vaddr_to_dma(buffer_list),
rxq_desc.desc,
vaddr_to_dma(filter_list),
(*(uint64_t *)mac_addr) >> 16);
if (rc != H_SUCCESS) {
printk("veth: Error %ld registering interface !\n", rc);
goto fail;
}
for (i = 0; i < RX_QUEUE_SIZE; i++) {
uint64_t *buf = veth_get_rx_buf(i);
union ibmveth_buf_desc desc;
*buf = (uint64_t)buf;
desc.fields.address = vaddr_to_dma(buf);
desc.fields.flags_len = IBMVETH_BUF_VALID | RX_BUF_SIZE;
h_add_logical_lan_buffer(g_reg, desc.desc);
}
snk_module_interface->running = 1;
return 0;
fail:
if (filter_list)
free(filter_list);
if (buffer_list)
free(buffer_list);
if (rx_queue)
free(rx_queue);
if (rx_bufs)
free(rx_bufs);
return -1;
}
static int veth_term(void)
{
dprintk("veth_term()\n");
if (snk_module_interface->running == 0)
return 0;
h_free_logical_lan(g_reg);
if (filter_list)
free(filter_list);
if (buffer_list)
free(buffer_list);
if (rx_queue)
free(rx_queue);
if (rx_bufs)
free(rx_bufs);
snk_module_interface->running = 0;
return 0;
}
static int veth_xmit(char *f_buffer_pc, int f_len_i)
{
union ibmveth_buf_desc tx_desc;
long rc;
dprintk("veth_xmit(packet at %p, %d bytes)\n", f_buffer_pc, f_len_i);
tx_desc.fields.address = vaddr_to_dma(f_buffer_pc);
tx_desc.fields.flags_len = IBMVETH_BUF_VALID | f_len_i;
rc = hv_send_logical_lan(g_reg, tx_desc.desc, 0, 0, 0, 0, 0);
if (rc != H_SUCCESS) {
printk("veth: Error %ld sending packet !\n", rc);
return -1;
}
return f_len_i;
}
static int veth_receive(char *f_buffer_pc, int f_len_i)
{
int packet = 0;
dprintk("veth_receive()\n");
while(!packet) {
struct ibmveth_rx_q_entry *desc = &rx_queue[cur_rx_index];
union ibmveth_buf_desc bdesc;
void *buf;
buf = (void *)desc->correlator;
if ((desc->flags_off & IBMVETH_RXQ_TOGGLE) != cur_rx_toggle)
break;
if (!(desc->flags_off & IBMVETH_RXQ_VALID))
goto recycle;
if (desc->length > f_len_i) {
printk("veth: Dropping too big packet [%d bytes]\n",
desc->length);
goto recycle;
}
packet = desc->length;
memcpy(f_buffer_pc,
buf + (desc->flags_off & IBMVETH_RXQ_OFF_MASK), packet);
recycle:
bdesc.fields.address = vaddr_to_dma(buf);
bdesc.fields.flags_len = IBMVETH_BUF_VALID | RX_BUF_SIZE;
h_add_logical_lan_buffer(g_reg, bdesc.desc);
cur_rx_index = (cur_rx_index + 1) % RX_QUEUE_SIZE;
if (cur_rx_index == 0)
cur_rx_toggle ^= IBMVETH_RXQ_TOGGLE;
}
return packet;
}
static int veth_ioctl(int request, void* data)
{
dprintk("veth_ioctl()\n");
return 0;
}
static snk_module_t veth_interface = {
.version = 1,
.type = MOD_TYPE_NETWORK,
.running = 0,
.init = veth_init,
.term = veth_term,
.write = veth_xmit,
.read = veth_receive,
.ioctl = veth_ioctl
};
static int check_driver(vio_config_t *conf)
{
if (snk_kernel_interface->strcmp(conf->compat, "IBM,l-lan")) {
dprintk( "veth: netdevice not supported\n" );
return -1;
}
g_reg = conf->reg[0];
return 0;
}
snk_module_t* veth_module_init(snk_kernel_t *snk_kernel_int, vio_config_t *conf)
{
snk_kernel_interface = snk_kernel_int;
snk_module_interface = &veth_interface;
if (snk_kernel_int->version != snk_module_interface->version)
return 0;
/* Check if this is the right driver */
if (check_driver(conf) < 0)
return 0;
snk_module_interface->link_addr = module_init;
return snk_module_interface;
}