diff mbox series

[v5] wwan: core: Support slicing in port TX flow of WWAN subsystem

Message ID 20221124074725.74325-1-haozhe.chang@mediatek.com
State Superseded
Headers show
Series [v5] wwan: core: Support slicing in port TX flow of WWAN subsystem | expand

Commit Message

Haozhe Chang (常浩哲) Nov. 24, 2022, 7:47 a.m. UTC
From: haozhe chang <haozhe.chang@mediatek.com>

wwan_port_fops_write inputs the SKB parameter to the TX callback of
the WWAN device driver. However, the WWAN device (e.g., t7xx) may
have an MTU less than the size of SKB, causing the TX buffer to be
sliced and copied once more in the WWAN device driver.

This patch implements the slicing in the WWAN subsystem and gives
the WWAN devices driver the option to slice(by frag_len) or not. By
doing so, the additional memory copy is reduced.

Meanwhile, this patch gives WWAN devices driver the option to reserve
headroom in fragments for the device-specific metadata.

Signed-off-by: haozhe chang <haozhe.chang@mediatek.com>

---
Changes in v2
  -send fragments to device driver by skb frag_list.

Changes in v3
  -move frag_len and headroom_len setting to wwan_create_port.

Changes in v4
  -change unreadable parameters to macro definition.

Changes in v5
  -optimize comments for WWAN_NO_HEADROOM, WWAN_NO_FRAGMENT.
---
 drivers/net/wwan/iosm/iosm_ipc_port.c  |  3 +-
 drivers/net/wwan/mhi_wwan_ctrl.c       |  3 +-
 drivers/net/wwan/rpmsg_wwan_ctrl.c     |  3 +-
 drivers/net/wwan/t7xx/t7xx_port_wwan.c | 34 +++++++--------
 drivers/net/wwan/wwan_core.c           | 59 ++++++++++++++++++++------
 drivers/net/wwan/wwan_hwsim.c          |  1 +
 drivers/usb/class/cdc-wdm.c            |  3 +-
 include/linux/wwan.h                   | 16 +++++++
 8 files changed, 87 insertions(+), 35 deletions(-)

Comments

Loic Poulain Dec. 1, 2022, 9:56 a.m. UTC | #1
On Thu, 24 Nov 2022 at 08:47, <haozhe.chang@mediatek.com> wrote:
>
> From: haozhe chang <haozhe.chang@mediatek.com>
>
> wwan_port_fops_write inputs the SKB parameter to the TX callback of
> the WWAN device driver. However, the WWAN device (e.g., t7xx) may
> have an MTU less than the size of SKB, causing the TX buffer to be
> sliced and copied once more in the WWAN device driver.
>
> This patch implements the slicing in the WWAN subsystem and gives
> the WWAN devices driver the option to slice(by frag_len) or not. By
> doing so, the additional memory copy is reduced.
>
> Meanwhile, this patch gives WWAN devices driver the option to reserve
> headroom in fragments for the device-specific metadata.
>
> Signed-off-by: haozhe chang <haozhe.chang@mediatek.com>

Reviewed-by: Loic Poulain <loic.poulain@linaro.org>
Haozhe Chang (常浩哲) Dec. 14, 2022, 8:25 a.m. UTC | #2
HI Loic

On Thu, 2022-12-01 at 10:56 +0100, Loic Poulain wrote:
> On Thu, 24 Nov 2022 at 08:47, <haozhe.chang@mediatek.com> wrote:
> > 
> > From: haozhe chang <haozhe.chang@mediatek.com>
> > 
> > wwan_port_fops_write inputs the SKB parameter to the TX callback of
> > the WWAN device driver. However, the WWAN device (e.g., t7xx) may
> > have an MTU less than the size of SKB, causing the TX buffer to be
> > sliced and copied once more in the WWAN device driver.
> > 
> > This patch implements the slicing in the WWAN subsystem and gives
> > the WWAN devices driver the option to slice(by frag_len) or not. By
> > doing so, the additional memory copy is reduced.
> > 
> > Meanwhile, this patch gives WWAN devices driver the option to
> > reserve
> > headroom in fragments for the device-specific metadata.
> > 
> > Signed-off-by: haozhe chang <haozhe.chang@mediatek.com>
> 
> Reviewed-by: Loic Poulain <loic.poulain@linaro.org>

