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 {
testStarted(int mode, int bufferSize)38         void testStarted(int mode, int bufferSize);
testFinished(int mode, int bufferSize)39         void testFinished(int mode, int bufferSize);
testSuiteFinished()40         void testSuiteFinished();
testResult(int mode, String update)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 
SpeedMeasurementController(Context context, UsbDevice device, UsbDeviceConnection conn, SpeedMeasurementControllerCallback callback)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 
release()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
run()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 
runTest(BaseWriterThread writer, ReaderThread reader, int mode, int bufferSize)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 
runSyncTest(int bufferSize)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 
runAsyncTest(int bufferSize)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 
ReaderThread(UsbDevice device, UsbDeviceConnection conn, int testMode)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 
requestToQuit()190         public synchronized void requestToQuit() {
191             mShouldQuit = true;
192         }
193 
shouldQuit()194         private synchronized boolean shouldQuit() {
195             return mShouldQuit;
196         }
197 
198         @Override
run()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 
BaseWriterThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize)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 
requestToQuit()246         public synchronized void requestToQuit() {
247             mShouldQuit = true;
248         }
249 
shouldQuit()250         protected synchronized boolean shouldQuit() {
251             return mShouldQuit;
252         }
253 
getSpeed()254         public synchronized String getSpeed() {
255             return Formatter.formatFileSize(mContext, mSpeed) + "/s";
256         }
257 
setSpeed(long speed)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 
intToByte(int value)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 {
WriterSyncThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize)270         private WriterSyncThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize) {
271             super(device, conn, bufferSize);
272         }
273 
writeBufferSize()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
run()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 {
WriterAsyncThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize)316         private WriterAsyncThread(UsbDevice device, UsbDeviceConnection conn, int bufferSize) {
317             super(device, conn, bufferSize);
318         }
319 
drainRequests(int numRequests)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 
writeBufferSize()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 
getNewRequest()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
run()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