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