#include "ldevusbu.h"
#include "lcomp_driver_version.h"
#include <linux/version.h>

MODULE_DESCRIPTION("Driver for L Card USB devices");
MODULE_AUTHOR("L Card");
MODULE_LICENSE("GPL");
MODULE_VERSION(LCOMP_DRIVER_VERSION);

static long ldevusb_stop(ldevusb *dev);

static void ldev_io_urb_complete(struct urb *urb) {
    unsigned long flags;
    t_ldev_io *io = urb->context;
    int status = urb->status;

    spin_lock_irqsave(&io->lock, flags);

    /* In 2.5 we require hcds' endpoint queues not to progress after fault
         * reports, until the completion callback (this!) returns.  That lets
         * device driver code (like this routine) unlink queued urbs first,
         * if it needs to, since the HC won't work on them at all.  So it's
         * not possible for page N+1 to overwrite page N, and so on.
         *
         * That's only for "hard" faults; "soft" faults (unlinks) sometimes
         * complete before the HCD can get requests away from hardware,
         * though never during cleanup after a hard fault.
         */
    if (io->status
            && (io->status != -ECONNRESET
                || status != -ECONNRESET)
            && urb->actual_length) {
        dev_err(io->dev->bus->controller,
                "dev %s ep%d%s scatterlist error %d/%d\n",
                io->dev->devpath,
                usb_endpoint_num(&urb->ep->desc),
                usb_urb_dir_in(urb) ? "in" : "out",
                status, io->status);
        /* BUG (); */
    }

    if (io->status == 0 && status && status != -ECONNRESET) {
        int i, found, retval;

        io->status = status;

        /* the previous urbs, and this one, completed already.
             * unlink pending urbs so they won't rx/tx bad data.
             * careful: unlink can sometimes be synchronous...
             */
        spin_unlock_irqrestore(&io->lock, flags);
        for (i = 0, found = 0; i < io->cur_entries; i++) {
            if (!io->urbs[i])
                continue;
            if (found) {
                usb_block_urb(io->urbs[i]);
                retval = usb_unlink_urb(io->urbs[i]);
                if (retval != -EINPROGRESS &&
                        retval != -ENODEV &&
                        retval != -EBUSY &&
                        retval != -EIDRM)
                    dev_err(&io->dev->dev,
                            "%s, unlink --> %d\n",
                            __func__, retval);
            } else if (urb == io->urbs[i])
                found = 1;
        }
        spin_lock_irqsave(&io->lock, flags);
    }

    /* on the last completion, signal usb_sg_wait() */
    io->bytes += urb->actual_length;
    io->count--;
    if (!io->count)
        complete(&io->complete);

    spin_unlock_irqrestore(&io->lock, flags);
}


static void ldev_io_clean(t_ldev_io *io) {
    if (io->urbs) {
        while (io->max_entries--)
            usb_free_urb(io->urbs[io->max_entries]);
        kfree(io->urbs);
        io->urbs = NULL;
    }
    io->dev = NULL;
    kfree(io->mem_sg);
    io->mem_sg = NULL;
}


static int ldev_io_alloc(t_ldev_io *io, struct usb_device *dev, unsigned pipe, int len) {
    int status = 0;
    int pages_cnt = len * sizeof(u16)/ PAGE_SIZE;
    gfp_t mem_flags = GFP_KERNEL;

    /* учитываем, что может быть нецелая часть страницы как в начале, так и
     * в конце блока памяти и тогда в предельном случае может понадобится на
     * 2 странцы больше (если irq_step меньше страницы, то при переходе
     * через границу все равно может понадобится 2) */
    if ((len * sizeof(u16)) % PAGE_SIZE != 0) {
        pages_cnt += 2;
    }

    memset(io, 0, sizeof(t_ldev_io));

    io->dev = dev;
    io->pipe = pipe;
    io->total_len = len;
    io->total_bytes = len * sizeof(u16);
    io->cur_entries = 0;

    io->mem_sg = kmalloc_array(pages_cnt, sizeof(*io->mem_sg), mem_flags);

    if (io->mem_sg == NULL) {
        status = -ENOMEM;
    } else {

        if (io->dev->bus->sg_tablesize > 0) {
            io->use_sg = true;
            io->max_entries = 1;
        } else {
            io->use_sg = false;
            io->max_entries = pages_cnt;
        }

        /* initialize all the urbs we'll use */
        io->urbs = kmalloc_array(io->max_entries, sizeof(*io->urbs), mem_flags);
        if (!io->urbs) {
            status = -ENOMEM;
        } else {
            int i;
            int urb_flags = URB_NO_INTERRUPT;
            if (usb_pipein(io->pipe))
                urb_flags |= URB_SHORT_NOT_OK;

            for (i = 0; i < io->max_entries; i++) {
                struct urb *urb;
                unsigned len;

                urb = usb_alloc_urb(0, mem_flags);
                if (!urb) {
                    io->max_entries = i;
                    status = -ENOMEM;
                    break;
                }

                io->urbs[i] = urb;

                urb->dev = NULL;
                urb->pipe = io->pipe;
                urb->transfer_flags = urb_flags;
                urb->complete = ldev_io_urb_complete;
                urb->context = io;

                if (io->use_sg) {
                    /* There is no single transfer buffer */
                    urb->transfer_buffer = NULL;
                    urb->num_sgs = pages_cnt;

                    len = io->total_bytes;
                } else {
                    urb->transfer_buffer = NULL;

                    len = PAGE_SIZE;
                }
                urb->transfer_buffer_length = len;
            }
            io->urbs[--i]->transfer_flags &= ~URB_NO_INTERRUPT;
        }
    }

    if (status != 0)
        ldev_io_clean(io);

    return status;
}