I have submitted patch V6 to add a reviewer, do you have any other
suggestions about the patch?
Loic Poulain Dec. 14, 2022, 9:10 a.m. UTC | #3
> On Thu, 2022-12-01 at 10:56 +0100, Loic Poulain wrote:
> > On Thu, 24 Nov 2022 at 08:47, <haozhe.chang@mediatek.com> wrote:
> > >
> > > From: haozhe chang <haozhe.chang@mediatek.com>
> > >
> > > wwan_port_fops_write inputs the SKB parameter to the TX callback of
> > > the WWAN device driver. However, the WWAN device (e.g., t7xx) may
> > > have an MTU less than the size of SKB, causing the TX buffer to be
> > > sliced and copied once more in the WWAN device driver.
> > >
> > > This patch implements the slicing in the WWAN subsystem and gives
> > > the WWAN devices driver the option to slice(by frag_len) or not. By
> > > doing so, the additional memory copy is reduced.
> > >
> > > Meanwhile, this patch gives WWAN devices driver the option to
> > > reserve
> > > headroom in fragments for the device-specific metadata.
> > >
> > > Signed-off-by: haozhe chang <haozhe.chang@mediatek.com>
> >
> > Reviewed-by: Loic Poulain <loic.poulain@linaro.org>
>
> I have submitted patch V6 to add a reviewer, do you have any other
> suggestions about the patch?

You normally don't need to resubmit a version just for adding review
tags, as it is well tracked. You can see status of netdev changes from
patchwork:
https://patchwork.kernel.org/project/netdevbpf/list/?series=&submitter=207580&state=*&q=&archive=both&delegate=

Regarding this change you should however resubmit for the net-next
tree with appropriate subject since it is not a bug fix:
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html?highlight=netdev#how-do-i-indicate-which-tree-net-vs-net-next-my-patch-should-be-in

Then it should be picked by netdev maintainer(s). But note that we're
currently in the Linux 6.2 merge window, so merging for net-next can
be delayed until the mainline merge window is closed (and net-next
open):
https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html?highlight=netdev#how-often-do-changes-from-these-trees-make-it-to-the-mainline-linus-tree

Regards,
Loic
Haozhe Chang (常浩哲) March 3, 2023, 8:59 a.m. UTC | #4
Dear Loic

I'm sorry to bother you, but I want to know whether my patch is accepted by the community.
Because it seems to be a merge window, but the patch state still is "Not Applicable". Could you 
give me some suggestions about this patch state?

BR
Haozhe

-----邮件原件-----
发件人: Loic Poulain <loic.poulain@linaro.org> 
发送时间: 2022年12月14日 17:11
收件人: Haozhe Chang (常浩哲) <Haozhe.Chang@mediatek.com>
抄送: stephan@gerhold.net; oneukum@suse.com; linux-kernel@vger.kernel.org; linux-usb@vger.kernel.org; linux-remoteproc@vger.kernel.org; linuxwwan@intel.com; m.chetan.kumar@intel.com; linux-mediatek@lists.infradead.org; Hua Yang (杨华) <Hua.Yang@mediatek.com>; chiranjeevi.rapolu@linux.intel.com; Haijun Liu (刘海军) <haijun.liu@mediatek.com>; linux-arm-kernel@lists.infradead.org; ryazanov.s.a@gmail.com; kuba@kernel.org; Xiayu Zhang (张夏宇) <Xiayu.Zhang@mediatek.com>; pabeni@redhat.com; edumazet@google.com; chandrashekar.devegowda@intel.com; johannes@sipsolutions.net; gregkh@linuxfoundation.org; shangxiaojing@huawei.com; Lambert Wang (王伟) <Lambert.Wang@mediatek.com>; matthias.bgg@gmail.com; davem@davemloft.net; ricardo.martinez@linux.intel.com; netdev@vger.kernel.org
主题: Re: [PATCH v5] wwan: core: Support slicing in port TX flow of WWAN subsystem

