root/drivers/video/fbdev/udlfb.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * 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>
 * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
 *
 * 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.
 */

#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>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/unaligned.h>
#include <video/udlfb.h>
#include "edid.h"

#define OUT_EP_NUM      1       /* The endpoint number we will use */

static const struct fb_fix_screeninfo dlfb_fix = {
        .id =           "udlfb",
        .type =         FB_TYPE_PACKED_PIXELS,
        .visual =       FB_VISUAL_TRUECOLOR,
        .xpanstep =     0,
        .ypanstep =     0,
        .ywrapstep =    0,
        .accel =        FB_ACCEL_NONE,
};

static const u32 udlfb_info_flags = FBINFO_READS_FAST |
                FBINFO_VIRTFB |
                FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT |
                FBINFO_HWACCEL_COPYAREA | FBINFO_MISC_ALWAYS_SETPAR;

/*
 * 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
 */
static const struct usb_device_id id_table[] = {
        {.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,
        },
        {},
};
MODULE_DEVICE_TABLE(usb, id_table);

/* module options */
static bool console = true; /* Allow fbcon to open framebuffer */
static bool fb_defio = true;  /* Detect mmap writes using page faults */
static bool shadow = true; /* Optionally disable shadow framebuffer */
static int pixel_limit; /* Optionally force a pixel resolution limit */

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);

/* dlfb keeps a list of urbs for efficient bulk transfers */
static void dlfb_urb_completion(struct urb *urb);
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);

/*
 * All DisplayLink bulk operations start with 0xAF, followed by specific code
 * All operations are written to buffers which then later get sent to device
 */
static char *dlfb_set_register(char *buf, u8 reg, u8 val)
{
        *buf++ = 0xAF;
        *buf++ = 0x20;
        *buf++ = reg;
        *buf++ = val;
        return buf;
}

static char *dlfb_vidreg_lock(char *buf)
{
        return dlfb_set_register(buf, 0xFF, 0x00);
}

static char *dlfb_vidreg_unlock(char *buf)
{
        return dlfb_set_register(buf, 0xFF, 0xFF);
}

/*
 * 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
 */
static char *dlfb_blanking(char *buf, int fb_blank)
{
        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;
}

static char *dlfb_set_color_depth(char *buf, u8 selection)
{
        return dlfb_set_register(buf, 0x00, selection);
}

static char *dlfb_set_base16bpp(char *wrptr, u32 base)
{
        /* the base pointer is 16 bits wide, 0x20 is hi byte. */
        wrptr = dlfb_set_register(wrptr, 0x20, base >> 16);
        wrptr = dlfb_set_register(wrptr, 0x21, base >> 8);
        return dlfb_set_register(wrptr, 0x22, base);
}

/*
 * DisplayLink HW has separate 16bpp and 8bpp framebuffers.
 * In 24bpp modes, the low 323 RGB bits go in the 8bpp framebuffer
 */
static char *dlfb_set_base8bpp(char *wrptr, u32 base)
{
        wrptr = dlfb_set_register(wrptr, 0x26, base >> 16);
        wrptr = dlfb_set_register(wrptr, 0x27, base >> 8);
        return dlfb_set_register(wrptr, 0x28, base);
}

static char *dlfb_set_register_16(char *wrptr, u8 reg, u16 value)
{
        wrptr = dlfb_set_register(wrptr, reg, value >> 8);
        return dlfb_set_register(wrptr, reg+1, value);
}

/*
 * This is kind of weird because the controller takes some
 * register values in a different byte order than other registers.
 */
static char *dlfb_set_register_16be(char *wrptr, u8 reg, u16 value)
{
        wrptr = dlfb_set_register(wrptr, reg, value);
        return dlfb_set_register(wrptr, reg+1, value >> 8);
}

/*
 * 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.
 */
static u16 dlfb_lfsr16(u16 actual_count)
{
        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;
        }

        return (u16) lv;
}

/*
 * This does LFSR conversion on the value that is to be written.
 * See LFSR explanation above for more detail.
 */
static char *dlfb_set_register_lfsr16(char *wrptr, u8 reg, u16 value)
{
        return dlfb_set_register_16(wrptr, reg, dlfb_lfsr16(value));
}

/*
 * This takes a standard fbdev screeninfo struct and all of its monitor mode
 * details and converts them into the DisplayLink equivalent register commands.
 */
static char *dlfb_set_vid_cmds(char *wrptr, struct fb_var_screeninfo *var)
{
        u16 xds, yds;
        u16 xde, yde;
        u16 yec;

        /* x display start */
        xds = var->left_margin + var->hsync_len;
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x01, xds);
        /* x display end */
        xde = xds + var->xres;
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x03, xde);

        /* y display start */
        yds = var->upper_margin + var->vsync_len;
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x05, yds);
        /* y display end */
        yde = yds + var->yres;
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x07, yde);

        /* x end count is active + blanking - 1 */
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x09,
                        xde + var->right_margin - 1);

        /* libdlo hardcodes hsync start to 1 */
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x0B, 1);

        /* hsync end is width of sync pulse + 1 */
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x0D, var->hsync_len + 1);

        /* hpixels is active pixels */
        wrptr = dlfb_set_register_16(wrptr, 0x0F, var->xres);

        /* yendcount is vertical active + vertical blanking */
        yec = var->yres + var->upper_margin + var->lower_margin +
                        var->vsync_len;
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x11, yec);

        /* libdlo hardcodes vsync start to 0 */
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x13, 0);

        /* vsync end is width of vsync pulse */
        wrptr = dlfb_set_register_lfsr16(wrptr, 0x15, var->vsync_len);

        /* vpixels is active pixels */
        wrptr = dlfb_set_register_16(wrptr, 0x17, var->yres);

        /* convert picoseconds to 5kHz multiple for pclk5k = x * 1E12/5k */
        wrptr = dlfb_set_register_16be(wrptr, 0x1B,
                        200*1000*1000/var->pixclock);

        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.
 */
static int dlfb_set_video_mode(struct dlfb_data *dlfb,
                                struct fb_var_screeninfo *var)
{
        char *buf;
        char *wrptr;
        int retval;
        int writesize;
        struct urb *urb;