static void ldev_io_cancel(t_ldev_io *io) {
    unsigned long flags;
    int i, retval;

    spin_lock_irqsave(&io->lock, flags);
    if (io->status || io->count == 0) {
        spin_unlock_irqrestore(&io->lock, flags);
        return;
    }
    /* shut everything down */
    io->status = -ECONNRESET;
    io->count++;		/* Keep the request alive until we're done */
    spin_unlock_irqrestore(&io->lock, flags);

    for (i = io->cur_entries - 1; i >= 0; --i) {
        usb_block_urb(io->urbs[i]);

        retval = usb_unlink_urb(io->urbs[i]);
        if (retval != -EINPROGRESS
            && retval != -ENODEV
            && retval != -EBUSY
            && retval != -EIDRM)
            dev_warn(&io->dev->dev, "%s, unlink --> %d\n",
                 __func__, retval);
    }

    spin_lock_irqsave(&io->lock, flags);
    io->count--;
    if (!io->count)
        complete(&io->complete);
    spin_unlock_irqrestore(&io->lock, flags);
}


static int ldev_io_submit(t_ldev_io *io, hb *hbuf, int pos) {
    int status = 0;
    int first_page_idx  = pos * sizeof(u16)/PAGE_SIZE + 1; /* +1, т.к. первая страница под Sync */
    int first_page_offs = pos * sizeof(u16) % PAGE_SIZE; /* если позиция не выровнена на страницу, то первая страница не полная */
    int first_page_len  = PAGE_SIZE - first_page_offs;
    /* для учета возможной нецелой страницы как в начале, так и в конце, вычисляем количество страниц для данных без первой + первая страница */
    int pages_cnt = (io->total_bytes - first_page_len + PAGE_SIZE - 1)/PAGE_SIZE + 1;
    int page_idx, rem_bytes;
    struct scatterlist *sg;
    int i;




    sg_init_table(io->mem_sg, pages_cnt);

    rem_bytes = io->total_bytes;
    page_idx = first_page_idx;
    for_each_sg(io->mem_sg, sg, pages_cnt, i) {
        if (i == 0) {
            sg_set_buf(sg, hbuf->addr[page_idx] + first_page_offs, first_page_len);
            rem_bytes -= first_page_len;
        } else {
            int page_size = rem_bytes < PAGE_SIZE ? rem_bytes : PAGE_SIZE;
            sg_set_buf(sg, hbuf->addr[page_idx], page_size);
            rem_bytes -= page_size;
        }
        page_idx++;
    }

    io->cur_entries = io->use_sg ? 1 : pages_cnt;

    for_each_sg(io->mem_sg, sg, io->cur_entries, i) {
        io->urbs[i]->sg  = sg;
        if (io->use_sg) {
            io->urbs[i]->transfer_buffer = NULL;
            io->urbs[i]->num_sgs = pages_cnt;
        } else {
            if (!PageHighMem(sg_page(sg))) {
                io->urbs[i]->transfer_buffer = sg_virt(sg);
            } else {
                io->urbs[i]->transfer_buffer = NULL;
            }
        }
    }

    /* transaction state */
    io->count = io->cur_entries;
    io->status = 0;
    io->bytes = 0;
    init_completion(&io->complete);

    /* queue the urbs.  */
    spin_lock_irq(&io->lock);
    i = 0;
    while ((i < io->cur_entries) && !io->status) {
        int retval;
        io->urbs[i]->dev = io->dev;
        spin_unlock_irq(&io->lock);
        retval = usb_submit_urb(io->urbs[i], GFP_NOIO);

        switch (retval) {
        /* maybe we retrying will recover */
        case -ENXIO:	/* hc didn't queue this one */
        case -EAGAIN:
        case -ENOMEM:
            retval = 0;
            yield();
            break;

            /* no error? continue immediately.
                 *
                 * NOTE: to work better with UHCI (4K I/O buffer may
                 * need 3K of TDs) it may be good to limit how many
                 * URBs are queued at once; N milliseconds?
                 */
        case 0:
            ++i;
            cpu_relax();
            break;

            /* fail any uncompleted urbs */
        default:
            io->urbs[i]->status = retval;
            /** @todo */
            ldev_io_cancel(io);
        }
        spin_lock_irq(&io->lock);
        if (retval && (io->status == 0 || io->status == -ECONNRESET))
            io->status = retval;
    }
    io->count -= io->cur_entries - i;
    if (io->count == 0)
        complete(&io->complete);
    spin_unlock_irq(&io->lock);


    return status;
}


