Merge "Switch MtpFfsHandle to aio, add control functions."
diff --git a/media/mtp/Android.bp b/media/mtp/Android.bp
index 5d5ae49..543ad5c 100644
--- a/media/mtp/Android.bp
+++ b/media/mtp/Android.bp
@@ -17,13 +17,13 @@
cc_library_shared {
name: "libmtp",
srcs: [
- "AsyncIO.cpp",
"MtpDataPacket.cpp",
"MtpDebug.cpp",
"MtpDevHandle.cpp",
"MtpDevice.cpp",
"MtpDeviceInfo.cpp",
"MtpEventPacket.cpp",
+ "MtpFfsCompatHandle.cpp",
"MtpFfsHandle.cpp",
"MtpObjectInfo.cpp",
"MtpPacket.cpp",
@@ -35,6 +35,7 @@
"MtpStorageInfo.cpp",
"MtpStringBuffer.cpp",
"MtpUtils.cpp",
+ "PosixAsyncIO.cpp",
],
export_include_dirs: ["."],
cflags: [
@@ -45,6 +46,7 @@
"-Werror",
],
shared_libs: [
+ "libasyncio",
"libbase",
"libutils",
"liblog",
diff --git a/media/mtp/AsyncIO.cpp b/media/mtp/AsyncIO.cpp
deleted file mode 100644
index bfb07dc..0000000
--- a/media/mtp/AsyncIO.cpp
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android-base/logging.h>
-#include <condition_variable>
-#include <memory>
-#include <mutex>
-#include <queue>
-
-#include "AsyncIO.h"
-
-namespace {
-
-void read_func(struct aiocb *aiocbp) {
- aiocbp->ret = TEMP_FAILURE_RETRY(pread(aiocbp->aio_fildes,
- aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
- if (aiocbp->ret == -1) aiocbp->error = errno;
-}
-
-void write_func(struct aiocb *aiocbp) {
- aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
- aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
- if (aiocbp->ret == -1) aiocbp->error = errno;
-}
-
-void splice_read_func(struct aiocb *aiocbp) {
- loff_t long_offset = aiocbp->aio_offset;
- aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes,
- &long_offset, aiocbp->aio_sink,
- NULL, aiocbp->aio_nbytes, 0));
- if (aiocbp->ret == -1) aiocbp->error = errno;
-}
-
-void splice_write_func(struct aiocb *aiocbp) {
- loff_t long_offset = aiocbp->aio_offset;
- aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes, NULL,
- aiocbp->aio_sink, &long_offset,
- aiocbp->aio_nbytes, 0));
- if (aiocbp->ret == -1) aiocbp->error = errno;
-}
-
-std::queue<std::unique_ptr<struct aiocb>> queue;
-std::mutex queue_lock;
-std::condition_variable queue_cond;
-std::condition_variable write_cond;
-int done = 1;
-void splice_write_pool_func(int) {
- while(1) {
- std::unique_lock<std::mutex> lk(queue_lock);
- queue_cond.wait(lk, []{return !queue.empty() || done;});
- if (queue.empty() && done) {
- return;
- }
- std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front());
- queue.pop();
- lk.unlock();
- write_cond.notify_one();
- splice_write_func(aiocbp.get());
- close(aiocbp->aio_fildes);
- }
-}
-
-void write_pool_func(int) {
- while(1) {
- std::unique_lock<std::mutex> lk(queue_lock);
- queue_cond.wait(lk, []{return !queue.empty() || done;});
- if (queue.empty() && done) {
- return;
- }
- std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front());
- queue.pop();
- lk.unlock();
- write_cond.notify_one();
- aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
- aiocbp->aio_pool_buf.get(), aiocbp->aio_nbytes, aiocbp->aio_offset));
- if (aiocbp->ret == -1) aiocbp->error = errno;
- }
-}
-
-constexpr int NUM_THREADS = 1;
-constexpr int MAX_QUEUE_SIZE = 10;
-std::thread pool[NUM_THREADS];
-
-} // end anonymous namespace
-
-aiocb::~aiocb() {
- CHECK(!thread.joinable());
-}
-
-void aio_pool_init(void(f)(int)) {
- CHECK(done == 1);
- done = 0;
- for (int i = 0; i < NUM_THREADS; i++) {
- pool[i] = std::thread(f, i);
- }
-}
-
-void aio_pool_splice_init() {
- aio_pool_init(splice_write_pool_func);
-}
-
-void aio_pool_write_init() {
- aio_pool_init(write_pool_func);
-}
-
-void aio_pool_end() {
- done = 1;
- for (int i = 0; i < NUM_THREADS; i++) {
- std::unique_lock<std::mutex> lk(queue_lock);
- lk.unlock();
- queue_cond.notify_one();
- }
-
- for (int i = 0; i < NUM_THREADS; i++) {
- pool[i].join();
- }
-}
-
-// used for both writes and splices depending on which init was used before.
-int aio_pool_write(struct aiocb *aiocbp) {
- std::unique_lock<std::mutex> lk(queue_lock);
- write_cond.wait(lk, []{return queue.size() < MAX_QUEUE_SIZE;});
- queue.push(std::unique_ptr<struct aiocb>(aiocbp));
- lk.unlock();
- queue_cond.notify_one();
- return 0;
-}
-
-int aio_read(struct aiocb *aiocbp) {
- aiocbp->thread = std::thread(read_func, aiocbp);
- return 0;
-}
-
-int aio_write(struct aiocb *aiocbp) {
- aiocbp->thread = std::thread(write_func, aiocbp);
- return 0;
-}
-
-int aio_splice_read(struct aiocb *aiocbp) {
- aiocbp->thread = std::thread(splice_read_func, aiocbp);
- return 0;
-}
-
-int aio_splice_write(struct aiocb *aiocbp) {
- aiocbp->thread = std::thread(splice_write_func, aiocbp);
- return 0;
-}
-
-int aio_error(const struct aiocb *aiocbp) {
- return aiocbp->error;
-}
-
-ssize_t aio_return(struct aiocb *aiocbp) {
- return aiocbp->ret;
-}
-
-int aio_suspend(struct aiocb *aiocbp[], int n,
- const struct timespec *) {
- for (int i = 0; i < n; i++) {
- aiocbp[i]->thread.join();
- }
- return 0;
-}
-
-int aio_cancel(int, struct aiocb *) {
- // Not implemented
- return -1;
-}
-
diff --git a/media/mtp/AsyncIO.h b/media/mtp/AsyncIO.h
deleted file mode 100644
index ed80828..0000000
--- a/media/mtp/AsyncIO.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _ASYNCIO_H
-#define _ASYNCIO_H
-
-#include <fcntl.h>
-#include <linux/aio_abi.h>
-#include <memory>
-#include <signal.h>
-#include <sys/cdefs.h>
-#include <sys/types.h>
-#include <time.h>
-#include <thread>
-#include <unistd.h>
-
-/**
- * Provides a subset of POSIX aio operations, as well
- * as similar operations with splice and threadpools.
- */
-
-struct aiocb {
- int aio_fildes; // Assumed to be the source for splices
- void *aio_buf; // Unused for splices
-
- // Used for threadpool operations only, freed automatically
- std::unique_ptr<char[]> aio_pool_buf;
-
- off_t aio_offset;
- size_t aio_nbytes;
-
- int aio_sink; // Unused for non splice r/w
-
- // Used internally
- std::thread thread;
- ssize_t ret;
- int error;
-
- ~aiocb();
-};
-
-// Submit a request for IO to be completed
-int aio_read(struct aiocb *);
-int aio_write(struct aiocb *);
-int aio_splice_read(struct aiocb *);
-int aio_splice_write(struct aiocb *);
-
-// Suspend current thread until given IO is complete, at which point
-// its return value and any errors can be accessed
-// All submitted requests must have a corresponding suspend.
-// aiocb->aio_buf must refer to valid memory until after the suspend call
-int aio_suspend(struct aiocb *[], int, const struct timespec *);
-int aio_error(const struct aiocb *);
-ssize_t aio_return(struct aiocb *);
-
-// (Currently unimplemented)
-int aio_cancel(int, struct aiocb *);
-
-// Initialize a threadpool to perform IO. Only one pool can be
-// running at a time.
-void aio_pool_write_init();
-void aio_pool_splice_init();
-// Suspend current thread until all queued work is complete, then ends the threadpool
-void aio_pool_end();
-// Submit IO work for the threadpool to complete. Memory associated with the work is
-// freed automatically when the work is complete.
-int aio_pool_write(struct aiocb *);
-
-#endif // ASYNCIO_H
-
diff --git a/media/mtp/IMtpHandle.h b/media/mtp/IMtpHandle.h
index 0557596..c65bdd0 100644
--- a/media/mtp/IMtpHandle.h
+++ b/media/mtp/IMtpHandle.h
@@ -18,13 +18,13 @@
#include <linux/usb/f_mtp.h>
-constexpr char FFS_MTP_EP0[] = "/dev/usb-ffs/mtp/ep0";
+namespace android {
class IMtpHandle {
public:
// Return number of bytes read/written, or -1 and errno is set
- virtual int read(void *data, int len) = 0;
- virtual int write(const void *data, int len) = 0;
+ virtual int read(void *data, size_t len) = 0;
+ virtual int write(const void *data, size_t len) = 0;
// Return 0 if send/receive is successful, or -1 and errno is set
virtual int receiveFile(mtp_file_range mfr, bool zero_packet) = 0;
@@ -40,8 +40,7 @@
virtual ~IMtpHandle() {}
};
-IMtpHandle *get_ffs_handle();
-IMtpHandle *get_mtp_handle();
+}
#endif // _IMTP_HANDLE_H
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
index a449d6f..1ddb821 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -20,12 +20,12 @@
#include "MtpPacket.h"
#include "mtp.h"
-class IMtpHandle;
struct usb_device;
struct usb_request;
namespace android {
+class IMtpHandle;
class MtpStringBuffer;
class MtpDataPacket : public MtpPacket {
diff --git a/media/mtp/MtpDevHandle.cpp b/media/mtp/MtpDevHandle.cpp
index 9aa0aec..6aa57ac 100644
--- a/media/mtp/MtpDevHandle.cpp
+++ b/media/mtp/MtpDevHandle.cpp
@@ -14,57 +14,37 @@
* limitations under the License.
*/
-#include <utils/Log.h>
-#include <fcntl.h>
-#include <sys/stat.h>
+#include <android-base/logging.h>
#include <cutils/properties.h>
#include <dirent.h>
#include <errno.h>
+#include <fcntl.h>
#include <linux/usb/ch9.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/endian.h>
#include <unistd.h>
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
-#include "IMtpHandle.h"
+#include "MtpDevHandle.h"
+
+namespace android {
constexpr char mtp_dev_path[] = "/dev/mtp_usb";
-class MtpDevHandle : public IMtpHandle {
-private:
- android::base::unique_fd mFd;
-
-public:
- MtpDevHandle();
- ~MtpDevHandle();
- int read(void *data, int len);
- int write(const void *data, int len);
-
- int receiveFile(mtp_file_range mfr, bool);
- int sendFile(mtp_file_range mfr);
- int sendEvent(mtp_event me);
-
- int start();
- void close();
-
- int configure(bool ptp);
-};
-
MtpDevHandle::MtpDevHandle()
: mFd(-1) {};
MtpDevHandle::~MtpDevHandle() {}
-int MtpDevHandle::read(void *data, int len) {
+int MtpDevHandle::read(void *data, size_t len) {
return ::read(mFd, data, len);
}
-int MtpDevHandle::write(const void *data, int len) {
+int MtpDevHandle::write(const void *data, size_t len) {
return ::write(mFd, data, len);
}
@@ -81,7 +61,7 @@
}
int MtpDevHandle::start() {
- mFd = android::base::unique_fd(TEMP_FAILURE_RETRY(open(mtp_dev_path, O_RDWR)));
+ mFd.reset(TEMP_FAILURE_RETRY(open(mtp_dev_path, O_RDWR)));
if (mFd == -1) return -1;
return 0;
}
@@ -95,6 +75,4 @@
return 0;
}
-IMtpHandle *get_mtp_handle() {
- return new MtpDevHandle();
-}
+} // namespace android
diff --git a/media/mtp/MtpDevHandle.h b/media/mtp/MtpDevHandle.h
new file mode 100644
index 0000000..b0480ed
--- /dev/null
+++ b/media/mtp/MtpDevHandle.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEV_HANDLE_H
+#define _MTP_DEV_HANDLE_H
+
+#include <android-base/unique_fd.h>
+#include "IMtpHandle.h"
+
+namespace android {
+
+class MtpDevHandle : public IMtpHandle {
+private:
+ android::base::unique_fd mFd;
+
+public:
+ MtpDevHandle();
+ ~MtpDevHandle();
+ int read(void *data, size_t len);
+ int write(const void *data, size_t len);
+
+ int receiveFile(mtp_file_range mfr, bool);
+ int sendFile(mtp_file_range mfr);
+ int sendEvent(mtp_event me);
+
+ int start();
+ void close();
+
+ int configure(bool ptp);
+};
+
+} // namespace android
+
+#endif // _MTP_FFS_HANDLE_H
diff --git a/media/mtp/MtpFfsCompatHandle.cpp b/media/mtp/MtpFfsCompatHandle.cpp
new file mode 100644
index 0000000..3dd73f3
--- /dev/null
+++ b/media/mtp/MtpFfsCompatHandle.cpp
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
+#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/endian.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "PosixAsyncIO.h"
+#include "MtpFfsCompatHandle.h"
+#include "mtp.h"
+
+#define FUNCTIONFS_ENDPOINT_ALLOC _IOR('g', 231, __u32)
+
+namespace {
+
+// Must be divisible by all max packet size values
+constexpr int MAX_FILE_CHUNK_SIZE = 3145728;
+
+// Safe values since some devices cannot handle large DMAs
+// To get good performance, override these with
+// higher values per device using the properties
+// sys.usb.ffs.max_read and sys.usb.ffs.max_write
+constexpr int USB_FFS_MAX_WRITE = MTP_BUFFER_SIZE;
+constexpr int USB_FFS_MAX_READ = MTP_BUFFER_SIZE;
+
+static_assert(USB_FFS_MAX_WRITE > 0, "Max r/w values must be > 0!");
+static_assert(USB_FFS_MAX_READ > 0, "Max r/w values must be > 0!");
+
+constexpr unsigned int MAX_MTP_FILE_SIZE = 0xFFFFFFFF;
+
+constexpr size_t ENDPOINT_ALLOC_RETRIES = 10;
+
+} // anonymous namespace
+
+namespace android {
+
+MtpFfsCompatHandle::MtpFfsCompatHandle() :
+ mMaxWrite(USB_FFS_MAX_WRITE),
+ mMaxRead(USB_FFS_MAX_READ) {}
+
+MtpFfsCompatHandle::~MtpFfsCompatHandle() {}
+
+int MtpFfsCompatHandle::writeHandle(int fd, const void* data, size_t len) {
+ int ret = 0;
+ const char* buf = static_cast<const char*>(data);
+ while (len > 0) {
+ int write_len = std::min(mMaxWrite, len);
+ int n = TEMP_FAILURE_RETRY(::write(fd, buf, write_len));
+
+ if (n < 0) {
+ PLOG(ERROR) << "write ERROR: fd = " << fd << ", n = " << n;
+ return -1;
+ } else if (n < write_len) {
+ errno = EIO;
+ PLOG(ERROR) << "less written than expected";
+ return -1;
+ }
+ buf += n;
+ len -= n;
+ ret += n;
+ }
+ return ret;
+}
+
+int MtpFfsCompatHandle::readHandle(int fd, void* data, size_t len) {
+ int ret = 0;
+ char* buf = static_cast<char*>(data);
+ while (len > 0) {
+ int read_len = std::min(mMaxRead, len);
+ int n = TEMP_FAILURE_RETRY(::read(fd, buf, read_len));
+ if (n < 0) {
+ PLOG(ERROR) << "read ERROR: fd = " << fd << ", n = " << n;
+ return -1;
+ }
+ ret += n;
+ if (n < read_len) // done reading early
+ break;
+ buf += n;
+ len -= n;
+ }
+ return ret;
+}
+
+int MtpFfsCompatHandle::start() {
+ mLock.lock();
+
+ if (!openEndpoints())
+ return -1;
+
+ for (unsigned i = 0; i < NUM_IO_BUFS; i++) {
+ mIobuf[i].bufs.resize(MAX_FILE_CHUNK_SIZE);
+ posix_madvise(mIobuf[i].bufs.data(), MAX_FILE_CHUNK_SIZE,
+ POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED);
+ }
+
+ // Get device specific r/w size
+ mMaxWrite = android::base::GetIntProperty("sys.usb.ffs.max_write", USB_FFS_MAX_WRITE);
+ mMaxRead = android::base::GetIntProperty("sys.usb.ffs.max_read", USB_FFS_MAX_READ);
+
+ size_t attempts = 0;
+ while (mMaxWrite >= USB_FFS_MAX_WRITE && mMaxRead >= USB_FFS_MAX_READ &&
+ attempts < ENDPOINT_ALLOC_RETRIES) {
+ // If larger contiguous chunks of memory aren't available, attempt to try
+ // smaller allocations.
+ if (ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxWrite)) ||
+ ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxRead))) {
+ if (errno == ENODEV) {
+ // Driver hasn't enabled endpoints yet.
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ attempts += 1;
+ continue;
+ }
+ mMaxWrite /= 2;
+ mMaxRead /=2;
+ } else {
+ return 0;
+ }
+ }
+ // Try to start MtpServer anyway, with the smallest max r/w values
+ mMaxWrite = USB_FFS_MAX_WRITE;
+ mMaxRead = USB_FFS_MAX_READ;
+ PLOG(ERROR) << "Functionfs could not allocate any memory!";
+ return 0;
+}
+
+int MtpFfsCompatHandle::read(void* data, size_t len) {
+ return readHandle(mBulkOut, data, len);
+}
+
+int MtpFfsCompatHandle::write(const void* data, size_t len) {
+ return writeHandle(mBulkIn, data, len);
+}
+
+int MtpFfsCompatHandle::receiveFile(mtp_file_range mfr, bool zero_packet) {
+ // When receiving files, the incoming length is given in 32 bits.
+ // A >4G file is given as 0xFFFFFFFF
+ uint32_t file_length = mfr.length;
+ uint64_t offset = mfr.offset;
+ int packet_size = getPacketSize(mBulkOut);
+
+ unsigned char *data = mIobuf[0].bufs.data();
+ unsigned char *data2 = mIobuf[1].bufs.data();
+
+ struct aiocb aio;
+ aio.aio_fildes = mfr.fd;
+ aio.aio_buf = nullptr;
+ struct aiocb *aiol[] = {&aio};
+ int ret = -1;
+ size_t length;
+ bool read = false;
+ bool write = false;
+
+ posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+
+ // Break down the file into pieces that fit in buffers
+ while (file_length > 0 || write) {
+ if (file_length > 0) {
+ length = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length);
+
+ // Read data from USB, handle errors after waiting for write thread.
+ ret = readHandle(mBulkOut, data, length);
+
+ if (file_length != MAX_MTP_FILE_SIZE && ret < static_cast<int>(length)) {
+ ret = -1;
+ errno = EIO;
+ }
+ read = true;
+ }
+
+ if (write) {
+ // get the return status of the last write request
+ aio_suspend(aiol, 1, nullptr);
+
+ int written = aio_return(&aio);
+ if (written == -1) {
+ errno = aio_error(&aio);
+ return -1;
+ }
+ if (static_cast<size_t>(written) < aio.aio_nbytes) {
+ errno = EIO;
+ return -1;
+ }
+ write = false;
+ }
+
+ // If there was an error reading above
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (read) {
+ if (file_length == MAX_MTP_FILE_SIZE) {
+ // For larger files, receive until a short packet is received.
+ if (static_cast<size_t>(ret) < length) {
+ file_length = 0;
+ }
+ } else {
+ file_length -= ret;
+ }
+ // Enqueue a new write request
+ aio_prepare(&aio, data, length, offset);
+ aio_write(&aio);
+
+ offset += ret;
+ std::swap(data, data2);
+
+ write = true;
+ read = false;
+ }
+ }
+ // Receive an empty packet if size is a multiple of the endpoint size.
+ if (ret % packet_size == 0 || zero_packet) {
+ if (TEMP_FAILURE_RETRY(::read(mBulkOut, data, packet_size)) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int MtpFfsCompatHandle::sendFile(mtp_file_range mfr) {
+ uint64_t file_length = mfr.length;
+ uint32_t given_length = std::min(static_cast<uint64_t>(MAX_MTP_FILE_SIZE),
+ file_length + sizeof(mtp_data_header));
+ uint64_t offset = mfr.offset;
+ int packet_size = getPacketSize(mBulkIn);
+
+ // If file_length is larger than a size_t, truncating would produce the wrong comparison.
+ // Instead, promote the left side to 64 bits, then truncate the small result.
+ int init_read_len = std::min(
+ static_cast<uint64_t>(packet_size - sizeof(mtp_data_header)), file_length);
+
+ unsigned char *data = mIobuf[0].bufs.data();
+ unsigned char *data2 = mIobuf[1].bufs.data();
+
+ posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+
+ struct aiocb aio;
+ aio.aio_fildes = mfr.fd;
+ struct aiocb *aiol[] = {&aio};
+ int ret, length;
+ int error = 0;
+ bool read = false;
+ bool write = false;
+
+ // Send the header data
+ mtp_data_header *header = reinterpret_cast<mtp_data_header*>(data);
+ header->length = htole32(given_length);
+ header->type = htole16(2); /* data packet */
+ header->command = htole16(mfr.command);
+ header->transaction_id = htole32(mfr.transaction_id);
+
+ // Some hosts don't support header/data separation even though MTP allows it
+ // Handle by filling first packet with initial file data
+ if (TEMP_FAILURE_RETRY(pread(mfr.fd, reinterpret_cast<char*>(data) +
+ sizeof(mtp_data_header), init_read_len, offset))
+ != init_read_len) return -1;
+ if (writeHandle(mBulkIn, data, sizeof(mtp_data_header) + init_read_len) == -1) return -1;
+ file_length -= init_read_len;
+ offset += init_read_len;
+ ret = init_read_len + sizeof(mtp_data_header);
+
+ // Break down the file into pieces that fit in buffers
+ while (file_length > 0) {
+ if (read) {
+ // Wait for the previous read to finish
+ aio_suspend(aiol, 1, nullptr);
+ ret = aio_return(&aio);
+ if (ret == -1) {
+ errno = aio_error(&aio);
+ return -1;
+ }
+ if (static_cast<size_t>(ret) < aio.aio_nbytes) {
+ errno = EIO;
+ return -1;
+ }
+
+ file_length -= ret;
+ offset += ret;
+ std::swap(data, data2);
+ read = false;
+ write = true;
+ }
+
+ if (error == -1) {
+ return -1;
+ }
+
+ if (file_length > 0) {
+ length = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length);
+ // Queue up another read
+ aio_prepare(&aio, data, length, offset);
+ aio_read(&aio);
+ read = true;
+ }
+
+ if (write) {
+ if (writeHandle(mBulkIn, data2, ret) == -1) {
+ error = -1;
+ }
+ write = false;
+ }
+ }
+
+ if (ret % packet_size == 0) {
+ // If the last packet wasn't short, send a final empty packet
+ if (TEMP_FAILURE_RETRY(::write(mBulkIn, data, 0)) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+} // namespace android
+
diff --git a/media/mtp/MtpFfsCompatHandle.h b/media/mtp/MtpFfsCompatHandle.h
new file mode 100644
index 0000000..cd61482
--- /dev/null
+++ b/media/mtp/MtpFfsCompatHandle.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_FFS_COMPAT_HANDLE_H
+#define _MTP_FFS_COMPAT_HANDLE_H
+
+#include <MtpFfsHandle.h>
+
+namespace android {
+
+template <class T> class MtpFfsHandleTest;
+
+class MtpFfsCompatHandle : public MtpFfsHandle {
+ template <class T> friend class android::MtpFfsHandleTest;
+private:
+ int writeHandle(int fd, const void *data, size_t len);
+ int readHandle(int fd, void *data, size_t len);
+
+ size_t mMaxWrite;
+ size_t mMaxRead;
+
+public:
+ int read(void* data, size_t len) override;
+ int write(const void* data, size_t len) override;
+ int receiveFile(mtp_file_range mfr, bool zero_packet) override;
+ int sendFile(mtp_file_range mfr) override;
+
+ /**
+ * Open ffs endpoints and allocate necessary kernel and user memory.
+ * Will sleep until endpoints are enabled, for up to 1 second.
+ */
+ int start() override;
+
+ MtpFfsCompatHandle();
+ ~MtpFfsCompatHandle();
+};
+
+} // namespace android
+
+#endif // _MTP_FFS_COMPAT_HANDLE_H
+
diff --git a/media/mtp/MtpFfsHandle.cpp b/media/mtp/MtpFfsHandle.cpp
index 4132fed..89b20e5 100644
--- a/media/mtp/MtpFfsHandle.cpp
+++ b/media/mtp/MtpFfsHandle.cpp
@@ -16,32 +16,29 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <asyncio/AsyncIO.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/usb/ch9.h>
#include <linux/usb/functionfs.h>
-#include <mutex>
+#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/endian.h>
+#include <sys/eventfd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
+#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
-#include <vector>
-#include "AsyncIO.h"
+#include "PosixAsyncIO.h"
#include "MtpFfsHandle.h"
#include "mtp.h"
-#define cpu_to_le16(x) htole16(x)
-#define cpu_to_le32(x) htole32(x)
-
-#define FUNCTIONFS_ENDPOINT_ALLOC _IOR('g', 231, __u32)
-
namespace {
constexpr char FFS_MTP_EP_IN[] = "/dev/usb-ffs/mtp/ep1";
@@ -51,23 +48,18 @@
constexpr int MAX_PACKET_SIZE_FS = 64;
constexpr int MAX_PACKET_SIZE_HS = 512;
constexpr int MAX_PACKET_SIZE_SS = 1024;
+constexpr int MAX_PACKET_SIZE_EV = 28;
-// Must be divisible by all max packet size values
-constexpr int MAX_FILE_CHUNK_SIZE = 3145728;
+constexpr unsigned AIO_BUFS_MAX = 128;
+constexpr unsigned AIO_BUF_LEN = 16384;
-// Safe values since some devices cannot handle large DMAs
-// To get good performance, override these with
-// higher values per device using the properties
-// sys.usb.ffs.max_read and sys.usb.ffs.max_write
-constexpr int USB_FFS_MAX_WRITE = MTP_BUFFER_SIZE;
-constexpr int USB_FFS_MAX_READ = MTP_BUFFER_SIZE;
+constexpr unsigned FFS_NUM_EVENTS = 5;
-static_assert(USB_FFS_MAX_WRITE > 0, "Max r/w values must be > 0!");
-static_assert(USB_FFS_MAX_READ > 0, "Max r/w values must be > 0!");
+constexpr unsigned MAX_FILE_CHUNK_SIZE = AIO_BUFS_MAX * AIO_BUF_LEN;
-constexpr unsigned int MAX_MTP_FILE_SIZE = 0xFFFFFFFF;
+constexpr uint32_t MAX_MTP_FILE_SIZE = 0xFFFFFFFF;
-constexpr size_t ENDPOINT_ALLOC_RETRIES = 10;
+struct timespec ZERO_TIMEOUT = { 0, 0 };
struct func_desc {
struct usb_interface_descriptor intf;
@@ -143,12 +135,12 @@
.wMaxPacketSize = MAX_PACKET_SIZE_FS,
};
-const struct usb_endpoint_descriptor_no_audio fs_intr = {
+const struct usb_endpoint_descriptor_no_audio intr = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 3 | USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_INT,
- .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+ .wMaxPacketSize = MAX_PACKET_SIZE_EV,
.bInterval = 6,
};
@@ -168,15 +160,6 @@
.wMaxPacketSize = MAX_PACKET_SIZE_HS,
};
-const struct usb_endpoint_descriptor_no_audio hs_intr = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = 3 | USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_INT,
- .wMaxPacketSize = MAX_PACKET_SIZE_HS,
- .bInterval = 6,
-};
-
const struct usb_endpoint_descriptor_no_audio ss_sink = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -193,15 +176,6 @@
.wMaxPacketSize = MAX_PACKET_SIZE_SS,
};
-const struct usb_endpoint_descriptor_no_audio ss_intr = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = 3 | USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_INT,
- .wMaxPacketSize = MAX_PACKET_SIZE_SS,
- .bInterval = 6,
-};
-
const struct usb_ss_ep_comp_descriptor ss_sink_comp = {
.bLength = sizeof(ss_sink_comp),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
@@ -223,14 +197,14 @@
.intf = mtp_interface_desc,
.sink = fs_sink,
.source = fs_source,
- .intr = fs_intr,
+ .intr = intr,
};
const struct func_desc mtp_hs_descriptors = {
.intf = mtp_interface_desc,
.sink = hs_sink,
.source = hs_source,
- .intr = hs_intr,
+ .intr = intr,
};
const struct ss_func_desc mtp_ss_descriptors = {
@@ -239,7 +213,7 @@
.sink_comp = ss_sink_comp,
.source = ss_source,
.source_comp = ss_source_comp,
- .intr = ss_intr,
+ .intr = intr,
.intr_comp = ss_intr_comp,
};
@@ -247,14 +221,14 @@
.intf = ptp_interface_desc,
.sink = fs_sink,
.source = fs_source,
- .intr = fs_intr,
+ .intr = intr,
};
const struct func_desc ptp_hs_descriptors = {
.intf = ptp_interface_desc,
.sink = hs_sink,
.source = hs_source,
- .intr = hs_intr,
+ .intr = intr,
};
const struct ss_func_desc ptp_ss_descriptors = {
@@ -263,7 +237,7 @@
.sink_comp = ss_sink_comp,
.source = ss_source,
.source_comp = ss_source_comp,
- .intr = ss_intr,
+ .intr = intr,
.intr_comp = ss_intr_comp,
};
@@ -276,24 +250,37 @@
} __attribute__((packed)) lang0;
} __attribute__((packed)) strings = {
.header = {
- .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
- .length = cpu_to_le32(sizeof(strings)),
- .str_count = cpu_to_le32(1),
- .lang_count = cpu_to_le32(1),
+ .magic = htole32(FUNCTIONFS_STRINGS_MAGIC),
+ .length = htole32(sizeof(strings)),
+ .str_count = htole32(1),
+ .lang_count = htole32(1),
},
.lang0 = {
- .code = cpu_to_le16(0x0409),
+ .code = htole16(0x0409),
.str1 = STR_INTERFACE,
},
};
+struct mtp_device_status {
+ uint16_t wLength;
+ uint16_t wCode;
+};
+
} // anonymous namespace
namespace android {
-MtpFfsHandle::MtpFfsHandle() :
- mMaxWrite(USB_FFS_MAX_WRITE),
- mMaxRead(USB_FFS_MAX_READ) {}
+int MtpFfsHandle::getPacketSize(int ffs_fd) {
+ struct usb_endpoint_descriptor desc;
+ if (ioctl(ffs_fd, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&desc))) {
+ PLOG(ERROR) << "Could not get FFS bulk-in descriptor";
+ return MAX_PACKET_SIZE_HS;
+ } else {
+ return desc.wMaxPacketSize;
+ }
+}
+
+MtpFfsHandle::MtpFfsHandle() {}
MtpFfsHandle::~MtpFfsHandle() {}
@@ -303,13 +290,51 @@
mBulkOut.reset();
}
+bool MtpFfsHandle::openEndpoints() {
+ if (mBulkIn < 0) {
+ mBulkIn.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_IN, O_RDWR)));
+ if (mBulkIn < 0) {
+ PLOG(ERROR) << FFS_MTP_EP_IN << ": cannot open bulk in ep";
+ return false;
+ }
+ }
+
+ if (mBulkOut < 0) {
+ mBulkOut.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_OUT, O_RDWR)));
+ if (mBulkOut < 0) {
+ PLOG(ERROR) << FFS_MTP_EP_OUT << ": cannot open bulk out ep";
+ return false;
+ }
+ }
+
+ if (mIntr < 0) {
+ mIntr.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_INTR, O_RDWR)));
+ if (mIntr < 0) {
+ PLOG(ERROR) << FFS_MTP_EP_INTR << ": cannot open intr ep";
+ return false;
+ }
+ }
+ return true;
+}
+
+void MtpFfsHandle::advise(int fd) {
+ for (unsigned i = 0; i < NUM_IO_BUFS; i++) {
+ if (posix_madvise(mIobuf[i].bufs.data(), MAX_FILE_CHUNK_SIZE,
+ POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) < 0)
+ PLOG(ERROR) << "Failed to madvise";
+ }
+ if (posix_fadvise(fd, 0, 0,
+ POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED) < 0)
+ PLOG(ERROR) << "Failed to fadvise";
+}
+
bool MtpFfsHandle::initFunctionfs() {
ssize_t ret;
struct desc_v1 v1_descriptor;
struct desc_v2 v2_descriptor;
- v2_descriptor.header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2);
- v2_descriptor.header.length = cpu_to_le32(sizeof(v2_descriptor));
+ v2_descriptor.header.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2);
+ v2_descriptor.header.length = htole32(sizeof(v2_descriptor));
v2_descriptor.header.flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC |
FUNCTIONFS_HAS_SS_DESC;
v2_descriptor.fs_count = 4;
@@ -328,8 +353,8 @@
ret = TEMP_FAILURE_RETRY(::write(mControl, &v2_descriptor, sizeof(v2_descriptor)));
if (ret < 0) {
- v1_descriptor.header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC);
- v1_descriptor.header.length = cpu_to_le32(sizeof(v1_descriptor));
+ v1_descriptor.header.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC);
+ v1_descriptor.header.length = htole32(sizeof(v1_descriptor));
v1_descriptor.header.fs_count = 4;
v1_descriptor.header.hs_count = 4;
v1_descriptor.fs_descs = mPtp ? ptp_fs_descriptors : mtp_fs_descriptors;
@@ -347,8 +372,6 @@
goto err;
}
}
- if (mBulkIn > -1 || mBulkOut > -1 || mIntr > -1)
- LOG(WARNING) << "Endpoints were not closed before configure!";
return true;
@@ -361,130 +384,152 @@
mControl.reset();
}
-int MtpFfsHandle::writeHandle(int fd, const void* data, int len) {
- LOG(VERBOSE) << "MTP about to write fd = " << fd << ", len=" << len;
- int ret = 0;
- const char* buf = static_cast<const char*>(data);
- while (len > 0) {
- int write_len = std::min(mMaxWrite, len);
- int n = TEMP_FAILURE_RETRY(::write(fd, buf, write_len));
-
- if (n < 0) {
- PLOG(ERROR) << "write ERROR: fd = " << fd << ", n = " << n;
- return -1;
- } else if (n < write_len) {
- errno = EIO;
- PLOG(ERROR) << "less written than expected";
- return -1;
- }
- buf += n;
- len -= n;
- ret += n;
+int MtpFfsHandle::doAsync(void* data, size_t len, bool read) {
+ struct io_event ioevs[1];
+ if (len > AIO_BUF_LEN) {
+ LOG(ERROR) << "Mtp read/write too large " << len;
+ errno = EINVAL;
+ return -1;
}
+ mIobuf[0].buf[0] = reinterpret_cast<unsigned char*>(data);
+ if (iobufSubmit(&mIobuf[0], read ? mBulkOut : mBulkIn, len, read) == -1)
+ return -1;
+ int ret = waitEvents(&mIobuf[0], 1, ioevs, nullptr);
+ mIobuf[0].buf[0] = mIobuf[0].bufs.data();
return ret;
}
-int MtpFfsHandle::readHandle(int fd, void* data, int len) {
- LOG(VERBOSE) << "MTP about to read fd = " << fd << ", len=" << len;
+int MtpFfsHandle::read(void* data, size_t len) {
+ return doAsync(data, len, true);
+}
+
+int MtpFfsHandle::write(const void* data, size_t len) {
+ return doAsync(const_cast<void*>(data), len, false);
+}
+
+int MtpFfsHandle::handleEvent() {
+
+ std::vector<usb_functionfs_event> events(FFS_NUM_EVENTS);
+ usb_functionfs_event *event = events.data();
+ int nbytes = TEMP_FAILURE_RETRY(::read(mControl, event,
+ events.size() * sizeof(usb_functionfs_event)));
+ if (nbytes == -1) {
+ return -1;
+ }
int ret = 0;
- char* buf = static_cast<char*>(data);
- while (len > 0) {
- int read_len = std::min(mMaxRead, len);
- int n = TEMP_FAILURE_RETRY(::read(fd, buf, read_len));
- if (n < 0) {
- PLOG(ERROR) << "read ERROR: fd = " << fd << ", n = " << n;
- return -1;
- }
- ret += n;
- if (n < read_len) // done reading early
+ for (size_t n = nbytes / sizeof *event; n; --n, ++event) {
+ switch (event->type) {
+ case FUNCTIONFS_BIND:
+ case FUNCTIONFS_ENABLE:
+ case FUNCTIONFS_RESUME:
+ ret = 0;
+ errno = 0;
break;
- buf += n;
- len -= n;
- }
- return ret;
-}
-
-int MtpFfsHandle::spliceReadHandle(int fd, int pipe_out, int len) {
- LOG(VERBOSE) << "MTP about to splice read fd = " << fd << ", len=" << len;
- int ret = 0;
- loff_t dummyoff;
- while (len > 0) {
- int read_len = std::min(mMaxRead, len);
- dummyoff = 0;
- int n = TEMP_FAILURE_RETRY(splice(fd, &dummyoff, pipe_out, nullptr, read_len, 0));
- if (n < 0) {
- PLOG(ERROR) << "splice read ERROR: fd = " << fd << ", n = " << n;
- return -1;
- }
- ret += n;
- if (n < read_len) // done reading early
+ case FUNCTIONFS_SUSPEND:
+ case FUNCTIONFS_UNBIND:
+ case FUNCTIONFS_DISABLE:
+ errno = ESHUTDOWN;
+ ret = -1;
break;
- len -= n;
+ case FUNCTIONFS_SETUP:
+ if (handleControlRequest(&event->u.setup) == -1)
+ ret = -1;
+ break;
+ default:
+ LOG(ERROR) << "Mtp Event " << event->type << " (unknown)";
+ }
}
return ret;
}
-int MtpFfsHandle::read(void* data, int len) {
- return readHandle(mBulkOut, data, len);
-}
+int MtpFfsHandle::handleControlRequest(const struct usb_ctrlrequest *setup) {
+ uint8_t type = setup->bRequestType;
+ uint8_t code = setup->bRequest;
+ uint16_t length = setup->wLength;
+ uint16_t index = setup->wIndex;
+ uint16_t value = setup->wValue;
+ std::vector<char> buf;
+ buf.resize(length);
+ int ret = 0;
-int MtpFfsHandle::write(const void* data, int len) {
- return writeHandle(mBulkIn, data, len);
+ if (!(type & USB_DIR_IN)) {
+ if (::read(mControl, buf.data(), length) != length) {
+ PLOG(ERROR) << "Mtp error ctrlreq read data";
+ }
+ }
+
+ if ((type & USB_TYPE_MASK) == USB_TYPE_CLASS && index == 0 && value == 0) {
+ switch(code) {
+ case MTP_REQ_RESET:
+ case MTP_REQ_CANCEL:
+ errno = ECANCELED;
+ ret = -1;
+ break;
+ case MTP_REQ_GET_DEVICE_STATUS:
+ {
+ if (length < sizeof(struct mtp_device_status) + 4) {
+ errno = EINVAL;
+ return -1;
+ }
+ struct mtp_device_status *st = reinterpret_cast<struct mtp_device_status*>(buf.data());
+ st->wLength = htole16(sizeof(st));
+ if (mCanceled) {
+ st->wLength += 4;
+ st->wCode = MTP_RESPONSE_TRANSACTION_CANCELLED;
+ uint16_t *endpoints = reinterpret_cast<uint16_t*>(st + 1);
+ endpoints[0] = ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_REVMAP);
+ endpoints[1] = ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_REVMAP);
+ mCanceled = false;
+ } else {
+ st->wCode = MTP_RESPONSE_OK;
+ }
+ length = st->wLength;
+ break;
+ }
+ default:
+ LOG(ERROR) << "Unrecognized Mtp class request! " << code;
+ }
+ } else {
+ LOG(ERROR) << "Unrecognized request type " << type;
+ }
+
+ if (type & USB_DIR_IN) {
+ if (::write(mControl, buf.data(), length) != length) {
+ PLOG(ERROR) << "Mtp error ctrlreq write data";
+ }
+ }
+ return 0;
}
int MtpFfsHandle::start() {
mLock.lock();
- mBulkIn.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_IN, O_RDWR)));
- if (mBulkIn < 0) {
- PLOG(ERROR) << FFS_MTP_EP_IN << ": cannot open bulk in ep";
+ if (!openEndpoints())
return -1;
- }
- mBulkOut.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_OUT, O_RDWR)));
- if (mBulkOut < 0) {
- PLOG(ERROR) << FFS_MTP_EP_OUT << ": cannot open bulk out ep";
- return -1;
- }
-
- mIntr.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_INTR, O_RDWR)));
- if (mIntr < 0) {
- PLOG(ERROR) << FFS_MTP_EP0 << ": cannot open intr ep";
- return -1;
- }
-
- mBuffer1.resize(MAX_FILE_CHUNK_SIZE);
- mBuffer2.resize(MAX_FILE_CHUNK_SIZE);
- posix_madvise(mBuffer1.data(), MAX_FILE_CHUNK_SIZE,
- POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED);
- posix_madvise(mBuffer2.data(), MAX_FILE_CHUNK_SIZE,
- POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED);
-
- // Get device specific r/w size
- mMaxWrite = android::base::GetIntProperty("sys.usb.ffs.max_write", USB_FFS_MAX_WRITE);
- mMaxRead = android::base::GetIntProperty("sys.usb.ffs.max_read", USB_FFS_MAX_READ);
-
- size_t attempts = 0;
- while (mMaxWrite >= USB_FFS_MAX_WRITE && mMaxRead >= USB_FFS_MAX_READ &&
- attempts < ENDPOINT_ALLOC_RETRIES) {
- // If larger contiguous chunks of memory aren't available, attempt to try
- // smaller allocations.
- if (ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxWrite)) ||
- ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxRead))) {
- if (errno == ENODEV) {
- // Driver hasn't enabled endpoints yet.
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- attempts += 1;
- continue;
- }
- mMaxWrite /= 2;
- mMaxRead /=2;
- } else {
- return 0;
+ for (unsigned i = 0; i < NUM_IO_BUFS; i++) {
+ mIobuf[i].bufs.resize(MAX_FILE_CHUNK_SIZE);
+ mIobuf[i].iocb.resize(AIO_BUFS_MAX);
+ mIobuf[i].iocbs.resize(AIO_BUFS_MAX);
+ mIobuf[i].buf.resize(AIO_BUFS_MAX);
+ for (unsigned j = 0; j < AIO_BUFS_MAX; j++) {
+ mIobuf[i].buf[j] = mIobuf[i].bufs.data() + j * AIO_BUF_LEN;
+ mIobuf[i].iocb[j] = &mIobuf[i].iocbs[j];
}
}
- // Try to start MtpServer anyway, with the smallest max r/w values
- PLOG(ERROR) << "Functionfs could not allocate any memory!";
+
+ memset(&mCtx, 0, sizeof(mCtx));
+ if (io_setup(AIO_BUFS_MAX, &mCtx) < 0) {
+ PLOG(ERROR) << "unable to setup aio";
+ return -1;
+ }
+ mEventFd.reset(eventfd(0, EFD_NONBLOCK));
+ mPollFds[0].fd = mControl;
+ mPollFds[0].events = POLLIN;
+ mPollFds[1].fd = mEventFd;
+ mPollFds[1].events = POLLIN;
+
+ mCanceled = false;
return 0;
}
@@ -506,215 +551,368 @@
if (!initFunctionfs()) {
ret = -1;
}
+
mLock.unlock();
return ret;
}
void MtpFfsHandle::close() {
+ io_destroy(mCtx);
closeEndpoints();
mLock.unlock();
}
-/* Read from USB and write to a local file. */
-int MtpFfsHandle::receiveFile(mtp_file_range mfr, bool zero_packet) {
- // When receiving files, the incoming length is given in 32 bits.
- // A >4G file is given as 0xFFFFFFFF
- uint32_t file_length = mfr.length;
- uint64_t offset = mfr.offset;
- struct usb_endpoint_descriptor mBulkOut_desc;
- int packet_size;
+int MtpFfsHandle::waitEvents(struct io_buffer *buf, int min_events, struct io_event *events,
+ int *counter) {
+ int num_events = 0;
+ int ret = 0;
+ int error = 0;
- if (ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&mBulkOut_desc))) {
- PLOG(ERROR) << "Could not get FFS bulk-out descriptor";
- packet_size = MAX_PACKET_SIZE_HS;
- } else {
- packet_size = mBulkOut_desc.wMaxPacketSize;
+ while (num_events < min_events) {
+ if (poll(mPollFds, 2, 0) == -1) {
+ PLOG(ERROR) << "Mtp error during poll()";
+ return -1;
+ }
+ if (mPollFds[0].revents & POLLIN) {
+ mPollFds[0].revents = 0;
+ if (handleEvent() == -1) {
+ error = errno;
+ }
+ }
+ if (mPollFds[1].revents & POLLIN) {
+ mPollFds[1].revents = 0;
+ uint64_t ev_cnt = 0;
+
+ if (::read(mEventFd, &ev_cnt, sizeof(ev_cnt)) == -1) {
+ PLOG(ERROR) << "Mtp unable to read eventfd";
+ error = errno;
+ continue;
+ }
+
+ // It's possible that io_getevents will return more events than the eventFd reported,
+ // since events may appear in the time between the calls. In this case, the eventFd will
+ // show up as readable next iteration, but there will be fewer or no events to actually
+ // wait for. Thus we never want io_getevents to block.
+ int this_events = TEMP_FAILURE_RETRY(io_getevents(mCtx, 0, AIO_BUFS_MAX, events, &ZERO_TIMEOUT));
+ if (this_events == -1) {
+ PLOG(ERROR) << "Mtp error getting events";
+ error = errno;
+ }
+ // Add up the total amount of data and find errors on the way.
+ for (unsigned j = 0; j < static_cast<unsigned>(this_events); j++) {
+ if (events[j].res < 0) {
+ errno = -events[j].res;
+ PLOG(ERROR) << "Mtp got error event at " << j << " and " << buf->actual << " total";
+ error = errno;
+ }
+ ret += events[j].res;
+ }
+ num_events += this_events;
+ if (counter)
+ *counter += this_events;
+ }
+ if (error) {
+ errno = error;
+ ret = -1;
+ break;
+ }
+ }
+ return ret;
+}
+
+void MtpFfsHandle::cancelTransaction() {
+ // Device cancels by stalling both bulk endpoints.
+ if (::read(mBulkIn, nullptr, 0) != -1 || errno != EBADMSG)
+ PLOG(ERROR) << "Mtp stall failed on bulk in";
+ if (::write(mBulkOut, nullptr, 0) != -1 || errno != EBADMSG)
+ PLOG(ERROR) << "Mtp stall failed on bulk out";
+ mCanceled = true;
+ errno = ECANCELED;
+}
+
+int MtpFfsHandle::cancelEvents(struct iocb **iocb, struct io_event *events, unsigned start,
+ unsigned end) {
+ // Some manpages for io_cancel are out of date and incorrect.
+ // io_cancel will return -EINPROGRESS on success and does
+ // not place the event in the given memory. We have to use
+ // io_getevents to wait for all the events we cancelled.
+ int ret = 0;
+ unsigned num_events = 0;
+ int save_errno = errno;
+ errno = 0;
+
+ for (unsigned j = start; j < end; j++) {
+ if (io_cancel(mCtx, iocb[j], nullptr) != -1 || errno != EINPROGRESS) {
+ PLOG(ERROR) << "Mtp couldn't cancel request " << j;
+ } else {
+ num_events++;
+ }
+ }
+ if (num_events != end - start) {
+ ret = -1;
+ errno = EIO;
+ }
+ int evs = TEMP_FAILURE_RETRY(io_getevents(mCtx, num_events, AIO_BUFS_MAX, events, nullptr));
+ if (static_cast<unsigned>(evs) != num_events) {
+ PLOG(ERROR) << "Mtp couldn't cancel all requests, got " << evs;
+ ret = -1;
}
- char *data = mBuffer1.data();
- char *data2 = mBuffer2.data();
+ uint64_t ev_cnt = 0;
+ if (num_events && ::read(mEventFd, &ev_cnt, sizeof(ev_cnt)) == -1)
+ PLOG(ERROR) << "Mtp Unable to read event fd";
+
+ if (ret == 0) {
+ // Restore errno since it probably got overriden with EINPROGRESS.
+ errno = save_errno;
+ }
+ return ret;
+}
+
+int MtpFfsHandle::iobufSubmit(struct io_buffer *buf, int fd, unsigned length, bool read) {
+ int ret = 0;
+ buf->actual = AIO_BUFS_MAX;
+ for (unsigned j = 0; j < AIO_BUFS_MAX; j++) {
+ unsigned rq_length = std::min(AIO_BUF_LEN, length - AIO_BUF_LEN * j);
+ io_prep(buf->iocb[j], fd, buf->buf[j], rq_length, 0, read);
+ buf->iocb[j]->aio_flags |= IOCB_FLAG_RESFD;
+ buf->iocb[j]->aio_resfd = mEventFd;
+
+ // Not enough data, so table is truncated.
+ if (rq_length < AIO_BUF_LEN || length == AIO_BUF_LEN * (j + 1)) {
+ buf->actual = j + 1;
+ break;
+ }
+ }
+
+ ret = io_submit(mCtx, buf->actual, buf->iocb.data());
+ if (ret != static_cast<int>(buf->actual)) {
+ PLOG(ERROR) << "Mtp io_submit got " << ret << " expected " << buf->actual;
+ if (ret != -1) {
+ errno = EIO;
+ }
+ ret = -1;
+ }
+ return ret;
+}
+
+int MtpFfsHandle::receiveFile(mtp_file_range mfr, bool zero_packet) {
+ // When receiving files, the incoming length is given in 32 bits.
+ // A >=4G file is given as 0xFFFFFFFF
+ uint32_t file_length = mfr.length;
+ uint64_t offset = mfr.offset;
struct aiocb aio;
aio.aio_fildes = mfr.fd;
aio.aio_buf = nullptr;
struct aiocb *aiol[] = {&aio};
- int ret = -1;
- size_t length;
- bool read = false;
- bool write = false;
- posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+ int ret = -1;
+ unsigned i = 0;
+ size_t length;
+ struct io_event ioevs[AIO_BUFS_MAX];
+ bool has_write = false;
+ bool error = false;
+ bool write_error = false;
+ int packet_size = getPacketSize(mBulkOut);
+ bool short_packet = false;
+ advise(mfr.fd);
// Break down the file into pieces that fit in buffers
- while (file_length > 0 || write) {
+ while (file_length > 0 || has_write) {
+ // Queue an asynchronous read from USB.
if (file_length > 0) {
length = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length);
-
- // Read data from USB, handle errors after waiting for write thread.
- ret = readHandle(mBulkOut, data, length);
-
- if (file_length != MAX_MTP_FILE_SIZE && ret < static_cast<int>(length)) {
- ret = -1;
- errno = EIO;
- }
- read = true;
+ if (iobufSubmit(&mIobuf[i], mBulkOut, length, true) == -1)
+ error = true;
}
- if (write) {
- // get the return status of the last write request
+ // Get the return status of the last write request.
+ if (has_write) {
aio_suspend(aiol, 1, nullptr);
-
int written = aio_return(&aio);
- if (written == -1) {
- errno = aio_error(&aio);
- return -1;
- }
if (static_cast<size_t>(written) < aio.aio_nbytes) {
- errno = EIO;
- return -1;
+ errno = written == -1 ? aio_error(&aio) : EIO;
+ PLOG(ERROR) << "Mtp error writing to disk";
+ write_error = true;
}
- write = false;
+ has_write = false;
}
- // If there was an error reading above
- if (ret == -1) {
+ if (error) {
return -1;
}
- if (read) {
+ // Get the result of the read request, and queue a write to disk.
+ if (file_length > 0) {
+ unsigned num_events = 0;
+ ret = 0;
+ unsigned short_i = mIobuf[i].actual;
+ while (num_events < short_i) {
+ // Get all events up to the short read, if there is one.
+ // We must wait for each event since data transfer could end at any time.
+ int this_events = 0;
+ int event_ret = waitEvents(&mIobuf[i], 1, ioevs, &this_events);
+ num_events += this_events;
+
+ if (event_ret == -1) {
+ cancelEvents(mIobuf[i].iocb.data(), ioevs, num_events, mIobuf[i].actual);
+ return -1;
+ }
+ ret += event_ret;
+ for (int j = 0; j < this_events; j++) {
+ // struct io_event contains a pointer to the associated struct iocb as a __u64.
+ if (static_cast<__u64>(ioevs[j].res) <
+ reinterpret_cast<struct iocb*>(ioevs[j].obj)->aio_nbytes) {
+ // We've found a short event. Store the index since
+ // events won't necessarily arrive in the order they are queued.
+ short_i = (ioevs[j].obj - reinterpret_cast<uint64_t>(mIobuf[i].iocbs.data()))
+ / sizeof(struct iocb) + 1;
+ short_packet = true;
+ }
+ }
+ }
+ if (short_packet) {
+ if (cancelEvents(mIobuf[i].iocb.data(), ioevs, short_i, mIobuf[i].actual)) {
+ write_error = true;
+ }
+ }
if (file_length == MAX_MTP_FILE_SIZE) {
// For larger files, receive until a short packet is received.
if (static_cast<size_t>(ret) < length) {
file_length = 0;
}
+ } else if (ret < static_cast<int>(length)) {
+ // If file is less than 4G and we get a short packet, it's an error.
+ errno = EIO;
+ LOG(ERROR) << "Mtp got unexpected short packet";
+ return -1;
} else {
- // Receive an empty packet if size is a multiple of the endpoint size.
file_length -= ret;
}
+
+ if (write_error) {
+ cancelTransaction();
+ return -1;
+ }
+
// Enqueue a new write request
- aio.aio_buf = data;
- aio.aio_sink = mfr.fd;
- aio.aio_offset = offset;
- aio.aio_nbytes = ret;
+ aio_prepare(&aio, mIobuf[i].bufs.data(), ret, offset);
aio_write(&aio);
offset += ret;
- std::swap(data, data2);
-
- write = true;
- read = false;
+ i = (i + 1) % NUM_IO_BUFS;
+ has_write = true;
}
}
- if (ret % packet_size == 0 || zero_packet) {
- if (TEMP_FAILURE_RETRY(::read(mBulkOut, data, packet_size)) != 0) {
+ if ((ret % packet_size == 0 && !short_packet) || zero_packet) {
+ // Receive an empty packet if size is a multiple of the endpoint size
+ // and we didn't already get an empty packet from the header or large file.
+ if (read(mIobuf[0].bufs.data(), packet_size) != 0) {
return -1;
}
}
return 0;
}
-/* Read from a local file and send over USB. */
int MtpFfsHandle::sendFile(mtp_file_range mfr) {
uint64_t file_length = mfr.length;
uint32_t given_length = std::min(static_cast<uint64_t>(MAX_MTP_FILE_SIZE),
file_length + sizeof(mtp_data_header));
uint64_t offset = mfr.offset;
- struct usb_endpoint_descriptor mBulkIn_desc;
- int packet_size;
-
- if (ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&mBulkIn_desc))) {
- PLOG(ERROR) << "Could not get FFS bulk-in descriptor";
- packet_size = MAX_PACKET_SIZE_HS;
- } else {
- packet_size = mBulkIn_desc.wMaxPacketSize;
- }
+ int packet_size = getPacketSize(mBulkIn);
// If file_length is larger than a size_t, truncating would produce the wrong comparison.
// Instead, promote the left side to 64 bits, then truncate the small result.
int init_read_len = std::min(
static_cast<uint64_t>(packet_size - sizeof(mtp_data_header)), file_length);
- char *data = mBuffer1.data();
- char *data2 = mBuffer2.data();
-
- posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+ advise(mfr.fd);
struct aiocb aio;
aio.aio_fildes = mfr.fd;
struct aiocb *aiol[] = {&aio};
- int ret, length;
- int error = 0;
- bool read = false;
- bool write = false;
+ int ret = 0;
+ int length, num_read;
+ unsigned i = 0;
+ struct io_event ioevs[AIO_BUFS_MAX];
+ bool error = false;
+ bool has_write = false;
// Send the header data
- mtp_data_header *header = reinterpret_cast<mtp_data_header*>(data);
- header->length = __cpu_to_le32(given_length);
- header->type = __cpu_to_le16(2); /* data packet */
- header->command = __cpu_to_le16(mfr.command);
- header->transaction_id = __cpu_to_le32(mfr.transaction_id);
+ mtp_data_header *header = reinterpret_cast<mtp_data_header*>(mIobuf[0].bufs.data());
+ header->length = htole32(given_length);
+ header->type = htole16(2); // data packet
+ header->command = htole16(mfr.command);
+ header->transaction_id = htole32(mfr.transaction_id);
// Some hosts don't support header/data separation even though MTP allows it
// Handle by filling first packet with initial file data
- if (TEMP_FAILURE_RETRY(pread(mfr.fd, reinterpret_cast<char*>(data) +
+ if (TEMP_FAILURE_RETRY(pread(mfr.fd, mIobuf[0].bufs.data() +
sizeof(mtp_data_header), init_read_len, offset))
!= init_read_len) return -1;
- if (writeHandle(mBulkIn, data, sizeof(mtp_data_header) + init_read_len) == -1) return -1;
+ if (write(mIobuf[0].bufs.data(), sizeof(mtp_data_header) + init_read_len) == -1)
+ return -1;
file_length -= init_read_len;
offset += init_read_len;
ret = init_read_len + sizeof(mtp_data_header);
// Break down the file into pieces that fit in buffers
- while(file_length > 0) {
- if (read) {
- // Wait for the previous read to finish
- aio_suspend(aiol, 1, nullptr);
- ret = aio_return(&aio);
- if (ret == -1) {
- errno = aio_error(&aio);
- return -1;
- }
- if (static_cast<size_t>(ret) < aio.aio_nbytes) {
- errno = EIO;
- return -1;
- }
-
- file_length -= ret;
- offset += ret;
- std::swap(data, data2);
- read = false;
- write = true;
+ while(file_length > 0 || has_write) {
+ if (file_length > 0) {
+ // Queue up a read from disk.
+ length = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length);
+ aio_prepare(&aio, mIobuf[i].bufs.data(), length, offset);
+ aio_read(&aio);
}
- if (error == -1) {
- return -1;
+ if (has_write) {
+ // Wait for usb write. Cancel unwritten portion if there's an error.
+ int num_events = 0;
+ if (waitEvents(&mIobuf[(i-1)%NUM_IO_BUFS], mIobuf[(i-1)%NUM_IO_BUFS].actual, ioevs,
+ &num_events) != ret) {
+ error = true;
+ cancelEvents(mIobuf[(i-1)%NUM_IO_BUFS].iocb.data(), ioevs, num_events,
+ mIobuf[(i-1)%NUM_IO_BUFS].actual);
+ }
+ has_write = false;
}
if (file_length > 0) {
- length = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length);
- // Queue up another read
- aio.aio_buf = data;
- aio.aio_offset = offset;
- aio.aio_nbytes = length;
- aio_read(&aio);
- read = true;
+ // Wait for the previous read to finish
+ aio_suspend(aiol, 1, nullptr);
+ num_read = aio_return(&aio);
+ if (static_cast<size_t>(num_read) < aio.aio_nbytes) {
+ errno = num_read == -1 ? aio_error(&aio) : EIO;
+ PLOG(ERROR) << "Mtp error reading from disk";
+ cancelTransaction();
+ return -1;
+ }
+
+ file_length -= num_read;
+ offset += num_read;
+
+ if (error) {
+ return -1;
+ }
+
+ // Queue up a write to usb.
+ if (iobufSubmit(&mIobuf[i], mBulkIn, num_read, false) == -1) {
+ return -1;
+ }
+ has_write = true;
+ ret = num_read;
}
- if (write) {
- if (writeHandle(mBulkIn, data2, ret) == -1) {
- error = -1;
- }
- write = false;
- }
+ i = (i + 1) % NUM_IO_BUFS;
}
if (ret % packet_size == 0) {
// If the last packet wasn't short, send a final empty packet
- if (TEMP_FAILURE_RETRY(::write(mBulkIn, data, 0)) != 0) {
+ if (write(mIobuf[0].bufs.data(), 0) != 0) {
return -1;
}
}
-
return 0;
}
@@ -739,7 +937,3 @@
} // namespace android
-IMtpHandle *get_ffs_handle() {
- return new android::MtpFfsHandle();
-}
-
diff --git a/media/mtp/MtpFfsHandle.h b/media/mtp/MtpFfsHandle.h
index b637d65..2f90bd1 100644
--- a/media/mtp/MtpFfsHandle.h
+++ b/media/mtp/MtpFfsHandle.h
@@ -18,26 +18,51 @@
#define _MTP_FFS_HANDLE_H
#include <android-base/unique_fd.h>
+#include <linux/aio_abi.h>
+#include <mutex>
+#include <sys/poll.h>
+#include <time.h>
+#include <thread>
+#include <vector>
+
#include <IMtpHandle.h>
namespace android {
-class MtpFfsHandleTest;
+constexpr char FFS_MTP_EP0[] = "/dev/usb-ffs/mtp/ep0";
+
+constexpr int NUM_IO_BUFS = 2;
+
+struct io_buffer {
+ std::vector<struct iocb> iocbs; // Holds memory for all iocbs. Not used directly.
+ std::vector<struct iocb*> iocb; // Pointers to individual iocbs, for syscalls
+ std::vector<unsigned char> bufs; // A large buffer, used with filesystem io
+ std::vector<unsigned char*> buf; // Pointers within the larger buffer, for syscalls
+ unsigned actual; // The number of buffers submitted for this request
+};
+
+template <class T> class MtpFfsHandleTest;
class MtpFfsHandle : public IMtpHandle {
- friend class android::MtpFfsHandleTest;
-private:
- int writeHandle(int fd, const void *data, int len);
- int readHandle(int fd, void *data, int len);
- int spliceReadHandle(int fd, int fd_out, int len);
+ template <class T> friend class android::MtpFfsHandleTest;
+protected:
bool initFunctionfs();
void closeConfig();
void closeEndpoints();
+ void advise(int fd);
+ int handleControlRequest(const struct usb_ctrlrequest *request);
+ int doAsync(void* data, size_t len, bool read);
+ int handleEvent();
+ void cancelTransaction();
void doSendEvent(mtp_event me);
+ bool openEndpoints();
+
+ static int getPacketSize(int ffs_fd);
bool mPtp;
+ bool mCanceled;
- std::timed_mutex mLock;
+ std::timed_mutex mLock; // protects configure() vs main loop
android::base::unique_fd mControl;
// "in" from the host's perspective => sink for mtp server
@@ -46,28 +71,35 @@
android::base::unique_fd mBulkOut;
android::base::unique_fd mIntr;
- int mMaxWrite;
- int mMaxRead;
+ aio_context_t mCtx;
- std::vector<char> mBuffer1;
- std::vector<char> mBuffer2;
+ android::base::unique_fd mEventFd;
+ struct pollfd mPollFds[2];
+
+ struct io_buffer mIobuf[NUM_IO_BUFS];
+
+ // Submit an io request of given length. Return amount submitted or -1.
+ int iobufSubmit(struct io_buffer *buf, int fd, unsigned length, bool read);
+
+ // Cancel submitted requests from start to end in the given array. Return 0 or -1.
+ int cancelEvents(struct iocb **iocb, struct io_event *events, unsigned start, unsigned end);
+
+ // Wait for at minimum the given number of events. Returns the amount of data in the returned
+ // events. Increments counter by the number of events returned.
+ int waitEvents(struct io_buffer *buf, int min_events, struct io_event *events, int *counter);
public:
- int read(void *data, int len);
- int write(const void *data, int len);
+ int read(void *data, size_t len) override;
+ int write(const void *data, size_t len) override;
- int receiveFile(mtp_file_range mfr, bool zero_packet);
- int sendFile(mtp_file_range mfr);
- int sendEvent(mtp_event me);
+ int receiveFile(mtp_file_range mfr, bool zero_packet) override;
+ int sendFile(mtp_file_range mfr) override;
+ int sendEvent(mtp_event me) override;
- /**
- * Open ffs endpoints and allocate necessary kernel and user memory.
- * Will sleep until endpoints are enabled, for up to 1 second.
- */
- int start();
- void close();
+ int start() override;
+ void close() override;
- int configure(bool ptp);
+ int configure(bool ptp) override;
MtpFfsHandle();
~MtpFfsHandle();
@@ -86,5 +118,5 @@
} // namespace android
-#endif // _MTP_FF_HANDLE_H
+#endif // _MTP_FFS_HANDLE_H
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 2180462..e148b0c 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -31,6 +31,9 @@
#include "MtpDebug.h"
#include "MtpDatabase.h"
+#include "MtpDevHandle.h"
+#include "MtpFfsCompatHandle.h"
+#include "MtpFfsHandle.h"
#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpServer.h"
@@ -125,16 +128,21 @@
IMtpHandle* MtpServer::sHandle = nullptr;
int MtpServer::configure(bool usePtp) {
+ bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
if (sHandle == nullptr) {
- bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
- sHandle = ffs_ok ? get_ffs_handle() : get_mtp_handle();
+ if (ffs_ok) {
+ bool aio_compat = android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false);
+ sHandle = aio_compat ? new MtpFfsCompatHandle() : new MtpFfsHandle();
+ } else {
+ sHandle = new MtpDevHandle();
+ }
}
-
- int ret = sHandle->configure(usePtp);
- if (ret) ALOGE("Failed to configure MTP driver!");
- else android::base::SetProperty("sys.usb.ffs.mtp.ready", "1");
-
- return ret;
+ if (sHandle->configure(usePtp)) {
+ ALOGE("Failed to configure Mtp driver!");
+ return -1;
+ }
+ android::base::SetProperty("sys.usb.ffs.mtp.ready", "1");
+ return 0;
}
void MtpServer::addStorage(MtpStorage* storage) {
@@ -878,6 +886,7 @@
length = fileLength - offset;
const char* filePath = (const char *)pathBuf;
+ ALOGV("sending partial %s %" PRIu64 " %" PRIu32, filePath, offset, length);
mtp_file_range mfr;
mfr.fd = open(filePath, O_RDONLY);
if (mfr.fd < 0) {
diff --git a/media/mtp/PosixAsyncIO.cpp b/media/mtp/PosixAsyncIO.cpp
new file mode 100644
index 0000000..e67c568
--- /dev/null
+++ b/media/mtp/PosixAsyncIO.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <unistd.h>
+
+#include "PosixAsyncIO.h"
+
+namespace {
+
+void read_func(struct aiocb *aiocbp) {
+ aiocbp->ret = TEMP_FAILURE_RETRY(pread(aiocbp->aio_fildes,
+ aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+ if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void write_func(struct aiocb *aiocbp) {
+ aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
+ aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+ if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+} // end anonymous namespace
+
+aiocb::~aiocb() {
+ CHECK(!thread.joinable());
+}
+
+int aio_read(struct aiocb *aiocbp) {
+ aiocbp->thread = std::thread(read_func, aiocbp);
+ return 0;
+}
+
+int aio_write(struct aiocb *aiocbp) {
+ aiocbp->thread = std::thread(write_func, aiocbp);
+ return 0;
+}
+
+int aio_error(const struct aiocb *aiocbp) {
+ return aiocbp->error;
+}
+
+ssize_t aio_return(struct aiocb *aiocbp) {
+ return aiocbp->ret;
+}
+
+int aio_suspend(struct aiocb *aiocbp[], int n,
+ const struct timespec *) {
+ for (int i = 0; i < n; i++) {
+ aiocbp[i]->thread.join();
+ }
+ return 0;
+}
+
+void aio_prepare(struct aiocb *aiocbp, void* buf, size_t count, off_t offset) {
+ aiocbp->aio_buf = buf;
+ aiocbp->aio_offset = offset;
+ aiocbp->aio_nbytes = count;
+}
diff --git a/media/mtp/PosixAsyncIO.h b/media/mtp/PosixAsyncIO.h
new file mode 100644
index 0000000..590aaef
--- /dev/null
+++ b/media/mtp/PosixAsyncIO.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _POSIXASYNCIO_H
+#define _POSIXASYNCIO_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <time.h>
+#include <thread>
+#include <unistd.h>
+
+/**
+ * Provides a subset of POSIX aio operations.
+ */
+
+struct aiocb {
+ int aio_fildes;
+ void *aio_buf;
+
+ off_t aio_offset;
+ size_t aio_nbytes;
+
+ // Used internally
+ std::thread thread;
+ ssize_t ret;
+ int error;
+
+ ~aiocb();
+};
+
+// Submit a request for IO to be completed
+int aio_read(struct aiocb *);
+int aio_write(struct aiocb *);
+
+// Suspend current thread until given IO is complete, at which point
+// its return value and any errors can be accessed
+// All submitted requests must have a corresponding suspend.
+// aiocb->aio_buf must refer to valid memory until after the suspend call
+int aio_suspend(struct aiocb *[], int, const struct timespec *);
+int aio_error(const struct aiocb *);
+ssize_t aio_return(struct aiocb *);
+
+// Helper method for setting aiocb members
+void aio_prepare(struct aiocb *, void*, size_t, off_t);
+
+#endif // POSIXASYNCIO_H
+
diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h
index adfb102..644780f 100644
--- a/media/mtp/mtp.h
+++ b/media/mtp/mtp.h
@@ -493,4 +493,10 @@
#define MTP_ASSOCIATION_TYPE_UNDEFINED 0x0000
#define MTP_ASSOCIATION_TYPE_GENERIC_FOLDER 0x0001
+// MTP class reqeusts
+#define MTP_REQ_CANCEL 0x64
+#define MTP_REQ_GET_EXT_EVENT_DATA 0x65
+#define MTP_REQ_RESET 0x66
+#define MTP_REQ_GET_DEVICE_STATUS 0x67
+
#endif // _MTP_H
diff --git a/media/mtp/tests/Android.bp b/media/mtp/tests/Android.bp
index 356406d..a0480b6 100644
--- a/media/mtp/tests/Android.bp
+++ b/media/mtp/tests/Android.bp
@@ -30,8 +30,9 @@
}
cc_test {
- name: "async_io_test",
- srcs: ["AsyncIO_test.cpp"],
+ name: "posix_async_io_test",
+ test_suites: ["device-tests"],
+ srcs: ["PosixAsyncIO_test.cpp"],
shared_libs: [
"libbase",
"libmtp",
diff --git a/media/mtp/tests/AsyncIO_test.cpp b/media/mtp/tests/AsyncIO_test.cpp
deleted file mode 100644
index b5f4538..0000000
--- a/media/mtp/tests/AsyncIO_test.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#define LOG_TAG "AsyncIO_test.cpp"
-
-#include <android-base/test_utils.h>
-#include <fcntl.h>
-#include <gtest/gtest.h>
-#include <string>
-#include <unistd.h>
-#include <utils/Log.h>
-
-#include "AsyncIO.h"
-
-namespace android {
-
-constexpr int TEST_PACKET_SIZE = 512;
-constexpr int POOL_COUNT = 10;
-
-static const std::string dummyDataStr =
- "/*\n * Copyright 2015 The Android Open Source Project\n *\n * Licensed un"
- "der the Apache License, Version 2.0 (the \"License\");\n * you may not us"
- "e this file except in compliance with the License.\n * You may obtain a c"
- "opy of the License at\n *\n * http://www.apache.org/licenses/LICENSE"
- "-2.0\n *\n * Unless required by applicable law or agreed to in writing, s"
- "oftware\n * distributed under the License is distributed on an \"AS IS\" "
- "BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express o"
- "r implied.\n * Se";
-
-
-class AsyncIOTest : public ::testing::Test {
-protected:
- TemporaryFile dummy_file;
-
- AsyncIOTest() {}
- ~AsyncIOTest() {}
-};
-
-TEST_F(AsyncIOTest, testRead) {
- char buf[TEST_PACKET_SIZE + 1];
- buf[TEST_PACKET_SIZE] = '\0';
- EXPECT_EQ(write(dummy_file.fd, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- struct aiocb aio;
- struct aiocb *aiol[] = {&aio};
- aio.aio_fildes = dummy_file.fd;
- aio.aio_buf = buf;
- aio.aio_offset = 0;
- aio.aio_nbytes = TEST_PACKET_SIZE;
-
- EXPECT_EQ(aio_read(&aio), 0);
- EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
- EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
- EXPECT_STREQ(buf, dummyDataStr.c_str());
-}
-
-TEST_F(AsyncIOTest, testWrite) {
- char buf[TEST_PACKET_SIZE + 1];
- buf[TEST_PACKET_SIZE] = '\0';
- struct aiocb aio;
- struct aiocb *aiol[] = {&aio};
- aio.aio_fildes = dummy_file.fd;
- aio.aio_buf = const_cast<char*>(dummyDataStr.c_str());
- aio.aio_offset = 0;
- aio.aio_nbytes = TEST_PACKET_SIZE;
-
- EXPECT_EQ(aio_write(&aio), 0);
- EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
- EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
- EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- EXPECT_STREQ(buf, dummyDataStr.c_str());
-}
-
-TEST_F(AsyncIOTest, testError) {
- char buf[TEST_PACKET_SIZE + 1];
- buf[TEST_PACKET_SIZE] = '\0';
- struct aiocb aio;
- struct aiocb *aiol[] = {&aio};
- aio.aio_fildes = -1;
- aio.aio_buf = const_cast<char*>(dummyDataStr.c_str());
- aio.aio_offset = 0;
- aio.aio_nbytes = TEST_PACKET_SIZE;
-
- EXPECT_EQ(aio_write(&aio), 0);
- EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
- EXPECT_EQ(aio_return(&aio), -1);
- EXPECT_EQ(aio_error(&aio), EBADF);
-}
-
-TEST_F(AsyncIOTest, testSpliceRead) {
- char buf[TEST_PACKET_SIZE + 1];
- buf[TEST_PACKET_SIZE] = '\0';
- int pipeFd[2];
- EXPECT_EQ(pipe(pipeFd), 0);
- EXPECT_EQ(write(dummy_file.fd, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- struct aiocb aio;
- struct aiocb *aiol[] = {&aio};
- aio.aio_fildes = dummy_file.fd;
- aio.aio_sink = pipeFd[1];
- aio.aio_offset = 0;
- aio.aio_nbytes = TEST_PACKET_SIZE;
-
- EXPECT_EQ(aio_splice_read(&aio), 0);
- EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
- EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
-
- EXPECT_EQ(read(pipeFd[0], buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- EXPECT_STREQ(buf, dummyDataStr.c_str());
-}
-
-TEST_F(AsyncIOTest, testSpliceWrite) {
- char buf[TEST_PACKET_SIZE + 1];
- buf[TEST_PACKET_SIZE] = '\0';
- int pipeFd[2];
- EXPECT_EQ(pipe(pipeFd), 0);
- EXPECT_EQ(write(pipeFd[1], dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- struct aiocb aio;
- struct aiocb *aiol[] = {&aio};
- aio.aio_fildes = pipeFd[0];
- aio.aio_sink = dummy_file.fd;
- aio.aio_offset = 0;
- aio.aio_nbytes = TEST_PACKET_SIZE;
-
- EXPECT_EQ(aio_splice_write(&aio), 0);
- EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
- EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
- EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- EXPECT_STREQ(buf, dummyDataStr.c_str());
-}
-
-TEST_F(AsyncIOTest, testPoolWrite) {
- aio_pool_write_init();
- char buf[TEST_PACKET_SIZE * POOL_COUNT + 1];
- buf[TEST_PACKET_SIZE * POOL_COUNT] = '\0';
-
- for (int i = 0; i < POOL_COUNT; i++) {
- struct aiocb *aiop = new struct aiocb;
- aiop->aio_fildes = dummy_file.fd;
- aiop->aio_pool_buf = std::unique_ptr<char[]>(new char[TEST_PACKET_SIZE]);
- memcpy(aiop->aio_pool_buf.get(), dummyDataStr.c_str(), TEST_PACKET_SIZE);
- aiop->aio_offset = i * TEST_PACKET_SIZE;
- aiop->aio_nbytes = TEST_PACKET_SIZE;
- EXPECT_EQ(aio_pool_write(aiop), 0);
- }
- aio_pool_end();
- EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE * POOL_COUNT), TEST_PACKET_SIZE * POOL_COUNT);
-
- std::stringstream ss;
- for (int i = 0; i < POOL_COUNT; i++)
- ss << dummyDataStr;
-
- EXPECT_STREQ(buf, ss.str().c_str());
-}
-
-TEST_F(AsyncIOTest, testSplicePoolWrite) {
- aio_pool_splice_init();
- char buf[TEST_PACKET_SIZE * POOL_COUNT + 1];
- buf[TEST_PACKET_SIZE * POOL_COUNT] = '\0';
-
- for (int i = 0; i < POOL_COUNT; i++) {
- int pipeFd[2];
- EXPECT_EQ(pipe(pipeFd), 0);
- EXPECT_EQ(write(pipeFd[1], dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- struct aiocb *aiop = new struct aiocb;
- aiop->aio_fildes = pipeFd[0];
- aiop->aio_sink = dummy_file.fd;
- aiop->aio_offset = i * TEST_PACKET_SIZE;
- aiop->aio_nbytes = TEST_PACKET_SIZE;
- EXPECT_EQ(aio_pool_write(aiop), 0);
- }
- aio_pool_end();
- EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE * POOL_COUNT), TEST_PACKET_SIZE * POOL_COUNT);
-
- std::stringstream ss;
- for (int i = 0; i < POOL_COUNT; i++)
- ss << dummyDataStr;
-
- EXPECT_STREQ(buf, ss.str().c_str());
-}
-
-} // namespace android
diff --git a/media/mtp/tests/MtpFfsHandle_test.cpp b/media/mtp/tests/MtpFfsHandle_test.cpp
index 554f867..8d7301d 100644
--- a/media/mtp/tests/MtpFfsHandle_test.cpp
+++ b/media/mtp/tests/MtpFfsHandle_test.cpp
@@ -26,12 +26,11 @@
#include <utils/Log.h>
#include "MtpFfsHandle.h"
+#include "MtpFfsCompatHandle.h"
namespace android {
-constexpr int MAX_FILE_CHUNK_SIZE = 3 * 1024 * 1024;
-
-constexpr int TEST_PACKET_SIZE = 512;
+constexpr int TEST_PACKET_SIZE = 500;
constexpr int SMALL_MULT = 30;
constexpr int MED_MULT = 510;
@@ -43,17 +42,19 @@
"-2.0\n *\n * Unless required by applicable law or agreed to in writing, s"
"oftware\n * distributed under the License is distributed on an \"AS IS\" "
"BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express o"
- "r implied.\n * Se";
+ "r im";
/**
* Functional tests for the MtpFfsHandle class. Ensures header and data integrity
* by mocking ffs endpoints as pipes to capture input / output.
*/
+template <class T>
class MtpFfsHandleTest : public ::testing::Test {
protected:
- std::unique_ptr<IMtpHandle> handle;
+ std::unique_ptr<MtpFfsHandle> handle;
// Pipes for reading endpoint data
+ android::base::unique_fd control;
android::base::unique_fd bulk_in;
android::base::unique_fd bulk_out;
android::base::unique_fd intr;
@@ -62,88 +63,144 @@
MtpFfsHandleTest() {
int fd[2];
- handle = std::unique_ptr<IMtpHandle>(get_ffs_handle());
- MtpFfsHandle *ffs_handle = static_cast<MtpFfsHandle*>(handle.get());
- EXPECT_TRUE(ffs_handle != NULL);
+ handle = std::make_unique<T>();
+
+ EXPECT_EQ(pipe(fd), 0);
+ handle->mControl.reset(fd[0]);
+ control.reset(fd[1]);
EXPECT_EQ(pipe(fd), 0);
EXPECT_EQ(fcntl(fd[0], F_SETPIPE_SZ, 1048576), 1048576);
bulk_in.reset(fd[0]);
- ffs_handle->mBulkIn.reset(fd[1]);
+ handle->mBulkIn.reset(fd[1]);
EXPECT_EQ(pipe(fd), 0);
EXPECT_EQ(fcntl(fd[0], F_SETPIPE_SZ, 1048576), 1048576);
bulk_out.reset(fd[1]);
- ffs_handle->mBulkOut.reset(fd[0]);
+ handle->mBulkOut.reset(fd[0]);
EXPECT_EQ(pipe(fd), 0);
intr.reset(fd[0]);
- ffs_handle->mIntr.reset(fd[1]);
+ handle->mIntr.reset(fd[1]);
- ffs_handle->mBuffer1.resize(MAX_FILE_CHUNK_SIZE);
- ffs_handle->mBuffer2.resize(MAX_FILE_CHUNK_SIZE);
+ handle->start();
}
- ~MtpFfsHandleTest() {}
+ ~MtpFfsHandleTest() {
+ handle->close();
+ }
};
-TEST_F(MtpFfsHandleTest, testRead) {
- EXPECT_EQ(write(bulk_out, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+typedef ::testing::Types<MtpFfsHandle, MtpFfsCompatHandle> mtpHandles;
+TYPED_TEST_CASE(MtpFfsHandleTest, mtpHandles);
+
+TYPED_TEST(MtpFfsHandleTest, testRead) {
+ EXPECT_EQ(write(this->bulk_out, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
char buf[TEST_PACKET_SIZE + 1];
buf[TEST_PACKET_SIZE] = '\0';
- EXPECT_EQ(handle->read(buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+ EXPECT_EQ(this->handle->read(buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
EXPECT_STREQ(buf, dummyDataStr.c_str());
}
-TEST_F(MtpFfsHandleTest, testWrite) {
+TYPED_TEST(MtpFfsHandleTest, testWrite) {
char buf[TEST_PACKET_SIZE + 1];
buf[TEST_PACKET_SIZE] = '\0';
- EXPECT_EQ(handle->write(dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
- EXPECT_EQ(read(bulk_in, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+ EXPECT_EQ(this->handle->write(dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+ EXPECT_EQ(read(this->bulk_in, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
EXPECT_STREQ(buf, dummyDataStr.c_str());
}
-TEST_F(MtpFfsHandleTest, testReceiveFileSmall) {
+TYPED_TEST(MtpFfsHandleTest, testReceiveFileEmpty) {
+ std::stringstream ss;
+ mtp_file_range mfr;
+ int size = 0;
+ char buf[size + 1];
+ buf[size] = '\0';
+
+ mfr.offset = 0;
+ mfr.length = size;
+ mfr.fd = this->dummy_file.fd;
+
+ EXPECT_EQ(write(this->bulk_out, ss.str().c_str(), size), size);
+ EXPECT_EQ(this->handle->receiveFile(mfr, false), 0);
+
+ EXPECT_EQ(read(this->dummy_file.fd, buf, size), size);
+}
+
+TYPED_TEST(MtpFfsHandleTest, testReceiveFileSmall) {
std::stringstream ss;
mtp_file_range mfr;
int size = TEST_PACKET_SIZE * SMALL_MULT;
char buf[size + 1];
buf[size] = '\0';
+ mfr.offset = 0;
mfr.length = size;
- mfr.fd = dummy_file.fd;
+ mfr.fd = this->dummy_file.fd;
for (int i = 0; i < SMALL_MULT; i++)
ss << dummyDataStr;
- EXPECT_EQ(write(bulk_out, ss.str().c_str(), size), size);
- EXPECT_EQ(handle->receiveFile(mfr, false), 0);
+ EXPECT_EQ(write(this->bulk_out, ss.str().c_str(), size), size);
+ EXPECT_EQ(this->handle->receiveFile(mfr, false), 0);
- EXPECT_EQ(read(dummy_file.fd, buf, size), size);
+ EXPECT_EQ(read(this->dummy_file.fd, buf, size), size);
EXPECT_STREQ(buf, ss.str().c_str());
}
-TEST_F(MtpFfsHandleTest, testReceiveFileMed) {
+TYPED_TEST(MtpFfsHandleTest, testReceiveFileMed) {
std::stringstream ss;
mtp_file_range mfr;
int size = TEST_PACKET_SIZE * MED_MULT;
char buf[size + 1];
buf[size] = '\0';
+ mfr.offset = 0;
mfr.length = size;
- mfr.fd = dummy_file.fd;
+ mfr.fd = this->dummy_file.fd;
for (int i = 0; i < MED_MULT; i++)
ss << dummyDataStr;
- EXPECT_EQ(write(bulk_out, ss.str().c_str(), size), size);
- EXPECT_EQ(handle->receiveFile(mfr, false), 0);
+ EXPECT_EQ(write(this->bulk_out, ss.str().c_str(), size), size);
+ EXPECT_EQ(this->handle->receiveFile(mfr, false), 0);
- EXPECT_EQ(read(dummy_file.fd, buf, size), size);
+ EXPECT_EQ(read(this->dummy_file.fd, buf, size), size);
EXPECT_STREQ(buf, ss.str().c_str());
}
-TEST_F(MtpFfsHandleTest, testSendFileSmall) {
+TYPED_TEST(MtpFfsHandleTest, testReceiveFileMedPartial) {
+ std::stringstream ss;
+ mtp_file_range mfr;
+ int size = TEST_PACKET_SIZE * MED_MULT;
+ char buf[size + 1];
+ buf[size] = '\0';
+
+ mfr.fd = this->dummy_file.fd;
+ for (int i = 0; i < MED_MULT; i++)
+ ss << dummyDataStr;
+
+ EXPECT_EQ(write(this->bulk_out, ss.str().c_str(), size), size);
+
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_int_distribution<> dis(1, TEST_PACKET_SIZE);
+ int offset = 0;
+ while (offset != size) {
+ mfr.offset = offset;
+ int length = std::min(size - offset, dis(gen));
+ mfr.length = length;
+
+ EXPECT_EQ(this->handle->receiveFile(mfr, false), 0);
+ offset += length;
+ }
+
+ EXPECT_EQ(read(this->dummy_file.fd, buf, size), size);
+
+ EXPECT_STREQ(buf, ss.str().c_str());
+}
+
+TYPED_TEST(MtpFfsHandleTest, testSendFileSmall) {
std::stringstream ss;
mtp_file_range mfr;
mfr.command = 42;
@@ -154,14 +211,14 @@
buf[size + sizeof(mtp_data_header)] = '\0';
mfr.length = size;
- mfr.fd = dummy_file.fd;
+ mfr.fd = this->dummy_file.fd;
for (int i = 0; i < SMALL_MULT; i++)
ss << dummyDataStr;
- EXPECT_EQ(write(dummy_file.fd, ss.str().c_str(), size), size);
- EXPECT_EQ(handle->sendFile(mfr), 0);
+ EXPECT_EQ(write(this->dummy_file.fd, ss.str().c_str(), size), size);
+ EXPECT_EQ(this->handle->sendFile(mfr), 0);
- EXPECT_EQ(read(bulk_in, buf, size + sizeof(mtp_data_header)),
+ EXPECT_EQ(read(this->bulk_in, buf, size + sizeof(mtp_data_header)),
static_cast<long>(size + sizeof(mtp_data_header)));
struct mtp_data_header *header = reinterpret_cast<struct mtp_data_header*>(buf);
@@ -172,7 +229,7 @@
EXPECT_EQ(header->transaction_id, static_cast<unsigned int>(1337));
}
-TEST_F(MtpFfsHandleTest, testSendFileMed) {
+TYPED_TEST(MtpFfsHandleTest, testSendFileMed) {
std::stringstream ss;
mtp_file_range mfr;
mfr.command = 42;
@@ -183,14 +240,14 @@
buf[size + sizeof(mtp_data_header)] = '\0';
mfr.length = size;
- mfr.fd = dummy_file.fd;
+ mfr.fd = this->dummy_file.fd;
for (int i = 0; i < MED_MULT; i++)
ss << dummyDataStr;
- EXPECT_EQ(write(dummy_file.fd, ss.str().c_str(), size), size);
- EXPECT_EQ(handle->sendFile(mfr), 0);
+ EXPECT_EQ(write(this->dummy_file.fd, ss.str().c_str(), size), size);
+ EXPECT_EQ(this->handle->sendFile(mfr), 0);
- EXPECT_EQ(read(bulk_in, buf, size + sizeof(mtp_data_header)),
+ EXPECT_EQ(read(this->bulk_in, buf, size + sizeof(mtp_data_header)),
static_cast<long>(size + sizeof(mtp_data_header)));
struct mtp_data_header *header = reinterpret_cast<struct mtp_data_header*>(buf);
@@ -201,10 +258,10 @@
EXPECT_EQ(header->transaction_id, static_cast<unsigned int>(1337));
}
-TEST_F(MtpFfsHandleTest, testSendFileMedPartial) {
+TYPED_TEST(MtpFfsHandleTest, testSendFileMedPartial) {
std::stringstream ss;
mtp_file_range mfr;
- mfr.fd = dummy_file.fd;
+ mfr.fd = this->dummy_file.fd;
mfr.command = 42;
mfr.transaction_id = 1337;
int size = TEST_PACKET_SIZE * MED_MULT;
@@ -214,7 +271,7 @@
for (int i = 0; i < MED_MULT; i++)
ss << dummyDataStr;
- EXPECT_EQ(write(dummy_file.fd, ss.str().c_str(), size), size);
+ EXPECT_EQ(write(this->dummy_file.fd, ss.str().c_str(), size), size);
std::random_device rd;
std::mt19937 gen(rd());
@@ -225,9 +282,9 @@
int length = std::min(size - offset, dis(gen));
mfr.length = length;
char temp_buf[length + sizeof(mtp_data_header)];
- EXPECT_EQ(handle->sendFile(mfr), 0);
+ EXPECT_EQ(this->handle->sendFile(mfr), 0);
- EXPECT_EQ(read(bulk_in, temp_buf, length + sizeof(mtp_data_header)),
+ EXPECT_EQ(read(this->bulk_in, temp_buf, length + sizeof(mtp_data_header)),
static_cast<long>(length + sizeof(mtp_data_header)));
struct mtp_data_header *header = reinterpret_cast<struct mtp_data_header*>(temp_buf);
@@ -241,7 +298,7 @@
EXPECT_STREQ(buf, ss.str().c_str());
}
-TEST_F(MtpFfsHandleTest, testSendFileEmpty) {
+TYPED_TEST(MtpFfsHandleTest, testSendFileEmpty) {
mtp_file_range mfr;
mfr.command = 42;
mfr.transaction_id = 1337;
@@ -251,11 +308,11 @@
buf[size + sizeof(mtp_data_header)] = '\0';
mfr.length = size;
- mfr.fd = dummy_file.fd;
+ mfr.fd = this->dummy_file.fd;
- EXPECT_EQ(handle->sendFile(mfr), 0);
+ EXPECT_EQ(this->handle->sendFile(mfr), 0);
- EXPECT_EQ(read(bulk_in, buf, size + sizeof(mtp_data_header)),
+ EXPECT_EQ(read(this->bulk_in, buf, size + sizeof(mtp_data_header)),
static_cast<long>(size + sizeof(mtp_data_header)));
struct mtp_data_header *header = reinterpret_cast<struct mtp_data_header*>(buf);
@@ -265,15 +322,15 @@
EXPECT_EQ(header->transaction_id, static_cast<unsigned int>(1337));
}
-TEST_F(MtpFfsHandleTest, testSendEvent) {
+TYPED_TEST(MtpFfsHandleTest, testSendEvent) {
struct mtp_event event;
event.length = TEST_PACKET_SIZE;
event.data = const_cast<char*>(dummyDataStr.c_str());
char buf[TEST_PACKET_SIZE + 1];
buf[TEST_PACKET_SIZE] = '\0';
- handle->sendEvent(event);
- read(intr, buf, TEST_PACKET_SIZE);
+ this->handle->sendEvent(event);
+ read(this->intr, buf, TEST_PACKET_SIZE);
EXPECT_STREQ(buf, dummyDataStr.c_str());
}
diff --git a/media/mtp/tests/PosixAsyncIO_test.cpp b/media/mtp/tests/PosixAsyncIO_test.cpp
new file mode 100644
index 0000000..63b9a35
--- /dev/null
+++ b/media/mtp/tests/PosixAsyncIO_test.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "PosixAsyncIO_test.cpp"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <string>
+#include <unistd.h>
+#include <utils/Log.h>
+
+#include "PosixAsyncIO.h"
+
+namespace android {
+
+constexpr int TEST_PACKET_SIZE = 512;
+
+static const std::string dummyDataStr =
+ "/*\n * Copyright 2015 The Android Open Source Project\n *\n * Licensed un"
+ "der the Apache License, Version 2.0 (the \"License\");\n * you may not us"
+ "e this file except in compliance with the License.\n * You may obtain a c"
+ "opy of the License at\n *\n * http://www.apache.org/licenses/LICENSE"
+ "-2.0\n *\n * Unless required by applicable law or agreed to in writing, s"
+ "oftware\n * distributed under the License is distributed on an \"AS IS\" "
+ "BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express o"
+ "r implied.\n * Se";
+
+
+class PosixAsyncIOTest : public ::testing::Test {
+protected:
+ TemporaryFile dummy_file;
+
+ PosixAsyncIOTest() {}
+ ~PosixAsyncIOTest() {}
+};
+
+TEST_F(PosixAsyncIOTest, testRead) {
+ char buf[TEST_PACKET_SIZE + 1];
+ buf[TEST_PACKET_SIZE] = '\0';
+ EXPECT_EQ(write(dummy_file.fd, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+ struct aiocb aio;
+ struct aiocb *aiol[] = {&aio};
+ aio.aio_fildes = dummy_file.fd;
+ aio.aio_buf = buf;
+ aio.aio_offset = 0;
+ aio.aio_nbytes = TEST_PACKET_SIZE;
+
+ EXPECT_EQ(aio_read(&aio), 0);
+ EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+ EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
+ EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(PosixAsyncIOTest, testWrite) {
+ char buf[TEST_PACKET_SIZE + 1];
+ buf[TEST_PACKET_SIZE] = '\0';
+ struct aiocb aio;
+ struct aiocb *aiol[] = {&aio};
+ aio.aio_fildes = dummy_file.fd;
+ aio.aio_buf = const_cast<char*>(dummyDataStr.c_str());
+ aio.aio_offset = 0;
+ aio.aio_nbytes = TEST_PACKET_SIZE;
+
+ EXPECT_EQ(aio_write(&aio), 0);
+ EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+ EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
+ EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+ EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(PosixAsyncIOTest, testError) {
+ char buf[TEST_PACKET_SIZE + 1];
+ buf[TEST_PACKET_SIZE] = '\0';
+ struct aiocb aio;
+ struct aiocb *aiol[] = {&aio};
+ aio.aio_fildes = -1;
+ aio.aio_buf = const_cast<char*>(dummyDataStr.c_str());
+ aio.aio_offset = 0;
+ aio.aio_nbytes = TEST_PACKET_SIZE;
+
+ EXPECT_EQ(aio_write(&aio), 0);
+ EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+ EXPECT_EQ(aio_return(&aio), -1);
+ EXPECT_EQ(aio_error(&aio), EBADF);
+}
+
+} // namespace android