        if (!atomic_read(&dlfb->usb_active))
                return -EPERM;

        urb = dlfb_get_urb(dlfb);
        if (!urb)
                return -ENOMEM;

        buf = (char *) urb->transfer_buffer;

        /*
        * 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.
        */
        wrptr = dlfb_vidreg_lock(buf);
        wrptr = dlfb_set_color_depth(wrptr, 0x00);
        /* set base for 16bpp segment to 0 */
        wrptr = dlfb_set_base16bpp(wrptr, 0);
        /* set base for 8bpp segment to end of fb */
        wrptr = dlfb_set_base8bpp(wrptr, dlfb->info->fix.smem_len);

        wrptr = dlfb_set_vid_cmds(wrptr, var);
        wrptr = dlfb_blanking(wrptr, FB_BLANK_UNBLANK);
        wrptr = dlfb_vidreg_unlock(wrptr);

        writesize = wrptr - buf;

        retval = dlfb_submit_urb(dlfb, urb, writesize);

        dlfb->blank_mode = FB_BLANK_UNBLANK;

        return retval;
}

static void dlfb_vm_open(struct vm_area_struct *vma)
{
        struct dlfb_data *dlfb = vma->vm_private_data;

        atomic_inc(&dlfb->mmap_count);
}

static void dlfb_vm_close(struct vm_area_struct *vma)
{
        struct dlfb_data *dlfb = vma->vm_private_data;

        atomic_dec(&dlfb->mmap_count);
}

static const struct vm_operations_struct dlfb_vm_ops = {
        .open  = dlfb_vm_open,
        .close = dlfb_vm_close,
};

static int dlfb_ops_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
        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;
        struct dlfb_data *dlfb = info->par;

        if (info->fbdefio)
                return fb_deferred_io_mmap(info, vma);

        vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);

        if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
                return -EINVAL;
        if (size > info->fix.smem_len)
                return -EINVAL;
        if (offset > info->fix.smem_len - size)
                return -EINVAL;

        pos = (unsigned long)info->fix.smem_start + offset;

        dev_dbg(info->dev, "mmap() framebuffer addr:%lu size:%lu\n",
                pos, size);

        while (size > 0) {
                page = vmalloc_to_pfn((void *)pos);
                if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED))
                        return -EAGAIN;

                start += PAGE_SIZE;
                pos += PAGE_SIZE;
                if (size > PAGE_SIZE)
                        size -= PAGE_SIZE;
                else
                        size = 0;
        }

        vma->vm_ops = &dlfb_vm_ops;
        vma->vm_private_data = dlfb;
        atomic_inc(&dlfb->mmap_count);
        return 0;
}

/*
 * 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)
{
        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;
        int start = width;
        int end = width;

        for (j = 0; j < width; j++) {
                if (back[j] != front[j]) {
                        start = j;
                        break;
                }
        }

        for (k = width - 1; k > j; k--) {
                if (back[k] != front[k]) {
                        end = k+1;
                        break;
                }
        }

        identical = start + (width - end);
        *bfront = (u8 *) &front[start];
        *width_bytes = (end - start) * sizeof(unsigned long);

        return identical * sizeof(unsigned long);
}

/*
 * 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
 *
 * 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
 * compression than 256 pixel raw or rle commands, with similar CPU consumpion.
 * But for very rl friendly data, will compress not quite as well.
 */
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,
        const uint8_t *const cmd_buffer_end,
        unsigned long back_buffer_offset,
        int *ident_ptr)
{
        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)) {
                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;

                if (back_buffer_offset &&
                    *pixel == *(u16 *)((u8 *)pixel + back_buffer_offset)) {
                        pixel++;
                        dev_addr += BPP;
                        (*ident_ptr)++;
                        continue;
                }

                *cmd++ = 0xAF;
                *cmd++ = 0x6B;
                *cmd++ = dev_addr >> 16;
                *cmd++ = dev_addr >> 8;
                *cmd++ = dev_addr;

                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;

                cmd_pixel_end = pixel + min3(MAX_CMD_PIXELS + 1UL,
                                        (unsigned long)(pixel_end - pixel),
                                        (unsigned long)(cmd_buffer_end - 1 - cmd) / BPP);

                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--;
                }

                while (pixel < cmd_pixel_end) {
                        const uint16_t * const repeating_pixel = pixel;
                        u16 pixel_value = *pixel;

                        put_unaligned_be16(pixel_value, cmd);
                        if (back_buffer_offset)
                                *(u16 *)((u8 *)pixel + back_buffer_offset) = pixel_value;
                        cmd += 2;
                        pixel++;

                        if (unlikely((pixel < cmd_pixel_end) &&
                                     (*pixel == pixel_value))) {
                                /* go back and fill in raw pixel count */
                                *raw_pixels_count_byte = ((repeating_pixel -
                                                raw_pixel_start) + 1) & 0xFF;

                                do {
                                        if (back_buffer_offset)
                                                *(u16 *)((u8 *)pixel + back_buffer_offset) = pixel_value;
                                        pixel++;
                                } while ((pixel < cmd_pixel_end) &&
                                         (*pixel == pixel_value));

                                /* immediately after raw data is repeat byte */
                                *cmd++ = ((pixel - repeating_pixel) - 1) & 0xFF;

                                /* Then start another raw pixel span */
                                raw_pixel_start = pixel;
                                raw_pixels_count_byte = cmd++;
                        }
                }

                if (pixel > raw_pixel_start) {
                        /* finalize last RAW span */
                        *raw_pixels_count_byte = (pixel-raw_pixel_start) & 0xFF;
                } else {
                        /* undo unused byte */
                        cmd--;
                }

                *cmd_pixels_count_byte = (pixel - cmd_pixel_start) & 0xFF;
                dev_addr += (u8 *)pixel - (u8 *)cmd_pixel_start;
        }

        if (cmd_buffer_end - MIN_RLX_CMD_BYTES <= cmd) {
                /* Fill leftover bytes with no-ops */
                if (cmd_buffer_end > cmd)
                        memset(cmd, 0xAF, cmd_buffer_end - cmd);
                cmd = (uint8_t *) cmd_buffer_end;
        }

        *command_buffer_ptr = cmd;
        *pixel_start_ptr = pixel;
        *device_address_ptr = dev_addr;
}

