Hello guys. Sorry for the long delay / timing: but I arrived to the conclusion I had to shut up and inform myself and write somecode. :D Yes, you madeit! So here are my findings and answers.
On Tue, 9 Jun 2015, Oliver Neukum wrote: ==Date: Tue, 9 Jun 2015 12:38:59 ==From: Oliver Neukum <oneu...@suse.com> ==To: Enrico Mioso <mrkiko...@gmail.com> ==Cc: netdev@vger.kernel.org ==Subject: Re: [RFC PATCH] cdc_ncm: support moving the NDP part of the frame to == the end of NCM package == ==On Tue, 2015-06-09 at 09:46 +0200, Enrico Mioso wrote: == ==> ==Not another parameter please. If the alternate frames are processed as ==> ==well as the current frames, all is well and we can generally produces ==> ==such frames. If not, we want a black list. ==> ==> I would agree on this point: and I was exploring different alternatives also, ==> such as having this option settable when invoking cdc_ncm_bind_common from ==> huawei_cdc_ncm for example. Unfortunately, I don't feel confident we could do ==> that. == ==Using a module parameter is simply wrong. A system can be connected to ==multiple devices (in turn). As a minimum the feature must be per device. == Sure. ==> First of all: this driver supports more devices than Huawei ones, so I feel we ==> have chances to break them modifying the frame structure. == ==In theory we must never break or impede compliant devices for ==uncompliant devices. Yet I doubt we can break any device doing what ==Windows does. ==Does it have a standard NCM driver that works with Huawei devices? == ==> Even more complicated is the fact that huawei NCM devices are not easily ==> distinguishable one another from the driver perspective. Some heuristics may be ==> put in place probably, but I don't have access to old enough NCM devices to ==> know best. ==> The Huawei vendor driver put NDPs at end of frame - and does so for all devices ==> in my opinion, but I can't be really sure about that. == ==Doesn't it have a list of supported devices? I don't have a Windows box readily at disposal to look into - partly due to accessiblity problems here. Still, Mobile Partner comes with it's own drivers for NCM devices. == ==> Not now. Anyway I can change this as desired. Would something like a sysfs knob ==> be preferable? User-space surely has a good chance to tell us what to do here. == ==Not really. The answer will come from a list. The question is just ==whether easier updates are worth splitting up the fix. == The Linux huawei vendor driver defines various kinds of NDIS interfaces: but doesn't threat them differently when it comes to NCM framing: it always writes the NDP after the datagrams sequence, at least from what I understand reading the code. Some device work with both the cdc_ncm-way and huawei_cdc-way, others don't. ==> ==> --- a/include/linux/usb/cdc_ncm.h ==> ==> +++ b/include/linux/usb/cdc_ncm.h ==> ==> @@ -93,6 +93,7 @@ struct cdc_ncm_ctx { ==> ==> const struct usb_cdc_mbim_desc *mbim_desc; ==> ==> const struct usb_cdc_mbim_extended_desc *mbim_extended_desc; ==> ==> const struct usb_cdc_ether_desc *ether_desc; ==> ==> + struct usb_cdc_ncm_ndp16 *delayed_ndp; ==> == ==> ==What prevents you from embedding this in the structure? ==> == ==> == Regards ==> == Oliver ==> == ==> == ==> == ==> Sorry - I think I didn't understand your ocmment here. Still, I am open to any ==> suggestion. == ==Why don't you put "struct usb_cdc_ncm_ndp16 delayed_ndp" as opposed to a ==pointer to it into struct cdc_ncm_ctx. You never need more than one at a ==time. == == Regards == Oliver == == == I guess this will be clear reading the code I am posting here. Based on the situation and constrains, I decided to re-write the NCM tx stack: and write a huawei specific one, in the huawei_cdc_ncm.c driver. I can actually produce SKBs which are processed and seemigly received from the Linux cdc_ncm rx_fixup function, still not by the device I am testing this on, an E3131 3G dongle. I don't know exactly why at the moment - still, I imagine two possibilities. I might be aligning things wrong, or I might be doing some programming errors and corrupting the data somewhere. Any suggestion on how to verify this are welcome. Si I post here the entire huawei_cdc_ncm.c file, since a patch would be probably not so smaller. The only things I changed are still huawei_ncm_mgmt, huawei_ncm_tx_fixup and some defines got added. I decided to split the NCM engine in two parts: 1 - The manager: which performs operations on frames based on what you ask. 2 - The packet processing path, which is in huawei_cdc_ncm_tx_fixup. I expect all kinds of errors here: still hoping my work can show a serious intention in trying to do my best. Obviously, printks sparkled all-over, huawei_show_ndp and probably many checks will go away. Some of them are redundant probably and make the code ugly. I put them in place at least so I can avoid rebooting my virtual machine :D . thank you, Enrico /* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as * transport layer. * Copyright (C) 2013 Enrico Mioso <mrkiko...@gmail.com> * * * ABSTRACT: * This driver handles devices resembling the CDC NCM standard, but * encapsulating another protocol inside it. An example are some Huawei 3G * devices, exposing an embedded AT channel where you can set up the NCM * connection. * This code has been heavily inspired by the cdc_mbim.c driver, which is * Copyright (c) 2012 Smith Micro Software, Inc. * Copyright (c) 2012 Bjørn Mork <bj...@mork.no> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. */ #include <linux/module.h> #include <linux/netdevice.h> #include <linux/ethtool.h> #include <linux/if_vlan.h> #include <linux/ip.h> #include <linux/mii.h> #include <linux/usb.h> #include <linux/usb/cdc.h> #include <linux/usb/usbnet.h> #include <linux/usb/cdc-wdm.h> #include <linux/usb/cdc_ncm.h> /* NCM management operations: */ /* INIT_NCM_FRAME: prepare for a new frame. * NTH16 header is written to output SKB, and NDP data is reset. * Now, data may be added to this NCM package. */ #define NCM_INIT_FRAME 1 /* NCM_UPDATE_NDP: adds data to an NDP structure, hence updating it. * Some checks are performed to be sure data fits in, respecting device and * spec constrains. */ #define NCM_UPDATE_NDP 2 /* Write NDP: commits NDP to output SKB. */ #define NCM_COMMIT_NDP 3 /* NCM_FRAME_AVAILSPC: return to caller how much space is available in the frame. */ #define NCM_NDP_AVAILSPC 4 /* Driver data */ struct huawei_cdc_ncm_state { struct cdc_ncm_ctx *ctx; atomic_t pmcount; struct usb_driver *subdriver; struct usb_interface *control; struct usb_interface *data; /* Keeps track of where data starts and ends in SKBs. */ int data_start; int data_len; /* Temporary NDP storage */ struct usb_cdc_ncm_ndp16 *ndp; /* Temporary SKB for excess data. */ struct sk_buff *skb_excess; }; static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on) { struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; int rv; if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) || (!on && atomic_dec_and_test(&drvstate->pmcount))) { rv = usb_autopm_get_interface(usbnet_dev->intf); usbnet_dev->intf->needs_remote_wakeup = on; if (!rv) usb_autopm_put_interface(usbnet_dev->intf); } return 0; } static void huawei_show_ndp(struct usb_cdc_ncm_ndp16 *ndp16) { u16 ndplen; int i; int index; if (!ndp16) { printk("\nNo NDP to show."); return; } ndplen = le16_to_cpu(ndp16->wLength); index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1; printk("\nNDP INFO: \nNDP length: %d; Points to next NDP at %d.",ndp16->wLength,ndp16->wNextNdpIndex); printk("\nDPE data follows: "); for (i=0;i<=index;i++) { printk("\nIndex %d: ",i); printk("datagram index %d, wdatagramlength %d.",cpu_to_le16(ndp16->dpe16[i].wDatagramIndex),cpu_to_le16(ndp16->dpe16[i].wDatagramLength)); } printk("\nEND NDP INFO"); return; } int huawei_ncm_mgmt(struct huawei_cdc_ncm_state *drvstate, struct sk_buff *skb_out, int mode) { struct usb_cdc_ncm_nth16 *nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data; struct cdc_ncm_ctx *ctx = drvstate->ctx; struct usb_cdc_ncm_ndp16 *ndp16 = drvstate->ndp; int ret = -EINVAL; u16 ndplen, index; switch(mode) { case NCM_INIT_FRAME: /* Write a new NTH16 header */ nth16 = (struct usb_cdc_ncm_nth16 *)memset(skb_put(skb_out, sizeof(struct usb_cdc_ncm_nth16)), 0, sizeof(struct usb_cdc_ncm_nth16)); if (!nth16) { printk("\nNo memory for NTH."); ret = -EINVAL; goto error; } /* NTH16 signature and header length are known a-priori. */ nth16->dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN); nth16->wHeaderLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_nth16)); /* TX sequence numbering: not initialized. */ nth16->wSequence = cpu_to_le16(ctx->tx_seq++); /* Prepare a new NDP to add data on subsequent calls. */ ndp16 = memset(drvstate->ndp, 0, ctx->max_ndp_size); /* Initial NDP length */ ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16)); /* Frame signature: to be reworked. */ ndp16->dwSignature = cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN); /* All went well. */ ret = 0; break; case NCM_UPDATE_NDP: /* Do we have enough space for the data? */ if (skb_out->len + ctx->max_ndp_size > ctx->tx_max) { printk("\nDevice constrains are not allowing this data."); return ret; } /* Calculate frame number withing this NDP */ ndplen = le16_to_cpu(ndp16->wLength); index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1; if (index >= CDC_NCM_DPT_DATAGRAMS_MAX) { printk("\nDPT16 limit."); return ret; } /* tx_max shouldn't be exceeded after committing. */ if (ndplen + sizeof(struct usb_cdc_ncm_dpe16) > ctx->tx_max) { printk("\nNo room for NDP."); return ret; } /* Adding a DPT entry. */ ndp16->dpe16[index].wDatagramLength = cpu_to_le16(drvstate->data_len); ndp16->dpe16[index].wDatagramIndex = cpu_to_le16(drvstate->data_start); ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16)); ret = 0; break; case NCM_COMMIT_NDP: /* Add the NULL DPE ndplen = le16_to_cpu(ndp16->wLength); index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1; ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16)); */ /* NTH is here! */ nth16->wNdpIndex = cpu_to_le16(skb_out->len); /* Write NDP */ memcpy(skb_put(skb_out, ctx->max_ndp_size), ndp16, ctx->max_ndp_size); /* Finalize NTH16 header, setting it's block length */ nth16->wBlockLength = cpu_to_le16(skb_out->len); huawei_show_ndp(ndp16); ret = 0; break; default: ret = -EINVAL; printk("\nInvalid operation requested: %d.",mode); break; } error: return ret; } struct sk_buff * huawei_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb_in, gfp_t flags) { struct huawei_cdc_ncm_state *drvstate = (void *)&dev->data; struct cdc_ncm_ctx *ctx = drvstate->ctx; struct sk_buff *skb_out; int status; /* Allocate a fresh OUT skb. */ skb_out = alloc_skb(ctx->tx_max, GFP_NOIO); printk("\nSKB allocated."); /* Create base NCM structure */ status = huawei_ncm_mgmt(drvstate, skb_out, NCM_INIT_FRAME); printk("\nStarting new NCM frame."); /* Do we have excess data? */ if (drvstate->skb_excess) { drvstate->data_start = skb_out->len; /* Align beginning of next frame. */ cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max); memcpy(skb_put(skb_out, drvstate->skb_excess->len), drvstate->skb_excess->data, drvstate->skb_excess->len); drvstate->data_len = skb_out->len-drvstate->data_start; printk("\nCopying excess data: from %d to %d, asking mgr to update NDP.",drvstate->data_start,skb_out->len); status = huawei_ncm_mgmt(drvstate, skb_out, NCM_UPDATE_NDP); if (status) { printk("\nExcess data can't fit in NDP. MTU problem? "); goto error_dealloc; } dev_kfree_skb_any(drvstate->skb_excess); } /* Timer context may be placed here. */ if (skb_in->len + skb_out->len > ctx->tx_max) { printk("\nTotal data can not fit, still excess can be sent."); /* Still we might try to send out excess data we already added. */ if (skb_out->len != 0) { goto send_out; } goto error_dealloc; } /* Align beginning of next frame. */ cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max); drvstate->data_start = skb_out->len; memcpy(skb_put(skb_out, skb_in->len), skb_in->data, skb_in->len); drvstate->data_len = skb_out->len-drvstate->data_start; printk("\nIN data copied from %d to %d, asking MGR to update NDP.",drvstate->data_start,skb_out->len); /* Add data to NDP */ status = huawei_ncm_mgmt(drvstate, skb_out, NCM_UPDATE_NDP); if (status) { printk("\nNew data can not fit in NDP."); drvstate->skb_excess = alloc_skb(skb_in->len, GFP_NOIO); if (!drvstate->skb_excess) { printk("\nCan not allocate excess data buffer."); goto error_dealloc; } memcpy(skb_put(drvstate->skb_excess, skb_in->len), skb_in->data, skb_in->len); goto send_out; } send_out: /* Write final NDP frame */ printk("\nCommitting NDP."); /* Align new NDP */ cdc_ncm_align_tail(skb_out, ctx->tx_ndp_modulus, 0, ctx->tx_max); status = huawei_ncm_mgmt(drvstate, skb_out, NCM_COMMIT_NDP); if (status) { printk("\nError writing final NDP."); goto error_dealloc; } if (skb_in) { dev_kfree_skb_any(skb_in); printk("\nIN skb deallocated."); } //cdc_ncm_rx_test(dev, skb_out); return skb_out; error_dealloc: if (drvstate->skb_excess) dev_kfree_skb_any(drvstate->skb_excess); //not_enough_data: if (skb_in) dev_kfree_skb_any(skb_in); return NULL; } static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf, int status) { struct usbnet *usbnet_dev = usb_get_intfdata(intf); /* can be called while disconnecting */ if (!usbnet_dev) return 0; return huawei_cdc_ncm_manage_power(usbnet_dev, status); } static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev, struct usb_interface *intf) { struct cdc_ncm_ctx *ctx; struct usb_driver *subdriver = ERR_PTR(-ENODEV); int ret = -ENODEV; struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; /* altsetting should always be 1 for NCM devices - so we hard-coded * it here */ ret = cdc_ncm_bind_common(usbnet_dev, intf, 1); if (ret) goto err; ctx = drvstate->ctx; if (usbnet_dev->status) /* The wMaxCommand buffer must be big enough to hold * any message from the modem. Experience has shown * that some replies are more than 256 bytes long */ subdriver = usb_cdc_wdm_register(ctx->control, &usbnet_dev->status->desc, 1024, /* wMaxCommand */ huawei_cdc_ncm_wdm_manage_power); if (IS_ERR(subdriver)) { ret = PTR_ERR(subdriver); cdc_ncm_unbind(usbnet_dev, intf); goto err; } /* Prevent usbnet from using the status descriptor */ usbnet_dev->status = NULL; drvstate->subdriver = subdriver; /* Allocate temporary NDP storage for NDP building */ drvstate->ndp = kzalloc(ctx->max_ndp_size, GFP_KERNEL); err: return ret; } static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, struct usb_interface *intf) { struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; struct cdc_ncm_ctx *ctx = drvstate->ctx; if (drvstate->subdriver && drvstate->subdriver->disconnect) drvstate->subdriver->disconnect(ctx->control); drvstate->subdriver = NULL; cdc_ncm_unbind(usbnet_dev, intf); /* Deallocate temporary NDP storage */ kfree(drvstate->ndp); } static int huawei_cdc_ncm_suspend(struct usb_interface *intf, pm_message_t message) { int ret = 0; struct usbnet *usbnet_dev = usb_get_intfdata(intf); struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; struct cdc_ncm_ctx *ctx = drvstate->ctx; if (ctx == NULL) { ret = -ENODEV; goto error; } ret = usbnet_suspend(intf, message); if (ret < 0) goto error; if (intf == ctx->control && drvstate->subdriver && drvstate->subdriver->suspend) ret = drvstate->subdriver->suspend(intf, message); if (ret < 0) usbnet_resume(intf); error: return ret; } static int huawei_cdc_ncm_resume(struct usb_interface *intf) { int ret = 0; struct usbnet *usbnet_dev = usb_get_intfdata(intf); struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; bool callsub; struct cdc_ncm_ctx *ctx = drvstate->ctx; /* should we call subdriver's resume function? */ callsub = (intf == ctx->control && drvstate->subdriver && drvstate->subdriver->resume); if (callsub) ret = drvstate->subdriver->resume(intf); if (ret < 0) goto err; ret = usbnet_resume(intf); if (ret < 0 && callsub) drvstate->subdriver->suspend(intf, PMSG_SUSPEND); err: return ret; } 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, .manage_power = huawei_cdc_ncm_manage_power, .rx_fixup = cdc_ncm_rx_fixup, .tx_fixup = huawei_ncm_tx_fixup, }; static const struct usb_device_id huawei_cdc_ncm_devs[] = { /* Huawei NCM devices disguised as vendor specific */ { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16), .driver_info = (unsigned long)&huawei_cdc_ncm_info, }, { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46), .driver_info = (unsigned long)&huawei_cdc_ncm_info, }, { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76), .driver_info = (unsigned long)&huawei_cdc_ncm_info, }, { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16), .driver_info = (unsigned long)&huawei_cdc_ncm_info, }, /* Terminating entry */ { }, }; MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs); static struct usb_driver huawei_cdc_ncm_driver = { .name = "huawei_cdc_ncm", .id_table = huawei_cdc_ncm_devs, .probe = usbnet_probe, .disconnect = usbnet_disconnect, .suspend = huawei_cdc_ncm_suspend, .resume = huawei_cdc_ncm_resume, .reset_resume = huawei_cdc_ncm_resume, .supports_autosuspend = 1, .disable_hub_initiated_lpm = 1, }; module_usb_driver(huawei_cdc_ncm_driver); MODULE_AUTHOR("Enrico Mioso <mrkiko...@gmail.com>"); MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support"); MODULE_LICENSE("GPL");