From af32bb9c4f4f06e92de3435ed2db3153c0701094 Mon Sep 17 00:00:00 2001 From: bigbiff bigbiff Date: Tue, 18 Dec 2018 18:39:53 -0500 Subject: MTP FFS updates: This update splits old MTP code and new MTP code from Google into two trees, legacy and ffs. Depending on the SDK level, the build system will select the correct version. The reason for separating the versions out are due to older android trees not supporting the updated MTP code from Google. Most MTP code is from Google, with additions needed from implementing the Java functions in C++ for TWRP and FFS. We assume if you are in android-9.0 or above, your kernel has support for FFS over MTP. Verify that your init.rc is mounting the MTP FFS driver to the proper location. Change-Id: I4b107b239bd9bc5699527f9c8c77d9079f264a7e --- mtp/ffs/MtpFfsCompatHandle.cpp | 338 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 mtp/ffs/MtpFfsCompatHandle.cpp (limited to 'mtp/ffs/MtpFfsCompatHandle.cpp') diff --git a/mtp/ffs/MtpFfsCompatHandle.cpp b/mtp/ffs/MtpFfsCompatHandle.cpp new file mode 100644 index 000000000..4027d601c --- /dev/null +++ b/mtp/ffs/MtpFfsCompatHandle.cpp @@ -0,0 +1,338 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + + +MtpFfsCompatHandle::MtpFfsCompatHandle(int controlFd) : + MtpFfsHandle(controlFd), + 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(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(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(bool ptp) { + if (!openEndpoints(ptp)) + 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(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(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(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(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(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(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(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(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(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(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; +} + -- cgit v1.2.3