📄 sisusb.c
字号:
/* * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles * * Main part * * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria * * If distributed as part of the Linux kernel, this code is licensed under the * terms of the GPL v2. * * Otherwise, the following license terms apply: * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1) Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2) Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3) The name of the author may not be used to endorse or promote products * * derived from this software without specific psisusbr written permission. * * * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Author: Thomas Winischhofer <thomas@winischhofer.net> * */#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/errno.h>#include <linux/poll.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/kref.h>#include <linux/usb.h>#include <linux/smp_lock.h>#include <linux/vmalloc.h>#include "sisusb.h"#ifdef INCL_SISUSB_CON#include <linux/font.h>#endif#define SISUSB_DONTSYNC/* Forward declarations / clean-up routines */#ifdef INCL_SISUSB_CONint sisusb_setreg(struct sisusb_usb_data *sisusb, int port, u8 data);int sisusb_getreg(struct sisusb_usb_data *sisusb, int port, u8 *data);int sisusb_setidxreg(struct sisusb_usb_data *sisusb, int port, u8 index, u8 data);int sisusb_getidxreg(struct sisusb_usb_data *sisusb, int port, u8 index, u8 *data);int sisusb_setidxregandor(struct sisusb_usb_data *sisusb, int port, u8 idx, u8 myand, u8 myor);int sisusb_setidxregor(struct sisusb_usb_data *sisusb, int port, u8 index, u8 myor);int sisusb_setidxregand(struct sisusb_usb_data *sisusb, int port, u8 idx, u8 myand);int sisusb_writeb(struct sisusb_usb_data *sisusb, u32 adr, u8 data);int sisusb_readb(struct sisusb_usb_data *sisusb, u32 adr, u8 *data);int sisusb_writew(struct sisusb_usb_data *sisusb, u32 adr, u16 data);int sisusb_readw(struct sisusb_usb_data *sisusb, u32 adr, u16 *data);int sisusb_copy_memory(struct sisusb_usb_data *sisusb, char *src, u32 dest, int length, size_t *bytes_written);int sisusb_reset_text_mode(struct sisusb_usb_data *sisusb, int init);extern int SiSUSBSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo);extern int SiSUSBSetVESAMode(struct SiS_Private *SiS_Pr, unsigned short VModeNo);extern void sisusb_init_concode(void);extern int sisusb_console_init(struct sisusb_usb_data *sisusb, int first, int last);extern void sisusb_console_exit(struct sisusb_usb_data *sisusb);extern void sisusb_set_cursor(struct sisusb_usb_data *sisusb, unsigned int location);extern int sisusbcon_do_font_op(struct sisusb_usb_data *sisusb, int set, int slot, u8 *arg, int cmapsz, int ch512, int dorecalc, struct vc_data *c, int fh, int uplock);static int sisusb_first_vc = 0;static int sisusb_last_vc = 0;module_param_named(first, sisusb_first_vc, int, 0);module_param_named(last, sisusb_last_vc, int, 0);MODULE_PARM_DESC(first, "Number of first console to take over (1 - MAX_NR_CONSOLES)");MODULE_PARM_DESC(last, "Number of last console to take over (1 - MAX_NR_CONSOLES)");#endifstatic struct usb_driver sisusb_driver;DECLARE_MUTEX(disconnect_sem);static voidsisusb_free_buffers(struct sisusb_usb_data *sisusb){ int i; for (i = 0; i < NUMOBUFS; i++) { if (sisusb->obuf[i]) { usb_buffer_free(sisusb->sisusb_dev, sisusb->obufsize, sisusb->obuf[i], sisusb->transfer_dma_out[i]); sisusb->obuf[i] = NULL; } } if (sisusb->ibuf) { usb_buffer_free(sisusb->sisusb_dev, sisusb->ibufsize, sisusb->ibuf, sisusb->transfer_dma_in); sisusb->ibuf = NULL; }}static voidsisusb_free_urbs(struct sisusb_usb_data *sisusb){ int i; for (i = 0; i < NUMOBUFS; i++) { usb_free_urb(sisusb->sisurbout[i]); sisusb->sisurbout[i] = NULL; } usb_free_urb(sisusb->sisurbin); sisusb->sisurbin = NULL;}/* Level 0: USB transport layer *//* 1. out-bulks *//* out-urb management *//* Return 1 if all free, 0 otherwise */static intsisusb_all_free(struct sisusb_usb_data *sisusb){ int i; for (i = 0; i < sisusb->numobufs; i++) { if (sisusb->urbstatus[i] & SU_URB_BUSY) return 0; } return 1;}/* Kill all busy URBs */static voidsisusb_kill_all_busy(struct sisusb_usb_data *sisusb){ int i; if (sisusb_all_free(sisusb)) return; for (i = 0; i < sisusb->numobufs; i++) { if (sisusb->urbstatus[i] & SU_URB_BUSY) usb_kill_urb(sisusb->sisurbout[i]); }}/* Return 1 if ok, 0 if error (not all complete within timeout) */static intsisusb_wait_all_out_complete(struct sisusb_usb_data *sisusb){ int timeout = 5 * HZ, i = 1; wait_event_timeout(sisusb->wait_q, (i = sisusb_all_free(sisusb)), timeout); return i;}static intsisusb_outurb_available(struct sisusb_usb_data *sisusb){ int i; for (i = 0; i < sisusb->numobufs; i++) { if ((sisusb->urbstatus[i] & (SU_URB_BUSY|SU_URB_ALLOC)) == 0) return i; } return -1;}static intsisusb_get_free_outbuf(struct sisusb_usb_data *sisusb){ int i, timeout = 5 * HZ; wait_event_timeout(sisusb->wait_q, ((i = sisusb_outurb_available(sisusb)) >= 0), timeout); return i;}static intsisusb_alloc_outbuf(struct sisusb_usb_data *sisusb){ int i; i = sisusb_outurb_available(sisusb); if (i >= 0) sisusb->urbstatus[i] |= SU_URB_ALLOC; return i;}static voidsisusb_free_outbuf(struct sisusb_usb_data *sisusb, int index){ if ((index >= 0) && (index < sisusb->numobufs)) sisusb->urbstatus[index] &= ~SU_URB_ALLOC;}/* completion callback */static voidsisusb_bulk_completeout(struct urb *urb, struct pt_regs *regs){ struct sisusb_urb_context *context = urb->context; struct sisusb_usb_data *sisusb; if (!context) return; sisusb = context->sisusb; if (!sisusb || !sisusb->sisusb_dev || !sisusb->present) return;#ifndef SISUSB_DONTSYNC if (context->actual_length) *(context->actual_length) += urb->actual_length;#endif sisusb->urbstatus[context->urbindex] &= ~SU_URB_BUSY; wake_up(&sisusb->wait_q);}static intsisusb_bulkout_msg(struct sisusb_usb_data *sisusb, int index, unsigned int pipe, void *data, int len, int *actual_length, int timeout, unsigned int tflags, dma_addr_t transfer_dma){ struct urb *urb = sisusb->sisurbout[index]; int retval, byteswritten = 0; /* Set up URB */ urb->transfer_flags = 0; usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len, sisusb_bulk_completeout, &sisusb->urbout_context[index]); urb->transfer_flags |= tflags; urb->actual_length = 0; if ((urb->transfer_dma = transfer_dma)) urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* Set up context */ sisusb->urbout_context[index].actual_length = (timeout) ? NULL : actual_length; /* Declare this urb/buffer in use */ sisusb->urbstatus[index] |= SU_URB_BUSY; /* Submit URB */ retval = usb_submit_urb(urb, GFP_ATOMIC); /* If OK, and if timeout > 0, wait for completion */ if ((retval == 0) && timeout) { wait_event_timeout(sisusb->wait_q, (!(sisusb->urbstatus[index] & SU_URB_BUSY)), timeout); if (sisusb->urbstatus[index] & SU_URB_BUSY) { /* URB timed out... kill it and report error */ usb_kill_urb(urb); retval = -ETIMEDOUT; } else { /* Otherwise, report urb status */ retval = urb->status; byteswritten = urb->actual_length; } } if (actual_length) *actual_length = byteswritten; return retval;}/* 2. in-bulks *//* completion callback */static voidsisusb_bulk_completein(struct urb *urb, struct pt_regs *regs){ struct sisusb_usb_data *sisusb = urb->context; if (!sisusb || !sisusb->sisusb_dev || !sisusb->present) return; sisusb->completein = 1; wake_up(&sisusb->wait_q);}static intsisusb_bulkin_msg(struct sisusb_usb_data *sisusb, unsigned int pipe, void *data, int len, int *actual_length, int timeout, unsigned int tflags, dma_addr_t transfer_dma){ struct urb *urb = sisusb->sisurbin; int retval, readbytes = 0; urb->transfer_flags = 0; usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len, sisusb_bulk_completein, sisusb); urb->transfer_flags |= tflags; urb->actual_length = 0; if ((urb->transfer_dma = transfer_dma)) urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; sisusb->completein = 0; retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval == 0) { wait_event_timeout(sisusb->wait_q, sisusb->completein, timeout); if (!sisusb->completein) { /* URB timed out... kill it and report error */ usb_kill_urb(urb); retval = -ETIMEDOUT; } else { /* URB completed within timout */ retval = urb->status; readbytes = urb->actual_length; } } if (actual_length) *actual_length = readbytes; return retval;}/* Level 1: *//* Send a bulk message of variable size * * To copy the data from userspace, give pointer to "userbuffer", * to copy from (non-DMA) kernel memory, give "kernbuffer". If * both of these are NULL, it is assumed, that the transfer * buffer "sisusb->obuf[index]" is set up with the data to send. * Index is ignored if either kernbuffer or userbuffer is set. * If async is nonzero, URBs will be sent without waiting for * completion of the previous URB. * * (return 0 on success) */static int sisusb_send_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len, char *kernbuffer, const char __user *userbuffer, int index, ssize_t *bytes_written, unsigned int tflags, int async){ int result = 0, retry, count = len; int passsize, thispass, transferred_len = 0; int fromuser = (userbuffer != NULL) ? 1 : 0; int fromkern = (kernbuffer != NULL) ? 1 : 0; unsigned int pipe; char *buffer; (*bytes_written) = 0; /* Sanity check */ if (!sisusb || !sisusb->present || !sisusb->sisusb_dev) return -ENODEV; /* If we copy data from kernel or userspace, force the * allocation of a buffer/urb. If we have the data in * the transfer buffer[index] already, reuse the buffer/URB * if the length is > buffer size. (So, transmitting * large data amounts directly from the transfer buffer * treats the buffer as a ring buffer. However, we need * to sync in this case.) */ if (fromuser || fromkern) index = -1; else if (len > sisusb->obufsize) async = 0; pipe = usb_sndbulkpipe(sisusb->sisusb_dev, ep); do { passsize = thispass = (sisusb->obufsize < count) ? sisusb->obufsize : count; if (index < 0) index = sisusb_get_free_outbuf(sisusb); if (index < 0) return -EIO; buffer = sisusb->obuf[index]; if (fromuser) { if (copy_from_user(buffer, userbuffer, passsize)) return -EFAULT; userbuffer += passsize; } else if (fromkern) { memcpy(buffer, kernbuffer, passsize); kernbuffer += passsize; } retry = 5; while (thispass) { if (!sisusb->sisusb_dev) return -ENODEV; result = sisusb_bulkout_msg(sisusb, index, pipe, buffer, thispass, &transferred_len, async ? 0 : 5 * HZ, tflags, sisusb->transfer_dma_out[index]); if (result == -ETIMEDOUT) { /* Will not happen if async */ if (!retry--) return -ETIME; continue; } else if ((result == 0) && !async && transferred_len) { thispass -= transferred_len; if (thispass) { if (sisusb->transfer_dma_out) { /* If DMA, copy remaining * to beginning of buffer */ memcpy(buffer, buffer + transferred_len, thispass); } else { /* If not DMA, simply increase * the pointer */ buffer += transferred_len; } } } else break; }; if (result) return result; (*bytes_written) += passsize; count -= passsize; /* Force new allocation in next iteration */ if (fromuser || fromkern) index = -1; } while (count > 0); if (async) {#ifdef SISUSB_DONTSYNC (*bytes_written) = len; /* Some URBs/buffers might be busy */#else sisusb_wait_all_out_complete(sisusb); (*bytes_written) = transferred_len; /* All URBs and all buffers are available */#endif } return ((*bytes_written) == len) ? 0 : -EIO;}/* Receive a bulk message of variable size * * To copy the data to userspace, give pointer to "userbuffer", * to copy to kernel memory, give "kernbuffer". One of them * MUST be set. (There is no technique for letting the caller * read directly from the ibuf.) * */static int sisusb_recv_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len, void *kernbuffer, char __user *userbuffer, ssize_t *bytes_read, unsigned int tflags){ int result = 0, retry, count = len; int bufsize, thispass, transferred_len; unsigned int pipe; char *buffer; (*bytes_read) = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -