nfc: netlink: Add capability to reply to vendor_cmd with data

A proprietary vendor command may send back useful data to the user
application.

For example, the field level applied on the NFC router antenna.

Still based on net/wireless/nl80211.c implementation,
add nfc_vendor_cmd_alloc_reply_skb and nfc_vendor_cmd_reply in
order to send back over netlink data generated by a proprietary
command.

Signed-off-by: Christophe Ricard <christophe-h.ricard@st.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Christophe Ricard 2015-08-19 21:26:43 +02:00 committed by Samuel Ortiz
parent 5a9e0ffc0f
commit 29e76924cf
2 changed files with 125 additions and 2 deletions

View file

@ -203,6 +203,7 @@ struct nfc_dev {
int n_vendor_cmds;
struct nfc_ops *ops;
struct genl_info *cur_cmd_info;
};
#define to_nfc_dev(_dev) container_of(_dev, struct nfc_dev, dev)
@ -318,4 +319,44 @@ static inline int nfc_set_vendor_cmds(struct nfc_dev *dev,
return 0;
}
struct sk_buff *__nfc_alloc_vendor_cmd_reply_skb(struct nfc_dev *dev,
enum nfc_attrs attr,
u32 oui, u32 subcmd,
int approxlen);
int nfc_vendor_cmd_reply(struct sk_buff *skb);
/**
* nfc_vendor_cmd_alloc_reply_skb - allocate vendor command reply
* @dev: nfc device
* @oui: vendor oui
* @approxlen: an upper bound of the length of the data that will
* be put into the skb
*
* This function allocates and pre-fills an skb for a reply to
* a vendor command. Since it is intended for a reply, calling
* it outside of a vendor command's doit() operation is invalid.
*
* The returned skb is pre-filled with some identifying data in
* a way that any data that is put into the skb (with skb_put(),
* nla_put() or similar) will end up being within the
* %NFC_ATTR_VENDOR_DATA attribute, so all that needs to be done
* with the skb is adding data for the corresponding userspace tool
* which can then read that data out of the vendor data attribute.
* You must not modify the skb in any other way.
*
* When done, call nfc_vendor_cmd_reply() with the skb and return
* its error code as the result of the doit() operation.
*
* Return: An allocated and pre-filled skb. %NULL if any errors happen.
*/
static inline struct sk_buff *
nfc_vendor_cmd_alloc_reply_skb(struct nfc_dev *dev,
u32 oui, u32 subcmd, int approxlen)
{
return __nfc_alloc_vendor_cmd_reply_skb(dev,
NFC_ATTR_VENDOR_DATA,
oui,
subcmd, approxlen);
}
#endif /* __NET_NFC_H */

View file

@ -63,6 +63,8 @@ static const struct nla_policy nfc_genl_policy[NFC_ATTR_MAX + 1] = {
[NFC_ATTR_FIRMWARE_NAME] = { .type = NLA_STRING,
.len = NFC_FIRMWARE_NAME_MAXSIZE },
[NFC_ATTR_SE_APDU] = { .type = NLA_BINARY },
[NFC_ATTR_VENDOR_DATA] = { .type = NLA_BINARY },
};
static const struct nla_policy nfc_sdp_genl_policy[NFC_SDP_ATTR_MAX + 1] = {
@ -1503,7 +1505,7 @@ static int nfc_genl_vendor_cmd(struct sk_buff *skb,
u32 dev_idx, vid, subcmd;
u8 *data;
size_t data_len;
int i;
int i, err;
if (!info->attrs[NFC_ATTR_DEVICE_INDEX] ||
!info->attrs[NFC_ATTR_VENDOR_ID] ||
@ -1534,12 +1536,92 @@ static int nfc_genl_vendor_cmd(struct sk_buff *skb,
if (cmd->vendor_id != vid || cmd->subcmd != subcmd)
continue;
return cmd->doit(dev, data, data_len);
dev->cur_cmd_info = info;
err = cmd->doit(dev, data, data_len);
dev->cur_cmd_info = NULL;
return err;
}
return -EOPNOTSUPP;
}
/* message building helper */
static inline void *nfc_hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
int flags, u8 cmd)
{
/* since there is no private header just add the generic one */
return genlmsg_put(skb, portid, seq, &nfc_genl_family, flags, cmd);
}
static struct sk_buff *
__nfc_alloc_vendor_cmd_skb(struct nfc_dev *dev, int approxlen,
u32 portid, u32 seq,
enum nfc_attrs attr,
u32 oui, u32 subcmd, gfp_t gfp)
{
struct sk_buff *skb;
void *hdr;
skb = nlmsg_new(approxlen + 100, gfp);
if (!skb)
return NULL;
hdr = nfc_hdr_put(skb, portid, seq, 0, NFC_CMD_VENDOR);
if (!hdr) {
kfree_skb(skb);
return NULL;
}
if (nla_put_u32(skb, NFC_ATTR_DEVICE_INDEX, dev->idx))
goto nla_put_failure;
if (nla_put_u32(skb, NFC_ATTR_VENDOR_ID, oui))
goto nla_put_failure;
if (nla_put_u32(skb, NFC_ATTR_VENDOR_SUBCMD, subcmd))
goto nla_put_failure;
((void **)skb->cb)[0] = dev;
((void **)skb->cb)[1] = hdr;
return skb;
nla_put_failure:
kfree_skb(skb);
return NULL;
}
struct sk_buff *__nfc_alloc_vendor_cmd_reply_skb(struct nfc_dev *dev,
enum nfc_attrs attr,
u32 oui, u32 subcmd,
int approxlen)
{
if (WARN_ON(!dev->cur_cmd_info))
return NULL;
return __nfc_alloc_vendor_cmd_skb(dev, approxlen,
dev->cur_cmd_info->snd_portid,
dev->cur_cmd_info->snd_seq, attr,
oui, subcmd, GFP_KERNEL);
}
EXPORT_SYMBOL(__nfc_alloc_vendor_cmd_reply_skb);
int nfc_vendor_cmd_reply(struct sk_buff *skb)
{
struct nfc_dev *dev = ((void **)skb->cb)[0];
void *hdr = ((void **)skb->cb)[1];
/* clear CB data for netlink core to own from now on */
memset(skb->cb, 0, sizeof(skb->cb));
if (WARN_ON(!dev->cur_cmd_info)) {
kfree_skb(skb);
return -EINVAL;
}
genlmsg_end(skb, hdr);
return genlmsg_reply(skb, dev->cur_cmd_info);
}
EXPORT_SYMBOL(nfc_vendor_cmd_reply);
static const struct genl_ops nfc_genl_ops[] = {
{
.cmd = NFC_CMD_GET_DEVICE,