cregit-Linux how code gets into the kernel

Release 4.10 drivers/nfc/nfcwilink.c

Directory: drivers/nfc
/*
 *  Texas Instrument's NFC Driver For Shared Transport.
 *
 *  NFC Driver acts as interface between NCI core and
 *  TI Shared Transport Layer.
 *
 *  Copyright (C) 2011 Texas Instruments, Inc.
 *
 *  Written by Ilan Elias <ilane@ti.com>
 *
 *  Acknowledgements:
 *  This file is based on btwilink.c, which was written
 *  by Raja Mani and Pavan Savoy.
 *
 *  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.
 *
 *  This program is distributed in the hope that 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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 */
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/firmware.h>
#include <linux/nfc.h>
#include <net/nfc/nci.h>
#include <net/nfc/nci_core.h>
#include <linux/ti_wilink_st.h>


#define NFCWILINK_CHNL			12

#define NFCWILINK_OPCODE		7

#define NFCWILINK_MAX_FRAME_SIZE	300

#define NFCWILINK_HDR_LEN		4

#define NFCWILINK_OFFSET_LEN_IN_HDR	1

#define NFCWILINK_LEN_SIZE		2

#define NFCWILINK_REGISTER_TIMEOUT	8000	
/* 8 sec */

#define NFCWILINK_CMD_TIMEOUT		5000	
/* 5 sec */


#define BTS_FILE_NAME_MAX_SIZE		40

#define BTS_FILE_HDR_MAGIC		0x42535442

#define BTS_FILE_CMD_MAX_LEN		0xff

#define BTS_FILE_ACTION_TYPE_SEND_CMD	1


#define NCI_VS_NFCC_INFO_CMD_GID	0x2f

#define NCI_VS_NFCC_INFO_CMD_OID	0x12

#define NCI_VS_NFCC_INFO_RSP_GID	0x4f

#define NCI_VS_NFCC_INFO_RSP_OID	0x12


struct nfcwilink_hdr {
	
__u8 chnl;
	
__u8 opcode;
	
__le16 len;
} 
__packed;


struct nci_vs_nfcc_info_cmd {
	
__u8 gid;
	
__u8 oid;
	
__u8 plen;
} 
__packed;


struct nci_vs_nfcc_info_rsp {
	
__u8 gid;
	
__u8 oid;
	
__u8 plen;
	
__u8 status;
	
__u8 hw_id;
	
__u8 sw_ver_x;
	
__u8 sw_ver_z;
	
__u8 patch_id;
} 
__packed;


struct bts_file_hdr {
	
__le32 magic;
	
__le32 ver;
	
__u8 rfu[24];
	
__u8 actions[0];
} 
__packed;


struct bts_file_action {
	
__le16 type;
	
__le16 len;
	
__u8 data[0];
} 
__packed;


struct nfcwilink {
	
struct platform_device		*pdev;
	
struct nci_dev			*ndev;
	
unsigned long			flags;

	
int				st_register_cb_status;
	
long				(*st_write) (struct sk_buff *);

	
struct completion		completed;

	
struct nci_vs_nfcc_info_rsp	nfcc_info;
};

/* NFCWILINK driver flags */
enum {
	
NFCWILINK_RUNNING,
	
NFCWILINK_FW_DOWNLOAD,
};

static int nfcwilink_send(struct nci_dev *ndev, struct sk_buff *skb);


