2009-11-25 02:52:21 +03:00
|
|
|
/*
|
|
|
|
* udlfb.c -- Framebuffer driver for DisplayLink USB controller
|
|
|
|
*
|
|
|
|
* Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
|
|
|
|
* Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
|
2010-02-15 17:46:13 +03:00
|
|
|
* Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
|
2009-11-25 02:52:21 +03:00
|
|
|
*
|
|
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
|
|
* License v2. See the file COPYING in the main directory of this archive for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* Layout is based on skeletonfb by James Simmons and Geert Uytterhoeven,
|
|
|
|
* usb-skeleton by GregKH.
|
|
|
|
*
|
|
|
|
* Device-specific portions based on information from Displaylink, with work
|
|
|
|
* from Florian Echtler, Henrik Bjerregaard Pedersen, and others.
|
|
|
|
*/
|
2009-06-04 01:03:06 +04:00
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/usb.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/fb.h>
|
2009-07-27 13:01:03 +04:00
|
|
|
#include <linux/vmalloc.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 11:04:11 +03:00
|
|
|
#include <linux/slab.h>
|
2010-09-06 03:35:19 +04:00
|
|
|
#include <linux/delay.h>
|
2018-07-25 16:41:54 +03:00
|
|
|
#include <asm/unaligned.h>
|
2010-11-16 08:00:24 +03:00
|
|
|
#include <video/udlfb.h>
|
2011-01-06 12:04:02 +03:00
|
|
|
#include "edid.h"
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
static const struct fb_fix_screeninfo dlfb_fix = {
|
2010-02-15 17:46:13 +03:00
|
|
|
.id = "udlfb",
|
2010-02-15 17:45:43 +03:00
|
|
|
.type = FB_TYPE_PACKED_PIXELS,
|
|
|
|
.visual = FB_VISUAL_TRUECOLOR,
|
|
|
|
.xpanstep = 0,
|
|
|
|
.ypanstep = 0,
|
|
|
|
.ywrapstep = 0,
|
|
|
|
.accel = FB_ACCEL_NONE,
|
2009-11-25 02:52:21 +03:00
|
|
|
};
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
static const u32 udlfb_info_flags = FBINFO_DEFAULT | FBINFO_READS_FAST |
|
|
|
|
FBINFO_VIRTFB |
|
|
|
|
FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT |
|
|
|
|
FBINFO_HWACCEL_COPYAREA | FBINFO_MISC_ALWAYS_SETPAR;
|
|
|
|
|
2010-02-15 17:45:49 +03:00
|
|
|
/*
|
2011-07-10 11:30:00 +04:00
|
|
|
* There are many DisplayLink-based graphics products, all with unique PIDs.
|
|
|
|
* So we match on DisplayLink's VID + Vendor-Defined Interface Class (0xff)
|
|
|
|
* We also require a match on SubClass (0x00) and Protocol (0x00),
|
|
|
|
* which is compatible with all known USB 2.0 era graphics chips and firmware,
|
|
|
|
* but allows DisplayLink to increment those for any future incompatible chips
|
2010-02-15 17:45:49 +03:00
|
|
|
*/
|
2017-08-21 17:49:57 +03:00
|
|
|
static const struct usb_device_id id_table[] = {
|
2011-07-10 11:30:00 +04:00
|
|
|
{.idVendor = 0x17e9,
|
|
|
|
.bInterfaceClass = 0xff,
|
|
|
|
.bInterfaceSubClass = 0x00,
|
|
|
|
.bInterfaceProtocol = 0x00,
|
|
|
|
.match_flags = USB_DEVICE_ID_MATCH_VENDOR |
|
|
|
|
USB_DEVICE_ID_MATCH_INT_CLASS |
|
|
|
|
USB_DEVICE_ID_MATCH_INT_SUBCLASS |
|
|
|
|
USB_DEVICE_ID_MATCH_INT_PROTOCOL,
|
|
|
|
},
|
2010-02-15 17:45:49 +03:00
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(usb, id_table);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-09-06 03:35:39 +04:00
|
|
|
/* module options */
|
2012-01-13 03:02:20 +04:00
|
|
|
static bool console = 1; /* Allow fbcon to open framebuffer */
|
|
|
|
static bool fb_defio = 1; /* Detect mmap writes using page faults */
|
|
|
|
static bool shadow = 1; /* Optionally disable shadow framebuffer */
|
2012-03-04 00:57:37 +04:00
|
|
|
static int pixel_limit; /* Optionally force a pixel resolution limit */
|
2010-02-15 17:46:35 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
struct dlfb_deferred_free {
|
|
|
|
struct list_head list;
|
|
|
|
void *mem;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info, u32 new_len);
|
|
|
|
|
2010-02-15 17:45:55 +03:00
|
|
|
/* dlfb keeps a list of urbs for efficient bulk transfers */
|
|
|
|
static void dlfb_urb_completion(struct urb *urb);
|
2018-01-15 19:04:22 +03:00
|
|
|
static struct urb *dlfb_get_urb(struct dlfb_data *dlfb);
|
|
|
|
static int dlfb_submit_urb(struct dlfb_data *dlfb, struct urb * urb, size_t len);
|
|
|
|
static int dlfb_alloc_urb_list(struct dlfb_data *dlfb, int count, size_t size);
|
|
|
|
static void dlfb_free_urb_list(struct dlfb_data *dlfb);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2009-11-25 02:52:21 +03:00
|
|
|
/*
|
2010-02-15 17:46:48 +03:00
|
|
|
* All DisplayLink bulk operations start with 0xAF, followed by specific code
|
|
|
|
* All operations are written to buffers which then later get sent to device
|
2009-11-25 02:52:21 +03:00
|
|
|
*/
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_register(char *buf, u8 reg, u8 val)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
2010-02-15 17:45:43 +03:00
|
|
|
*buf++ = 0xAF;
|
|
|
|
*buf++ = 0x20;
|
|
|
|
*buf++ = reg;
|
|
|
|
*buf++ = val;
|
|
|
|
return buf;
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_vidreg_lock(char *buf)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:46:04 +03:00
|
|
|
return dlfb_set_register(buf, 0xFF, 0x00);
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_vidreg_unlock(char *buf)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:46:04 +03:00
|
|
|
return dlfb_set_register(buf, 0xFF, 0xFF);
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2011-08-22 00:34:11 +04:00
|
|
|
* Map FB_BLANK_* to DisplayLink register
|
|
|
|
* DLReg FB_BLANK_*
|
|
|
|
* ----- -----------------------------
|
|
|
|
* 0x00 FB_BLANK_UNBLANK (0)
|
|
|
|
* 0x01 FB_BLANK (1)
|
|
|
|
* 0x03 FB_BLANK_VSYNC_SUSPEND (2)
|
|
|
|
* 0x05 FB_BLANK_HSYNC_SUSPEND (3)
|
|
|
|
* 0x07 FB_BLANK_POWERDOWN (4) Note: requires modeset to come back
|
2009-11-25 02:52:21 +03:00
|
|
|
*/
|
2011-08-22 00:34:11 +04:00
|
|
|
static char *dlfb_blanking(char *buf, int fb_blank)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2011-08-22 00:34:11 +04:00
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
switch (fb_blank) {
|
|
|
|
case FB_BLANK_POWERDOWN:
|
|
|
|
reg = 0x07;
|
|
|
|
break;
|
|
|
|
case FB_BLANK_HSYNC_SUSPEND:
|
|
|
|
reg = 0x05;
|
|
|
|
break;
|
|
|
|
case FB_BLANK_VSYNC_SUSPEND:
|
|
|
|
reg = 0x03;
|
|
|
|
break;
|
|
|
|
case FB_BLANK_NORMAL:
|
|
|
|
reg = 0x01;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
reg = 0x00;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = dlfb_set_register(buf, 0x1F, reg);
|
|
|
|
|
|
|
|
return buf;
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_color_depth(char *buf, u8 selection)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:46:04 +03:00
|
|
|
return dlfb_set_register(buf, 0x00, selection);
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_base16bpp(char *wrptr, u32 base)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:45:43 +03:00
|
|
|
/* the base pointer is 16 bits wide, 0x20 is hi byte. */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register(wrptr, 0x20, base >> 16);
|
|
|
|
wrptr = dlfb_set_register(wrptr, 0x21, base >> 8);
|
|
|
|
return dlfb_set_register(wrptr, 0x22, base);
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:48 +03:00
|
|
|
/*
|
|
|
|
* DisplayLink HW has separate 16bpp and 8bpp framebuffers.
|
|
|
|
* In 24bpp modes, the low 323 RGB bits go in the 8bpp framebuffer
|
|
|
|
*/
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_base8bpp(char *wrptr, u32 base)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register(wrptr, 0x26, base >> 16);
|
|
|
|
wrptr = dlfb_set_register(wrptr, 0x27, base >> 8);
|
|
|
|
return dlfb_set_register(wrptr, 0x28, base);
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_register_16(char *wrptr, u8 reg, u16 value)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register(wrptr, reg, value >> 8);
|
|
|
|
return dlfb_set_register(wrptr, reg+1, value);
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is kind of weird because the controller takes some
|
|
|
|
* register values in a different byte order than other registers.
|
|
|
|
*/
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_register_16be(char *wrptr, u8 reg, u16 value)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register(wrptr, reg, value);
|
|
|
|
return dlfb_set_register(wrptr, reg+1, value >> 8);
|
2009-11-25 02:52:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* LFSR is linear feedback shift register. The reason we have this is
|
|
|
|
* because the display controller needs to minimize the clock depth of
|
|
|
|
* various counters used in the display path. So this code reverses the
|
|
|
|
* provided value into the lfsr16 value by counting backwards to get
|
|
|
|
* the value that needs to be set in the hardware comparator to get the
|
|
|
|
* same actual count. This makes sense once you read above a couple of
|
|
|
|
* times and think about it from a hardware perspective.
|
|
|
|
*/
|
2010-02-15 17:46:48 +03:00
|
|
|
static u16 dlfb_lfsr16(u16 actual_count)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
|
|
|
u32 lv = 0xFFFF; /* This is the lfsr value that the hw starts with */
|
|
|
|
|
|
|
|
while (actual_count--) {
|
|
|
|
lv = ((lv << 1) |
|
|
|
|
(((lv >> 15) ^ (lv >> 4) ^ (lv >> 2) ^ (lv >> 1)) & 1))
|
|
|
|
& 0xFFFF;
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2009-11-25 02:52:21 +03:00
|
|
|
return (u16) lv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This does LFSR conversion on the value that is to be written.
|
|
|
|
* See LFSR explanation above for more detail.
|
|
|
|
*/
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_register_lfsr16(char *wrptr, u8 reg, u16 value)
|
2009-11-25 02:52:21 +03:00
|
|
|
{
|
2010-02-15 17:46:48 +03:00
|
|
|
return dlfb_set_register_16(wrptr, reg, dlfb_lfsr16(value));
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2009-11-25 02:52:21 +03:00
|
|
|
/*
|
|
|
|
* This takes a standard fbdev screeninfo struct and all of its monitor mode
|
|
|
|
* details and converts them into the DisplayLink equivalent register commands.
|
|
|
|
*/
|
2010-02-15 17:46:04 +03:00
|
|
|
static char *dlfb_set_vid_cmds(char *wrptr, struct fb_var_screeninfo *var)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
2009-11-25 02:52:21 +03:00
|
|
|
u16 xds, yds;
|
|
|
|
u16 xde, yde;
|
|
|
|
u16 yec;
|
|
|
|
|
|
|
|
/* x display start */
|
|
|
|
xds = var->left_margin + var->hsync_len;
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x01, xds);
|
2009-11-25 02:52:21 +03:00
|
|
|
/* x display end */
|
|
|
|
xde = xds + var->xres;
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x03, xde);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* y display start */
|
|
|
|
yds = var->upper_margin + var->vsync_len;
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x05, yds);
|
2009-11-25 02:52:21 +03:00
|
|
|
/* y display end */
|
|
|
|
yde = yds + var->yres;
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x07, yde);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* x end count is active + blanking - 1 */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x09,
|
|
|
|
xde + var->right_margin - 1);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* libdlo hardcodes hsync start to 1 */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x0B, 1);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* hsync end is width of sync pulse + 1 */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x0D, var->hsync_len + 1);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* hpixels is active pixels */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_16(wrptr, 0x0F, var->xres);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2009-11-25 02:52:21 +03:00
|
|
|
/* yendcount is vertical active + vertical blanking */
|
|
|
|
yec = var->yres + var->upper_margin + var->lower_margin +
|
|
|
|
var->vsync_len;
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x11, yec);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2009-11-25 02:52:21 +03:00
|
|
|
/* libdlo hardcodes vsync start to 0 */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x13, 0);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* vsync end is width of vsync pulse */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_lfsr16(wrptr, 0x15, var->vsync_len);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* vpixels is active pixels */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_16(wrptr, 0x17, var->yres);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/* convert picoseconds to 5kHz multiple for pclk5k = x * 1E12/5k */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_register_16be(wrptr, 0x1B,
|
|
|
|
200*1000*1000/var->pixclock);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
return wrptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This takes a standard fbdev screeninfo struct that was fetched or prepared
|
|
|
|
* and then generates the appropriate command sequence that then drives the
|
|
|
|
* display controller.
|
|
|
|
*/
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_set_video_mode(struct dlfb_data *dlfb,
|
2009-11-25 02:52:21 +03:00
|
|
|
struct fb_var_screeninfo *var)
|
|
|
|
{
|
|
|
|
char *buf;
|
|
|
|
char *wrptr;
|
2015-06-08 02:02:47 +03:00
|
|
|
int retval;
|
2009-11-25 02:52:21 +03:00
|
|
|
int writesize;
|
2010-02-15 17:46:21 +03:00
|
|
|
struct urb *urb;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (!atomic_read(&dlfb->usb_active))
|
2010-02-15 17:46:21 +03:00
|
|
|
return -EPERM;
|
2009-11-25 02:52:21 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
urb = dlfb_get_urb(dlfb);
|
2010-02-15 17:46:21 +03:00
|
|
|
if (!urb)
|
|
|
|
return -ENOMEM;
|
2010-09-06 05:29:56 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
buf = (char *) urb->transfer_buffer;
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This first section has to do with setting the base address on the
|
|
|
|
* controller * associated with the display. There are 2 base
|
|
|
|
* pointers, currently, we only * use the 16 bpp segment.
|
|
|
|
*/
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_vidreg_lock(buf);
|
|
|
|
wrptr = dlfb_set_color_depth(wrptr, 0x00);
|
2009-11-25 02:52:21 +03:00
|
|
|
/* set base for 16bpp segment to 0 */
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_base16bpp(wrptr, 0);
|
2009-11-25 02:52:21 +03:00
|
|
|
/* set base for 8bpp segment to end of fb */
|
2018-01-15 19:04:22 +03:00
|
|
|
wrptr = dlfb_set_base8bpp(wrptr, dlfb->info->fix.smem_len);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_set_vid_cmds(wrptr, var);
|
2011-08-22 00:34:11 +04:00
|
|
|
wrptr = dlfb_blanking(wrptr, FB_BLANK_UNBLANK);
|
2010-02-15 17:46:04 +03:00
|
|
|
wrptr = dlfb_vidreg_unlock(wrptr);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
|
|
|
writesize = wrptr - buf;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
retval = dlfb_submit_urb(dlfb, urb, writesize);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->blank_mode = FB_BLANK_UNBLANK;
|
2011-08-22 00:34:11 +04:00
|
|
|
|
2009-11-25 02:52:21 +03:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
static int dlfb_ops_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
|
|
|
unsigned long start = vma->vm_start;
|
|
|
|
unsigned long size = vma->vm_end - vma->vm_start;
|
|
|
|
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
|
|
|
|
unsigned long page, pos;
|
|
|
|
|
2013-04-18 09:39:47 +04:00
|
|
|
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
|
|
|
|
return -EINVAL;
|
|
|
|
if (size > info->fix.smem_len)
|
|
|
|
return -EINVAL;
|
|
|
|
if (offset > info->fix.smem_len - size)
|
2009-06-04 01:03:06 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
pos = (unsigned long)info->fix.smem_start + offset;
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_dbg(info->dev, "mmap() framebuffer addr:%lu size:%lu\n",
|
|
|
|
pos, size);
|
2010-09-06 05:29:56 +04:00
|
|
|
|
2009-06-04 01:03:06 +04:00
|
|
|
while (size > 0) {
|
|
|
|
page = vmalloc_to_pfn((void *)pos);
|
2009-06-04 01:47:08 +04:00
|
|
|
if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED))
|
2009-06-04 01:03:06 +04:00
|
|
|
return -EAGAIN;
|
2009-06-04 01:47:08 +04:00
|
|
|
|
2009-06-04 01:03:06 +04:00
|
|
|
start += PAGE_SIZE;
|
|
|
|
pos += PAGE_SIZE;
|
|
|
|
if (size > PAGE_SIZE)
|
|
|
|
size -= PAGE_SIZE;
|
|
|
|
else
|
|
|
|
size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
/*
|
|
|
|
* Trims identical data from front and back of line
|
|
|
|
* Sets new front buffer address and width
|
|
|
|
* And returns byte count of identical pixels
|
|
|
|
* Assumes CPU natural alignment (unsigned long)
|
|
|
|
* for back and front buffer ptrs and width
|
|
|
|
*/
|
|
|
|
static int dlfb_trim_hline(const u8 *bback, const u8 **bfront, int *width_bytes)
|
2009-06-11 10:02:19 +04:00
|
|
|
{
|
2010-02-15 17:46:21 +03:00
|
|
|
int j, k;
|
|
|
|
const unsigned long *back = (const unsigned long *) bback;
|
|
|
|
const unsigned long *front = (const unsigned long *) *bfront;
|
|
|
|
const int width = *width_bytes / sizeof(unsigned long);
|
|
|
|
int identical = width;
|
|
|
|
int start = width;
|
|
|
|
int end = width;
|
|
|
|
|
|
|
|
for (j = 0; j < width; j++) {
|
|
|
|
if (back[j] != front[j]) {
|
|
|
|
start = j;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
for (k = width - 1; k > j; k--) {
|
|
|
|
if (back[k] != front[k]) {
|
|
|
|
end = k+1;
|
|
|
|
break;
|
|
|
|
}
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
identical = start + (width - end);
|
|
|
|
*bfront = (u8 *) &front[start];
|
|
|
|
*width_bytes = (end - start) * sizeof(unsigned long);
|
|
|
|
|
|
|
|
return identical * sizeof(unsigned long);
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2010-04-03 09:00:37 +04:00
|
|
|
* Render a command stream for an encoded horizontal line segment of pixels.
|
|
|
|
*
|
|
|
|
* A command buffer holds several commands.
|
|
|
|
* It always begins with a fresh command header
|
|
|
|
* (the protocol doesn't require this, but we enforce it to allow
|
|
|
|
* multiple buffers to be potentially encoded and sent in parallel).
|
|
|
|
* A single command encodes one contiguous horizontal line of pixels
|
|
|
|
*
|
|
|
|
* The function relies on the client to do all allocation, so that
|
|
|
|
* rendering can be done directly to output buffers (e.g. USB URBs).
|
|
|
|
* The function fills the supplied command buffer, providing information
|
|
|
|
* on where it left off, so the client may call in again with additional
|
|
|
|
* buffers if the line will take several buffers to complete.
|
|
|
|
*
|
|
|
|
* A single command can transmit a maximum of 256 pixels,
|
|
|
|
* regardless of the compression ratio (protocol design limit).
|
|
|
|
* To the hardware, 0 for a size byte means 256
|
2010-09-06 05:29:56 +04:00
|
|
|
*
|
2010-04-03 09:00:37 +04:00
|
|
|
* Rather than 256 pixel commands which are either rl or raw encoded,
|
|
|
|
* the rlx command simply assumes alternating raw and rl spans within one cmd.
|
|
|
|
* This has a slightly larger header overhead, but produces more even results.
|
|
|
|
* It also processes all data (read and write) in a single pass.
|
|
|
|
* Performance benchmarks of common cases show it having just slightly better
|
2010-09-06 05:29:56 +04:00
|
|
|
* compression than 256 pixel raw or rle commands, with similar CPU consumpion.
|
2010-04-03 09:00:37 +04:00
|
|
|
* But for very rl friendly data, will compress not quite as well.
|
|
|
|
*/
|
2010-02-15 17:46:21 +03:00
|
|
|
static void dlfb_compress_hline(
|
|
|
|
const uint16_t **pixel_start_ptr,
|
|
|
|
const uint16_t *const pixel_end,
|
|
|
|
uint32_t *device_address_ptr,
|
|
|
|
uint8_t **command_buffer_ptr,
|
2018-07-25 16:41:56 +03:00
|
|
|
const uint8_t *const cmd_buffer_end,
|
|
|
|
unsigned long back_buffer_offset,
|
|
|
|
int *ident_ptr)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
2010-02-15 17:46:21 +03:00
|
|
|
const uint16_t *pixel = *pixel_start_ptr;
|
|
|
|
uint32_t dev_addr = *device_address_ptr;
|
|
|
|
uint8_t *cmd = *command_buffer_ptr;
|
|
|
|
|
|
|
|
while ((pixel_end > pixel) &&
|
|
|
|
(cmd_buffer_end - MIN_RLX_CMD_BYTES > cmd)) {
|
2013-05-10 15:46:48 +04:00
|
|
|
uint8_t *raw_pixels_count_byte = NULL;
|
|
|
|
uint8_t *cmd_pixels_count_byte = NULL;
|
|
|
|
const uint16_t *raw_pixel_start = NULL;
|
|
|
|
const uint16_t *cmd_pixel_start, *cmd_pixel_end = NULL;
|
2010-02-15 17:46:21 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
if (back_buffer_offset &&
|
|
|
|
*pixel == *(u16 *)((u8 *)pixel + back_buffer_offset)) {
|
|
|
|
pixel++;
|
|
|
|
dev_addr += BPP;
|
|
|
|
(*ident_ptr)++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
*cmd++ = 0xAF;
|
|
|
|
*cmd++ = 0x6B;
|
2018-03-12 19:06:53 +03:00
|
|
|
*cmd++ = dev_addr >> 16;
|
|
|
|
*cmd++ = dev_addr >> 8;
|
|
|
|
*cmd++ = dev_addr;
|
2010-02-15 17:46:21 +03:00
|
|
|
|
|
|
|
cmd_pixels_count_byte = cmd++; /* we'll know this later */
|
|
|
|
cmd_pixel_start = pixel;
|
|
|
|
|
|
|
|
raw_pixels_count_byte = cmd++; /* we'll know this later */
|
|
|
|
raw_pixel_start = pixel;
|
|
|
|
|
2018-07-25 16:41:54 +03:00
|
|
|
cmd_pixel_end = pixel + min3(MAX_CMD_PIXELS + 1UL,
|
|
|
|
(unsigned long)(pixel_end - pixel),
|
|
|
|
(unsigned long)(cmd_buffer_end - 1 - cmd) / BPP);
|
2010-02-15 17:46:21 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
if (back_buffer_offset) {
|
|
|
|
/* note: the framebuffer may change under us, so we must test for underflow */
|
|
|
|
while (cmd_pixel_end - 1 > pixel &&
|
|
|
|
*(cmd_pixel_end - 1) == *(u16 *)((u8 *)(cmd_pixel_end - 1) + back_buffer_offset))
|
|
|
|
cmd_pixel_end--;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
while (pixel < cmd_pixel_end) {
|
|
|
|
const uint16_t * const repeating_pixel = pixel;
|
2018-07-25 16:41:56 +03:00
|
|
|
u16 pixel_value = *pixel;
|
2010-02-15 17:46:21 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
put_unaligned_be16(pixel_value, cmd);
|
|
|
|
if (back_buffer_offset)
|
|
|
|
*(u16 *)((u8 *)pixel + back_buffer_offset) = pixel_value;
|
2018-07-25 16:41:54 +03:00
|
|
|
cmd += 2;
|
2010-02-15 17:46:21 +03:00
|
|
|
pixel++;
|
|
|
|
|
|
|
|
if (unlikely((pixel < cmd_pixel_end) &&
|
2018-07-25 16:41:56 +03:00
|
|
|
(*pixel == pixel_value))) {
|
2010-02-15 17:46:21 +03:00
|
|
|
/* go back and fill in raw pixel count */
|
|
|
|
*raw_pixels_count_byte = ((repeating_pixel -
|
|
|
|
raw_pixel_start) + 1) & 0xFF;
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
do {
|
|
|
|
if (back_buffer_offset)
|
|
|
|
*(u16 *)((u8 *)pixel + back_buffer_offset) = pixel_value;
|
2010-02-15 17:46:21 +03:00
|
|
|
pixel++;
|
2018-07-25 16:41:56 +03:00
|
|
|
} while ((pixel < cmd_pixel_end) &&
|
|
|
|
(*pixel == pixel_value));
|
2009-11-25 02:52:21 +03:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
/* immediately after raw data is repeat byte */
|
|
|
|
*cmd++ = ((pixel - repeating_pixel) - 1) & 0xFF;
|
2009-11-25 02:52:21 +03:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
/* Then start another raw pixel span */
|
|
|
|
raw_pixel_start = pixel;
|
|
|
|
raw_pixels_count_byte = cmd++;
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
if (pixel > raw_pixel_start) {
|
|
|
|
/* finalize last RAW span */
|
|
|
|
*raw_pixels_count_byte = (pixel-raw_pixel_start) & 0xFF;
|
2018-07-25 16:41:54 +03:00
|
|
|
} else {
|
|
|
|
/* undo unused byte */
|
|
|
|
cmd--;
|
2010-02-15 17:46:21 +03:00
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
*cmd_pixels_count_byte = (pixel - cmd_pixel_start) & 0xFF;
|
2018-07-25 16:41:54 +03:00
|
|
|
dev_addr += (u8 *)pixel - (u8 *)cmd_pixel_start;
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2018-07-25 16:41:54 +03:00
|
|
|
if (cmd_buffer_end - MIN_RLX_CMD_BYTES <= cmd) {
|
2010-02-15 17:46:21 +03:00
|
|
|
/* Fill leftover bytes with no-ops */
|
|
|
|
if (cmd_buffer_end > cmd)
|
|
|
|
memset(cmd, 0xAF, cmd_buffer_end - cmd);
|
|
|
|
cmd = (uint8_t *) cmd_buffer_end;
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
*command_buffer_ptr = cmd;
|
|
|
|
*pixel_start_ptr = pixel;
|
|
|
|
*device_address_ptr = dev_addr;
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
/*
|
|
|
|
* There are 3 copies of every pixel: The front buffer that the fbdev
|
|
|
|
* client renders to, the actual framebuffer across the USB bus in hardware
|
|
|
|
* (that we can only write to, slowly, and can never read), and (optionally)
|
|
|
|
* our shadow copy that tracks what's been sent to that hardware buffer.
|
|
|
|
*/
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_render_hline(struct dlfb_data *dlfb, struct urb **urb_ptr,
|
2010-02-15 17:46:21 +03:00
|
|
|
const char *front, char **urb_buf_ptr,
|
|
|
|
u32 byte_offset, u32 byte_width,
|
|
|
|
int *ident_ptr, int *sent_ptr)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
2010-02-15 17:46:21 +03:00
|
|
|
const u8 *line_start, *line_end, *next_pixel;
|
2018-01-15 19:04:22 +03:00
|
|
|
u32 dev_addr = dlfb->base16 + byte_offset;
|
2010-02-15 17:46:21 +03:00
|
|
|
struct urb *urb = *urb_ptr;
|
|
|
|
u8 *cmd = *urb_buf_ptr;
|
|
|
|
u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length;
|
2018-07-25 16:41:56 +03:00
|
|
|
unsigned long back_buffer_offset = 0;
|
2010-02-15 17:46:21 +03:00
|
|
|
|
|
|
|
line_start = (u8 *) (front + byte_offset);
|
|
|
|
next_pixel = line_start;
|
|
|
|
line_end = next_pixel + byte_width;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (dlfb->backing_buffer) {
|
2010-02-15 17:46:21 +03:00
|
|
|
int offset;
|
2018-01-15 19:04:22 +03:00
|
|
|
const u8 *back_start = (u8 *) (dlfb->backing_buffer
|
2010-02-15 17:46:21 +03:00
|
|
|
+ byte_offset);
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
back_buffer_offset = (unsigned long)back_start - (unsigned long)line_start;
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
*ident_ptr += dlfb_trim_hline(back_start, &next_pixel,
|
|
|
|
&byte_width);
|
|
|
|
|
|
|
|
offset = next_pixel - line_start;
|
|
|
|
line_end = next_pixel + byte_width;
|
|
|
|
dev_addr += offset;
|
|
|
|
back_start += offset;
|
|
|
|
line_start += offset;
|
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
while (next_pixel < line_end) {
|
|
|
|
|
|
|
|
dlfb_compress_hline((const uint16_t **) &next_pixel,
|
|
|
|
(const uint16_t *) line_end, &dev_addr,
|
2018-07-25 16:41:56 +03:00
|
|
|
(u8 **) &cmd, (u8 *) cmd_end, back_buffer_offset,
|
|
|
|
ident_ptr);
|
2010-02-15 17:46:21 +03:00
|
|
|
|
|
|
|
if (cmd >= cmd_end) {
|
|
|
|
int len = cmd - (u8 *) urb->transfer_buffer;
|
2018-01-15 19:04:22 +03:00
|
|
|
if (dlfb_submit_urb(dlfb, urb, len))
|
2010-09-06 05:28:29 +04:00
|
|
|
return 1; /* lost pixels is set */
|
2010-02-15 17:46:21 +03:00
|
|
|
*sent_ptr += len;
|
2018-01-15 19:04:22 +03:00
|
|
|
urb = dlfb_get_urb(dlfb);
|
2010-02-15 17:46:21 +03:00
|
|
|
if (!urb)
|
2010-09-06 05:28:29 +04:00
|
|
|
return 1; /* lost_pixels is set */
|
2010-02-15 17:46:21 +03:00
|
|
|
*urb_ptr = urb;
|
|
|
|
cmd = urb->transfer_buffer;
|
|
|
|
cmd_end = &cmd[urb->transfer_buffer_length];
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
*urb_buf_ptr = cmd;
|
2010-09-06 05:28:29 +04:00
|
|
|
|
|
|
|
return 0;
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_handle_damage(struct dlfb_data *dlfb, int x, int y,
|
2010-02-15 17:46:21 +03:00
|
|
|
int width, int height, char *data)
|
2009-06-11 10:02:19 +04:00
|
|
|
{
|
|
|
|
int i, ret;
|
2010-02-15 17:46:21 +03:00
|
|
|
char *cmd;
|
|
|
|
cycles_t start_cycles, end_cycles;
|
|
|
|
int bytes_sent = 0;
|
|
|
|
int bytes_identical = 0;
|
|
|
|
struct urb *urb;
|
|
|
|
int aligned_x;
|
|
|
|
|
|
|
|
start_cycles = get_cycles();
|
|
|
|
|
|
|
|
aligned_x = DL_ALIGN_DOWN(x, sizeof(unsigned long));
|
|
|
|
width = DL_ALIGN_UP(width + (x-aligned_x), sizeof(unsigned long));
|
|
|
|
x = aligned_x;
|
|
|
|
|
|
|
|
if ((width <= 0) ||
|
2018-01-15 19:04:22 +03:00
|
|
|
(x + width > dlfb->info->var.xres) ||
|
|
|
|
(y + height > dlfb->info->var.yres))
|
2010-02-15 17:46:21 +03:00
|
|
|
return -EINVAL;
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (!atomic_read(&dlfb->usb_active))
|
2010-02-15 17:46:21 +03:00
|
|
|
return 0;
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
urb = dlfb_get_urb(dlfb);
|
2010-02-15 17:46:21 +03:00
|
|
|
if (!urb)
|
|
|
|
return 0;
|
|
|
|
cmd = urb->transfer_buffer;
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
for (i = y; i < y + height ; i++) {
|
2018-01-15 19:04:22 +03:00
|
|
|
const int line_offset = dlfb->info->fix.line_length * i;
|
2010-02-15 17:46:21 +03:00
|
|
|
const int byte_offset = line_offset + (x * BPP);
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (dlfb_render_hline(dlfb, &urb,
|
|
|
|
(char *) dlfb->info->fix.smem_start,
|
2010-09-06 05:29:56 +04:00
|
|
|
&cmd, byte_offset, width * BPP,
|
2010-09-06 05:28:29 +04:00
|
|
|
&bytes_identical, &bytes_sent))
|
|
|
|
goto error;
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
if (cmd > (char *) urb->transfer_buffer) {
|
2018-07-25 16:41:54 +03:00
|
|
|
int len;
|
|
|
|
if (cmd < (char *) urb->transfer_buffer + urb->transfer_buffer_length)
|
|
|
|
*cmd++ = 0xAF;
|
2010-02-15 17:46:21 +03:00
|
|
|
/* Send partial buffer remaining before exiting */
|
2018-07-25 16:41:54 +03:00
|
|
|
len = cmd - (char *) urb->transfer_buffer;
|
2018-01-15 19:04:22 +03:00
|
|
|
ret = dlfb_submit_urb(dlfb, urb, len);
|
2010-02-15 17:46:21 +03:00
|
|
|
bytes_sent += len;
|
|
|
|
} else
|
|
|
|
dlfb_urb_completion(urb);
|
|
|
|
|
2010-09-06 05:28:29 +04:00
|
|
|
error:
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_add(bytes_sent, &dlfb->bytes_sent);
|
|
|
|
atomic_add(bytes_identical, &dlfb->bytes_identical);
|
|
|
|
atomic_add(width*height*2, &dlfb->bytes_rendered);
|
2010-02-15 17:46:21 +03:00
|
|
|
end_cycles = get_cycles();
|
|
|
|
atomic_add(((unsigned int) ((end_cycles - start_cycles)
|
|
|
|
>> 10)), /* Kcycles */
|
2018-01-15 19:04:22 +03:00
|
|
|
&dlfb->cpu_kcycles_used);
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
return 0;
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:04 +04:00
|
|
|
/*
|
|
|
|
* Path triggered by usermode clients who write to filesystem
|
|
|
|
* e.g. cat filename > /dev/fb1
|
|
|
|
* Not used by X Windows or text-mode console. But useful for testing.
|
|
|
|
* Slow because of extra copy and we must assume all pixels dirty.
|
|
|
|
*/
|
|
|
|
static ssize_t dlfb_ops_write(struct fb_info *info, const char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2011-01-06 11:29:24 +03:00
|
|
|
ssize_t result;
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2010-09-06 03:35:04 +04:00
|
|
|
u32 offset = (u32) *ppos;
|
|
|
|
|
|
|
|
result = fb_sys_write(info, buf, count, ppos);
|
|
|
|
|
|
|
|
if (result > 0) {
|
2012-08-14 11:11:09 +04:00
|
|
|
int start = max((int)(offset / info->fix.line_length), 0);
|
2010-09-06 03:35:04 +04:00
|
|
|
int lines = min((u32)((result / info->fix.line_length) + 1),
|
|
|
|
(u32)info->var.yres);
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_handle_damage(dlfb, 0, start, info->var.xres,
|
2010-09-06 03:35:04 +04:00
|
|
|
lines, info->screen_base);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
/* hardware has native COPY command (see libdlo), but not worth it for fbcon */
|
|
|
|
static void dlfb_ops_copyarea(struct fb_info *info,
|
|
|
|
const struct fb_copyarea *area)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
sys_copyarea(info, area);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_handle_damage(dlfb, area->dx, area->dy,
|
2010-02-15 17:46:21 +03:00
|
|
|
area->width, area->height, info->screen_base);
|
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
static void dlfb_ops_imageblit(struct fb_info *info,
|
|
|
|
const struct fb_image *image)
|
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
sys_imageblit(info, image);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_handle_damage(dlfb, image->dx, image->dy,
|
2010-02-15 17:46:21 +03:00
|
|
|
image->width, image->height, info->screen_base);
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
static void dlfb_ops_fillrect(struct fb_info *info,
|
|
|
|
const struct fb_fillrect *rect)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
sys_fillrect(info, rect);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_handle_damage(dlfb, rect->dx, rect->dy, rect->width,
|
2010-02-15 17:46:21 +03:00
|
|
|
rect->height, info->screen_base);
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:39 +04:00
|
|
|
/*
|
|
|
|
* NOTE: fb_defio.c is holding info->fbdefio.mutex
|
|
|
|
* Touching ANY framebuffer memory that triggers a page fault
|
|
|
|
* in fb_defio will cause a deadlock, when it also tries to
|
|
|
|
* grab the same mutex.
|
|
|
|
*/
|
|
|
|
static void dlfb_dpy_deferred_io(struct fb_info *info,
|
|
|
|
struct list_head *pagelist)
|
|
|
|
{
|
|
|
|
struct page *cur;
|
|
|
|
struct fb_deferred_io *fbdefio = info->fbdefio;
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2010-09-06 03:35:39 +04:00
|
|
|
struct urb *urb;
|
|
|
|
char *cmd;
|
|
|
|
cycles_t start_cycles, end_cycles;
|
|
|
|
int bytes_sent = 0;
|
|
|
|
int bytes_identical = 0;
|
|
|
|
int bytes_rendered = 0;
|
|
|
|
|
|
|
|
if (!fb_defio)
|
|
|
|
return;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (!atomic_read(&dlfb->usb_active))
|
2010-09-06 03:35:39 +04:00
|
|
|
return;
|
|
|
|
|
|
|
|
start_cycles = get_cycles();
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
urb = dlfb_get_urb(dlfb);
|
2010-09-06 03:35:39 +04:00
|
|
|
if (!urb)
|
|
|
|
return;
|
|
|
|
|
|
|
|
cmd = urb->transfer_buffer;
|
|
|
|
|
|
|
|
/* walk the written page list and render each to device */
|
|
|
|
list_for_each_entry(cur, &fbdefio->pagelist, lru) {
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (dlfb_render_hline(dlfb, &urb, (char *) info->fix.smem_start,
|
2010-09-06 03:35:39 +04:00
|
|
|
&cmd, cur->index << PAGE_SHIFT,
|
2010-09-06 05:28:29 +04:00
|
|
|
PAGE_SIZE, &bytes_identical, &bytes_sent))
|
|
|
|
goto error;
|
2010-09-06 03:35:39 +04:00
|
|
|
bytes_rendered += PAGE_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd > (char *) urb->transfer_buffer) {
|
2018-07-25 16:41:54 +03:00
|
|
|
int len;
|
|
|
|
if (cmd < (char *) urb->transfer_buffer + urb->transfer_buffer_length)
|
|
|
|
*cmd++ = 0xAF;
|
2010-09-06 03:35:39 +04:00
|
|
|
/* Send partial buffer remaining before exiting */
|
2018-07-25 16:41:54 +03:00
|
|
|
len = cmd - (char *) urb->transfer_buffer;
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_submit_urb(dlfb, urb, len);
|
2010-09-06 03:35:39 +04:00
|
|
|
bytes_sent += len;
|
|
|
|
} else
|
|
|
|
dlfb_urb_completion(urb);
|
|
|
|
|
2010-09-06 05:28:29 +04:00
|
|
|
error:
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_add(bytes_sent, &dlfb->bytes_sent);
|
|
|
|
atomic_add(bytes_identical, &dlfb->bytes_identical);
|
|
|
|
atomic_add(bytes_rendered, &dlfb->bytes_rendered);
|
2010-09-06 03:35:39 +04:00
|
|
|
end_cycles = get_cycles();
|
|
|
|
atomic_add(((unsigned int) ((end_cycles - start_cycles)
|
|
|
|
>> 10)), /* Kcycles */
|
2018-01-15 19:04:22 +03:00
|
|
|
&dlfb->cpu_kcycles_used);
|
2010-09-06 03:35:39 +04:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_get_edid(struct dlfb_data *dlfb, char *edid, int len)
|
2010-02-15 17:46:08 +03:00
|
|
|
{
|
2018-01-16 18:35:20 +03:00
|
|
|
int i, ret;
|
2010-09-06 03:35:23 +04:00
|
|
|
char *rbuf;
|
2010-02-15 17:46:08 +03:00
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
rbuf = kmalloc(2, GFP_KERNEL);
|
|
|
|
if (!rbuf)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
2018-01-15 19:04:22 +03:00
|
|
|
ret = usb_control_msg(dlfb->udev,
|
|
|
|
usb_rcvctrlpipe(dlfb->udev, 0), 0x02,
|
2017-11-09 20:09:30 +03:00
|
|
|
(0x80 | (0x02 << 5)), i << 8, 0xA1,
|
|
|
|
rbuf, 2, USB_CTRL_GET_TIMEOUT);
|
|
|
|
if (ret < 2) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(&dlfb->udev->dev,
|
|
|
|
"Read EDID byte %d failed: %d\n", i, ret);
|
2010-09-06 03:35:23 +04:00
|
|
|
i--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
edid[i] = rbuf[1];
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
kfree(rbuf);
|
|
|
|
|
|
|
|
return i;
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:04 +03:00
|
|
|
static int dlfb_ops_ioctl(struct fb_info *info, unsigned int cmd,
|
2010-02-15 17:46:21 +03:00
|
|
|
unsigned long arg)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
2010-02-15 17:46:21 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (!atomic_read(&dlfb->usb_active))
|
2010-02-15 17:46:21 +03:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* TODO: Update X server to get this from sysfs instead */
|
|
|
|
if (cmd == DLFB_IOCTL_RETURN_EDID) {
|
2011-08-22 00:34:15 +04:00
|
|
|
void __user *edid = (void __user *)arg;
|
2018-01-15 19:04:22 +03:00
|
|
|
if (copy_to_user(edid, dlfb->edid, dlfb->edid_size))
|
2009-06-11 10:02:19 +04:00
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:21 +03:00
|
|
|
/* TODO: Help propose a standard fb.h ioctl to report mmap damage */
|
|
|
|
if (cmd == DLFB_IOCTL_REPORT_DAMAGE) {
|
2011-08-22 00:34:15 +04:00
|
|
|
struct dloarea area;
|
|
|
|
|
|
|
|
if (copy_from_user(&area, (void __user *)arg,
|
|
|
|
sizeof(struct dloarea)))
|
|
|
|
return -EFAULT;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-09-06 05:28:29 +04:00
|
|
|
/*
|
|
|
|
* If we have a damage-aware client, turn fb_defio "off"
|
2011-03-31 05:57:33 +04:00
|
|
|
* To avoid perf imact of unnecessary page fault handling.
|
2010-09-06 05:28:29 +04:00
|
|
|
* Done by resetting the delay for this fb_info to a very
|
|
|
|
* long period. Pages will become writable and stay that way.
|
|
|
|
* Reset to normal value when all clients have closed this fb.
|
|
|
|
*/
|
|
|
|
if (info->fbdefio)
|
|
|
|
info->fbdefio->delay = DL_DEFIO_WRITE_DISABLE;
|
|
|
|
|
2011-08-22 00:34:15 +04:00
|
|
|
if (area.x < 0)
|
|
|
|
area.x = 0;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2011-08-22 00:34:15 +04:00
|
|
|
if (area.x > info->var.xres)
|
|
|
|
area.x = info->var.xres;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2011-08-22 00:34:15 +04:00
|
|
|
if (area.y < 0)
|
|
|
|
area.y = 0;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2011-08-22 00:34:15 +04:00
|
|
|
if (area.y > info->var.yres)
|
|
|
|
area.y = info->var.yres;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_handle_damage(dlfb, area.x, area.y, area.w, area.h,
|
2009-06-04 01:03:06 +04:00
|
|
|
info->screen_base);
|
|
|
|
}
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2009-06-04 01:03:06 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-04 01:47:08 +04:00
|
|
|
/* taken from vesafb */
|
2009-06-04 01:03:06 +04:00
|
|
|
static int
|
2010-02-15 17:46:04 +03:00
|
|
|
dlfb_ops_setcolreg(unsigned regno, unsigned red, unsigned green,
|
2009-06-04 01:03:06 +04:00
|
|
|
unsigned blue, unsigned transp, struct fb_info *info)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (regno >= info->cmap.len)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (regno < 16) {
|
|
|
|
if (info->var.red.offset == 10) {
|
|
|
|
/* 1:5:5:5 */
|
|
|
|
((u32 *) (info->pseudo_palette))[regno] =
|
|
|
|
((red & 0xf800) >> 1) |
|
|
|
|
((green & 0xf800) >> 6) | ((blue & 0xf800) >> 11);
|
|
|
|
} else {
|
|
|
|
/* 0:5:6:5 */
|
|
|
|
((u32 *) (info->pseudo_palette))[regno] =
|
|
|
|
((red & 0xf800)) |
|
|
|
|
((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:26 +03:00
|
|
|
/*
|
|
|
|
* It's common for several clients to have framebuffer open simultaneously.
|
|
|
|
* e.g. both fbcon and X. Makes things interesting.
|
2010-09-06 03:35:19 +04:00
|
|
|
* Assumes caller is holding info->lock (for open and release at least)
|
2010-02-15 17:46:26 +03:00
|
|
|
*/
|
|
|
|
static int dlfb_ops_open(struct fb_info *info, int user)
|
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2010-02-15 17:46:26 +03:00
|
|
|
|
2010-09-06 03:35:39 +04:00
|
|
|
/*
|
|
|
|
* fbcon aggressively connects to first framebuffer it finds,
|
|
|
|
* preventing other clients (X) from working properly. Usually
|
|
|
|
* not what the user wants. Fail by default with option to enable.
|
|
|
|
*/
|
2011-08-22 00:34:15 +04:00
|
|
|
if ((user == 0) && (!console))
|
2010-09-06 03:35:39 +04:00
|
|
|
return -EBUSY;
|
2010-02-15 17:46:26 +03:00
|
|
|
|
2010-09-06 03:35:19 +04:00
|
|
|
/* If the USB device is gone, we don't accept new opens */
|
2018-01-15 19:04:22 +03:00
|
|
|
if (dlfb->virtualized)
|
2010-09-06 03:35:19 +04:00
|
|
|
return -ENODEV;
|
2010-02-15 17:46:26 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->fb_count++;
|
2010-02-15 17:46:26 +03:00
|
|
|
|
2010-09-06 03:35:39 +04:00
|
|
|
if (fb_defio && (info->fbdefio == NULL)) {
|
2010-09-06 05:28:29 +04:00
|
|
|
/* enable defio at last moment if not disabled by client */
|
|
|
|
|
|
|
|
struct fb_deferred_io *fbdefio;
|
|
|
|
|
2012-04-28 14:19:10 +04:00
|
|
|
fbdefio = kzalloc(sizeof(struct fb_deferred_io), GFP_KERNEL);
|
2010-09-06 05:28:29 +04:00
|
|
|
|
|
|
|
if (fbdefio) {
|
|
|
|
fbdefio->delay = DL_DEFIO_WRITE_DELAY;
|
|
|
|
fbdefio->deferred_io = dlfb_dpy_deferred_io;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->fbdefio = fbdefio;
|
2010-02-15 17:46:26 +03:00
|
|
|
fb_deferred_io_init(info);
|
|
|
|
}
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_dbg(info->dev, "open, user=%d fb_info=%p count=%d\n",
|
|
|
|
user, info, dlfb->fb_count);
|
2010-02-15 17:46:26 +03:00
|
|
|
|
2009-06-04 01:03:06 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
static void dlfb_ops_destroy(struct fb_info *info)
|
2010-02-15 17:45:55 +03:00
|
|
|
{
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
|
|
|
|
|
|
|
if (info->cmap.len != 0)
|
|
|
|
fb_dealloc_cmap(&info->cmap);
|
|
|
|
if (info->monspecs.modedb)
|
|
|
|
fb_destroy_modedb(info->monspecs.modedb);
|
|
|
|
vfree(info->screen_base);
|
|
|
|
|
|
|
|
fb_destroy_modelist(&info->modelist);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
while (!list_empty(&dlfb->deferred_free)) {
|
|
|
|
struct dlfb_deferred_free *d = list_entry(dlfb->deferred_free.next, struct dlfb_deferred_free, list);
|
|
|
|
list_del(&d->list);
|
|
|
|
vfree(d->mem);
|
|
|
|
kfree(d);
|
|
|
|
}
|
2018-01-15 19:04:22 +03:00
|
|
|
vfree(dlfb->backing_buffer);
|
|
|
|
kfree(dlfb->edid);
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
usb_put_dev(dlfb->udev);
|
2018-01-15 19:04:22 +03:00
|
|
|
kfree(dlfb);
|
2010-09-06 03:35:19 +04:00
|
|
|
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
/* Assume info structure is freed after this point */
|
|
|
|
framebuffer_release(info);
|
2010-09-06 03:35:19 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Assumes caller is holding info->lock mutex (for open and release at least)
|
|
|
|
*/
|
|
|
|
static int dlfb_ops_release(struct fb_info *info, int user)
|
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2010-09-06 03:35:19 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->fb_count--;
|
2010-09-06 03:35:19 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if ((dlfb->fb_count == 0) && (info->fbdefio)) {
|
2010-09-06 03:35:19 +04:00
|
|
|
fb_deferred_io_cleanup(info);
|
|
|
|
kfree(info->fbdefio);
|
|
|
|
info->fbdefio = NULL;
|
|
|
|
info->fbops->fb_mmap = dlfb_ops_mmap;
|
|
|
|
}
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_dbg(info->dev, "release, user=%d count=%d\n", user, dlfb->fb_count);
|
2010-09-06 03:35:19 +04:00
|
|
|
|
|
|
|
return 0;
|
2010-02-15 17:46:13 +03:00
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:08 +03:00
|
|
|
/*
|
|
|
|
* Check whether a video mode is supported by the DisplayLink chip
|
|
|
|
* We start from monitor's modes, so don't need to filter that here
|
|
|
|
*/
|
2018-01-16 18:35:20 +03:00
|
|
|
static int dlfb_is_valid_mode(struct fb_videomode *mode, struct dlfb_data *dlfb)
|
2010-02-15 17:46:08 +03:00
|
|
|
{
|
2018-01-16 18:35:20 +03:00
|
|
|
if (mode->xres * mode->yres > dlfb->sku_pixel_limit)
|
2010-02-15 17:46:08 +03:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dlfb_var_color_format(struct fb_var_screeninfo *var)
|
|
|
|
{
|
|
|
|
const struct fb_bitfield red = { 11, 5, 0 };
|
|
|
|
const struct fb_bitfield green = { 5, 6, 0 };
|
|
|
|
const struct fb_bitfield blue = { 0, 5, 0 };
|
|
|
|
|
|
|
|
var->bits_per_pixel = 16;
|
|
|
|
var->red = red;
|
|
|
|
var->green = green;
|
|
|
|
var->blue = blue;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
static int dlfb_ops_check_var(struct fb_var_screeninfo *var,
|
|
|
|
struct fb_info *info)
|
|
|
|
{
|
|
|
|
struct fb_videomode mode;
|
2018-01-16 18:35:20 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2010-02-15 17:46:13 +03:00
|
|
|
|
|
|
|
/* set device-specific elements of var unrelated to mode */
|
|
|
|
dlfb_var_color_format(var);
|
|
|
|
|
|
|
|
fb_var_to_videomode(&mode, var);
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
if (!dlfb_is_valid_mode(&mode, dlfb))
|
2010-02-15 17:46:13 +03:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dlfb_ops_set_par(struct fb_info *info)
|
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2010-09-06 03:35:23 +04:00
|
|
|
int result;
|
|
|
|
u16 *pix_framebuffer;
|
|
|
|
int i;
|
2018-07-25 16:41:55 +03:00
|
|
|
struct fb_var_screeninfo fvs;
|
2018-07-25 16:41:56 +03:00
|
|
|
u32 line_length = info->var.xres * (info->var.bits_per_pixel / 8);
|
2018-07-25 16:41:55 +03:00
|
|
|
|
|
|
|
/* clear the activate field because it causes spurious miscompares */
|
|
|
|
fvs = info->var;
|
|
|
|
fvs.activate = 0;
|
|
|
|
fvs.vmode &= ~FB_VMODE_SMOOTH_XPAN;
|
|
|
|
|
|
|
|
if (!memcmp(&dlfb->current_mode, &fvs, sizeof(struct fb_var_screeninfo)))
|
|
|
|
return 0;
|
2010-02-15 17:46:13 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
result = dlfb_realloc_framebuffer(dlfb, info, info->var.yres * line_length);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
result = dlfb_set_video_mode(dlfb, &info->var);
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-07-25 16:41:55 +03:00
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
dlfb->current_mode = fvs;
|
2018-07-25 16:41:56 +03:00
|
|
|
info->fix.line_length = line_length;
|
2018-07-25 16:41:55 +03:00
|
|
|
|
|
|
|
if (dlfb->fb_count == 0) {
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
/* paint greenscreen */
|
|
|
|
|
|
|
|
pix_framebuffer = (u16 *) info->screen_base;
|
|
|
|
for (i = 0; i < info->fix.smem_len / 2; i++)
|
|
|
|
pix_framebuffer[i] = 0x37e6;
|
|
|
|
}
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
dlfb_handle_damage(dlfb, 0, 0, info->var.xres, info->var.yres,
|
|
|
|
info->screen_base);
|
|
|
|
|
2018-07-25 16:41:55 +03:00
|
|
|
return 0;
|
2010-02-15 17:46:13 +03:00
|
|
|
}
|
|
|
|
|
2011-08-22 00:34:11 +04:00
|
|
|
/* To fonzi the jukebox (e.g. make blanking changes take effect) */
|
|
|
|
static char *dlfb_dummy_render(char *buf)
|
|
|
|
{
|
|
|
|
*buf++ = 0xAF;
|
|
|
|
*buf++ = 0x6A; /* copy */
|
|
|
|
*buf++ = 0x00; /* from address*/
|
|
|
|
*buf++ = 0x00;
|
|
|
|
*buf++ = 0x00;
|
|
|
|
*buf++ = 0x01; /* one pixel */
|
|
|
|
*buf++ = 0x00; /* to address */
|
|
|
|
*buf++ = 0x00;
|
|
|
|
*buf++ = 0x00;
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:10 +04:00
|
|
|
/*
|
|
|
|
* In order to come back from full DPMS off, we need to set the mode again
|
|
|
|
*/
|
2010-02-15 17:46:04 +03:00
|
|
|
static int dlfb_ops_blank(int blank_mode, struct fb_info *info)
|
2009-06-04 01:47:08 +04:00
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = info->par;
|
2011-08-22 00:34:11 +04:00
|
|
|
char *bufptr;
|
|
|
|
struct urb *urb;
|
2009-06-11 10:02:19 +04:00
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_dbg(info->dev, "blank, mode %d --> %d\n",
|
|
|
|
dlfb->blank_mode, blank_mode);
|
2010-02-15 17:46:21 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if ((dlfb->blank_mode == FB_BLANK_POWERDOWN) &&
|
2011-08-22 00:34:11 +04:00
|
|
|
(blank_mode != FB_BLANK_POWERDOWN)) {
|
2010-09-06 03:35:10 +04:00
|
|
|
|
2011-08-22 00:34:11 +04:00
|
|
|
/* returning from powerdown requires a fresh modeset */
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_set_video_mode(dlfb, &info->var);
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
urb = dlfb_get_urb(dlfb);
|
2011-08-22 00:34:11 +04:00
|
|
|
if (!urb)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bufptr = (char *) urb->transfer_buffer;
|
|
|
|
bufptr = dlfb_vidreg_lock(bufptr);
|
|
|
|
bufptr = dlfb_blanking(bufptr, blank_mode);
|
|
|
|
bufptr = dlfb_vidreg_unlock(bufptr);
|
|
|
|
|
|
|
|
/* seems like a render op is needed to have blank change take effect */
|
|
|
|
bufptr = dlfb_dummy_render(bufptr);
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_submit_urb(dlfb, urb, bufptr -
|
2011-08-22 00:34:11 +04:00
|
|
|
(char *) urb->transfer_buffer);
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->blank_mode = blank_mode;
|
2011-08-22 00:34:11 +04:00
|
|
|
|
2009-06-04 01:03:06 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct fb_ops dlfb_ops = {
|
2010-02-15 17:46:13 +03:00
|
|
|
.owner = THIS_MODULE,
|
2011-01-06 11:29:24 +03:00
|
|
|
.fb_read = fb_sys_read,
|
2010-09-06 03:35:04 +04:00
|
|
|
.fb_write = dlfb_ops_write,
|
2010-02-15 17:46:04 +03:00
|
|
|
.fb_setcolreg = dlfb_ops_setcolreg,
|
|
|
|
.fb_fillrect = dlfb_ops_fillrect,
|
|
|
|
.fb_copyarea = dlfb_ops_copyarea,
|
|
|
|
.fb_imageblit = dlfb_ops_imageblit,
|
|
|
|
.fb_mmap = dlfb_ops_mmap,
|
|
|
|
.fb_ioctl = dlfb_ops_ioctl,
|
2010-02-15 17:46:26 +03:00
|
|
|
.fb_open = dlfb_ops_open,
|
2010-02-15 17:46:04 +03:00
|
|
|
.fb_release = dlfb_ops_release,
|
|
|
|
.fb_blank = dlfb_ops_blank,
|
2010-02-15 17:46:13 +03:00
|
|
|
.fb_check_var = dlfb_ops_check_var,
|
|
|
|
.fb_set_par = dlfb_ops_set_par,
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
.fb_destroy = dlfb_ops_destroy,
|
2009-06-04 01:03:06 +04:00
|
|
|
};
|
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
static void dlfb_deferred_vfree(struct dlfb_data *dlfb, void *mem)
|
|
|
|
{
|
|
|
|
struct dlfb_deferred_free *d = kmalloc(sizeof(struct dlfb_deferred_free), GFP_KERNEL);
|
|
|
|
if (!d)
|
|
|
|
return;
|
|
|
|
d->mem = mem;
|
|
|
|
list_add(&d->list, &dlfb->deferred_free);
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:08 +03:00
|
|
|
/*
|
2010-09-06 03:35:23 +04:00
|
|
|
* Assumes &info->lock held by caller
|
|
|
|
* Assumes no active clients have framebuffer open
|
|
|
|
*/
|
2018-07-25 16:41:56 +03:00
|
|
|
static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info, u32 new_len)
|
2010-09-06 03:35:23 +04:00
|
|
|
{
|
2018-07-25 16:41:56 +03:00
|
|
|
u32 old_len = info->fix.smem_len;
|
|
|
|
const void *old_fb = (const void __force *)info->screen_base;
|
2010-09-06 03:35:23 +04:00
|
|
|
unsigned char *new_fb;
|
2013-09-25 15:29:49 +04:00
|
|
|
unsigned char *new_back = NULL;
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
new_len = PAGE_ALIGN(new_len);
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
if (new_len > old_len) {
|
2010-09-06 03:35:23 +04:00
|
|
|
/*
|
|
|
|
* Alloc system memory for virtual framebuffer
|
|
|
|
*/
|
|
|
|
new_fb = vmalloc(new_len);
|
|
|
|
if (!new_fb) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(info->dev, "Virtual framebuffer alloc failed\n");
|
2018-03-28 17:34:27 +03:00
|
|
|
return -ENOMEM;
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
2018-07-25 16:41:56 +03:00
|
|
|
memset(new_fb, 0xff, new_len);
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
if (info->screen_base) {
|
|
|
|
memcpy(new_fb, old_fb, old_len);
|
2018-07-25 16:41:56 +03:00
|
|
|
dlfb_deferred_vfree(dlfb, (void __force *)info->screen_base);
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
info->screen_base = (char __iomem *)new_fb;
|
|
|
|
info->fix.smem_len = new_len;
|
2010-09-06 03:35:23 +04:00
|
|
|
info->fix.smem_start = (unsigned long) new_fb;
|
|
|
|
info->flags = udlfb_info_flags;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Second framebuffer copy to mirror the framebuffer state
|
|
|
|
* on the physical USB device. We can function without this.
|
|
|
|
* But with imperfect damage info we may send pixels over USB
|
|
|
|
* that were, in fact, unchanged - wasting limited USB bandwidth
|
|
|
|
*/
|
2011-08-22 00:34:17 +04:00
|
|
|
if (shadow)
|
|
|
|
new_back = vzalloc(new_len);
|
2010-09-06 03:35:23 +04:00
|
|
|
if (!new_back)
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_info(info->dev,
|
|
|
|
"No shadow/backing buffer allocated\n");
|
2010-09-06 03:35:23 +04:00
|
|
|
else {
|
2018-07-25 16:41:56 +03:00
|
|
|
dlfb_deferred_vfree(dlfb, dlfb->backing_buffer);
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->backing_buffer = new_back;
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
|
|
|
}
|
2018-03-28 17:34:27 +03:00
|
|
|
return 0;
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 1) Get EDID from hw, or use sw default
|
|
|
|
* 2) Parse into various fb_info structs
|
|
|
|
* 3) Allocate virtual framebuffer memory to back highest res mode
|
|
|
|
*
|
|
|
|
* Parses EDID into three places used by various parts of fbdev:
|
2010-02-15 17:46:08 +03:00
|
|
|
* fb_var_screeninfo contains the timing of the monitor's preferred mode
|
|
|
|
* fb_info.monspecs is full parsed EDID info, including monspecs.modedb
|
|
|
|
* fb_info.modelist is a linked list of all monitor & VESA modes which work
|
|
|
|
*
|
|
|
|
* If EDID is not readable/valid, then modelist is all VESA modes,
|
|
|
|
* monspecs is NULL, and fb_var_screeninfo is set to safe VESA mode
|
2010-09-06 03:35:23 +04:00
|
|
|
* Returns 0 if successful
|
2010-02-15 17:46:08 +03:00
|
|
|
*/
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_setup_modes(struct dlfb_data *dlfb,
|
2010-09-06 03:35:23 +04:00
|
|
|
struct fb_info *info,
|
|
|
|
char *default_edid, size_t default_edid_size)
|
2010-02-15 17:46:08 +03:00
|
|
|
{
|
2010-09-06 03:35:23 +04:00
|
|
|
char *edid;
|
2018-01-16 18:35:20 +03:00
|
|
|
int i, result = 0, tries = 3;
|
|
|
|
struct device *dev = info->device;
|
|
|
|
struct fb_videomode *mode;
|
|
|
|
const struct fb_videomode *default_vmode = NULL;
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
if (info->dev) {
|
|
|
|
/* only use mutex if info has been registered */
|
2010-09-06 03:35:23 +04:00
|
|
|
mutex_lock(&info->lock);
|
2018-01-16 18:35:20 +03:00
|
|
|
/* parent device is used otherwise */
|
|
|
|
dev = info->dev;
|
|
|
|
}
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2011-01-06 12:04:02 +03:00
|
|
|
edid = kmalloc(EDID_LENGTH, GFP_KERNEL);
|
2010-09-06 03:35:23 +04:00
|
|
|
if (!edid) {
|
|
|
|
result = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
2010-02-15 17:46:08 +03:00
|
|
|
|
|
|
|
fb_destroy_modelist(&info->modelist);
|
|
|
|
memset(&info->monspecs, 0, sizeof(info->monspecs));
|
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
/*
|
|
|
|
* Try to (re)read EDID from hardware first
|
|
|
|
* EDID data may return, but not parse as valid
|
|
|
|
* Try again a few times, in case of e.g. analog cable noise
|
|
|
|
*/
|
|
|
|
while (tries--) {
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
i = dlfb_get_edid(dlfb, edid, EDID_LENGTH);
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2011-01-06 12:04:02 +03:00
|
|
|
if (i >= EDID_LENGTH)
|
2010-09-06 03:35:23 +04:00
|
|
|
fb_edid_to_monspecs(edid, &info->monspecs);
|
|
|
|
|
|
|
|
if (info->monspecs.modedb_len > 0) {
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->edid = edid;
|
|
|
|
dlfb->edid_size = i;
|
2010-09-06 03:35:23 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If that fails, use a previously returned EDID if available */
|
|
|
|
if (info->monspecs.modedb_len == 0) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(dev, "Unable to get valid EDID from device/display\n");
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (dlfb->edid) {
|
|
|
|
fb_edid_to_monspecs(dlfb->edid, &info->monspecs);
|
2010-09-06 03:35:23 +04:00
|
|
|
if (info->monspecs.modedb_len > 0)
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(dev, "Using previously queried EDID\n");
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If that fails, use the default EDID we were handed */
|
|
|
|
if (info->monspecs.modedb_len == 0) {
|
2011-01-06 12:04:02 +03:00
|
|
|
if (default_edid_size >= EDID_LENGTH) {
|
2010-09-06 03:35:23 +04:00
|
|
|
fb_edid_to_monspecs(default_edid, &info->monspecs);
|
|
|
|
if (info->monspecs.modedb_len > 0) {
|
|
|
|
memcpy(edid, default_edid, default_edid_size);
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->edid = edid;
|
|
|
|
dlfb->edid_size = default_edid_size;
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(dev, "Using default/backup EDID\n");
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-15 17:46:08 +03:00
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
/* If we've got modes, let's pick a best default mode */
|
2010-02-15 17:46:08 +03:00
|
|
|
if (info->monspecs.modedb_len > 0) {
|
|
|
|
|
|
|
|
for (i = 0; i < info->monspecs.modedb_len; i++) {
|
2018-01-16 18:35:20 +03:00
|
|
|
mode = &info->monspecs.modedb[i];
|
|
|
|
if (dlfb_is_valid_mode(mode, dlfb)) {
|
|
|
|
fb_add_videomode(mode, &info->modelist);
|
|
|
|
} else {
|
|
|
|
dev_dbg(dev, "Specified mode %dx%d too big\n",
|
|
|
|
mode->xres, mode->yres);
|
2011-06-23 17:16:29 +04:00
|
|
|
if (i == 0)
|
|
|
|
/* if we've removed top/best mode */
|
|
|
|
info->monspecs.misc
|
|
|
|
&= ~FB_MISC_1ST_DETAIL;
|
|
|
|
}
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
default_vmode = fb_find_best_display(&info->monspecs,
|
|
|
|
&info->modelist);
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
2010-02-15 17:46:08 +03:00
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
/* If everything else has failed, fall back to safe default mode */
|
|
|
|
if (default_vmode == NULL) {
|
|
|
|
|
|
|
|
struct fb_videomode fb_vmode = {0};
|
2010-02-15 17:46:08 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the standard VESA modes to our modelist
|
|
|
|
* Since we don't have EDID, there may be modes that
|
|
|
|
* overspec monitor and/or are incorrect aspect ratio, etc.
|
|
|
|
* But at least the user has a chance to choose
|
|
|
|
*/
|
|
|
|
for (i = 0; i < VESA_MODEDB_SIZE; i++) {
|
2018-01-16 18:35:20 +03:00
|
|
|
mode = (struct fb_videomode *)&vesa_modes[i];
|
|
|
|
if (dlfb_is_valid_mode(mode, dlfb))
|
|
|
|
fb_add_videomode(mode, &info->modelist);
|
|
|
|
else
|
|
|
|
dev_dbg(dev, "VESA mode %dx%d too big\n",
|
|
|
|
mode->xres, mode->yres);
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* default to resolution safe for projectors
|
|
|
|
* (since they are most common case without EDID)
|
|
|
|
*/
|
|
|
|
fb_vmode.xres = 800;
|
|
|
|
fb_vmode.yres = 600;
|
|
|
|
fb_vmode.refresh = 60;
|
|
|
|
default_vmode = fb_find_nearest_mode(&fb_vmode,
|
|
|
|
&info->modelist);
|
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
/* If we have good mode and no active clients*/
|
2018-01-15 19:04:22 +03:00
|
|
|
if ((default_vmode != NULL) && (dlfb->fb_count == 0)) {
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
fb_videomode_to_var(&info->var, default_vmode);
|
|
|
|
dlfb_var_color_format(&info->var);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* with mode size info, we can now alloc our framebuffer.
|
|
|
|
*/
|
|
|
|
memcpy(&info->fix, &dlfb_fix, sizeof(dlfb_fix));
|
|
|
|
} else
|
|
|
|
result = -EINVAL;
|
|
|
|
|
|
|
|
error:
|
2018-01-15 19:04:22 +03:00
|
|
|
if (edid && (dlfb->edid != edid))
|
2010-09-06 03:35:23 +04:00
|
|
|
kfree(edid);
|
|
|
|
|
|
|
|
if (info->dev)
|
|
|
|
mutex_unlock(&info->lock);
|
2010-02-15 17:46:08 +03:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t metrics_bytes_rendered_show(struct device *fbdev,
|
|
|
|
struct device_attribute *a, char *buf) {
|
|
|
|
struct fb_info *fb_info = dev_get_drvdata(fbdev);
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = fb_info->par;
|
2010-02-15 17:46:08 +03:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n",
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_read(&dlfb->bytes_rendered));
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t metrics_bytes_identical_show(struct device *fbdev,
|
|
|
|
struct device_attribute *a, char *buf) {
|
|
|
|
struct fb_info *fb_info = dev_get_drvdata(fbdev);
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = fb_info->par;
|
2010-02-15 17:46:08 +03:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n",
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_read(&dlfb->bytes_identical));
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t metrics_bytes_sent_show(struct device *fbdev,
|
|
|
|
struct device_attribute *a, char *buf) {
|
|
|
|
struct fb_info *fb_info = dev_get_drvdata(fbdev);
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = fb_info->par;
|
2010-02-15 17:46:08 +03:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n",
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_read(&dlfb->bytes_sent));
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t metrics_cpu_kcycles_used_show(struct device *fbdev,
|
|
|
|
struct device_attribute *a, char *buf) {
|
|
|
|
struct fb_info *fb_info = dev_get_drvdata(fbdev);
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = fb_info->par;
|
2010-02-15 17:46:08 +03:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n",
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_read(&dlfb->cpu_kcycles_used));
|
2010-02-15 17:46:08 +03:00
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
static ssize_t edid_show(
|
|
|
|
struct file *filp,
|
|
|
|
struct kobject *kobj, struct bin_attribute *a,
|
2010-02-15 17:46:08 +03:00
|
|
|
char *buf, loff_t off, size_t count) {
|
|
|
|
struct device *fbdev = container_of(kobj, struct device, kobj);
|
|
|
|
struct fb_info *fb_info = dev_get_drvdata(fbdev);
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = fb_info->par;
|
2010-02-15 17:46:08 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (dlfb->edid == NULL)
|
2010-02-15 17:46:08 +03:00
|
|
|
return 0;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if ((off >= dlfb->edid_size) || (count > dlfb->edid_size))
|
2010-02-15 17:46:08 +03:00
|
|
|
return 0;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (off + count > dlfb->edid_size)
|
|
|
|
count = dlfb->edid_size - off;
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
memcpy(buf, dlfb->edid, count);
|
2010-02-15 17:46:08 +03:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:31 +04:00
|
|
|
static ssize_t edid_store(
|
|
|
|
struct file *filp,
|
|
|
|
struct kobject *kobj, struct bin_attribute *a,
|
|
|
|
char *src, loff_t src_off, size_t src_size) {
|
|
|
|
struct device *fbdev = container_of(kobj, struct device, kobj);
|
|
|
|
struct fb_info *fb_info = dev_get_drvdata(fbdev);
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = fb_info->par;
|
2012-02-29 11:06:40 +04:00
|
|
|
int ret;
|
2010-09-06 03:35:31 +04:00
|
|
|
|
|
|
|
/* We only support write of entire EDID at once, no offset*/
|
2011-01-06 12:04:02 +03:00
|
|
|
if ((src_size != EDID_LENGTH) || (src_off != 0))
|
2012-02-29 11:06:40 +04:00
|
|
|
return -EINVAL;
|
2010-09-06 03:35:31 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
ret = dlfb_setup_modes(dlfb, fb_info, src, src_size);
|
2012-02-29 11:06:40 +04:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2010-09-06 03:35:31 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
if (!dlfb->edid || memcmp(src, dlfb->edid, src_size))
|
2012-02-29 11:06:40 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
ret = dlfb_ops_set_par(fb_info);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2012-02-29 11:06:40 +04:00
|
|
|
return src_size;
|
2010-09-06 03:35:31 +04:00
|
|
|
}
|
2010-02-15 17:46:08 +03:00
|
|
|
|
|
|
|
static ssize_t metrics_reset_store(struct device *fbdev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct fb_info *fb_info = dev_get_drvdata(fbdev);
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = fb_info->par;
|
2010-02-15 17:46:08 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_set(&dlfb->bytes_rendered, 0);
|
|
|
|
atomic_set(&dlfb->bytes_identical, 0);
|
|
|
|
atomic_set(&dlfb->bytes_sent, 0);
|
|
|
|
atomic_set(&dlfb->cpu_kcycles_used, 0);
|
2010-02-15 17:46:08 +03:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2017-08-18 20:56:40 +03:00
|
|
|
static const struct bin_attribute edid_attr = {
|
2010-02-15 17:46:08 +03:00
|
|
|
.attr.name = "edid",
|
2010-09-06 03:35:31 +04:00
|
|
|
.attr.mode = 0666,
|
2011-01-06 12:04:02 +03:00
|
|
|
.size = EDID_LENGTH,
|
2010-02-15 17:46:08 +03:00
|
|
|
.read = edid_show,
|
2010-09-06 03:35:31 +04:00
|
|
|
.write = edid_store
|
2010-02-15 17:46:08 +03:00
|
|
|
};
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
static const struct device_attribute fb_device_attrs[] = {
|
2010-02-15 17:46:08 +03:00
|
|
|
__ATTR_RO(metrics_bytes_rendered),
|
|
|
|
__ATTR_RO(metrics_bytes_identical),
|
|
|
|
__ATTR_RO(metrics_bytes_sent),
|
|
|
|
__ATTR_RO(metrics_cpu_kcycles_used),
|
2010-11-18 22:21:04 +03:00
|
|
|
__ATTR(metrics_reset, S_IWUSR, NULL, metrics_reset_store),
|
2010-02-15 17:46:08 +03:00
|
|
|
};
|
|
|
|
|
2010-02-15 17:45:49 +03:00
|
|
|
/*
|
|
|
|
* This is necessary before we can communicate with the display controller.
|
|
|
|
*/
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_select_std_channel(struct dlfb_data *dlfb)
|
2010-02-15 17:45:49 +03:00
|
|
|
{
|
|
|
|
int ret;
|
2017-05-02 14:47:53 +03:00
|
|
|
void *buf;
|
|
|
|
static const u8 set_def_chn[] = {
|
|
|
|
0x57, 0xCD, 0xDC, 0xA7,
|
2010-02-15 17:45:49 +03:00
|
|
|
0x1C, 0x88, 0x5E, 0x15,
|
|
|
|
0x60, 0xFE, 0xC6, 0x97,
|
|
|
|
0x16, 0x3D, 0x47, 0xF2 };
|
|
|
|
|
2017-05-02 14:47:53 +03:00
|
|
|
buf = kmemdup(set_def_chn, sizeof(set_def_chn), GFP_KERNEL);
|
|
|
|
|
|
|
|
if (!buf)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
ret = usb_control_msg(dlfb->udev, usb_sndctrlpipe(dlfb->udev, 0),
|
2010-02-15 17:45:49 +03:00
|
|
|
NR_USB_REQUEST_CHANNEL,
|
|
|
|
(USB_DIR_OUT | USB_TYPE_VENDOR), 0, 0,
|
2017-05-02 14:47:53 +03:00
|
|
|
buf, sizeof(set_def_chn), USB_CTRL_SET_TIMEOUT);
|
|
|
|
|
|
|
|
kfree(buf);
|
|
|
|
|
2010-02-15 17:45:49 +03:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_parse_vendor_descriptor(struct dlfb_data *dlfb,
|
2018-01-16 18:35:20 +03:00
|
|
|
struct usb_interface *intf)
|
2010-09-06 03:35:23 +04:00
|
|
|
{
|
|
|
|
char *desc;
|
|
|
|
char *buf;
|
|
|
|
char *desc_end;
|
2015-06-08 02:02:47 +03:00
|
|
|
int total_len;
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
buf = kzalloc(MAX_VENDOR_DESCRIPTOR_SIZE, GFP_KERNEL);
|
|
|
|
if (!buf)
|
|
|
|
return false;
|
|
|
|
desc = buf;
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
total_len = usb_get_descriptor(interface_to_usbdev(intf),
|
2011-08-22 00:34:13 +04:00
|
|
|
0x5f, /* vendor specific */
|
|
|
|
0, desc, MAX_VENDOR_DESCRIPTOR_SIZE);
|
|
|
|
|
|
|
|
/* if not found, look in configuration descriptor */
|
|
|
|
if (total_len < 0) {
|
2018-01-16 18:35:20 +03:00
|
|
|
if (0 == usb_get_extra_descriptor(intf->cur_altsetting,
|
2011-08-22 00:34:13 +04:00
|
|
|
0x5f, &desc))
|
|
|
|
total_len = (int) desc[0];
|
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
if (total_len > 5) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_info(&intf->dev,
|
|
|
|
"vendor descriptor length: %d data: %11ph\n",
|
|
|
|
total_len, desc);
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
if ((desc[0] != total_len) || /* descriptor length */
|
|
|
|
(desc[1] != 0x5f) || /* vendor descriptor type */
|
|
|
|
(desc[2] != 0x01) || /* version (2 bytes) */
|
|
|
|
(desc[3] != 0x00) ||
|
|
|
|
(desc[4] != total_len - 2)) /* length after type */
|
|
|
|
goto unrecognized;
|
|
|
|
|
|
|
|
desc_end = desc + total_len;
|
|
|
|
desc += 5; /* the fixed header we've already parsed */
|
|
|
|
|
|
|
|
while (desc < desc_end) {
|
|
|
|
u8 length;
|
|
|
|
u16 key;
|
|
|
|
|
2018-03-12 19:06:53 +03:00
|
|
|
key = *desc++;
|
|
|
|
key |= (u16)*desc++ << 8;
|
|
|
|
length = *desc++;
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
case 0x0200: { /* max_area */
|
2018-03-12 19:06:53 +03:00
|
|
|
u32 max_area = *desc++;
|
|
|
|
max_area |= (u32)*desc++ << 8;
|
|
|
|
max_area |= (u32)*desc++ << 16;
|
|
|
|
max_area |= (u32)*desc++ << 24;
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_warn(&intf->dev,
|
|
|
|
"DL chip limited to %d pixel modes\n",
|
|
|
|
max_area);
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->sku_pixel_limit = max_area;
|
2010-09-06 03:35:23 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
desc += length;
|
|
|
|
}
|
2011-08-22 00:34:13 +04:00
|
|
|
} else {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_info(&intf->dev, "vendor descriptor not available (%d)\n",
|
|
|
|
total_len);
|
2010-09-06 03:35:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
goto success;
|
2010-02-15 17:46:13 +03:00
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
unrecognized:
|
|
|
|
/* allow udlfb to load for now even if firmware unrecognized */
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(&intf->dev, "Unrecognized vendor firmware descriptor\n");
|
2010-09-06 03:35:23 +04:00
|
|
|
|
|
|
|
success:
|
|
|
|
kfree(buf);
|
|
|
|
return true;
|
|
|
|
}
|
2012-03-02 05:35:48 +04:00
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
static int dlfb_usb_probe(struct usb_interface *intf,
|
|
|
|
const struct usb_device_id *id)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
int i;
|
|
|
|
const struct device_attribute *attr;
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb;
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
struct fb_info *info;
|
2009-11-25 02:52:21 +03:00
|
|
|
int retval = -ENOMEM;
|
2018-01-16 18:35:20 +03:00
|
|
|
struct usb_device *usbdev = interface_to_usbdev(intf);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
/* usb initialization */
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb = kzalloc(sizeof(*dlfb), GFP_KERNEL);
|
2018-01-16 18:35:20 +03:00
|
|
|
if (!dlfb) {
|
|
|
|
dev_err(&intf->dev, "%s: failed to allocate dlfb\n", __func__);
|
2010-02-15 17:46:13 +03:00
|
|
|
goto error;
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
INIT_LIST_HEAD(&dlfb->deferred_free);
|
2010-02-15 17:46:13 +03:00
|
|
|
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
dlfb->udev = usb_get_dev(usbdev);
|
2018-01-16 18:35:20 +03:00
|
|
|
usb_set_intfdata(intf, dlfb);
|
|
|
|
|
|
|
|
dev_dbg(&intf->dev, "console enable=%d\n", console);
|
|
|
|
dev_dbg(&intf->dev, "fb_defio enable=%d\n", fb_defio);
|
|
|
|
dev_dbg(&intf->dev, "shadow enable=%d\n", shadow);
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->sku_pixel_limit = 2048 * 1152; /* default to maximum */
|
2010-09-06 03:35:23 +04:00
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
if (!dlfb_parse_vendor_descriptor(dlfb, intf)) {
|
|
|
|
dev_err(&intf->dev,
|
|
|
|
"firmware not recognized, incompatible device?\n");
|
2010-09-06 03:35:23 +04:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2012-03-04 00:57:37 +04:00
|
|
|
if (pixel_limit) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_warn(&intf->dev,
|
|
|
|
"DL chip limit of %d overridden to %d\n",
|
|
|
|
dlfb->sku_pixel_limit, pixel_limit);
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->sku_pixel_limit = pixel_limit;
|
2012-03-04 00:57:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
/* allocates framebuffer driver structure, not framebuffer memory */
|
2018-01-15 19:04:22 +03:00
|
|
|
info = framebuffer_alloc(0, &dlfb->udev->dev);
|
2010-02-15 17:46:13 +03:00
|
|
|
if (!info) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(&dlfb->udev->dev, "framebuffer_alloc failed\n");
|
2010-02-15 17:46:13 +03:00
|
|
|
goto error;
|
|
|
|
}
|
2010-09-06 03:35:19 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->info = info;
|
|
|
|
info->par = dlfb;
|
|
|
|
info->pseudo_palette = dlfb->pseudo_palette;
|
2018-07-25 16:41:55 +03:00
|
|
|
dlfb->ops = dlfb_ops;
|
|
|
|
info->fbops = &dlfb->ops;
|
2009-11-25 02:52:21 +03:00
|
|
|
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
INIT_LIST_HEAD(&info->modelist);
|
|
|
|
|
|
|
|
if (!dlfb_alloc_urb_list(dlfb, WRITES_IN_FLIGHT, MAX_TRANSFER)) {
|
|
|
|
retval = -ENOMEM;
|
|
|
|
dev_err(&intf->dev, "unable to allocate urb list\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We don't register a new USB class. Our client interface is dlfbev */
|
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
retval = fb_alloc_cmap(&info->cmap, 256, 0);
|
|
|
|
if (retval < 0) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(info->device, "cmap allocation failed: %d\n", retval);
|
2010-02-15 17:46:13 +03:00
|
|
|
goto error;
|
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
retval = dlfb_setup_modes(dlfb, info, NULL, 0);
|
2010-09-06 03:35:23 +04:00
|
|
|
if (retval != 0) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(info->device,
|
|
|
|
"unable to find common mode for display and adapter\n");
|
2010-09-06 03:35:23 +04:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
/* ready to begin using device */
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_set(&dlfb->usb_active, 1);
|
|
|
|
dlfb_select_std_channel(dlfb);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-09-06 03:35:23 +04:00
|
|
|
dlfb_ops_check_var(&info->var, info);
|
2018-07-25 16:41:56 +03:00
|
|
|
retval = dlfb_ops_set_par(info);
|
|
|
|
if (retval)
|
|
|
|
goto error;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
retval = register_framebuffer(info);
|
2009-11-25 02:52:21 +03:00
|
|
|
if (retval < 0) {
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(info->device, "unable to register framebuffer: %d\n",
|
|
|
|
retval);
|
2010-02-15 17:46:13 +03:00
|
|
|
goto error;
|
2009-06-11 10:02:19 +04:00
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2011-04-18 14:44:38 +04:00
|
|
|
for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) {
|
2018-01-16 18:35:20 +03:00
|
|
|
attr = &fb_device_attrs[i];
|
|
|
|
retval = device_create_file(info->dev, attr);
|
|
|
|
if (retval)
|
|
|
|
dev_warn(info->device,
|
|
|
|
"failed to create '%s' attribute: %d\n",
|
|
|
|
attr->attr.name, retval);
|
2011-04-18 14:44:38 +04:00
|
|
|
}
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2011-04-18 14:44:38 +04:00
|
|
|
retval = device_create_bin_file(info->dev, &edid_attr);
|
2018-01-16 18:35:20 +03:00
|
|
|
if (retval)
|
|
|
|
dev_warn(info->device, "failed to create '%s' attribute: %d\n",
|
|
|
|
edid_attr.attr.name, retval);
|
|
|
|
|
|
|
|
dev_info(info->device,
|
|
|
|
"%s is DisplayLink USB device (%dx%d, %dK framebuffer memory)\n",
|
|
|
|
dev_name(info->dev), info->var.xres, info->var.yres,
|
|
|
|
((dlfb->backing_buffer) ?
|
|
|
|
info->fix.smem_len * 2 : info->fix.smem_len) >> 10);
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
return 0;
|
2011-04-18 14:44:38 +04:00
|
|
|
|
2010-02-15 17:46:13 +03:00
|
|
|
error:
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
if (dlfb->info) {
|
|
|
|
dlfb_ops_destroy(dlfb->info);
|
|
|
|
} else if (dlfb) {
|
|
|
|
usb_put_dev(dlfb->udev);
|
|
|
|
kfree(dlfb);
|
|
|
|
}
|
|
|
|
return retval;
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
static void dlfb_usb_disconnect(struct usb_interface *intf)
|
2009-06-04 01:03:06 +04:00
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb;
|
2009-11-25 02:52:21 +03:00
|
|
|
struct fb_info *info;
|
2010-02-15 17:46:13 +03:00
|
|
|
int i;
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
dlfb = usb_get_intfdata(intf);
|
2018-01-15 19:04:22 +03:00
|
|
|
info = dlfb->info;
|
2010-02-15 17:46:13 +03:00
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_dbg(&intf->dev, "USB disconnect starting\n");
|
2010-02-15 17:46:13 +03:00
|
|
|
|
2010-09-06 03:35:19 +04:00
|
|
|
/* we virtualize until all fb clients release. Then we free */
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->virtualized = true;
|
2010-09-06 03:35:19 +04:00
|
|
|
|
|
|
|
/* When non-active we'll update virtual framebuffer, but no new urbs */
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_set(&dlfb->usb_active, 0);
|
2009-11-25 02:52:21 +03:00
|
|
|
|
2012-03-02 05:35:48 +04:00
|
|
|
/* this function will wait for all in-flight urbs to complete */
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb_free_urb_list(dlfb);
|
2012-03-02 05:35:48 +04:00
|
|
|
|
|
|
|
if (info) {
|
|
|
|
/* remove udlfb's sysfs interfaces */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++)
|
|
|
|
device_remove_file(info->dev, &fb_device_attrs[i]);
|
|
|
|
device_remove_bin_file(info->dev, &edid_attr);
|
|
|
|
}
|
|
|
|
|
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-10-08 13:57:34 +03:00
|
|
|
unregister_framebuffer(info);
|
2009-06-04 01:03:06 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct usb_driver dlfb_driver = {
|
|
|
|
.name = "udlfb",
|
2010-02-15 17:46:13 +03:00
|
|
|
.probe = dlfb_usb_probe,
|
|
|
|
.disconnect = dlfb_usb_disconnect,
|
2009-06-04 01:03:06 +04:00
|
|
|
.id_table = id_table,
|
|
|
|
};
|
|
|
|
|
2011-11-18 21:52:10 +04:00
|
|
|
module_usb_driver(dlfb_driver);
|
2009-06-04 01:03:06 +04:00
|
|
|
|
2010-02-15 17:45:55 +03:00
|
|
|
static void dlfb_urb_completion(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct urb_node *unode = urb->context;
|
2018-01-15 19:04:22 +03:00
|
|
|
struct dlfb_data *dlfb = unode->dlfb;
|
2010-02-15 17:45:55 +03:00
|
|
|
unsigned long flags;
|
|
|
|
|
2018-01-16 18:35:20 +03:00
|
|
|
switch (urb->status) {
|
|
|
|
case 0:
|
|
|
|
/* success */
|
|
|
|
break;
|
|
|
|
case -ECONNRESET:
|
|
|
|
case -ENOENT:
|
|
|
|
case -ESHUTDOWN:
|
|
|
|
/* sync/async unlink faults aren't errors */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(&dlfb->udev->dev,
|
|
|
|
"%s - nonzero write bulk status received: %d\n",
|
|
|
|
__func__, urb->status);
|
|
|
|
atomic_set(&dlfb->lost_pixels, 1);
|
|
|
|
break;
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
urb->transfer_buffer_length = dlfb->urbs.size; /* reset to actual */
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
spin_lock_irqsave(&dlfb->urbs.lock, flags);
|
|
|
|
list_add_tail(&unode->entry, &dlfb->urbs.list);
|
|
|
|
dlfb->urbs.available++;
|
|
|
|
spin_unlock_irqrestore(&dlfb->urbs.lock, flags);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
udlfb: fix semaphore value leak
I observed that the performance of the udl fb driver degrades over time.
On a freshly booted machine, it takes 6 seconds to do "ls -la /usr/bin";
after some time of use, the same operation takes 14 seconds.
The reason is that the value of "limit_sem" decays over time.
The udl driver uses a semaphore "limit_set" to specify how many free urbs
are there on dlfb->urbs.list. If the count is zero, the "down" operation
will sleep until some urbs are added to the freelist.
In order to avoid some hypothetical deadlock, the driver will not call
"up" immediately, but it will offload it to a workqueue. The problem is
that if we call "schedule_delayed_work" on the same work item multiple
times, the work item may only be executed once.
This is happening:
* some urb completes
* dlfb_urb_completion adds it to the free list
* dlfb_urb_completion calls schedule_delayed_work to schedule the function
dlfb_release_urb_work to increase the semaphore count
* as the urb is on the free list, some other task grabs it and submits it
* the submitted urb completes, dlfb_urb_completion is called again
* dlfb_urb_completion calls schedule_delayed_work, but the work is already
scheduled, so it does nothing
* finally, dlfb_release_urb_work is called, it increases the semaphore
count by 1, although it should increase it by 2
So, the semaphore count is decreasing over time, and this causes gradual
performance degradation.
Note that in the current kernel, the "up" function may be called from
interrupt and it may race with the "down" function called by another
thread, so we don't have to offload the call of "up" to a workqueue at
all. This patch removes the workqueue code. The patch also changes
"down_interruptible" to "down" in dlfb_free_urb_list, so that we will
clean up the driver properly even if a signal arrives.
With this patch, the performance of udlfb no longer degrades.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org
[b.zolnierkie: fix immediatelly -> immediately typo]
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-07-25 16:41:54 +03:00
|
|
|
up(&dlfb->urbs.limit_sem);
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
static void dlfb_free_urb_list(struct dlfb_data *dlfb)
|
2010-02-15 17:45:55 +03:00
|
|
|
{
|
2018-01-15 19:04:22 +03:00
|
|
|
int count = dlfb->urbs.count;
|
2010-02-15 17:45:55 +03:00
|
|
|
struct list_head *node;
|
|
|
|
struct urb_node *unode;
|
|
|
|
struct urb *urb;
|
|
|
|
|
|
|
|
/* keep waiting and freeing, until we've got 'em all */
|
|
|
|
while (count--) {
|
udlfb: fix semaphore value leak
I observed that the performance of the udl fb driver degrades over time.
On a freshly booted machine, it takes 6 seconds to do "ls -la /usr/bin";
after some time of use, the same operation takes 14 seconds.
The reason is that the value of "limit_sem" decays over time.
The udl driver uses a semaphore "limit_set" to specify how many free urbs
are there on dlfb->urbs.list. If the count is zero, the "down" operation
will sleep until some urbs are added to the freelist.
In order to avoid some hypothetical deadlock, the driver will not call
"up" immediately, but it will offload it to a workqueue. The problem is
that if we call "schedule_delayed_work" on the same work item multiple
times, the work item may only be executed once.
This is happening:
* some urb completes
* dlfb_urb_completion adds it to the free list
* dlfb_urb_completion calls schedule_delayed_work to schedule the function
dlfb_release_urb_work to increase the semaphore count
* as the urb is on the free list, some other task grabs it and submits it
* the submitted urb completes, dlfb_urb_completion is called again
* dlfb_urb_completion calls schedule_delayed_work, but the work is already
scheduled, so it does nothing
* finally, dlfb_release_urb_work is called, it increases the semaphore
count by 1, although it should increase it by 2
So, the semaphore count is decreasing over time, and this causes gradual
performance degradation.
Note that in the current kernel, the "up" function may be called from
interrupt and it may race with the "down" function called by another
thread, so we don't have to offload the call of "up" to a workqueue at
all. This patch removes the workqueue code. The patch also changes
"down_interruptible" to "down" in dlfb_free_urb_list, so that we will
clean up the driver properly even if a signal arrives.
With this patch, the performance of udlfb no longer degrades.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org
[b.zolnierkie: fix immediatelly -> immediately typo]
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
2018-07-25 16:41:54 +03:00
|
|
|
down(&dlfb->urbs.limit_sem);
|
2010-09-06 03:35:19 +04:00
|
|
|
|
2018-07-25 16:41:57 +03:00
|
|
|
spin_lock_irq(&dlfb->urbs.lock);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
node = dlfb->urbs.list.next; /* have reserved one with sem */
|
2010-02-15 17:45:55 +03:00
|
|
|
list_del_init(node);
|
|
|
|
|
2018-07-25 16:41:57 +03:00
|
|
|
spin_unlock_irq(&dlfb->urbs.lock);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
|
|
|
unode = list_entry(node, struct urb_node, entry);
|
|
|
|
urb = unode->urb;
|
|
|
|
|
|
|
|
/* Free each separately allocated piece */
|
2018-01-15 19:04:22 +03:00
|
|
|
usb_free_coherent(urb->dev, dlfb->urbs.size,
|
2010-04-30 02:36:29 +04:00
|
|
|
urb->transfer_buffer, urb->transfer_dma);
|
2010-02-15 17:45:55 +03:00
|
|
|
usb_free_urb(urb);
|
|
|
|
kfree(node);
|
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->urbs.count = 0;
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_alloc_urb_list(struct dlfb_data *dlfb, int count, size_t size)
|
2010-02-15 17:45:55 +03:00
|
|
|
{
|
|
|
|
struct urb *urb;
|
|
|
|
struct urb_node *unode;
|
|
|
|
char *buf;
|
2018-07-25 16:41:56 +03:00
|
|
|
size_t wanted_size = count * size;
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
spin_lock_init(&dlfb->urbs.lock);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
retry:
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->urbs.size = size;
|
|
|
|
INIT_LIST_HEAD(&dlfb->urbs.list);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
sema_init(&dlfb->urbs.limit_sem, 0);
|
|
|
|
dlfb->urbs.count = 0;
|
|
|
|
dlfb->urbs.available = 0;
|
|
|
|
|
|
|
|
while (dlfb->urbs.count * size < wanted_size) {
|
2017-12-29 21:48:44 +03:00
|
|
|
unode = kzalloc(sizeof(*unode), GFP_KERNEL);
|
2010-02-15 17:45:55 +03:00
|
|
|
if (!unode)
|
|
|
|
break;
|
2018-01-15 19:04:22 +03:00
|
|
|
unode->dlfb = dlfb;
|
2010-02-15 17:45:55 +03:00
|
|
|
|
|
|
|
urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!urb) {
|
|
|
|
kfree(unode);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
unode->urb = urb;
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
buf = usb_alloc_coherent(dlfb->udev, size, GFP_KERNEL,
|
2010-04-30 02:36:29 +04:00
|
|
|
&urb->transfer_dma);
|
2010-02-15 17:45:55 +03:00
|
|
|
if (!buf) {
|
|
|
|
kfree(unode);
|
|
|
|
usb_free_urb(urb);
|
2018-07-25 16:41:56 +03:00
|
|
|
if (size > PAGE_SIZE) {
|
|
|
|
size /= 2;
|
|
|
|
dlfb_free_urb_list(dlfb);
|
|
|
|
goto retry;
|
|
|
|
}
|
2010-02-15 17:45:55 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* urb->transfer_buffer_length set to actual before submit */
|
2018-01-15 19:04:22 +03:00
|
|
|
usb_fill_bulk_urb(urb, dlfb->udev, usb_sndbulkpipe(dlfb->udev, 1),
|
2010-02-15 17:45:55 +03:00
|
|
|
buf, size, dlfb_urb_completion, unode);
|
|
|
|
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
list_add_tail(&unode->entry, &dlfb->urbs.list);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
up(&dlfb->urbs.limit_sem);
|
|
|
|
dlfb->urbs.count++;
|
|
|
|
dlfb->urbs.available++;
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
|
2018-07-25 16:41:56 +03:00
|
|
|
return dlfb->urbs.count;
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
static struct urb *dlfb_get_urb(struct dlfb_data *dlfb)
|
2010-02-15 17:45:55 +03:00
|
|
|
{
|
2015-06-08 02:02:47 +03:00
|
|
|
int ret;
|
2010-02-15 17:45:55 +03:00
|
|
|
struct list_head *entry;
|
|
|
|
struct urb_node *unode;
|
|
|
|
|
|
|
|
/* Wait for an in-flight buffer to complete and get re-queued */
|
2018-01-15 19:04:22 +03:00
|
|
|
ret = down_timeout(&dlfb->urbs.limit_sem, GET_URB_TIMEOUT);
|
2010-02-15 17:45:55 +03:00
|
|
|
if (ret) {
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_set(&dlfb->lost_pixels, 1);
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_warn(&dlfb->udev->dev,
|
|
|
|
"wait for urb interrupted: %d available: %d\n",
|
|
|
|
ret, dlfb->urbs.available);
|
2018-01-15 19:04:21 +03:00
|
|
|
return NULL;
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
|
2018-07-25 16:41:57 +03:00
|
|
|
spin_lock_irq(&dlfb->urbs.lock);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
BUG_ON(list_empty(&dlfb->urbs.list)); /* reserved one with limit_sem */
|
|
|
|
entry = dlfb->urbs.list.next;
|
2010-02-15 17:45:55 +03:00
|
|
|
list_del_init(entry);
|
2018-01-15 19:04:22 +03:00
|
|
|
dlfb->urbs.available--;
|
2010-02-15 17:45:55 +03:00
|
|
|
|
2018-07-25 16:41:57 +03:00
|
|
|
spin_unlock_irq(&dlfb->urbs.lock);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
|
|
|
unode = list_entry(entry, struct urb_node, entry);
|
2018-01-15 19:04:21 +03:00
|
|
|
return unode->urb;
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
static int dlfb_submit_urb(struct dlfb_data *dlfb, struct urb *urb, size_t len)
|
2010-02-15 17:45:55 +03:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2018-01-15 19:04:22 +03:00
|
|
|
BUG_ON(len > dlfb->urbs.size);
|
2010-02-15 17:45:55 +03:00
|
|
|
|
|
|
|
urb->transfer_buffer_length = len; /* set to actual payload len */
|
|
|
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
|
|
|
if (ret) {
|
|
|
|
dlfb_urb_completion(urb); /* because no one else will */
|
2018-01-15 19:04:22 +03:00
|
|
|
atomic_set(&dlfb->lost_pixels, 1);
|
2018-01-16 18:35:20 +03:00
|
|
|
dev_err(&dlfb->udev->dev, "submit urb error: %d\n", ret);
|
2010-02-15 17:45:55 +03:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-09-06 03:35:39 +04:00
|
|
|
module_param(console, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
|
2011-08-22 00:35:39 +04:00
|
|
|
MODULE_PARM_DESC(console, "Allow fbcon to open framebuffer");
|
2010-09-06 03:35:39 +04:00
|
|
|
|
|
|
|
module_param(fb_defio, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
|
2011-08-22 00:35:38 +04:00
|
|
|
MODULE_PARM_DESC(fb_defio, "Page fault detection of mmap writes");
|
2010-09-06 03:35:39 +04:00
|
|
|
|
2011-08-22 00:34:17 +04:00
|
|
|
module_param(shadow, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
|
|
|
|
MODULE_PARM_DESC(shadow, "Shadow vid mem. Disable to save mem but lose perf");
|
|
|
|
|
2012-03-04 00:57:37 +04:00
|
|
|
module_param(pixel_limit, int, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
|
|
|
|
MODULE_PARM_DESC(pixel_limit, "Force limit on max mode (in x*y pixels)");
|
|
|
|
|
2009-11-25 02:52:21 +03:00
|
|
|
MODULE_AUTHOR("Roberto De Ioris <roberto@unbit.it>, "
|
2010-02-15 17:46:13 +03:00
|
|
|
"Jaya Kumar <jayakumar.lkml@gmail.com>, "
|
|
|
|
"Bernie Thompson <bernie@plugable.com>");
|
|
|
|
MODULE_DESCRIPTION("DisplayLink kernel framebuffer driver");
|
2009-06-04 01:03:06 +04:00
|
|
|
MODULE_LICENSE("GPL");
|
2010-02-15 17:46:13 +03:00
|
|
|
|