1/*
2 * Copyright (C) 2010 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
17package android.hardware.usb;
18
19import android.annotation.Nullable;
20import android.util.Log;
21
22import com.android.internal.util.Preconditions;
23
24import dalvik.system.CloseGuard;
25
26import java.nio.BufferOverflowException;
27import java.nio.ByteBuffer;
28
29/**
30 * A class representing USB request packet.
31 * This can be used for both reading and writing data to or from a
32 * {@link android.hardware.usb.UsbDeviceConnection}.
33 * UsbRequests can be used to transfer data on bulk and interrupt endpoints.
34 * Requests on bulk endpoints can be sent synchronously via {@link UsbDeviceConnection#bulkTransfer}
35 * or asynchronously via {@link #queue} and {@link UsbDeviceConnection#requestWait}.
36 * Requests on interrupt endpoints are only send and received asynchronously.
37 *
38 * <p>Requests on endpoint zero are not supported by this class;
39 * use {@link UsbDeviceConnection#controlTransfer} for endpoint zero requests instead.
40 */
41public class UsbRequest {
42
43    private static final String TAG = "UsbRequest";
44
45    // From drivers/usb/core/devio.c
46    private static final int MAX_USBFS_BUFFER_SIZE = 16384;
47
48    // used by the JNI code
49    private long mNativeContext;
50
51    private UsbEndpoint mEndpoint;
52
53    /** The buffer that is currently being read / written */
54    private ByteBuffer mBuffer;
55
56    /** The amount of data to read / write when using {@link #queue} */
57    private int mLength;
58
59    // for client use
60    private Object mClientData;
61
62    // Prevent the connection from being finalized
63    private UsbDeviceConnection mConnection;
64
65    /**
66     * Whether this buffer was {@link #queue(ByteBuffer) queued using the new behavior} or
67     * {@link #queue(ByteBuffer, int) queued using the deprecated behavior}.
68     */
69    private boolean mIsUsingNewQueue;
70
71    /** Temporary buffer than might be used while buffer is enqueued */
72    private ByteBuffer mTempBuffer;
73
74    private final CloseGuard mCloseGuard = CloseGuard.get();
75
76    /**
77     * Lock for queue, enqueue and dequeue, so a queue operation can be finished by a dequeue
78     * operation on a different thread.
79     */
80    private final Object mLock = new Object();
81
82    public UsbRequest() {
83    }
84
85    /**
86     * Initializes the request so it can read or write data on the given endpoint.
87     * Whether the request allows reading or writing depends on the direction of the endpoint.
88     *
89     * @param endpoint the endpoint to be used for this request.
90     * @return true if the request was successfully opened.
91     */
92    public boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) {
93        mEndpoint = endpoint;
94        mConnection = Preconditions.checkNotNull(connection, "connection");
95
96        boolean wasInitialized = native_init(connection, endpoint.getAddress(),
97                endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval());
98
99        if (wasInitialized) {
100            mCloseGuard.open("close");
101        }
102
103        return wasInitialized;
104    }
105
106    /**
107     * Releases all resources related to this request.
108     */
109    public void close() {
110        if (mNativeContext != 0) {
111            mEndpoint = null;
112            mConnection = null;
113            native_close();
114            mCloseGuard.close();
115        }
116    }
117
118    @Override
119    protected void finalize() throws Throwable {
120        try {
121            if (mCloseGuard != null) {
122                mCloseGuard.warnIfOpen();
123            }
124
125            close();
126        } finally {
127            super.finalize();
128        }
129    }
130
131    /**
132     * Returns the endpoint for the request, or null if the request is not opened.
133     *
134     * @return the request's endpoint
135     */
136    public UsbEndpoint getEndpoint() {
137        return mEndpoint;
138    }
139
140    /**
141     * Returns the client data for the request.
142     * This can be used in conjunction with {@link #setClientData}
143     * to associate another object with this request, which can be useful for
144     * maintaining state between calls to {@link #queue} and
145     * {@link android.hardware.usb.UsbDeviceConnection#requestWait}
146     *
147     * @return the client data for the request
148     */
149    public Object getClientData() {
150        return mClientData;
151    }
152
153    /**
154     * Sets the client data for the request.
155     * This can be used in conjunction with {@link #getClientData}
156     * to associate another object with this request, which can be useful for
157     * maintaining state between calls to {@link #queue} and
158     * {@link android.hardware.usb.UsbDeviceConnection#requestWait}
159     *
160     * @param data the client data for the request
161     */
162    public void setClientData(Object data) {
163        mClientData = data;
164    }
165
166    /**
167     * Queues the request to send or receive data on its endpoint.
168     * <p>For OUT endpoints, the given buffer data will be sent on the endpoint. For IN endpoints,
169     * the endpoint will attempt to read the given number of bytes into the specified buffer. If the
170     * queueing operation is successful, return true. The result will be returned via
171     * {@link UsbDeviceConnection#requestWait}</p>
172     *
173     * @param buffer the buffer containing the bytes to write, or location to store the results of a
174     *               read. Position and array offset will be ignored and assumed to be 0. Limit and
175     *               capacity will be ignored. Once the request
176     *               {@link UsbDeviceConnection#requestWait() is processed} the position will be set
177     *               to the number of bytes read/written.
178     * @param length number of bytes to read or write.
179     *
180     * @return true if the queueing operation succeeded
181     *
182     * @deprecated Use {@link #queue(ByteBuffer)} instead.
183     */
184    @Deprecated
185    public boolean queue(ByteBuffer buffer, int length) {
186        boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
187        boolean result;
188
189        synchronized (mLock) {
190            // save our buffer for when the request has completed
191            mBuffer = buffer;
192            mLength = length;
193
194            // Note: On a buffer slice we lost the capacity information about the underlying buffer,
195            // hence we cannot check if the access would be a data leak/memory corruption.
196
197            if (buffer.isDirect()) {
198                result = native_queue_direct(buffer, length, out);
199            } else if (buffer.hasArray()) {
200                result = native_queue_array(buffer.array(), length, out);
201            } else {
202                throw new IllegalArgumentException("buffer is not direct and has no array");
203            }
204            if (!result) {
205                mBuffer = null;
206                mLength = 0;
207            }
208        }
209
210        return result;
211    }
212
213    /**
214     * Queues the request to send or receive data on its endpoint.
215     *
216     * <p>For OUT endpoints, the remaining bytes of the buffer will be sent on the endpoint. For IN
217     * endpoints, the endpoint will attempt to fill the remaining bytes of the buffer. If the
218     * queueing operation is successful, return true. The result will be returned via
219     * {@link UsbDeviceConnection#requestWait}</p>
220     *
221     * @param buffer the buffer containing the bytes to send, or the buffer to fill. The state
222     *               of the buffer is undefined until the request is returned by
223     *               {@link UsbDeviceConnection#requestWait}. If the request failed the buffer
224     *               will be unchanged; if the request succeeded the position of the buffer is
225     *               incremented by the number of bytes sent/received.
226     *
227     * @return true if the queueing operation succeeded
228     */
229    public boolean queue(@Nullable ByteBuffer buffer) {
230        // Request need to be initialized
231        Preconditions.checkState(mNativeContext != 0, "request is not initialized");
232
233        // Request can not be currently queued
234        Preconditions.checkState(!mIsUsingNewQueue, "this request is currently queued");
235
236        boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
237        boolean wasQueued;
238
239        synchronized (mLock) {
240            mBuffer = buffer;
241
242            if (buffer == null) {
243                // Null buffers enqueue empty USB requests which is supported
244                mIsUsingNewQueue = true;
245                wasQueued = native_queue(null, 0, 0);
246            } else {
247                // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once
248                Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE,
249                        "number of remaining bytes");
250
251                // Can not receive into read-only buffers.
252                Preconditions.checkArgument(!(buffer.isReadOnly() && !isSend), "buffer can not be "
253                        + "read-only when receiving data");
254
255                if (!buffer.isDirect()) {
256                    mTempBuffer = ByteBuffer.allocateDirect(mBuffer.remaining());
257
258                    if (isSend) {
259                        // Copy buffer into temporary buffer
260                        mBuffer.mark();
261                        mTempBuffer.put(mBuffer);
262                        mTempBuffer.flip();
263                        mBuffer.reset();
264                    }
265
266                    // Send/Receive into the temp buffer instead
267                    buffer = mTempBuffer;
268                }
269
270                mIsUsingNewQueue = true;
271                wasQueued = native_queue(buffer, buffer.position(), buffer.remaining());
272            }
273        }
274
275        if (!wasQueued) {
276            mIsUsingNewQueue = false;
277            mTempBuffer = null;
278            mBuffer = null;
279        }
280
281        return wasQueued;
282    }
283
284    /* package */ void dequeue(boolean useBufferOverflowInsteadOfIllegalArg) {
285        boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
286        int bytesTransferred;
287
288        synchronized (mLock) {
289            if (mIsUsingNewQueue) {
290                bytesTransferred = native_dequeue_direct();
291                mIsUsingNewQueue = false;
292
293                if (mBuffer == null) {
294                    // Nothing to do
295                } else if (mTempBuffer == null) {
296                    mBuffer.position(mBuffer.position() + bytesTransferred);
297                } else {
298                    mTempBuffer.limit(bytesTransferred);
299
300                    // The user might have modified mBuffer which might make put/position fail.
301                    // Changing the buffer while a request is in flight is not supported. Still,
302                    // make sure to free mTempBuffer correctly.
303                    try {
304                        if (isSend) {
305                            mBuffer.position(mBuffer.position() + bytesTransferred);
306                        } else {
307                            // Copy temp buffer back into original buffer
308                            mBuffer.put(mTempBuffer);
309                        }
310                    } finally {
311                        mTempBuffer = null;
312                    }
313                }
314            } else {
315                if (mBuffer.isDirect()) {
316                    bytesTransferred = native_dequeue_direct();
317                } else {
318                    bytesTransferred = native_dequeue_array(mBuffer.array(), mLength, isSend);
319                }
320                if (bytesTransferred >= 0) {
321                    int bytesToStore = Math.min(bytesTransferred, mLength);
322                    try {
323                        mBuffer.position(bytesToStore);
324                    } catch (IllegalArgumentException e) {
325                        if (useBufferOverflowInsteadOfIllegalArg) {
326                            Log.e(TAG, "Buffer " + mBuffer + " does not have enough space to read "
327                                    + bytesToStore + " bytes", e);
328                            throw new BufferOverflowException();
329                        } else {
330                            throw e;
331                        }
332                    }
333                }
334            }
335
336            mBuffer = null;
337            mLength = 0;
338        }
339    }
340
341    /**
342     * Cancels a pending queue operation.
343     *
344     * @return true if cancelling succeeded
345     */
346    public boolean cancel() {
347        return native_cancel();
348    }
349
350    private native boolean native_init(UsbDeviceConnection connection, int ep_address,
351            int ep_attributes, int ep_max_packet_size, int ep_interval);
352    private native void native_close();
353    private native boolean native_queue(ByteBuffer buffer, int offset, int length);
354    private native boolean native_queue_array(byte[] buffer, int length, boolean out);
355    private native int native_dequeue_array(byte[] buffer, int length, boolean out);
356    private native boolean native_queue_direct(ByteBuffer buffer, int length, boolean out);
357    private native int native_dequeue_direct();
358    private native boolean native_cancel();
359}
360