static int ldev_io_thread_func(void *ctx) {
    int status = 0;
    int out = 0;
    int transf_tx = 0, transf_rx = 0, i;
    ldevusb *ldev = (ldevusb *)(ctx);
    /* размер всего буфера может быть не кратен физическим страницам. вычисляем его исходя из irq_step и его кол-ва */
    int hb_total_size = ldev->wIrqStep * ldev->wPages;
    bool stop_req = false;

    ldev->rx_ctx.start_io_pos =  ldev->rx_ctx.finish_io_pos = 0;

    DbgPrint("acq thread start");

    for (i = 0; (i < LDEV_IO_TRANSF_CNT) && (status == 0) && !stop_req; ++i) {
        status = ldev_io_submit(&ldev->rx_ctx.transfs[i], ldev->Sync, ldev->rx_ctx.start_io_pos);
        DbgPrint("acq thread prestart done. status %d, pos %d, len %d", status, i, ldev->rx_ctx.transfs[i].total_len);

        if (status == 0) {
            ldev->rx_ctx.start_io_pos += ldev->rx_ctx.transfs[i].total_len;
            if (ldev->rx_ctx.start_io_pos == hb_total_size) {
                ldev->rx_ctx.start_io_pos = 0;
                if (ldev->adcPar.t3.AutoInit == 0) {
                    stop_req = true;
                }
            }
            if (++transf_tx == LDEV_IO_TRANSF_CNT)
                transf_tx = 0;
        }
    }

    while ((status == 0) && !out) {
        t_ldev_io *transf = &ldev->rx_ctx.transfs[transf_rx];
        wait_for_completion(&transf->complete);
        status = transf->status;

        DbgPrint("acq thread rx done. status %d, rx_pos %d", status, transf_rx);


        if (kthread_should_stop() || !test_bit(FLAG_WORKING,&ldev->dev_flags)) {
            out = stop_req = true;
            DbgPrint("acq thread stop requested");
        }


        if (status == 0) {
            ldev->rx_ctx.finish_io_pos += transf->total_len;
            *ldev->SyncCntr = ldev->rx_ctx.finish_io_pos;
            DbgPrint("acq thread set sync cntr = %d", ldev->rx_ctx.finish_io_pos);
            mb();
            if (++transf_rx == LDEV_IO_TRANSF_CNT)
                transf_rx = 0;

            if (ldev->rx_ctx.finish_io_pos == hb_total_size) {
                ldev->rx_ctx.finish_io_pos = 0;
                if (ldev->adcPar.t3.AutoInit == 0) {

                    DbgPrint("acq thread rx autostop");
                    /* mb();
                      *dev->SyncCntr = 0; */
                    set_bit(FLAG_ADC_EVT, &ldev->dev_flags);
                    wake_up_interruptible(&ldev->adc_wq);
                    out = true;
                }
             }

             if (!stop_req) {
                status = ldev_io_submit(&ldev->rx_ctx.transfs[transf_tx], ldev->Sync, ldev->rx_ctx.start_io_pos);
                DbgPrint("acq thread start tx. status = %d, pos %d", status, transf_tx);
                if (status == 0) {
                    ldev->rx_ctx.start_io_pos += ldev->rx_ctx.transfs[transf_tx].total_len;
                    if (ldev->rx_ctx.start_io_pos == hb_total_size) {
                        ldev->rx_ctx.start_io_pos = 0;
                        if (ldev->adcPar.t3.AutoInit == 0) {
                            stop_req = true;
                            DbgPrint("acq thread tx autostop");
                        }
                    }
                }

                if (++transf_tx == LDEV_IO_TRANSF_CNT) {
                    transf_tx = 0;
                }
             }
        }
    }

    DbgPrint("acq thread cycle out");
    /* отменяем все оставшиеся запросы */
    for (i = 0; i < LDEV_IO_TRANSF_CNT; i++) {
        t_ldev_io *transf = &ldev->rx_ctx.transfs[i];
        ldev_io_cancel(transf);
    }

    /* ожидаем завершения не завершенных еще запросов */
    for (i = 0; i < LDEV_IO_TRANSF_CNT; i++) {
        unsigned long flags;
        int wip = 0;
        t_ldev_io *transf = &ldev->rx_ctx.transfs[i];
        spin_lock_irqsave(&transf->lock, flags);
        wip = transf->count > 0;
        spin_unlock_irqrestore(&transf->lock, flags);
        if (wip) {
            wait_for_completion(&transf->complete);
        }
    }

    DbgPrint("acq thread out");


    return status;
}


//////////////////////// HUGE BUFFER FW ////////////////////////////////////
void hb_free(hb *buf) {
   int i;
   for(i=0; i < buf->nr_pages; i++)
       free_page((unsigned long)buf->addr[i]);
   kfree(buf->addr);
   kfree(buf);
}

hb *hb_malloc(int pages) {
   int i;
   hb *buf = (hb *)kmalloc(sizeof(hb),GFP_KERNEL);
   if(buf == NULL)
       return NULL;

   buf->nr_pages = pages;
   buf->addr = kmalloc(buf->nr_pages*sizeof(u8 *), GFP_KERNEL);
   if(!buf->addr) {
       kfree(buf);
       return NULL;
   }

   memset(buf->addr, 0, buf->nr_pages*sizeof(u8 *));
   for(i = 0; i < buf->nr_pages; i++) {
      buf->addr[i] = (u8 *)__get_free_page(GFP_KERNEL);
      if(buf->addr[i] == NULL) {
          buf->nr_pages=i;
          hb_free(buf);
          return NULL;
      }
   }
   return buf;
}

#define hb_getel(buf, pos, type) (((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))])
#define hb_setel(buf, pos, type, data) {((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))] = data;}
#define hb_addel(buf, pos, type, data) {((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))] += data;}
#define hb_decel(buf, pos, type, data) {((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))] -= data;}


int hb_memcpyto(u8 *src, hb *buf, int pos, int size) {
    int page = pos/PAGE_SIZE;
    int off =  pos%PAGE_SIZE;
    int len = PAGE_SIZE-off;
    do {
        if(size < len) {
            len=size;
        }
        memcpy((void *)(buf->addr[page]+off), (void *)src, len);
        src += len;
        page++;
        size -= len;
        off = 0;
        len = PAGE_SIZE;
    } while(size);
    return 0;
}