/*
 * 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.
 */
static int dlfb_render_hline(struct dlfb_data *dlfb, struct urb **urb_ptr,
                              const char *front, char **urb_buf_ptr,
                              u32 byte_offset, u32 byte_width,
                              int *ident_ptr, int *sent_ptr)
{
        const u8 *line_start, *line_end, *next_pixel;
        u32 dev_addr = dlfb->base16 + byte_offset;
        struct urb *urb = *urb_ptr;
        u8 *cmd = *urb_buf_ptr;
        u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length;
        unsigned long back_buffer_offset = 0;

        line_start = (u8 *) (front + byte_offset);
        next_pixel = line_start;
        line_end = next_pixel + byte_width;

        if (dlfb->backing_buffer) {
                int offset;
                const u8 *back_start = (u8 *) (dlfb->backing_buffer
                                                + byte_offset);

                back_buffer_offset = (unsigned long)back_start - (unsigned long)line_start;

                *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;
        }

        while (next_pixel < line_end) {

                dlfb_compress_hline((const uint16_t **) &next_pixel,
                             (const uint16_t *) line_end, &dev_addr,
                        (u8 **) &cmd, (u8 *) cmd_end, back_buffer_offset,
                        ident_ptr);

                if (cmd >= cmd_end) {
                        int len = cmd - (u8 *) urb->transfer_buffer;
                        if (dlfb_submit_urb(dlfb, urb, len))
                                return 1; /* lost pixels is set */
                        *sent_ptr += len;
                        urb = dlfb_get_urb(dlfb);
                        if (!urb)
                                return 1; /* lost_pixels is set */
                        *urb_ptr = urb;
                        cmd = urb->transfer_buffer;
                        cmd_end = &cmd[urb->transfer_buffer_length];
                }
        }

        *urb_buf_ptr = cmd;

        return 0;
}

static int dlfb_handle_damage(struct dlfb_data *dlfb, int x, int y, int width, int height)
{
        int i, ret;
        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();

        mutex_lock(&dlfb->render_mutex);

        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) ||
            (x + width > dlfb->info->var.xres) ||
            (y + height > dlfb->info->var.yres)) {
                ret = -EINVAL;
                goto unlock_ret;
        }

        if (!atomic_read(&dlfb->usb_active)) {
                ret = 0;
                goto unlock_ret;
        }

        urb = dlfb_get_urb(dlfb);
        if (!urb) {
                ret = 0;
                goto unlock_ret;
        }
        cmd = urb->transfer_buffer;

        for (i = y; i < y + height ; i++) {
                const int line_offset = dlfb->info->fix.line_length * i;
                const int byte_offset = line_offset + (x * BPP);

                if (dlfb_render_hline(dlfb, &urb,
                                      (char *) dlfb->info->fix.smem_start,
                                      &cmd, byte_offset, width * BPP,
                                      &bytes_identical, &bytes_sent))
                        goto error;
        }

        if (cmd > (char *) urb->transfer_buffer) {
                int len;
                if (cmd < (char *) urb->transfer_buffer + urb->transfer_buffer_length)
                        *cmd++ = 0xAF;
                /* Send partial buffer remaining before exiting */
                len = cmd - (char *) urb->transfer_buffer;
                dlfb_submit_urb(dlfb, urb, len);
                bytes_sent += len;
        } else
                dlfb_urb_completion(urb);

error:
        atomic_add(bytes_sent, &dlfb->bytes_sent);
        atomic_add(bytes_identical, &dlfb->bytes_identical);
        atomic_add(width*height*2, &dlfb->bytes_rendered);
        end_cycles = get_cycles();
        atomic_add(((unsigned int) ((end_cycles - start_cycles)
                    >> 10)), /* Kcycles */
                   &dlfb->cpu_kcycles_used);

        ret = 0;

unlock_ret:
        mutex_unlock(&dlfb->render_mutex);
        return ret;
}

static void dlfb_init_damage(struct dlfb_data *dlfb)
{
        dlfb->damage_x = INT_MAX;
        dlfb->damage_x2 = 0;
        dlfb->damage_y = INT_MAX;
        dlfb->damage_y2 = 0;
}

static void dlfb_damage_work(struct work_struct *w)
{
        struct dlfb_data *dlfb = container_of(w, struct dlfb_data, damage_work);
        int x, x2, y, y2;

        spin_lock_irq(&dlfb->damage_lock);
        x = dlfb->damage_x;
        x2 = dlfb->damage_x2;
        y = dlfb->damage_y;
        y2 = dlfb->damage_y2;
        dlfb_init_damage(dlfb);
        spin_unlock_irq(&dlfb->damage_lock);

        if (x < x2 && y < y2)
                dlfb_handle_damage(dlfb, x, y, x2 - x, y2 - y);
}

static void dlfb_offload_damage(struct dlfb_data *dlfb, int x, int y, int width, int height)
{
        unsigned long flags;
        int x2 = x + width;
        int y2 = y + height;

        if (x >= x2 || y >= y2)
                return;

        spin_lock_irqsave(&dlfb->damage_lock, flags);
        dlfb->damage_x = min(x, dlfb->damage_x);
        dlfb->damage_x2 = max(x2, dlfb->damage_x2);
        dlfb->damage_y = min(y, dlfb->damage_y);
        dlfb->damage_y2 = max(y2, dlfb->damage_y2);
        spin_unlock_irqrestore(&dlfb->damage_lock, flags);

        schedule_work(&dlfb->damage_work);
}

