1 /*
2     This file is part of Android File Transfer For Linux.
3     Copyright (C) 2015-2020  Vladimir Menshakov
4 
5     This library is free software; you can redistribute it and/or modify it
6     under the terms of the GNU Lesser General Public License as published by
7     the Free Software Foundation; either version 2.1 of the License,
8     or (at your option) any later version.
9 
10     This library is distributed in the hope that it will be useful, but
11     WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13     Lesser General Public License for more details.
14 
15     You should have received a copy of the GNU Lesser General Public License
16     along with this library; if not, write to the Free Software Foundation,
17     Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 
20 #ifndef AFTL_MTP_BACKEND_LINUX_USB_BUFFERALLOCATOR_H
21 #define AFTL_MTP_BACKEND_LINUX_USB_BUFFERALLOCATOR_H
22 
23 #include <mtp/ByteArray.h>
24 #include <mtp/types.h>
25 #include <mtp/log.h>
26 #include <Exception.h>
27 #include <array>
28 
29 #include <sys/mman.h>
30 #include <unistd.h>
31 
32 namespace mtp { namespace usb
33 {
34 	class BufferAllocator;
35 	class Buffer
36 	{
37 		u8 *				_data;
38 		size_t				_size;
39 
40 	public:
Buffer(u8 * data,size_t size)41 		Buffer(u8 *data, size_t size): _data(data), _size(size)
42 		{ }
43 
GetData()44 		u8 * GetData() const
45 		{ return _data; }
GetSize()46 		size_t GetSize() const
47 		{ return _size; }
48 	};
49 
50 	class BufferAllocator : Noncopyable
51 	{
52 		static constexpr size_t Buffers		= 16; //for parallel endpoint access
53 		static constexpr size_t BufferSize	= 64 * 1024;
54 
55 		std::mutex	_mutex;
56 		int			_fd;
57 		long		_pageSize;
58 		u8 *		_buffer;
59 		size_t		_bufferSize;
60 		ByteArray	_normalBuffer;
61 
62 		std::array<bool, Buffers> _bufferAllocated;
63 
AllocateNormalBuffer()64 		void AllocateNormalBuffer()
65 		{
66 			_fd = -1;
67 			_normalBuffer.resize(Buffers * BufferSize);
68 			_buffer = _normalBuffer.data();
69 			_bufferSize = _normalBuffer.size();
70 		}
71 
72 	public:
BufferAllocator(int fd)73 		BufferAllocator(int fd): _fd(fd), _pageSize(sysconf(_SC_PAGESIZE)), _buffer(nullptr), _bufferSize(0), _bufferAllocated()
74 		{
75 			if (_pageSize <= 0)
76 				throw posix::Exception("sysconf(_SC_PAGESIZE)");
77 			debug("page size = ", _pageSize);
78 		}
79 
~BufferAllocator()80 		~BufferAllocator()
81 		{
82 			if (_fd >= 0)
83 				munmap(_buffer, _bufferSize);
84 		}
85 
Free(Buffer & buffer)86 		void Free(Buffer &buffer)
87 		{
88 			scoped_mutex_lock l(_mutex);
89 			size_t index = (buffer.GetData() - _buffer) / BufferSize;
90 			_bufferAllocated.at(index) = false;
91 		}
92 
Allocate(size_t size)93 		Buffer Allocate(size_t size)
94 		{
95 			scoped_mutex_lock l(_mutex);
96 			if (!_buffer)
97 			{
98 				_bufferSize = (BufferSize + _pageSize - 1) / _pageSize * _pageSize;
99 				if (_fd >= 0)
100 				{
101 					try
102 					{
103 						auto buffer = static_cast<u8 *>(mmap(nullptr, _bufferSize * Buffers, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0));
104 						if (buffer == MAP_FAILED)
105 							throw posix::Exception("mmap failed");
106 
107 						_buffer = buffer;
108 						debug("mapped buffer of ", _bufferSize * Buffers, " bytes to ", static_cast<void *>(_buffer));
109 					}
110 					catch(const std::exception &ex)
111 					{
112 						error("zerocopy allocator failed: ", ex.what());
113 						AllocateNormalBuffer();
114 					}
115 				}
116 				else
117 					AllocateNormalBuffer();
118 			}
119 			if (size > BufferSize)
120 				size = BufferSize;
121 
122 			for(size_t i = 0; i < _bufferAllocated.size(); ++i)
123 			{
124 				if (!_bufferAllocated[i])
125 				{
126 					_bufferAllocated[i] = true;
127 					return Buffer(_buffer + BufferSize * i, size);
128 				}
129 			}
130 			throw std::runtime_error("BufferAllocator::Allocate: out of mapped memory");
131 		}
132 	};
133 
134 }}
135 
136 #endif
137