2013-10-28 23:11:21 +00:00
/ * Copyright 2011 - 2013 Google Inc .
* Copyright 2013 mike wakerly < opensource @hoho.com >
2013-02-19 21:22:02 +00:00
*
2013-10-28 23:11:21 +00:00
* Project home page : https : //github.com/mik3y/usb-serial-for-android
2013-02-19 21:22:02 +00:00
* /
package com.hoho.android.usbserial.driver ;
import android.hardware.usb.UsbDevice ;
import android.hardware.usb.UsbDeviceConnection ;
2019-11-09 19:18:47 +00:00
import android.hardware.usb.UsbEndpoint ;
import android.hardware.usb.UsbRequest ;
import android.util.Log ;
2013-02-19 21:22:02 +00:00
2021-03-16 22:39:00 +00:00
import com.hoho.android.usbserial.util.MonotonicClock ;
2013-02-19 21:22:02 +00:00
import java.io.IOException ;
2019-11-09 19:18:47 +00:00
import java.nio.ByteBuffer ;
2020-06-28 18:10:45 +00:00
import java.util.EnumSet ;
2013-02-19 21:22:02 +00:00
/ * *
* A base class shared by several driver implementations .
*
* @author mike wakerly ( opensource @hoho.com )
* /
2019-11-03 12:59:50 +00:00
public abstract class CommonUsbSerialPort implements UsbSerialPort {
2013-02-19 21:22:02 +00:00
2019-11-09 19:18:47 +00:00
private static final String TAG = CommonUsbSerialPort . class . getSimpleName ( ) ;
private static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024 ;
2019-11-15 20:45:22 +00:00
private static final int MAX_READ_SIZE = 16 * 1024 ; // = old bulkTransfer limit
2013-02-19 21:22:02 +00:00
protected final UsbDevice mDevice ;
2014-02-04 22:22:14 +00:00
protected final int mPortNumber ;
2013-10-28 23:11:21 +00:00
// non-null when open()
protected UsbDeviceConnection mConnection = null ;
2019-11-09 19:18:47 +00:00
protected UsbEndpoint mReadEndpoint ;
protected UsbEndpoint mWriteEndpoint ;
protected UsbRequest mUsbRequest ;
2013-02-19 21:22:02 +00:00
protected final Object mWriteBufferLock = new Object ( ) ;
/** Internal write buffer. Guarded by {@link #mWriteBufferLock}. */
protected byte [ ] mWriteBuffer ;
2014-02-04 22:22:14 +00:00
public CommonUsbSerialPort ( UsbDevice device , int portNumber ) {
2013-02-19 21:22:02 +00:00
mDevice = device ;
2014-02-04 22:22:14 +00:00
mPortNumber = portNumber ;
2013-02-19 21:22:02 +00:00
mWriteBuffer = new byte [ DEFAULT_WRITE_BUFFER_SIZE ] ;
}
2019-11-09 19:18:47 +00:00
2014-02-04 22:22:14 +00:00
@Override
public String toString ( ) {
return String . format ( "<%s device_name=%s device_id=%s port_number=%s>" ,
getClass ( ) . getSimpleName ( ) , mDevice . getDeviceName ( ) ,
mDevice . getDeviceId ( ) , mPortNumber ) ;
}
2013-02-19 21:22:02 +00:00
2020-06-08 19:57:16 +00:00
@Override
public UsbDevice getDevice ( ) {
2013-02-19 21:22:02 +00:00
return mDevice ;
}
2014-02-04 22:22:14 +00:00
@Override
public int getPortNumber ( ) {
return mPortNumber ;
}
2021-01-31 19:01:12 +00:00
/ * *
* Returns the write endpoint .
* @return write endpoint
* /
public UsbEndpoint getWriteEndpoint ( ) { return mWriteEndpoint ; }
/ * *
* Returns the read endpoint .
* @return read endpoint
* /
public UsbEndpoint getReadEndpoint ( ) { return mReadEndpoint ; }
2014-09-09 20:06:28 +00:00
/ * *
* Returns the device serial number
* @return serial number
* /
@Override
public String getSerial ( ) {
return mConnection . getSerial ( ) ;
}
2014-02-04 22:22:14 +00:00
2013-02-19 21:22:02 +00:00
/ * *
* Sets the size of the internal buffer used to exchange data with the USB
* stack for write operations . Most users should not need to change this .
*
* @param bufferSize the size in bytes
* /
public final void setWriteBufferSize ( int bufferSize ) {
synchronized ( mWriteBufferLock ) {
if ( bufferSize = = mWriteBuffer . length ) {
return ;
}
mWriteBuffer = new byte [ bufferSize ] ;
}
}
@Override
2019-12-09 21:42:51 +00:00
public void open ( UsbDeviceConnection connection ) throws IOException {
if ( mConnection ! = null ) {
throw new IOException ( "Already open" ) ;
}
2021-01-31 18:56:43 +00:00
if ( connection = = null ) {
throw new IllegalArgumentException ( "Connection is null" ) ;
}
2019-12-09 21:42:51 +00:00
mConnection = connection ;
try {
openInt ( connection ) ;
if ( mReadEndpoint = = null | | mWriteEndpoint = = null ) {
throw new IOException ( "Could not get read & write endpoints" ) ;
}
mUsbRequest = new UsbRequest ( ) ;
mUsbRequest . initialize ( mConnection , mReadEndpoint ) ;
} catch ( Exception e ) {
2020-12-13 23:23:29 +00:00
try {
close ( ) ;
} catch ( Exception ignored ) { }
2019-12-09 21:42:51 +00:00
throw e ;
}
}
protected abstract void openInt ( UsbDeviceConnection connection ) throws IOException ;
2013-02-19 21:22:02 +00:00
@Override
2019-11-09 21:48:00 +00:00
public void close ( ) throws IOException {
if ( mConnection = = null ) {
throw new IOException ( "Already closed" ) ;
}
2019-12-09 21:42:51 +00:00
try {
mUsbRequest . cancel ( ) ;
} catch ( Exception ignored ) { }
mUsbRequest = null ;
2019-11-09 21:48:00 +00:00
try {
closeInt ( ) ;
} catch ( Exception ignored ) { }
try {
mConnection . close ( ) ;
2019-12-09 21:42:51 +00:00
} catch ( Exception ignored ) { }
mConnection = null ;
2019-11-09 21:48:00 +00:00
}
protected abstract void closeInt ( ) ;
2013-02-19 21:22:02 +00:00
2020-09-06 17:58:18 +00:00
/ * *
* use simple USB request supported by all devices to test if connection is still valid
* /
protected void testConnection ( ) throws IOException {
byte [ ] buf = new byte [ 2 ] ;
int len = mConnection . controlTransfer ( 0x80 /*DEVICE*/ , 0 /*GET_STATUS*/ , 0 , 0 , buf , buf . length , 200 ) ;
if ( len < 0 )
2021-03-22 07:57:16 +00:00
throw new IOException ( "Connection lost, USB get_status request failed" ) ;
2020-09-06 17:58:18 +00:00
}
2013-02-19 21:22:02 +00:00
@Override
2019-11-15 20:45:22 +00:00
public int read ( final byte [ ] dest , final int timeout ) throws IOException {
2020-09-06 17:58:18 +00:00
return read ( dest , timeout , true ) ;
}
protected int read ( final byte [ ] dest , final int timeout , boolean testConnection ) throws IOException {
2019-11-09 19:18:47 +00:00
if ( mConnection = = null ) {
throw new IOException ( "Connection closed" ) ;
}
2020-09-06 07:11:35 +00:00
if ( dest . length < = 0 ) {
2020-09-06 17:58:18 +00:00
throw new IllegalArgumentException ( "Read buffer to small" ) ;
2020-09-06 07:11:35 +00:00
}
2019-11-15 20:45:22 +00:00
final int nread ;
if ( timeout ! = 0 ) {
// bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer
// https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data
// but mConnection.requestWait(timeout) available since Android 8.0 es even worse,
// as it crashes with short timeout, e.g.
// A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test)
// /system/lib64/libusbhost.so (usb_request_wait+192)
// /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84)
// data loss / crashes were observed with timeout up to 200 msec
2021-03-16 22:39:00 +00:00
long endTime = testConnection ? MonotonicClock . millis ( ) + timeout : 0 ;
2019-11-15 20:45:22 +00:00
int readMax = Math . min ( dest . length , MAX_READ_SIZE ) ;
nread = mConnection . bulkTransfer ( mReadEndpoint , dest , readMax , timeout ) ;
2021-03-22 07:57:16 +00:00
// Android error propagation is improvable, nread == -1 can be: timeout, connection lost, buffer to small
2021-03-16 22:39:00 +00:00
if ( nread = = - 1 & & testConnection & & MonotonicClock . millis ( ) < endTime )
2020-09-06 17:58:18 +00:00
testConnection ( ) ;
2019-11-15 20:45:22 +00:00
} else {
2019-12-09 21:42:51 +00:00
final ByteBuffer buf = ByteBuffer . wrap ( dest ) ;
if ( ! mUsbRequest . queue ( buf , dest . length ) ) {
throw new IOException ( "Queueing USB request failed" ) ;
}
final UsbRequest response = mConnection . requestWait ( ) ;
if ( response = = null ) {
throw new IOException ( "Waiting for USB request failed" ) ;
2019-11-09 19:18:47 +00:00
}
2019-12-09 21:42:51 +00:00
nread = buf . position ( ) ;
2021-03-22 07:57:16 +00:00
if ( nread = = 0 ) {
if ( dest . length % mReadEndpoint . getMaxPacketSize ( ) ! = 0 ) {
throw new IOException ( "Connection lost or buffer to small" ) ;
} else {
throw new IOException ( "Connection lost" ) ;
}
}
2019-11-15 20:45:22 +00:00
}
2021-03-22 07:57:16 +00:00
return Math . max ( nread , 0 ) ;
2019-11-09 19:18:47 +00:00
}
2013-02-19 21:22:02 +00:00
@Override
2021-01-31 19:01:12 +00:00
public void write ( final byte [ ] src , final int timeout ) throws IOException {
2019-11-09 19:18:47 +00:00
int offset = 0 ;
2021-03-16 22:39:00 +00:00
final long endTime = ( timeout = = 0 ) ? 0 : ( MonotonicClock . millis ( ) + timeout ) ;
2019-11-09 19:18:47 +00:00
if ( mConnection = = null ) {
throw new IOException ( "Connection closed" ) ;
}
while ( offset < src . length ) {
2021-02-14 13:16:25 +00:00
int requestTimeout ;
2021-01-31 19:01:12 +00:00
final int requestLength ;
final int actualLength ;
2019-11-09 19:18:47 +00:00
synchronized ( mWriteBufferLock ) {
final byte [ ] writeBuffer ;
2021-01-31 19:01:12 +00:00
requestLength = Math . min ( src . length - offset , mWriteBuffer . length ) ;
2019-11-09 19:18:47 +00:00
if ( offset = = 0 ) {
writeBuffer = src ;
} else {
// bulkTransfer does not support offsets, make a copy.
2021-01-31 19:01:12 +00:00
System . arraycopy ( src , offset , mWriteBuffer , 0 , requestLength ) ;
2019-11-09 19:18:47 +00:00
writeBuffer = mWriteBuffer ;
}
2021-02-08 21:28:46 +00:00
if ( timeout = = 0 | | offset = = 0 ) {
requestTimeout = timeout ;
} else {
2021-03-16 22:39:00 +00:00
requestTimeout = ( int ) ( endTime - MonotonicClock . millis ( ) ) ;
2021-02-14 13:16:25 +00:00
if ( requestTimeout = = 0 )
requestTimeout = - 1 ;
2021-02-08 21:28:46 +00:00
}
2021-01-31 19:01:12 +00:00
if ( requestTimeout < 0 ) {
actualLength = - 2 ;
} else {
actualLength = mConnection . bulkTransfer ( mWriteEndpoint , writeBuffer , requestLength , requestTimeout ) ;
}
2019-11-09 19:18:47 +00:00
}
2021-01-31 19:01:12 +00:00
Log . d ( TAG , "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src . length + " timeout " + requestTimeout ) ;
if ( actualLength < = 0 ) {
2021-03-16 22:39:00 +00:00
if ( timeout ! = 0 & & MonotonicClock . millis ( ) > = endTime ) {
2021-02-08 21:28:46 +00:00
SerialTimeoutException ex = new SerialTimeoutException ( "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src . length + ", rc=" + actualLength ) ;
2021-01-31 19:01:12 +00:00
ex . bytesTransferred = offset ;
throw ex ;
} else {
throw new IOException ( "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src . length ) ;
}
}
offset + = actualLength ;
2019-11-09 19:18:47 +00:00
}
}
2020-05-19 18:50:42 +00:00
@Override
public boolean isOpen ( ) {
return mConnection ! = null ;
}
2013-02-19 21:22:02 +00:00
@Override
2021-01-16 21:17:34 +00:00
public abstract void setParameters ( int baudRate , int dataBits , int stopBits , @Parity int parity ) throws IOException ;
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public boolean getCD ( ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public boolean getCTS ( ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public boolean getDSR ( ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public boolean getDTR ( ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public void setDTR ( boolean value ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public boolean getRI ( ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public boolean getRTS ( ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
@Override
2020-07-31 19:02:59 +00:00
public void setRTS ( boolean value ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
2020-06-28 18:10:45 +00:00
@Override
public abstract EnumSet < ControlLine > getControlLines ( ) throws IOException ;
@Override
public abstract EnumSet < ControlLine > getSupportedControlLines ( ) throws IOException ;
2013-05-26 16:55:49 +00:00
@Override
2020-07-31 19:02:59 +00:00
public void purgeHwBuffers ( boolean purgeWriteBuffers , boolean purgeReadBuffers ) throws IOException {
throw new UnsupportedOperationException ( ) ;
2013-05-26 16:55:49 +00:00
}
2020-10-14 17:23:55 +00:00
@Override
public void setBreak ( boolean value ) throws IOException { throw new UnsupportedOperationException ( ) ; }
2013-02-19 21:22:02 +00:00
}