/*
 * 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 *pagereflist)
{
        struct fb_deferred_io_pageref *pageref;
        struct dlfb_data *dlfb = info->par;
        struct urb *urb;
        char *cmd;
        cycles_t start_cycles, end_cycles;
        int bytes_sent = 0;
        int bytes_identical = 0;
        int bytes_rendered = 0;

        mutex_lock(&dlfb->render_mutex);

        if (!fb_defio)
                goto unlock_ret;

        if (!atomic_read(&dlfb->usb_active))
                goto unlock_ret;

        start_cycles = get_cycles();

        urb = dlfb_get_urb(dlfb);
        if (!urb)
                goto unlock_ret;

        cmd = urb->transfer_buffer;

        /* walk the written page list and render each to device */
        list_for_each_entry(pageref, pagereflist, list) {
                if (dlfb_render_hline(dlfb, &urb, (char *) info->fix.smem_start,
                                      &cmd, pageref->offset, PAGE_SIZE,
                                      &bytes_identical, &bytes_sent))
                        goto error;
                bytes_rendered += PAGE_SIZE;
        }

        if (cmd > (char *) urb->transfer_buffer) {
                int len;
                if (cmd < (char *) urb->transfer_buffer + urb->transfer_buffer_length)
                        *cmd++ = 0xAF;
                /* Send partial buffer remaining before exiting */
                len = cmd - (char *) urb->transfer_buffer;
                dlfb_submit_urb(dlfb, urb, len);
                bytes_sent += len;
        } else
                dlfb_urb_completion(urb);

error:
        atomic_add(bytes_sent, &dlfb->bytes_sent);
        atomic_add(bytes_identical, &dlfb->bytes_identical);
        atomic_add(bytes_rendered, &dlfb->bytes_rendered);
        end_cycles = get_cycles();
        atomic_add(((unsigned int) ((end_cycles - start_cycles)
                    >> 10)), /* Kcycles */
                   &dlfb->cpu_kcycles_used);
unlock_ret:
        mutex_unlock(&dlfb->render_mutex);
}

static int dlfb_get_edid(struct dlfb_data *dlfb, char *edid, int len)
{
        int i, ret;
        char *rbuf;

        rbuf = kmalloc(2, GFP_KERNEL);
        if (!rbuf)
                return 0;

        for (i = 0; i < len; i++) {
                ret = usb_control_msg(dlfb->udev,
                                      usb_rcvctrlpipe(dlfb->udev, 0), 0x02,
                                      (0x80 | (0x02 << 5)), i << 8, 0xA1,
                                      rbuf, 2, USB_CTRL_GET_TIMEOUT);
                if (ret < 2) {
                        dev_err(&dlfb->udev->dev,
                                "Read EDID byte %d failed: %d\n", i, ret);
                        i--;
                        break;
                }
                edid[i] = rbuf[1];
        }

        kfree(rbuf);

        return i;
}

static int dlfb_ops_ioctl(struct fb_info *info, unsigned int cmd,
                                unsigned long arg)
{

        struct dlfb_data *dlfb = info->par;

        if (!atomic_read(&dlfb->usb_active))
                return 0;

        /* TODO: Update X server to get this from sysfs instead */
        if (cmd == DLFB_IOCTL_RETURN_EDID) {
                void __user *edid = (void __user *)arg;
                if (copy_to_user(edid, dlfb->edid, dlfb->edid_size))
                        return -EFAULT;
                return 0;
        }

        /* TODO: Help propose a standard fb.h ioctl to report mmap damage */
        if (cmd == DLFB_IOCTL_REPORT_DAMAGE) {
                struct dloarea area;

                if (copy_from_user(&area, (void __user *)arg,
                                  sizeof(struct dloarea)))
                        return -EFAULT;

                /*
                 * If we have a damage-aware client, turn fb_defio "off"
                 * To avoid perf imact of unnecessary page fault handling.
                 * 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;

                if (area.x < 0)
                        area.x = 0;

                if (area.x > info->var.xres)
                        area.x = info->var.xres;

                if (area.y < 0)
                        area.y = 0;

                if (area.y > info->var.yres)
                        area.y = info->var.yres;

                dlfb_handle_damage(dlfb, area.x, area.y, area.w, area.h);
        }

        return 0;
}

/* taken from vesafb */
static int
dlfb_ops_setcolreg(unsigned regno, unsigned red, unsigned green,
               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;
}

/*
 * It's common for several clients to have framebuffer open simultaneously.
 * e.g. both fbcon and X. Makes things interesting.
 * Assumes caller is holding info->lock (for open and release at least)
 */
static int dlfb_ops_open(struct fb_info *info, int user)
{
        struct dlfb_data *dlfb = info->par;

        /*
         * 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.
         */
        if ((user == 0) && (!console))
                return -EBUSY;

        /* If the USB device is gone, we don't accept new opens */
        if (dlfb->virtualized)
                return -ENODEV;

        dlfb->fb_count++;

        if (fb_defio && (info->fbdefio == NULL)) {
                /* enable defio at last moment if not disabled by client */

                struct fb_deferred_io *fbdefio;

                fbdefio = kzalloc_obj(struct fb_deferred_io);

                if (fbdefio) {
                        fbdefio->delay = DL_DEFIO_WRITE_DELAY;
                        fbdefio->sort_pagereflist = true;
                        fbdefio->deferred_io = dlfb_dpy_deferred_io;
                }

                info->fbdefio = fbdefio;
                fb_deferred_io_init(info);
        }

        dev_dbg(info->dev, "open, user=%d fb_info=%p count=%d\n",
                user, info, dlfb->fb_count);

        return 0;
}

static void dlfb_ops_destroy(struct fb_info *info)
{
        struct dlfb_data *dlfb = info->par;

        cancel_work_sync(&dlfb->damage_work);

        mutex_destroy(&dlfb->render_mutex);

        if (info->cmap.len != 0)
                fb_dealloc_cmap(&info->cmap);
        if (info->monspecs.modedb)
                fb_destroy_modedb(info->monspecs.modedb);
        vfree(info->screen_buffer);

        fb_destroy_modelist(&info->modelist);

        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);
        }
        vfree(dlfb->backing_buffer);
        kfree(dlfb->edid);
        dlfb_free_urb_list(dlfb);
        usb_put_dev(dlfb->udev);
        kfree(dlfb);

        /* Assume info structure is freed after this point */
        framebuffer_release(info);
}

/*
 * Assumes caller is holding info->lock mutex (for open and release at least)
 */
