diff options
Diffstat (limited to 'drivers/net/ethernet/ibm/iseries_veth.c')
-rw-r--r-- | drivers/net/ethernet/ibm/iseries_veth.c | 1710 |
1 files changed, 1710 insertions, 0 deletions
diff --git a/drivers/net/ethernet/ibm/iseries_veth.c b/drivers/net/ethernet/ibm/iseries_veth.c new file mode 100644 index 00000000000..4326681df38 --- /dev/null +++ b/drivers/net/ethernet/ibm/iseries_veth.c @@ -0,0 +1,1710 @@ +/* File veth.c created by Kyle A. Lucke on Mon Aug 7 2000. */ +/* + * IBM eServer iSeries Virtual Ethernet Device Driver + * Copyright (C) 2001 Kyle A. Lucke (klucke@us.ibm.com), IBM Corp. + * Substantially cleaned up by: + * Copyright (C) 2003 David Gibson <dwg@au1.ibm.com>, IBM Corporation. + * Copyright (C) 2004-2005 Michael Ellerman, IBM Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * + * This module implements the virtual ethernet device for iSeries LPAR + * Linux. It uses hypervisor message passing to implement an + * ethernet-like network device communicating between partitions on + * the iSeries. + * + * The iSeries LPAR hypervisor currently allows for up to 16 different + * virtual ethernets. These are all dynamically configurable on + * OS/400 partitions, but dynamic configuration is not supported under + * Linux yet. An ethXX network device will be created for each + * virtual ethernet this partition is connected to. + * + * - This driver is responsible for routing packets to and from other + * partitions. The MAC addresses used by the virtual ethernets + * contains meaning and must not be modified. + * + * - Having 2 virtual ethernets to the same remote partition DOES NOT + * double the available bandwidth. The 2 devices will share the + * available hypervisor bandwidth. + * + * - If you send a packet to your own mac address, it will just be + * dropped, you won't get it on the receive side. + * + * - Multicast is implemented by sending the frame frame to every + * other partition. It is the responsibility of the receiving + * partition to filter the addresses desired. + * + * Tunable parameters: + * + * VETH_NUMBUFFERS: This compile time option defaults to 120. It + * controls how much memory Linux will allocate per remote partition + * it is communicating with. It can be thought of as the maximum + * number of packets outstanding to a remote partition at a time. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/ethtool.h> +#include <linux/if_ether.h> +#include <linux/slab.h> + +#include <asm/abs_addr.h> +#include <asm/iseries/mf.h> +#include <asm/uaccess.h> +#include <asm/firmware.h> +#include <asm/iseries/hv_lp_config.h> +#include <asm/iseries/hv_types.h> +#include <asm/iseries/hv_lp_event.h> +#include <asm/iommu.h> +#include <asm/vio.h> + +#undef DEBUG + +MODULE_AUTHOR("Kyle Lucke <klucke@us.ibm.com>"); +MODULE_DESCRIPTION("iSeries Virtual ethernet driver"); +MODULE_LICENSE("GPL"); + +#define VETH_EVENT_CAP (0) +#define VETH_EVENT_FRAMES (1) +#define VETH_EVENT_MONITOR (2) +#define VETH_EVENT_FRAMES_ACK (3) + +#define VETH_MAX_ACKS_PER_MSG (20) +#define VETH_MAX_FRAMES_PER_MSG (6) + +struct veth_frames_data { + u32 addr[VETH_MAX_FRAMES_PER_MSG]; + u16 len[VETH_MAX_FRAMES_PER_MSG]; + u32 eofmask; +}; +#define VETH_EOF_SHIFT (32-VETH_MAX_FRAMES_PER_MSG) + +struct veth_frames_ack_data { + u16 token[VETH_MAX_ACKS_PER_MSG]; +}; + +struct veth_cap_data { + u8 caps_version; + u8 rsvd1; + u16 num_buffers; + u16 ack_threshold; + u16 rsvd2; + u32 ack_timeout; + u32 rsvd3; + u64 rsvd4[3]; +}; + +struct veth_lpevent { + struct HvLpEvent base_event; + union { + struct veth_cap_data caps_data; + struct veth_frames_data frames_data; + struct veth_frames_ack_data frames_ack_data; + } u; + +}; + +#define DRV_NAME "iseries_veth" +#define DRV_VERSION "2.0" + +#define VETH_NUMBUFFERS (120) +#define VETH_ACKTIMEOUT (1000000) /* microseconds */ +#define VETH_MAX_MCAST (12) + +#define VETH_MAX_MTU (9000) + +#if VETH_NUMBUFFERS < 10 +#define ACK_THRESHOLD (1) +#elif VETH_NUMBUFFERS < 20 +#define ACK_THRESHOLD (4) +#elif VETH_NUMBUFFERS < 40 +#define ACK_THRESHOLD (10) +#else +#define ACK_THRESHOLD (20) +#endif + +#define VETH_STATE_SHUTDOWN (0x0001) +#define VETH_STATE_OPEN (0x0002) +#define VETH_STATE_RESET (0x0004) +#define VETH_STATE_SENTMON (0x0008) +#define VETH_STATE_SENTCAPS (0x0010) +#define VETH_STATE_GOTCAPACK (0x0020) +#define VETH_STATE_GOTCAPS (0x0040) +#define VETH_STATE_SENTCAPACK (0x0080) +#define VETH_STATE_READY (0x0100) + +struct veth_msg { + struct veth_msg *next; + struct veth_frames_data data; + int token; + int in_use; + struct sk_buff *skb; + struct device *dev; +}; + +struct veth_lpar_connection { + HvLpIndex remote_lp; + struct delayed_work statemachine_wq; + struct veth_msg *msgs; + int num_events; + struct veth_cap_data local_caps; + + struct kobject kobject; + struct timer_list ack_timer; + + struct timer_list reset_timer; + unsigned int reset_timeout; + unsigned long last_contact; + int outstanding_tx; + + spinlock_t lock; + unsigned long state; + HvLpInstanceId src_inst; + HvLpInstanceId dst_inst; + struct veth_lpevent cap_event, cap_ack_event; + u16 pending_acks[VETH_MAX_ACKS_PER_MSG]; + u32 num_pending_acks; + + int num_ack_events; + struct veth_cap_data remote_caps; + u32 ack_timeout; + + struct veth_msg *msg_stack_head; +}; + +struct veth_port { + struct device *dev; + u64 mac_addr; + HvLpIndexMap lpar_map; + + /* queue_lock protects the stopped_map and dev's queue. */ + spinlock_t queue_lock; + HvLpIndexMap stopped_map; + + /* mcast_gate protects promiscuous, num_mcast & mcast_addr. */ + rwlock_t mcast_gate; + int promiscuous; + int num_mcast; + u64 mcast_addr[VETH_MAX_MCAST]; + + struct kobject kobject; +}; + +static HvLpIndex this_lp; +static struct veth_lpar_connection *veth_cnx[HVMAXARCHITECTEDLPS]; /* = 0 */ +static struct net_device *veth_dev[HVMAXARCHITECTEDVIRTUALLANS]; /* = 0 */ + +static int veth_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void veth_recycle_msg(struct veth_lpar_connection *, struct veth_msg *); +static void veth_wake_queues(struct veth_lpar_connection *cnx); +static void veth_stop_queues(struct veth_lpar_connection *cnx); +static void veth_receive(struct veth_lpar_connection *, struct veth_lpevent *); +static void veth_release_connection(struct kobject *kobject); +static void veth_timed_ack(unsigned long ptr); +static void veth_timed_reset(unsigned long ptr); + +/* + * Utility functions + */ + +#define veth_info(fmt, args...) \ + printk(KERN_INFO DRV_NAME ": " fmt, ## args) + +#define veth_error(fmt, args...) \ + printk(KERN_ERR DRV_NAME ": Error: " fmt, ## args) + +#ifdef DEBUG +#define veth_debug(fmt, args...) \ + printk(KERN_DEBUG DRV_NAME ": " fmt, ## args) +#else +#define veth_debug(fmt, args...) do {} while (0) +#endif + +/* You must hold the connection's lock when you call this function. */ +static inline void veth_stack_push(struct veth_lpar_connection *cnx, + struct veth_msg *msg) +{ + msg->next = cnx->msg_stack_head; + cnx->msg_stack_head = msg; +} + +/* You must hold the connection's lock when you call this function. */ +static inline struct veth_msg *veth_stack_pop(struct veth_lpar_connection *cnx) +{ + struct veth_msg *msg; + + msg = cnx->msg_stack_head; + if (msg) + cnx->msg_stack_head = cnx->msg_stack_head->next; + + return msg; +} + +/* You must hold the connection's lock when you call this function. */ +static inline int veth_stack_is_empty(struct veth_lpar_connection *cnx) +{ + return cnx->msg_stack_head == NULL; +} + +static inline HvLpEvent_Rc +veth_signalevent(struct veth_lpar_connection *cnx, u16 subtype, + HvLpEvent_AckInd ackind, HvLpEvent_AckType acktype, + u64 token, + u64 data1, u64 data2, u64 data3, u64 data4, u64 data5) +{ + return HvCallEvent_signalLpEventFast(cnx->remote_lp, + HvLpEvent_Type_VirtualLan, + subtype, ackind, acktype, + cnx->src_inst, + cnx->dst_inst, + token, data1, data2, data3, + data4, data5); +} + +static inline HvLpEvent_Rc veth_signaldata(struct veth_lpar_connection *cnx, + u16 subtype, u64 token, void *data) +{ + u64 *p = (u64 *) data; + + return veth_signalevent(cnx, subtype, HvLpEvent_AckInd_NoAck, + HvLpEvent_AckType_ImmediateAck, + token, p[0], p[1], p[2], p[3], p[4]); +} + +struct veth_allocation { + struct completion c; + int num; +}; + +static void veth_complete_allocation(void *parm, int number) +{ + struct veth_allocation *vc = (struct veth_allocation *)parm; + + vc->num = number; + complete(&vc->c); +} + +static int veth_allocate_events(HvLpIndex rlp, int number) +{ + struct veth_allocation vc = + { COMPLETION_INITIALIZER_ONSTACK(vc.c), 0 }; + + mf_allocate_lp_events(rlp, HvLpEvent_Type_VirtualLan, + sizeof(struct veth_lpevent), number, + &veth_complete_allocation, &vc); + wait_for_completion(&vc.c); + + return vc.num; +} + +/* + * sysfs support + */ + +struct veth_cnx_attribute { + struct attribute attr; + ssize_t (*show)(struct veth_lpar_connection *, char *buf); + ssize_t (*store)(struct veth_lpar_connection *, const char *buf); +}; + +static ssize_t veth_cnx_attribute_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct veth_cnx_attribute *cnx_attr; + struct veth_lpar_connection *cnx; + + cnx_attr = container_of(attr, struct veth_cnx_attribute, attr); + cnx = container_of(kobj, struct veth_lpar_connection, kobject); + + if (!cnx_attr->show) + return -EIO; + + return cnx_attr->show(cnx, buf); +} + +#define CUSTOM_CNX_ATTR(_name, _format, _expression) \ +static ssize_t _name##_show(struct veth_lpar_connection *cnx, char *buf)\ +{ \ + return sprintf(buf, _format, _expression); \ +} \ +struct veth_cnx_attribute veth_cnx_attr_##_name = __ATTR_RO(_name) + +#define SIMPLE_CNX_ATTR(_name) \ + CUSTOM_CNX_ATTR(_name, "%lu\n", (unsigned long)cnx->_name) + +SIMPLE_CNX_ATTR(outstanding_tx); +SIMPLE_CNX_ATTR(remote_lp); +SIMPLE_CNX_ATTR(num_events); +SIMPLE_CNX_ATTR(src_inst); +SIMPLE_CNX_ATTR(dst_inst); +SIMPLE_CNX_ATTR(num_pending_acks); +SIMPLE_CNX_ATTR(num_ack_events); +CUSTOM_CNX_ATTR(ack_timeout, "%d\n", jiffies_to_msecs(cnx->ack_timeout)); +CUSTOM_CNX_ATTR(reset_timeout, "%d\n", jiffies_to_msecs(cnx->reset_timeout)); +CUSTOM_CNX_ATTR(state, "0x%.4lX\n", cnx->state); +CUSTOM_CNX_ATTR(last_contact, "%d\n", cnx->last_contact ? + jiffies_to_msecs(jiffies - cnx->last_contact) : 0); + +#define GET_CNX_ATTR(_name) (&veth_cnx_attr_##_name.attr) + +static struct attribute *veth_cnx_default_attrs[] = { + GET_CNX_ATTR(outstanding_tx), + GET_CNX_ATTR(remote_lp), + GET_CNX_ATTR(num_events), + GET_CNX_ATTR(reset_timeout), + GET_CNX_ATTR(last_contact), + GET_CNX_ATTR(state), + GET_CNX_ATTR(src_inst), + GET_CNX_ATTR(dst_inst), + GET_CNX_ATTR(num_pending_acks), + GET_CNX_ATTR(num_ack_events), + GET_CNX_ATTR(ack_timeout), + NULL +}; + +static const struct sysfs_ops veth_cnx_sysfs_ops = { + .show = veth_cnx_attribute_show +}; + +static struct kobj_type veth_lpar_connection_ktype = { + .release = veth_release_connection, + .sysfs_ops = &veth_cnx_sysfs_ops, + .default_attrs = veth_cnx_default_attrs +}; + +struct veth_port_attribute { + struct attribute attr; + ssize_t (*show)(struct veth_port *, char *buf); + ssize_t (*store)(struct veth_port *, const char *buf); +}; + +static ssize_t veth_port_attribute_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct veth_port_attribute *port_attr; + struct veth_port *port; + + port_attr = container_of(attr, struct veth_port_attribute, attr); + port = container_of(kobj, struct veth_port, kobject); + + if (!port_attr->show) + return -EIO; + + return port_attr->show(port, buf); +} + +#define CUSTOM_PORT_ATTR(_name, _format, _expression) \ +static ssize_t _name##_show(struct veth_port *port, char *buf) \ +{ \ + return sprintf(buf, _format, _expression); \ +} \ +struct veth_port_attribute veth_port_attr_##_name = __ATTR_RO(_name) + +#define SIMPLE_PORT_ATTR(_name) \ + CUSTOM_PORT_ATTR(_name, "%lu\n", (unsigned long)port->_name) + +SIMPLE_PORT_ATTR(promiscuous); +SIMPLE_PORT_ATTR(num_mcast); +CUSTOM_PORT_ATTR(lpar_map, "0x%X\n", port->lpar_map); +CUSTOM_PORT_ATTR(stopped_map, "0x%X\n", port->stopped_map); +CUSTOM_PORT_ATTR(mac_addr, "0x%llX\n", port->mac_addr); + +#define GET_PORT_ATTR(_name) (&veth_port_attr_##_name.attr) +static struct attribute *veth_port_default_attrs[] = { + GET_PORT_ATTR(mac_addr), + GET_PORT_ATTR(lpar_map), + GET_PORT_ATTR(stopped_map), + GET_PORT_ATTR(promiscuous), + GET_PORT_ATTR(num_mcast), + NULL +}; + +static const struct sysfs_ops veth_port_sysfs_ops = { + .show = veth_port_attribute_show +}; + +static struct kobj_type veth_port_ktype = { + .sysfs_ops = &veth_port_sysfs_ops, + .default_attrs = veth_port_default_attrs +}; + +/* + * LPAR connection code + */ + +static inline void veth_kick_statemachine(struct veth_lpar_connection *cnx) +{ + schedule_delayed_work(&cnx->statemachine_wq, 0); +} + +static void veth_take_cap(struct veth_lpar_connection *cnx, + struct veth_lpevent *event) +{ + unsigned long flags; + + spin_lock_irqsave(&cnx->lock, flags); + /* Receiving caps may mean the other end has just come up, so + * we need to reload the instance ID of the far end */ + cnx->dst_inst = + HvCallEvent_getTargetLpInstanceId(cnx->remote_lp, + HvLpEvent_Type_VirtualLan); + + if (cnx->state & VETH_STATE_GOTCAPS) { + veth_error("Received a second capabilities from LPAR %d.\n", + cnx->remote_lp); + event->base_event.xRc = HvLpEvent_Rc_BufferNotAvailable; + HvCallEvent_ackLpEvent((struct HvLpEvent *) event); + } else { + memcpy(&cnx->cap_event, event, sizeof(cnx->cap_event)); + cnx->state |= VETH_STATE_GOTCAPS; + veth_kick_statemachine(cnx); + } + spin_unlock_irqrestore(&cnx->lock, flags); +} + +static void veth_take_cap_ack(struct veth_lpar_connection *cnx, + struct veth_lpevent *event) +{ + unsigned long flags; + + spin_lock_irqsave(&cnx->lock, flags); + if (cnx->state & VETH_STATE_GOTCAPACK) { + veth_error("Received a second capabilities ack from LPAR %d.\n", + cnx->remote_lp); + } else { + memcpy(&cnx->cap_ack_event, event, + sizeof(cnx->cap_ack_event)); + cnx->state |= VETH_STATE_GOTCAPACK; + veth_kick_statemachine(cnx); + } + spin_unlock_irqrestore(&cnx->lock, flags); +} + +static void veth_take_monitor_ack(struct veth_lpar_connection *cnx, + struct veth_lpevent *event) +{ + unsigned long flags; + + spin_lock_irqsave(&cnx->lock, flags); + veth_debug("cnx %d: lost connection.\n", cnx->remote_lp); + + /* Avoid kicking the statemachine once we're shutdown. + * It's unnecessary and it could break veth_stop_connection(). */ + + if (! (cnx->state & VETH_STATE_SHUTDOWN)) { + cnx->state |= VETH_STATE_RESET; + veth_kick_statemachine(cnx); + } + spin_unlock_irqrestore(&cnx->lock, flags); +} + +static void veth_handle_ack(struct veth_lpevent *event) +{ + HvLpIndex rlp = event->base_event.xTargetLp; + struct veth_lpar_connection *cnx = veth_cnx[rlp]; + + BUG_ON(! cnx); + + switch (event->base_event.xSubtype) { + case VETH_EVENT_CAP: + veth_take_cap_ack(cnx, event); + break; + case VETH_EVENT_MONITOR: + veth_take_monitor_ack(cnx, event); + break; + default: + veth_error("Unknown ack type %d from LPAR %d.\n", + event->base_event.xSubtype, rlp); + } +} + +static void veth_handle_int(struct veth_lpevent *event) +{ + HvLpIndex rlp = event->base_event.xSourceLp; + struct veth_lpar_connection *cnx = veth_cnx[rlp]; + unsigned long flags; + int i, acked = 0; + + BUG_ON(! cnx); + + switch (event->base_event.xSubtype) { + case VETH_EVENT_CAP: + veth_take_cap(cnx, event); + break; + case VETH_EVENT_MONITOR: + /* do nothing... this'll hang out here til we're dead, + * and the hypervisor will return it for us. */ + break; + case VETH_EVENT_FRAMES_ACK: + spin_lock_irqsave(&cnx->lock, flags); + + for (i = 0; i < VETH_MAX_ACKS_PER_MSG; ++i) { + u16 msgnum = event->u.frames_ack_data.token[i]; + + if (msgnum < VETH_NUMBUFFERS) { + veth_recycle_msg(cnx, cnx->msgs + msgnum); + cnx->outstanding_tx--; + acked++; + } + } + + if (acked > 0) { + cnx->last_contact = jiffies; + veth_wake_queues(cnx); + } + + spin_unlock_irqrestore(&cnx->lock, flags); + break; + case VETH_EVENT_FRAMES: + veth_receive(cnx, event); + break; + default: + veth_error("Unknown interrupt type %d from LPAR %d.\n", + event->base_event.xSubtype, rlp); + } +} + +static void veth_handle_event(struct HvLpEvent *event) +{ + struct veth_lpevent *veth_event = (struct veth_lpevent *)event; + + if (hvlpevent_is_ack(event)) + veth_handle_ack(veth_event); + else + veth_handle_int(veth_event); +} + +static int veth_process_caps(struct veth_lpar_connection *cnx) +{ + struct veth_cap_data *remote_caps = &cnx->remote_caps; + int num_acks_needed; + + /* Convert timer to jiffies */ + cnx->ack_timeout = remote_caps->ack_timeout * HZ / 1000000; + + if ( (remote_caps->num_buffers == 0) || + (remote_caps->ack_threshold > VETH_MAX_ACKS_PER_MSG) || + (remote_caps->ack_threshold == 0) || + (cnx->ack_timeout == 0) ) { + veth_error("Received incompatible capabilities from LPAR %d.\n", + cnx->remote_lp); + return HvLpEvent_Rc_InvalidSubtypeData; + } + + num_acks_needed = (remote_caps->num_buffers + / remote_caps->ack_threshold) + 1; + + /* FIXME: locking on num_ack_events? */ + if (cnx->num_ack_events < num_acks_needed) { + int num; + + num = veth_allocate_events(cnx->remote_lp, + num_acks_needed-cnx->num_ack_events); + if (num > 0) + cnx->num_ack_events += num; + + if (cnx->num_ack_events < num_acks_needed) { + veth_error("Couldn't allocate enough ack events " + "for LPAR %d.\n", cnx->remote_lp); + + return HvLpEvent_Rc_BufferNotAvailable; + } + } + + + return HvLpEvent_Rc_Good; +} + +/* FIXME: The gotos here are a bit dubious */ +static void veth_statemachine(struct work_struct *work) +{ + struct veth_lpar_connection *cnx = + container_of(work, struct veth_lpar_connection, + statemachine_wq.work); + int rlp = cnx->remote_lp; + int rc; + + spin_lock_irq(&cnx->lock); + + restart: + if (cnx->state & VETH_STATE_RESET) { + if (cnx->state & VETH_STATE_OPEN) + HvCallEvent_closeLpEventPath(cnx->remote_lp, + HvLpEvent_Type_VirtualLan); + + /* + * Reset ack data. This prevents the ack_timer actually + * doing anything, even if it runs one more time when + * we drop the lock below. + */ + memset(&cnx->pending_acks, 0xff, sizeof (cnx->pending_acks)); + cnx->num_pending_acks = 0; + + cnx->state &= ~(VETH_STATE_RESET | VETH_STATE_SENTMON + | VETH_STATE_OPEN | VETH_STATE_SENTCAPS + | VETH_STATE_GOTCAPACK | VETH_STATE_GOTCAPS + | VETH_STATE_SENTCAPACK | VETH_STATE_READY); + + /* Clean up any leftover messages */ + if (cnx->msgs) { + int i; + for (i = 0; i < VETH_NUMBUFFERS; ++i) + veth_recycle_msg(cnx, cnx->msgs + i); + } + + cnx->outstanding_tx = 0; + veth_wake_queues(cnx); + + /* Drop the lock so we can do stuff that might sleep or + * take other locks. */ + spin_unlock_irq(&cnx->lock); + + del_timer_sync(&cnx->ack_timer); + del_timer_sync(&cnx->reset_timer); + + spin_lock_irq(&cnx->lock); + + if (cnx->state & VETH_STATE_RESET) + goto restart; + + /* Hack, wait for the other end to reset itself. */ + if (! (cnx->state & VETH_STATE_SHUTDOWN)) { + schedule_delayed_work(&cnx->statemachine_wq, 5 * HZ); + goto out; + } + } + + if (cnx->state & VETH_STATE_SHUTDOWN) + /* It's all over, do nothing */ + goto out; + + if ( !(cnx->state & VETH_STATE_OPEN) ) { + if (! cnx->msgs || (cnx->num_events < (2 + VETH_NUMBUFFERS)) ) + goto cant_cope; + + HvCallEvent_openLpEventPath(rlp, HvLpEvent_Type_VirtualLan); + cnx->src_inst = + HvCallEvent_getSourceLpInstanceId(rlp, + HvLpEvent_Type_VirtualLan); + cnx->dst_inst = + HvCallEvent_getTargetLpInstanceId(rlp, + HvLpEvent_Type_VirtualLan); + cnx->state |= VETH_STATE_OPEN; + } + + if ( (cnx->state & VETH_STATE_OPEN) && + !(cnx->state & VETH_STATE_SENTMON) ) { + rc = veth_signalevent(cnx, VETH_EVENT_MONITOR, + HvLpEvent_AckInd_DoAck, + HvLpEvent_AckType_DeferredAck, + 0, 0, 0, 0, 0, 0); + + if (rc == HvLpEvent_Rc_Good) { + cnx->state |= VETH_STATE_SENTMON; + } else { + if ( (rc != HvLpEvent_Rc_PartitionDead) && + (rc != HvLpEvent_Rc_PathClosed) ) + veth_error("Error sending monitor to LPAR %d, " + "rc = %d\n", rlp, rc); + + /* Oh well, hope we get a cap from the other + * end and do better when that kicks us */ + goto out; + } + } + + if ( (cnx->state & VETH_STATE_OPEN) && + !(cnx->state & VETH_STATE_SENTCAPS)) { + u64 *rawcap = (u64 *)&cnx->local_caps; + + rc = veth_signalevent(cnx, VETH_EVENT_CAP, + HvLpEvent_AckInd_DoAck, + HvLpEvent_AckType_ImmediateAck, + 0, rawcap[0], rawcap[1], rawcap[2], + rawcap[3], rawcap[4]); + + if (rc == HvLpEvent_Rc_Good) { + cnx->state |= VETH_STATE_SENTCAPS; + } else { + if ( (rc != HvLpEvent_Rc_PartitionDead) && + (rc != HvLpEvent_Rc_PathClosed) ) + veth_error("Error sending caps to LPAR %d, " + "rc = %d\n", rlp, rc); + + /* Oh well, hope we get a cap from the other + * end and do better when that kicks us */ + goto out; + } + } + + if ((cnx->state & VETH_STATE_GOTCAPS) && + !(cnx->state & VETH_STATE_SENTCAPACK)) { + struct veth_cap_data *remote_caps = &cnx->remote_caps; + + memcpy(remote_caps, &cnx->cap_event.u.caps_data, + sizeof(*remote_caps)); + + spin_unlock_irq(&cnx->lock); + rc = veth_process_caps(cnx); + spin_lock_irq(&cnx->lock); + + /* We dropped the lock, so recheck for anything which + * might mess us up */ + if (cnx->state & (VETH_STATE_RESET|VETH_STATE_SHUTDOWN)) + goto restart; + + cnx->cap_event.base_event.xRc = rc; + HvCallEvent_ackLpEvent((struct HvLpEvent *)&cnx->cap_event); + if (rc == HvLpEvent_Rc_Good) + cnx->state |= VETH_STATE_SENTCAPACK; + else + goto cant_cope; + } + + if ((cnx->state & VETH_STATE_GOTCAPACK) && + (cnx->state & VETH_STATE_GOTCAPS) && + !(cnx->state & VETH_STATE_READY)) { + if (cnx->cap_ack_event.base_event.xRc == HvLpEvent_Rc_Good) { + /* Start the ACK timer */ + cnx->ack_timer.expires = jiffies + cnx->ack_timeout; + add_timer(&cnx->ack_timer); + cnx->state |= VETH_STATE_READY; + } else { + veth_error("Caps rejected by LPAR %d, rc = %d\n", + rlp, cnx->cap_ack_event.base_event.xRc); + goto cant_cope; + } + } + + out: + spin_unlock_irq(&cnx->lock); + return; + + cant_cope: + /* FIXME: we get here if something happens we really can't + * cope with. The link will never work once we get here, and + * all we can do is not lock the rest of the system up */ + veth_error("Unrecoverable error on connection to LPAR %d, shutting down" + " (state = 0x%04lx)\n", rlp, cnx->state); + cnx->state |= VETH_STATE_SHUTDOWN; + spin_unlock_irq(&cnx->lock); +} + +static int veth_init_connection(u8 rlp) +{ + struct veth_lpar_connection *cnx; + struct veth_msg *msgs; + int i; + + if ( (rlp == this_lp) || + ! HvLpConfig_doLpsCommunicateOnVirtualLan(this_lp, rlp) ) + return 0; + + cnx = kzalloc(sizeof(*cnx), GFP_KERNEL); + if (! cnx) + return -ENOMEM; + + cnx->remote_lp = rlp; + spin_lock_init(&cnx->lock); + INIT_DELAYED_WORK(&cnx->statemachine_wq, veth_statemachine); + + init_timer(&cnx->ack_timer); + cnx->ack_timer.function = veth_timed_ack; + cnx->ack_timer.data = (unsigned long) cnx; + + init_timer(&cnx->reset_timer); + cnx->reset_timer.function = veth_timed_reset; + cnx->reset_timer.data = (unsigned long) cnx; + cnx->reset_timeout = 5 * HZ * (VETH_ACKTIMEOUT / 1000000); + + memset(&cnx->pending_acks, 0xff, sizeof (cnx->pending_acks)); + + veth_cnx[rlp] = cnx; + + /* This gets us 1 reference, which is held on behalf of the driver + * infrastructure. It's released at module unload. */ + kobject_init(&cnx->kobject, &veth_lpar_connection_ktype); + + msgs = kcalloc(VETH_NUMBUFFERS, sizeof(struct veth_msg), GFP_KERNEL); + if (! msgs) { + veth_error("Can't allocate buffers for LPAR %d.\n", rlp); + return -ENOMEM; + } + + cnx->msgs = msgs; + + for (i = 0; i < VETH_NUMBUFFERS; i++) { + msgs[i].token = i; + veth_stack_push(cnx, msgs + i); + } + + cnx->num_events = veth_allocate_events(rlp, 2 + VETH_NUMBUFFERS); + + if (cnx->num_events < (2 + VETH_NUMBUFFERS)) { + veth_error("Can't allocate enough events for LPAR %d.\n", rlp); + return -ENOMEM; + } + + cnx->local_caps.num_buffers = VETH_NUMBUFFERS; + cnx->local_caps.ack_threshold = ACK_THRESHOLD; + cnx->local_caps.ack_timeout = VETH_ACKTIMEOUT; + + return 0; +} + +static void veth_stop_connection(struct veth_lpar_connection *cnx) +{ + if (!cnx) + return; + + spin_lock_irq(&cnx->lock); + cnx->state |= VETH_STATE_RESET | VETH_STATE_SHUTDOWN; + veth_kick_statemachine(cnx); + spin_unlock_irq(&cnx->lock); + + /* ensure the statemachine runs now and waits for its completion */ + flush_delayed_work_sync(&cnx->statemachine_wq); +} + +static void veth_destroy_connection(struct veth_lpar_connection *cnx) +{ + if (!cnx) + return; + + if (cnx->num_events > 0) + mf_deallocate_lp_events(cnx->remote_lp, + HvLpEvent_Type_VirtualLan, + cnx->num_events, + NULL, NULL); + if (cnx->num_ack_events > 0) + mf_deallocate_lp_events(cnx->remote_lp, + HvLpEvent_Type_VirtualLan, + cnx->num_ack_events, + NULL, NULL); + + kfree(cnx->msgs); + veth_cnx[cnx->remote_lp] = NULL; + kfree(cnx); +} + +static void veth_release_connection(struct kobject *kobj) +{ + struct veth_lpar_connection *cnx; + cnx = container_of(kobj, struct veth_lpar_connection, kobject); + veth_stop_connection(cnx); + veth_destroy_connection(cnx); +} + +/* + * net_device code + */ + +static int veth_open(struct net_device *dev) +{ + netif_start_queue(dev); + return 0; +} + +static int veth_close(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +static int veth_change_mtu(struct net_device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > VETH_MAX_MTU)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} + +static void veth_set_multicast_list(struct net_device *dev) +{ + struct veth_port *port = netdev_priv(dev); + unsigned long flags; + + write_lock_irqsave(&port->mcast_gate, flags); + + if ((dev->flags & IFF_PROMISC) || (dev->flags & IFF_ALLMULTI) || + (netdev_mc_count(dev) > VETH_MAX_MCAST)) { + port->promiscuous = 1; + } else { + struct netdev_hw_addr *ha; + + port->promiscuous = 0; + + /* Update table */ + port->num_mcast = 0; + + netdev_for_each_mc_addr(ha, dev) { + u8 *addr = ha->addr; + u64 xaddr = 0; + + memcpy(&xaddr, addr, ETH_ALEN); + port->mcast_addr[port->num_mcast] = xaddr; + port->num_mcast++; + } + } + + write_unlock_irqrestore(&port->mcast_gate, flags); +} + +static void veth_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + strncpy(info->driver, DRV_NAME, sizeof(info->driver) - 1); + info->driver[sizeof(info->driver) - 1] = '\0'; + strncpy(info->version, DRV_VERSION, sizeof(info->version) - 1); + info->version[sizeof(info->version) - 1] = '\0'; +} + +static int veth_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + ecmd->supported = (SUPPORTED_1000baseT_Full + | SUPPORTED_Autoneg | SUPPORTED_FIBRE); + ecmd->advertising = (SUPPORTED_1000baseT_Full + | SUPPORTED_Autoneg | SUPPORTED_FIBRE); + ecmd->port = PORT_FIBRE; + ecmd->transceiver = XCVR_INTERNAL; + ecmd->phy_address = 0; + ecmd->speed = SPEED_1000; + ecmd->duplex = DUPLEX_FULL; + ecmd->autoneg = AUTONEG_ENABLE; + ecmd->maxtxpkt = 120; + ecmd->maxrxpkt = 120; + return 0; +} + +static const struct ethtool_ops ops = { + .get_drvinfo = veth_get_drvinfo, + .get_settings = veth_get_settings, + .get_link = ethtool_op_get_link, +}; + +static const struct net_device_ops veth_netdev_ops = { + .ndo_open = veth_open, + .ndo_stop = veth_close, + .ndo_start_xmit = veth_start_xmit, + .ndo_change_mtu = veth_change_mtu, + .ndo_set_rx_mode = veth_set_multicast_list, + .ndo_set_mac_address = NULL, + .ndo_validate_addr = eth_validate_addr, +}; + +static struct net_device *veth_probe_one(int vlan, + struct vio_dev *vio_dev) +{ + struct net_device *dev; + struct veth_port *port; + struct device *vdev = &vio_dev->dev; + int i, rc; + const unsigned char *mac_addr; + + mac_addr = vio_get_attribute(vio_dev, "local-mac-address", NULL); + if (mac_addr == NULL) + mac_addr = vio_get_attribute(vio_dev, "mac-address", NULL); + if (mac_addr == NULL) { + veth_error("Unable to fetch MAC address from device tree.\n"); + return NULL; + } + + dev = alloc_etherdev(sizeof (struct veth_port)); + if (! dev) { + veth_error("Unable to allocate net_device structure!\n"); + return NULL; + } + + port = netdev_priv(dev); + + spin_lock_init(&port->queue_lock); + rwlock_init(&port->mcast_gate); + port->stopped_map = 0; + + for (i = 0; i < HVMAXARCHITECTEDLPS; i++) { + HvLpVirtualLanIndexMap map; + + if (i == this_lp) + continue; + map = HvLpConfig_getVirtualLanIndexMapForLp(i); + if (map & (0x8000 >> vlan)) + port->lpar_map |= (1 << i); + } + port->dev = vdev; + + memcpy(dev->dev_addr, mac_addr, ETH_ALEN); + + dev->mtu = VETH_MAX_MTU; + + memcpy(&port->mac_addr, mac_addr, ETH_ALEN); + + dev->netdev_ops = &veth_netdev_ops; + SET_ETHTOOL_OPS(dev, &ops); + + SET_NETDEV_DEV(dev, vdev); + + rc = register_netdev(dev); + if (rc != 0) { + veth_error("Failed registering net device for vlan%d.\n", vlan); + free_netdev(dev); + return NULL; + } + + kobject_init(&port->kobject, &veth_port_ktype); + if (0 != kobject_add(&port->kobject, &dev->dev.kobj, "veth_port")) + veth_error("Failed adding port for %s to sysfs.\n", dev->name); + + veth_info("%s attached to iSeries vlan %d (LPAR map = 0x%.4X)\n", + dev->name, vlan, port->lpar_map); + + return dev; +} + +/* + * Tx path + */ + +static int veth_transmit_to_one(struct sk_buff *skb, HvLpIndex rlp, + struct net_device *dev) +{ + struct veth_lpar_connection *cnx = veth_cnx[rlp]; + struct veth_port *port = netdev_priv(dev); + HvLpEvent_Rc rc; + struct veth_msg *msg = NULL; + unsigned long flags; + + if (! cnx) + return 0; + + spin_lock_irqsave(&cnx->lock, flags); + + if (! (cnx->state & VETH_STATE_READY)) + goto no_error; + + if ((skb->len - ETH_HLEN) > VETH_MAX_MTU) + goto drop; + + msg = veth_stack_pop(cnx); + if (! msg) + goto drop; + + msg->in_use = 1; + msg->skb = skb_get(skb); + + msg->data.addr[0] = dma_map_single(port->dev, skb->data, + skb->len, DMA_TO_DEVICE); + + if (dma_mapping_error(port->dev, msg->data.addr[0])) + goto recycle_and_drop; + + msg->dev = port->dev; + msg->data.len[0] = skb->len; + msg->data.eofmask = 1 << VETH_EOF_SHIFT; + + rc = veth_signaldata(cnx, VETH_EVENT_FRAMES, msg->token, &msg->data); + + if (rc != HvLpEvent_Rc_Good) + goto recycle_and_drop; + + /* If the timer's not already running, start it now. */ + if (0 == cnx->outstanding_tx) + mod_timer(&cnx->reset_timer, jiffies + cnx->reset_timeout); + + cnx->last_contact = jiffies; + cnx->outstanding_tx++; + + if (veth_stack_is_empty(cnx)) + veth_stop_queues(cnx); + + no_error: + spin_unlock_irqrestore(&cnx->lock, flags); + return 0; + + recycle_and_drop: + veth_recycle_msg(cnx, msg); + drop: + spin_unlock_irqrestore(&cnx->lock, flags); + return 1; +} + +static void veth_transmit_to_many(struct sk_buff *skb, + HvLpIndexMap lpmask, + struct net_device *dev) +{ + int i, success, error; + + success = error = 0; + + for (i = 0; i < HVMAXARCHITECTEDLPS; i++) { + if ((lpmask & (1 << i)) == 0) + continue; + + if (veth_transmit_to_one(skb, i, dev)) + error = 1; + else + success = 1; + } + + if (error) + dev->stats.tx_errors++; + + if (success) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; |