Commit 33fcc5e0 authored by David S. Miller's avatar David S. Miller

Merge branch 'cdc_ncm-coalesce'

Bjørn Mork says:

====================
cdc_ncm: add buffer tuning and stats using ethtool

Quoting the previous description of this series (skip to the
changelog below if you only want a summary of the changes):

"I have got quite a few reports from frustrated users of OpenWRT
hosts trying to use some powerful LTE modem, but not achieving
full speed.  This is typically caused by a combination of
big buffers and little memory, giving in allocation errors and
bad performance as a result.

This series is an attempt to let users adjust the size of these
buffers without having to rebuild the driver.

Patches 1 - 4 are mostly rearranging existing code, in preparing
for the dynamic buffer size changes.

Patch 5 adds userspace control (ab)using the ethtool coalescing
API. This isn't a perfect match, which is the main reason why I
post this series as a RFC.

Patch 6 is an unrelated framing optimization, reducing the
overhead quite a bit and allowing for better use of smaller
buffers.

Patch 7 changes the way we calculate frame padding cutoff. The
problem with big buffers is made much worse by the current padding
strategy where zero padding often can account for more than 90% of
the frames.

Patch 8 add some counters giving some insight into how well the
NCM/MBIM protocol works, supporting further tuning.

Patch 9 reduce the initial maximum buffer size from 32kB to 16kB
in an attempt to make the default better suit all. It is still
possible to tune this up again to the old fixed max, using the
new tuning knobs.

I must admit that I had higher hopes for this series before I
tested it on my own modems.  One really unexpected result was
that one of the MBIM modems accepted the new rx buffer size we
set, but happily continued sending buffers of the same size as
before.  Needless to say:  This did not work very well...

So don't really expect to be able to use any values with any
given device. Firmware implementations are still... I don't
think I have words suitable for a public mailing list.

But I am hoping this will help the many users who have had success
rebuilding the driver with lower fixed limits.

Please test and/or comment!"

Changes:

** RFC -> v1 **

Patch 10 - a follow-up to a comment Joe Perches made in November
           2013.  I don't always forget :-)
Patch 11 - removes the redundant "connected" driver state, and the
           associated .check_connect callbacks.

** v1 -> v2 **

Patch 1  - Better handling of minium rx buffer size, based on feedback
           from Oliver Neukum and Enrico Mioso
Patch 5  - fixed locking around timer interval update
Patch 9  - fixed whitespace error
Patch 12 - new fix related to the tuneable tx timer

