mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 03:10:12 +00:00
mac80211_hwsim: add frame transmission support over virtio
This allows communication with external entities. It also required fixing up the netlink policy, since NLA_UNSPEC attributes are no longer accepted. Signed-off-by: Erel Geron <erelx.geron@intel.com> [port to backports, inline the ID, use 29 as the ID as requested, drop != NULL checks, reduce ifdefs] Link: https://lore.kernel.org/r/20200305143212.c6e4c87d225b.I7ce60bf143e863dcdf0fb8040aab7168ba549b99@changeid Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
parent
1f7e9f46c2
commit
5d44fe7c98
3 changed files with 331 additions and 18 deletions
|
@ -4,7 +4,7 @@
|
|||
* Copyright (c) 2008, Jouni Malinen <j@w1.fi>
|
||||
* Copyright (c) 2011, Javier Lopez <jlopex@gmail.com>
|
||||
* Copyright (c) 2016 - 2017 Intel Deutschland GmbH
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
* Copyright (C) 2018 - 2020 Intel Corporation
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -33,6 +33,9 @@
|
|||
#include <net/netns/generic.h>
|
||||
#include <linux/rhashtable.h>
|
||||
#include <linux/nospec.h>
|
||||
#include <linux/virtio.h>
|
||||
#include <linux/virtio_ids.h>
|
||||
#include <linux/virtio_config.h>
|
||||
#include "mac80211_hwsim.h"
|
||||
|
||||
#define WARN_QUEUE 100
|
||||
|
@ -613,14 +616,14 @@ static const struct genl_multicast_group hwsim_mcgrps[] = {
|
|||
/* MAC80211_HWSIM netlink policy */
|
||||
|
||||
static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = {
|
||||
[HWSIM_ATTR_ADDR_RECEIVER] = { .type = NLA_UNSPEC, .len = ETH_ALEN },
|
||||
[HWSIM_ATTR_ADDR_TRANSMITTER] = { .type = NLA_UNSPEC, .len = ETH_ALEN },
|
||||
[HWSIM_ATTR_ADDR_RECEIVER] = NLA_POLICY_ETH_ADDR_COMPAT,
|
||||
[HWSIM_ATTR_ADDR_TRANSMITTER] = NLA_POLICY_ETH_ADDR_COMPAT,
|
||||
[HWSIM_ATTR_FRAME] = { .type = NLA_BINARY,
|
||||
.len = IEEE80211_MAX_DATA_LEN },
|
||||
[HWSIM_ATTR_FLAGS] = { .type = NLA_U32 },
|
||||
[HWSIM_ATTR_RX_RATE] = { .type = NLA_U32 },
|
||||
[HWSIM_ATTR_SIGNAL] = { .type = NLA_U32 },
|
||||
[HWSIM_ATTR_TX_INFO] = { .type = NLA_UNSPEC,
|
||||
[HWSIM_ATTR_TX_INFO] = { .type = NLA_BINARY,
|
||||
.len = IEEE80211_TX_MAX_RATES *
|
||||
sizeof(struct hwsim_tx_rate)},
|
||||
[HWSIM_ATTR_COOKIE] = { .type = NLA_U64 },
|
||||
|
@ -630,15 +633,61 @@ static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = {
|
|||
[HWSIM_ATTR_REG_CUSTOM_REG] = { .type = NLA_U32 },
|
||||
[HWSIM_ATTR_REG_STRICT_REG] = { .type = NLA_FLAG },
|
||||
[HWSIM_ATTR_SUPPORT_P2P_DEVICE] = { .type = NLA_FLAG },
|
||||
[HWSIM_ATTR_USE_CHANCTX] = { .type = NLA_FLAG },
|
||||
[HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE] = { .type = NLA_FLAG },
|
||||
[HWSIM_ATTR_RADIO_NAME] = { .type = NLA_STRING },
|
||||
[HWSIM_ATTR_NO_VIF] = { .type = NLA_FLAG },
|
||||
[HWSIM_ATTR_FREQ] = { .type = NLA_U32 },
|
||||
[HWSIM_ATTR_PERM_ADDR] = { .type = NLA_UNSPEC, .len = ETH_ALEN },
|
||||
[HWSIM_ATTR_TX_INFO_FLAGS] = { .type = NLA_BINARY },
|
||||
[HWSIM_ATTR_PERM_ADDR] = NLA_POLICY_ETH_ADDR_COMPAT,
|
||||
[HWSIM_ATTR_IFTYPE_SUPPORT] = { .type = NLA_U32 },
|
||||
[HWSIM_ATTR_CIPHER_SUPPORT] = { .type = NLA_BINARY },
|
||||
};
|
||||
|
||||
#if IS_REACHABLE(CONFIG_VIRTIO)
|
||||
|
||||
/* MAC80211_HWSIM virtio queues */
|
||||
static struct virtqueue *hwsim_vqs[HWSIM_NUM_VQS];
|
||||
static bool hwsim_virtio_enabled;
|
||||
static spinlock_t hwsim_virtio_lock;
|
||||
|
||||
static void hwsim_virtio_rx_work(struct work_struct *work);
|
||||
static DECLARE_WORK(hwsim_virtio_rx, hwsim_virtio_rx_work);
|
||||
|
||||
static int hwsim_tx_virtio(struct mac80211_hwsim_data *data,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct scatterlist sg[1];
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
spin_lock_irqsave(&hwsim_virtio_lock, flags);
|
||||
if (!hwsim_virtio_enabled) {
|
||||
err = -ENODEV;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
sg_init_one(sg, skb->head, skb_end_offset(skb));
|
||||
err = virtqueue_add_outbuf(hwsim_vqs[HWSIM_VQ_TX], sg, 1, skb,
|
||||
GFP_ATOMIC);
|
||||
if (err)
|
||||
goto out_free;
|
||||
virtqueue_kick(hwsim_vqs[HWSIM_VQ_TX]);
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
nlmsg_free(skb);
|
||||
return err;
|
||||
}
|
||||
#else
|
||||
/* cause a linker error if this ends up being needed */
|
||||
extern int hwsim_tx_virtio(struct mac80211_hwsim_data *data,
|
||||
struct sk_buff *skb);
|
||||
#define hwsim_virtio_enabled false
|
||||
#endif
|
||||
|
||||
static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
|
||||
struct sk_buff *skb,
|
||||
struct ieee80211_channel *chan);
|
||||
|
@ -1138,8 +1187,14 @@ static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
|
|||
goto nla_put_failure;
|
||||
|
||||
genlmsg_end(skb, msg_head);
|
||||
if (hwsim_unicast_netgroup(data, skb, dst_portid))
|
||||
goto err_free_txskb;
|
||||
|
||||
if (hwsim_virtio_enabled) {
|
||||
if (hwsim_tx_virtio(data, skb))
|
||||
goto err_free_txskb;
|
||||
} else {
|
||||
if (hwsim_unicast_netgroup(data, skb, dst_portid))
|
||||
goto err_free_txskb;
|
||||
}
|
||||
|
||||
/* Enqueue the packet */
|
||||
skb_queue_tail(&data->pending, my_skb);
|
||||
|
@ -1441,7 +1496,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
|
|||
/* wmediumd mode check */
|
||||
_portid = READ_ONCE(data->wmediumd);
|
||||
|
||||
if (_portid)
|
||||
if (_portid || hwsim_virtio_enabled)
|
||||
return mac80211_hwsim_tx_frame_nl(hw, skb, _portid);
|
||||
|
||||
/* NO wmediumd detected, perfect medium simulation */
|
||||
|
@ -1547,7 +1602,7 @@ static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
|
|||
|
||||
mac80211_hwsim_monitor_rx(hw, skb, chan);
|
||||
|
||||
if (_pid)
|
||||
if (_pid || hwsim_virtio_enabled)
|
||||
return mac80211_hwsim_tx_frame_nl(hw, skb, _pid);
|
||||
|
||||
mac80211_hwsim_tx_frame_no_nl(hw, skb, chan);
|
||||
|
@ -3293,11 +3348,14 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2,
|
|||
if (!data2)
|
||||
goto out;
|
||||
|
||||
if (hwsim_net_get_netgroup(genl_info_net(info)) != data2->netgroup)
|
||||
goto out;
|
||||
if (!hwsim_virtio_enabled) {
|
||||
if (hwsim_net_get_netgroup(genl_info_net(info)) !=
|
||||
data2->netgroup)
|
||||
goto out;
|
||||
|
||||
if (info->snd_portid != data2->wmediumd)
|
||||
goto out;
|
||||
if (info->snd_portid != data2->wmediumd)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* look for the skb matching the cookie passed back from user */
|
||||
skb_queue_walk_safe(&data2->pending, skb, tmp) {
|
||||
|
@ -3387,11 +3445,14 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2,
|
|||
if (!data2)
|
||||
goto out;
|
||||
|
||||
if (hwsim_net_get_netgroup(genl_info_net(info)) != data2->netgroup)
|
||||
goto out;
|
||||
if (!hwsim_virtio_enabled) {
|
||||
if (hwsim_net_get_netgroup(genl_info_net(info)) !=
|
||||
data2->netgroup)
|
||||
goto out;
|
||||
|
||||
if (info->snd_portid != data2->wmediumd)
|
||||
goto out;
|
||||
if (info->snd_portid != data2->wmediumd)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* check if radio is configured properly */
|
||||
|
||||
|
@ -3932,6 +3993,229 @@ static void hwsim_exit_netlink(void)
|
|||
genl_unregister_family(&hwsim_genl_family);
|
||||
}
|
||||
|
||||
#if IS_REACHABLE(CONFIG_VIRTIO)
|
||||
static void hwsim_virtio_tx_done(struct virtqueue *vq)
|
||||
{
|
||||
unsigned int len;
|
||||
struct sk_buff *skb;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&hwsim_virtio_lock, flags);
|
||||
while ((skb = virtqueue_get_buf(vq, &len)))
|
||||
nlmsg_free(skb);
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
}
|
||||
|
||||
static int hwsim_virtio_handle_cmd(struct sk_buff *skb)
|
||||
{
|
||||
struct nlmsghdr *nlh;
|
||||
struct genlmsghdr *gnlh;
|
||||
struct nlattr *tb[HWSIM_ATTR_MAX + 1];
|
||||
struct genl_info info = {};
|
||||
int err;
|
||||
|
||||
nlh = nlmsg_hdr(skb);
|
||||
gnlh = nlmsg_data(nlh);
|
||||
err = genlmsg_parse(nlh, &hwsim_genl_family, tb, HWSIM_ATTR_MAX,
|
||||
hwsim_genl_policy, NULL);
|
||||
if (err) {
|
||||
pr_err_ratelimited("hwsim: genlmsg_parse returned %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
info.attrs = tb;
|
||||
|
||||
switch (gnlh->cmd) {
|
||||
case HWSIM_CMD_FRAME:
|
||||
hwsim_cloned_frame_received_nl(skb, &info);
|
||||
break;
|
||||
case HWSIM_CMD_TX_INFO_FRAME:
|
||||
hwsim_tx_info_frame_received_nl(skb, &info);
|
||||
break;
|
||||
default:
|
||||
pr_err_ratelimited("hwsim: invalid cmd: %d\n", gnlh->cmd);
|
||||
return -EPROTO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hwsim_virtio_rx_work(struct work_struct *work)
|
||||
{
|
||||
struct virtqueue *vq;
|
||||
unsigned int len;
|
||||
struct sk_buff *skb;
|
||||
struct scatterlist sg[1];
|
||||
int err;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&hwsim_virtio_lock, flags);
|
||||
if (!hwsim_virtio_enabled)
|
||||
goto out_unlock;
|
||||
|
||||
skb = virtqueue_get_buf(hwsim_vqs[HWSIM_VQ_RX], &len);
|
||||
if (!skb)
|
||||
goto out_unlock;
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
|
||||
skb->data = skb->head;
|
||||
skb_set_tail_pointer(skb, len);
|
||||
hwsim_virtio_handle_cmd(skb);
|
||||
|
||||
spin_lock_irqsave(&hwsim_virtio_lock, flags);
|
||||
if (!hwsim_virtio_enabled) {
|
||||
nlmsg_free(skb);
|
||||
goto out_unlock;
|
||||
}
|
||||
vq = hwsim_vqs[HWSIM_VQ_RX];
|
||||
sg_init_one(sg, skb->head, skb_end_offset(skb));
|
||||
err = virtqueue_add_inbuf(vq, sg, 1, skb, GFP_KERNEL);
|
||||
if (WARN(err, "virtqueue_add_inbuf returned %d\n", err))
|
||||
nlmsg_free(skb);
|
||||
else
|
||||
virtqueue_kick(vq);
|
||||
schedule_work(&hwsim_virtio_rx);
|
||||
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
}
|
||||
|
||||
static void hwsim_virtio_rx_done(struct virtqueue *vq)
|
||||
{
|
||||
schedule_work(&hwsim_virtio_rx);
|
||||
}
|
||||
|
||||
static int init_vqs(struct virtio_device *vdev)
|
||||
{
|
||||
vq_callback_t *callbacks[HWSIM_NUM_VQS] = {
|
||||
[HWSIM_VQ_TX] = hwsim_virtio_tx_done,
|
||||
[HWSIM_VQ_RX] = hwsim_virtio_rx_done,
|
||||
};
|
||||
const char *names[HWSIM_NUM_VQS] = {
|
||||
[HWSIM_VQ_TX] = "tx",
|
||||
[HWSIM_VQ_RX] = "rx",
|
||||
};
|
||||
|
||||
return virtio_find_vqs(vdev, HWSIM_NUM_VQS,
|
||||
hwsim_vqs, callbacks, names, NULL);
|
||||
}
|
||||
|
||||
static int fill_vq(struct virtqueue *vq)
|
||||
{
|
||||
int i, err;
|
||||
struct sk_buff *skb;
|
||||
struct scatterlist sg[1];
|
||||
|
||||
for (i = 0; i < virtqueue_get_vring_size(vq); i++) {
|
||||
skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
|
||||
sg_init_one(sg, skb->head, skb_end_offset(skb));
|
||||
err = virtqueue_add_inbuf(vq, sg, 1, skb, GFP_KERNEL);
|
||||
if (err) {
|
||||
nlmsg_free(skb);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
virtqueue_kick(vq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void remove_vqs(struct virtio_device *vdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
vdev->config->reset(vdev);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(hwsim_vqs); i++) {
|
||||
struct virtqueue *vq = hwsim_vqs[i];
|
||||
struct sk_buff *skb;
|
||||
|
||||
while ((skb = virtqueue_detach_unused_buf(vq)))
|
||||
nlmsg_free(skb);
|
||||
}
|
||||
|
||||
vdev->config->del_vqs(vdev);
|
||||
}
|
||||
|
||||
static int hwsim_virtio_probe(struct virtio_device *vdev)
|
||||
{
|
||||
int err;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&hwsim_virtio_lock, flags);
|
||||
if (hwsim_virtio_enabled) {
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
return -EEXIST;
|
||||
}
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
|
||||
err = init_vqs(vdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = fill_vq(hwsim_vqs[HWSIM_VQ_RX]);
|
||||
if (err)
|
||||
goto out_remove;
|
||||
|
||||
spin_lock_irqsave(&hwsim_virtio_lock, flags);
|
||||
hwsim_virtio_enabled = true;
|
||||
spin_unlock_irqrestore(&hwsim_virtio_lock, flags);
|
||||
|
||||
schedule_work(&hwsim_virtio_rx);
|
||||
return 0;
|
||||
|
||||
out_remove:
|
||||
remove_vqs(vdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void hwsim_virtio_remove(struct virtio_device *vdev)
|
||||
{
|
||||
hwsim_virtio_enabled = false;
|
||||
|
||||
cancel_work_sync(&hwsim_virtio_rx);
|
||||
|
||||
remove_vqs(vdev);
|
||||
}
|
||||
|
||||
/* MAC80211_HWSIM virtio device id table */
|
||||
static const struct virtio_device_id id_table[] = {
|
||||
{ VIRTIO_ID_MAC80211_HWSIM, VIRTIO_DEV_ANY_ID },
|
||||
{ 0 }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(virtio, id_table);
|
||||
|
||||
static struct virtio_driver virtio_hwsim = {
|
||||
.driver.name = KBUILD_MODNAME,
|
||||
.driver.owner = THIS_MODULE,
|
||||
.id_table = id_table,
|
||||
.probe = hwsim_virtio_probe,
|
||||
.remove = hwsim_virtio_remove,
|
||||
};
|
||||
|
||||
static int hwsim_register_virtio_driver(void)
|
||||
{
|
||||
spin_lock_init(&hwsim_virtio_lock);
|
||||
|
||||
return register_virtio_driver(&virtio_hwsim);
|
||||
}
|
||||
|
||||
static void hwsim_unregister_virtio_driver(void)
|
||||
{
|
||||
unregister_virtio_driver(&virtio_hwsim);
|
||||
}
|
||||
#else
|
||||
static inline int hwsim_register_virtio_driver(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void hwsim_unregister_virtio_driver(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __init init_mac80211_hwsim(void)
|
||||
{
|
||||
int i, err;
|
||||
|
@ -3960,10 +4244,14 @@ static int __init init_mac80211_hwsim(void)
|
|||
if (err)
|
||||
goto out_unregister_driver;
|
||||
|
||||
err = hwsim_register_virtio_driver();
|
||||
if (err)
|
||||
goto out_exit_netlink;
|
||||
|
||||
hwsim_class = class_create(THIS_MODULE, "mac80211_hwsim");
|
||||
if (IS_ERR(hwsim_class)) {
|
||||
err = PTR_ERR(hwsim_class);
|
||||
goto out_exit_netlink;
|
||||
goto out_exit_virtio;
|
||||
}
|
||||
|
||||
for (i = 0; i < radios; i++) {
|
||||
|
@ -4075,6 +4363,8 @@ static int __init init_mac80211_hwsim(void)
|
|||
free_netdev(hwsim_mon);
|
||||
out_free_radios:
|
||||
mac80211_hwsim_free();
|
||||
out_exit_virtio:
|
||||
hwsim_unregister_virtio_driver();
|
||||
out_exit_netlink:
|
||||
hwsim_exit_netlink();
|
||||
out_unregister_driver:
|
||||
|
@ -4091,6 +4381,7 @@ static void __exit exit_mac80211_hwsim(void)
|
|||
{
|
||||
pr_debug("mac80211_hwsim: unregister radios\n");
|
||||
|
||||
hwsim_unregister_virtio_driver();
|
||||
hwsim_exit_netlink();
|
||||
|
||||
mac80211_hwsim_free();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* mac80211_hwsim - software simulator of 802.11 radio(s) for mac80211
|
||||
* Copyright (c) 2008, Jouni Malinen <j@w1.fi>
|
||||
* Copyright (c) 2011, Javier Lopez <jlopex@gmail.com>
|
||||
* Copyright (C) 2020 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef __MAC80211_HWSIM_H
|
||||
|
@ -245,4 +246,24 @@ struct hwsim_tx_rate_flag {
|
|||
s8 idx;
|
||||
u16 flags;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* DOC: Frame transmission support over virtio
|
||||
*
|
||||
* Frame transmission is also supported over virtio to allow communication
|
||||
* with external entities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* enum hwsim_vqs - queues for virtio frame transmission
|
||||
*
|
||||
* @HWSIM_VQ_TX: send frames to external entity
|
||||
* @HWSIM_VQ_RX: receive frames and transmission info reports
|
||||
* @HWSIM_NUM_VQS: enum limit
|
||||
*/
|
||||
enum {
|
||||
HWSIM_VQ_TX,
|
||||
HWSIM_VQ_RX,
|
||||
HWSIM_NUM_VQS,
|
||||
};
|
||||
#endif /* __MAC80211_HWSIM_H */
|
||||
|
|
|
@ -46,5 +46,6 @@
|
|||
#define VIRTIO_ID_IOMMU 23 /* virtio IOMMU */
|
||||
#define VIRTIO_ID_FS 26 /* virtio filesystem */
|
||||
#define VIRTIO_ID_PMEM 27 /* virtio pmem */
|
||||
#define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
|
||||
|
||||
#endif /* _LINUX_VIRTIO_IDS_H */
|
||||
|
|
Loading…
Reference in a new issue