int hb_memcpyfrom(u8 *dst, hb *buf, int pos, int size) {
    int page = pos/PAGE_SIZE;
    int off =  pos%PAGE_SIZE;
    int len = PAGE_SIZE-off;
    do {
        if(size < len) {
            len=size;
        }
        memcpy((void *)dst, (void *)(buf->addr[page]+off), len);
        dst += len;
        page++;
        size -= len;
        off = 0;
        len = PAGE_SIZE;
    } while(size);
    return 0;
}

//////////////////////////////////////////////////////////////////



// forward decl
static char device_name[] = "ldevusb";
static struct file_operations fops;
static struct usb_driver ldriver;
static void ldevusb_delete(struct kref *kref);

static int ldriver_probe(struct usb_interface *interface, const struct usb_device_id *id) {
    ldevusb *dev = NULL;
    struct usb_host_interface *iface_desc;
    struct usb_endpoint_descriptor *endpoint;
    int i;

    int retval = -ENOMEM;
    int status = 0;

    /* allocate memory for our device state and initialize it */
    dev = kmalloc(sizeof(ldevusb), GFP_KERNEL);
    if (dev == NULL) {
        DbgPrint("Out of memory");
        goto error;
    }
    memset(dev, 0x00, sizeof (*dev));
    kref_init(&dev->kref);

    dev->Slot = ldev_add();

    dev->udev = usb_get_dev(interface_to_usbdev(interface));
    dev->interface = interface;
    interface->minor=dev->Slot;

    /* set up the endpoint information */
    /* use only the first bulk-in and bulk-out endpoints */
    iface_desc = interface->cur_altsetting;
    for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
        endpoint = &iface_desc->endpoint[i].desc;

        if (!dev->bulk_in_endpointAddr &&
            (endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                == USB_ENDPOINT_XFER_BULK)) {
            dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
        }

        if (!dev->bulk_out_endpointAddr &&
            !(endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                == USB_ENDPOINT_XFER_BULK)) {
            dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
        }
    }

    if(!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
        DbgPrint("Could not find both bulk-in and bulk-out endpoints");
        goto error;
    }

    /* save our data pointer in this interface device */
    usb_set_intfdata(interface, dev);

    // fill up SLOT_PAR for client
    memset(&dev->sl,0,sizeof(SLOT_PAR));
    switch(id->idProduct) {
        case 0x0440: {dev->Type = E440; dev->sl.DSPType = 2185;} break;
        case 0x2010: {dev->Type = E2010; dev->sl.DSPType = 0;} break;
        case 0x0140: {dev->Type = E140; dev->sl.DSPType = 0; } break;
        case 0x0154: {dev->Type = E154; dev->sl.DSPType = 0; }
    }

    switch(dev->Type) {
        case E440: break;
        case E140: {
            char *mn = (char *)kmalloc(256,GFP_KERNEL);
            int status = -1;
   
            status = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
                        V_GET_MODULE_NAME_E140, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
                        0, 0, mn, 255, HZ*10);
         
            DbgPrint("############# module name  %s %x \n",mn, status);
            kfree(mn);
        } break;
        case E154: break;
        case E2010:
            status = ldev_e2010_init(dev);
            break;
    };

    dev->sl.BoardType = dev->Type;

    init_waitqueue_head(&dev->adc_wq);
    mutex_init(&dev->io_lock);
    dev->dev_flags=0;

    DbgPrint("ldevusb device now attached to ldev%d \n", dev->Slot);

    ldev_register(&fops, dev->Slot);

    return 0;
error:
    if(dev) {
        kref_put(&dev->kref, ldevusb_delete);
    }
    return retval;
}

static void ldevusb_delete(struct kref *kref) {
    ldevusb *dev = to_ldevusb_dev(kref);
    kfree(dev->devspec);
    DbgPrint("in delete \n");
    usb_put_dev(dev->udev);
    ldev_remove(dev->Slot);
    kfree(dev);
}


static void ldriver_disconnect(struct usb_interface *interface) {
    ldevusb *dev;

    /* prevent skel_open() from racing skel_disconnect() */
    /////!!!!   lock_kernel();

    dev = usb_get_intfdata(interface);
    usb_set_intfdata(interface, NULL);

    /////!!!!   unlock_kernel();

    /* decrement our usage count */
    kref_put(&dev->kref, ldevusb_delete);
    DbgPrint("ldev%d now disconnected \n", dev->Slot);
}

static int ldevusb_open(struct inode *inode, struct file *filp) {
    ldevusb *dev;
    struct usb_interface *interface;
    int subminor;
//   u32 Addr;
//   u32 LongAddr;

    subminor = iminor(inode);
    DbgPrint("subminor %d", subminor);
    interface = usb_find_interface(&ldriver, subminor);
    if(!interface) {
        DbgPrint ("%s - error, can't find device for minor %d",  __FUNCTION__, subminor);
        return -ENODEV;
    }

    dev = usb_get_intfdata(interface);
    if(!dev) {
        return -ENODEV;
    }

    mutex_lock(&dev->io_lock);
    {
        if (test_bit(FLAG_USED, &dev->dev_flags)) {
            mutex_unlock(&dev->io_lock);
            return -EBUSY;
        }

        set_bit(FLAG_USED, &dev->dev_flags);
        clear_bit(FLAG_WORKING, &dev->dev_flags);

        dev->Sync = NULL;
        dev->bios = NULL;

        kref_get(&dev->kref);
        filp->private_data = dev;
    }
    mutex_unlock(&dev->io_lock);
    return 0;
}