static int dlfb_ops_release(struct fb_info *info, int user)
{
        struct dlfb_data *dlfb = info->par;

        dlfb->fb_count--;

        if ((dlfb->fb_count == 0) && (info->fbdefio)) {
                fb_deferred_io_cleanup(info);
                kfree(info->fbdefio);
                info->fbdefio = NULL;
        }

        dev_dbg(info->dev, "release, user=%d count=%d\n", user, dlfb->fb_count);

        return 0;
}

/*
 * 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
 */
static int dlfb_is_valid_mode(struct fb_videomode *mode, struct dlfb_data *dlfb)
{
        if (mode->xres * mode->yres > dlfb->sku_pixel_limit)
                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;
}

static int dlfb_ops_check_var(struct fb_var_screeninfo *var,
                                struct fb_info *info)
{
        struct fb_videomode mode;
        struct dlfb_data *dlfb = info->par;

        if (!var->pixclock)
                return -EINVAL;

        /* set device-specific elements of var unrelated to mode */
        dlfb_var_color_format(var);

        fb_var_to_videomode(&mode, var);

        if (!dlfb_is_valid_mode(&mode, dlfb))
                return -EINVAL;

        return 0;
}

static int dlfb_ops_set_par(struct fb_info *info)
{
        struct dlfb_data *dlfb = info->par;
        int result;
        u16 *pix_framebuffer;
        int i;
        struct fb_var_screeninfo fvs;
        u32 line_length = info->var.xres * (info->var.bits_per_pixel / 8);

        /* 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;

        result = dlfb_realloc_framebuffer(dlfb, info, info->var.yres * line_length);
        if (result)
                return result;

        result = dlfb_set_video_mode(dlfb, &info->var);

        if (result)
                return result;

        dlfb->current_mode = fvs;
        info->fix.line_length = line_length;

        if (dlfb->fb_count == 0) {

                /* paint greenscreen */

                pix_framebuffer = (u16 *)info->screen_buffer;
                for (i = 0; i < info->fix.smem_len / 2; i++)
                        pix_framebuffer[i] = 0x37e6;
        }

        dlfb_handle_damage(dlfb, 0, 0, info->var.xres, info->var.yres);

        return 0;
}

/* 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;
}

/*
 * In order to come back from full DPMS off, we need to set the mode again
 */
static int dlfb_ops_blank(int blank_mode, struct fb_info *info)
{
        struct dlfb_data *dlfb = info->par;
        char *bufptr;
        struct urb *urb;

        dev_dbg(info->dev, "blank, mode %d --> %d\n",
                dlfb->blank_mode, blank_mode);

        if ((dlfb->blank_mode == FB_BLANK_POWERDOWN) &&
            (blank_mode != FB_BLANK_POWERDOWN)) {

                /* returning from powerdown requires a fresh modeset */
                dlfb_set_video_mode(dlfb, &info->var);
        }

        urb = dlfb_get_urb(dlfb);
        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);

        dlfb_submit_urb(dlfb, urb, bufptr -
                        (char *) urb->transfer_buffer);

        dlfb->blank_mode = blank_mode;

        return 0;
}

static void dlfb_ops_damage_range(struct fb_info *info, off_t off, size_t len)
{
        struct dlfb_data *dlfb = info->par;
        int start = max((int)(off / info->fix.line_length), 0);
        int lines = min((u32)((len / info->fix.line_length) + 1), (u32)info->var.yres);

        dlfb_handle_damage(dlfb, 0, start, info->var.xres, lines);
}

static void dlfb_ops_damage_area(struct fb_info *info, u32 x, u32 y, u32 width, u32 height)
{
        struct dlfb_data *dlfb = info->par;

        dlfb_offload_damage(dlfb, x, y, width, height);
}

FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(dlfb_ops,
                                   dlfb_ops_damage_range,
                                   dlfb_ops_damage_area)

static const struct fb_ops dlfb_ops = {
        .owner = THIS_MODULE,
        __FB_DEFAULT_DEFERRED_OPS_RDWR(dlfb_ops),
        .fb_setcolreg = dlfb_ops_setcolreg,
        __FB_DEFAULT_DEFERRED_OPS_DRAW(dlfb_ops),
        .fb_mmap = dlfb_ops_mmap,
        .fb_ioctl = dlfb_ops_ioctl,
        .fb_open = dlfb_ops_open,
        .fb_release = dlfb_ops_release,
        .fb_blank = dlfb_ops_blank,
        .fb_check_var = dlfb_ops_check_var,
        .fb_set_par = dlfb_ops_set_par,
        .fb_destroy = dlfb_ops_destroy,
};


static void dlfb_deferred_vfree(struct dlfb_data *dlfb, void *mem)
{
        struct dlfb_deferred_free *d = kmalloc_obj(struct dlfb_deferred_free);
        if (!d)
                return;
        d->mem = mem;
        list_add(&d->list, &dlfb->deferred_free);
}

/*
 * Assumes &info->lock held by caller
 */
static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info, u32 new_len)
{
        u32 old_len = info->fix.smem_len;
        const void *old_fb = info->screen_buffer;
        unsigned char *new_fb;
        unsigned char *new_back = NULL;

        new_len = PAGE_ALIGN(new_len);

        if (new_len > old_len) {
                if (atomic_read(&dlfb->mmap_count) > 0) {
                        dev_warn(info->dev,
                                "refusing realloc: %d active mmaps\n",
                                atomic_read(&dlfb->mmap_count));
                        return -EBUSY;
                }

                /*
                 * Alloc system memory for virtual framebuffer
                 */
                new_fb = vmalloc(new_len);
                if (!new_fb) {
                        dev_err(info->dev, "Virtual framebuffer alloc failed\n");
                        return -ENOMEM;
                }
                memset(new_fb, 0xff, new_len);

                if (info->screen_buffer) {
                        memcpy(new_fb, old_fb, old_len);
                        dlfb_deferred_vfree(dlfb, info->screen_buffer);
                }

                info->screen_buffer = new_fb;
                info->fix.smem_len = new_len;
                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
                 */
                if (shadow)
                        new_back = vzalloc(new_len);
                if (!new_back)
                        dev_info(info->dev,
                                 "No shadow/backing buffer allocated\n");
                else {
                        dlfb_deferred_vfree(dlfb, dlfb->backing_buffer);
                        dlfb->backing_buffer = new_back;
                }
        }
        return 0;
}

