1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#pragma once
6
7#include <stdbool.h>
8#include <stdio.h>
9#include <stdlib.h>
10
11#include <ddk/device.h>
12#include <fvm/fvm.h>
13#include <zircon/device/block.h>
14#include <zircon/thread_annotations.h>
15#include <zircon/types.h>
16
17#ifdef __cplusplus
18
19#include <ddktl/device.h>
20#include <ddktl/protocol/block.h>
21#include <lib/fzl/mapped-vmo.h>
22#include <fbl/algorithm.h>
23#include <fbl/intrusive_wavl_tree.h>
24#include <fbl/mutex.h>
25#include <fbl/unique_ptr.h>
26#include <fbl/vector.h>
27
28namespace fvm {
29
30class SliceExtent : public fbl::WAVLTreeContainable<fbl::unique_ptr<SliceExtent>> {
31public:
32    size_t GetKey() const { return vslice_start_; }
33    // Vslice start (inclusive)
34    size_t start() const { return vslice_start_; }
35    // Vslice end (exclusive)
36    size_t end() const { return vslice_start_ + pslices_.size(); }
37    // Extent length
38    size_t size() const { return end() - start(); }
39    // Look up a pslice given a vslice
40    uint32_t get(size_t vslice) const {
41        if (vslice - vslice_start_ >= pslices_.size()) {
42            return 0;
43        }
44        return pslices_[vslice - vslice_start_];
45    }
46
47    // Breaks the extent from:
48    //   [start(), end())
49    // Into:
50    //   [start(), vslice] and [vslice + 1, end()).
51    // Returns the latter extent on success; returns nullptr
52    // if a memory allocation failure occurs.
53    fbl::unique_ptr<SliceExtent> Split(size_t vslice);
54
55    // Combines the other extent into this one.
56    // 'other' must immediately follow the current slice.
57    bool Merge(const SliceExtent& other);
58
59    bool push_back(uint32_t pslice) {
60        ZX_DEBUG_ASSERT(pslice != PSLICE_UNALLOCATED);
61        fbl::AllocChecker ac;
62        pslices_.push_back(pslice, &ac);
63        return ac.check();
64    }
65    void pop_back() { pslices_.pop_back(); }
66    bool is_empty() const { return pslices_.size() == 0; }
67
68    SliceExtent(size_t vslice_start)
69        : vslice_start_(vslice_start) {}
70
71private:
72    friend class TypeWAVLTraits;
73    DISALLOW_COPY_ASSIGN_AND_MOVE(SliceExtent);
74
75    fbl::Vector<uint32_t> pslices_;
76    const size_t vslice_start_;
77};
78
79class VPartitionManager;
80using ManagerDeviceType = ddk::Device<VPartitionManager, ddk::Ioctlable, ddk::Unbindable>;
81
82class VPartition;
83using PartitionDeviceType = ddk::Device<VPartition,
84                                        ddk::Ioctlable,
85                                        ddk::GetSizable,
86                                        ddk::Unbindable>;
87
88class VPartitionManager : public ManagerDeviceType {
89public:
90    DISALLOW_COPY_ASSIGN_AND_MOVE(VPartitionManager);
91    static zx_status_t Bind(zx_device_t* dev);
92
93    // Read the underlying block device, initialize the recorded VPartitions.
94    zx_status_t Load();
95
96    // Block Protocol
97    size_t BlockOpSize() const { return block_op_size_; }
98    void Queue(block_op_t* txn) const { bp_.ops->queue(bp_.ctx, txn); }
99
100    // Queue a BLOCK_OP_FLUSH request and wait for it to complete
101    zx_status_t Flush() const;
102
103    // Acquire access to a VPart Entry which has already been modified (and
104    // will, as a consequence, not be de-allocated underneath us).
105    vpart_entry_t* GetAllocatedVPartEntry(size_t index) const TA_NO_THREAD_SAFETY_ANALYSIS {
106        auto entry = GetVPartEntryLocked(index);
107        ZX_DEBUG_ASSERT(entry->slices > 0);
108        return entry;
109    }
110
111    // Allocate 'count' slices, write back the FVM.
112    zx_status_t AllocateSlices(VPartition* vp, size_t vslice_start, size_t count) TA_EXCL(lock_);
113
114    // Deallocate 'count' slices, write back the FVM.
115    // If a request is made to remove vslice_count = 0, deallocates the entire
116    // VPartition.
117    zx_status_t FreeSlices(VPartition* vp, size_t vslice_start, size_t count) TA_EXCL(lock_);
118
119    // Returns global information about the FVM.
120    void Query(fvm_info_t* info) TA_EXCL(lock_);
121
122    size_t DiskSize() const { return info_.block_count * info_.block_size; }
123    size_t SliceSize() const { return slice_size_; }
124    size_t VSliceMax() const { return VSLICE_MAX; }
125    const block_info_t& Info() const { return info_; }
126
127    zx_status_t DdkIoctl(uint32_t op, const void* cmd, size_t cmdlen,
128                         void* reply, size_t max, size_t* out_actual);
129    void DdkUnbind();
130    void DdkRelease();
131
132    VPartitionManager(zx_device_t* dev, const block_info_t& info, size_t block_op_size,
133                      const block_protocol_t* bp);
134    ~VPartitionManager();
135
136private:
137    // Marks the partition with instance GUID |old_guid| as inactive,
138    // and marks partitions with instance GUID |new_guid| as active.
139    //
140    // If a partition with |old_guid| does not exist, it is ignored.
141    // If |old_guid| equals |new_guid|, then |old_guid| is ignored.
142    // If a partition with |new_guid| does not exist, |ZX_ERR_NOT_FOUND|
143    // is returned.
144    //
145    // Updates the FVM metadata atomically.
146    zx_status_t Upgrade(const uint8_t* old_guid, const uint8_t* new_guid) TA_EXCL(lock_);
147
148    // Given a VPartition object, add a corresponding ddk device.
149    zx_status_t AddPartition(fbl::unique_ptr<VPartition> vp) const;
150
151    // Update, hash, and write back the current copy of the FVM metadata.
152    // Automatically handles alternating writes to primary / backup copy of FVM.
153    zx_status_t WriteFvmLocked() TA_REQ(lock_);
154
155    zx_status_t AllocateSlicesLocked(VPartition* vp, size_t vslice_start,
156                                     size_t count) TA_REQ(lock_);
157
158    zx_status_t FreeSlicesLocked(VPartition* vp, size_t vslice_start,
159                                 size_t count) TA_REQ(lock_);
160
161    zx_status_t FindFreeVPartEntryLocked(size_t* out) const TA_REQ(lock_);
162    zx_status_t FindFreeSliceLocked(size_t* out, size_t hint) const TA_REQ(lock_);
163
164    fvm_t* GetFvmLocked() const TA_REQ(lock_) {
165        return reinterpret_cast<fvm_t*>(metadata_->GetData());
166    }
167
168    // Mark a slice as free in the metadata structure.
169    // Update free slice accounting.
170    void FreePhysicalSlice(size_t pslice) TA_REQ(lock_);
171
172    // Mark a slice as allocated in the metadata structure.
173    // Update allocated slice accounting.
174    void AllocatePhysicalSlice(size_t pslice, uint64_t vpart, uint64_t vslice) TA_REQ(lock_);
175
176    // Given a physical slice (acting as an index into the slice table),
177    // return the associated slice entry.
178    slice_entry_t* GetSliceEntryLocked(size_t index) const TA_REQ(lock_);
179
180    // Given an index into the vpartition table, return the associated
181    // virtual partition entry.
182    vpart_entry_t* GetVPartEntryLocked(size_t index) const TA_REQ(lock_);
183
184    size_t PrimaryOffsetLocked() const TA_REQ(lock_) {
185        return first_metadata_is_primary_ ? 0 : MetadataSize();
186    }
187
188    size_t BackupOffsetLocked() const TA_REQ(lock_) {
189        return first_metadata_is_primary_ ? MetadataSize() : 0;
190    }
191
192    size_t MetadataSize() const {
193        return metadata_size_;
194    }
195
196    zx_status_t DoIoLocked(zx_handle_t vmo, size_t off, size_t len, uint32_t command);
197
198    thrd_t initialization_thread_;
199    block_info_t info_; // Cached info from parent device
200
201    fbl::Mutex lock_;
202    fbl::unique_ptr<fzl::MappedVmo> metadata_ TA_GUARDED(lock_);
203    bool first_metadata_is_primary_ TA_GUARDED(lock_);
204    size_t metadata_size_;
205    size_t slice_size_;
206    // Number of allocatable slices.
207    size_t pslice_total_count_;
208    // Number of currently allocated slices.
209    size_t pslice_allocated_count_ TA_GUARDED(lock_);
210
211    // Block Protocol
212    const size_t block_op_size_;
213    block_protocol_t bp_;
214};
215
216class VPartition : public PartitionDeviceType, public ddk::BlockProtocol<VPartition> {
217public:
218    static zx_status_t Create(VPartitionManager* vpm, size_t entry_index,
219                              fbl::unique_ptr<VPartition>* out);
220    // Device Protocol
221    zx_status_t DdkIoctl(uint32_t op, const void* cmd, size_t cmdlen,
222                         void* reply, size_t max, size_t* out_actual);
223    zx_off_t DdkGetSize();
224    void DdkUnbind();
225    void DdkRelease();
226
227    // Block Protocol
228    void BlockQuery(block_info_t* info_out, size_t* block_op_size_out);
229    void BlockQueue(block_op_t* txn);
230
231    auto ExtentBegin() TA_REQ(lock_) {
232        return slice_map_.begin();
233    }
234
235    // Given a virtual slice, return the physical slice allocated
236    // to it. If no slice is allocated, return PSLICE_UNALLOCATED.
237    uint32_t SliceGetLocked(size_t vslice) const TA_REQ(lock_);
238
239    // Check slices starting from |vslice_start|.
240    // Sets |*count| to the number of contiguous allocated or unallocated slices found.
241    // Sets |*allocated| to true if the vslice range is allocated, and false otherwise.
242    zx_status_t CheckSlices(size_t vslice_start, size_t* count, bool* allocated) TA_EXCL(lock_);
243
244    zx_status_t SliceSetUnsafe(size_t vslice, uint32_t pslice) TA_NO_THREAD_SAFETY_ANALYSIS {
245        return SliceSetLocked(vslice, pslice);
246    }
247    zx_status_t SliceSetLocked(size_t vslice, uint32_t pslice) TA_REQ(lock_);
248
249    bool SliceCanFree(size_t vslice) const TA_REQ(lock_) {
250        auto extent = --slice_map_.upper_bound(vslice);
251        return extent.IsValid() && extent->get(vslice) != PSLICE_UNALLOCATED;
252    }
253
254    // Returns "true" if slice freed successfully, false otherwise.
255    // If freeing from the back of an extent, guaranteed not to fail.
256    bool SliceFreeLocked(size_t vslice) TA_REQ(lock_);
257
258    // Destroy the extent containing the vslice.
259    void ExtentDestroyLocked(size_t vslice) TA_REQ(lock_);
260
261    size_t BlockSize() const TA_NO_THREAD_SAFETY_ANALYSIS {
262        return info_.block_size;
263    }
264    void AddBlocksLocked(ssize_t nblocks) TA_REQ(lock_) {
265        info_.block_count += nblocks;
266    }
267
268    size_t GetEntryIndex() const { return entry_index_; }
269
270    void KillLocked() TA_REQ(lock_) { entry_index_ = 0; }
271    bool IsKilledLocked() TA_REQ(lock_) { return entry_index_ == 0; }
272
273    VPartition(VPartitionManager* vpm, size_t entry_index, size_t block_op_size);
274    ~VPartition();
275    fbl::Mutex lock_;
276
277private:
278    DISALLOW_COPY_ASSIGN_AND_MOVE(VPartition);
279
280    VPartitionManager* mgr_;
281    size_t entry_index_;
282
283    // Mapping of virtual slice number (index) to physical slice number (value).
284    // Physical slice zero is reserved to mean "unmapped", so a zeroed slice_map
285    // indicates that the vpartition is completely unmapped, and uses no
286    // physical slices.
287    fbl::WAVLTree<size_t, fbl::unique_ptr<SliceExtent>> slice_map_ TA_GUARDED(lock_);
288    block_info_t info_ TA_GUARDED(lock_);
289};
290
291} // namespace fvm
292
293#endif // ifdef __cplusplus
294
295__BEGIN_CDECLS
296
297/////////////////// C-compatibility definitions (Provided to C from C++)
298
299// Binds FVM driver to a device; loads the VPartition devices asynchronously in
300// a background thread.
301zx_status_t fvm_bind(zx_device_t* dev);
302
303__END_CDECLS
304