static inline struct sk_buff *nfcwilink_skb_alloc(unsigned int len, gfp_t how) { struct sk_buff *skb; skb = alloc_skb(len + NFCWILINK_HDR_LEN, how); if (skb) skb_reserve(skb, NFCWILINK_HDR_LEN); return skb; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias46100.00%1100.00%
Total46100.00%1100.00%


static void nfcwilink_fw_download_receive(struct nfcwilink *drv, struct sk_buff *skb) { struct nci_vs_nfcc_info_rsp *rsp = (void *)skb->data; /* Detect NCI_VS_NFCC_INFO_RSP and store the result */ if ((skb->len > 3) && (rsp->gid == NCI_VS_NFCC_INFO_RSP_GID) && (rsp->oid == NCI_VS_NFCC_INFO_RSP_OID)) { memcpy(&drv->nfcc_info, rsp, sizeof(struct nci_vs_nfcc_info_rsp)); } kfree_skb(skb); complete(&drv->completed); }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias87100.00%1100.00%
Total87100.00%1100.00%


static int nfcwilink_get_bts_file_name(struct nfcwilink *drv, char *file_name) { struct nci_vs_nfcc_info_cmd *cmd; struct sk_buff *skb; unsigned long comp_ret; int rc; skb = nfcwilink_skb_alloc(sizeof(struct nci_vs_nfcc_info_cmd), GFP_KERNEL); if (!skb) { nfc_err(&drv->pdev->dev, "no memory for nci_vs_nfcc_info_cmd\n"); return -ENOMEM; } cmd = (struct nci_vs_nfcc_info_cmd *) skb_put(skb, sizeof(struct nci_vs_nfcc_info_cmd)); cmd->gid = NCI_VS_NFCC_INFO_CMD_GID; cmd->oid = NCI_VS_NFCC_INFO_CMD_OID; cmd->plen = 0; drv->nfcc_info.plen = 0; rc = nfcwilink_send(drv->ndev, skb); if (rc) return rc; comp_ret = wait_for_completion_timeout(&drv->completed, msecs_to_jiffies(NFCWILINK_CMD_TIMEOUT)); dev_dbg(&drv->pdev->dev, "wait_for_completion_timeout returned %ld\n", comp_ret); if (comp_ret == 0) { nfc_err(&drv->pdev->dev, "timeout on wait_for_completion_timeout\n"); return -ETIMEDOUT; } dev_dbg(&drv->pdev->dev, "nci_vs_nfcc_info_rsp: plen %d, status %d\n", drv->nfcc_info.plen, drv->nfcc_info.status); if ((drv->nfcc_info.plen != 5) || (drv->nfcc_info.status != 0)) { nfc_err(&drv->pdev->dev, "invalid nci_vs_nfcc_info_rsp\n"); return -EINVAL; } snprintf(file_name, BTS_FILE_NAME_MAX_SIZE, "TINfcInit_%d.%d.%d.%d.bts", drv->nfcc_info.hw_id, drv->nfcc_info.sw_ver_x, drv->nfcc_info.sw_ver_z, drv->nfcc_info.patch_id); nfc_info(&drv->pdev->dev, "nfcwilink FW file name: %s\n", file_name); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias28194.61%120.00%
joe perchesjoe perches124.04%360.00%
frederic danisfrederic danis41.35%120.00%
Total297100.00%5100.00%


static int nfcwilink_send_bts_cmd(struct nfcwilink *drv, __u8 *data, int len) { struct nfcwilink_hdr *hdr = (struct nfcwilink_hdr *)data; struct sk_buff *skb; unsigned long comp_ret; int rc; /* verify valid cmd for the NFC channel */ if ((len <= sizeof(struct nfcwilink_hdr)) || (len > BTS_FILE_CMD_MAX_LEN) || (hdr->chnl != NFCWILINK_CHNL) || (hdr->opcode != NFCWILINK_OPCODE)) { nfc_err(&drv->pdev->dev, "ignoring invalid bts cmd, len %d, chnl %d, opcode %d\n", len, hdr->chnl, hdr->opcode); return 0; } /* remove the ST header */ len -= sizeof(struct nfcwilink_hdr); data += sizeof(struct nfcwilink_hdr); skb = nfcwilink_skb_alloc(len, GFP_KERNEL); if (!skb) { nfc_err(&drv->pdev->dev, "no memory for bts cmd\n"); return -ENOMEM; } memcpy(skb_put(skb, len), data, len); rc = nfcwilink_send(drv->ndev, skb); if (rc) return rc; comp_ret = wait_for_completion_timeout(&drv->completed, msecs_to_jiffies(NFCWILINK_CMD_TIMEOUT)); dev_dbg(&drv->pdev->dev, "wait_for_completion_timeout returned %ld\n", comp_ret); if (comp_ret == 0) { nfc_err(&drv->pdev->dev, "timeout on wait_for_completion_timeout\n"); return -ETIMEDOUT; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias22995.02%125.00%
joe perchesjoe perches83.32%250.00%
frederic danisfrederic danis41.66%125.00%
Total241100.00%4100.00%


static int nfcwilink_download_fw(struct nfcwilink *drv) { unsigned char file_name[BTS_FILE_NAME_MAX_SIZE]; const struct firmware *fw; __u16 action_type, action_len; __u8 *ptr; int len, rc; set_bit(NFCWILINK_FW_DOWNLOAD, &drv->flags); rc = nfcwilink_get_bts_file_name(drv, file_name); if (rc) goto exit; rc = request_firmware(&fw, file_name, &drv->pdev->dev); if (rc) { nfc_err(&drv->pdev->dev, "request_firmware failed %d\n", rc); /* if the file is not found, don't exit with failure */ if (rc == -ENOENT) rc = 0; goto exit; } len = fw->size; ptr = (__u8 *)fw->data; if ((len == 0) || (ptr == NULL)) { dev_dbg(&drv->pdev->dev, "request_firmware returned size %d\n", len); goto release_fw; } if (__le32_to_cpu(((struct bts_file_hdr *)ptr)->magic) != BTS_FILE_HDR_MAGIC) { nfc_err(&drv->pdev->dev, "wrong bts magic number\n"); rc = -EINVAL; goto release_fw; } /* remove the BTS header */ len -= sizeof(struct bts_file_hdr); ptr += sizeof(struct bts_file_hdr); while (len > 0) { action_type = __le16_to_cpu(((struct bts_file_action *)ptr)->type); action_len = __le16_to_cpu(((struct bts_file_action *)ptr)->len); dev_dbg(&drv->pdev->dev, "bts_file_action type %d, len %d\n", action_type, action_len); switch (action_type) { case BTS_FILE_ACTION_TYPE_SEND_CMD: rc = nfcwilink_send_bts_cmd(drv, ((struct bts_file_action *)ptr)->data, action_len); if (rc) goto release_fw; break; } /* advance to the next action */ len -= (sizeof(struct bts_file_action) + action_len); ptr += (sizeof(struct bts_file_action) + action_len); } release_fw: release_firmware(fw); exit: clear_bit(NFCWILINK_FW_DOWNLOAD, &drv->flags); return rc; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias35497.79%250.00%
joe perchesjoe perches82.21%250.00%
Total362100.00%4100.00%

/* Called by ST when registration is complete */
static void nfcwilink_register_complete(void *priv_data, int data) { struct nfcwilink *drv = priv_data; /* store ST registration status */ drv->st_register_cb_status = data; /* complete the wait in nfc_st_open() */ complete(&drv->completed); }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias3597.22%266.67%
geert uytterhoevengeert uytterhoeven12.78%133.33%
Total36100.00%3100.00%

/* Called by ST when receive data is available */
static long nfcwilink_receive(void *priv_data, struct sk_buff *skb) { struct nfcwilink *drv = priv_data; int rc; if (!skb) return -EFAULT; if (!drv) { kfree_skb(skb); return -EFAULT; } dev_dbg(&drv->pdev->dev, "receive entry, len %d\n", skb->len); /* strip the ST header (apart for the chnl byte, which is not received in the hdr) */ skb_pull(skb, (NFCWILINK_HDR_LEN-1)); if (test_bit(NFCWILINK_FW_DOWNLOAD, &drv->flags)) { nfcwilink_fw_download_receive(drv, skb); return 0; } /* Forward skb to NCI core layer */ rc = nci_recv_frame(drv->ndev, skb); if (rc < 0) { nfc_err(&drv->pdev->dev, "nci_recv_frame failed %d\n", rc); return rc; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias12084.51%233.33%
wei yongjunwei yongjun149.86%116.67%
frederic danisfrederic danis42.82%116.67%
joe perchesjoe perches42.82%233.33%
Total142100.00%6100.00%

/* protocol structure registered with ST */ static struct st_proto_s nfcwilink_proto = { .chnl_id = NFCWILINK_CHNL, .max_frame_size = NFCWILINK_MAX_FRAME_SIZE, .hdr_len = (NFCWILINK_HDR_LEN-1), /* not including chnl byte */ .offset_len_in_hdr = NFCWILINK_OFFSET_LEN_IN_HDR, .len_size = NFCWILINK_LEN_SIZE, .reserve = 0, .recv = nfcwilink_receive, .reg_complete_cb = nfcwilink_register_complete, .write = NULL, };
static int nfcwilink_open(struct nci_dev *ndev) { struct nfcwilink *drv = nci_get_drvdata(ndev); unsigned long comp_ret; int rc; if (test_and_set_bit(NFCWILINK_RUNNING, &drv->flags)) { rc = -EBUSY; goto exit; } nfcwilink_proto.priv_data = drv; init_completion(&drv->completed); drv->st_register_cb_status = -EINPROGRESS; rc = st_register(&nfcwilink_proto); if (rc < 0) { if (rc == -EINPROGRESS) { comp_ret = wait_for_completion_timeout( &drv->completed, msecs_to_jiffies(NFCWILINK_REGISTER_TIMEOUT)); dev_dbg(&drv->pdev->dev, "wait_for_completion_timeout returned %ld\n", comp_ret); if (comp_ret == 0) { /* timeout */ rc = -ETIMEDOUT; goto clear_exit; } else if (drv->st_register_cb_status != 0) { rc = drv->st_register_cb_status; nfc_err(&drv->pdev->dev, "st_register_cb failed %d\n", rc); goto clear_exit; } } else { nfc_err(&drv->pdev->dev, "st_register failed %d\n", rc); goto clear_exit; } } /* st_register MUST fill the write callback */ BUG_ON(nfcwilink_proto.write == NULL); drv->st_write = nfcwilink_proto.write; if (nfcwilink_download_fw(drv)) { nfc_err(&drv->pdev->dev, "nfcwilink_download_fw failed %d\n", rc); /* open should succeed, even if the FW download failed */ } goto exit; clear_exit: clear_bit(NFCWILINK_RUNNING, &drv->flags); exit: return rc; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias25096.90%250.00%
joe perchesjoe perches83.10%250.00%
Total258100.00%4100.00%


static int nfcwilink_close(struct nci_dev *ndev) { struct nfcwilink *drv = nci_get_drvdata(ndev); int rc; if (!test_and_clear_bit(NFCWILINK_RUNNING, &drv->flags)) return 0; rc = st_unregister(&nfcwilink_proto); if (rc) nfc_err(&drv->pdev->dev, "st_unregister failed %d\n", rc); drv->st_write = NULL; return rc; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias7397.33%150.00%
joe perchesjoe perches22.67%150.00%
Total75100.00%2100.00%


static int nfcwilink_send(struct nci_dev *ndev, struct sk_buff *skb) { struct nfcwilink *drv = nci_get_drvdata(ndev); struct nfcwilink_hdr hdr = {NFCWILINK_CHNL, NFCWILINK_OPCODE, 0x0000}; long len; dev_dbg(&drv->pdev->dev, "send entry, len %d\n", skb->len); if (!test_bit(NFCWILINK_RUNNING, &drv->flags)) { kfree_skb(skb); return -EINVAL; } /* add the ST hdr to the start of the buffer */ hdr.len = cpu_to_le16(skb->len); memcpy(skb_push(skb, NFCWILINK_HDR_LEN), &hdr, NFCWILINK_HDR_LEN); /* Insert skb to shared transport layer's transmit queue. * Freeing skb memory is taken care in shared transport layer, * so don't free skb memory here. */ len = drv->st_write(skb); if (len < 0) { kfree_skb(skb); nfc_err(&drv->pdev->dev, "st_write failed %ld\n", len); return -EFAULT; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias14394.08%350.00%
frederic danisfrederic danis53.29%116.67%
joe perchesjoe perches42.63%233.33%
Total152100.00%6100.00%

static struct nci_ops nfcwilink_ops = { .open = nfcwilink_open, .close = nfcwilink_close, .send = nfcwilink_send, };
static int nfcwilink_probe(struct platform_device *pdev) { struct nfcwilink *drv; int rc; __u32 protocols; drv = devm_kzalloc(&pdev->dev, sizeof(struct nfcwilink), GFP_KERNEL); if (!drv) { rc = -ENOMEM; goto exit; } drv->pdev = pdev; protocols = NFC_PROTO_JEWEL_MASK | NFC_PROTO_MIFARE_MASK | NFC_PROTO_FELICA_MASK | NFC_PROTO_ISO14443_MASK | NFC_PROTO_ISO14443_B_MASK | NFC_PROTO_NFC_DEP_MASK; drv->ndev = nci_allocate_device(&nfcwilink_ops, protocols, NFCWILINK_HDR_LEN, 0); if (!drv->ndev) { nfc_err(&pdev->dev, "nci_allocate_device failed\n"); rc = -ENOMEM; goto exit; } nci_set_parent_dev(drv->ndev, &pdev->dev); nci_set_drvdata(drv->ndev, drv); rc = nci_register_device(drv->ndev); if (rc < 0) { nfc_err(&pdev->dev, "nci_register_device failed %d\n", rc); goto free_dev_exit; } dev_set_drvdata(&pdev->dev, drv); goto exit; free_dev_exit: nci_free_device(drv->ndev); exit: return rc; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias18593.43%240.00%
julia lawalljulia lawall73.54%120.00%
joe perchesjoe perches42.02%120.00%
samuel ortizsamuel ortiz21.01%120.00%
Total198100.00%5100.00%


static int nfcwilink_remove(struct platform_device *pdev) { struct nfcwilink *drv = dev_get_drvdata(&pdev->dev); struct nci_dev *ndev; if (!drv) return -EFAULT; ndev = drv->ndev; nci_unregister_device(ndev); nci_free_device(ndev); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias57100.00%1100.00%
Total57100.00%1100.00%

static struct platform_driver nfcwilink_driver = { .probe = nfcwilink_probe, .remove = nfcwilink_remove, .driver = { .name = "nfcwilink", }, }; module_platform_driver(nfcwilink_driver); /* ------ Module Info ------ */ MODULE_AUTHOR("Ilan Elias <ilane@ti.com>"); MODULE_DESCRIPTION("NFC Driver for TI Shared Transport"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
ilan eliasilan elias223595.59%426.67%
joe perchesjoe perches502.14%320.00%
frederic danisfrederic danis220.94%16.67%
wei yongjunwei yongjun140.60%16.67%
julia lawalljulia lawall70.30%16.67%
paul gortmakerpaul gortmaker30.13%16.67%
geert uytterhoevengeert uytterhoeven20.09%16.67%
samuel ortizsamuel ortiz20.09%16.67%
syam sidhardhansyam sidhardhan20.09%16.67%
jeff kirsherjeff kirsher10.04%16.67%
Total2338100.00%15100.00%
Directory: drivers/nfc
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.