/*
 * 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:
 * 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
 * Returns 0 if successful
 */
static int dlfb_setup_modes(struct dlfb_data *dlfb,
                           struct fb_info *info,
                           char *default_edid, size_t default_edid_size)
{
        char *edid;
        int i, result = 0, tries = 3;
        struct device *dev = info->device;
        struct fb_videomode *mode;
        const struct fb_videomode *default_vmode = NULL;

        if (info->dev) {
                /* only use mutex if info has been registered */
                mutex_lock(&info->lock);
                /* parent device is used otherwise */
                dev = info->dev;
        }

        edid = kmalloc(EDID_LENGTH, GFP_KERNEL);
        if (!edid) {
                result = -ENOMEM;
                goto error;
        }

        fb_destroy_modelist(&info->modelist);
        memset(&info->monspecs, 0, sizeof(info->monspecs));

        /*
         * 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--) {

                i = dlfb_get_edid(dlfb, edid, EDID_LENGTH);

                if (i >= EDID_LENGTH)
                        fb_edid_to_monspecs(edid, &info->monspecs);

                if (info->monspecs.modedb_len > 0) {
                        dlfb->edid = edid;
                        dlfb->edid_size = i;
                        break;
                }
        }

        /* If that fails, use a previously returned EDID if available */
        if (info->monspecs.modedb_len == 0) {
                dev_err(dev, "Unable to get valid EDID from device/display\n");

                if (dlfb->edid) {
                        fb_edid_to_monspecs(dlfb->edid, &info->monspecs);
                        if (info->monspecs.modedb_len > 0)
                                dev_err(dev, "Using previously queried EDID\n");
                }
        }

        /* If that fails, use the default EDID we were handed */
        if (info->monspecs.modedb_len == 0) {
                if (default_edid_size >= EDID_LENGTH) {
                        fb_edid_to_monspecs(default_edid, &info->monspecs);
                        if (info->monspecs.modedb_len > 0) {
                                memcpy(edid, default_edid, default_edid_size);
                                dlfb->edid = edid;
                                dlfb->edid_size = default_edid_size;
                                dev_err(dev, "Using default/backup EDID\n");
                        }
                }
        }

        /* If we've got modes, let's pick a best default mode */
        if (info->monspecs.modedb_len > 0) {

                for (i = 0; i < info->monspecs.modedb_len; i++) {
                        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);
                                if (i == 0)
                                        /* if we've removed top/best mode */
                                        info->monspecs.misc
                                                &= ~FB_MISC_1ST_DETAIL;
                        }
                }

                default_vmode = fb_find_best_display(&info->monspecs,
                                                     &info->modelist);
        }

        /* If everything else has failed, fall back to safe default mode */
        if (default_vmode == NULL) {

                struct fb_videomode fb_vmode = {0};

                /*
                 * 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++) {
                        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);
                }

                /*
                 * 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);
        }

        /* If we have good mode and no active clients*/
        if ((default_vmode != NULL) && (dlfb->fb_count == 0)) {

                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:
        if (edid && (dlfb->edid != edid))
                kfree(edid);

        if (info->dev)
                mutex_unlock(&info->lock);

        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);
        struct dlfb_data *dlfb = fb_info->par;
        return sysfs_emit(buf, "%u\n",
                        atomic_read(&dlfb->bytes_rendered));
}

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);
        struct dlfb_data *dlfb = fb_info->par;
        return sysfs_emit(buf, "%u\n",
                        atomic_read(&dlfb->bytes_identical));
}

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);
        struct dlfb_data *dlfb = fb_info->par;
        return sysfs_emit(buf, "%u\n",
                        atomic_read(&dlfb->bytes_sent));
}

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);
        struct dlfb_data *dlfb = fb_info->par;
        return sysfs_emit(buf, "%u\n",
                        atomic_read(&dlfb->cpu_kcycles_used));
}

static ssize_t edid_show(
                        struct file *filp,
                        struct kobject *kobj, const struct bin_attribute *a,
                         char *buf, loff_t off, size_t count) {
        struct device *fbdev = kobj_to_dev(kobj);
        struct fb_info *fb_info = dev_get_drvdata(fbdev);
        struct dlfb_data *dlfb = fb_info->par;

        if (dlfb->edid == NULL)
                return 0;

        if ((off >= dlfb->edid_size) || (count > dlfb->edid_size))
                return 0;

        if (off + count > dlfb->edid_size)
                count = dlfb->edid_size - off;

        memcpy(buf, dlfb->edid, count);

        return count;
}

static ssize_t edid_store(
                        struct file *filp,
                        struct kobject *kobj, const struct bin_attribute *a,
                        char *src, loff_t src_off, size_t src_size) {
        struct device *fbdev = kobj_to_dev(kobj);
        struct fb_info *fb_info = dev_get_drvdata(fbdev);
        struct dlfb_data *dlfb = fb_info->par;
        int ret;

        /* We only support write of entire EDID at once, no offset*/
        if ((src_size != EDID_LENGTH) || (src_off != 0))
                return -EINVAL;

        ret = dlfb_setup_modes(dlfb, fb_info, src, src_size);
        if (ret)
                return ret;

        if (!dlfb->edid || memcmp(src, dlfb->edid, src_size))
                return -EINVAL;

        ret = dlfb_ops_set_par(fb_info);
        if (ret)
                return ret;

        return src_size;
}

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);
        struct dlfb_data *dlfb = fb_info->par;

        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);

        return count;
}

static const struct bin_attribute edid_attr = {
        .attr.name = "edid",
        .attr.mode = 0666,
        .size = EDID_LENGTH,
        .read = edid_show,
        .write = edid_store
};