static int ldevusb_release(struct inode *inode, struct file *filp) {
    ldevusb *dev = (ldevusb *)filp->private_data;
    if (dev == NULL) {
        return -ENODEV;
    }

    mutex_lock(&dev->io_lock);
    {
        ldevusb_stop(dev);
/*
        if(dev->OutBulk) {
            for (i = 0; i < (dev->Pages*PAGE_SIZE); i+= PAGE_SIZE) {
                ClearPageReserved(vmalloc_to_page((void *)(((unsigned long)dev->OutBulk) + i)));
            }
            vfree(dev->OutBulk);
            dev->OutBulk=NULL;
        }
*/

        if(dev->Sync)
            hb_free(dev->Sync);

        if(dev->bios) {
            kfree(dev->bios);
            dev->bios=NULL;
        }

        clear_bit(FLAG_USED, &dev->dev_flags);
        kref_put(&dev->kref, ldevusb_delete);
    }
    mutex_unlock(&dev->io_lock);
    return 0;
}

static long ldevusb_stop(ldevusb *dev) {
    long status = 0;
    if (test_bit(FLAG_WORKING, &dev->dev_flags)) {
        int i;
        clear_bit(FLAG_WORKING, &dev->dev_flags);
        ldevusb_enable(dev, 0);
        for (i = 0; i < LDEV_IO_TRANSF_CNT; ++i) {
            ldev_io_cancel(&dev->rx_ctx.transfs[i]);
        }
        if (dev->io_thread != NULL) {
            kthread_stop(dev->io_thread);
            put_task_struct(dev->io_thread);
            dev->io_thread = NULL;
        }
        for (i = 0; i < LDEV_IO_TRANSF_CNT; ++i) {
            ldev_io_clean(&dev->rx_ctx.transfs[i]);
        }
    }
    return status;
}