> On Thu, 2022-12-01 at 10:56 +0100, Loic Poulain wrote:
> > On Thu, 24 Nov 2022 at 08:47, <haozhe.chang@mediatek.com> wrote:
> > >
> > > From: haozhe chang <haozhe.chang@mediatek.com>
> > >
> > > wwan_port_fops_write inputs the SKB parameter to the TX callback 
> > > of the WWAN device driver. However, the WWAN device (e.g., t7xx) 
> > > may have an MTU less than the size of SKB, causing the TX buffer 
> > > to be sliced and copied once more in the WWAN device driver.
> > >
> > > This patch implements the slicing in the WWAN subsystem and gives 
> > > the WWAN devices driver the option to slice(by frag_len) or not. 
> > > By doing so, the additional memory copy is reduced.
> > >
> > > Meanwhile, this patch gives WWAN devices driver the option to 
> > > reserve headroom in fragments for the device-specific metadata.
> > >
> > > Signed-off-by: haozhe chang <haozhe.chang@mediatek.com>
> >
> > Reviewed-by: Loic Poulain <loic.poulain@linaro.org>
>
> I have submitted patch V6 to add a reviewer, do you have any other 
> suggestions about the patch?

You normally don't need to resubmit a version just for adding review tags, as it is well tracked. You can see status of netdev changes from
patchwork:
https://urldefense.com/v3/__https://patchwork.kernel.org/project/netdevbpf/list/?series=&submitter=207580&state=*&q=&archive=both&delegate=__;Kg!!CTRNKA9wMg0ARbw!jXuZ2mOXJnaIzFA696nBp09HXGpJBmuH1VVD_RRUAITEOXm--zUP8GDdAm0wa70-IbxCBmbRhYR9SaLn8Muf2bG1E68$  

Regarding this change you should however resubmit for the net-next tree with appropriate subject since it is not a bug fix:
https://urldefense.com/v3/__https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html?highlight=netdev*how-do-i-indicate-which-tree-net-vs-net-next-my-patch-should-be-in__;Iw!!CTRNKA9wMg0ARbw!jXuZ2mOXJnaIzFA696nBp09HXGpJBmuH1VVD_RRUAITEOXm--zUP8GDdAm0wa70-IbxCBmbRhYR9SaLn8Mufd3UkIVI$  

Then it should be picked by netdev maintainer(s). But note that we're currently in the Linux 6.2 merge window, so merging for net-next can be delayed until the mainline merge window is closed (and net-next
open):
https://urldefense.com/v3/__https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html?highlight=netdev*how-often-do-changes-from-these-trees-make-it-to-the-mainline-linus-tree__;Iw!!CTRNKA9wMg0ARbw!jXuZ2mOXJnaIzFA696nBp09HXGpJBmuH1VVD_RRUAITEOXm--zUP8GDdAm0wa70-IbxCBmbRhYR9SaLn8MufBL1Lhxg$  

Regards,
Loic
Loic Poulain March 6, 2023, 4 p.m. UTC | #5
Hi Haozhe,

On Fri, 3 Mar 2023 at 09:59, Haozhe Chang (常浩哲)
<Haozhe.Chang@mediatek.com> wrote:
> I'm sorry to bother you, but I want to know whether my patch is accepted by the community.
> Because it seems to be a merge window, but the patch state still is "Not Applicable". Could you
> give me some suggestions about this patch state?

Please rebase your change and resubmit the latest version (can add
RESEND tag to subject). Please, also read my previous reply, I
mentioned your change should target net-next... you should add it in
the subject (look at the documentation or other mailing-list patches).

Regards,
Loic
diff mbox series

Patch

