/* sunvnet.c: Sun LDOM Virtual Network Driver.
*
* Copyright (C) 2007, 2008 David S. Miller <davem@davemloft.net>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/etherdevice.h>
#include <linux/mutex.h>
#include <asm/vio.h>
#include <asm/ldc.h>
#include "sunvnet.h"
#define DRV_MODULE_NAME "sunvnet"
#define PFX DRV_MODULE_NAME ": "
#define DRV_MODULE_VERSION "1.0"
#define DRV_MODULE_RELDATE "June 25, 2007"
static char version[] __devinitdata =
DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n";
MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
MODULE_DESCRIPTION("Sun LDOM virtual network driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_MODULE_VERSION);
/* Ordered from largest major to lowest */
static struct vio_version vnet_versions[] = {
{ .major = 1, .minor = 0 },
};
static inline u32 vnet_tx_dring_avail(struct vio_dring_state *dr)
{
return vio_dring_avail(dr, VNET_TX_RING_SIZE);
}
static int vnet_handle_unknown(struct vnet_port *port, void *arg)
{
struct vio_msg_tag *pkt = arg;
printk(KERN_ERR PFX "Received unknown msg [%02x:%02x:%04x:%08x]\n",
pkt->type, pkt->stype, pkt->stype_env, pkt->sid);
printk(KERN_ERR PFX "Resetting connection.\n");
ldc_disconnect(port->vio.lp);
return -ECONNRESET;
}
static int vnet_send_attr(struct vio_driver_state *vio)
{
struct vnet_port *port = to_vnet_port(vio);
struct net_device *dev = port->vp->dev;
struct vio_net_attr_info pkt;
int i;
memset(&pkt, 0, sizeof(pkt));
pkt.tag.type = VIO_TYPE_CTRL;
pkt.tag.stype = VIO_SUBTYPE_INFO;
pkt.tag.stype_env = VIO_ATTR_INFO;
pkt.tag.sid = vio_send_sid(vio);
pkt.xfer_mode = VIO_DRING_MODE;
pkt.addr_type = VNET_ADDR_ETHERMAC;
pkt.ack_freq = 0;
for (i = 0; i < 6; i++)
pkt.addr |= (u64)dev->dev_addr[i] << ((5 - i) * 8);
pkt.mtu = ETH_FRAME_LEN;
viodbg(HS, "SEND NET ATTR xmode[0x%x] atype[0x%x] addr[%llx] "
"ackfreq[%u] mtu[%llu]\n",
pkt.xfer_mode, pkt.addr_type,
(unsigned long long) pkt.addr,
pkt.ack_freq,
(unsigned long long) pkt.mtu);
return vio_ldc_send(vio, &pkt, sizeof(pkt));
}
static int handle_attr_info(struct vio_driver_state *vio,
struct vio_net_attr_info *pkt)
{
viodbg(HS, "GOT NET ATTR INFO xmode[0x%x] atype[0x%x] addr[%llx] "
"ackfreq[%u] mtu[%llu]\n",
pkt->xfer_mode, pkt->addr_type,
(unsigned long long) pkt->addr,
pkt->ack_freq,
(unsigned long long) pkt->mtu);
pkt->tag.sid = vio_send_sid(vio);
if (pkt->xfer_mode != VIO_DRING_MODE ||
pkt->addr_type != VNET_ADDR_ETHERMAC ||
pkt->mtu != ETH_FRAME_LEN) {
viodbg(HS, "SEND NET ATTR NACK\n");
pkt->tag.stype = VIO_SUBTYPE_NACK;
(void) vio_ldc_send(vio, pkt, sizeof(*pkt));
return -ECONNRESET;
} else {
viodbg(HS, "SEND NET ATTR ACK\n");
pkt->tag.stype = VIO_SUBTYPE_ACK;
return vio_ldc_send(vio, pkt, sizeof(*pkt));
}
}
static int handle_attr_ack(struct vio_driver_state *vio,
struct vio_net_attr_info *pkt)
{
viodbg(HS, "GOT NET ATTR ACK\n");
return 0;
}
static int handle_attr_nack(struct vio_driver_state *vio,
struct vio_net_attr_info *pkt)
{
viodbg(HS, "GOT NET ATTR NACK\n");
return -ECONNRESET;
}
static int vnet_handle_attr(struct vio_driver_state *vio, void *arg)
{
struct vio_net_attr_info *pkt = arg;
switch (pkt->tag.stype) {
case VIO_SUBTYPE_INFO:
return handle_attr_info(vio, pkt);
case VIO_SUBTYPE_ACK:
return handle_attr_ack(vio, pkt);
case VIO_SUBTYPE_NACK:
return handle_attr_nack(vio, pkt);
default:
return -ECONNRESET;
}
}
static void vnet_handshake_complete(struct vio_driver_state *vio)
{
struct vio_dring_state *dr;
dr = &vio->drings[VIO_DRIVER_RX_RING];
dr->snd_nxt = dr->rcv_nxt = 1;
dr = &vio->drings[VIO_DRIVER_TX_RING];
dr->snd_nxt = dr->rcv_nxt = 1;
}
/* The hypervisor interface that implements copying to/from imported
* memory from another domain requires that copies are done to 8-byte
* aligned buffers, and that the lengths of such copies are also 8-byte
* multiples.
*
* So we align skb->data to an 8-byte multiple and pad-out the data
* area so we can round the copy length up to the next multiple of
* 8 for the copy.
*
* The transmitter puts the actual start of the packet 6 bytes into
* the buffer it sends over, so that the IP headers after the ethernet
* header are aligned properly. These 6 bytes are not in the descriptor
* length, they are simply implied. This offset is represented using
* the VNET_PACKET_SKIP macro.
*/
static struct sk_buff *alloc_and_align_skb(struct net_device *dev,
unsigned int len)
{
struct sk_buff *skb = netdev_alloc_skb(dev, len+VNET_PACKET_SKIP+8+8);
unsigned long addr, off;
if (unlikely(!skb))
return NULL;
addr = (unsigned long) skb->data;
off = ((addr + 7UL) & ~7UL) - addr;
if (off)
skb_reserve(skb, off);
return skb;
}
static int vnet_rx_one(struct vnet_port *port, unsigned int len,
struct ldc_trans_cookie *cookies, int ncookies)
{
struct net_device *dev = port->vp->dev;
unsigned int copy_len;
struct sk_buff *skb;
int err;
err = -EMSGSIZE;
if (unlikely(len < ETH_ZLEN || len > ETH_FRAME_LEN)) {
dev->stats.rx_length_errors++;
goto out_dropped;
}
skb = alloc_and_align_skb(dev, len);
err = -ENOMEM;
if (unlikely(!skb)) {
dev->stats.rx_missed_errors++;
goto out_dropped;
}
copy_len = (len + VNET_PACKET_SKIP + 7U) & ~7U;
skb_put(skb, copy_len);
err = ldc_copy(port->vio.lp, LDC_COPY_IN,
skb->data, copy_len, 0,
cookies, ncookies);
if (unlikely(err < 0)) {
dev->stats.rx_frame_errors++;
goto out_free_skb;
}
skb_pull(skb, VNET_PACKET_SKIP);
skb_trim(skb, len);
skb->protocol = eth_type_trans(skb, dev);
dev->stats.rx_packets++;
dev->stats.rx_bytes += len;
netif_rx(skb);
return 0;