static const struct device_attribute fb_device_attrs[] = {
        __ATTR_RO(metrics_bytes_rendered),
        __ATTR_RO(metrics_bytes_identical),
        __ATTR_RO(metrics_bytes_sent),
        __ATTR_RO(metrics_cpu_kcycles_used),
        __ATTR(metrics_reset, S_IWUSR, NULL, metrics_reset_store),
};

/*
 * This is necessary before we can communicate with the display controller.
 */
static int dlfb_select_std_channel(struct dlfb_data *dlfb)
{
        int ret;
        static const u8 set_def_chn[] = {
                                0x57, 0xCD, 0xDC, 0xA7,
                                0x1C, 0x88, 0x5E, 0x15,
                                0x60, 0xFE, 0xC6, 0x97,
                                0x16, 0x3D, 0x47, 0xF2  };

        ret = usb_control_msg_send(dlfb->udev, 0, NR_USB_REQUEST_CHANNEL,
                        (USB_DIR_OUT | USB_TYPE_VENDOR), 0, 0,
                        &set_def_chn, sizeof(set_def_chn), USB_CTRL_SET_TIMEOUT,
                        GFP_KERNEL);

        return ret;
}

static int dlfb_parse_vendor_descriptor(struct dlfb_data *dlfb,
                                        struct usb_interface *intf)
{
        char *desc;
        char *buf;
        char *desc_end;
        int total_len;

        buf = kzalloc(MAX_VENDOR_DESCRIPTOR_SIZE, GFP_KERNEL);
        if (!buf)
                return false;
        desc = buf;

        total_len = usb_get_descriptor(interface_to_usbdev(intf),
                                        0x5f, /* vendor specific */
                                        0, desc, MAX_VENDOR_DESCRIPTOR_SIZE);

        /* if not found, look in configuration descriptor */
        if (total_len < 0) {
                if (0 == usb_get_extra_descriptor(intf->cur_altsetting,
                        0x5f, &desc))
                        total_len = (int) desc[0];
        }

        if (total_len > 5) {
                dev_info(&intf->dev,
                         "vendor descriptor length: %d data: %11ph\n",
                         total_len, desc);

                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;

                        key = *desc++;
                        key |= (u16)*desc++ << 8;
                        length = *desc++;

                        switch (key) {
                        case 0x0200: { /* max_area */
                                u32 max_area = *desc++;
                                max_area |= (u32)*desc++ << 8;
                                max_area |= (u32)*desc++ << 16;
                                max_area |= (u32)*desc++ << 24;
                                dev_warn(&intf->dev,
                                         "DL chip limited to %d pixel modes\n",
                                         max_area);
                                dlfb->sku_pixel_limit = max_area;
                                break;
                        }
                        default:
                                break;
                        }
                        desc += length;
                }
        } else {
                dev_info(&intf->dev, "vendor descriptor not available (%d)\n",
                         total_len);
        }

        goto success;

unrecognized:
        /* allow udlfb to load for now even if firmware unrecognized */
        dev_err(&intf->dev, "Unrecognized vendor firmware descriptor\n");

success:
        kfree(buf);
        return true;
}

static int dlfb_usb_probe(struct usb_interface *intf,
                          const struct usb_device_id *id)
{
        int i;
        const struct device_attribute *attr;
        struct dlfb_data *dlfb;
        struct fb_info *info;
        int retval;
        struct usb_device *usbdev = interface_to_usbdev(intf);
        static u8 out_ep[] = {OUT_EP_NUM + USB_DIR_OUT, 0};

        /* usb initialization */
        dlfb = kzalloc_obj(*dlfb);
        if (!dlfb) {
                dev_err(&intf->dev, "%s: failed to allocate dlfb\n", __func__);
                return -ENOMEM;
        }

        INIT_LIST_HEAD(&dlfb->deferred_free);

        dlfb->udev = usb_get_dev(usbdev);
        usb_set_intfdata(intf, dlfb);

        if (!usb_check_bulk_endpoints(intf, out_ep)) {
                dev_err(&intf->dev, "Invalid DisplayLink device!\n");
                retval = -EINVAL;
                goto error;
        }

        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);

        dlfb->sku_pixel_limit = 2048 * 1152; /* default to maximum */

        if (!dlfb_parse_vendor_descriptor(dlfb, intf)) {
                dev_err(&intf->dev,
                        "firmware not recognized, incompatible device?\n");
                retval = -ENODEV;
                goto error;
        }

        if (pixel_limit) {
                dev_warn(&intf->dev,
                         "DL chip limit of %d overridden to %d\n",
                         dlfb->sku_pixel_limit, pixel_limit);
                dlfb->sku_pixel_limit = pixel_limit;
        }


        /* allocates framebuffer driver structure, not framebuffer memory */
        info = framebuffer_alloc(0, &dlfb->udev->dev);
        if (!info) {
                retval = -ENOMEM;
                goto error;
        }

        dlfb->info = info;
        info->par = dlfb;
        info->pseudo_palette = dlfb->pseudo_palette;
        dlfb->ops = dlfb_ops;
        info->fbops = &dlfb->ops;

        mutex_init(&dlfb->render_mutex);
        dlfb_init_damage(dlfb);
        spin_lock_init(&dlfb->damage_lock);
        INIT_WORK(&dlfb->damage_work, dlfb_damage_work);

        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 */

        retval = fb_alloc_cmap(&info->cmap, 256, 0);
        if (retval < 0) {
                dev_err(info->device, "cmap allocation failed: %d\n", retval);
                goto error;
        }

        retval = dlfb_setup_modes(dlfb, info, NULL, 0);
        if (retval != 0) {
                dev_err(info->device,
                        "unable to find common mode for display and adapter\n");
                goto error;
        }

        /* ready to begin using device */

        atomic_set(&dlfb->usb_active, 1);
        dlfb_select_std_channel(dlfb);

        dlfb_ops_check_var(&info->var, info);
        retval = dlfb_ops_set_par(info);
        if (retval)
                goto error;

        retval = register_framebuffer(info);
        if (retval < 0) {
                dev_err(info->device, "unable to register framebuffer: %d\n",
                        retval);
                goto error;
        }

        for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) {
                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);
        }

        retval = device_create_bin_file(info->dev, &edid_attr);
        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);
        return 0;

