Home | History | Annotate | Download | only in host
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.google.android.car.usb.aoap.host;
     17 
     18 import android.content.Context;
     19 import android.hardware.usb.UsbConstants;
     20 import android.hardware.usb.UsbDevice;
     21 import android.hardware.usb.UsbDeviceConnection;
     22 import android.hardware.usb.UsbEndpoint;
     23 import android.hardware.usb.UsbInterface;
     24 import android.hardware.usb.UsbRequest;
     25 import android.os.SystemClock;
     26 import android.text.format.Formatter;
     27 import android.util.Log;
     28 
     29 import java.nio.ByteBuffer;
     30 import java.nio.ByteOrder;
     31 import java.util.Random;
     32 
     33 /** Controller that measures USB AOAP transfer speed. */
     34 class SpeedMeasurementController extends Thread {
     35     private static final String TAG = SpeedMeasurementController.class.getSimpleName();
     36 
     37     interface SpeedMeasurementControllerCallback {
     38         void testStarted(int mode, int bufferSize);
     39         void testFinished(int mode, int bufferSize);
     40         void testSuiteFinished();
     41         void testResult(int mode, String update);
     42     }
     43 
     44     public static final int TEST_MODE_SYNC = 1;
     45     public static final int TEST_MODE_ASYNC = 2;
     46 
     47     private static final int TEST_DATA_SIZE = 100 * 1024 * 1024; // 100MB
     48     private static final int TEST_DATA_1_BATCH_SIZE = 15000;
     49     private static final int TEST_DATA_2_BATCH_SIZE = 1500;
     50     private static final int USB_TIMEOUT_MS = 1000; // 1s
     51     private static final int TEST_MAX_TIME_MS = 200000; // 200s
     52     private static final int ASYNC_MAX_OUTSTANDING_REQUESTS = 5;
     53 
     54     private static final ByteOrder ORDER = ByteOrder.BIG_ENDIAN;
     55 
     56     private final UsbDevice mDevice;
     57     private final UsbDeviceConnection mUsbConnection;
     58     private final SpeedMeasurementControllerCallback mCallback;
     59     private final Context mContext;
     60 
     61     public static Random sRandom = new Random(SystemClock.uptimeMillis());
     62 
     63     SpeedMeasurementController(Context context,
     64             UsbDevice device, UsbDeviceConnection conn,
     65             SpeedMeasurementControllerCallback callback) {
     66         if (TEST_DATA_SIZE % TEST_DATA_1_BATCH_SIZE == 0) {
     67             throw new AssertionError("TEST_DATA_SIZE mod TEST_DATA_BIG_BATCH_SIZE must not be 0");
     68         }
     69         if (TEST_DATA_SIZE % TEST_DATA_2_BATCH_SIZE == 0) {
     70             throw new AssertionError("TEST_DATA_SIZE mod TEST_DATA_SMALL_BATCH_SIZE must not be 0");
     71         }
     72         mContext = context;
     73         mDevice = device;
     74         mUsbConnection = conn;
     75         mCallback = callback;
     76     }
     77 
     78     protected void release() {
     79         if (mUsbConnection != null) {
     80             mUsbConnection.close();
     81         }
     82     }
     83 
     84     /**
     85      * {@inheritDoc}
     86      *
     87      * Test runs two type of USB host->phone write tests:
     88      * <ul>
     89      * <li> Synchronous write with usage of UsbDeviceConnection.bulkTransfer. </li>
     90      * <li> Aynchronous write with usage of UsbRequest and UsbDeviceConnection.requestWait. </li>
     91      * </ul>
     92      * Each test scenario also runs with different buffer size.
     93      */
     94     @Override
     95     public void run() {
     96         Log.v(TAG, "Running sync test with buffer size #1");
     97         runSyncTest(TEST_DATA_1_BATCH_SIZE);
     98         Log.v(TAG, "Running sync test with buffer size #2");
     99         runSyncTest(TEST_DATA_2_BATCH_SIZE);
    100         Log.v(TAG, "Running async test with buffer size #1");
    101         runAsyncTest(TEST_DATA_1_BATCH_SIZE);
    102         Log.v(TAG, "Running async test with buffer size #2");
    103         runAsyncTest(TEST_DATA_2_BATCH_SIZE);
    104         Log.v(TAG, "Done running tests");
    105         release();
    106         mCallback.testSuiteFinished();
    107     }
    108 
    109     private void runTest(BaseWriterThread writer, ReaderThread reader, int mode, int bufferSize) {
    110         mCallback.testStarted(mode, bufferSize);
    111         writer.start();
    112         reader.start();
    113         try {
    114             writer.join(TEST_MAX_TIME_MS);
    115         } catch (InterruptedException e) {}
    116         try {
    117             reader.join(TEST_MAX_TIME_MS);
    118         } catch (InterruptedException e) {}
    119         if (reader.isAlive()) {
    120             reader.requestToQuit();
    121             try {
    122                 reader.join(USB_TIMEOUT_MS);
    123             } catch (InterruptedException e) {}
    124             if (reader.isAlive()) {
    125                 throw new RuntimeException("ReaderSyncThread still alive");
    126             }
    127         }
    128         if (writer.isAlive()) {
    129             writer.requestToQuit();
    130             try {
    131                 writer.join(USB_TIMEOUT_MS);
    132             } catch (InterruptedException e) {}
    133             if (writer.isAlive()) {
    134                 throw new RuntimeException("WriterSyncThread still alive");
    135             }
    136         }
    137         mCallback.testFinished(mode, bufferSize);
    138         mCallback.testResult(
    139                 mode,
    140                 "Buffer size: " + bufferSize + " bytes. Speed " + writer.getSpeed());
    141     }
    142 
    143     private void runSyncTest(int bufferSize) {
    144         ReaderThread readerSync = new ReaderThread(mDevice, mUsbConnection, TEST_MODE_SYNC);
    145         WriterSyncThread writerSync = new WriterSyncThread(mDevice, mUsbConnection, bufferSize);
    146         runTest(writerSync, readerSync, TEST_MODE_SYNC, bufferSize);
    147     }
    148 
    149     private void runAsyncTest(int bufferSize) {
    150         ReaderThread readerAsync = new ReaderThread(mDevice, mUsbConnection, TEST_MODE_ASYNC);
    151         WriterAsyncThread writerAsync = new WriterAsyncThread(mDevice, mUsbConnection, bufferSize);
    152         runTest(writerAsync, readerAsync, TEST_MODE_ASYNC, bufferSize);
    153     }
    154 
    155     private class ReaderThread extends Thread {
    156         private boolean mShouldQuit = false;
    157         private final UsbDevice mDevice;
    158         private final UsbDeviceConnection mUsbConnection;
    159         private final int mMode;
    160         private final UsbEndpoint mBulkIn;
    161         private final byte[] mBuffer = new byte[16384];
    162 
    163         private ReaderThread(UsbDevice device, UsbDeviceConnection conn, int testMode) {
    164             super("AOAP reader");
    165             mDevice = device;
    166             mUsbConnection = conn;
    167             mMode = testMode;
    168             UsbInterface iface = mDevice.getInterface(0);
    169             // Setup bulk endpoints.
    170             UsbEndpoint bulkIn = null;
    171             UsbEndpoint bulkOut = null;
    172             for (int i = 0; i < iface.getEndpointCount(); i++) {
    173                 UsbEndpoint ep = iface.getEndpoint(i);
    174                 if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
    175                     if (bulkIn == null) {
    176                         bulkIn = ep;
    177                     }
    178                 } else {
    179                     if (bulkOut == null) {
    180                         bulkOut = ep;
    181                     }
    182                 }
    183             }
    184             if (bulkIn == null || bulkOut == null) {
    185                 throw new IllegalStateException("Unable to find bulk endpoints");
    186             }
    187             mBulkIn = bulkIn;
    188         }
    189 
    190         public synchronized void requestToQuit() {
    191             mShouldQuit = true;
    192         }
    193 
    194         private synchronized boolean shouldQuit() {
    195             return mShouldQuit;
    196         }
    197 
    198         @Override
    199         public void run() {
    200             while (!shouldQuit()) {
    201                 int read = mUsbConnection.bulkTransfer(
    202                         mBulkIn, mBuffer, mBuffer.length, USB_TIMEOUT_MS);
    203                 if (read > 0) {
    204                     Log.v(TAG, "Read " + read + " bytes");
    205                     break;
    206                 }
    207             }
    208         }
    209     }
    210 
    211     private abstract class BaseWriterThread extends Thread {
    212         protected boolean mShouldQuit = false;
    213         protected long mSpeed;
    214         protected final UsbDevice mDevice;
    215         protected final int mBufferSize;
    216         protected final UsbDeviceConnection mUsbConnection;
    217         protected final UsbEndpoint mBulkOut;
    218 
    219         private BaseWriterThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize) {
    220             super("AOAP writer");
    221             mDevice = device;
    222             mUsbConnection = conn;
    223             mBufferSize = bufferSize;
    224             UsbInterface iface = mDevice.getInterface(0);
    225             // Setup bulk endpoints.
    226             UsbEndpoint bulkIn = null;
    227             UsbEndpoint bulkOut = null;
    228             for (int i = 0; i < iface.getEndpointCount(); i++) {
    229                 UsbEndpoint ep = iface.getEndpoint(i);
    230                 if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
    231                     if (bulkIn == null) {
    232                         bulkIn = ep;
    233                     }
    234                 } else {
    235                     if (bulkOut == null) {
    236                         bulkOut = ep;
    237                     }
    238                 }
    239             }
    240             if (bulkIn == null || bulkOut == null) {
    241                 throw new IllegalStateException("Unable to find bulk endpoints");
    242             }
    243             mBulkOut = bulkOut;
    244         }
    245 
    246         public synchronized void requestToQuit() {
    247             mShouldQuit = true;
    248         }
    249 
    250         protected synchronized boolean shouldQuit() {
    251             return mShouldQuit;
    252         }
    253 
    254         public synchronized String getSpeed() {
    255             return Formatter.formatFileSize(mContext, mSpeed) + "/s";
    256         }
    257 
    258         protected synchronized void setSpeed(long speed) {
    259             // Speed is set in bytes/ms. Convert it to bytes/s.
    260             mSpeed = speed * 1000;
    261         }
    262 
    263         protected byte[] intToByte(int value) {
    264             return ByteBuffer.allocate(4).order(ORDER).putInt(value).array();
    265         }
    266 
    267     }
    268 
    269     private class WriterSyncThread extends BaseWriterThread {
    270         private WriterSyncThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize) {
    271             super(device, conn, bufferSize);
    272         }
    273 
    274         private boolean writeBufferSize() {
    275             byte[] bufferSizeArray = intToByte(mBufferSize);
    276             int sentBytes = mUsbConnection.bulkTransfer(
    277                     mBulkOut,
    278                     bufferSizeArray,
    279                     bufferSizeArray.length,
    280                     USB_TIMEOUT_MS);
    281             if (sentBytes < 0) {
    282                 Log.e(TAG, "Failed to write data");
    283                 return false;
    284             }
    285             return true;
    286         }
    287 
    288         @Override
    289         public void run() {
    290             int bytesToSend = TEST_DATA_SIZE;
    291             if (!writeBufferSize()) {
    292                 return;
    293             }
    294             byte[] buffer = new byte[mBufferSize];
    295             sRandom.nextBytes(buffer);
    296 
    297             long timeStart = System.currentTimeMillis();
    298             while (bytesToSend > 0 && !shouldQuit()) {
    299                 int sentBytes = mUsbConnection.bulkTransfer(
    300                         mBulkOut,
    301                         buffer,
    302                         (bytesToSend > buffer.length ? buffer.length : bytesToSend),
    303                         USB_TIMEOUT_MS);
    304                 if (sentBytes < 0) {
    305                     Log.e(TAG, "Failed to write data/");
    306                     return;
    307                 } else {
    308                     bytesToSend -= sentBytes;
    309                 }
    310             }
    311             setSpeed(TEST_DATA_SIZE / (System.currentTimeMillis() - timeStart));
    312         }
    313     }
    314 
    315     private class WriterAsyncThread extends BaseWriterThread {
    316         private WriterAsyncThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize) {
    317             super(device, conn, bufferSize);
    318         }
    319 
    320         private boolean drainRequests(int numRequests) {
    321             while (numRequests > 0) {
    322                 UsbRequest req = mUsbConnection.requestWait();
    323                 if (req == null) {
    324                     Log.e(TAG, "Error while requestWait");
    325                     return false;
    326                 }
    327                 req.close();
    328                 numRequests--;
    329             }
    330             return true;
    331         }
    332 
    333         private boolean writeBufferSize() {
    334             byte[] bufferSizeArray = intToByte(mBufferSize);
    335             UsbRequest sendRequest = getNewRequest();
    336             if (sendRequest == null) {
    337                 return false;
    338             }
    339 
    340             ByteBuffer bufferToSend = ByteBuffer.wrap(bufferSizeArray, 0, bufferSizeArray.length);
    341             boolean queued = sendRequest.queue(bufferToSend, bufferSizeArray.length);
    342 
    343             if (!queued) {
    344                 Log.e(TAG, "Failed to queue request");
    345                 return false;
    346             }
    347 
    348             UsbRequest req = mUsbConnection.requestWait();
    349             if (req == null) {
    350                 Log.e(TAG, "Error while waiting for request to complete.");
    351                 return false;
    352             }
    353             req.close();
    354             return true;
    355         }
    356 
    357         private UsbRequest getNewRequest() {
    358             UsbRequest request = new UsbRequest();
    359             if (!request.initialize(mUsbConnection, mBulkOut)) {
    360                 Log.e(TAG, "Failed to init");
    361                 return null;
    362             }
    363             return request;
    364         }
    365 
    366         @Override
    367         public void run() {
    368             int bytesToSend = TEST_DATA_SIZE;
    369             if (!writeBufferSize()) {
    370                 return;
    371             }
    372             int numRequests = 0;
    373             byte[] buffer = new byte[mBufferSize];
    374             sRandom.nextBytes(buffer);
    375 
    376             long timeStart = System.currentTimeMillis();
    377             while (bytesToSend > 0 && !shouldQuit()) {
    378                 numRequests++;
    379                 UsbRequest sendRequest = getNewRequest();
    380                 if (sendRequest == null) {
    381                     return;
    382                 }
    383 
    384                 int bufferSize = (bytesToSend > buffer.length ? buffer.length : bytesToSend);
    385                 ByteBuffer bufferToSend = ByteBuffer.wrap(buffer, 0, bufferSize);
    386                 boolean queued = sendRequest.queue(bufferToSend, bufferSize);
    387                 if (queued) {
    388                     bytesToSend -= buffer.length;
    389                 } else {
    390                     Log.e(TAG, "Failed to queue more data");
    391                     return;
    392                 }
    393 
    394                 if (numRequests == ASYNC_MAX_OUTSTANDING_REQUESTS) {
    395                     UsbRequest req = mUsbConnection.requestWait();
    396                     if (req == null) {
    397                         Log.e(TAG, "Error while waiting for request to complete.");
    398                         return;
    399                     }
    400                     req.close();
    401                     numRequests--;
    402                 }
    403             }
    404 
    405             if (!drainRequests(numRequests)) {
    406                 return;
    407             }
    408             setSpeed(TEST_DATA_SIZE / (System.currentTimeMillis() - timeStart));
    409             Log.d(TAG, "Wrote all the data. Exiting thread");
    410         }
    411     }
    412 }
    413