diff --git a/drivers/net/wwan/iosm/iosm_ipc_port.c b/drivers/net/wwan/iosm/iosm_ipc_port.c
index b6d81c627277..7798348f61d0 100644
--- a/drivers/net/wwan/iosm/iosm_ipc_port.c
+++ b/drivers/net/wwan/iosm/iosm_ipc_port.c
@@ -63,7 +63,8 @@  struct iosm_cdev *ipc_port_init(struct iosm_imem *ipc_imem,
 	ipc_port->ipc_imem = ipc_imem;
 
 	ipc_port->iosm_port = wwan_create_port(ipc_port->dev, port_type,
-					       &ipc_wwan_ctrl_ops, ipc_port);
+					       &ipc_wwan_ctrl_ops, WWAN_NO_FRAGMENT,
+					       WWAN_NO_HEADROOM, ipc_port);
 
 	return ipc_port;
 }
diff --git a/drivers/net/wwan/mhi_wwan_ctrl.c b/drivers/net/wwan/mhi_wwan_ctrl.c
index f7ca52353f40..c397aa53db5d 100644
--- a/drivers/net/wwan/mhi_wwan_ctrl.c
+++ b/drivers/net/wwan/mhi_wwan_ctrl.c
@@ -237,7 +237,8 @@  static int mhi_wwan_ctrl_probe(struct mhi_device *mhi_dev,
 
 	/* Register as a wwan port, id->driver_data contains wwan port type */
 	port = wwan_create_port(&cntrl->mhi_dev->dev, id->driver_data,
-				&wwan_pops, mhiwwan);
+				&wwan_pops, WWAN_NO_FRAGMENT, WWAN_NO_HEADROOM,
+				mhiwwan);
 	if (IS_ERR(port)) {
 		kfree(mhiwwan);
 		return PTR_ERR(port);
diff --git a/drivers/net/wwan/rpmsg_wwan_ctrl.c b/drivers/net/wwan/rpmsg_wwan_ctrl.c
index 31c24420ab2e..fc6c228b7e1c 100644
--- a/drivers/net/wwan/rpmsg_wwan_ctrl.c
+++ b/drivers/net/wwan/rpmsg_wwan_ctrl.c
@@ -129,7 +129,8 @@  static int rpmsg_wwan_ctrl_probe(struct rpmsg_device *rpdev)
 
 	/* Register as a wwan port, id.driver_data contains wwan port type */
 	port = wwan_create_port(parent, rpdev->id.driver_data,
-				&rpmsg_wwan_pops, rpwwan);
+				&rpmsg_wwan_pops, WWAN_NO_FRAGMENT,
+				WWAN_NO_HEADROOM, rpwwan);
 	if (IS_ERR(port))
 		return PTR_ERR(port);
 
diff --git a/drivers/net/wwan/t7xx/t7xx_port_wwan.c b/drivers/net/wwan/t7xx/t7xx_port_wwan.c
index 33931bfd78fd..b75bb272f861 100644
--- a/drivers/net/wwan/t7xx/t7xx_port_wwan.c
+++ b/drivers/net/wwan/t7xx/t7xx_port_wwan.c
@@ -54,13 +54,13 @@  static void t7xx_port_ctrl_stop(struct wwan_port *port)
 static int t7xx_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
 {
 	struct t7xx_port *port_private = wwan_port_get_drvdata(port);
-	size_t len, offset, chunk_len = 0, txq_mtu = CLDMA_MTU;
 	const struct t7xx_port_conf *port_conf;
+	struct sk_buff *cur = skb, *cloned;
 	struct t7xx_fsm_ctl *ctl;
 	enum md_state md_state;
+	int cnt = 0, ret;
 
-	len = skb->len;
-	if (!len || !port_private->chan_enable)
+	if (!port_private->chan_enable)
 		return -EINVAL;
 
 	port_conf = port_private->port_conf;
@@ -72,23 +72,21 @@  static int t7xx_port_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
 		return -ENODEV;
 	}
 
