1 /*
2 * Copyright (c) 2009 Travis Geiselbrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8 #include <lk/debug.h>
9 #include <lk/err.h>
10 #include <lk/trace.h>
11 #include <stdlib.h>
12 #include <lib/bio.h>
13
14 #define LOCAL_TRACE 0
15
16 typedef struct {
17 // inheirit the usual bits
18 bdev_t dev;
19
20 // we're a subdevice of this
21 bdev_t *parent;
22
23 // Storage for our erase geometry info. Subdevices are only permitted to
24 // have homogeneous erase geometry.
25 bio_erase_geometry_info_t geometry;
26
27 // we're this many blocks into it
28 bnum_t offset;
29 } subdev_t;
30
subdev_read(struct bdev * _dev,void * buf,off_t offset,size_t len)31 static ssize_t subdev_read(struct bdev *_dev, void *buf, off_t offset, size_t len) {
32 subdev_t *subdev = (subdev_t *)_dev;
33
34 return bio_read(subdev->parent, buf, offset + subdev->offset * subdev->dev.block_size, len);
35 }
36
subdev_read_block(struct bdev * _dev,void * buf,bnum_t block,uint count)37 static ssize_t subdev_read_block(struct bdev *_dev, void *buf, bnum_t block, uint count) {
38 subdev_t *subdev = (subdev_t *)_dev;
39
40 return bio_read_block(subdev->parent, buf, block + subdev->offset, count);
41 }
42
subdev_write(struct bdev * _dev,const void * buf,off_t offset,size_t len)43 static ssize_t subdev_write(struct bdev *_dev, const void *buf, off_t offset, size_t len) {
44 subdev_t *subdev = (subdev_t *)_dev;
45
46 return bio_write(subdev->parent, buf, offset + subdev->offset * subdev->dev.block_size, len);
47 }
48
subdev_write_block(struct bdev * _dev,const void * buf,bnum_t block,uint count)49 static ssize_t subdev_write_block(struct bdev *_dev, const void *buf, bnum_t block, uint count) {
50 subdev_t *subdev = (subdev_t *)_dev;
51
52 return bio_write_block(subdev->parent, buf, block + subdev->offset, count);
53 }
54
subdev_erase(struct bdev * _dev,off_t offset,size_t len)55 static ssize_t subdev_erase(struct bdev *_dev, off_t offset, size_t len) {
56 subdev_t *subdev = (subdev_t *)_dev;
57
58 return bio_erase(subdev->parent, offset + subdev->offset * subdev->dev.block_size, len);
59 }
60
subdev_close(struct bdev * _dev)61 static void subdev_close(struct bdev *_dev) {
62 subdev_t *subdev = (subdev_t *)_dev;
63
64 bio_close(subdev->parent);
65 subdev->parent = NULL;
66 }
67
68 #define BAIL(__err) do { err = __err; goto bailout; } while (0)
bio_publish_subdevice(const char * parent_dev,const char * subdev,bnum_t startblock,bnum_t block_count)69 status_t bio_publish_subdevice(const char *parent_dev,
70 const char *subdev,
71 bnum_t startblock,
72 bnum_t block_count) {
73 status_t err = NO_ERROR;
74 bdev_t *parent = NULL;
75 subdev_t *sub = NULL;
76 size_t geometry_count;
77 bio_erase_geometry_info_t *geometry;
78
79 LTRACEF("parent \"%s\", sub \"%s\", startblock %u, count %u\n", parent_dev, subdev, startblock, block_count);
80
81 // Make sure our parent exists
82 parent = bio_open(parent_dev);
83 if (!parent) {
84 LTRACEF("Failed to find parent \"%s\"\n", parent_dev);
85 BAIL(ERR_NOT_FOUND);
86 }
87
88 // Allocate our sub-device.
89 sub = malloc(sizeof(subdev_t));
90 if (!sub) {
91 LTRACEF("Failed to allocate subdevice\n");
92 BAIL(ERR_NO_MEMORY);
93 }
94
95 /* Make sure we're able to do this. If the device has a specified erase
96 * geometry, the specified sub-region must exist entirely with one of our
97 * parent's erase regions, and be aligned to an erase unit boundary.
98 * Otherwise, the specified region must simply exist within the block range
99 * of the device.
100 */
101 if (parent->geometry_count && parent->geometry) {
102 uint64_t byte_start = ((uint64_t)startblock << parent->block_shift);
103 uint64_t byte_size = ((uint64_t)block_count << parent->block_shift);
104 const bio_erase_geometry_info_t *geo = NULL;
105
106 LTRACEF("Searching geometry for region which contains @[0x%llx, 0x%llx)\n",
107 byte_start, byte_start + byte_size);
108
109 // Start by finding the erase region which completely contains the requested range.
110 for (size_t i = 0; i < parent->geometry_count; ++i) {
111 geo = parent->geometry + i;
112
113 LTRACEF("Checking geometry @[0x%llx, 0x%llx) erase size 0x%zx\n",
114 geo->start,
115 geo->start + geo->size,
116 geo->erase_size);
117
118 if (bio_contains_range(geo->start, geo->size, byte_start, byte_size))
119 break;
120 }
121
122 if (!geo) {
123 LTRACEF("No suitable erase region found\n");
124 BAIL(ERR_INVALID_ARGS);
125 }
126
127 // Now check out alignment.
128 uint64_t erase_mask = ((uint64_t)0x1 << geo->erase_shift) - 1;
129 if ((byte_start & erase_mask) || (byte_size & erase_mask)) {
130 LTRACEF("Requested region has improper alignment/length\n");
131 BAIL(ERR_INVALID_ARGS);
132 }
133
134 geometry_count = 1;
135 geometry = &sub->geometry;
136
137 geometry->start = 0;
138 geometry->size = byte_size;
139 geometry->erase_size = geo->erase_size;
140 geometry->erase_shift = geo->erase_shift;
141 } else {
142 bnum_t endblock = startblock + block_count;
143
144 if ((endblock < startblock) || (endblock > parent->block_count))
145 BAIL(ERR_INVALID_ARGS);
146
147 geometry_count = 0;
148 geometry = NULL;
149 }
150
151 bio_initialize_bdev(&sub->dev, subdev,
152 parent->block_size, block_count,
153 geometry_count, geometry, BIO_FLAGS_NONE);
154
155 sub->parent = parent;
156 sub->dev.erase_byte = parent->erase_byte;
157 sub->offset = startblock;
158
159 sub->dev.read = &subdev_read;
160 sub->dev.read_block = &subdev_read_block;
161 sub->dev.write = &subdev_write;
162 sub->dev.write_block = &subdev_write_block;
163 sub->dev.erase = &subdev_erase;
164 sub->dev.close = &subdev_close;
165
166 bio_register_device(&sub->dev);
167
168 bailout:
169 if (err < 0) {
170 if (NULL != parent)
171 bio_close(parent);
172 free(sub);
173 }
174
175 return err;
176 }
177 #undef BAIL
178