mirror of
https://github.com/rd-stuffs/msm-4.14.git
synced 2025-02-20 11:45:48 +08:00
Some users have a large AoE target while others like to use many AoE targets at the same time. In the latter case, there is an opportunity to greatly improve aggregate throughput by allowing different threads to complete the I/O associated with each target. For 36 targets, 4 KiB read throughput roughly doubles, for example, with these changes in place. Signed-off-by: Ed Cashin <ecashin@coraid.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
224 lines
4.4 KiB
C
224 lines
4.4 KiB
C
/* Copyright (c) 2012 Coraid, Inc. See COPYING for GPL terms. */
|
|
/*
|
|
* aoenet.c
|
|
* Ethernet portion of AoE driver
|
|
*/
|
|
|
|
#include <linux/gfp.h>
|
|
#include <linux/hdreg.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <net/net_namespace.h>
|
|
#include <asm/unaligned.h>
|
|
#include "aoe.h"
|
|
|
|
#define NECODES 5
|
|
|
|
static char *aoe_errlist[] =
|
|
{
|
|
"no such error",
|
|
"unrecognized command code",
|
|
"bad argument parameter",
|
|
"device unavailable",
|
|
"config string present",
|
|
"unsupported version"
|
|
};
|
|
|
|
enum {
|
|
IFLISTSZ = 1024,
|
|
};
|
|
|
|
static char aoe_iflist[IFLISTSZ];
|
|
module_param_string(aoe_iflist, aoe_iflist, IFLISTSZ, 0600);
|
|
MODULE_PARM_DESC(aoe_iflist, "aoe_iflist=dev1[,dev2...]");
|
|
|
|
static wait_queue_head_t txwq;
|
|
static struct ktstate kts;
|
|
|
|
#ifndef MODULE
|
|
static int __init aoe_iflist_setup(char *str)
|
|
{
|
|
strncpy(aoe_iflist, str, IFLISTSZ);
|
|
aoe_iflist[IFLISTSZ - 1] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
__setup("aoe_iflist=", aoe_iflist_setup);
|
|
#endif
|
|
|
|
static spinlock_t txlock;
|
|
static struct sk_buff_head skbtxq;
|
|
|
|
/* enters with txlock held */
|
|
static int
|
|
tx(int id) __must_hold(&txlock)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct net_device *ifp;
|
|
|
|
while ((skb = skb_dequeue(&skbtxq))) {
|
|
spin_unlock_irq(&txlock);
|
|
ifp = skb->dev;
|
|
if (dev_queue_xmit(skb) == NET_XMIT_DROP && net_ratelimit())
|
|
pr_warn("aoe: packet could not be sent on %s. %s\n",
|
|
ifp ? ifp->name : "netif",
|
|
"consider increasing tx_queue_len");
|
|
spin_lock_irq(&txlock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
is_aoe_netif(struct net_device *ifp)
|
|
{
|
|
register char *p, *q;
|
|
register int len;
|
|
|
|
if (aoe_iflist[0] == '\0')
|
|
return 1;
|
|
|
|
p = aoe_iflist + strspn(aoe_iflist, WHITESPACE);
|
|
for (; *p; p = q + strspn(q, WHITESPACE)) {
|
|
q = p + strcspn(p, WHITESPACE);
|
|
if (q != p)
|
|
len = q - p;
|
|
else
|
|
len = strlen(p); /* last token in aoe_iflist */
|
|
|
|
if (strlen(ifp->name) == len && !strncmp(ifp->name, p, len))
|
|
return 1;
|
|
if (q == p)
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
set_aoe_iflist(const char __user *user_str, size_t size)
|
|
{
|
|
if (size >= IFLISTSZ)
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(aoe_iflist, user_str, size)) {
|
|
printk(KERN_INFO "aoe: copy from user failed\n");
|
|
return -EFAULT;
|
|
}
|
|
aoe_iflist[size] = 0x00;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
aoenet_xmit(struct sk_buff_head *queue)
|
|
{
|
|
struct sk_buff *skb, *tmp;
|
|
ulong flags;
|
|
|
|
skb_queue_walk_safe(queue, skb, tmp) {
|
|
__skb_unlink(skb, queue);
|
|
spin_lock_irqsave(&txlock, flags);
|
|
skb_queue_tail(&skbtxq, skb);
|
|
spin_unlock_irqrestore(&txlock, flags);
|
|
wake_up(&txwq);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* (1) len doesn't include the header by default. I want this.
|
|
*/
|
|
static int
|
|
aoenet_rcv(struct sk_buff *skb, struct net_device *ifp, struct packet_type *pt, struct net_device *orig_dev)
|
|
{
|
|
struct aoe_hdr *h;
|
|
struct aoe_atahdr *ah;
|
|
u32 n;
|
|
int sn;
|
|
|
|
if (dev_net(ifp) != &init_net)
|
|
goto exit;
|
|
|
|
skb = skb_share_check(skb, GFP_ATOMIC);
|
|
if (skb == NULL)
|
|
return 0;
|
|
if (!is_aoe_netif(ifp))
|
|
goto exit;
|
|
skb_push(skb, ETH_HLEN); /* (1) */
|
|
sn = sizeof(*h) + sizeof(*ah);
|
|
if (skb->len >= sn) {
|
|
sn -= skb_headlen(skb);
|
|
if (sn > 0 && !__pskb_pull_tail(skb, sn))
|
|
goto exit;
|
|
}
|
|
h = (struct aoe_hdr *) skb->data;
|
|
n = get_unaligned_be32(&h->tag);
|
|
if ((h->verfl & AOEFL_RSP) == 0 || (n & 1<<31))
|
|
goto exit;
|
|
|
|
if (h->verfl & AOEFL_ERR) {
|
|
n = h->err;
|
|
if (n > NECODES)
|
|
n = 0;
|
|
if (net_ratelimit())
|
|
printk(KERN_ERR
|
|
"%s%d.%d@%s; ecode=%d '%s'\n",
|
|
"aoe: error packet from ",
|
|
get_unaligned_be16(&h->major),
|
|
h->minor, skb->dev->name,
|
|
h->err, aoe_errlist[n]);
|
|
goto exit;
|
|
}
|
|
|
|
switch (h->cmd) {
|
|
case AOECMD_ATA:
|
|
/* ata_rsp may keep skb for later processing or give it back */
|
|
skb = aoecmd_ata_rsp(skb);
|
|
break;
|
|
case AOECMD_CFG:
|
|
aoecmd_cfg_rsp(skb);
|
|
break;
|
|
default:
|
|
if (h->cmd >= AOECMD_VEND_MIN)
|
|
break; /* don't complain about vendor commands */
|
|
pr_info("aoe: unknown AoE command type 0x%02x\n", h->cmd);
|
|
break;
|
|
}
|
|
|
|
if (!skb)
|
|
return 0;
|
|
exit:
|
|
dev_kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static struct packet_type aoe_pt __read_mostly = {
|
|
.type = __constant_htons(ETH_P_AOE),
|
|
.func = aoenet_rcv,
|
|
};
|
|
|
|
int __init
|
|
aoenet_init(void)
|
|
{
|
|
skb_queue_head_init(&skbtxq);
|
|
init_waitqueue_head(&txwq);
|
|
spin_lock_init(&txlock);
|
|
kts.lock = &txlock;
|
|
kts.fn = tx;
|
|
kts.waitq = &txwq;
|
|
kts.id = 0;
|
|
snprintf(kts.name, sizeof(kts.name), "aoe_tx%d", kts.id);
|
|
if (aoe_ktstart(&kts))
|
|
return -EAGAIN;
|
|
dev_add_pack(&aoe_pt);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
aoenet_exit(void)
|
|
{
|
|
aoe_ktstop(&kts);
|
|
skb_queue_purge(&skbtxq);
|
|
dev_remove_pack(&aoe_pt);
|
|
}
|
|
|