...and spelling fixes all over the commit messages.  I have finally
added a spelling hook, which I'm sure may of you will appreciate :-)
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 97dc48e2 046c6594
......@@ -420,6 +420,7 @@ static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
struct usb_cdc_ncm_dpe16 *dpe16;
int ndpoffset;
int loopcount = 50; /* arbitrary max preventing infinite loop */
u32 payload = 0;
u8 *c;
u16 tci;
......@@ -482,6 +483,7 @@ static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
if (!skb)
goto error;
usbnet_skb_return(dev, skb);
payload += len; /* count payload bytes in this NTB */
}
}
err_ndp:
......@@ -490,6 +492,10 @@ static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
if (ndpoffset && loopcount--)
goto next_ndp;
/* update stats */
ctx->rx_overhead += skb_in->len - payload;
ctx->rx_ntbs++;
return 1;
error:
return 0;
......
......@@ -65,169 +65,353 @@ static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx);
static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *hr_timer);
static struct usb_driver cdc_ncm_driver;
static int cdc_ncm_setup(struct usbnet *dev)
struct cdc_ncm_stats {
char stat_string[ETH_GSTRING_LEN];
int sizeof_stat;
int stat_offset;
};
#define CDC_NCM_STAT(str, m) { \
.stat_string = str, \
.sizeof_stat = sizeof(((struct cdc_ncm_ctx *)0)->m), \
.stat_offset = offsetof(struct cdc_ncm_ctx, m) }
#define CDC_NCM_SIMPLE_STAT(m) CDC_NCM_STAT(__stringify(m), m)
static const struct cdc_ncm_stats cdc_ncm_gstrings_stats[] = {
CDC_NCM_SIMPLE_STAT(tx_reason_ntb_full),
CDC_NCM_SIMPLE_STAT(tx_reason_ndp_full),
CDC_NCM_SIMPLE_STAT(tx_reason_timeout),
CDC_NCM_SIMPLE_STAT(tx_reason_max_datagram),
CDC_NCM_SIMPLE_STAT(tx_overhead),
CDC_NCM_SIMPLE_STAT(tx_ntbs),
CDC_NCM_SIMPLE_STAT(rx_overhead),
CDC_NCM_SIMPLE_STAT(rx_ntbs),
};
static int cdc_ncm_get_sset_count(struct net_device __always_unused *netdev, int sset)
{
switch (sset) {
case ETH_SS_STATS:
return ARRAY_SIZE(cdc_ncm_gstrings_stats);
default:
return -EOPNOTSUPP;
}
}
static void cdc_ncm_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats __always_unused *stats,
u64 *data)
{
struct usbnet *dev = netdev_priv(netdev);
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
u32 val;
u8 flags;
u8 iface_no;
int err;
int eth_hlen;
u16 mbim_mtu;
u16 ntb_fmt_supported;
__le16 max_datagram_size;
int i;
char *p = NULL;
iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber;
for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) {
p = (char *)ctx + cdc_ncm_gstrings_stats[i].stat_offset;
data[i] = (cdc_ncm_gstrings_stats[i].sizeof_stat == sizeof(u64)) ? *(u64 *)p : *(u32 *)p;
}
}
err = usbnet_read_cmd(dev, USB_CDC_GET_NTB_PARAMETERS,
USB_TYPE_CLASS | USB_DIR_IN
|USB_RECIP_INTERFACE,
0, iface_no, &ctx->ncm_parm,
sizeof(ctx->ncm_parm));
if (err < 0) {
dev_err(&dev->intf->dev, "failed GET_NTB_PARAMETERS\n");
return err; /* GET_NTB_PARAMETERS is required */
static void cdc_ncm_get_strings(struct net_device __always_unused *netdev, u32 stringset, u8 *data)
{
u8 *p = data;
int i;
switch (stringset) {
case ETH_SS_STATS:
for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) {
memcpy(p, cdc_ncm_gstrings_stats[i].stat_string, ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
}
}
/* read correct set of parameters according to device mode */
ctx->rx_max = le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize);
ctx->tx_max = le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize);
ctx->tx_remainder = le16_to_cpu(ctx->ncm_parm.wNdpOutPayloadRemainder);
ctx->tx_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutDivisor);
ctx->tx_ndp_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutAlignment);
/* devices prior to NCM Errata shall set this field to zero */
ctx->tx_max_datagrams = le16_to_cpu(ctx->ncm_parm.wNtbOutMaxDatagrams);
ntb_fmt_supported = le16_to_cpu(ctx->ncm_parm.bmNtbFormatsSupported);
static int cdc_ncm_get_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec)
{
struct usbnet *dev = netdev_priv(netdev);
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
/* there are some minor differences in NCM and MBIM defaults */
if (cdc_ncm_comm_intf_is_mbim(ctx->control->cur_altsetting)) {
if (!ctx->mbim_desc)
return -EINVAL;
eth_hlen = 0;
flags = ctx->mbim_desc->bmNetworkCapabilities;
ctx->max_datagram_size = le16_to_cpu(ctx->mbim_desc->wMaxSegmentSize);
if (ctx->max_datagram_size < CDC_MBIM_MIN_DATAGRAM_SIZE)
ctx->max_datagram_size = CDC_MBIM_MIN_DATAGRAM_SIZE;
} else {
if (!ctx->func_desc)
/* assuming maximum sized dgrams and ignoring NDPs */
ec->rx_max_coalesced_frames = ctx->rx_max / ctx->max_datagram_size;
ec->tx_max_coalesced_frames = ctx->tx_max / ctx->max_datagram_size;
/* the timer will fire CDC_NCM_TIMER_PENDING_CNT times in a row */
ec->tx_coalesce_usecs = (ctx->timer_interval * CDC_NCM_TIMER_PENDING_CNT) / NSEC_PER_USEC;
return 0;
}
static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx);
static int cdc_ncm_set_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec)
{
struct usbnet *dev = netdev_priv(netdev);
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
u32 new_rx_max = ctx->rx_max;
u32 new_tx_max = ctx->tx_max;
/* assuming maximum sized dgrams and a single NDP */
if (ec->rx_max_coalesced_frames)
new_rx_max = ec->rx_max_coalesced_frames * ctx->max_datagram_size;
if (ec->tx_max_coalesced_frames)
new_tx_max = ec->tx_max_coalesced_frames * ctx->max_datagram_size;
if (ec->tx_coalesce_usecs &&
(ec->tx_coalesce_usecs < CDC_NCM_TIMER_INTERVAL_MIN * CDC_NCM_TIMER_PENDING_CNT ||
ec->tx_coalesce_usecs > CDC_NCM_TIMER_INTERVAL_MAX * CDC_NCM_TIMER_PENDING_CNT))
return -EINVAL;
eth_hlen = ETH_HLEN;
flags = ctx->func_desc->bmNetworkCapabilities;
ctx->max_datagram_size = le16_to_cpu(ctx->ether_desc->wMaxSegmentSize);
if (ctx->max_datagram_size < CDC_NCM_MIN_DATAGRAM_SIZE)
ctx->max_datagram_size = CDC_NCM_MIN_DATAGRAM_SIZE;
}
/* common absolute max for NCM and MBIM */
if (ctx->max_datagram_size > CDC_NCM_MAX_DATAGRAM_SIZE)
ctx->max_datagram_size = CDC_NCM_MAX_DATAGRAM_SIZE;
spin_lock_bh(&ctx->mtx);
ctx->timer_interval = ec->tx_coalesce_usecs * NSEC_PER_USEC / CDC_NCM_TIMER_PENDING_CNT;
if (!ctx->timer_interval)
ctx->tx_timer_pending = 0;
spin_unlock_bh(&ctx->mtx);
dev_dbg(&dev->intf->dev,
"dwNtbInMaxSize=%u dwNtbOutMaxSize=%u wNdpOutPayloadRemainder=%u wNdpOutDivisor=%u wNdpOutAlignment=%u wNtbOutMaxDatagrams=%u flags=0x%x\n",
ctx->rx_max, ctx->tx_max, ctx->tx_remainder, ctx->tx_modulus,
ctx->tx_ndp_modulus, ctx->tx_max_datagrams, flags);
/* inform device of new values */
if (new_rx_max != ctx->rx_max || new_tx_max != ctx->tx_max)
cdc_ncm_update_rxtx_max(dev, new_rx_max, new_tx_max);
return 0;
}
/* max count of tx datagrams */
if ((ctx->tx_max_datagrams == 0) ||
(ctx->tx_max_datagrams > CDC_NCM_DPT_DATAGRAMS_MAX))
ctx->tx_max_datagrams = CDC_NCM_DPT_DATAGRAMS_MAX;
static const struct ethtool_ops cdc_ncm_ethtool_ops = {
.get_settings = usbnet_get_settings,
.set_settings = usbnet_set_settings,
.get_link = usbnet_get_link,
.nway_reset = usbnet_nway_reset,
.get_drvinfo = usbnet_get_drvinfo,
.get_msglevel = usbnet_get_msglevel,
.set_msglevel = usbnet_set_msglevel,
.get_ts_info = ethtool_op_get_ts_info,
.get_sset_count = cdc_ncm_get_sset_count,
.get_strings = cdc_ncm_get_strings,
.get_ethtool_stats = cdc_ncm_get_ethtool_stats,
.get_coalesce = cdc_ncm_get_coalesce,
.set_coalesce = cdc_ncm_set_coalesce,
};
/* verify maximum size of received NTB in bytes */
if (ctx->rx_max < USB_CDC_NCM_NTB_MIN_IN_SIZE) {
dev_dbg(&dev->intf->dev, "Using min receive length=%d\n",
USB_CDC_NCM_NTB_MIN_IN_SIZE);
ctx->rx_max = USB_CDC_NCM_NTB_MIN_IN_SIZE;
/* handle rx_max and tx_max changes */
static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber;
u32 val, max, min;
/* clamp new_rx to sane values */
min = USB_CDC_NCM_NTB_MIN_IN_SIZE;
max = min_t(u32, CDC_NCM_NTB_MAX_SIZE_RX, le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize));
/* dwNtbInMaxSize spec violation? Use MIN size for both limits */
if (max < min) {
dev_warn(&dev->intf->dev, "dwNtbInMaxSize=%u is too small. Using %u\n",
le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize), min);
max = min;
}
if (ctx->rx_max > CDC_NCM_NTB_MAX_SIZE_RX) {
dev_dbg(&dev->intf->dev, "Using default maximum receive length=%d\n",
CDC_NCM_NTB_MAX_SIZE_RX);
ctx->rx_max = CDC_NCM_NTB_MAX_SIZE_RX;
val = clamp_t(u32, new_rx, min, max);
if (val != new_rx) {
dev_dbg(&dev->intf->dev, "rx_max must be in the [%u, %u] range. Using %u\n",
min, max, val);
}
/* usbnet use these values for sizing rx queues */
dev->rx_urb_size = val;
/* inform device about NTB input size changes */
if (ctx->rx_max != le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize)) {
__le32 dwNtbInMaxSize = cpu_to_le32(ctx->rx_max);
if (val != ctx->rx_max) {
__le32 dwNtbInMaxSize = cpu_to_le32(val);
dev_info(&dev->intf->dev, "setting rx_max = %u\n", val);
/* need to unlink rx urbs before increasing buffer size */
if (netif_running(dev->net) && dev->rx_urb_size > ctx->rx_max)
usbnet_unlink_rx_urbs(dev);
err = usbnet_write_cmd(dev, USB_CDC_SET_NTB_INPUT_SIZE,
/* tell device to use new size */
if (usbnet_write_cmd(dev, USB_CDC_SET_NTB_INPUT_SIZE,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE,
0, iface_no, &dwNtbInMaxSize, 4);
if (err < 0)
0, iface_no, &dwNtbInMaxSize, 4) < 0)
dev_dbg(&dev->intf->dev, "Setting NTB Input Size failed\n");
else
ctx->rx_max = val;
}
/* verify maximum size of transmitted NTB in bytes */
if (ctx->tx_max > CDC_NCM_NTB_MAX_SIZE_TX) {
dev_dbg(&dev->intf->dev, "Using default maximum transmit length=%d\n",
CDC_NCM_NTB_MAX_SIZE_TX);
ctx->tx_max = CDC_NCM_NTB_MAX_SIZE_TX;
}
/* clamp new_tx to sane values */
min = ctx->max_datagram_size + ctx->max_ndp_size + sizeof(struct usb_cdc_ncm_nth16);
max = min_t(u32, CDC_NCM_NTB_MAX_SIZE_TX, le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize));
/*
* verify that the structure alignment is:
* - power of two
* - not greater than the maximum transmit length
* - not less than four bytes
*/
val = ctx->tx_ndp_modulus;
/* some devices set dwNtbOutMaxSize too low for the above default */
min = min(min, max);
if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||
(val != ((-val) & val)) || (val >= ctx->tx_max)) {
dev_dbg(&dev->intf->dev, "Using default alignment: 4 bytes\n");
ctx->tx_ndp_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;
val = clamp_t(u32, new_tx, min, max);
if (val != new_tx) {
dev_dbg(&dev->intf->dev, "tx_max must be in the [%u, %u] range. Using %u\n",
min, max, val);
}
if (val != ctx->tx_max)
dev_info(&dev->intf->dev, "setting tx_max = %u\n", val);
/*
* verify that the payload alignment is:
* - power of two
* - not greater than the maximum transmit length
* - not less than four bytes
/* Adding a pad byte here if necessary simplifies the handling
* in cdc_ncm_fill_tx_frame, making tx_max always represent
* the real skb max size.
*
* We cannot use dev->maxpacket here because this is called from
* .bind which is called before usbnet sets up dev->maxpacket
*/
val = ctx->tx_modulus;
if (val != le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize) &&
val % usb_maxpacket(dev->udev, dev->out, 1) == 0)
val++;
if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||
(val != ((-val) & val)) || (val >= ctx->tx_max)) {
dev_dbg(&dev->intf->dev, "Using default transmit modulus: 4 bytes\n");
ctx->tx_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;
/* we might need to flush any pending tx buffers if running */
if (netif_running(dev->net) && val > ctx->tx_max) {
netif_tx_lock_bh(dev->net);
usbnet_start_xmit(NULL, dev->net);
ctx->tx_max = val;
netif_tx_unlock_bh(dev->net);
} else {
ctx->tx_max = val;
}
/* verify the payload remainder */
if (ctx->tx_remainder >= ctx->tx_modulus) {
dev_dbg(&dev->intf->dev, "Using default transmit remainder: 0 bytes\n");
ctx->tx_remainder = 0;
}
dev->hard_mtu = ctx->tx_max;
/* adjust TX-remainder according to NCM specification. */
ctx->tx_remainder = ((ctx->tx_remainder - eth_hlen) &
(ctx->tx_modulus - 1));
/* max qlen depend on hard_mtu and rx_urb_size */
usbnet_update_max_qlen(dev);
/* never pad more than 3 full USB packets per transfer */
ctx->min_tx_pkt = clamp_t(u16, ctx->tx_max - 3 * usb_maxpacket(dev->udev, dev->out, 1),
CDC_NCM_MIN_TX_PKT, ctx->tx_max);
}
/* helpers for NCM and MBIM differences */
static u8 cdc_ncm_flags(struct usbnet *dev)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting) && ctx->mbim_desc)
return ctx->mbim_desc->bmNetworkCapabilities;
if (ctx->func_desc)
return ctx->func_desc->bmNetworkCapabilities;
return 0;
}
static int cdc_ncm_eth_hlen(struct usbnet *dev)
{
if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting))
return 0;
return ETH_HLEN;
}
/* additional configuration */
static u32 cdc_ncm_min_dgram_size(struct usbnet *dev)
{
if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting))
return CDC_MBIM_MIN_DATAGRAM_SIZE;
return CDC_NCM_MIN_DATAGRAM_SIZE;
}
static u32 cdc_ncm_max_dgram_size(struct usbnet *dev)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting) && ctx->mbim_desc)
return le16_to_cpu(ctx->mbim_desc->wMaxSegmentSize);
if (ctx->ether_desc)
return le16_to_cpu(ctx->ether_desc->wMaxSegmentSize);
return CDC_NCM_MAX_DATAGRAM_SIZE;
}
/* initial one-time device setup. MUST be called with the data interface
* in altsetting 0
*/
static int cdc_ncm_init(struct usbnet *dev)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber;
int err;
err = usbnet_read_cmd(dev, USB_CDC_GET_NTB_PARAMETERS,
USB_TYPE_CLASS | USB_DIR_IN
|USB_RECIP_INTERFACE,
0, iface_no, &ctx->ncm_parm,
sizeof(ctx->ncm_parm));
if (err < 0) {
dev_err(&dev->intf->dev, "failed GET_NTB_PARAMETERS\n");
return err; /* GET_NTB_PARAMETERS is required */
}
/* set CRC Mode */
if (flags & USB_CDC_NCM_NCAP_CRC_MODE) {
if (cdc_ncm_flags(dev) & USB_CDC_NCM_NCAP_CRC_MODE) {
dev_dbg(&dev->intf->dev, "Setting CRC mode off\n");
err = usbnet_write_cmd(dev, USB_CDC_SET_CRC_MODE,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE,
USB_CDC_NCM_CRC_NOT_APPENDED,
iface_no, NULL, 0);
if (err < 0)
dev_dbg(&dev->intf->dev, "Setting CRC mode off failed\n");
dev_err(&dev->intf->dev, "SET_CRC_MODE failed\n");
}
/* set NTB format, if both formats are supported */
if (ntb_fmt_supported & USB_CDC_NCM_NTH32_SIGN) {
/* set NTB format, if both formats are supported.
*
* "The host shall only send this command while the NCM Data
* Interface is in alternate setting 0."
*/
if (le16_to_cpu(ctx->ncm_parm.bmNtbFormatsSupported) & USB_CDC_NCM_NTH32_SIGN) {
dev_dbg(&dev->intf->dev, "Setting NTB format to 16-bit\n");
err = usbnet_write_cmd(dev, USB_CDC_SET_NTB_FORMAT,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE,
USB_CDC_NCM_NTB16_FORMAT,
iface_no, NULL, 0);
if (err < 0)
dev_dbg(&dev->intf->dev, "Setting NTB format to 16-bit failed\n");
dev_err(&dev->intf->dev, "SET_NTB_FORMAT failed\n");
}
/* inform the device about the selected Max Datagram Size */
if (!(flags & USB_CDC_NCM_NCAP_MAX_DATAGRAM_SIZE))
/* set initial device values */
ctx->rx_max = le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize);
ctx->tx_max = le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize);
ctx->tx_remainder = le16_to_cpu(ctx->ncm_parm.wNdpOutPayloadRemainder);
ctx->tx_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutDivisor);
ctx->tx_ndp_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutAlignment);
/* devices prior to NCM Errata shall set this field to zero */
ctx->tx_max_datagrams = le16_to_cpu(ctx->ncm_parm.wNtbOutMaxDatagrams);
dev_dbg(&dev->intf->dev,
"dwNtbInMaxSize=%u dwNtbOutMaxSize=%u wNdpOutPayloadRemainder=%u wNdpOutDivisor=%u wNdpOutAlignment=%u wNtbOutMaxDatagrams=%u flags=0x%x\n",
ctx->rx_max, ctx->tx_max, ctx->tx_remainder, ctx->tx_modulus,
ctx->tx_ndp_modulus, ctx->tx_max_datagrams, cdc_ncm_flags(dev));
/* max count of tx datagrams */
if ((ctx->tx_max_datagrams == 0) ||
(ctx->tx_max_datagrams > CDC_NCM_DPT_DATAGRAMS_MAX))
ctx->tx_max_datagrams = CDC_NCM_DPT_DATAGRAMS_MAX;
/* set up maximum NDP size */
ctx->max_ndp_size = sizeof(struct usb_cdc_ncm_ndp16) + (ctx->tx_max_datagrams + 1) * sizeof(struct usb_cdc_ncm_dpe16);
/* initial coalescing timer interval */
ctx->timer_interval = CDC_NCM_TIMER_INTERVAL_USEC * NSEC_PER_USEC;
return 0;
}
/* set a new max datagram size */
static void cdc_ncm_set_dgram_size(struct usbnet *dev, int new_size)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber;
__le16 max_datagram_size;
u16 mbim_mtu;
int err;
/* set default based on descriptors */
ctx->max_datagram_size = clamp_t(u32, new_size,
cdc_ncm_min_dgram_size(dev),
CDC_NCM_MAX_DATAGRAM_SIZE);
/* inform the device about the selected Max Datagram Size? */
if (!(cdc_ncm_flags(dev) & USB_CDC_NCM_NCAP_MAX_DATAGRAM_SIZE))
goto out;
/* read current mtu value from device */
......@@ -251,8 +435,7 @@ static int cdc_ncm_setup(struct usbnet *dev)
out:
/* set MTU to max supported by the device if necessary */
if (dev->net->mtu > ctx->max_datagram_size - eth_hlen)
dev->net->mtu = ctx->max_datagram_size - eth_hlen;
dev->net->mtu = min_t(int, dev->net->mtu, ctx->max_datagram_size - cdc_ncm_eth_hlen(dev));
/* do not exceed operater preferred MTU */
if (ctx->mbim_extended_desc) {
......@@ -260,7 +443,73 @@ static int cdc_ncm_setup(struct usbnet *dev)
if (mbim_mtu != 0 && mbim_mtu < dev->net->mtu)
dev->net->mtu = mbim_mtu;
}
}
static void cdc_ncm_fix_modulus(struct usbnet *dev)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
u32 val;
/*
* verify that the structure alignment is:
* - power of two
* - not greater than the maximum transmit length
* - not less than four bytes
*/
val = ctx->tx_ndp_modulus;
if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||
(val != ((-val) & val)) || (val >= ctx->tx_max)) {
dev_dbg(&dev->intf->dev, "Using default alignment: 4 bytes\n");
ctx->tx_ndp_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;
}
/*
* verify that the payload alignment is:
* - power of two
* - not greater than the maximum transmit length
* - not less than four bytes
*/
val = ctx->tx_modulus;
if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||
(val != ((-val) & val)) || (val >= ctx->tx_max)) {
dev_dbg(&dev->intf->dev, "Using default transmit modulus: 4 bytes\n");
ctx->tx_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;
}
/* verify the payload remainder */
if (ctx->tx_remainder >= ctx->tx_modulus) {
dev_dbg(&dev->intf->dev, "Using default transmit remainder: 0 bytes\n");
ctx->tx_remainder = 0;
}
/* adjust TX-remainder according to NCM specification. */
ctx->tx_remainder = ((ctx->tx_remainder - cdc_ncm_eth_hlen(dev)) &
(ctx->tx_modulus - 1));
}
static int cdc_ncm_setup(struct usbnet *dev)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
u32 def_rx, def_tx;
/* be conservative when selecting intial buffer size to
* increase the number of hosts this will work for
*/
def_rx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_RX,
le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize));
def_tx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_TX,
le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize));
/* clamp rx_max and tx_max and inform device */
cdc_ncm_update_rxtx_max(dev, def_rx, def_tx);
/* sanitize the modulus and remainder values */
cdc_ncm_fix_modulus(dev);
/* set max datagram size */
cdc_ncm_set_dgram_size(dev, cdc_ncm_max_dgram_size(dev));
return 0;
}
......@@ -424,10 +673,21 @@ int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_
}
/* check if we got everything */
if (!ctx->data || (!ctx->mbim_desc && !ctx->ether_desc)) {
dev_dbg(&intf->dev, "CDC descriptors missing\n");
if (!ctx->data) {
dev_dbg(&intf->dev, "CDC Union missing and no IAD found\n");
goto error;
}
if (cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) {
if (!ctx->mbim_desc) {
dev_dbg(&intf->dev, "MBIM functional descriptor missing\n");
goto error;
}
} else {
if (!ctx->ether_desc || !ctx->func_desc) {
dev_dbg(&intf->dev, "NCM or ECM functional descriptors missing\n");
goto error;
}
}
/* claim data interface, if different from control */
if (ctx->data != ctx->control) {
......@@ -447,8 +707,8 @@ int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_
goto error2;
}
/* initialize data interface */
if (cdc_ncm_setup(dev))
/* initialize basic device settings */
if (cdc_ncm_init(dev))
goto error2;
/* configure data interface */
......@@ -477,18 +737,11 @@ int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_
dev_info(&intf->dev, "MAC-Address: %pM\n", dev->net->dev_addr);
}
/* usbnet use these values for sizing tx/rx queues */
dev->hard_mtu = ctx->tx_max;
dev->rx_urb_size = ctx->rx_max;
/* finish setting up the device specific data */
cdc_ncm_setup(dev);
/* cdc_ncm_setup will override dwNtbOutMaxSize if it is
* outside the sane range. Adding a pad byte here if necessary
* simplifies the handling in cdc_ncm_fill_tx_frame, making
* tx_max always represent the real skb max size.
*/
if (ctx->tx_max != le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize) &&
ctx->tx_max % usb_maxpacket(dev->udev, dev->out, 1) == 0)
ctx->tx_max++;
/* override ethtool_ops */
dev->net->ethtool_ops = &cdc_ncm_ethtool_ops;
return 0;
......@@ -627,7 +880,7 @@ static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_
cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max);
/* verify that there is room for the NDP and the datagram (reserve) */
if ((ctx->tx_max - skb->len - reserve) < CDC_NCM_NDP_SIZE)
if ((ctx->tx_max - skb->len - reserve) < ctx->max_ndp_size)
return NULL;
/* link to it */
......@@ -637,7 +890,7 @@ static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_
nth16->wNdpIndex = cpu_to_le16(skb->len);
/* push a new empty NDP */
ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, CDC_NCM_NDP_SIZE), 0, CDC_NCM_NDP_SIZE);
ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, ctx->max_ndp_size), 0, ctx->max_ndp_size);
ndp16->dwSignature = sign;
ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16));
return ndp16;
......@@ -682,6 +935,9 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
/* count total number of frames in this NTB */
ctx->tx_curr_frame_num = 0;
/* recent payload counter for this skb_out */
ctx->tx_curr_frame_payload = 0;
}
for (n = ctx->tx_curr_frame_num; n < ctx->tx_max_datagrams; n++) {
......@@ -719,6 +975,7 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
ctx->tx_rem_sign = sign;
skb = NULL;
ready2send = 1;
ctx->tx_reason_ntb_full++; /* count reason for transmitting */
}
break;
}
......@@ -732,12 +989,14 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
ndp16->dpe16[index].wDatagramIndex = cpu_to_le16(skb_out->len);
ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16));
memcpy(skb_put(skb_out, skb->len), skb->data, skb->len);
ctx->tx_curr_frame_payload += skb->len; /* count real tx payload data */
dev_kfree_skb_any(skb);
skb = NULL;
/* send now if this NDP is full */
if (index >= CDC_NCM_DPT_DATAGRAMS_MAX) {
ready2send = 1;
ctx->tx_reason_ndp_full++; /* count reason for transmitting */
break;
}
}
......@@ -757,7 +1016,7 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
ctx->tx_curr_skb = skb_out;
goto exit_no_skb;
} else if ((n < ctx->tx_max_datagrams) && (ready2send == 0)) {
} else if ((n < ctx->tx_max_datagrams) && (ready2send == 0) && (ctx->timer_interval > 0)) {
/* wait for more frames */
/* push variables */
ctx->tx_curr_skb = skb_out;
......@@ -767,11 +1026,13 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
goto exit_no_skb;
} else {
if (n == ctx->tx_max_datagrams)
ctx->tx_reason_max_datagram++; /* count reason for transmitting */
/* frame goes out */
/* variables will be reset at next call */
}
/* If collected data size is less or equal CDC_NCM_MIN_TX_PKT
/* If collected data size is less or equal ctx->min_tx_pkt
* bytes, we send buffers as it is. If we get more data, it
* would be more efficient for USB HS mobile device with DMA
* engine to receive a full size NTB, than canceling DMA
......@@ -781,7 +1042,7 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
* a ZLP after full sized NTBs.
*/
if (!(dev->driver_info->flags & FLAG_SEND_ZLP) &&
skb_out->len > CDC_NCM_MIN_TX_PKT)
skb_out->len > ctx->min_tx_pkt)
memset(skb_put(skb_out, ctx->tx_max - skb_out->len), 0,
ctx->tx_max - skb_out->len);
else if (skb_out->len < ctx->tx_max && (skb_out->len % dev->maxpacket) == 0)
......@@ -794,11 +1055,22 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
/* return skb */
ctx->tx_curr_skb = NULL;
dev->net->stats.tx_packets += ctx->tx_curr_frame_num;
/* keep private stats: framing overhead and number of NTBs */
ctx->tx_overhead += skb_out->len - ctx->tx_curr_frame_payload;
ctx->tx_ntbs++;
/* usbnet has already counted all the framing overhead.
* Adjust the stats so that the tx_bytes counter show real
* payload data instead.
*/
dev->net->stats.tx_bytes -= skb_out->len - ctx->tx_curr_frame_payload;
return skb_out;
exit_no_skb:
/* Start timer, if there is a remaining skb */
if (ctx->tx_curr_skb != NULL)
/* Start timer, if there is a remaining non-empty skb */
if (ctx->tx_curr_skb != NULL && n > 0)
cdc_ncm_tx_timeout_start(ctx);
return NULL;
}
......@@ -809,7 +1081,7 @@ static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx)
/* start timer, if not already started */
if (!(hrtimer_active(&ctx->tx_timer) || atomic_read(&ctx->stop)))
hrtimer_start(&ctx->tx_timer,
ktime_set(0, CDC_NCM_TIMER_INTERVAL),
ktime_set(0, ctx->timer_interval),
HRTIMER_MODE_REL);
}
......@@ -834,6 +1106,7 @@ static void cdc_ncm_txpath_bh(unsigned long param)
cdc_ncm_tx_timeout_start(ctx);
spin_unlock_bh(&ctx->mtx);
} else if (dev->net != NULL) {
ctx->tx_reason_timeout++; /* count reason for transmitting */
spin_unlock_bh(&ctx->mtx);
netif_tx_lock_bh(dev->net);
usbnet_start_xmit(NULL, dev->net);
......@@ -969,6 +1242,7 @@ int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
struct usb_cdc_ncm_dpe16 *dpe16;
int ndpoffset;
int loopcount = 50; /* arbitrary max preventing infinite loop */
u32 payload = 0;
ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in);
if (ndpoffset < 0)
......@@ -1021,6 +1295,7 @@ int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
skb->data = ((u8 *)skb_in->data) + offset;
skb_set_tail_pointer(skb, len);
usbnet_skb_return(dev, skb);
payload += len; /* count payload bytes in this NTB */
}
}
err_ndp:
......@@ -1029,6 +1304,10 @@ int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
if (ndpoffset && loopcount--)
goto next_ndp;
/* update stats */
ctx->rx_overhead += skb_in->len - payload;
ctx->rx_ntbs++;
return 1;
error:
return 0;
......@@ -1085,11 +1364,10 @@ static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)
* USB_CDC_NOTIFY_NETWORK_CONNECTION notification shall be
* sent by device after USB_CDC_NOTIFY_SPEED_CHANGE.
*/
ctx->connected = le16_to_cpu(event->wValue);
netif_info(dev, link, dev->net,
"network connection: %sconnected\n",
ctx->connected ? "" : "dis");
usbnet_link_change(dev, ctx->connected, 0);
!!event->wValue ? "" : "dis");
usbnet_link_change(dev, !!event->wValue, 0);
break;
case USB_CDC_NOTIFY_SPEED_CHANGE:
......@@ -1109,23 +1387,11 @@ static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)
}
}
static int cdc_ncm_check_connect(struct usbnet *dev)
{
struct cdc_ncm_ctx *ctx;
ctx = (struct cdc_ncm_ctx *)dev->data[0];
if (ctx == NULL)
return 1; /* disconnected */
return !ctx->connected;
}
static const struct driver_info cdc_ncm_info = {
.description = "CDC NCM",
.flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET,
.bind = cdc_ncm_bind,
.unbind = cdc_ncm_unbind,
.check_connect = cdc_ncm_check_connect,
.manage_power = usbnet_manage_power,
.status = cdc_ncm_status,
.rx_fixup = cdc_ncm_rx_fixup,
......@@ -1139,7 +1405,6 @@ static const struct driver_info wwan_info = {
| FLAG_WWAN,
.bind = cdc_ncm_bind,
.unbind = cdc_ncm_unbind,
.check_connect = cdc_ncm_check_connect,
.manage_power = usbnet_manage_power,
.status = cdc_ncm_status,
.rx_fixup = cdc_ncm_rx_fixup,
......@@ -1153,7 +1418,6 @@ static const struct driver_info wwan_noarp_info = {
| FLAG_WWAN | FLAG_NOARP,
.bind = cdc_ncm_bind,
.unbind = cdc_ncm_unbind,
.check_connect = cdc_ncm_check_connect,
.manage_power = usbnet_manage_power,
.status = cdc_ncm_status,
.rx_fixup = cdc_ncm_rx_fixup,
......
......@@ -172,24 +172,11 @@ static int huawei_cdc_ncm_resume(struct usb_interface *intf)
return ret;
}
static int huawei_cdc_ncm_check_connect(struct usbnet *usbnet_dev)
{
struct cdc_ncm_ctx *ctx;
ctx = (struct cdc_ncm_ctx *)usbnet_dev->data[0];
if (ctx == NULL)
return 1; /* disconnected */
return !ctx->connected;
}
static const struct driver_info huawei_cdc_ncm_info = {
.description = "Huawei CDC NCM device",
.flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
.bind = huawei_cdc_ncm_bind,
.unbind = huawei_cdc_ncm_unbind,
.check_connect = huawei_cdc_ncm_check_connect,
.manage_power = huawei_cdc_ncm_manage_power,
.rx_fixup = cdc_ncm_rx_fixup,
.tx_fixup = cdc_ncm_tx_fixup,
......
......@@ -52,6 +52,10 @@
#define CDC_NCM_NTB_MAX_SIZE_TX 32768 /* bytes */
#define CDC_NCM_NTB_MAX_SIZE_RX 32768 /* bytes */
/* Initial NTB length */
#define CDC_NCM_NTB_DEF_SIZE_TX 16384 /* bytes */
#define CDC_NCM_NTB_DEF_SIZE_RX 16384 /* bytes */
/* Minimum value for MaxDatagramSize, ch. 6.2.9 */
#define CDC_NCM_MIN_DATAGRAM_SIZE 1514 /* bytes */
......@@ -72,16 +76,9 @@
/* Restart the timer, if amount of datagrams is less than given value */
#define CDC_NCM_RESTART_TIMER_DATAGRAM_CNT 3
#define CDC_NCM_TIMER_PENDING_CNT 2
#define CDC_NCM_TIMER_INTERVAL (400UL * NSEC_PER_USEC)
/* The following macro defines the minimum header space */
#define CDC_NCM_MIN_HDR_SIZE \
(sizeof(struct usb_cdc_ncm_nth16) + sizeof(struct usb_cdc_ncm_ndp16) + \
(CDC_NCM_DPT_DATAGRAMS_MAX + 1) * sizeof(struct usb_cdc_ncm_dpe16))
#define CDC_NCM_NDP_SIZE \
(sizeof(struct usb_cdc_ncm_ndp16) + \
(CDC_NCM_DPT_DATAGRAMS_MAX + 1) * sizeof(struct usb_cdc_ncm_dpe16))
#define CDC_NCM_TIMER_INTERVAL_USEC 400UL
#define CDC_NCM_TIMER_INTERVAL_MIN 5UL
#define CDC_NCM_TIMER_INTERVAL_MAX (15UL * USEC_PER_SEC)
#define cdc_ncm_comm_intf_is_mbim(x) ((x)->desc.bInterfaceSubClass == USB_CDC_SUBCLASS_MBIM && \
(x)->desc.bInterfaceProtocol == USB_CDC_PROTO_NONE)
......@@ -107,6 +104,9 @@ struct cdc_ncm_ctx {
spinlock_t mtx;
atomic_t stop;
u64 timer_interval;
u32 max_ndp_size;
u32 tx_timer_pending;
u32 tx_curr_frame_num;
u32 rx_max;
......@@ -118,7 +118,18 @@ struct cdc_ncm_ctx {
u16 tx_ndp_modulus;
u16 tx_seq;
u16 rx_seq;
u16 connected;
u16 min_tx_pkt;
/* statistics */
u32 tx_curr_frame_payload;
u32 tx_reason_ntb_full;
u32 tx_reason_ndp_full;
u32 tx_reason_timeout;
u32 tx_reason_max_datagram;
u64 tx_overhead;
u64 tx_ntbs;
u64 rx_overhead;
u64 rx_ntbs;
};
u8 cdc_ncm_select_altsetting(struct usb_interface *intf);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment