diff options
Diffstat (limited to 'drivers/nfc/pn533.c')
| -rw-r--r-- | drivers/nfc/pn533.c | 228 | 
1 files changed, 175 insertions, 53 deletions
diff --git a/drivers/nfc/pn533.c b/drivers/nfc/pn533.c index cb6204f7830..e6ec16d92e6 100644 --- a/drivers/nfc/pn533.c +++ b/drivers/nfc/pn533.c @@ -266,9 +266,13 @@ struct pn533 {  	int in_maxlen;  	struct pn533_frame *in_frame; -	struct tasklet_struct tasklet; -	struct pn533_frame *tklt_in_frame; -	int tklt_in_error; +	struct sk_buff_head resp_q; + +	struct workqueue_struct	*wq; +	struct work_struct cmd_work; +	struct work_struct mi_work; +	struct pn533_frame *wq_in_frame; +	int wq_in_error;  	pn533_cmd_complete_t cmd_complete;  	void *cmd_complete_arg; @@ -383,15 +387,21 @@ static bool pn533_rx_frame_is_cmd_response(struct pn533_frame *frame, u8 cmd)  	return (PN533_FRAME_CMD(frame) == PN533_CMD_RESPONSE(cmd));  } -static void pn533_tasklet_cmd_complete(unsigned long arg) + +static void pn533_wq_cmd_complete(struct work_struct *work)  { -	struct pn533 *dev = (struct pn533 *) arg; -	struct pn533_frame *in_frame = dev->tklt_in_frame; +	struct pn533 *dev = container_of(work, struct pn533, cmd_work); +	struct pn533_frame *in_frame;  	int rc; -	if (dev->tklt_in_error) +	if (dev == NULL) +		return; + +	in_frame = dev->wq_in_frame; + +	if (dev->wq_in_error)  		rc = dev->cmd_complete(dev, dev->cmd_complete_arg, NULL, -							dev->tklt_in_error); +							dev->wq_in_error);  	else  		rc = dev->cmd_complete(dev, dev->cmd_complete_arg,  					PN533_FRAME_CMD_PARAMS_PTR(in_frame), @@ -406,7 +416,7 @@ static void pn533_recv_response(struct urb *urb)  	struct pn533 *dev = urb->context;  	struct pn533_frame *in_frame; -	dev->tklt_in_frame = NULL; +	dev->wq_in_frame = NULL;  	switch (urb->status) {  	case 0: @@ -417,36 +427,36 @@ static void pn533_recv_response(struct urb *urb)  	case -ESHUTDOWN:  		nfc_dev_dbg(&dev->interface->dev, "Urb shutting down with"  						" status: %d", urb->status); -		dev->tklt_in_error = urb->status; -		goto sched_tasklet; +		dev->wq_in_error = urb->status; +		goto sched_wq;  	default:  		nfc_dev_err(&dev->interface->dev, "Nonzero urb status received:"  							" %d", urb->status); -		dev->tklt_in_error = urb->status; -		goto sched_tasklet; +		dev->wq_in_error = urb->status; +		goto sched_wq;  	}  	in_frame = dev->in_urb->transfer_buffer;  	if (!pn533_rx_frame_is_valid(in_frame)) {  		nfc_dev_err(&dev->interface->dev, "Received an invalid frame"); -		dev->tklt_in_error = -EIO; -		goto sched_tasklet; +		dev->wq_in_error = -EIO; +		goto sched_wq;  	}  	if (!pn533_rx_frame_is_cmd_response(in_frame, dev->cmd)) {  		nfc_dev_err(&dev->interface->dev, "The received frame is not "  						"response to the last command"); -		dev->tklt_in_error = -EIO; -		goto sched_tasklet; +		dev->wq_in_error = -EIO; +		goto sched_wq;  	}  	nfc_dev_dbg(&dev->interface->dev, "Received a valid frame"); -	dev->tklt_in_error = 0; -	dev->tklt_in_frame = in_frame; +	dev->wq_in_error = 0; +	dev->wq_in_frame = in_frame; -sched_tasklet: -	tasklet_schedule(&dev->tasklet); +sched_wq: +	queue_work(dev->wq, &dev->cmd_work);  }  static int pn533_submit_urb_for_response(struct pn533 *dev, gfp_t flags) @@ -471,21 +481,21 @@ static void pn533_recv_ack(struct urb *urb)  	case -ESHUTDOWN:  		nfc_dev_dbg(&dev->interface->dev, "Urb shutting down with"  						" status: %d", urb->status); -		dev->tklt_in_error = urb->status; -		goto sched_tasklet; +		dev->wq_in_error = urb->status; +		goto sched_wq;  	default:  		nfc_dev_err(&dev->interface->dev, "Nonzero urb status received:"  							" %d", urb->status); -		dev->tklt_in_error = urb->status; -		goto sched_tasklet; +		dev->wq_in_error = urb->status; +		goto sched_wq;  	}  	in_frame = dev->in_urb->transfer_buffer;  	if (!pn533_rx_frame_is_ack(in_frame)) {  		nfc_dev_err(&dev->interface->dev, "Received an invalid ack"); -		dev->tklt_in_error = -EIO; -		goto sched_tasklet; +		dev->wq_in_error = -EIO; +		goto sched_wq;  	}  	nfc_dev_dbg(&dev->interface->dev, "Received a valid ack"); @@ -494,15 +504,15 @@ static void pn533_recv_ack(struct urb *urb)  	if (rc) {  		nfc_dev_err(&dev->interface->dev, "usb_submit_urb failed with"  							" result %d", rc); -		dev->tklt_in_error = rc; -		goto sched_tasklet; +		dev->wq_in_error = rc; +		goto sched_wq;  	}  	return; -sched_tasklet: -	dev->tklt_in_frame = NULL; -	tasklet_schedule(&dev->tasklet); +sched_wq: +	dev->wq_in_frame = NULL; +	queue_work(dev->wq, &dev->cmd_work);  }  static int pn533_submit_urb_for_ack(struct pn533 *dev, gfp_t flags) @@ -1249,6 +1259,8 @@ static void pn533_deactivate_target(struct nfc_dev *nfc_dev, u32 target_idx)  	dev->tgt_active_prot = 0; +	skb_queue_purge(&dev->resp_q); +  	pn533_tx_frame_init(dev->out_frame, PN533_CMD_IN_RELEASE);  	tg = 1; @@ -1447,11 +1459,49 @@ struct pn533_data_exchange_arg {  	void *cb_context;  }; +static struct sk_buff *pn533_build_response(struct pn533 *dev) +{ +	struct sk_buff *skb, *tmp, *t; +	unsigned int skb_len = 0, tmp_len = 0; + +	nfc_dev_dbg(&dev->interface->dev, "%s\n", __func__); + +	if (skb_queue_empty(&dev->resp_q)) +		return NULL; + +	if (skb_queue_len(&dev->resp_q) == 1) { +		skb = skb_dequeue(&dev->resp_q); +		goto out; +	} + +	skb_queue_walk_safe(&dev->resp_q, tmp, t) +		skb_len += tmp->len; + +	nfc_dev_dbg(&dev->interface->dev, "%s total length %d\n", +		    __func__, skb_len); + +	skb = alloc_skb(skb_len, GFP_KERNEL); +	if (skb == NULL) +		goto out; + +	skb_put(skb, skb_len); + +	skb_queue_walk_safe(&dev->resp_q, tmp, t) { +		memcpy(skb->data + tmp_len, tmp->data, tmp->len); +		tmp_len += tmp->len; +	} + +out: +	skb_queue_purge(&dev->resp_q); + +	return skb; +} +  static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,  						u8 *params, int params_len)  {  	struct pn533_data_exchange_arg *arg = _arg; -	struct sk_buff *skb_resp = arg->skb_resp; +	struct sk_buff *skb = NULL, *skb_resp = arg->skb_resp;  	struct pn533_frame *in_frame = (struct pn533_frame *) skb_resp->data;  	int err = 0;  	u8 status; @@ -1459,15 +1509,13 @@ static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,  	nfc_dev_dbg(&dev->interface->dev, "%s", __func__); -	dev_kfree_skb_irq(arg->skb_out); +	dev_kfree_skb(arg->skb_out);  	if (params_len < 0) { /* error */  		err = params_len;  		goto error;  	} -	skb_put(skb_resp, PN533_FRAME_SIZE(in_frame)); -  	status = params[0];  	cmd_ret = status & PN533_CMD_RET_MASK; @@ -1478,25 +1526,27 @@ static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,  		goto error;  	} +	skb_put(skb_resp, PN533_FRAME_SIZE(in_frame)); +	skb_pull(skb_resp, PN533_CMD_DATAEXCH_HEAD_LEN); +	skb_trim(skb_resp, skb_resp->len - PN533_FRAME_TAIL_SIZE); +	skb_queue_tail(&dev->resp_q, skb_resp); +  	if (status & PN533_CMD_MI_MASK) { -		/* TODO: Implement support to multi-part data exchange */ -		nfc_dev_err(&dev->interface->dev, "Multi-part message not yet" -								" supported"); -		/* Prevent the other messages from controller */ -		pn533_send_ack(dev, GFP_ATOMIC); -		err = -ENOSYS; -		goto error; +		queue_work(dev->wq, &dev->mi_work); +		return -EINPROGRESS;  	} -	skb_pull(skb_resp, PN533_CMD_DATAEXCH_HEAD_LEN); -	skb_trim(skb_resp, skb_resp->len - PN533_FRAME_TAIL_SIZE); +	skb = pn533_build_response(dev); +	if (skb == NULL) +		goto error; -	arg->cb(arg->cb_context, skb_resp, 0); +	arg->cb(arg->cb_context, skb, 0);  	kfree(arg);  	return 0;  error: -	dev_kfree_skb_irq(skb_resp); +	skb_queue_purge(&dev->resp_q); +	dev_kfree_skb(skb_resp);  	arg->cb(arg->cb_context, NULL, err);  	kfree(arg);  	return 0; @@ -1571,6 +1621,68 @@ error:  	return rc;  } +static void pn533_wq_mi_recv(struct work_struct *work) +{ +	struct pn533 *dev = container_of(work, struct pn533, mi_work); +	struct sk_buff *skb_cmd; +	struct pn533_data_exchange_arg *arg = dev->cmd_complete_arg; +	struct pn533_frame *out_frame, *in_frame; +	struct sk_buff *skb_resp; +	int skb_resp_len; +	int rc; + +	nfc_dev_dbg(&dev->interface->dev, "%s", __func__); + +	/* This is a zero payload size skb */ +	skb_cmd = alloc_skb(PN533_CMD_DATAEXCH_HEAD_LEN + PN533_FRAME_TAIL_SIZE, +			    GFP_KERNEL); +	if (skb_cmd == NULL) +		goto error_cmd; + +	skb_reserve(skb_cmd, PN533_CMD_DATAEXCH_HEAD_LEN); + +	rc = pn533_data_exchange_tx_frame(dev, skb_cmd); +	if (rc) +		goto error_frame; + +	skb_resp_len = PN533_CMD_DATAEXCH_HEAD_LEN + +			PN533_CMD_DATAEXCH_DATA_MAXLEN + +			PN533_FRAME_TAIL_SIZE; +	skb_resp = alloc_skb(skb_resp_len, GFP_KERNEL); +	if (!skb_resp) { +		rc = -ENOMEM; +		goto error_frame; +	} + +	in_frame = (struct pn533_frame *) skb_resp->data; +	out_frame = (struct pn533_frame *) skb_cmd->data; + +	arg->skb_resp = skb_resp; +	arg->skb_out = skb_cmd; + +	rc = __pn533_send_cmd_frame_async(dev, out_frame, in_frame, +					  skb_resp_len, +					  pn533_data_exchange_complete, +					  dev->cmd_complete_arg, GFP_KERNEL); +	if (!rc) +		return; + +	nfc_dev_err(&dev->interface->dev, "Error %d when trying to" +						" perform data_exchange", rc); + +	kfree_skb(skb_resp); + +error_frame: +	kfree_skb(skb_cmd); + +error_cmd: +	pn533_send_ack(dev, GFP_KERNEL); + +	kfree(arg); + +	up(&dev->cmd_lock); +} +  static int pn533_set_configuration(struct pn533 *dev, u8 cfgitem, u8 *cfgdata,  								u8 cfgdata_len)  { @@ -1668,7 +1780,15 @@ static int pn533_probe(struct usb_interface *interface,  			NULL, 0,  			pn533_send_complete, dev); -	tasklet_init(&dev->tasklet, pn533_tasklet_cmd_complete, (ulong)dev); +	INIT_WORK(&dev->cmd_work, pn533_wq_cmd_complete); +	INIT_WORK(&dev->mi_work, pn533_wq_mi_recv); +	dev->wq = alloc_workqueue("pn533", +				  WQ_NON_REENTRANT | WQ_UNBOUND | WQ_MEM_RECLAIM, +				  1); +	if (dev->wq == NULL) +		goto error; + +	skb_queue_head_init(&dev->resp_q);  	usb_set_intfdata(interface, dev); @@ -1678,7 +1798,7 @@ static int pn533_probe(struct usb_interface *interface,  	rc = pn533_send_cmd_frame_sync(dev, dev->out_frame, dev->in_frame,  								dev->in_maxlen);  	if (rc) -		goto kill_tasklet; +		goto destroy_wq;  	fw_ver = (struct pn533_fw_version *)  				PN533_FRAME_CMD_PARAMS_PTR(dev->in_frame); @@ -1694,7 +1814,7 @@ static int pn533_probe(struct usb_interface *interface,  					   PN533_CMD_DATAEXCH_HEAD_LEN,  					   PN533_FRAME_TAIL_SIZE);  	if (!dev->nfc_dev) -		goto kill_tasklet; +		goto destroy_wq;  	nfc_set_parent_dev(dev->nfc_dev, &interface->dev);  	nfc_set_drvdata(dev->nfc_dev, dev); @@ -1720,8 +1840,8 @@ static int pn533_probe(struct usb_interface *interface,  free_nfc_dev:  	nfc_free_device(dev->nfc_dev); -kill_tasklet: -	tasklet_kill(&dev->tasklet); +destroy_wq: +	destroy_workqueue(dev->wq);  error:  	kfree(dev->in_frame);  	usb_free_urb(dev->in_urb); @@ -1744,7 +1864,9 @@ static void pn533_disconnect(struct usb_interface *interface)  	usb_kill_urb(dev->in_urb);  	usb_kill_urb(dev->out_urb); -	tasklet_kill(&dev->tasklet); +	destroy_workqueue(dev->wq); + +	skb_queue_purge(&dev->resp_q);  	kfree(dev->in_frame);  	usb_free_urb(dev->in_urb);  |