| /* | 
 |  * NFC hardware simulation driver | 
 |  * Copyright (c) 2013, Intel Corporation. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify it | 
 |  * under the terms and conditions of the GNU General Public License, | 
 |  * version 2, as published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope it will be useful, but WITHOUT | 
 |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
 |  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
 |  * more details. | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/device.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/ctype.h> | 
 | #include <linux/debugfs.h> | 
 | #include <linux/nfc.h> | 
 | #include <net/nfc/nfc.h> | 
 | #include <net/nfc/digital.h> | 
 |  | 
 | #define NFCSIM_ERR(d, fmt, args...) nfc_err(&d->nfc_digital_dev->nfc_dev->dev, \ | 
 | 					    "%s: " fmt, __func__, ## args) | 
 |  | 
 | #define NFCSIM_DBG(d, fmt, args...) dev_dbg(&d->nfc_digital_dev->nfc_dev->dev, \ | 
 | 					    "%s: " fmt, __func__, ## args) | 
 |  | 
 | #define NFCSIM_VERSION "0.2" | 
 |  | 
 | #define NFCSIM_MODE_NONE	0 | 
 | #define NFCSIM_MODE_INITIATOR	1 | 
 | #define NFCSIM_MODE_TARGET	2 | 
 |  | 
 | #define NFCSIM_CAPABILITIES (NFC_DIGITAL_DRV_CAPS_IN_CRC   | \ | 
 | 			     NFC_DIGITAL_DRV_CAPS_TG_CRC) | 
 |  | 
 | struct nfcsim { | 
 | 	struct nfc_digital_dev *nfc_digital_dev; | 
 |  | 
 | 	struct work_struct recv_work; | 
 | 	struct delayed_work send_work; | 
 |  | 
 | 	struct nfcsim_link *link_in; | 
 | 	struct nfcsim_link *link_out; | 
 |  | 
 | 	bool up; | 
 | 	u8 mode; | 
 | 	u8 rf_tech; | 
 |  | 
 | 	u16 recv_timeout; | 
 |  | 
 | 	nfc_digital_cmd_complete_t cb; | 
 | 	void *arg; | 
 |  | 
 | 	u8 dropframe; | 
 | }; | 
 |  | 
 | struct nfcsim_link { | 
 | 	struct mutex lock; | 
 |  | 
 | 	u8 rf_tech; | 
 | 	u8 mode; | 
 |  | 
 | 	u8 shutdown; | 
 |  | 
 | 	struct sk_buff *skb; | 
 | 	wait_queue_head_t recv_wait; | 
 | 	u8 cond; | 
 | }; | 
 |  | 
 | static struct nfcsim_link *nfcsim_link_new(void) | 
 | { | 
 | 	struct nfcsim_link *link; | 
 |  | 
 | 	link = kzalloc(sizeof(struct nfcsim_link), GFP_KERNEL); | 
 | 	if (!link) | 
 | 		return NULL; | 
 |  | 
 | 	mutex_init(&link->lock); | 
 | 	init_waitqueue_head(&link->recv_wait); | 
 |  | 
 | 	return link; | 
 | } | 
 |  | 
 | static void nfcsim_link_free(struct nfcsim_link *link) | 
 | { | 
 | 	dev_kfree_skb(link->skb); | 
 | 	kfree(link); | 
 | } | 
 |  | 
 | static void nfcsim_link_recv_wake(struct nfcsim_link *link) | 
 | { | 
 | 	link->cond = 1; | 
 | 	wake_up_interruptible(&link->recv_wait); | 
 | } | 
 |  | 
 | static void nfcsim_link_set_skb(struct nfcsim_link *link, struct sk_buff *skb, | 
 | 				u8 rf_tech, u8 mode) | 
 | { | 
 | 	mutex_lock(&link->lock); | 
 |  | 
 | 	dev_kfree_skb(link->skb); | 
 | 	link->skb = skb; | 
 | 	link->rf_tech = rf_tech; | 
 | 	link->mode = mode; | 
 |  | 
 | 	mutex_unlock(&link->lock); | 
 | } | 
 |  | 
 | static void nfcsim_link_recv_cancel(struct nfcsim_link *link) | 
 | { | 
 | 	mutex_lock(&link->lock); | 
 |  | 
 | 	link->mode = NFCSIM_MODE_NONE; | 
 |  | 
 | 	mutex_unlock(&link->lock); | 
 |  | 
 | 	nfcsim_link_recv_wake(link); | 
 | } | 
 |  | 
 | static void nfcsim_link_shutdown(struct nfcsim_link *link) | 
 | { | 
 | 	mutex_lock(&link->lock); | 
 |  | 
 | 	link->shutdown = 1; | 
 | 	link->mode = NFCSIM_MODE_NONE; | 
 |  | 
 | 	mutex_unlock(&link->lock); | 
 |  | 
 | 	nfcsim_link_recv_wake(link); | 
 | } | 
 |  | 
 | static struct sk_buff *nfcsim_link_recv_skb(struct nfcsim_link *link, | 
 | 					    int timeout, u8 rf_tech, u8 mode) | 
 | { | 
 | 	int rc; | 
 | 	struct sk_buff *skb; | 
 |  | 
 | 	rc = wait_event_interruptible_timeout(link->recv_wait, | 
 | 					      link->cond, | 
 | 					      msecs_to_jiffies(timeout)); | 
 |  | 
 | 	mutex_lock(&link->lock); | 
 |  | 
 | 	skb = link->skb; | 
 | 	link->skb = NULL; | 
 |  | 
 | 	if (!rc) { | 
 | 		rc = -ETIMEDOUT; | 
 | 		goto done; | 
 | 	} | 
 |  | 
 | 	if (!skb || link->rf_tech != rf_tech || link->mode == mode) { | 
 | 		rc = -EINVAL; | 
 | 		goto done; | 
 | 	} | 
 |  | 
 | 	if (link->shutdown) { | 
 | 		rc = -ENODEV; | 
 | 		goto done; | 
 | 	} | 
 |  | 
 | done: | 
 | 	mutex_unlock(&link->lock); | 
 |  | 
 | 	if (rc < 0) { | 
 | 		dev_kfree_skb(skb); | 
 | 		skb = ERR_PTR(rc); | 
 | 	} | 
 |  | 
 | 	link->cond = 0; | 
 |  | 
 | 	return skb; | 
 | } | 
 |  | 
 | static void nfcsim_send_wq(struct work_struct *work) | 
 | { | 
 | 	struct nfcsim *dev = container_of(work, struct nfcsim, send_work.work); | 
 |  | 
 | 	/* | 
 | 	 * To effectively send data, the device just wake up its link_out which | 
 | 	 * is the link_in of the peer device. The exchanged skb has already been | 
 | 	 * stored in the dev->link_out through nfcsim_link_set_skb(). | 
 | 	 */ | 
 | 	nfcsim_link_recv_wake(dev->link_out); | 
 | } | 
 |  | 
 | static void nfcsim_recv_wq(struct work_struct *work) | 
 | { | 
 | 	struct nfcsim *dev = container_of(work, struct nfcsim, recv_work); | 
 | 	struct sk_buff *skb; | 
 |  | 
 | 	skb = nfcsim_link_recv_skb(dev->link_in, dev->recv_timeout, | 
 | 				   dev->rf_tech, dev->mode); | 
 |  | 
 | 	if (!dev->up) { | 
 | 		NFCSIM_ERR(dev, "Device is down\n"); | 
 |  | 
 | 		if (!IS_ERR(skb)) | 
 | 			dev_kfree_skb(skb); | 
 |  | 
 | 		skb = ERR_PTR(-ENODEV); | 
 | 	} | 
 |  | 
 | 	dev->cb(dev->nfc_digital_dev, dev->arg, skb); | 
 | } | 
 |  | 
 | static int nfcsim_send(struct nfc_digital_dev *ddev, struct sk_buff *skb, | 
 | 		       u16 timeout, nfc_digital_cmd_complete_t cb, void *arg) | 
 | { | 
 | 	struct nfcsim *dev = nfc_digital_get_drvdata(ddev); | 
 | 	u8 delay; | 
 |  | 
 | 	if (!dev->up) { | 
 | 		NFCSIM_ERR(dev, "Device is down\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	dev->recv_timeout = timeout; | 
 | 	dev->cb = cb; | 
 | 	dev->arg = arg; | 
 |  | 
 | 	schedule_work(&dev->recv_work); | 
 |  | 
 | 	if (dev->dropframe) { | 
 | 		NFCSIM_DBG(dev, "dropping frame (out of %d)\n", dev->dropframe); | 
 | 		dev_kfree_skb(skb); | 
 | 		dev->dropframe--; | 
 |  | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if (skb) { | 
 | 		nfcsim_link_set_skb(dev->link_out, skb, dev->rf_tech, | 
 | 				    dev->mode); | 
 |  | 
 | 		/* Add random delay (between 3 and 10 ms) before sending data */ | 
 | 		get_random_bytes(&delay, 1); | 
 | 		delay = 3 + (delay & 0x07); | 
 |  | 
 | 		schedule_delayed_work(&dev->send_work, msecs_to_jiffies(delay)); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void nfcsim_abort_cmd(struct nfc_digital_dev *ddev) | 
 | { | 
 | 	struct nfcsim *dev = nfc_digital_get_drvdata(ddev); | 
 |  | 
 | 	nfcsim_link_recv_cancel(dev->link_in); | 
 | } | 
 |  | 
 | static int nfcsim_switch_rf(struct nfc_digital_dev *ddev, bool on) | 
 | { | 
 | 	struct nfcsim *dev = nfc_digital_get_drvdata(ddev); | 
 |  | 
 | 	dev->up = on; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int nfcsim_in_configure_hw(struct nfc_digital_dev *ddev, | 
 | 					  int type, int param) | 
 | { | 
 | 	struct nfcsim *dev = nfc_digital_get_drvdata(ddev); | 
 |  | 
 | 	switch (type) { | 
 | 	case NFC_DIGITAL_CONFIG_RF_TECH: | 
 | 		dev->up = true; | 
 | 		dev->mode = NFCSIM_MODE_INITIATOR; | 
 | 		dev->rf_tech = param; | 
 | 		break; | 
 |  | 
 | 	case NFC_DIGITAL_CONFIG_FRAMING: | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NFCSIM_ERR(dev, "Invalid configuration type: %d\n", type); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int nfcsim_in_send_cmd(struct nfc_digital_dev *ddev, | 
 | 			       struct sk_buff *skb, u16 timeout, | 
 | 			       nfc_digital_cmd_complete_t cb, void *arg) | 
 | { | 
 | 	return nfcsim_send(ddev, skb, timeout, cb, arg); | 
 | } | 
 |  | 
 | static int nfcsim_tg_configure_hw(struct nfc_digital_dev *ddev, | 
 | 					  int type, int param) | 
 | { | 
 | 	struct nfcsim *dev = nfc_digital_get_drvdata(ddev); | 
 |  | 
 | 	switch (type) { | 
 | 	case NFC_DIGITAL_CONFIG_RF_TECH: | 
 | 		dev->up = true; | 
 | 		dev->mode = NFCSIM_MODE_TARGET; | 
 | 		dev->rf_tech = param; | 
 | 		break; | 
 |  | 
 | 	case NFC_DIGITAL_CONFIG_FRAMING: | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		NFCSIM_ERR(dev, "Invalid configuration type: %d\n", type); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int nfcsim_tg_send_cmd(struct nfc_digital_dev *ddev, | 
 | 			       struct sk_buff *skb, u16 timeout, | 
 | 			       nfc_digital_cmd_complete_t cb, void *arg) | 
 | { | 
 | 	return nfcsim_send(ddev, skb, timeout, cb, arg); | 
 | } | 
 |  | 
 | static int nfcsim_tg_listen(struct nfc_digital_dev *ddev, u16 timeout, | 
 | 			    nfc_digital_cmd_complete_t cb, void *arg) | 
 | { | 
 | 	return nfcsim_send(ddev, NULL, timeout, cb, arg); | 
 | } | 
 |  | 
 | static struct nfc_digital_ops nfcsim_digital_ops = { | 
 | 	.in_configure_hw = nfcsim_in_configure_hw, | 
 | 	.in_send_cmd = nfcsim_in_send_cmd, | 
 |  | 
 | 	.tg_listen = nfcsim_tg_listen, | 
 | 	.tg_configure_hw = nfcsim_tg_configure_hw, | 
 | 	.tg_send_cmd = nfcsim_tg_send_cmd, | 
 |  | 
 | 	.abort_cmd = nfcsim_abort_cmd, | 
 | 	.switch_rf = nfcsim_switch_rf, | 
 | }; | 
 |  | 
 | static struct dentry *nfcsim_debugfs_root; | 
 |  | 
 | static void nfcsim_debugfs_init(void) | 
 | { | 
 | 	nfcsim_debugfs_root = debugfs_create_dir("nfcsim", NULL); | 
 |  | 
 | 	if (!nfcsim_debugfs_root) | 
 | 		pr_err("Could not create debugfs entry\n"); | 
 |  | 
 | } | 
 |  | 
 | static void nfcsim_debugfs_remove(void) | 
 | { | 
 | 	debugfs_remove_recursive(nfcsim_debugfs_root); | 
 | } | 
 |  | 
 | static void nfcsim_debugfs_init_dev(struct nfcsim *dev) | 
 | { | 
 | 	struct dentry *dev_dir; | 
 | 	char devname[5]; /* nfcX\0 */ | 
 | 	u32 idx; | 
 | 	int n; | 
 |  | 
 | 	if (!nfcsim_debugfs_root) { | 
 | 		NFCSIM_ERR(dev, "nfcsim debugfs not initialized\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	idx = dev->nfc_digital_dev->nfc_dev->idx; | 
 | 	n = snprintf(devname, sizeof(devname), "nfc%d", idx); | 
 | 	if (n >= sizeof(devname)) { | 
 | 		NFCSIM_ERR(dev, "Could not compute dev name for dev %d\n", idx); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	dev_dir = debugfs_create_dir(devname, nfcsim_debugfs_root); | 
 | 	if (!dev_dir) { | 
 | 		NFCSIM_ERR(dev, "Could not create debugfs entries for nfc%d\n", | 
 | 			   idx); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	debugfs_create_u8("dropframe", 0664, dev_dir, &dev->dropframe); | 
 | } | 
 |  | 
 | static struct nfcsim *nfcsim_device_new(struct nfcsim_link *link_in, | 
 | 					struct nfcsim_link *link_out) | 
 | { | 
 | 	struct nfcsim *dev; | 
 | 	int rc; | 
 |  | 
 | 	dev = kzalloc(sizeof(struct nfcsim), GFP_KERNEL); | 
 | 	if (!dev) | 
 | 		return ERR_PTR(-ENOMEM); | 
 |  | 
 | 	INIT_DELAYED_WORK(&dev->send_work, nfcsim_send_wq); | 
 | 	INIT_WORK(&dev->recv_work, nfcsim_recv_wq); | 
 |  | 
 | 	dev->nfc_digital_dev = | 
 | 			nfc_digital_allocate_device(&nfcsim_digital_ops, | 
 | 						    NFC_PROTO_NFC_DEP_MASK, | 
 | 						    NFCSIM_CAPABILITIES, | 
 | 						    0, 0); | 
 | 	if (!dev->nfc_digital_dev) { | 
 | 		kfree(dev); | 
 | 		return ERR_PTR(-ENOMEM); | 
 | 	} | 
 |  | 
 | 	nfc_digital_set_drvdata(dev->nfc_digital_dev, dev); | 
 |  | 
 | 	dev->link_in = link_in; | 
 | 	dev->link_out = link_out; | 
 |  | 
 | 	rc = nfc_digital_register_device(dev->nfc_digital_dev); | 
 | 	if (rc) { | 
 | 		pr_err("Could not register digital device (%d)\n", rc); | 
 | 		nfc_digital_free_device(dev->nfc_digital_dev); | 
 | 		kfree(dev); | 
 |  | 
 | 		return ERR_PTR(rc); | 
 | 	} | 
 |  | 
 | 	nfcsim_debugfs_init_dev(dev); | 
 |  | 
 | 	return dev; | 
 | } | 
 |  | 
 | static void nfcsim_device_free(struct nfcsim *dev) | 
 | { | 
 | 	nfc_digital_unregister_device(dev->nfc_digital_dev); | 
 |  | 
 | 	dev->up = false; | 
 |  | 
 | 	nfcsim_link_shutdown(dev->link_in); | 
 |  | 
 | 	cancel_delayed_work_sync(&dev->send_work); | 
 | 	cancel_work_sync(&dev->recv_work); | 
 |  | 
 | 	nfc_digital_free_device(dev->nfc_digital_dev); | 
 |  | 
 | 	kfree(dev); | 
 | } | 
 |  | 
 | static struct nfcsim *dev0; | 
 | static struct nfcsim *dev1; | 
 |  | 
 | static int __init nfcsim_init(void) | 
 | { | 
 | 	struct nfcsim_link *link0, *link1; | 
 | 	int rc; | 
 |  | 
 | 	link0 = nfcsim_link_new(); | 
 | 	link1 = nfcsim_link_new(); | 
 | 	if (!link0 || !link1) { | 
 | 		rc = -ENOMEM; | 
 | 		goto exit_err; | 
 | 	} | 
 |  | 
 | 	nfcsim_debugfs_init(); | 
 |  | 
 | 	dev0 = nfcsim_device_new(link0, link1); | 
 | 	if (IS_ERR(dev0)) { | 
 | 		rc = PTR_ERR(dev0); | 
 | 		goto exit_err; | 
 | 	} | 
 |  | 
 | 	dev1 = nfcsim_device_new(link1, link0); | 
 | 	if (IS_ERR(dev1)) { | 
 | 		nfcsim_device_free(dev0); | 
 |  | 
 | 		rc = PTR_ERR(dev1); | 
 | 		goto exit_err; | 
 | 	} | 
 |  | 
 | 	pr_info("nfcsim " NFCSIM_VERSION " initialized\n"); | 
 |  | 
 | 	return 0; | 
 |  | 
 | exit_err: | 
 | 	pr_err("Failed to initialize nfcsim driver (%d)\n", rc); | 
 |  | 
 | 	if (link0) | 
 | 		nfcsim_link_free(link0); | 
 | 	if (link1) | 
 | 		nfcsim_link_free(link1); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static void __exit nfcsim_exit(void) | 
 | { | 
 | 	struct nfcsim_link *link0, *link1; | 
 |  | 
 | 	link0 = dev0->link_in; | 
 | 	link1 = dev0->link_out; | 
 |  | 
 | 	nfcsim_device_free(dev0); | 
 | 	nfcsim_device_free(dev1); | 
 |  | 
 | 	nfcsim_link_free(link0); | 
 | 	nfcsim_link_free(link1); | 
 |  | 
 | 	nfcsim_debugfs_remove(); | 
 | } | 
 |  | 
 | module_init(nfcsim_init); | 
 | module_exit(nfcsim_exit); | 
 |  | 
 | MODULE_DESCRIPTION("NFCSim driver ver " NFCSIM_VERSION); | 
 | MODULE_VERSION(NFCSIM_VERSION); | 
 | MODULE_LICENSE("GPL"); |