1 /*
2 ** Copyright 2022, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 **     http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16 
17 #pragma once
18 
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <unistd.h>
22 
23 #include <iomanip>
24 #include <limits>
25 #include <mutex>
26 #include <sstream>
27 #include <string>
28 #include <type_traits>
29 #include <unordered_map>
30 
31 #include <binder/MemoryBase.h>
32 #include <binder/MemoryHeapBase.h>
33 #include <log/log_main.h>
34 #include <utils/StrongPointer.h>
35 
36 namespace std {
37 template <typename T>
38 struct hash<::android::wp<T>> {
39     size_t operator()(const ::android::wp<T>& x) const {
40         return std::hash<const T*>()(x.unsafe_get());
41     }
42 };
43 }  // namespace std
44 
45 namespace android::mediautils {
46 
47 // Allocations represent owning handles to a region of shared memory (and thus
48 // should not be copied in order to fulfill RAII).
49 // To share ownership between multiple objects, a
50 // ref-counting solution such as sp or shared ptr is appropriate, so the dtor
51 // is called once for a particular block of memory.
52 
53 using AllocationType = ::android::sp<IMemory>;
54 using WeakAllocationType = ::android::wp<IMemory>;
55 
56 namespace shared_allocator_impl {
57 constexpr inline size_t roundup(size_t size, size_t pageSize) {
58     LOG_ALWAYS_FATAL_IF(pageSize == 0 || (pageSize & (pageSize - 1)) != 0,
59                         "Page size not multiple of 2");
60     return ((size + pageSize - 1) & ~(pageSize - 1));
61 }
62 
63 constexpr inline bool isHeapValid(const sp<IMemoryHeap>& heap) {
64     return (heap && heap->getBase() &&
65             heap->getBase() != MAP_FAILED);  // TODO if not mapped locally
66 }
67 
68 template <typename, typename = void>
69 static constexpr bool has_deallocate_all = false;
70 
71 template <typename T>
72 static constexpr bool has_deallocate_all<
73         T, std::enable_if_t<std::is_same_v<decltype(std::declval<T>().deallocate_all()), void>,
74                             void>> = true;
75 
76 template <typename, typename = void>
77 static constexpr bool has_owns = false;
78 
79 template <typename T>
80 static constexpr bool
81         has_owns<T, std::enable_if_t<std::is_same_v<decltype(std::declval<T>().owns(
82                                                             std::declval<const AllocationType>())),
83                                                     bool>,
84                                      void>> = true;
85 
86 template <typename, typename = void>
87 static constexpr bool has_dump = false;
88 
89 template <typename T>
90 static constexpr bool has_dump<
91         T,
92         std::enable_if_t<std::is_same_v<decltype(std::declval<T>().dump()), std::string>, void>> =
93         true;
94 
95 }  // namespace shared_allocator_impl
96 
97 struct BasicAllocRequest {
98     size_t size;
99 };
100 struct NamedAllocRequest : public BasicAllocRequest {
101     std::string_view name;
102 };
103 
104 // We are required to add a layer of indirection to hold a handle to the actual
105 // block due to sp<> being unable to be created from an object once its
106 // ref-count has dropped to zero. So, we have to hold onto an extra reference
107 // here. We effectively want to know when the refCount of the object drops to
108 // one, since we need to hold on to a reference to pass the object to interfaces
109 // requiring an sp<>.
110 // TODO is there some way to avoid paying this cost?
111 template <typename Allocator>
112 class ScopedAllocator;
113 
114 class ScopedAllocation : public BnMemory {
115   public:
116     template <typename T>
117     friend class ScopedAllocator;
118     template <typename Deallocator>
119     ScopedAllocation(const AllocationType& allocation, Deallocator&& deallocator)
120         : mAllocation(allocation), mDeallocator(std::forward<Deallocator>(deallocator)) {}
121 
122     // Defer the implementation to the underlying mAllocation
123 
124     virtual sp<IMemoryHeap> getMemory(ssize_t* offset = nullptr,
125                                       size_t* size = nullptr) const override {
126         return mAllocation->getMemory(offset, size);
127     }
128 
129   private:
130     ~ScopedAllocation() override { mDeallocator(mAllocation); }
131 
132     const AllocationType mAllocation;
133     const std::function<void(const AllocationType&)> mDeallocator;
134 };
135 
136 // Allocations are only deallocated when going out of scope.
137 // This should almost always be the outermost allocator.
138 template <typename Allocator>
139 class ScopedAllocator {
140   public:
141     static size_t alignment() { return Allocator::alignment(); }
142 
143     explicit ScopedAllocator(const std::shared_ptr<Allocator>& allocator) : mAllocator(allocator) {}
144 
145     ScopedAllocator() : mAllocator(std::make_shared<Allocator>()) {}
146 
147     template <typename T>
148     auto allocate(T&& request) {
149         std::lock_guard l{*mLock};
150         const auto allocation = mAllocator->allocate(std::forward<T>(request));
151         if (!allocation) {
152             return sp<ScopedAllocation>{};
153         }
154         return sp<ScopedAllocation>::make(allocation,
155                 [allocator = mAllocator, lock = mLock] (const AllocationType& allocation) {
156                     std::lock_guard l{*lock};
157                     allocator->deallocate(allocation);
158                 });
159     }
160 
161     // Deallocate and deallocate_all are implicitly unsafe due to double
162     // deallocates upon ScopedAllocation destruction. We can protect against this
163     // efficiently with a gencount (for deallocate_all) or inefficiently (for
164     // deallocate) but we choose not to
165     //
166     // Owns is only safe to pseudo-impl due to static cast reqs
167     template <typename Enable = bool>
168     auto owns(const sp<ScopedAllocation>& allocation) const
169             -> std::enable_if_t<shared_allocator_impl::has_owns<Allocator>, Enable> {
170         std::lock_guard l{*mLock};
171         return mAllocator->owns(allocation->mAllocation);
172     }
173 
174     template <typename Enable = std::string>
175     auto dump() const -> std::enable_if_t<shared_allocator_impl::has_dump<Allocator>, Enable> {
176         std::lock_guard l{*mLock};
177         return mAllocator->dump();
178     }
179 
180   private:
181     // We store a shared pointer in order to ensure that the allocator outlives
182     // allocations (which call back to become dereferenced).
183     const std::shared_ptr<Allocator> mAllocator;
184     const std::shared_ptr<std::mutex> mLock = std::make_shared<std::mutex>();
185 };
186 
187 // A simple policy for PolicyAllocator which enforces a pool size and an allocation
188 // size range.
189 template <size_t PoolSize, size_t MinAllocSize = 0,
190           size_t MaxAllocSize = std::numeric_limits<size_t>::max()>
191 class SizePolicy {
192     static_assert(PoolSize > 0);
193 
194   public:
195     template <typename T>
196     bool isValid(T&& request) const {
197         static_assert(std::is_base_of_v<BasicAllocRequest, std::decay_t<T>>);
198         return !(request.size > kMaxAllocSize || request.size < kMinAllocSize ||
199                  mPoolSize + request.size > kPoolSize);
200     }
201 
202     void allocated(const AllocationType& alloc) { mPoolSize += alloc->size(); }
203 
204     void deallocated(const AllocationType& alloc) { mPoolSize -= alloc->size(); }
205 
206     void deallocated_all() { mPoolSize = 0; }
207 
208     static constexpr size_t kPoolSize = PoolSize;
209     static constexpr size_t kMinAllocSize = MinAllocSize;
210     static constexpr size_t kMaxAllocSize = MaxAllocSize;
211 
212   private:
213     size_t mPoolSize = 0;
214 };
215 
216 // An allocator which accepts or rejects allocation requests by a parametrized
217 // policy (which can carry state).
218 template <typename Allocator, typename Policy>
219 class PolicyAllocator {
220   public:
221     static size_t alignment() { return Allocator::alignment(); }
222 
223     PolicyAllocator(Allocator allocator, Policy policy)
224         : mAllocator(allocator), mPolicy(std::move(policy)) {}
225 
226     // Default initialize the allocator and policy
227     PolicyAllocator() = default;
228 
229     template <typename T>
230     AllocationType allocate(T&& request) {
231         static_assert(std::is_base_of_v<android::mediautils::BasicAllocRequest, std::decay_t<T>>);
232         request.size = shared_allocator_impl::roundup(request.size, alignment());
233         if (!mPolicy.isValid(request)) {
234             return {};
235         }
236         AllocationType val = mAllocator.allocate(std::forward<T>(request));
237         if (val == nullptr) return val;
238         mPolicy.allocated(val);
239         return val;
240     }
241 
242     void deallocate(const AllocationType& allocation) {
243         if (!allocation) return;
244         mPolicy.deallocated(allocation);
245         mAllocator.deallocate(allocation);
246     }
247 
248     template <typename Enable = void>
249     auto deallocate_all()
250             -> std::enable_if_t<shared_allocator_impl::has_deallocate_all<Allocator>, Enable> {
251         mAllocator.deallocate_all();
252         mPolicy.deallocated_all();
253     }
254 
255     template <typename Enable = bool>
256     auto owns(const AllocationType& allocation) const
257             -> std::enable_if_t<shared_allocator_impl::has_owns<Allocator>, Enable> {
258         return mAllocator.owns(allocation);
259     }
260 
261     template <typename Enable = std::string>
262     auto dump() const -> std::enable_if_t<shared_allocator_impl::has_dump<Allocator>, Enable> {
263         return mAllocator.dump();
264     }
265 
266   private:
267     [[no_unique_address]] Allocator mAllocator;
268     [[no_unique_address]] Policy mPolicy;
269 };
270 
271 // An allocator which keeps track of outstanding allocations for logging and
272 // querying ownership.
273 template <class Allocator>
274 class SnoopingAllocator {
275   public:
276     struct AllocationData {
277         std::string name;
278         size_t allocation_number;
279     };
280     static size_t alignment() { return Allocator::alignment(); }
281 
282     SnoopingAllocator(Allocator allocator, std::string_view name)
283         : mName(name), mAllocator(std::move(allocator)) {}
284 
285     explicit SnoopingAllocator(std::string_view name) : mName(name), mAllocator(Allocator{}) {}
286 
287     explicit SnoopingAllocator(Allocator allocator) : mAllocator(std::move(allocator)) {}
288 
289     // Default construct allocator and name
290     SnoopingAllocator() = default;
291 
292     template <typename T>
293     AllocationType allocate(T&& request) {
294         static_assert(std::is_base_of_v<NamedAllocRequest, std::decay_t<T>>);
295         AllocationType allocation = mAllocator.allocate(request);
296         if (allocation)
297             mAllocations.insert({WeakAllocationType{allocation},
298                                  {std::string{request.name}, mAllocationNumber++}});
299         return allocation;
300     }
301 
302     void deallocate(const AllocationType& allocation) {
303         if (!allocation) return;
304         mAllocations.erase(WeakAllocationType{allocation});
305         mAllocator.deallocate(allocation);
306     }
307 
308     void deallocate_all() {
309         if constexpr (shared_allocator_impl::has_deallocate_all<Allocator>) {
310             mAllocator.deallocate_all();
311         } else {
312             for (auto& [mem, value] : mAllocations) {
313                 mAllocator.deallocate(mem);
314             }
315         }
316         mAllocations.clear();
317     }
318 
319     bool owns(const AllocationType& allocation) const {
320         return (mAllocations.count(WeakAllocationType{allocation}) > 0);
321     }
322 
323     std::string dump() const {
324         std::ostringstream dump;
325         dump << mName << " Allocator Dump:\n";
326         dump << std::setw(8) << "HeapID" << std::setw(8) << "Size" << std::setw(8) << "Offset"
327              << std::setw(8) << "Order"
328              << "   Name\n";
329         for (auto& [mem, value] : mAllocations) {
330             // TODO Imem size and offset
331             const AllocationType handle = mem.promote();
332             if (!handle) {
333                 dump << "Invalid memory lifetime!";
334                 continue;
335             }
336             const auto heap = handle->getMemory();
337             dump << std::setw(8) << heap->getHeapID() << std::setw(8) << heap->getSize()
338                  << std::setw(8) << heap->getOffset() << std::setw(8) << value.allocation_number
339                  << "   " << value.name << "\n";
340         }
341         return dump.str();
342     }
343 
344     const std::unordered_map<WeakAllocationType, AllocationData>& getAllocations() {
345         return mAllocations;
346     }
347 
348   private:
349     const std::string mName;
350     [[no_unique_address]] Allocator mAllocator;
351     // We don't take copies of the underlying information in an allocation,
352     // rather, the allocation information is put on the heap and referenced via
353     // a ref-counted solution. So, the address of the allocation information is
354     // appropriate to hash. In order for this block to be freed, the underlying
355     // allocation must be referenced by no one (thus deallocated).
356     std::unordered_map<WeakAllocationType, AllocationData> mAllocations;
357     // For debugging purposes, monotonic
358     size_t mAllocationNumber = 0;
359 };
360 
361 // An allocator which passes a failed allocation request to a backup allocator.
362 template <class PrimaryAllocator, class SecondaryAllocator>
363 class FallbackAllocator {
364   public:
365     static_assert(shared_allocator_impl::has_owns<PrimaryAllocator>);
366 
367     static size_t alignment() { return PrimaryAllocator::alignment(); }
368 
369     FallbackAllocator(const PrimaryAllocator& primary, const SecondaryAllocator& secondary)
370         : mPrimary(primary), mSecondary(secondary) {
371       verify_alignment();
372     }
373 
374     // Default construct primary and secondary allocator
375     FallbackAllocator() {
376       verify_alignment();
377     }
378 
379     template <typename T>
380     AllocationType allocate(T&& request) {
381         AllocationType allocation = mPrimary.allocate(std::forward<T>(request));
382         if (!allocation) allocation = mSecondary.allocate(std::forward<T>(request));
383         return allocation;
384     }
385 
386     void deallocate(const AllocationType& allocation) {
387         if (!allocation) return;
388         if (mPrimary.owns(allocation)) {
389             mPrimary.deallocate(allocation);
390         } else {
391             mSecondary.deallocate(allocation);
392         }
393     }
394 
395     template <typename Enable = void>
396     auto deallocate_all() -> std::enable_if_t<
397             shared_allocator_impl::has_deallocate_all<PrimaryAllocator> &&
398                     shared_allocator_impl::has_deallocate_all<SecondaryAllocator>,
399             Enable> {
400         mPrimary.deallocate_all();
401         mSecondary.deallocate_all();
402     }
403 
404     template <typename Enable = bool>
405     auto owns(const AllocationType& allocation) const
406             -> std::enable_if_t<shared_allocator_impl::has_owns<SecondaryAllocator>, Enable> {
407         return mPrimary.owns(allocation) || mSecondary.owns(allocation);
408     }
409 
410     template <typename Enable = std::string>
411     auto dump() const
412             -> std::enable_if_t<shared_allocator_impl::has_dump<PrimaryAllocator> &&
413                                         shared_allocator_impl::has_dump<SecondaryAllocator>,
414                                 Enable> {
415         return std::string("Primary: \n") + mPrimary.dump() + std::string("Secondary: \n") +
416                mSecondary.dump();
417     }
418 
419   private:
420     void verify_alignment() {
421       LOG_ALWAYS_FATAL_IF(PrimaryAllocator::alignment() != SecondaryAllocator::alignment(),
422                           "PrimaryAllocator::alignment() != SecondaryAllocator::alignment()");
423     }
424     [[no_unique_address]] PrimaryAllocator mPrimary;
425     [[no_unique_address]] SecondaryAllocator mSecondary;
426 };
427 
428 // An allocator which is backed by a shared_ptr to an allocator, so multiple
429 // allocators can share the same backing allocator (and thus the same state).
430 template <typename Allocator>
431 class IndirectAllocator {
432   public:
433     static size_t alignment() { return Allocator::alignment(); }
434 
435     explicit IndirectAllocator(const std::shared_ptr<Allocator>& allocator)
436         : mAllocator(allocator) {}
437 
438     template <typename T>
439     AllocationType allocate(T&& request) {
440         return mAllocator->allocate(std::forward<T>(request));
441     }
442 
443     void deallocate(const AllocationType& allocation) {
444         if (!allocation) return;
445         mAllocator->deallocate(allocation);
446     }
447 
448     // We can't implement deallocate_all/dump/owns, since we may not be the only allocator with
449     // access to the underlying allocator (making it not well-defined). If these
450     // methods are necesesary, we need to wrap with a snooping allocator.
451   private:
452     const std::shared_ptr<Allocator> mAllocator;
453 };
454 
455 // Stateless. This allocator allocates full page-aligned MemoryHeapBases (backed by
456 // a shared memory mapped anonymous file) as allocations.
457 class MemoryHeapBaseAllocator {
458   public:
459     static size_t alignment() { return kPageSize; }
460     static constexpr unsigned FLAGS = 0;  // default flags
461 
462     template <typename T>
463     AllocationType allocate(T&& request) {
464         static_assert(std::is_base_of_v<BasicAllocRequest, std::decay_t<T>>);
465         auto heap =
466                 sp<MemoryHeapBase>::make(shared_allocator_impl::roundup(request.size, alignment()));
467         if (!shared_allocator_impl::isHeapValid(heap)) {
468             return {};
469         }
470         return sp<MemoryBase>::make(heap, 0, heap->getSize());
471     }
472 
473     // Passing a block not allocated by a HeapAllocator is undefined.
474     void deallocate(const AllocationType& allocation) {
475         if (!allocation) return;
476         const auto heap = allocation->getMemory();
477         if (!heap) return;
478         // This causes future mapped accesses (even across process boundaries)
479         // to receive SIGBUS.
480         ftruncate(heap->getHeapID(), 0);
481         // This static cast is safe, since as long as the block was originally
482         // allocated by us, the underlying IMemoryHeap was a MemoryHeapBase
483         static_cast<MemoryHeapBase&>(*heap).dispose();
484     }
485   private:
486     static inline const size_t kPageSize = getpagesize();
487 };
488 }  // namespace android::mediautils
489