[PATCH] fb: fix lost console when the user unplugs a USB adapter
Mikulas Patocka
mpatocka at redhat.com
Tue Jul 10 02:29:40 UTC 2018
On Wed, 4 Jul 2018, Bartlomiej Zolnierkiewicz wrote:
> On Tuesday, July 03, 2018 01:18:57 PM Mikulas Patocka wrote:
> >
> > On Tue, 3 Jul 2018, Bartlomiej Zolnierkiewicz wrote:
> >
> > > Hi,
> > >
> > > On Sunday, June 03, 2018 11:46:29 AM Mikulas Patocka wrote:
> > > > I have a USB display adapter using the udlfb driver and I use it on an ARM
> > > > board that doesn't have any graphics card. When I plug the adapter in, the
> > > > console is properly displayed, however when I unplug and re-plug the
> > > > adapter, the console is not displayed and I can't access it until I reboot
> > > > the board.
> > > >
> > > > The reason is this:
> > > > When the adapter is unplugged, dlfb_usb_disconnect calls
> > > > unlink_framebuffer, then it waits until the reference count drops to zero
> > > > and then it deallocates the framebuffer. However, the console that is
> > > > attached to the framebuffer device keeps the reference count non-zero, so
> > > > the framebuffer device is never destroyed. When the USB adapter is plugged
> > > > again, it creates a new device /dev/fb1 and the console is not attached to
> > > > it.
> > > >
> > > > This patch fixes the bug by unbinding the console from unlink_framebuffer.
> > > > The code to unbind the console is moved from do_unregister_framebuffer to
> > > > a function unbind_console. When the console is unbound, the reference
> > > > count drops to zero and the udlfb driver frees the framebuffer. When the
> > > > adapter is plugged back, a new framebuffer is created and the console is
> > > > attached to it.
> > > >
> > > > Signed-off-by: Mikulas Patocka <mpatocka at redhat.com>
> > > > Cc: stable at vger.kernel.org
> > >
> > > After this change unbind_console() will be called twice in the standard
> > > framebuffer unregister path:
> > >
> > > - first time, directly by do_unregister_framebuffer()
> > >
> > > - second time, indirectly by do_unregister_framebuffer()->unlink_framebuffer()
> > >
> > > This doesn't look correctly.
> >
> > unbind_console calls the FB_EVENT_FB_UNBIND notifier, FB_EVENT_FB_UNBIND
> > goes to the function fbcon_fb_unbind and fbcon_fb_unbind checks if the
> > console is bound to the framebuffer for which unbind is requested. So a
> > double call won't cause any trouble.
>
> Even if it works okay currently it is not a best design to send duplicate
> events - especially since this can be easily avoided (for non-udlfb users)
> by:
>
> - renaming "vanilla" unlink_framebuffer() to __unlink_framebuffer()
>
> - converting do_unregister_framebuffer() to use __unlink_framebuffer()
>
> - adding "new" unlink_framebuffer() that will also call unbind_console()
>
> > > Also why can't udlfb just use unregister_framebuffer() like all other
> > > drivers (it uses unlink_framebuffer() and it is the only user of this
> > > helper)?
> >
> > It uses unregister_framebuffer() - but - unregister_framebuffer() may only
> > be called when the open count of the framebuffer is zero. So, the udlfb
> > driver waits until the open count drops to zero and then calls
> > unregister_framebuffer().
> >
> > But the console subsystem keeps the framebuffer open - which means that if
> > user use unplugs the USB adapter, the open count won't drop to zero
> > (because the console is bound to it) - which means that
> > unregister_framebuffer() will not be called.
>
> Is it a really the console subsystem and not the user-space keeping
> /dev/fb0 (with console binded to fb0) opened after the USB device
> vanishes?
Yes - I unplugged the adapter without Xserver running and without any
other framebuffer application running - the console keeps it open.
> After re-plugging the USB device /dev/fb0 stays and /dev/fb1
> appears, right?
The file /dev/fb0 is deleted (because dlfb_usb_disconnect calls
unlink_framebuffer), but it is kept in the kernel. When I re-plug the
adapter, /dev/fb1 is created but the console is still bound to /dev/fb0.
When the adapter is re-plugged, it shows just a green screen.
> I also mean that unregister_framebuffer() should be called instead
> unlink_framebuffer(), not additionally some time later as it is done
> currently.
Can unregister_framebuffer() be called when /dev/fb0 is open as a file
handle and/or mapped to some process?
> Moreover the dlfb <-> fb_info locking scheme seems to be reversed
> (+racy) as it is dlfb that should control lifetime of fb_info, then
> in dlfb_free() we should just call framebuffer_release() etc.
How should in your opinion framebuffer destruction work?
Should the driver count the number of users and call
unregister_framebuffer() when it drops to zero?
Or should the driver call unregister_framebuffer() unconditionally when
the device is unplugged and destroy the device in the "fb_destroy"
callback? (fb_destroy seems to be called by the framebuffer subsystem when
the open count reaches zero)
If I grep the kernel for fb_destroy, very few framebuffer drivers use it.
> BTW comment in dlfb_ops_release():
>
> /* We can't free fb_info here - fbmem will touch it when we return */
>
> seems to be wrong as fbmem keeps an extra reference on fb_info
> during ->fb_release().
>
> > You must unbind the console before calling unregister_framebuffer(). The
>
> Hmm? The first thing that [do_]unregister_framebuffer) does seems to be
> unbinding the console.
>
> > PCI framebuffer drivers don't have this problem because the user is not
> > expected to just unplug the PCI card while it is being used by the
> > console.
>
> PCI framebuffer drivers currently don't use .suppress_bind_attrs driver
> flag so the PCI devices can be unbinded at any time by using sysfs "unbind"
> functionality (I guess we should be using .suppress_bind_attrs flag if it
> doesn't work currently).
I tested matrox PCI framebuffer driver on an old computer - and it suffers
from the same problem as udlfb. The matrox driver keeps the open count and
destroys itself when the open count reaches zero - but the console that is
bound to the driver prevents the open count from reaching zero - so if I
unbind the PCI device in sysfs, it does nothing and the console is still
active and works.
When I unbind the console with 'echo 0 >/sys/class/vtconsole/vtcon1/bind'
(after unbinding the PCI device), I get this deadlock:
[ 832.111652] sysrq: SysRq : Show Blocked State
[ 832.111674] task PC stack pid father
[ 832.111753] bash D 0 2254 2249 0x00000000
[ 832.111777] Call Trace:
[ 832.111816] ? __schedule+0x11e/0x3a0
[ 832.111834] ? schedule+0x26/0x80
[ 832.111855] ? schedule_timeout+0xed/0x140
[ 832.111879] ? __down+0x43/0x80
[ 832.111902] ? down+0x2d/0x40
[ 832.111917] ? console_lock+0xa/0x20
[ 832.111943] ? do_unregister_framebuffer+0x2a/0x100
[ 832.111963] ? unregister_framebuffer+0x14/0x40
[ 832.111989] ? matroxfb_remove.isra.10.part.11+0x65/0xe0 [matroxfb_base]
[ 832.112009] ? matroxfb_release+0x39/0xc0 [matroxfb_base]
[ 832.112025] ? fbcon_deinit+0x22e/0x300
[ 832.112049] ? do_bind_con_driver+0x176/0x360
[ 832.112071] ? do_unbind_con_driver+0x1ac/0x220
[ 832.112092] ? store_bind+0xe0/0x1e0
[ 832.112111] ? do_take_over_console+0x180/0x180
[ 832.112136] ? sysfs_kf_bin_read+0xc0/0xc0
[ 832.112154] ? dev_attr_store+0x11/0x20
[ 832.112172] ? sysfs_kf_write+0x24/0x60
[ 832.112191] ? kernfs_fop_write+0xc7/0x160
[ 832.112210] ? kernfs_fop_open+0x3a0/0x3a0
[ 832.112232] ? __vfs_write+0x1c/0x120
[ 832.112249] ? __alloc_fd+0x27/0x140
[ 832.112266] ? f_dupfd+0x4b/0x60
[ 832.112281] ? get_close_on_exec+0x25/0x40
[ 832.112297] ? do_fcntl+0x417/0x580
[ 832.112315] ? vfs_write+0x9e/0x1c0
[ 832.112334] ? ksys_write+0x32/0x80
[ 832.112352] ? do_int80_syscall_32+0x3e/0xe0
[ 832.112373] ? entry_INT80_32+0x27/0x27
Mikulas
More information about the dri-devel
mailing list