static /*int*/long ldevusb_ioctl(/*struct inode *inode,*/ struct file *filp, unsigned int cmd, unsigned long arg) {
    ldevusb *dev = (ldevusb *)filp->private_data;
    int status = -EFAULT;
    PIOCTL_BUFFER ibuf;
    u16 Param, Param1;
    u16 Dir,Cmd,Addr,Index;
    u32 Param32;
    u16 AdcCoef[24];

    ibuf = kmalloc(sizeof(IOCTL_BUFFER),GFP_KERNEL);
    if(ibuf == NULL) {
        return status;
    }
    if (copy_from_user(ibuf, (void*)arg, sizeof(IOCTL_BUFFER))) {
        kfree(ibuf);
        return status;
    }

    if(cmd == DIOC_WAIT_COMPLETE) {
        DbgPrint("wait start \n");
        wait_event_interruptible(dev->adc_wq, test_bit(FLAG_ADC_EVT, &dev->dev_flags));
        clear_bit(FLAG_ADC_EVT,&dev->dev_flags);
        DbgPrint("wait complete \n");
        return 0;
    }

    mutex_lock(&dev->io_lock);

    switch(cmd) {
        case DIOC_GET_PARAMS: {
            DbgPrint("in DIOC_GET_PARAMS \n");
            memcpy(ibuf->outBuffer, &(dev->sl), sizeof(SLOT_PAR));
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
        } break;
        case DIOC_SET_DSP_TYPE: {
            DbgPrint("In DIOC_SET_DSP_TYPE \n");
            DbgPrint("Only 2185 or no DSP at all\n");
            status = 0;
        } break;
        case DIOC_SEND_COMMAND: {
            DbgPrint("in DIOC_SEND_COMMAND \n");
            // 0(dir 0out 1 in) 1 -cmd  2 -addr 3 len/index
            Dir = ((u16 *)ibuf->inBuffer)[0];
            Cmd = ((u16 *)ibuf->inBuffer)[1];
            Addr = ((u16 *)ibuf->inBuffer)[2];
            Index = ((u16 *)ibuf->inBuffer)[3];

            DbgPrint("%d %d %d %d", Dir, Cmd, Addr, Index);

            if(Dir) {
                status = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
                        (u8)Cmd, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
                        Addr, Index, ibuf->outBuffer, ibuf->outSize, HZ*10);
            } else {
                status = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
                        (u8)Cmd, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
                        Addr, Index, ibuf->outBuffer, ibuf->outSize, HZ*10);
            }
            if(status < 0)
                break;

            ibuf->outSize = status;
            if(copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER)))
                break;
            status = 0;
        } break;
        case DIOC_SEND_BIOS: {
            status = 0;
            Param32 = *(u32 *)ibuf->inBuffer;
            if(dev->bios == NULL) {
                dev->bios = (u8 *) kmalloc(Param32,GFP_KERNEL);
                if(dev->bios == NULL) {
                    status = -ENOMEM;
                }
                dev->bios_pos = dev->bios;
            }

            if (dev->bios_pos != NULL) {
                memcpy(dev->bios_pos, ibuf->outBuffer, ibuf->outSize);
                dev->bios_pos += ibuf->outSize;
            }
        }
        break;
        case DIOC_LOAD_BIOS: {
            status = 0;
            Param32 = *(u32 *)ibuf->inBuffer;

            if(dev->bios || (Param32 == 0)) {
                if ((dev->Type == E2010B) || (dev->Type == E2010)) {
                    status = ldev_e2010_load_bios(dev, dev->bios, Param32);
                }
                kfree(dev->bios);
                dev->bios=NULL;
            }
        }
        break;

        case DIOC_READ_FLASH_WORD: {
            DbgPrint("In DIOC_READ_FLASH_WORD \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type) {
                case E440: ReadFlashWord_E440(dev, Param, (u16 *)ibuf->outBuffer); break;
                case E140: ReadFlashWord_E140(dev, Param, (u16 *)ibuf->outBuffer); break;
                case E2010B:
                case E2010: ldev_e2010_read_plata_descr(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16));break; // all at once
                case E154: ReadFlashWord_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16)); // all at once
            }
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
        } break;

        case DIOC_WRITE_FLASH_WORD: {
            DbgPrint("In DIOC_WRITE_FLASH_WORD \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type) {
                case E440: WriteFlashWord_E440(dev, Param, *(u16 *)ibuf->outBuffer); break;
                case E140: WriteFlashWord_E140(dev, Param, *(u16 *)ibuf->outBuffer); break;
                case E2010B:
                case E2010: e2010_write_plate_descr(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16));break; // all at once
                case E154: WriteFlashWord_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16)); // all at once
            }
            status = 0;
        } break;

        case DIOC_ENABLE_FLASH_WRITE: {
            DbgPrint("In DIOC_ENABLE_FLASH_WRITE \n");
            Param = *(u16 *)ibuf->outBuffer;
            switch(dev->Type) {
                case E440: EnableFlashWrite_E440(dev, Param); break;
                case E140: EnableFlashWrite_E140(dev, Param); break;
                case E154: EnableFlashWrite_E154(dev, Param); break;
                case E2010B:
                case E2010: break;
            }
            status = 0;
        } break;

        case DIOC_COMMAND_PLX: {
            DbgPrint("DIOC_COMMAND_PLX \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type) {
               case E440: COMMAND_E440(dev, Param); break;
               case E140: COMMAND_E140(dev, Param);break;
               case E154: COMMAND_E154(dev, Param);break;
            }
            status = 0;
        } break;

        case DIOC_GET_DM_A: {    // METHOD_OUT_DIRECT
            DbgPrint("DIOC_GET_DM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type) {
               case E440: GET_DATA_MEMORY_E440(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E140: GET_DATA_MEMORY_E140(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E154: GET_DATA_MEMORY_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
            }
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));;
        } break;

        case DIOC_PUT_DM_A: {   // METHOD_IN_DIRECT
            DbgPrint("DIOC_PUT_DM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type) {
               case E440: PUT_DATA_MEMORY_E440(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E140: PUT_DATA_MEMORY_E140(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E154: PUT_DATA_MEMORY_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
            }
            status = 0;
        } break;

        case DIOC_GET_PM_A: {    // METHOD_OUT_DIRECT
            DbgPrint("DIOC_GET_PM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type) {
               case E440: GET_PM_MEMORY_E440(dev, (u32 *)ibuf->outBuffer, ibuf->outSize/sizeof(u32), Param); break;
               case E140: *((u8 *)ibuf->outBuffer) = GET_PM_WORD_E140(dev, Param); break;
               case E154: *((u8 *)ibuf->outBuffer) = GET_PM_WORD_E154(dev, Param); break;
            }
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
        } break;

        case DIOC_PUT_PM_A: {   // METHOD_IN_DIRECT
            DbgPrint("DIOC_PUT_PM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type) {
               case E440: PUT_PM_MEMORY_E440(dev, (u32 *)ibuf->outBuffer, ibuf->outSize/sizeof(u32), Param); break;
               case E140: PUT_PM_WORD_E140(dev, Param, *((u8 *)ibuf->outBuffer)); break;
               case E154: PUT_PM_WORD_E154(dev, Param, *((u8 *)ibuf->outBuffer)); break;
            }
            status = 0;
        } break;
        case DIOC_ADCSAMPLE: {// for e-140 specific...
            DbgPrint("In DIOC_ADCSAMPLE \n");
            if(test_bit(FLAG_WORKING, &dev->dev_flags))
                break;
            switch(dev->Type) {
                case E140: {
                    Param1 = (u16)*((u32 *)ibuf->inBuffer);
                    Param = MAKE_E140CHAN(Param1);

                    ldevusb_enable(dev,0);

                    PUT_DM_WORD_E140(dev,L_ADC_CHANNEL_SELECT_E140, Param);
                    udelay(1000);
                    PUT_DM_WORD_E140(dev,L_ADC_CHANNEL_SELECT_E140, Param);
                    *((u32 *)ibuf->outBuffer) = GET_DM_WORD_E140(dev,L_ADC_SAMPLE_E140);
                } break;
                case E154: {
                    Param = (u16)*((u32 *)ibuf->inBuffer);
                    ldevusb_enable(dev,0);
                    PUT_DM_WORD_E154(dev,L_ADC_CHANNEL_SELECT_E154, Param);
                    *((u32 *)ibuf->outBuffer) = GET_DM_WORD_E154(dev,L_ADC_SAMPLE_E154);
                } break;
            }
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
        } break;
        case DIOC_DAC_OUT: // for e20-10 specific...
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                Param = *(u16 *)ibuf->inBuffer;
                status = ldev_e2010_put_dm_word(dev,SEL_DAC_DATA, Param);
            } else {
                status = -ENOTTY;
            }
            break;
        case DIOC_TTL_OUT: // for e20-10 specific...
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                Param = *(u16 *)ibuf->inBuffer;
                status = ldev_e2010_put_dm_word(dev,SEL_DIO_DATA, Param);
            }
            break;
        case DIOC_TTL_CFG: // for e20-10 specific...
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                Param = *(u16 *)ibuf->inBuffer;
                status = ldev_e2010_put_pm_word(dev,SEL_DIO_PARAM, (u8)Param);
            }
            break;
        case DIOC_TTL_IN:  // for e20-10 specific...
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                DbgPrint("In DIOC_TTL_IN \n");
                status = ldev_e2010_get_dm_word(dev, SEL_DIO_DATA, ((u16 *)ibuf->outBuffer));
                if (status == 0)
                    status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
            } else {
                status = -ENOTTY;
            }
            break;
        case DIOC_ENABLE_CORRECTION: // for e20-10B specific...
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                if (ibuf->inSize < (int)sizeof(AdcCoef)) {
                    status = -EINVAL;
                } else {
                    memcpy(AdcCoef, ibuf->inBuffer, sizeof(AdcCoef));
                    status = ldev_e2010_put_pm_word(dev, ADC_CORRECTION_ADDRESS, 1);
                    if (status == 0) {
                        status = ldev_e2010_put_data_memory(dev, (u8*)AdcCoef, sizeof(AdcCoef),
                                                            SEL_ADC_CALIBR_KOEFS);
                    }
                }
            } else {
                status = -ENOTTY;
            }
            break;
        case DIOC_SETBUFFER: { // METHOD_OUT_DIRECT
            DbgPrint("In SETBUFFER \n");
            if(test_bit(FLAG_WORKING, &dev->dev_flags))
                break;

            dev->wBufferSize = *(u32 *)ibuf->inBuffer;
            DbgPrint("Set Buffer %d \n", dev->wBufferSize);

            /* unmark the pages reserved */
            if(dev->Sync) {
                hb_free(dev->Sync);
            }

            dev->RealPages = (dev->wBufferSize * sizeof(u16) + PAGE_SIZE - 1)/PAGE_SIZE;
            dev->Sync = hb_malloc(dev->RealPages+1);
            if(dev->Sync == NULL)
                break;
            dev->SyncCntr = (volatile int *)dev->Sync->addr[0];

            DbgPrint("Alloc real Buffer %d \n", (int)(dev->RealPages*PAGE_SIZE/sizeof(u16)));
            *(u32 *)ibuf->outBuffer = (dev->RealPages)*PAGE_SIZE/sizeof(u16); // +1 in dll for mapping pagecount page
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
        }
        break;
        case DIOC_SETUP: { // METHOD_OUT_DIRECT - get ADC_PAR return UserData
            DbgPrint("In SETUP \n");
            if(!dev->Sync)
                break;

            if(test_bit(FLAG_WORKING,&dev->dev_flags))
                break;

            if (ibuf->inSize > sizeof(WDAQ_PAR))
                break;

            memcpy(&dev->adcPar,ibuf->inBuffer,ibuf->inSize);

            if ((dev->Type != E2010) && (dev->Type != E2010B)) {// E140 E440
                dev->wFIFO = dev->adcPar.t3.FIFO;
                dev->wIrqStep = dev->adcPar.t3.IrqStep;
                dev->wPages = dev->adcPar.t3.Pages;
                DbgPrint(" %x %x %x %x \n",dev->adcPar.t3.Rate,dev->adcPar.t3.NCh,dev->adcPar.t3.Chn[0],dev->adcPar.t3.FIFO);
            } else { // E2010
                dev->wFIFO = dev->adcPar.t4.FIFO;
                dev->wIrqStep = dev->adcPar.t4.IrqStep;
                dev->wPages = dev->adcPar.t4.Pages;
                DbgPrint(" %x %x %x %x \n",dev->adcPar.t4.Rate,dev->adcPar.t4.NCh,dev->adcPar.t4.Chn[0],dev->adcPar.t4.FIFO);
            }

            if (dev->wIrqStep == 0)
                break;

            DbgPrint("Set Buffer %d %d \n", dev->wPages, dev->wIrqStep);

            SetBoardFIFO(dev);// - УСТАНВЛИВАЕТ ФИФО на плате(( case внутри функции

            if (dev->wPages > (dev->RealPages*(PAGE_SIZE/sizeof(u16))/dev->wIrqStep)) {
                dev->wPages=(dev->RealPages*(PAGE_SIZE/sizeof(u16))/dev->wIrqStep);
            }

            ((u32 *)ibuf->outBuffer)[0] = dev->wPages;  // used size
            ((u32 *)ibuf->outBuffer)[1] = dev->wFIFO;
            ((u32 *)ibuf->outBuffer)[2] = dev->wIrqStep;
            ((u32 *)ibuf->outBuffer)[3] = 0xA5A5A5A5;
            ibuf->outSize=4*sizeof(u32);

            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
        } break;

        case DIOC_INIT_SYNC:{
            DbgPrint("In INIT_SYNC \n");
            if(!dev->Sync)
                break;
            if (test_bit(FLAG_WORKING, &dev->dev_flags))
                break;
            hb_setel(dev->Sync,0,int,0);
            //if(d->DacBuf) { ((u32 *)d->DacBuf)[0]=0; ((u32 *)d->DacBuf)[1]=0; }
            status = 0;
        } break;

        case DIOC_START: {
            int i;
            status = 0;
            if(!dev->Sync) {
                status = -EFAULT;
            } else if (test_bit(FLAG_WORKING,&dev->dev_flags)) {
                status = -EFAULT;
            }
            if (status == 0) {
                set_bit(FLAG_WORKING,&dev->dev_flags);

                for (i = 0; (i < LDEV_IO_TRANSF_CNT) && (status == 0); i++) {
                    status = ldev_io_alloc(&dev->rx_ctx.transfs[i], dev->udev,
                                           usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                                           dev->wIrqStep);
                }

                if (status == 0)
                    status = ldevusb_enable(dev, 1);

                if (status == 0) {
                    dev->io_thread
                            = kthread_create(ldev_io_thread_func, dev, "ldevusb");
                    if (IS_ERR(dev->io_thread)) {
                        status = -EFAULT;
                        dev->io_thread = NULL;
                    } else {
                        get_task_struct(dev->io_thread);
                        wake_up_process(dev->io_thread);
                    }
                }

                if (status != 0) {
                    ldevusb_stop(dev);
                }
            }
        }
        break;

        case DIOC_STOP:
            status = ldevusb_stop(dev);
            break;

        case DIOC_OUTM: {
            PORT_PAR par;
            if(ibuf->inSize != sizeof(PORT_PAR))
                break;
            memcpy(&par,ibuf->inBuffer,ibuf->inSize);
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                status = ldev_e2010_put_data_memory(dev, ibuf->outBuffer, ibuf->outSize, par.port);
                DbgPrint("OUTM addr 0x%08X, size = %d, status %d\n", par.port, ibuf->outSize, status);
            }
        } break;
        case DIOC_INM: {
            PORT_PAR par;
            if(ibuf->inSize != sizeof(PORT_PAR))
                break;
            memcpy(&par,ibuf->inBuffer,ibuf->inSize);
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                status = ldev_e2010_get_data_memory(dev, ibuf->outBuffer, ibuf->outSize, par.port);
                DbgPrint("INM addr 0x%08X, size = %d, status %d\n", par.port, ibuf->outSize, status);
            } else {
                status = -ENOTTY;
            }

            if (status == 0) {
                status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
            }
        } break;
        default:
            DbgPrint("Unknown IOCTL !!! \n");
            status = -ENOTTY;
    }

    mutex_unlock(&dev->io_lock);
    kfree(ibuf);
    return status;
}

