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