On 04/02/2018 06:42 AM, [email protected] wrote:
> From: Xiubo Li <[email protected]>
>
> This patch adds 1 tcmu attr to reset and complete all the blocked
> netlink waiting threads. It's used when the userspace daemon like
> tcmu-runner has crashed or forced to shutdown just before the
> netlink requests be replied to the kernel, then the netlink requeting
> threads will get stuck forever. We must reboot the machine to recover
> from it and by this the rebootng is not a must then.
>
> The netlink reset operation should be done before the userspace daemon
> could receive and handle the netlink requests to be safe.
>
> Signed-off-by: Xiubo Li <[email protected]>
> ---
> drivers/target/target_core_user.c | 99
> ++++++++++++++++++++++++++++++++++++---
> 1 file changed, 93 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/target/target_core_user.c
> b/drivers/target/target_core_user.c
> index 4ad89ea..dc8879d 100644
> --- a/drivers/target/target_core_user.c
> +++ b/drivers/target/target_core_user.c
> @@ -103,9 +103,13 @@ struct tcmu_hba {
>
> #define TCMU_CONFIG_LEN 256
>
> +static spinlock_t nl_complete_lock;
> +static struct idr complete_wait_udevs = IDR_INIT;
> +
> struct tcmu_nl_cmd {
> /* wake up thread waiting for reply */
> - struct completion complete;
> + bool complete;
> +
> int cmd;
> int status;
> };
> @@ -159,12 +163,17 @@ struct tcmu_dev {
>
> spinlock_t nl_cmd_lock;
> struct tcmu_nl_cmd curr_nl_cmd;
> - /* wake up threads waiting on curr_nl_cmd */
> + /* wake up threads waiting on nl_cmd_wq */
> wait_queue_head_t nl_cmd_wq;
>
> + /* complete thread waiting complete_wq */
> + wait_queue_head_t complete_wq;
> +
> char dev_config[TCMU_CONFIG_LEN];
>
> int nl_reply_supported;
> +
> + uint32_t dev_id;
> };
>
> #define TCMU_DEV(_se_dev) container_of(_se_dev, struct tcmu_dev, se_dev)
> @@ -251,6 +260,56 @@ static int tcmu_get_global_max_data_area(char *buffer,
> "Max MBs allowed to be allocated to all the tcmu device's "
> "data areas.");
>
> +static void tcmu_complete_wake_up(struct tcmu_dev *udev)
> +{
> + struct tcmu_nl_cmd *nl_cmd = &udev->curr_nl_cmd;
> +
> + spin_lock(&nl_complete_lock);
> + nl_cmd->complete = true;
> + wake_up(&udev->complete_wq);
> + spin_unlock(&nl_complete_lock);
> +}
> +
> +static void tcmu_complete_wake_up_all(void)
> +{
> + struct tcmu_nl_cmd *nl_cmd;
> + struct tcmu_dev *udev;
> + int i;
> +
> + spin_lock(&nl_complete_lock);
> + idr_for_each_entry(&complete_wait_udevs, udev, i) {
> + nl_cmd = &udev->curr_nl_cmd;
> + nl_cmd->complete = true;
> + wake_up(&udev->complete_wq);
> + }
> + spin_unlock(&nl_complete_lock);
> +}
> +
> +static int tcmu_complete_wait(struct tcmu_dev *udev)
> +{
> + struct tcmu_nl_cmd *nl_cmd = &udev->curr_nl_cmd;
> + uint32_t dev_id;
> +
> + spin_lock(&nl_complete_lock);
> + dev_id = idr_alloc(&complete_wait_udevs, udev, 1, USHRT_MAX,
> GFP_NOWAIT);
> + if (dev_id < 0) {
> + pr_err("tcmu: Could not allocate dev id.\n");
> + return dev_id;
> + }
> + udev->dev_id = dev_id;
dev_id is never used.
I think if you just wanted to loop over all the devices you could just
use a list.
Or,
Just add a helper around target_core_device.c:devices_idr that just
gives you the tcmu devices.
> + spin_unlock(&nl_complete_lock);
> +
> + pr_debug("sleeping for nl reply\n");
> + wait_event(udev->complete_wq, nl_cmd->complete);
I don't think you will need the complete field then or this function.
> +
> + spin_lock(&nl_complete_lock);
> + nl_cmd->complete = false;
> + idr_remove(&complete_wait_udevs, dev_id);
> + spin_unlock(&nl_complete_lock);
> +
> + return 0;
> +}
> +
> /* multicast group */
> enum tcmu_multicast_groups {
> TCMU_MCGRP_CONFIG,
> @@ -311,7 +370,7 @@ static int tcmu_genl_cmd_done(struct genl_info *info, int
> completed_cmd)
> if (!is_removed)
> target_undepend_item(&dev->dev_group.cg_item);
> if (!ret)
> - complete(&nl_cmd->complete);
> + tcmu_complete_wake_up(udev);
> return ret;
> }
>
> @@ -1258,6 +1317,7 @@ static struct se_device *tcmu_alloc_device(struct
> se_hba *hba, const char *name)
> timer_setup(&udev->cmd_timer, tcmu_cmd_timedout, 0);
>
> init_waitqueue_head(&udev->nl_cmd_wq);
> + init_waitqueue_head(&udev->complete_wq);
> spin_lock_init(&udev->nl_cmd_lock);
>
> INIT_RADIX_TREE(&udev->data_blocks, GFP_KERNEL);
> @@ -1462,7 +1522,11 @@ static void tcmu_dev_call_rcu(struct rcu_head *p)
>
> kfree(udev->uio_info.name);
> kfree(udev->name);
> +
> + spin_lock(&nl_complete_lock);
> + idr_remove(&complete_wait_udevs, udev->dev_id);
> kfree(udev);
> + spin_unlock(&nl_complete_lock);
> }
>
> static int tcmu_check_and_free_pending_cmd(struct tcmu_cmd *cmd)
> @@ -1555,7 +1619,6 @@ static void tcmu_init_genl_cmd_reply(struct tcmu_dev
> *udev, int cmd)
>
> memset(nl_cmd, 0, sizeof(*nl_cmd));
> nl_cmd->cmd = cmd;
> - init_completion(&nl_cmd->complete);
>
> spin_unlock(&udev->nl_cmd_lock);
> }
> @@ -1572,8 +1635,9 @@ static int tcmu_wait_genl_cmd_reply(struct tcmu_dev
> *udev)
> if (udev->nl_reply_supported <= 0)
> return 0;
>
> - pr_debug("sleeping for nl reply\n");
> - wait_for_completion(&nl_cmd->complete);
> + ret = tcmu_complete_wait(udev);
> + if (ret)
> + return ret;
>
> spin_lock(&udev->nl_cmd_lock);
> nl_cmd->cmd = TCMU_CMD_UNSPEC;
> @@ -2323,6 +2387,26 @@ static ssize_t tcmu_block_dev_store(struct config_item
> *item, const char *page,
> }
> CONFIGFS_ATTR(tcmu_, block_dev);
>
> +static ssize_t tcmu_reset_netlink_store(struct config_item *item, const char
> *page,
> + size_t count)
> +{
> + u8 val;
> + int ret;
> +
> + ret = kstrtou8(page, 0, &val);
> + if (ret < 0)
> + return ret;
> +
> + if (val != 1) {
> + pr_err("Invalid block value %d\n", val);
> + return -EINVAL;
> + }
> +
> + tcmu_complete_wake_up_all();
> + return count;
> +}
> +CONFIGFS_ATTR_WO(tcmu_, reset_netlink);
If it's on the device it should only reset the device its on, so if 2
daemons/apps are managing different devices it doesn't mess up the other.
Or we could just assume that there is only 1 daemon type and just do a
global attr at the module level. Probably just the per device is best in
case we end up with people running gluster + qemu+tcmu and ceph +
tcmu-runner.
If you do the per device then you can just take the insides of
tcmu_genl_cmd_done and make it into a helper so that you can do the
refcount/target_undepend_item properly and it would do the wake up. In
the reset configfs function then grab the nl_cmd_lock, and set the
curr_nl_cmd status to some or pass it into the helper, and then call
your helper which does the common stuff.
> +
> static ssize_t tcmu_reset_ring_store(struct config_item *item, const char
> *page,
> size_t count)
> {
> @@ -2363,6 +2447,7 @@ static ssize_t tcmu_reset_ring_store(struct config_item
> *item, const char *page,
> static struct configfs_attribute *tcmu_action_attrs[] = {
> &tcmu_attr_block_dev,
> &tcmu_attr_reset_ring,
> + &tcmu_attr_reset_netlink,
> NULL,
> };
>
> @@ -2519,6 +2604,8 @@ static int __init tcmu_module_init(void)
> }
> tcmu_ops.tb_dev_attrib_attrs = tcmu_attrs;
>
> + spin_lock_init(&nl_complete_lock);
> +
> ret = transport_backend_register(&tcmu_ops);
> if (ret)
> goto out_attrs;
>