int ldevusb_enable(ldevusb *dev, int flWork) {
    int status = 0;
    if (flWork) {
        //dev->CurPage=0;
        //DacCurPage = 0;
    }

    switch(dev->Type)  {
        case E440:  EnableE440(dev,flWork); break;
        case E140:  EnableE140(dev,flWork); break;
        case E154:  EnableE154(dev,flWork); break;

        case E2010B:
        case E2010:
            status = ldev_e2010_enable(dev,flWork);
            break;
    }
    return status;
}


static int ldevusb_mmap(struct file *filp, struct vm_area_struct *vma) {
    ldevusb *dev = (ldevusb *)filp->private_data;
    int status;
    long length = vma->vm_end - vma->vm_start;
    unsigned long start = vma->vm_start;
    u8 *vmalloc_area_ptr = NULL;
    unsigned long pfn;
    int pages;
    int i=0;

    DbgPrint("In MMAP IOCTL !!! \n");
    DbgPrint("offset field %ld", vma->vm_pgoff);
    if (!dev->Sync)
        return -ENOMEM;

    if(!(vma->vm_flags & VM_READ))
        return -EINVAL;

    //if(vma->vm_flags & VM_WRITE) { vmalloc_area_ptr = dev->OutBulk; DbgPrint("map out buf");}
    //else
    {
        vmalloc_area_ptr = (u8 *)dev->Sync->addr[i];
        pages = dev->RealPages+1;
        DbgPrint("map in buf");
    }
    /* check length - do not allow larger mappings than the number of pages allocated */
    if(length > (pages*PAGE_SIZE))
        return -EINVAL;

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0)
    vma->vm_flags|=VM_IO;
