Message ID | 20221130163611.14686-1-roger.pau@citrix.com (mailing list archive) |
---|---|
State | Accepted |
Commit | c0dccad87cf68fc6012aec7567e354353097ec1a |
Headers | show |
Series | [v2] hvc/xen: lock console list traversal | expand |
On Wed, 30 Nov 2022, Roger Pau Monne wrote: > The currently lockless access to the xen console list in > vtermno_to_xencons() is incorrect, as additions and removals from the > list can happen anytime, and as such the traversal of the list to get > the private console data for a given termno needs to happen with the > lock held. Note users that modify the list already do so with the > lock taken. > > Adjust current lock takers to use the _irq{save,restore} helpers, > since the context in which vtermno_to_xencons() is called can have > interrupts disabled. Use the _irq{save,restore} set of helpers to > switch the current callers to disable interrupts in the locked region. > I haven't checked if existing users could instead use the _irq > variant, as I think it's safer to use _irq{save,restore} upfront. > > While there switch from using list_for_each_entry_safe to > list_for_each_entry: the current entry cursor won't be removed as > part of the code in the loop body, so using the _safe variant is > pointless. > > Fixes: 02e19f9c7cac ('hvc_xen: implement multiconsole support') > Signed-off-by: Roger Pau Monné <roger.pau@citrix.com> Reviewed-by: Stefano Stabellini <sstabellini@kernel.org> > --- > Changes since v1: > - Switch current lock users to disable interrupts in the locked > region. > --- > drivers/tty/hvc/hvc_xen.c | 46 ++++++++++++++++++++++++--------------- > 1 file changed, 29 insertions(+), 17 deletions(-) > > diff --git a/drivers/tty/hvc/hvc_xen.c b/drivers/tty/hvc/hvc_xen.c > index e63c1761a361..d9d023275328 100644 > --- a/drivers/tty/hvc/hvc_xen.c > +++ b/drivers/tty/hvc/hvc_xen.c > @@ -53,17 +53,22 @@ static DEFINE_SPINLOCK(xencons_lock); > > static struct xencons_info *vtermno_to_xencons(int vtermno) > { > - struct xencons_info *entry, *n, *ret = NULL; > + struct xencons_info *entry, *ret = NULL; > + unsigned long flags; > > - if (list_empty(&xenconsoles)) > - return NULL; > + spin_lock_irqsave(&xencons_lock, flags); > + if (list_empty(&xenconsoles)) { > + spin_unlock_irqrestore(&xencons_lock, flags); > + return NULL; > + } > > - list_for_each_entry_safe(entry, n, &xenconsoles, list) { > + list_for_each_entry(entry, &xenconsoles, list) { > if (entry->vtermno == vtermno) { > ret = entry; > break; > } > } > + spin_unlock_irqrestore(&xencons_lock, flags); > > return ret; > } > @@ -234,7 +239,7 @@ static int xen_hvm_console_init(void) > { > int r; > uint64_t v = 0; > - unsigned long gfn; > + unsigned long gfn, flags; > struct xencons_info *info; > > if (!xen_hvm_domain()) > @@ -270,9 +275,9 @@ static int xen_hvm_console_init(void) > goto err; > info->vtermno = HVC_COOKIE; > > - spin_lock(&xencons_lock); > + spin_lock_irqsave(&xencons_lock, flags); > list_add_tail(&info->list, &xenconsoles); > - spin_unlock(&xencons_lock); > + spin_unlock_irqrestore(&xencons_lock, flags); > > return 0; > err: > @@ -296,6 +301,7 @@ static int xencons_info_pv_init(struct xencons_info *info, int vtermno) > static int xen_pv_console_init(void) > { > struct xencons_info *info; > + unsigned long flags; > > if (!xen_pv_domain()) > return -ENODEV; > @@ -312,9 +318,9 @@ static int xen_pv_console_init(void) > /* already configured */ > return 0; > } > - spin_lock(&xencons_lock); > + spin_lock_irqsave(&xencons_lock, flags); > xencons_info_pv_init(info, HVC_COOKIE); > - spin_unlock(&xencons_lock); > + spin_unlock_irqrestore(&xencons_lock, flags); > > return 0; > } > @@ -322,6 +328,7 @@ static int xen_pv_console_init(void) > static int xen_initial_domain_console_init(void) > { > struct xencons_info *info; > + unsigned long flags; > > if (!xen_initial_domain()) > return -ENODEV; > @@ -337,9 +344,9 @@ static int xen_initial_domain_console_init(void) > info->irq = bind_virq_to_irq(VIRQ_CONSOLE, 0, false); > info->vtermno = HVC_COOKIE; > > - spin_lock(&xencons_lock); > + spin_lock_irqsave(&xencons_lock, flags); > list_add_tail(&info->list, &xenconsoles); > - spin_unlock(&xencons_lock); > + spin_unlock_irqrestore(&xencons_lock, flags); > > return 0; > } > @@ -394,10 +401,12 @@ static void xencons_free(struct xencons_info *info) > > static int xen_console_remove(struct xencons_info *info) > { > + unsigned long flags; > + > xencons_disconnect_backend(info); > - spin_lock(&xencons_lock); > + spin_lock_irqsave(&xencons_lock, flags); > list_del(&info->list); > - spin_unlock(&xencons_lock); > + spin_unlock_irqrestore(&xencons_lock, flags); > if (info->xbdev != NULL) > xencons_free(info); > else { > @@ -478,6 +487,7 @@ static int xencons_probe(struct xenbus_device *dev, > { > int ret, devid; > struct xencons_info *info; > + unsigned long flags; > > devid = dev->nodename[strlen(dev->nodename) - 1] - '0'; > if (devid == 0) > @@ -497,9 +507,9 @@ static int xencons_probe(struct xenbus_device *dev, > ret = xencons_connect_backend(dev, info); > if (ret < 0) > goto error; > - spin_lock(&xencons_lock); > + spin_lock_irqsave(&xencons_lock, flags); > list_add_tail(&info->list, &xenconsoles); > - spin_unlock(&xencons_lock); > + spin_unlock_irqrestore(&xencons_lock, flags); > > return 0; > > @@ -599,10 +609,12 @@ static int __init xen_hvc_init(void) > > info->hvc = hvc_alloc(HVC_COOKIE, info->irq, ops, 256); > if (IS_ERR(info->hvc)) { > + unsigned long flags; > + > r = PTR_ERR(info->hvc); > - spin_lock(&xencons_lock); > + spin_lock_irqsave(&xencons_lock, flags); > list_del(&info->list); > - spin_unlock(&xencons_lock); > + spin_unlock_irqrestore(&xencons_lock, flags); > if (info->irq) > unbind_from_irqhandler(info->irq, NULL); > kfree(info); > -- > 2.37.3 >
On 30.11.22 17:36, Roger Pau Monne wrote: > The currently lockless access to the xen console list in > vtermno_to_xencons() is incorrect, as additions and removals from the > list can happen anytime, and as such the traversal of the list to get > the private console data for a given termno needs to happen with the > lock held. Note users that modify the list already do so with the > lock taken. > > Adjust current lock takers to use the _irq{save,restore} helpers, > since the context in which vtermno_to_xencons() is called can have > interrupts disabled. Use the _irq{save,restore} set of helpers to > switch the current callers to disable interrupts in the locked region. > I haven't checked if existing users could instead use the _irq > variant, as I think it's safer to use _irq{save,restore} upfront. > > While there switch from using list_for_each_entry_safe to > list_for_each_entry: the current entry cursor won't be removed as > part of the code in the loop body, so using the _safe variant is > pointless. > > Fixes: 02e19f9c7cac ('hvc_xen: implement multiconsole support') > Signed-off-by: Roger Pau Monné <roger.pau@citrix.com> Pushed to xen/tip.git for-linus-6.2 Juergen
diff --git a/drivers/tty/hvc/hvc_xen.c b/drivers/tty/hvc/hvc_xen.c index e63c1761a361..d9d023275328 100644 --- a/drivers/tty/hvc/hvc_xen.c +++ b/drivers/tty/hvc/hvc_xen.c @@ -53,17 +53,22 @@ static DEFINE_SPINLOCK(xencons_lock); static struct xencons_info *vtermno_to_xencons(int vtermno) { - struct xencons_info *entry, *n, *ret = NULL; + struct xencons_info *entry, *ret = NULL; + unsigned long flags; - if (list_empty(&xenconsoles)) - return NULL; + spin_lock_irqsave(&xencons_lock, flags); + if (list_empty(&xenconsoles)) { + spin_unlock_irqrestore(&xencons_lock, flags); + return NULL; + } - list_for_each_entry_safe(entry, n, &xenconsoles, list) { + list_for_each_entry(entry, &xenconsoles, list) { if (entry->vtermno == vtermno) { ret = entry; break; } } + spin_unlock_irqrestore(&xencons_lock, flags); return ret; } @@ -234,7 +239,7 @@ static int xen_hvm_console_init(void) { int r; uint64_t v = 0; - unsigned long gfn; + unsigned long gfn, flags; struct xencons_info *info; if (!xen_hvm_domain()) @@ -270,9 +275,9 @@ static int xen_hvm_console_init(void) goto err; info->vtermno = HVC_COOKIE; - spin_lock(&xencons_lock); + spin_lock_irqsave(&xencons_lock, flags); list_add_tail(&info->list, &xenconsoles); - spin_unlock(&xencons_lock); + spin_unlock_irqrestore(&xencons_lock, flags); return 0; err: @@ -296,6 +301,7 @@ static int xencons_info_pv_init(struct xencons_info *info, int vtermno) static int xen_pv_console_init(void) { struct xencons_info *info; + unsigned long flags; if (!xen_pv_domain()) return -ENODEV; @@ -312,9 +318,9 @@ static int xen_pv_console_init(void) /* already configured */ return 0; } - spin_lock(&xencons_lock); + spin_lock_irqsave(&xencons_lock, flags); xencons_info_pv_init(info, HVC_COOKIE); - spin_unlock(&xencons_lock); + spin_unlock_irqrestore(&xencons_lock, flags); return 0; } @@ -322,6 +328,7 @@ static int xen_pv_console_init(void) static int xen_initial_domain_console_init(void) { struct xencons_info *info; + unsigned long flags; if (!xen_initial_domain()) return -ENODEV; @@ -337,9 +344,9 @@ static int xen_initial_domain_console_init(void) info->irq = bind_virq_to_irq(VIRQ_CONSOLE, 0, false); info->vtermno = HVC_COOKIE; - spin_lock(&xencons_lock); + spin_lock_irqsave(&xencons_lock, flags); list_add_tail(&info->list, &xenconsoles); - spin_unlock(&xencons_lock); + spin_unlock_irqrestore(&xencons_lock, flags); return 0; } @@ -394,10 +401,12 @@ static void xencons_free(struct xencons_info *info) static int xen_console_remove(struct xencons_info *info) { + unsigned long flags; + xencons_disconnect_backend(info); - spin_lock(&xencons_lock); + spin_lock_irqsave(&xencons_lock, flags); list_del(&info->list); - spin_unlock(&xencons_lock); + spin_unlock_irqrestore(&xencons_lock, flags); if (info->xbdev != NULL) xencons_free(info); else { @@ -478,6 +487,7 @@ static int xencons_probe(struct xenbus_device *dev, { int ret, devid; struct xencons_info *info; + unsigned long flags; devid = dev->nodename[strlen(dev->nodename) - 1] - '0'; if (devid == 0) @@ -497,9 +507,9 @@ static int xencons_probe(struct xenbus_device *dev, ret = xencons_connect_backend(dev, info); if (ret < 0) goto error; - spin_lock(&xencons_lock); + spin_lock_irqsave(&xencons_lock, flags); list_add_tail(&info->list, &xenconsoles); - spin_unlock(&xencons_lock); + spin_unlock_irqrestore(&xencons_lock, flags); return 0; @@ -599,10 +609,12 @@ static int __init xen_hvc_init(void) info->hvc = hvc_alloc(HVC_COOKIE, info->irq, ops, 256); if (IS_ERR(info->hvc)) { + unsigned long flags; + r = PTR_ERR(info->hvc); - spin_lock(&xencons_lock); + spin_lock_irqsave(&xencons_lock, flags); list_del(&info->list); - spin_unlock(&xencons_lock); + spin_unlock_irqrestore(&xencons_lock, flags); if (info->irq) unbind_from_irqhandler(info->irq, NULL); kfree(info);
The currently lockless access to the xen console list in vtermno_to_xencons() is incorrect, as additions and removals from the list can happen anytime, and as such the traversal of the list to get the private console data for a given termno needs to happen with the lock held. Note users that modify the list already do so with the lock taken. Adjust current lock takers to use the _irq{save,restore} helpers, since the context in which vtermno_to_xencons() is called can have interrupts disabled. Use the _irq{save,restore} set of helpers to switch the current callers to disable interrupts in the locked region. I haven't checked if existing users could instead use the _irq variant, as I think it's safer to use _irq{save,restore} upfront. While there switch from using list_for_each_entry_safe to list_for_each_entry: the current entry cursor won't be removed as part of the code in the loop body, so using the _safe variant is pointless. Fixes: 02e19f9c7cac ('hvc_xen: implement multiconsole support') Signed-off-by: Roger Pau Monné <roger.pau@citrix.com> --- Changes since v1: - Switch current lock users to disable interrupts in the locked region. --- drivers/tty/hvc/hvc_xen.c | 46 ++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 17 deletions(-)