-	for (offset = 0; offset < len; offset += chunk_len) {
-		struct sk_buff *skb_ccci;
-		int ret;
-
-		chunk_len = min(len - offset, txq_mtu - sizeof(struct ccci_header));
-		skb_ccci = t7xx_port_alloc_skb(chunk_len);
-		if (!skb_ccci)
-			return -ENOMEM;
-
-		skb_put_data(skb_ccci, skb->data + offset, chunk_len);
-		ret = t7xx_port_send_skb(port_private, skb_ccci, 0, 0);
+	while (cur) {
+		cloned = skb_clone(cur, GFP_KERNEL);
+		cloned->len = skb_headlen(cur);
+		ret = t7xx_port_send_skb(port_private, cloned, 0, 0);
 		if (ret) {
-			dev_kfree_skb_any(skb_ccci);
+			dev_kfree_skb(cloned);
 			dev_err(port_private->dev, "Write error on %s port, %d\n",
 				port_conf->name, ret);
-			return ret;
+			return cnt ? cnt + ret : ret;
 		}
+		cnt += cur->len;
+		if (cur == skb)
+			cur = skb_shinfo(skb)->frag_list;
+		else
+			cur = cur->next;
 	}
 
 	dev_kfree_skb(skb);
@@ -154,13 +152,15 @@  static int t7xx_port_wwan_disable_chl(struct t7xx_port *port)
 static void t7xx_port_wwan_md_state_notify(struct t7xx_port *port, unsigned int state)
 {
 	const struct t7xx_port_conf *port_conf = port->port_conf;
+	unsigned int header_len = sizeof(struct ccci_header);
 
 	if (state != MD_STATE_READY)
 		return;
 
 	if (!port->wwan_port) {
 		port->wwan_port = wwan_create_port(port->dev, port_conf->port_type,
-						   &wwan_ops, port);
+						   &wwan_ops, CLDMA_MTU - header_len,
+						   header_len, port);
 		if (IS_ERR(port->wwan_port))
 			dev_err(port->dev, "Unable to create WWWAN port %s", port_conf->name);
 	}
diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
index 62e9f7d6c9fe..8d35513bcd4c 100644
--- a/drivers/net/wwan/wwan_core.c
+++ b/drivers/net/wwan/wwan_core.c
@@ -67,6 +67,8 @@  struct wwan_device {
  * @rxq: Buffer inbound queue
  * @waitqueue: The waitqueue for port fops (read/write/poll)
  * @data_lock: Port specific data access serialization
+ * @headroom_len: SKB reserved headroom size
+ * @frag_len: Length to fragment packet
  * @at_data: AT port specific data
  */
 struct wwan_port {
@@ -79,6 +81,8 @@  struct wwan_port {
 	struct sk_buff_head rxq;
 	wait_queue_head_t waitqueue;
 	struct mutex data_lock;	/* Port specific data access serialization */
+	size_t headroom_len;
+	size_t frag_len;
 	union {
 		struct {
 			struct ktermios termios;
@@ -422,6 +426,8 @@  static int __wwan_port_dev_assign_name(struct wwan_port *port, const char *fmt)
 struct wwan_port *wwan_create_port(struct device *parent,
 				   enum wwan_port_type type,
 				   const struct wwan_port_ops *ops,
+				   size_t frag_len,
+				   unsigned int headroom_len,
 				   void *drvdata)
 {
 	struct wwan_device *wwandev;
@@ -455,6 +461,8 @@  struct wwan_port *wwan_create_port(struct device *parent,
 
 	port->type = type;
 	port->ops = ops;
+	port->frag_len = frag_len ? frag_len : SIZE_MAX;
+	port->headroom_len = headroom_len;
 	mutex_init(&port->ops_lock);
 	skb_queue_head_init(&port->rxq);
 	init_waitqueue_head(&port->waitqueue);
@@ -698,30 +706,53 @@  static ssize_t wwan_port_fops_read(struct file *filp, char __user *buf,
 static ssize_t wwan_port_fops_write(struct file *filp, const char __user *buf,
 				    size_t count, loff_t *offp)
 {
+	struct sk_buff *skb, *head = NULL, *tail = NULL;
 	struct wwan_port *port = filp->private_data;
-	struct sk_buff *skb;
+	size_t frag_len, remain = count;
 	int ret;
 
 	ret = wwan_wait_tx(port, !!(filp->f_flags & O_NONBLOCK));
 	if (ret)
 		return ret;
 
-	skb = alloc_skb(count, GFP_KERNEL);
-	if (!skb)
-		return -ENOMEM;
+	do {
+		frag_len = min(remain, port->frag_len);
+		skb = alloc_skb(frag_len + port->headroom_len, GFP_KERNEL);
+		if (!skb) {
+			ret = -ENOMEM;
+			goto freeskb;
+		}
+		skb_reserve(skb, port->headroom_len);
+
+		if (!head) {
+			head = skb;
+		} else if (!tail) {
+			skb_shinfo(head)->frag_list = skb;
+			tail = skb;
+		} else {
+			tail->next = skb;
+			tail = skb;
+		}
 
-	if (copy_from_user(skb_put(skb, count), buf, count)) {
-		kfree_skb(skb);
-		return -EFAULT;
-	}
+		if (copy_from_user(skb_put(skb, frag_len), buf + count - remain, frag_len)) {
+			ret = -EFAULT;
+			goto freeskb;
+		}
 
-	ret = wwan_port_op_tx(port, skb, !!(filp->f_flags & O_NONBLOCK));
-	if (ret) {
-		kfree_skb(skb);
-		return ret;
-	}
+		if (skb != head) {
+			head->data_len += skb->len;
+			head->len += skb->len;
+			head->truesize += skb->truesize;
+		}
+	} while (remain -= frag_len);
+
+	ret = wwan_port_op_tx(port, head, !!(filp->f_flags & O_NONBLOCK));
+	if (!ret)
+		return count;
 
-	return count;
+freeskb:
+	kfree_skb(head);
+	return ret;
 }
 
 static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait)
diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c
index ff09a8cedf93..7fb54cb51628 100644
--- a/drivers/net/wwan/wwan_hwsim.c
+++ b/drivers/net/wwan/wwan_hwsim.c
@@ -205,6 +205,7 @@  static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev)
 
 	port->wwan = wwan_create_port(&dev->dev, WWAN_PORT_AT,
 				      &wwan_hwsim_port_ops,
+				      WWAN_NO_FRAGMENT, WWAN_NO_HEADROOM,
 				      port);
 	if (IS_ERR(port->wwan)) {
 		err = PTR_ERR(port->wwan);
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
index 1f0951be15ab..e0f0bc878bbd 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -929,7 +929,8 @@  static void wdm_wwan_init(struct wdm_device *desc)
 		return;
 	}
 
-	port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops, desc);
+	port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops,
+				WWAN_NO_FRAGMENT, WWAN_NO_HEADROOM, desc);
 	if (IS_ERR(port)) {
 		dev_err(&intf->dev, "%s: Unable to create WWAN port\n",
 			dev_name(intf->usb_dev));
diff --git a/include/linux/wwan.h b/include/linux/wwan.h
index 5ce2acf444fb..adaf1f4a8652 100644
--- a/include/linux/wwan.h
+++ b/include/linux/wwan.h
@@ -62,11 +62,25 @@  struct wwan_port_ops {
 			    poll_table *wait);
 };
 
+/*
+ * Used to indicate that the WWAN core should not fragment control packages.
+ */
+#define WWAN_NO_FRAGMENT	0
+
+/*
+ * Used to indicate that the WWAN core should not reserve headroom in control packages.
+ */
+#define WWAN_NO_HEADROOM	0
+
 /**
  * wwan_create_port - Add a new WWAN port
  * @parent: Device to use as parent and shared by all WWAN ports
  * @type: WWAN port type
  * @ops: WWAN port operations
+ * @frag_len: WWAN port TX fragments length, if WWAN_NO_FRAGMENT is set,
+ *            the WWAN core don't fragment control packages.
+ * @headroom_len: WWAN port TX fragments reserved headroom length, if WWAN_NO_HEADROOM
+ *                is set, the WWAN core don't reserve headroom in control packages.
  * @drvdata: Pointer to caller driver data
  *
  * Allocate and register a new WWAN port. The port will be automatically exposed
@@ -84,6 +98,8 @@  struct wwan_port_ops {
 struct wwan_port *wwan_create_port(struct device *parent,
 				   enum wwan_port_type type,
 				   const struct wwan_port_ops *ops,
+				   size_t frag_len,
+				   unsigned int headroom_len,
 				   void *drvdata);
 
 /**