diff --git a/drivers/CH341SER_LINUX.zip b/drivers/CH341SER_LINUX.zip deleted file mode 100644 index 1edb402..0000000 Binary files a/drivers/CH341SER_LINUX.zip and /dev/null differ diff --git a/drivers/CH341SER_LINUX/Makefile b/drivers/CH341SER_LINUX/Makefile deleted file mode 100755 index 7c521b8..0000000 --- a/drivers/CH341SER_LINUX/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -ifeq ($(KERNELRELEASE), ) -KERNELDIR := /lib/modules/$(shell uname -r)/build -PWD :=$(shell pwd) -default: - $(MAKE) -C $(KERNELDIR) M=$(PWD) -clean: - rm -rf .tmp_versions Module.symvers *.mod.c *.o *.ko .*.cmd Module.markers modules.order -load: - modprobe usbserial - insmod ch34x.ko -unload: - rmmod ch34x -else - obj-m := ch34x.o -endif diff --git a/drivers/CH341SER_LINUX/README.md b/drivers/CH341SER_LINUX/README.md new file mode 100644 index 0000000..1c924b8 --- /dev/null +++ b/drivers/CH341SER_LINUX/README.md @@ -0,0 +1,22 @@ +# ch341 linux serial driver +## Description + +​ USB serial driver for USB to UART chip ch340, ch341, etc. In fact Linux mainline kernels have built-in ch341 serial driver since kernel version 2.6.24. The location is: drivers/usb/serial/ch341.c, it's a pity that the built-in driver cannot be kept up to date. We suggest our customers use this driver. + +1. Open "Terminal" +2. Switch to "driver" directory +3. Compile the driver using "make", you will see the module "ch341.ko" if successful +4. Type "sudo make load" or "sudo insmod ch341.ko" to load the driver dynamically +5. Type "sudo make unload" or "sudo rmmod ch341.ko" to unload the driver +6. Type "sudo make install" to make the driver work permanently +7. Type "sudo make uninstall" to remove the driver +8. You can refer to the link below to acquire uart application, you can use gcc or Cross-compile with cross-gcc + https://github.com/WCHSoftGroup/tty_uart + +​ Before the driver works, you should make sure that the usb device has been plugged in and is working properly, you can use shell command "lsusb" or "dmesg" to confirm that, USB VID of these devices are [1A86], you can view all IDs from the id table which defined in "ch341.c". + +​ If the device works well, the driver will created tty devices named "ttyCH341USBx" in /dev directory. + +## Note + +​ Any question, you can send feedback to mail: tech@wch.cn diff --git a/drivers/CH341SER_LINUX/ch34x.c b/drivers/CH341SER_LINUX/ch34x.c deleted file mode 100755 index 65a7393..0000000 --- a/drivers/CH341SER_LINUX/ch34x.c +++ /dev/null @@ -1,1338 +0,0 @@ -// 2013.7 -//******************************************** -//** Copyright (C) WCH 2002-2013 ****** -//** Web: http://www.winchiphead.com ****** -//******************************************** -//** Driver for USB to serial adaptor CH34X** -//** GCC ** -//******************************************** - -// Support linux kernel version 2.6.25 and later -// - -#include -#ifndef KERNEL_VERSION -#define KERNEL_VERSION(ver, rel, seq) ((ver << 16) | (rel << 8) | (seq)) -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -//#include -#include -#include - -#define DRIVER_DESC "WCH CH34x USB to serial adaptor driver" -#define DRIVER_AUTHOR "" - -#define CH34x_VENDOR_ID 0x1A86 -#define CH340_PRODUCT_ID 0x7523 -#define CH341_PRODUCT_ID 0x5523 - -#define CH34x_CLOSING_WAIT (30 * HZ) - -#define CH34x_BUF_SIZE 1024 -#define CH34x_TMP_BUF_SIZE 1024 - -//Vendor define -#define VENDOR_WRITE_TYPE 0x40 -#define VENDOR_READ_TYPE 0xC0 - -#define VENDOR_READ 0x95 -#define VENDOR_WRITE 0x9A -#define VENDOR_SERIAL_INIT 0xA1 -#define VENDOR_MODEM_OUT 0xA4 -#define VENDOR_VERSION 0x5F - -//For CMD 0xA4 -#define UART_CTS 0x01 -#define UART_DSR 0x02 -#define UART_RING 0x04 -#define UART_DCD 0x08 -#define CONTROL_OUT 0x10 -#define CONTROL_DTR 0x20 -#define CONTROL_RTS 0x40 - -//Uart state -#define UART_STATE 0x00 -#define UART_OVERRUN_ERROR 0x01 -#define UART_BREAK_ERROR //no define -#define UART_PARITY_ERROR 0x02 -#define UART_FRAME_ERROR 0x06 -#define UART_RECV_ERROR 0x02 -#define UART_STATE_TRANSIENT_MASK 0x07 - -//Port state -#define PORTA_STATE 0x01 -#define PORTB_STATE 0x02 -#define PORTC_STATE 0x03 - -//CH34x Baud Rate -#define CH34x_BAUDRATE_FACTOR 1532620800 -#define CH34x_BAUDRATE_DIVMAX 3 - -//#define DEBUG_CH34x -#undef DEBUG_CH34x - -#ifdef DEBUG_CH34x -#define dbg_ch34x( format, arg... ) \ - printk( KERN_DEBUG "%d: " format "\n", __LINE__, ##arg ) -#else -#define dbg_ch34x( format, arg... ) \ -do{ \ - if(0) \ - printk(KERN_DEBUG "%d: " format "\n", __LINE__, ##arg); \ -} while (0) -#endif - -#ifdef DEBUG_CH34x -#define err_ch34x( format, arg... ) \ - printk(KERN_ERR KBUILD_MODNAME ": " format "\n", ##arg) -#else -#define err_ch34x( format, arg... ) \ -do{ \ - if(0) \ - printk( KERN_ERR KBUILD_MODNAME ": " format "\n", ##arg)\ -}while(0) -#endif - -// For debug -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,7,1)) -static int debug = 1; -#endif - -static DECLARE_WAIT_QUEUE_HEAD(wq); -static int wait_flag = 0; - -struct ch34x_buf { - unsigned int buf_size; - char *buf_buf; - char *buf_get; - char *buf_put; -}; - -struct ch34x_private { - spinlock_t lock; //access lock - struct ch34x_buf *buf; - int write_urb_in_use; - unsigned baud_rate; - wait_queue_head_t delta_msr_wait; - u8 line_control; - u8 line_status; - u8 termios_initialized; -}; - -static struct usb_device_id id_table [] = { - { USB_DEVICE(CH34x_VENDOR_ID, CH340_PRODUCT_ID) }, - { USB_DEVICE(CH34x_VENDOR_ID, CH341_PRODUCT_ID) }, - { } //End -}; -MODULE_DEVICE_TABLE( usb, id_table ); - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,5,2)) -static struct usb_driver ch34x_driver = { - .name = "ch34x", - .probe = usb_serial_probe, - .disconnect = usb_serial_disconnect, - .id_table = id_table, - .suspend = usb_serial_suspend, - .resume = usb_serial_resume, - .no_dynamic_id = 1, - .supports_autosuspend = 1, -}; -#endif - -// ch34x_buf_alloc -// Allocate a circular buffer and all associated memory -static struct ch34x_buf *ch34x_buf_alloc( unsigned int size ) -{ - struct ch34x_buf *pb; - - if( size == 0 ) - return NULL; - - pb = kmalloc( sizeof(struct ch34x_buf), GFP_KERNEL ); - if( pb == NULL ) - return NULL; - - pb->buf_buf = kmalloc( size, GFP_KERNEL ); - if( pb->buf_buf == NULL ) { - kfree(pb); - return NULL; - } - - pb->buf_size = size; - pb->buf_get = pb->buf_put = pb->buf_buf; - - return pb; -} - -// ch34x_buf_free -// Free the buffer and all associated memory -static void ch34x_buf_free( struct ch34x_buf *pb ) -{ - if( pb ) { - kfree( pb->buf_buf ); - kfree( pb ); - } -} - -// ch34x_buf_clear -// Clear out all data in the circular buffer -static void ch34x_buf_clear( struct ch34x_buf *pb ) -{ - if( pb != NULL ) - pb->buf_get = pb->buf_put; - // equivalent to a get of all data available -} - -// ch34x_buf_data_avail -// Return the number of bytes of data available in he circular buffer -static unsigned int ch34x_buf_data_avail( struct ch34x_buf *pb ) -{ - if( pb == NULL ) - return 0; - - return ((pb->buf_size + pb->buf_put - pb->buf_get) % pb->buf_size ); -} - -// ch34x_buf_space_avail -// Return the number of bytes of space available in the circular -static unsigned int ch34x_buf_space_avail( struct ch34x_buf *pb ) -{ - if( pb == NULL ) - return 0; - - return ((pb->buf_size + pb->buf_get - pb->buf_put - 1) % pb->buf_size ); -} - -// ch34x_buf_put -// Copy data from a user buffer and put it into the circular buffer. -// Restrict to the amount of space available -// Return the number of bytes copied -static unsigned int ch34x_buf_put( struct ch34x_buf *pb, - const char *buf, unsigned int count ) -{ - unsigned int len; - - if( pb == NULL ) - return 0; - - len = ch34x_buf_space_avail(pb); - if( count > len ) - count = len; - else if( count == 0 ) - return 0; - - len = pb->buf_buf + pb->buf_size - pb->buf_put; - if( count > len ) { - memcpy( pb->buf_put, buf, len ); - memcpy( pb->buf_buf, buf+len, count - len ); - pb->buf_put = pb->buf_buf + count - len; - } - else { - memcpy( pb->buf_put, buf, count ); - if( count < len ) - pb->buf_put += count; - else if( count == len ) - pb->buf_put = pb->buf_buf; - } - - return count; -} - -static unsigned int ch34x_buf_get( struct ch34x_buf *pb, - char *buf, unsigned int count ) -{ - unsigned int len; - - if( pb == NULL ) - return 0; - - len = ch34x_buf_data_avail(pb); - if( count > len ) - count = len; - else if( count == 0 ) - return 0; - - len = pb->buf_buf + pb->buf_size - pb->buf_get; - if( count > len ) { - memcpy( buf, pb->buf_get, len ); - memcpy( buf+len, pb->buf_buf, count - len ); - pb->buf_get = pb->buf_buf + count - len; - } - else { - memcpy( buf, pb->buf_get, count ); - if( count < len ) - pb->buf_get += count; - else if( count == len ) - pb->buf_get = pb->buf_buf; - } - - return count; -} - -static int ch34x_vendor_read( __u8 request, - __u16 value, - __u16 index, - struct usb_serial *serial, - unsigned char *buf, - __u16 len ) -{ - int retval; - - retval = usb_control_msg( serial->dev, usb_rcvctrlpipe(serial->dev, 0), - request, VENDOR_READ_TYPE, value, index, buf, len, 1000 ); - dbg_ch34x("0x%x:0x%x:0x%x:0x%x %d - %d", - VENDOR_READ_TYPE, request, value, index, retval, len ); - - return retval; -} - -static int ch34x_vendor_write( __u8 request, - __u16 value, - __u16 index, - struct usb_serial *serial, - unsigned char *buf, - __u16 len ) -{ - int retval; - - retval = usb_control_msg( serial->dev, - usb_sndctrlpipe(serial->dev, 0), - request, - VENDOR_WRITE_TYPE, - value, index, buf, len, 1000 ); - - return retval; -} - -static int set_control_lines( struct usb_serial *serial, - u8 value ) -{ - int retval; - - retval = ch34x_vendor_write( VENDOR_MODEM_OUT, (unsigned short)value, - 0x0000, serial, NULL, 0x00 ); - dbg_ch34x("%s - value=%d, retval=%d", __func__, value, retval ); - - return retval; -} - -static int ch34x_get_baud_rate( unsigned int baud_rate, - unsigned char *a, unsigned char *b ) -{ - unsigned long factor = 0; - short divisor = 0; - - if( !baud_rate ) - return -EINVAL; - - factor = (CH34x_BAUDRATE_FACTOR / baud_rate); - divisor = CH34x_BAUDRATE_DIVMAX; - - while( (factor > 0xfff0) && divisor ) { - factor >>= 3; - divisor --; - } - - if( factor > 0xfff0 ) - return -EINVAL; - - factor = 0x10000 - factor; - *a = (factor & 0xff00) >> 8; - *b = divisor; - - return 0; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static void ch34x_set_termios( struct tty_struct *tty, - struct usb_serial_port *port, struct ktermios *old_termios ) -{ -#else -static void ch34x_set_termios( struct usb_serial_port *port, - struct ktermios *old_termios ) -{ - struct tty_struct *tty = port->tty; -#endif - struct usb_serial *serial = port->serial; - struct ch34x_private *priv = usb_get_serial_port_data(port); -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) //sure - struct ktermios *termios = &tty->termios; -#else - struct ktermios *termios = tty->termios; -#endif - unsigned int baud_rate; - unsigned int cflag; - unsigned long flags; - u8 control; - - unsigned char divisor = 0; - unsigned char reg_count = 0; - unsigned char factor = 0; - unsigned char reg_value = 0; - unsigned short value = 0; - unsigned short index = 0; -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - spin_lock_irqsave( &priv->lock, flags ); - if( !priv->termios_initialized ) { - *(termios) = tty_std_termios; - termios->c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; - termios->c_ispeed = 9600; - termios->c_ospeed = 9600; - priv->termios_initialized = 1; - } - spin_unlock_irqrestore( &priv->lock, flags ); - - /* - * The ch34x is reported to lose bytes if you change serial setting - * even to the same vaules as before. Thus we actually need to filter - * in this specific case. - */ - if( !tty_termios_hw_change(termios, old_termios) ) - return; - - cflag = termios->c_cflag; -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s (%d) cflag=0x%x\n", __func__, port->number, cflag); -#else - dbg_ch34x("%s (%d) cflag=0x%x\n", __func__, port->port_number, cflag); -#endif - // Get the byte size - switch( cflag & CSIZE ) - { - case CS5: - reg_value |= 0x00; - break; - case CS6: - reg_value |= 0x01; - break; - case CS7: - reg_value |= 0x02; - break; - case CS8: - reg_value |= 0x03; - break; - default: - reg_value |= 0x03; - break; - } - dbg_ch34x("%s - data bits = %d", __func__, reg_value + 0x05 ); - - // Figure out the stop bits - if( cflag & CSTOPB ) { - reg_value |= 0x04; - dbg_ch34x("%s - stop bits = 2", __func__); - } - else - dbg_ch34x("%s - stop bits = 1", __func__); - - // Determine the parity - if( cflag & PARENB ) - if( cflag & PARODD ) { - reg_value |= (0x08 | 0x00); - dbg_ch34x("%s - parity = odd", __func__); - } - else { - reg_value |= (0x08 | 0x10); - dbg_ch34x("%s - parity = even", __func__); - } - else - dbg_ch34x("%s - parity = none", __func__); - - // Determine the baud rate - baud_rate = tty_get_baud_rate( tty ); - dbg_ch34x("%s = baud_rate = %d", __func__, baud_rate); - ch34x_get_baud_rate( baud_rate, &factor, &divisor ); - dbg_ch34x("----->>>> baud_rate = %d, factor:0x%x, divisor:0x%x", - baud_rate, factor, divisor ); - - //enable SFR_UART RX and TX - reg_value |= 0xc0; - //enable SFR_UART Control register and timer - reg_count |= 0x9c; - - value |= reg_count; - value |= (unsigned short)reg_value << 8; - index |= 0x80 | divisor; - index |= (unsigned short)factor << 8; - ch34x_vendor_write( VENDOR_SERIAL_INIT, value, index, serial, NULL, 0 ); - - // change control lines if we are switching to or from B0 - spin_lock_irqsave( &priv->lock, flags ); - control = priv->line_control; - if( (cflag & CBAUD) == B0 ) - priv->line_control &= ~(CONTROL_DTR | CONTROL_RTS); - else - priv->line_control |= (CONTROL_DTR | CONTROL_RTS); - - if( control != priv->line_control ) { - control = priv->line_control; - spin_unlock_irqrestore( &priv->lock, flags ); - set_control_lines( serial, control ); - } - else - spin_unlock_irqrestore( &priv->lock, flags ); - - if( cflag & CRTSCTS ) - ch34x_vendor_write( VENDOR_WRITE, 0x2727, 0x0101, serial, NULL, 0); - - // FIXME: Need to read back resulting baud rate - if( baud_rate ) - tty_encode_baud_rate(tty, baud_rate, baud_rate); - -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3)) -static int ch34x_tiocmget( struct tty_struct *tty ) -{ - struct usb_serial_port *port = tty->driver_data; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_tiocmget( struct tty_struct *tty, - struct file *filp ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_tiocmget( struct usb_serial_port *port, - struct file *filp ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - unsigned int mcr; - /*unsigned int msr;*/ - unsigned int retval; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - if( !usb_get_intfdata( port->serial->interface) ) - return -ENODEV; - - spin_lock_irqsave( &priv->lock, flags ); - mcr = priv->line_control; - spin_unlock_irqrestore( &priv->lock, flags ); - - retval = ((mcr & CONTROL_DTR) ? TIOCM_DTR : 0) | - ((mcr & CONTROL_RTS) ? TIOCM_RTS : 0) | - ((mcr & UART_CTS) ? TIOCM_CTS : 0) | - ((mcr & UART_DSR) ? TIOCM_DSR : 0) | - ((mcr & UART_RING) ? TIOCM_RI : 0) | - ((mcr & UART_DCD) ? TIOCM_CD : 0); - - dbg_ch34x("%s - retval=0x%x", __func__, retval); - - return retval; -} - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) ) -static void ch34x_close( struct tty_struct *tty, - struct usb_serial_port *port, struct file *filp ) -{ -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) ) -static void ch34x_close( struct usb_serial_port *port ) -{ - struct tty_struct *tty = port->port.tty; -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) -static void ch34x_close( struct usb_serial_port *port, - struct file *filp ) -{ - - struct tty_struct *tty = port->tty; -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - unsigned int c_cflag; - int bps; - long timeout; - wait_queue_t wait; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif -// dbg_ch34x("Addr of priv: 0x%x", priv); -// dbg_ch34x("Addr of tty: 0x%x", tty); - // wait for data do drain from the buffer -/* - spin_lock_irqsave( &priv->lock, flags ); - timeout = CH34x_CLOSING_WAIT; - init_waitqueue_entry( &wait, current ); - add_wait_queue( &tty->write_wait, &wait ); - for(;;) { - set_current_state( TASK_INTERRUPTIBLE ); - if( ch34x_buf_data_avail(priv->buf) == 0 || timeout == 0 || - signal_pending(current) || port->serial->disconnected ) - break; - spin_unlock_irqrestore( &priv->lock, flags ); - timeout = schedule_timeout( timeout ); - spin_lock_irqsave( &priv->lock, flags ); - } - set_current_state( TASK_RUNNING ); - remove_wait_queue( &tty->write_wait, &wait ); -*/ - spin_lock_irqsave( &priv->lock, flags ); - // clear out any remaining data in the buffer - ch34x_buf_clear( priv->buf ); - spin_unlock_irqrestore( &priv->lock, flags ); - -/* - bps = tty_get_baud_rate( tty ); - if( bps > 1200 ) - timeout = max( (HZ * 2560) / bps, HZ / 10 ); - else - timeout = 2 * HZ; - schedule_timeout_interruptible(timeout); -*/ - // shutdown our urbs - usb_kill_urb(port->interrupt_in_urb); - usb_kill_urb(port->read_urb); - usb_kill_urb(port->write_urb); - //usb_serial_generic_close(port, filp); - - if( tty ) { -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) //sure - c_cflag = tty->termios.c_cflag; -#else - c_cflag = tty->termios->c_cflag; -#endif - if( c_cflag & HUPCL ) { - // drop DTR and RTS - spin_lock_irqsave( &priv->lock, flags ); - priv->line_control = 0; - spin_unlock_irqrestore( &priv->lock, flags ); - set_control_lines( port->serial, 0 ); - } - } - -// usb_serial_generic_close(port); -// usb_kill_urb(port->interrupt_in_urb); - -} - -// kernel version -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) \ - && LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_open( struct tty_struct *tty, - struct usb_serial_port *port, struct file *filp ) -{ -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) -static int ch34x_open( struct tty_struct *tty, - struct usb_serial_port *port ) -{ -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) -static int ch34x_open( struct usb_serial_port *port, - struct file *filp ) -{ - struct tty_struct *tty = port->tty; -#endif - struct ktermios tmp_termios; - struct usb_serial *serial = port->serial; - int retval; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number ); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number ); -#endif - usb_clear_halt( serial->dev, port->write_urb->pipe ); - usb_clear_halt( serial->dev, port->read_urb->pipe ); - - if( tty ) { -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) - ch34x_set_termios( tty, port, &tmp_termios ); -#else - ch34x_set_termios( port, &tmp_termios ); -#endif - } - - dbg_ch34x("%s - submit read urb", __func__); - port->read_urb->dev = serial->dev; - retval = usb_submit_urb( port->read_urb, GFP_KERNEL ); - if(retval) { - dev_err( &port->dev, "%s - failed submit read urb,error %d\n", - __func__, retval ); -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) ) - ch34x_close(tty, port, NULL); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) - ch34x_close(port); -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) - ch34x_close(port, filp); -#endif - goto err_out; - } - - dbg_ch34x("%s - submit interrupt urb", __func__ ); - port->interrupt_in_urb->dev = serial->dev; - retval = usb_submit_urb( port->interrupt_in_urb, GFP_KERNEL ); - if(retval) { - dev_err( &port->dev, "%s - failed submit interrupt urb,error %d\n", - __func__, retval ); -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) && \ - LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) ) - ch34x_close(tty, port, NULL); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) - ch34x_close(port); -#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) - ch34x_close(port, filp); -#endif - goto err_out; - } - -err_out: - return retval; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3)) -static int ch34x_tiocmset( struct tty_struct *tty, - unsigned int set, unsigned int clear ) -{ - struct usb_serial_port *port = tty->driver_data; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_tiocmset( struct tty_struct *tty, - struct file *filp, unsigned int set, unsigned int clear ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_tiocmset( struct usb_serial_port *port, - struct file *filp, unsigned int set, unsigned int clear ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - /*unsigned int mcr = priv->line_control;*/ - u8 control; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - if( !usb_get_intfdata(port->serial->interface) ) - return -ENODEV; - - spin_lock_irqsave( &priv->lock, flags ); - if( set & TIOCM_RTS ) - priv->line_control |= CONTROL_RTS; - if( set & TIOCM_DTR ) - priv->line_control |= CONTROL_DTR; - if( clear & TIOCM_RTS ) - priv->line_control &= ~CONTROL_RTS; - if( clear & TIOCM_DTR ) - priv->line_control &= ~CONTROL_DTR; - control = priv->line_control; - spin_unlock_irqrestore( &priv->lock, flags ); - - return set_control_lines( port->serial, control ); -} - -static int wait_modem_info( struct usb_serial_port *port, - unsigned int arg ) -{ - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - unsigned int prevstatus; - unsigned int status; - unsigned int changed; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s -port:%d", __func__, port->number); -#else - dbg_ch34x("%s -port:%d", __func__, port->port_number); -#endif - spin_lock_irqsave( &priv->lock, flags ); - prevstatus = priv->line_status; - spin_unlock_irqrestore( &priv->lock, flags ); - - while(1) { - wait_event_interruptible( wq, wait_flag != 0 ); - wait_flag = 0; - // see if a signal did it - if( signal_pending(current) ) - return -ERESTARTSYS; - - spin_lock_irqsave( &priv->lock, flags ); - status = priv->line_status; - spin_unlock_irqrestore( &priv->lock, flags ); - - changed = prevstatus ^ status; - - if( ((arg & TIOCM_RNG) && (changed & UART_RING)) || - ((arg & TIOCM_DSR) && (changed & UART_DSR)) || - ((arg & TIOCM_CD) && (changed & UART_DCD)) || - ((arg & TIOCM_CTS) && (changed & UART_CTS)) ) - return 0; - - prevstatus = status; - } - - // Not reatched - return 0; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,3)) -static int ch34x_ioctl( struct tty_struct *tty, - unsigned int cmd, unsigned long arg ) -{ - struct usb_serial_port *port = tty->driver_data; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_ioctl( struct tty_struct *tty, - struct file *filp, unsigned int cmd, unsigned long arg ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_ioctl( struct usb_serial_port *port, - struct file *filp, unsigned int cmd, unsigned long arg ) -{ - //struct usb_serial_port *port = tty->driver_data; -#endif -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d, cmd=0x%04x", __func__, port->number, cmd); -#else - dbg_ch34x("%s - port:%d, cmd=0x%04x", __func__, port->port_number, cmd); -#endif - switch(cmd) - { - // Note here - case TIOCMIWAIT: -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d TIOCMIWAIT", __func__, port->number); -#else - dbg_ch34x("%s - port:%d TIOCMIWAIT", __func__, port->port_number); -#endif - - return wait_modem_info(port, arg); - default: - dbg_ch34x("%s not supported=0x%04x", __func__, cmd); - break; - } - - return -ENOIOCTLCMD; -} - -static void ch34x_send( struct usb_serial_port *port ) -{ - int count; - int retval; - struct ch34x_private *priv = usb_get_serial_port_data( port ); - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - spin_lock_irqsave( &priv->lock, flags ); - if( priv->write_urb_in_use ) { - spin_unlock_irqrestore( &priv->lock, flags ); - return; - } - - count = ch34x_buf_get( priv->buf, port->write_urb->transfer_buffer, - port->bulk_out_size ); - if( count == 0 ) { - spin_unlock_irqrestore( &priv->lock, flags ); - return; - } - - priv->write_urb_in_use = 1; - spin_unlock_irqrestore( &priv->lock, flags ); - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) - usb_serial_debug_data( &port->dev, __func__, count, - port->write_urb->transfer_buffer ); -#else - usb_serial_debug_data( debug, &port->dev, __func__, count, - port->write_urb->transfer_buffer ); -#endif - - port->write_urb->transfer_buffer_length = count; - port->write_urb->dev = port->serial->dev; - retval = usb_submit_urb( port->write_urb, GFP_ATOMIC ); - if( retval ) { - dev_err( &port->dev, "%s - failed submitting write urb,error %d\n" - , __func__, retval ); - priv->write_urb_in_use = 0; - // reschedule ch34x_send - } - - usb_serial_port_softint( port ); -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_write( struct tty_struct *tty, - struct usb_serial_port *port, const unsigned char *buf, int count ) -#else -static int ch34x_write( struct usb_serial_port *port, - const unsigned char *buf, int count ) -#endif -{ - struct ch34x_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d, %d bytes", __func__, port->number, count); -#else - dbg_ch34x("%s - port:%d, %d bytes", __func__, port->port_number, count); -#endif - - if( !count ) - return count; - - spin_lock_irqsave( &priv->lock, flags ); - count = ch34x_buf_put( priv->buf, buf, count ); - spin_unlock_irqrestore( &priv->lock, flags ); - - ch34x_send(port); - - return count; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_write_room( struct tty_struct *tty ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_write_room( struct usb_serial_port *port ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data( port ); - int room = 0; - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - spin_lock_irqsave( &priv->lock, flags ); - room = ch34x_buf_space_avail( priv->buf ); - spin_unlock_irqrestore( &priv->lock, flags ); - - dbg_ch34x("%s - room:%d", __func__, room ); - return room; -} - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) -static int ch34x_chars_in_buffer( struct tty_struct *tty ) -{ - struct usb_serial_port *port = tty->driver_data; -#else -static int ch34x_chars_in_buffer( struct usb_serial_port *port ) -{ -#endif - struct ch34x_private *priv = usb_get_serial_port_data(port); - int chars = 0; - unsigned long flags; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - - spin_lock_irqsave( &priv->lock, flags ); - chars = ch34x_buf_data_avail( priv->buf ); - spin_unlock_irqrestore( &priv->lock, flags ); - - dbg_ch34x("%s - chars:%d", __func__, chars ); - - return chars; -} - -static int ch34x_attach( struct usb_serial *serial ) -{ - struct ch34x_private *priv; - int i; - char buf[8]; - - dbg_ch34x("%s", __func__); - - for( i = 0; i < serial->num_ports; ++i ) { - priv = kzalloc( sizeof(struct ch34x_private), GFP_KERNEL ); - if( !priv ) - goto cleanup; - spin_lock_init( &priv->lock ); - priv->buf = ch34x_buf_alloc( CH34x_BUF_SIZE ); - if( priv->buf == NULL ) { - kfree( priv ); - goto cleanup; - } - init_waitqueue_head( &priv->delta_msr_wait ); - usb_set_serial_port_data( serial->port[i], priv ); - } - - ch34x_vendor_read( VENDOR_VERSION, 0x0000, 0x0000, - serial, buf, 0x02 ); - ch34x_vendor_write( VENDOR_SERIAL_INIT, 0x0000, 0x0000, - serial, NULL, 0x00 ); - ch34x_vendor_write( VENDOR_WRITE, 0x1312, 0xD982, - serial, NULL, 0x00 ); - ch34x_vendor_write( VENDOR_WRITE, 0x0F2C, 0x0004, - serial, NULL, 0x00 ); - ch34x_vendor_read( VENDOR_READ, 0x2518, 0x0000, - serial, buf, 0x02 ); - ch34x_vendor_write( VENDOR_WRITE, 0x2727, 0x0000, - serial, NULL, 0x00 ); - ch34x_vendor_write( VENDOR_MODEM_OUT, 0x009F, 0x0000, - serial, NULL, 0x00 ); - - return 0; - -cleanup: - for( --i; i >= 0; --i ) { - priv = usb_get_serial_port_data( serial->port[i] ); - ch34x_buf_free( priv->buf ); - kfree( priv ); - usb_set_serial_port_data( serial->port[i], NULL ); - } - - return -ENOMEM; -} - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)) -static void ch34x_shutdown( struct usb_serial *serial ) -{ - struct ch34x_private *priv; - int i; - - dbg_ch34x("%s", __func__); - - for( i = 0; i < serial->num_ports; ++i ) { - priv = usb_get_serial_port_data( serial->port[i] ); - if( priv ) { - ch34x_buf_free( priv->buf ); - kfree( priv ); - usb_set_serial_port_data( serial->port[i], NULL ); - } - } -} -#endif - -static void ch34x_update_line_status( struct usb_serial_port *port, - unsigned char *data, unsigned int actual_length ) -{ - struct ch34x_private *priv = usb_get_serial_port_data( port ); - unsigned long flags; - u8 length = UART_STATE + 0x04; - - if( actual_length < length ) - return; - - // Save off the uart status for others to look at - spin_lock_irqsave( &priv->lock, flags ); - priv->line_status = data[UART_STATE]; - priv->line_control = data[PORTB_STATE]; - spin_unlock_irqrestore( &priv->lock, flags ); - wait_flag = 1; - wake_up_interruptible( &priv->delta_msr_wait ); -} - -static void ch34x_read_int_callback( struct urb *urb ) -{ - struct usb_serial_port *port = (struct usb_serial_port *)urb->context; - unsigned char *data = urb->transfer_buffer; - unsigned int actual_length = urb->actual_length; - int status = urb->status; - int retval; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s port:%d", __func__, port->number ); -#else - dbg_ch34x("%s port:%d", __func__, port->port_number ); -#endif - switch( status ) { - case 0: //success - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: //this urb is terminated, clean up - dbg_ch34x("%s - urb shutting down with status:%d", __func__, status ); - return; - default: - dbg_ch34x("%s - nonzero urb status received:%d", __func__, status ); - goto exit; - } - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) - usb_serial_debug_data( &port->dev, __func__, - urb->actual_length, urb->transfer_buffer ); -#else - usb_serial_debug_data( debug, &port->dev, - __func__, urb->actual_length, urb->transfer_buffer ); -#endif - - ch34x_update_line_status( port, data, actual_length ); - -exit: - retval = usb_submit_urb( urb, GFP_ATOMIC ); - if( retval ) - dev_err( &urb->dev->dev, "%s - usb_submit_urb failed with result %d\n", - __func__, retval ); -} - -static void ch34x_read_bulk_callback( struct urb *urb ) -{ - struct usb_serial_port *port = (struct usb_serial_port *)urb->context; - struct ch34x_private *priv = usb_get_serial_port_data( port ); - struct tty_struct *tty; - unsigned char *data = urb->transfer_buffer; - unsigned long flags; - int i; - int retval; - int status = urb->status; - u8 line_status; - char tty_flag; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number ); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number); -#endif - if( status ) { - dbg_ch34x("%s - urb status=%d", __func__, status ); - if( status == -EPROTO ) { - // CH34x mysteriously fails with -EPROTO reschedule the read - dbg_ch34x("%s - caught -EPROTO, resubmitting the urb", __func__); - urb->dev = port->serial->dev; - retval = usb_submit_urb( urb, GFP_ATOMIC ); - if( retval ) { - dev_err( &urb->dev->dev, - "%s - failed resubmitting read urb, error %d\n", - __func__, retval ); - return; - } - } - - dbg_ch34x("%s - unable to handle the error, exiting.", __func__); - return; - } - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,1)) - usb_serial_debug_data( &port->dev, __func__, - urb->actual_length, data ); -#else - usb_serial_debug_data( debug, &port->dev, - __func__, urb->actual_length, data ); -#endif - - // get tty_flag from status - tty_flag = TTY_NORMAL; - - spin_lock_irqsave( &priv->lock, flags ); - line_status = priv->line_status; - priv->line_status &= ~UART_STATE_TRANSIENT_MASK; - spin_unlock_irqrestore( &priv->lock, flags ); - wait_flag = 1; - wake_up_interruptible( &priv->delta_msr_wait ); - - // break takes precedence over parity, - // which takes precedence over framing errors - if( line_status & UART_PARITY_ERROR ) - tty_flag = TTY_PARITY; - else if( line_status & UART_OVERRUN_ERROR ) - tty_flag = TTY_OVERRUN; - else if( line_status & UART_FRAME_ERROR ) - tty_flag = TTY_FRAME; - dbg_ch34x("%s - tty_flag=%d", __func__, tty_flag); - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) - tty = port->port.tty; -#else - tty = port->tty; -#endif - if( tty && urb->actual_length ) { -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_buffer_request_room( tty->port, urb->actual_length + 1); -#else - tty_buffer_request_room( tty, urb->actual_length + 1 ); -#endif - // overrun is special, not associated with a char - if( line_status & UART_OVERRUN_ERROR ) -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_insert_flip_char( tty->port, 0, TTY_OVERRUN ); -#else - tty_insert_flip_char( tty, 0, TTY_OVERRUN ); -#endif - for( i = 0; i < urb->actual_length; ++i ) -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_insert_flip_char( tty->port, data[i], tty_flag ); -#else - tty_insert_flip_char( tty, data[i], tty_flag ); -#endif - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,9,1)) - tty_flip_buffer_push( tty->port ); -#else - tty_flip_buffer_push( tty ); -#endif - } - - //Schedule the next read _if_ we are still open -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) - if( port->open_count ) -#endif - { - urb->dev = port->serial->dev; - retval = usb_submit_urb( urb, GFP_ATOMIC ); - if( retval ) - dev_err( &urb->dev->dev, - "%s - fialed resubmitting read urb, error %d\n", - __func__, retval ); - } - - return; -} - -static void ch34x_write_bulk_callback( struct urb *urb ) -{ - struct usb_serial_port *port = (struct usb_serial_port *)urb->context; - struct ch34x_private *priv = usb_get_serial_port_data(port); - int retval; - int status = urb->status; - -#if(LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)) - dbg_ch34x("%s - port:%d", __func__, port->number ); -#else - dbg_ch34x("%s - port:%d", __func__, port->port_number ); -#endif - switch( status ) { - case 0: //success - break; - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - // this urb is terminated, clean up - dbg_ch34x("%s - urb shutting down with status:%d", __func__, status); - priv->write_urb_in_use = 0; - return; - default: - // error in the urb, so we have to resubmit it - dbg_ch34x("%s - Overflow in write", __func__); - dbg_ch34x("%s - nonzero write bulk status received:%d", __func__, status); - port->write_urb->transfer_buffer_length = 1; - port->write_urb->dev = port->serial->dev; - retval = usb_submit_urb(port->write_urb, GFP_ATOMIC); - if( retval ) - dev_err( &urb->dev->dev, - "%s - failed resubmitting write urv, error:%d\n", - __func__, retval ); - else - return; - } - - priv->write_urb_in_use = 0; - - // send any buffered data - ch34x_send(port); -} - -static struct usb_serial_driver ch34x_device = { - .driver = { - .owner = THIS_MODULE, - .name = "ch34x", - }, - .id_table = id_table, -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1)) - .usb_driver = &ch34x_driver, -#endif - .num_ports = 1, - .open = ch34x_open, - .close = ch34x_close, - .write = ch34x_write, - .ioctl = ch34x_ioctl, - .set_termios = ch34x_set_termios, - .tiocmget = ch34x_tiocmget, - .tiocmset = ch34x_tiocmset, - .read_bulk_callback = ch34x_read_bulk_callback, - .read_int_callback = ch34x_read_int_callback, - .write_bulk_callback = ch34x_write_bulk_callback, - .write_room = ch34x_write_room, - .chars_in_buffer = ch34x_chars_in_buffer, - .attach = ch34x_attach, -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) ) - .shutdown = ch34x_shutdown, -#endif -}; - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5)) -static struct usb_serial_driver *const serial_driver [] = { - &ch34x_device, NULL -}; -#endif - - -static int __init ch34x_init(void) -{ -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,4,5)) - int retval = 0; - - retval = usb_serial_register( &ch34x_device ); - if( retval ) { - goto err_usb_serial_register; - } - retval = usb_register( &ch34x_driver ); - if( retval ) { - goto err_usb_register; - } -#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)) - info( DRIVER_DESC ); -#endif - return 0; - -err_usb_register: - usb_deregister( &ch34x_driver ); -err_usb_serial_register: - usb_serial_deregister( &ch34x_device ); - return retval; -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,1)) - return usb_serial_register_drivers( serial_driver, - KBUILD_MODNAME, id_table ); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5) && \ - LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1)) - return usb_serial_register_drivers(&ch34x_driver, serial_driver); -#endif -} - -static void __exit ch34x_exit(void) -{ -#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,4,5)) - usb_deregister( &ch34x_driver ); - usb_serial_deregister( &ch34x_device ); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,1)) - usb_serial_deregister_drivers( serial_driver ); -#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,5) && \ - LINUX_VERSION_CODE < KERNEL_VERSION(3,5,1)) - usb_serial_deregister_drivers(&ch34x_driver, serial_driver); -#endif -} - -module_init( ch34x_init ); -module_exit( ch34x_exit ); - - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_LICENSE("GPL"); diff --git a/drivers/CH341SER_LINUX/driver/Makefile b/drivers/CH341SER_LINUX/driver/Makefile new file mode 100644 index 0000000..ff44f60 --- /dev/null +++ b/drivers/CH341SER_LINUX/driver/Makefile @@ -0,0 +1,21 @@ +ifeq ($(KERNELRELEASE), ) +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD :=$(shell pwd) +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) +clean: + rm -rf *.mk .tmp_versions Module.symvers *.mod.c *.o *.ko .*.cmd Module.markers modules.order +load: + insmod ch341.ko +unload: + rmmod ch341 +install: default + mkdir -p /lib/modules/$(shell uname -r)/kernel/drivers/usb/serial/ + cp -f ./ch341.ko /lib/modules/$(shell uname -r)/kernel/drivers/usb/serial/ + depmod -a +uninstall: + rm -rf /lib/modules/$(shell uname -r)/kernel/drivers/usb/serial/ch341.ko + depmod -a +else + obj-m := ch341.o +endif diff --git a/drivers/CH341SER_LINUX/driver/ch341.c b/drivers/CH341SER_LINUX/driver/ch341.c new file mode 100644 index 0000000..755e02e --- /dev/null +++ b/drivers/CH341SER_LINUX/driver/ch341.c @@ -0,0 +1,1710 @@ +/* + * USB serial driver for USB to UART chip ch340, ch341, etc. + * + * Copyright (C) 2021 WCH. + * Author: TECH39 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * System required: + * Kernel version beyond 3.4.x + * Update Log: + * V1.0 - initial version + * V1.1 - added supports for high baudrates, modem signal, flow control, etc. + * V1.2 - fixed baud rate 0 bugs and add some debug messages + * V1.3 - special setting on usb packet upload timeout + * V1.4 - changed read urb length to ep size + * V1.5 - fixed hardware flowcontrol bugs + * V1.6 - added supports for application to get uart state + * - removed tty throttle methods + * - submits urbs when uart open while not probe + * - fixed tty kref errors in dtr_rts + * V1.7 - added supports for kernel version beyond 5.14.x + * - fixed data analysis in status ep callback + */ + +#define DEBUG +#define VERBOSE_DEBUG + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) +#include +#endif + +#include "ch341.h" + +#define DRIVER_AUTHOR "TECH39 " +#define DRIVER_DESC "USB serial driver for ch340, ch341, etc." +#define VERSION_DESC "V1.7 On 2022.08.23" + +static struct usb_driver ch341_driver; +static struct tty_driver *ch341_tty_driver; + +static DEFINE_IDR(ch341_minors); +static DEFINE_MUTEX(ch341_minors_lock); + +static void ch341_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old); + +/* + * ch341_minors accessors + */ + +/* + * Look up an ch341 structure by minor. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct ch341 *ch341_get_by_minor(unsigned int minor) +{ + struct ch341 *ch341; + + mutex_lock(&ch341_minors_lock); + ch341 = idr_find(&ch341_minors, minor); + if (ch341) { + mutex_lock(&ch341->mutex); + if (ch341->disconnected) { + mutex_unlock(&ch341->mutex); + ch341 = NULL; + } else { + tty_port_get(&ch341->port); + mutex_unlock(&ch341->mutex); + } + } + mutex_unlock(&ch341_minors_lock); + return ch341; +} + +/* + * Try to find an available minor number and if found, associate it with 'ch341'. + */ +static int ch341_alloc_minor(struct ch341 *ch341) +{ + int minor; + + mutex_lock(&ch341_minors_lock); + minor = idr_alloc(&ch341_minors, ch341, 0, CH341_TTY_MINORS, GFP_KERNEL); + mutex_unlock(&ch341_minors_lock); + + return minor; +} + +/* Release the minor number associated with 'ch341'. */ +static void ch341_release_minor(struct ch341 *ch341) +{ + mutex_lock(&ch341_minors_lock); + idr_remove(&ch341_minors, ch341->minor); + mutex_unlock(&ch341_minors_lock); +} + +/* + * Functions for CH341 control messages. + */ + +static int ch341_control_out(struct ch341 *ch341, u8 request, + u16 value, u16 index) +{ + int retval; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + return retval; + + retval = usb_control_msg(ch341->dev, usb_sndctrlpipe(ch341->dev, 0), + request, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + value, index, NULL, 0, DEFAULT_TIMEOUT); + + dev_vdbg(&ch341->data->dev, + "ch341_control_out(%02x,%02x,%04x,%04x)\n", + USB_DIR_OUT | 0x40, request, value, index); + + usb_autopm_put_interface(ch341->data); + + return retval < 0 ? retval : 0; +} + +static int ch341_control_in(struct ch341 *ch341, + u8 request, u16 value, u16 index, + char *buf, unsigned bufsize) +{ + int retval; + int i; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + return retval; + + retval = usb_control_msg(ch341->dev, usb_rcvctrlpipe(ch341->dev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, buf, bufsize, DEFAULT_TIMEOUT); + + dev_vdbg(&ch341->data->dev, + "ch341_control_in(%02x,%02x,%04x,%04x,%p,%u)\n", + USB_DIR_IN | 0x40, (u8)request, (u16)value, (u16)index, buf, + (int)bufsize); + + dev_vdbg(&ch341->data->dev, + "ch341_control_in result:"); + for (i = 0; i < retval; i++) { + dev_vdbg(&ch341->data->dev, + "0x%.2x ", (u8)buf[i]); + } + + usb_autopm_put_interface(ch341->data); + + return retval; +} + +static inline int ch341_set_control(struct ch341 *ch341, int control) +{ + u16 value = 0; + + value |= (u8)~control; + + return ch341_control_out(ch341, CMD_C2, + value, 0x0000); +} + +static inline int ch341_set_line(struct ch341 *ch341, struct usb_ch341_line_coding *line) +{ + return 0; +} + +static int ch341_get_status(struct ch341 *ch341) +{ + char *buffer; + int retval; + const unsigned size = 2; + unsigned long flags; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = ch341_control_in(ch341, CMD_R, 0x0706, 0, buffer, size); + if (retval < 0) + goto out; + + /* setup the private status if available */ + if (retval > 0) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->ctrlin = (~(*buffer)) & CH341_CTI_ST; + spin_unlock_irqrestore(&ch341->read_lock, flags); + } else + retval = -EPROTO; + +out: + kfree(buffer); + return retval; +} + +/* -------------------------------------------------------------------------- */ + +static int ch341_configure(struct ch341 *ch341) +{ + char *buffer; + int r; + const unsigned size = 2; + + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + r = ch341_control_in(ch341, CMD_C3, 0, 0, buffer, size); + if (r < 0) + goto out; + + r = ch341_control_out(ch341, CMD_C1, 0, 0); + if (r < 0) + goto out; + + r = ch341_control_out(ch341, CMD_W, 0x1312, 0xd982); + if (r < 0) + goto out; + r = ch341_control_out(ch341, CMD_W, 0x0f2c, 0x0007); + if (r < 0) + goto out; + r = ch341_control_in(ch341, CMD_R, 0x2518, 0, buffer, size); + if (r < 0) + goto out; + r = ch341_get_status(ch341); + if (r < 0) + goto out; + r = ch341_control_out(ch341, CMD_W, 0x2727, 0); + if (r < 0) + goto out; + +out: + kfree(buffer); + return r; +} + +/* + * Write buffer management. + * All of these assume proper locks taken by the caller. + */ + +static int ch341_wb_alloc(struct ch341 *ch341) +{ + int i, wbn; + struct ch341_wb *wb; + + wbn = 0; + i = 0; + for (;;) { + wb = &ch341->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % CH341_NW; + if (++i >= CH341_NW) + return -1; + } +} + +static int ch341_wb_is_avail(struct ch341 *ch341) +{ + int i, n; + unsigned long flags; + + n = CH341_NW; + spin_lock_irqsave(&ch341->write_lock, flags); + for (i = 0; i < CH341_NW; i++) + n -= ch341->wb[i].use; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return n; +} + +/* + * Finish write. Caller must hold ch341->write_lock + */ +static void ch341_write_done(struct ch341 *ch341, struct ch341_wb *wb) +{ + wb->use = 0; + ch341->transmitting--; + usb_autopm_put_interface_async(ch341->data); +} + +/* + * Poke write. + * + * the caller is responsible for locking + */ + +static int ch341_start_wb(struct ch341 *ch341, struct ch341_wb *wb) +{ + int rc; + + ch341->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = ch341->dev; + + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + ch341_write_done(ch341, wb); + } + return rc; +} + +static void ch341_update_status(struct ch341 *ch341, + unsigned char *data, size_t len) +{ + unsigned long flags; + u8 status; + u8 difference; + u8 type = data[0]; + u8 handled = 0; + + if (len < 4) + return; + + if (type & CH341_CTT_M) { + status = ~data[2] & CH341_CTI_ST; + + if (!ch341->clocal && (ch341->ctrlin & status & CH341_CTI_DC)) { + tty_port_tty_hangup(&ch341->port, false); + } + + spin_lock_irqsave(&ch341->read_lock, flags); + difference = status ^ ch341->ctrlin; + ch341->ctrlin = status; + ch341->oldcount = ch341->iocount; + if (difference) { + if (difference & CH341_CTI_C) { + ch341->iocount.cts++; + } + if (difference & CH341_CTI_DS) { + ch341->iocount.dsr++; + } + if (difference & CH341_CTRL_RI) { + ch341->iocount.rng++; + } + if (difference & CH341_CTI_DC) { + ch341->iocount.dcd++; + } + spin_unlock_irqrestore(&ch341->read_lock, flags); + wake_up_interruptible(&ch341->wioctl); + } else + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if (type & CH341_CTT_O) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.overrun++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if ((type & CH341_CTT_F) == CH341_CTT_F) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.frame++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } else if (type & CH341_CTT_P) { + spin_lock_irqsave(&ch341->read_lock, flags); + ch341->oldcount = ch341->iocount; + ch341->iocount.parity++; + spin_unlock_irqrestore(&ch341->read_lock, flags); + handled = 1; + } + if (!handled) + dev_err(&ch341->data->dev, + "%s - unknown status received:" + "len:%d, data0:0x%x, data1:0x%x\n", + __func__, + (int)len, data[0], data[1]); +} + +/* + * Interrupt handlers for various CH341 device responses + */ + +/* control interface reports status changes with "interrupt" transfers */ +static void ch341_ctrl_irq(struct urb *urb) +{ + struct ch341 *ch341 = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int len = urb->actual_length; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(&ch341->data->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(&ch341->data->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + usb_mark_last_busy(ch341->dev); + ch341_update_status(ch341, data, len); +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -EPERM) + dev_err(&ch341->data->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); +} + +static int ch341_submit_read_urb(struct ch341 *ch341, int index, gfp_t mem_flags) +{ + int res; + + if (!test_and_clear_bit(index, &ch341->read_urbs_free)) + return 0; + + dev_vdbg(&ch341->data->dev, "%s - urb %d\n", __func__, index); + + res = usb_submit_urb(ch341->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &ch341->read_urbs_free); + return res; + } + + return 0; +} + +static int ch341_submit_read_urbs(struct ch341 *ch341, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < ch341->rx_buflimit; ++i) { + res = ch341_submit_read_urb(ch341, i, mem_flags); + if (res) + return res; + } + + return 0; +} + +static void ch341_process_read_urb(struct ch341 *ch341, struct urb *urb) +{ + if (!urb->actual_length) + return; + + tty_insert_flip_string(&ch341->port, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(&ch341->port); +} + +static void ch341_read_bulk_callback(struct urb *urb) +{ + struct ch341_rb *rb = urb->context; + struct ch341 *ch341 = rb->instance; + int status = urb->status; + + dev_vdbg(&ch341->data->dev, "%s - urb %d, len %d\n", __func__, + rb->index, urb->actual_length); + + if (!ch341->dev) { + set_bit(rb->index, &ch341->read_urbs_free); + dev_dbg(&ch341->data->dev, "%s - disconnected\n", __func__); + return; + } + if (status) { + set_bit(rb->index, &ch341->read_urbs_free); + dev_dbg(&ch341->data->dev, "%s - non-zero urb status: %d\n", + __func__, status); + return; + } + usb_mark_last_busy(ch341->dev); + ch341_process_read_urb(ch341, urb); + set_bit(rb->index, &ch341->read_urbs_free); + ch341_submit_read_urb(ch341, rb->index, GFP_ATOMIC); +} + +/* data interface wrote those outgoing bytes */ +static void ch341_write_bulk(struct urb *urb) +{ + struct ch341_wb *wb = urb->context; + struct ch341 *ch341 = wb->instance; + unsigned long flags; + int status = urb->status; + + dev_vdbg(&ch341->data->dev, "%s, len %d\n", __func__, urb->actual_length); + if (status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&ch341->data->dev, "%s - len %d/%d, status %d\n", + __func__, + urb->actual_length, + urb->transfer_buffer_length, + status); + + spin_lock_irqsave(&ch341->write_lock, flags); + ch341_write_done(ch341, wb); + spin_unlock_irqrestore(&ch341->write_lock, flags); + schedule_work(&ch341->work); +} + +static void ch341_softint(struct work_struct *work) +{ + struct ch341 *ch341 = container_of(work, struct ch341, work); + + dev_vdbg(&ch341->data->dev, "%s\n", __func__); + + tty_port_tty_wakeup(&ch341->port); +} + +static int ch341_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct ch341 *ch341; + int retval; + + dev_dbg(tty->dev, "%s\n", __func__); + + ch341 = ch341_get_by_minor(tty->index); + if (!ch341) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + tty->driver_data = ch341; + + return 0; + +error_init_termios: + tty_port_put(&ch341->port); + return retval; +} + +static int ch341_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct ch341 *ch341 = tty->driver_data; + + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&ch341->port, tty, filp); +} + +static void ch341_port_dtr_rts(struct tty_port *port, int raise) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + int res; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + if (raise) + ch341->ctrlout |= CH341_CTO_D | CH341_CTO_R; + else + ch341->ctrlout &= ~(CH341_CTO_D | CH341_CTO_R); + if (ch341->hardflow) + ch341->ctrlout |= CH341_CTO_R; + res = ch341_set_control(ch341, ch341->ctrlout); + if (res) + dev_err(&ch341->data->dev, "failed to set dtr/rts\n"); +} + +static int ch341_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + int retval = -ENODEV; + int i; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + mutex_lock(&ch341->mutex); + if (ch341->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(ch341->data); + if (retval) + goto error_get_interface; + + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + ch341->data->needs_remote_wakeup = 1; + retval = ch341_configure(ch341); + if (retval) + goto error_configure; + + ch341_tty_set_termios(tty, NULL); + + retval = usb_submit_urb(ch341->ctrlurb, GFP_KERNEL); + if (retval) { + dev_err(&ch341->data->dev, + "%s - usb_submit_urb(ctrl cmd) failed\n", __func__); + goto error_submit_urb; + } + + retval = ch341_submit_read_urbs(ch341, GFP_KERNEL); + if (retval) + goto error_submit_read_urbs; + usb_autopm_put_interface(ch341->data); + + mutex_unlock(&ch341->mutex); + + return 0; + +error_submit_read_urbs: + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); +error_submit_urb: + usb_kill_urb(ch341->ctrlurb); +error_configure: + usb_autopm_put_interface(ch341->data); +error_get_interface: +disconnected: + mutex_unlock(&ch341->mutex); + + return usb_translate_errors(retval); +} + +static void ch341_port_destruct(struct tty_port *port) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + ch341_release_minor(ch341); + usb_put_intf(ch341->data); + kfree(ch341); +} + +static void ch341_port_shutdown(struct tty_port *port) +{ + struct ch341 *ch341 = container_of(port, struct ch341, port); + struct urb *urb; + struct ch341_wb *wb; + int i; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + usb_autopm_get_interface_no_resume(ch341->data); + ch341->data->needs_remote_wakeup = 0; + usb_autopm_put_interface(ch341->data); + + for (;;) { + urb = usb_get_from_anchor(&ch341->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(ch341->data); + } + + usb_kill_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_kill_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); +} + +static void ch341_tty_cleanup(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + dev_dbg(&ch341->data->dev, "%s\n", __func__); + tty_port_put(&ch341->port); +} + +static void ch341_tty_hangup(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + dev_dbg(&ch341->data->dev, "%s\n", __func__); + tty_port_hangup(&ch341->port); +} + +static void ch341_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct ch341 *ch341 = tty->driver_data; + dev_dbg(&ch341->data->dev, "%s\n", __func__); + tty_port_close(&ch341->port, tty, filp); +} + +static int ch341_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct ch341 *ch341 = tty->driver_data; + int stat; + unsigned long flags; + int wbn; + struct ch341_wb *wb; + + if (!count) + return 0; + + dev_vdbg(&ch341->data->dev, "%s - count %d\n", __func__, count); + + spin_lock_irqsave(&ch341->write_lock, flags); + wbn = ch341_wb_alloc(ch341); + if (wbn < 0) { + spin_unlock_irqrestore(&ch341->write_lock, flags); + return 0; + } + wb = &ch341->wb[wbn]; + + if (!ch341->dev) { + wb->use = 0; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return -ENODEV; + } + + count = (count > ch341->writesize) ? ch341->writesize : count; + + memcpy(wb->buf, buf, count); + wb->len = count; + + stat = usb_autopm_get_interface_async(ch341->data); + if (stat) { + wb->use = 0; + spin_unlock_irqrestore(&ch341->write_lock, flags); + return stat; + } + + if (ch341->susp_count) { + usb_anchor_urb(wb->urb, &ch341->delayed); + spin_unlock_irqrestore(&ch341->write_lock, flags); + return count; + } + + stat = ch341_start_wb(ch341, wb); + spin_unlock_irqrestore(&ch341->write_lock, flags); + + if (stat < 0) + return stat; + return count; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) +static unsigned int ch341_tty_write_room(struct tty_struct *tty) +#else +static int ch341_tty_write_room(struct tty_struct *tty) +#endif +{ + struct ch341 *ch341 = tty->driver_data; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return ch341_wb_is_avail(ch341) ? ch341->writesize : 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) +static unsigned int ch341_tty_chars_in_buffer(struct tty_struct *tty) +#else +static int ch341_tty_chars_in_buffer(struct tty_struct *tty) +#endif +{ + struct ch341 *ch341 = tty->driver_data; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector; + */ + if (ch341->disconnected) + return 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (CH341_NW - ch341_wb_is_avail(ch341)) * ch341->writesize; +} + +static int ch341_tty_break_ctl(struct tty_struct *tty, int state) +{ + struct ch341 *ch341 = tty->driver_data; + int retval; + uint16_t reg_contents; + uint8_t *regbuf; + const u16 regval = + ((u16)CH341_RL << 8) | CH341_RB; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + regbuf = kmalloc(2, GFP_KERNEL); + if (!regbuf) + return -1; + + retval = ch341_control_in(ch341, CMD_R, regval, 0, regbuf, 2); + if (retval < 0) { + dev_err(&ch341->data->dev, "%s - ch341_control_in error (%d)\n", + __func__, retval); + goto out; + } + if (state != 0) { + regbuf[0] &= ~CH341_NB; + regbuf[1] &= ~CH341_L_ET; + } else { + regbuf[0] |= CH341_NB; + regbuf[1] |= CH341_L_ET; + } + reg_contents = get_unaligned_le16(regbuf); + + retval = ch341_control_out(ch341, CMD_W, regval, + reg_contents); + if (retval < 0) + dev_err(&ch341->data->dev, "%s - USB control write error (%d)\n", + __func__, retval); +out: + kfree(regbuf); + + return retval; +} + +static int ch341_tty_tiocmget(struct tty_struct *tty) +{ + struct ch341 *ch341 = tty->driver_data; + unsigned long flags; + unsigned int result; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + spin_lock_irqsave(&ch341->read_lock, flags); + result = (ch341->ctrlout & CH341_CTO_D ? TIOCM_DTR : 0) | + (ch341->ctrlout & CH341_CTO_R ? TIOCM_RTS : 0) | + (ch341->ctrlin & CH341_CTI_C ? TIOCM_CTS : 0) | + (ch341->ctrlin & CH341_CTI_DS ? TIOCM_DSR : 0) | + (ch341->ctrlin & CH341_CTRL_RI ? TIOCM_RI : 0) | + (ch341->ctrlin & CH341_CTI_DC ? TIOCM_CD : 0); + spin_unlock_irqrestore(&ch341->read_lock, flags); + dev_dbg(&ch341->data->dev, "%s - result = %x\n", __func__, result); + + return result; +} + +static int ch341_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct ch341 *ch341 = tty->driver_data; + unsigned int newctrl; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + newctrl = ch341->ctrlout; + set = (set & TIOCM_DTR ? CH341_CTO_D : 0) | + (set & TIOCM_RTS ? CH341_CTO_R : 0); + clear = (clear & TIOCM_DTR ? CH341_CTO_D : 0) | + (clear & TIOCM_RTS ? CH341_CTO_R : 0); + newctrl = (newctrl & ~clear) | set; + if (C_CRTSCTS(tty)) + newctrl |= CH341_CTO_R; + + if (ch341->ctrlout == newctrl) + return 0; + return ch341_set_control(ch341, ch341->ctrlout = newctrl); +} + +static int get_serial_info(struct ch341 *ch341, struct serial_struct __user *info) +{ + struct serial_struct tmp; + + if (!info) + return -EINVAL; + + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY; + tmp.xmit_fifo_size = ch341->writesize; + tmp.baud_base = le32_to_cpu(ch341->line.dwDTERate); + tmp.close_delay = ch341->port.close_delay / 10; + tmp.closing_wait = ch341->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE : ch341->port.closing_wait / 10; + + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + else + return 0; +} + +static int set_serial_info(struct ch341 *ch341, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&ch341->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != ch341->port.close_delay) || + (closing_wait != ch341->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + ch341->port.close_delay = close_delay; + ch341->port.closing_wait = closing_wait; + } + + mutex_unlock(&ch341->port.mutex); + return retval; +} + +static int wait_serial_change(struct ch341 *ch341, unsigned long arg) +{ + int rv = 0; + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + + do { + spin_lock_irq(&ch341->read_lock); + old = ch341->oldcount; + new = ch341->iocount; + ch341->oldcount = new; + spin_unlock_irq(&ch341->read_lock); + + if ((arg & TIOCM_CTS) && + old.cts != new.cts) + break; + if ((arg & TIOCM_DSR) && + old.dsr != new.dsr) + break; + if ((arg & TIOCM_RI) && + old.rng != new.rng) + break; + if ((arg & TIOCM_CD) && + old.dcd != new.dcd) + break; + + add_wait_queue(&ch341->wioctl, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&ch341->wioctl, &wait); + if (ch341->disconnected) { + rv = -ENODEV; + } else { + if (signal_pending(current)) + rv = -ERESTARTSYS; + } + } while (!rv); + + return rv; +} + +static int get_serial_usage(struct ch341 *ch341, + struct serial_icounter_struct __user *count) +{ + struct serial_icounter_struct icount; + int rv = 0; + + memset(&icount, 0, sizeof(icount)); + icount.cts = ch341->iocount.cts; + icount.dsr = ch341->iocount.dsr; + icount.rng = ch341->iocount.rng; + icount.dcd = ch341->iocount.dcd; + icount.frame = ch341->iocount.frame; + icount.overrun = ch341->iocount.overrun; + icount.parity = ch341->iocount.parity; + + if (copy_to_user(count, &icount, sizeof(icount)) > 0) + rv = -EFAULT; + + return rv; +} + +static int ch341_tty_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct ch341 *ch341 = tty->driver_data; + int rv = -ENOIOCTLCMD; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + rv = get_serial_info(ch341, (struct serial_struct __user *)arg); + break; + case TIOCSSERIAL: + rv = set_serial_info(ch341, (struct serial_struct __user *)arg); + break; + case TIOCMIWAIT: + rv = usb_autopm_get_interface(ch341->data); + if (rv < 0) { + rv = -EIO; + break; + } + rv = wait_serial_change(ch341, arg); + usb_autopm_put_interface(ch341->data); + break; + case TIOCGICOUNT: + rv = get_serial_usage(ch341, (struct serial_icounter_struct __user *)arg); + break; + } + + return rv; +} + +static int ch341_get(unsigned int baval, + unsigned char *factor, unsigned char *divisor) +{ + unsigned char a; + unsigned char b; + unsigned long c; + + switch (baval) { + case 921600: + a = 0xf3; + b = 7; + break; + case 307200: + a = 0xd9; + b = 7; + break; + default: + if (baval > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (baval > 750000 / 255) { + b = 2; + c = 750000; + } else if (baval > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + a = (unsigned char)(c / baval); + if (a == 0 || a == 0xFF) + return -EINVAL; + if ((c / a - baval) > (baval - c / (a + 1))) + a++; + a = 256 - a; + break; + } + *factor = a; + *divisor = b; + + return 0; +} + +static void ch341_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct ch341 *ch341 = tty->driver_data; + struct ktermios *termios = &tty->termios; + struct usb_ch341_line_coding newline; + int newctrl = ch341->ctrlout; + + unsigned char divisor = 0; + unsigned char reg_count = 0; + unsigned char factor = 0; + unsigned char reg_value = 0; + unsigned short value = 0; + unsigned short index = 0; + + dev_dbg(tty->dev, "%s\n", __func__); + + if (termios_old && + !tty_termios_hw_change(&tty->termios, termios_old)) { + dev_dbg(tty->dev, "%s - nothing to change\n", __func__); + return; + } + + newline.dwDTERate = tty_get_baud_rate(tty); + + if (newline.dwDTERate == 0) + newline.dwDTERate = 9600; + ch341_get(newline.dwDTERate, &factor, &divisor); + + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 1; + if (newline.bCharFormat == 2) + reg_value |= CH341_L_SB; + + newline.bParityType = termios->c_cflag & PARENB ? (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) + : 0; + + switch (newline.bParityType) { + case 0x01: + reg_value |= CH341_L_PO; + break; + case 0x02: + reg_value |= CH341_L_PE; + break; + case 0x03: + reg_value |= CH341_L_PM; + break; + case 0x04: + reg_value |= CH341_L_PS; + break; + default: + break; + } + + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + reg_value |= CH341_L_D5; + break; + case CS6: + newline.bDataBits = 6; + reg_value |= CH341_L_D6; + break; + case CS7: + newline.bDataBits = 7; + reg_value |= CH341_L_D7; + break; + case CS8: + default: + newline.bDataBits = 8; + reg_value |= CH341_L_D8; + break; + } + + /* FIXME: Needs to clear unsupported bits in the termios */ + ch341->clocal = ((termios->c_cflag & CLOCAL) != 0); + + if (C_BAUD(tty) == B0) { + newline.dwDTERate = ch341->line.dwDTERate; + newctrl &= ~CH341_CTO_D; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= CH341_CTO_D; + } + + reg_value |= 0xc0; + reg_count |= 0x9c; + + value |= reg_count; + value |= (unsigned short)reg_value << 8; + index |= 0x80 | divisor; + index |= (unsigned short)factor << 8; + ch341_control_out(ch341, CMD_C1, value, index); + + if (newctrl != ch341->ctrlout) + ch341_set_control(ch341, ch341->ctrlout = newctrl); + + if (memcmp(&ch341->line, &newline, sizeof newline)) { + memcpy(&ch341->line, &newline, sizeof newline); + dev_dbg(&ch341->data->dev, "%s - set line: %d %d %d %d\n", + __func__, + newline.dwDTERate, + newline.bDataBits, newline.bCharFormat, + newline.bParityType); + } + if (C_CRTSCTS(tty)) { + ch341_control_out(ch341, CMD_W, 0x2727, 0x0101); + newctrl |= CH341_CTO_R; + ch341_set_control(ch341, ch341->ctrlout = newctrl); + ch341->hardflow = true; + } else { + ch341_control_out(ch341, CMD_W, 0x2727, 0x0000); + ch341->hardflow = false; + } +} + +static const struct tty_port_operations ch341_port_ops = { + .dtr_rts = ch341_port_dtr_rts, + .shutdown = ch341_port_shutdown, + .activate = ch341_port_activate, + .destruct = ch341_port_destruct, +}; + +/* + * USB probe and disconnect routines. + */ + +/* Little helpers: write/read buffers free */ +static void ch341_write_buffers_free(struct ch341 *ch341) +{ + int i; + struct ch341_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(ch341->data); + + for (wb = &ch341->wb[0], i = 0; i < CH341_NW; i++, wb++) + usb_free_coherent(usb_dev, ch341->writesize, wb->buf, wb->dmah); +} + +static void ch341_read_buffers_free(struct ch341 *ch341) +{ + struct usb_device *usb_dev = interface_to_usbdev(ch341->data); + int i; + + for (i = 0; i < ch341->rx_buflimit; i++) + usb_free_coherent(usb_dev, ch341->readsize, + ch341->read_buffers[i].base, ch341->read_buffers[i].dma); +} + +/* Little helper: write buffers allocate */ +static int ch341_write_buffers_alloc(struct ch341 *ch341) +{ + int i; + struct ch341_wb *wb; + + for (wb = &ch341->wb[0], i = 0; i < CH341_NW; i++, wb++) { + wb->buf = usb_alloc_coherent(ch341->dev, ch341->writesize, GFP_KERNEL, + &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_free_coherent(ch341->dev, ch341->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + +static int ch341_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_interface *data_interface; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct ch341 *ch341; + int minor; + int ctrlsize, readsize; + u8 *buf; + unsigned long quirks; + int num_rx_buf = CH341_NR; + int i; + struct device *tty_dev; + int rv = -ENOMEM; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + + /* handle quirks deadly to normal probing*/ + + data_interface = usb_ifnum_to_if(usb_dev, 0); + + if (intf != data_interface) + return -ENODEV; + + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + data_interface->cur_altsetting->desc.bNumEndpoints == 0) + return -EINVAL; + + epread = &data_interface->cur_altsetting->endpoint[0].desc; + epwrite = &data_interface->cur_altsetting->endpoint[1].desc; + epctrl = &data_interface->cur_altsetting->endpoint[2].desc; + + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + dev_dbg(&intf->dev, + "The data interface has switched endpoints\n"); + swap(epread, epwrite); + } + + dev_dbg(&intf->dev, "interface is valid\n"); + + ch341 = kzalloc(sizeof(struct ch341), GFP_KERNEL); + if (ch341 == NULL) + goto alloc_fail; + + minor = ch341_alloc_minor(ch341); + if (minor < 0) { + dev_err(&intf->dev, "no more free ch341 devices\n"); + kfree(ch341); + return -ENODEV; + } + + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread) * + (quirks == SINGLE_RX_URB ? 1 : 1); + ch341->writesize = usb_endpoint_maxp(epwrite) * 20; + ch341->data = data_interface; + ch341->minor = minor; + ch341->dev = usb_dev; + ch341->ctrlsize = ctrlsize; + ch341->readsize = readsize; + ch341->rx_buflimit = num_rx_buf; + + dev_dbg(&intf->dev, "epctrl: %d, epread: %d, epwrite: %d\n", + usb_endpoint_maxp(epctrl), usb_endpoint_maxp(epread), + usb_endpoint_maxp(epwrite)); + + INIT_WORK(&ch341->work, ch341_softint); + init_waitqueue_head(&ch341->wioctl); + spin_lock_init(&ch341->write_lock); + spin_lock_init(&ch341->read_lock); + mutex_init(&ch341->mutex); + ch341->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + tty_port_init(&ch341->port); + ch341->port.ops = &ch341_port_ops; + init_usb_anchor(&ch341->delayed); + ch341->quirks = quirks; + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &ch341->ctrl_dma); + if (!buf) + goto alloc_fail2; + ch341->ctrl_buffer = buf; + + if (ch341_write_buffers_alloc(ch341) < 0) + goto alloc_fail4; + + ch341->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!ch341->ctrlurb) + goto alloc_fail5; + + for (i = 0; i < num_rx_buf; i++) { + struct ch341_rb *rb = &(ch341->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(ch341->dev, readsize, GFP_KERNEL, + &rb->dma); + if (!rb->base) + goto alloc_fail6; + rb->index = i; + rb->instance = ch341; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto alloc_fail6; + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + usb_fill_bulk_urb(urb, ch341->dev, + ch341->rx_endpoint, + rb->base, + ch341->readsize, + ch341_read_bulk_callback, rb); + + ch341->read_urbs[i] = urb; + __set_bit(i, &ch341->read_urbs_free); + } + for (i = 0; i < CH341_NW; i++) { + struct ch341_wb *snd = &(ch341->wb[i]); + + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) + goto alloc_fail7; + + usb_fill_bulk_urb(snd->urb, usb_dev, + usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + NULL, ch341->writesize, ch341_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = ch341; + } + + usb_set_intfdata(intf, ch341); + + usb_fill_int_urb(ch341->ctrlurb, usb_dev, + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + ch341->ctrl_buffer, ctrlsize, ch341_ctrl_irq, ch341, + epctrl->bInterval ? epctrl->bInterval : 16); + ch341->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + ch341->ctrlurb->transfer_dma = ch341->ctrl_dma; + + dev_info(&intf->dev, "ttyCH341USB%d: ch341 USB device\n", minor); + + usb_driver_claim_interface(&ch341_driver, data_interface, ch341); + usb_set_intfdata(data_interface, ch341); + + usb_get_intf(data_interface); + tty_dev = tty_port_register_device(&ch341->port, ch341_tty_driver, minor, + &data_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail7; + } + + dev_dbg(&intf->dev, "ch341_probe finished!\n"); + + return 0; + +alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < CH341_NW; i++) + usb_free_urb(ch341->wb[i].urb); +alloc_fail6: + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(ch341->read_urbs[i]); + ch341_read_buffers_free(ch341); + usb_free_urb(ch341->ctrlurb); +alloc_fail5: + ch341_write_buffers_free(ch341); +alloc_fail4: + usb_free_coherent(usb_dev, ctrlsize, ch341->ctrl_buffer, ch341->ctrl_dma); +alloc_fail2: + ch341_release_minor(ch341); + kfree(ch341); +alloc_fail: + return rv; +} + +static void stop_data_traffic(struct ch341 *ch341) +{ + int i; + + dev_dbg(&ch341->data->dev, "%s\n", __func__); + + usb_kill_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_kill_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_kill_urb(ch341->read_urbs[i]); + + cancel_work_sync(&ch341->work); +} + +static void ch341_disconnect(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; + int i; + + dev_dbg(&intf->dev, "%s\n", __func__); + + /* sibling interface is already cleaning up */ + if (!ch341) + return; + + mutex_lock(&ch341->mutex); + ch341->disconnected = true; + wake_up_all(&ch341->wioctl); + usb_set_intfdata(ch341->data, NULL); + mutex_unlock(&ch341->mutex); + + tty = tty_port_tty_get(&ch341->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } + + stop_data_traffic(ch341); + + tty_unregister_device(ch341_tty_driver, ch341->minor); + + usb_free_urb(ch341->ctrlurb); + for (i = 0; i < CH341_NW; i++) + usb_free_urb(ch341->wb[i].urb); + for (i = 0; i < ch341->rx_buflimit; i++) + usb_free_urb(ch341->read_urbs[i]); + ch341_write_buffers_free(ch341); + usb_free_coherent(usb_dev, ch341->ctrlsize, ch341->ctrl_buffer, ch341->ctrl_dma); + ch341_read_buffers_free(ch341); + + usb_driver_release_interface(&ch341_driver, ch341->data); + + tty_port_put(&ch341->port); + dev_info(&intf->dev, "%s\n", "ch341 usb device disconnect."); +} + +#ifdef CONFIG_PM +static int ch341_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + int cnt; + + dev_dbg(&intf->dev, "%s\n", __func__); + spin_lock_irq(&ch341->write_lock); + if (PMSG_IS_AUTO(message)) { + if (ch341->transmitting) { + spin_unlock_irq(&ch341->write_lock); + return -EBUSY; + } + } + cnt = ch341->susp_count++; + spin_unlock_irq(&ch341->write_lock); + + if (cnt) + return 0; + + stop_data_traffic(ch341); + + return 0; +} + +static int ch341_resume(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + struct urb *urb; + int rv = 0; + + dev_dbg(&intf->dev, "%s\n", __func__); + spin_lock_irq(&ch341->write_lock); + + if (--ch341->susp_count) + goto out; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch341->port)) { +#else + if (test_bit(ASYNCB_INITIALIZED, &ch341->port.flags)) { +#endif + rv = usb_submit_urb(ch341->ctrlurb, GFP_ATOMIC); + + for (;;) { + urb = usb_get_from_anchor(&ch341->delayed); + if (!urb) + break; + + ch341_start_wb(ch341, urb->context); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ + if (rv < 0) + goto out; + + rv = ch341_submit_read_urbs(ch341, GFP_ATOMIC); + } +out: + spin_unlock_irq(&ch341->write_lock); + + return rv; +} + +static int ch341_reset_resume(struct usb_interface *intf) +{ + struct ch341 *ch341 = usb_get_intfdata(intf); + + dev_dbg(&intf->dev, "%s\n", __func__); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)) + if (tty_port_initialized(&ch341->port)) +#else + if (test_bit(ASYNCB_INITIALIZED, &ch341->port.flags)) +#endif + tty_port_tty_hangup(&ch341->port, false); + + return ch341_resume(intf); +} + +#endif /* CONFIG_PM */ + +/* + * USB driver structure. + */ + +static const struct usb_device_id ch341_ids[] = { + { + USB_DEVICE(0x1a86, 0x7523), + }, /* ch340 chip */ + { + USB_DEVICE(0x1a86, 0x7522), + }, /* ch340K chip */ + { + USB_DEVICE(0x1a86, 0x5523), + }, /* ch341 chip */ + { + USB_DEVICE(0x1a86, 0xE523), + }, /* ch330 chip */ + { + USB_DEVICE(0x4348, 0x5523), + }, /* ch340 custom chip */ + {}}; + +MODULE_DEVICE_TABLE(usb, ch341_ids); + +static struct usb_driver ch341_driver = { + .name = "usb_ch341", + .probe = ch341_probe, + .disconnect = ch341_disconnect, +#ifdef CONFIG_PM + .suspend = ch341_suspend, + .resume = ch341_resume, + .reset_resume = ch341_reset_resume, +#endif + .id_table = ch341_ids, +#ifdef CONFIG_PM + .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, +}; + +/* + * TTY driver structures. + */ + +static const struct tty_operations ch341_ops = { + .install = ch341_tty_install, + .open = ch341_tty_open, + .close = ch341_tty_close, + .cleanup = ch341_tty_cleanup, + .hangup = ch341_tty_hangup, + .write = ch341_tty_write, + .write_room = ch341_tty_write_room, + .ioctl = ch341_tty_ioctl, + .chars_in_buffer = ch341_tty_chars_in_buffer, + .break_ctl = ch341_tty_break_ctl, + .set_termios = ch341_tty_set_termios, + .tiocmget = ch341_tty_tiocmget, + .tiocmset = ch341_tty_tiocmset, +}; + +/* + * Init / exit. + */ + +static int __init ch341_init(void) +{ + int retval; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + ch341_tty_driver = tty_alloc_driver(CH341_TTY_MINORS, TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(ch341_tty_driver)) + return PTR_ERR(ch341_tty_driver); +#else + ch341_tty_driver = alloc_tty_driver(CH341_TTY_MINORS); + if (!ch341_tty_driver) + return -ENOMEM; +#endif + ch341_tty_driver->driver_name = "ch341_uart", + ch341_tty_driver->name = "ttyCH341USB", + ch341_tty_driver->major = CH341_TTY_MAJOR, + ch341_tty_driver->minor_start = 0, + ch341_tty_driver->type = TTY_DRIVER_TYPE_SERIAL, + ch341_tty_driver->subtype = SERIAL_TYPE_NORMAL, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + ch341_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; +#endif + ch341_tty_driver->init_termios = tty_std_termios; + ch341_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | + HUPCL | CLOCAL; + tty_set_operations(ch341_tty_driver, &ch341_ops); + + retval = tty_register_driver(ch341_tty_driver); + if (retval) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + return retval; + } + + retval = usb_register(&ch341_driver); + if (retval) { + tty_unregister_driver(ch341_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + return retval; + } + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": " VERSION_DESC "\n"); + + return 0; +} + +static void __exit ch341_exit(void) +{ + usb_deregister(&ch341_driver); + tty_unregister_driver(ch341_tty_driver); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)) + tty_driver_kref_put(ch341_tty_driver); +#else + put_tty_driver(ch341_tty_driver); +#endif + idr_destroy(&ch341_minors); + printk(KERN_INFO KBUILD_MODNAME ": " + "ch341 driver exit.\n"); +} + +module_init(ch341_init); +module_exit(ch341_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(CH341_TTY_MAJOR); diff --git a/drivers/CH341SER_LINUX/driver/ch341.h b/drivers/CH341SER_LINUX/driver/ch341.h new file mode 100644 index 0000000..5adaff4 --- /dev/null +++ b/drivers/CH341SER_LINUX/driver/ch341.h @@ -0,0 +1,161 @@ +#ifndef _CH341_H +#define _CH341_H + +/* + * Baud rate and default timeout + */ +#define DEFAULT_BAUD_RATE 9600 +#define DEFAULT_TIMEOUT 2000 + +/* + * CMSPAR, some architectures can't have space and mark parity. + */ + +#ifndef CMSPAR +#define CMSPAR 0 +#endif + +/* + * Major and minor numbers. + */ + +#define CH341_TTY_MAJOR 169 +#define CH341_TTY_MINORS 256 + +/* + * Requests. + */ + +#define USB_RT_CH341 (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +#define CMD_R 0x95 +#define CMD_W 0x9A +#define CMD_C1 0xA1 +#define CMD_C2 0xA4 +#define CMD_C3 0x5F + +#define CH341_CTO_O 0x10 +#define CH341_CTO_D 0x20 +#define CH341_CTO_R 0x40 +#define CH341_CTI_C 0x01 +#define CH341_CTI_DS 0x02 +#define CH341_CTRL_RI 0x04 +#define CH341_CTI_DC 0x08 +#define CH341_CTI_ST 0x0f + +#define CH341_CTT_M BIT(3) +#define CH341_CTT_F (BIT(2) | BIT(6)) +#define CH341_CTT_P BIT(2) +#define CH341_CTT_O BIT(1) + +#define CH341_L_ER 0x80 +#define CH341_L_ET 0x40 +#define CH341_L_PS 0x38 +#define CH341_L_PM 0x28 +#define CH341_L_PE 0x18 +#define CH341_L_PO 0x08 +#define CH341_L_SB 0x04 +#define CH341_L_D8 0x03 +#define CH341_L_D7 0x02 +#define CH341_L_D6 0x01 +#define CH341_L_D5 0x00 + +#define CH341_RB 0x05 +#define CH341_RL 0x18 +#define CH341_NB 0x01 + +/* + * Internal driver structures. + */ + +/* + * The only reason to have several buffers is to accommodate assumptions + * in line disciplines. They ask for empty space amount, receive our URB size, + * and proceed to issue several 1-character writes, assuming they will fit. + * The very first write takes a complete URB. Fortunately, this only happens + * when processing onlcr, so we only need 2 buffers. These values must be + * powers of 2. + */ +#define CH341_NW 16 +#define CH341_NR 16 + +struct ch341_wb { + unsigned char *buf; + dma_addr_t dmah; + int len; + int use; + struct urb *urb; + struct ch341 *instance; +}; + +struct ch341_rb { + int size; + unsigned char *base; + dma_addr_t dma; + int index; + struct ch341 *instance; +}; + +struct usb_ch341_line_coding { + __le32 dwDTERate; + __u8 bCharFormat; +#define USB_CH341_1_STOP_BITS 0 +#define USB_CH341_1_5_STOP_BITS 1 +#define USB_CH341_2_STOP_BITS 2 + + __u8 bParityType; +#define USB_CH341_NO_PARITY 0 +#define USB_CH341_ODD_PARITY 1 +#define USB_CH341_EVEN_PARITY 2 +#define USB_CH341_MARK_PARITY 3 +#define USB_CH341_SPACE_PARITY 4 + + __u8 bDataBits; +} __attribute__((packed)); + +struct ch341 { + struct usb_device *dev; /* the corresponding usb device */ + struct usb_interface *data; /* data interface */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ + u8 *ctrl_buffer; /* buffers of urbs */ + dma_addr_t ctrl_dma; /* dma handles of buffers */ + struct ch341_wb wb[CH341_NW]; + unsigned long read_urbs_free; + struct urb *read_urbs[CH341_NR]; + struct ch341_rb read_buffers[CH341_NR]; + int rx_buflimit; + int rx_endpoint; + spinlock_t read_lock; + int write_used; /* number of non-empty write buffers */ + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + struct usb_ch341_line_coding line; /* bits, stop, parity */ + struct work_struct work; /* work queue entry for line discipline waking up */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize, ctrlsize; /* buffer sizes for freeing */ + unsigned int minor; /* ch341 minor number */ + unsigned char clocal; /* termios CLOCAL */ + unsigned int susp_count; /* number of suspended interfaces */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ + unsigned long quirks; + bool hardflow; +}; + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL BIT(0) +#define SINGLE_RX_URB BIT(1) +#define NO_CAP_LINE BIT(2) +#define NO_DATA_INTERFACE BIT(4) +#define IGNORE_DEVICE BIT(5) +#define QUIRK_CONTROL_LINE_STATE BIT(6) +#define CLEAR_HALT_CONDITIONS BIT(7) +#endif diff --git a/drivers/CH341SER_LINUX/readme.txt b/drivers/CH341SER_LINUX/readme.txt deleted file mode 100755 index c86009a..0000000 --- a/drivers/CH341SER_LINUX/readme.txt +++ /dev/null @@ -1,19 +0,0 @@ -// ChangeLog -// 1.0 - 1.1 modified to solve transmition between ch341 and ch341 -// 1.1 - 1.2 Support high Linux kernel -Instructions - -Note: 1.Please run followed executable programs as root privilege - 2.Current Driver support versions of linux kernel range from 2.6.25 to 3.13.x - 3.Current Driver support 32bits and 64bits linux systems - -Usage: - (load or unload linux driver of CH34x) - //compile - #make - //load ch34x chips driver - #make load - //unload ch34x chips driver - #make unload -// 1.2 - 1.3 Fix some bugs -