error:
        if (dlfb->info) {
                dlfb_ops_destroy(dlfb->info);
        } else {
                usb_put_dev(dlfb->udev);
                kfree(dlfb);
        }
        return retval;
}

static void dlfb_usb_disconnect(struct usb_interface *intf)
{
        struct dlfb_data *dlfb;
        struct fb_info *info;
        int i;

        dlfb = usb_get_intfdata(intf);
        info = dlfb->info;

        dev_dbg(&intf->dev, "USB disconnect starting\n");

        /* we virtualize until all fb clients release. Then we free */
        dlfb->virtualized = true;

        /* When non-active we'll update virtual framebuffer, but no new urbs */
        atomic_set(&dlfb->usb_active, 0);

        /* this function will wait for all in-flight urbs to complete */
        dlfb_free_urb_list(dlfb);

        /* 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);

        unregister_framebuffer(info);
}

static struct usb_driver dlfb_driver = {
        .name = "udlfb",
        .probe = dlfb_usb_probe,
        .disconnect = dlfb_usb_disconnect,
        .id_table = id_table,
};

module_usb_driver(dlfb_driver);

static void dlfb_urb_completion(struct urb *urb)
{
        struct urb_node *unode = urb->context;
        struct dlfb_data *dlfb = unode->dlfb;
        unsigned long flags;

        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;
        }

        urb->transfer_buffer_length = dlfb->urbs.size; /* reset to actual */

        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);

        up(&dlfb->urbs.limit_sem);
}

static void dlfb_free_urb_list(struct dlfb_data *dlfb)
{
        int count = dlfb->urbs.count;
        struct list_head *node;
        struct urb_node *unode;
        struct urb *urb;

        /* keep waiting and freeing, until we've got 'em all */
        while (count--) {
                down(&dlfb->urbs.limit_sem);

                spin_lock_irq(&dlfb->urbs.lock);

                node = dlfb->urbs.list.next; /* have reserved one with sem */
                list_del_init(node);

                spin_unlock_irq(&dlfb->urbs.lock);

                unode = list_entry(node, struct urb_node, entry);
                urb = unode->urb;

                /* Free each separately allocated piece */
                usb_free_coherent(urb->dev, dlfb->urbs.size,
                                  urb->transfer_buffer, urb->transfer_dma);
                usb_free_urb(urb);
                kfree(node);
        }

        dlfb->urbs.count = 0;
}

static int dlfb_alloc_urb_list(struct dlfb_data *dlfb, int count, size_t size)
{
        struct urb *urb;
        struct urb_node *unode;
        char *buf;
        size_t wanted_size = count * size;

        spin_lock_init(&dlfb->urbs.lock);

retry:
        dlfb->urbs.size = size;
        INIT_LIST_HEAD(&dlfb->urbs.list);

        sema_init(&dlfb->urbs.limit_sem, 0);
        dlfb->urbs.count = 0;
        dlfb->urbs.available = 0;

        while (dlfb->urbs.count * size < wanted_size) {
                unode = kzalloc_obj(*unode);
                if (!unode)
                        break;
                unode->dlfb = dlfb;

                urb = usb_alloc_urb(0, GFP_KERNEL);
                if (!urb) {
                        kfree(unode);
                        break;
                }
                unode->urb = urb;

                buf = usb_alloc_coherent(dlfb->udev, size, GFP_KERNEL,
                                         &urb->transfer_dma);
                if (!buf) {
                        kfree(unode);
                        usb_free_urb(urb);
                        if (size > PAGE_SIZE) {
                                size /= 2;
                                dlfb_free_urb_list(dlfb);
                                goto retry;
                        }
                        break;
                }

                /* urb->transfer_buffer_length set to actual before submit */
                usb_fill_bulk_urb(urb, dlfb->udev,
                        usb_sndbulkpipe(dlfb->udev, OUT_EP_NUM),
                        buf, size, dlfb_urb_completion, unode);
                urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

                list_add_tail(&unode->entry, &dlfb->urbs.list);

                up(&dlfb->urbs.limit_sem);
                dlfb->urbs.count++;
                dlfb->urbs.available++;
        }

        return dlfb->urbs.count;
}

static struct urb *dlfb_get_urb(struct dlfb_data *dlfb)
{
        int ret;
        struct list_head *entry;
        struct urb_node *unode;

        /* Wait for an in-flight buffer to complete and get re-queued */
        ret = down_timeout(&dlfb->urbs.limit_sem, GET_URB_TIMEOUT);
        if (ret) {
                atomic_set(&dlfb->lost_pixels, 1);
                dev_warn(&dlfb->udev->dev,
                         "wait for urb interrupted: %d available: %d\n",
                         ret, dlfb->urbs.available);
                return NULL;
        }

        spin_lock_irq(&dlfb->urbs.lock);

        BUG_ON(list_empty(&dlfb->urbs.list)); /* reserved one with limit_sem */
        entry = dlfb->urbs.list.next;
        list_del_init(entry);
        dlfb->urbs.available--;

        spin_unlock_irq(&dlfb->urbs.lock);

        unode = list_entry(entry, struct urb_node, entry);
        return unode->urb;
}

static int dlfb_submit_urb(struct dlfb_data *dlfb, struct urb *urb, size_t len)
{
        int ret;

        BUG_ON(len > dlfb->urbs.size);

        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 */
                atomic_set(&dlfb->lost_pixels, 1);
                dev_err(&dlfb->udev->dev, "submit urb error: %d\n", ret);
        }
        return ret;
}

module_param(console, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
MODULE_PARM_DESC(console, "Allow fbcon to open framebuffer");

module_param(fb_defio, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
MODULE_PARM_DESC(fb_defio, "Page fault detection of mmap writes");

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");

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)");

MODULE_AUTHOR("Roberto De Ioris <roberto@unbit.it>, "
              "Jaya Kumar <jayakumar.lkml@gmail.com>, "
              "Bernie Thompson <bernie@plugable.com>");
MODULE_DESCRIPTION("DisplayLink kernel framebuffer driver");
MODULE_LICENSE("GPL");