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