#else
    vm_flags_set(vma, VM_IO);
#endif

    /* loop over all pages, map it page individually */
    while (length > 0)  {
        //pfn = __pa((void *)vmalloc_area_ptr) >> PAGE_SHIFT;
        pfn = page_to_pfn(virt_to_page(vmalloc_area_ptr));
        if((status = remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED))<0)
            return status;
        start += PAGE_SIZE;
        vmalloc_area_ptr = (u8 *)dev->Sync->addr[++i];
        length -= PAGE_SIZE;
    }

    return 0;
}


/////////////////////////////////////////////////////////////
// MAIN part
/////////////////////////////////////////////////////////////
/* table of devices that work with this driver */
static struct usb_device_id ldriver_table [] = {
   { USB_DEVICE(0x0471, 0x0440) },
   { USB_DEVICE(0x0471, 0x0140) },
   { USB_DEVICE(0x0471, 0x2010) },
   { USB_DEVICE(0x0471, 0x0154) },   
   {/*            vid     pid */} /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, ldriver_table);

static struct file_operations fops = {
   .owner =   THIS_MODULE,
   .open =    ldevusb_open,
   .unlocked_ioctl =   ldevusb_ioctl,
   .mmap =    ldevusb_mmap,
   .release = ldevusb_release,
};

static struct usb_driver ldriver = {
   .name = device_name,
   .id_table = ldriver_table,
   .probe = ldriver_probe,
   .disconnect = ldriver_disconnect,
};

static int __init ldriver_init(void)
{
   int status = usb_register(&ldriver);
   if(status) DbgPrint("usb_register failed. Error number %d \n", status);
   return status;
}

static void __exit ldriver_exit(void)
{
   usb_deregister(&ldriver);
}

module_init (ldriver_init);
module_exit (ldriver_exit);
