Commit | Line | Data |
---|---|---|
b7694961 DLM |
1 | /* |
2 | * Copyright (C) 2020 Western Digital Corporation or its affiliates. | |
3 | * | |
4 | * This file is released under the GPL. | |
5 | */ | |
6 | #include <errno.h> | |
7 | #include <string.h> | |
8 | #include <stdlib.h> | |
9 | #include <dirent.h> | |
10 | #include <fcntl.h> | |
11 | #include <sys/ioctl.h> | |
12 | #include <sys/stat.h> | |
13 | #include <unistd.h> | |
14 | ||
15 | #include "file.h" | |
16 | #include "fio.h" | |
17 | #include "lib/pow2.h" | |
18 | #include "log.h" | |
19 | #include "oslib/asprintf.h" | |
20 | #include "smalloc.h" | |
21 | #include "verify.h" | |
22 | #include "zbd_types.h" | |
23 | ||
24 | #include <linux/blkzoned.h> | |
25 | ||
6ee607ba NC |
26 | /* |
27 | * If the uapi headers installed on the system lacks zone capacity support, | |
28 | * use our local versions. If the installed headers are recent enough to | |
29 | * support zone capacity, do not redefine any structs. | |
30 | */ | |
31 | #ifndef CONFIG_HAVE_REP_CAPACITY | |
32 | #define BLK_ZONE_REP_CAPACITY (1 << 0) | |
33 | ||
34 | struct blk_zone_v2 { | |
35 | __u64 start; /* Zone start sector */ | |
36 | __u64 len; /* Zone length in number of sectors */ | |
37 | __u64 wp; /* Zone write pointer position */ | |
38 | __u8 type; /* Zone type */ | |
39 | __u8 cond; /* Zone condition */ | |
40 | __u8 non_seq; /* Non-sequential write resources active */ | |
41 | __u8 reset; /* Reset write pointer recommended */ | |
42 | __u8 resv[4]; | |
43 | __u64 capacity; /* Zone capacity in number of sectors */ | |
44 | __u8 reserved[24]; | |
45 | }; | |
46 | #define blk_zone blk_zone_v2 | |
47 | ||
48 | struct blk_zone_report_v2 { | |
49 | __u64 sector; | |
50 | __u32 nr_zones; | |
51 | __u32 flags; | |
52 | struct blk_zone zones[0]; | |
53 | }; | |
54 | #define blk_zone_report blk_zone_report_v2 | |
55 | #endif /* CONFIG_HAVE_REP_CAPACITY */ | |
56 | ||
b7694961 DLM |
57 | /* |
58 | * Read up to 255 characters from the first line of a file. Strip the trailing | |
59 | * newline. | |
60 | */ | |
61 | static char *read_file(const char *path) | |
62 | { | |
63 | char line[256], *p = line; | |
64 | FILE *f; | |
65 | ||
66 | f = fopen(path, "rb"); | |
67 | if (!f) | |
68 | return NULL; | |
69 | if (!fgets(line, sizeof(line), f)) | |
70 | line[0] = '\0'; | |
71 | strsep(&p, "\n"); | |
72 | fclose(f); | |
73 | ||
74 | return strdup(line); | |
75 | } | |
76 | ||
eaa45783 NC |
77 | /* |
78 | * Get the value of a sysfs attribute for a block device. | |
79 | * | |
80 | * Returns NULL on failure. | |
81 | * Returns a pointer to a string on success. | |
82 | * The caller is responsible for freeing the memory. | |
83 | */ | |
84 | static char *blkzoned_get_sysfs_attr(const char *file_name, const char *attr) | |
b7694961 | 85 | { |
eaa45783 | 86 | char *attr_path = NULL; |
b7694961 DLM |
87 | struct stat statbuf; |
88 | char *sys_devno_path = NULL; | |
89 | char *part_attr_path = NULL; | |
90 | char *part_str = NULL; | |
91 | char sys_path[PATH_MAX]; | |
92 | ssize_t sz; | |
93 | char *delim = NULL; | |
eaa45783 | 94 | char *attr_str = NULL; |
b7694961 DLM |
95 | |
96 | if (stat(file_name, &statbuf) < 0) | |
97 | goto out; | |
98 | ||
99 | if (asprintf(&sys_devno_path, "/sys/dev/block/%d:%d", | |
100 | major(statbuf.st_rdev), minor(statbuf.st_rdev)) < 0) | |
101 | goto out; | |
102 | ||
103 | sz = readlink(sys_devno_path, sys_path, sizeof(sys_path) - 1); | |
104 | if (sz < 0) | |
105 | goto out; | |
106 | sys_path[sz] = '\0'; | |
107 | ||
108 | /* | |
109 | * If the device is a partition device, cut the device name in the | |
110 | * canonical sysfs path to obtain the sysfs path of the holder device. | |
111 | * e.g.: /sys/devices/.../sda/sda1 -> /sys/devices/.../sda | |
112 | */ | |
113 | if (asprintf(&part_attr_path, "/sys/dev/block/%s/partition", | |
114 | sys_path) < 0) | |
115 | goto out; | |
116 | part_str = read_file(part_attr_path); | |
117 | if (part_str && *part_str == '1') { | |
118 | delim = strrchr(sys_path, '/'); | |
119 | if (!delim) | |
120 | goto out; | |
121 | *delim = '\0'; | |
122 | } | |
123 | ||
eaa45783 NC |
124 | if (asprintf(&attr_path, |
125 | "/sys/dev/block/%s/%s", sys_path, attr) < 0) | |
b7694961 DLM |
126 | goto out; |
127 | ||
eaa45783 NC |
128 | attr_str = read_file(attr_path); |
129 | out: | |
130 | free(attr_path); | |
131 | free(part_str); | |
132 | free(part_attr_path); | |
133 | free(sys_devno_path); | |
134 | ||
135 | return attr_str; | |
136 | } | |
137 | ||
138 | int blkzoned_get_zoned_model(struct thread_data *td, struct fio_file *f, | |
139 | enum zbd_zoned_model *model) | |
140 | { | |
141 | char *model_str = NULL; | |
142 | ||
2c7dd23e NC |
143 | if (f->filetype != FIO_TYPE_BLOCK) |
144 | return -EINVAL; | |
eaa45783 NC |
145 | |
146 | *model = ZBD_NONE; | |
147 | ||
148 | model_str = blkzoned_get_sysfs_attr(f->file_name, "queue/zoned"); | |
b7694961 | 149 | if (!model_str) |
eaa45783 NC |
150 | return 0; |
151 | ||
152 | dprint(FD_ZBD, "%s: zbd model string: %s\n", f->file_name, model_str); | |
b7694961 DLM |
153 | if (strcmp(model_str, "host-aware") == 0) |
154 | *model = ZBD_HOST_AWARE; | |
155 | else if (strcmp(model_str, "host-managed") == 0) | |
156 | *model = ZBD_HOST_MANAGED; | |
eaa45783 | 157 | |
b7694961 | 158 | free(model_str); |
eaa45783 | 159 | |
b7694961 DLM |
160 | return 0; |
161 | } | |
162 | ||
d2f442bc NC |
163 | int blkzoned_get_max_open_zones(struct thread_data *td, struct fio_file *f, |
164 | unsigned int *max_open_zones) | |
165 | { | |
166 | char *max_open_str; | |
167 | ||
168 | if (f->filetype != FIO_TYPE_BLOCK) | |
169 | return -EIO; | |
170 | ||
171 | max_open_str = blkzoned_get_sysfs_attr(f->file_name, "queue/max_open_zones"); | |
f3463241 DLM |
172 | if (!max_open_str) { |
173 | *max_open_zones = 0; | |
d2f442bc | 174 | return 0; |
f3463241 | 175 | } |
d2f442bc NC |
176 | |
177 | dprint(FD_ZBD, "%s: max open zones supported by device: %s\n", | |
178 | f->file_name, max_open_str); | |
179 | *max_open_zones = atoll(max_open_str); | |
180 | ||
181 | free(max_open_str); | |
182 | ||
183 | return 0; | |
184 | } | |
185 | ||
236d23a8 SK |
186 | static uint64_t zone_capacity(struct blk_zone_report *hdr, |
187 | struct blk_zone *blkz) | |
188 | { | |
236d23a8 SK |
189 | if (hdr->flags & BLK_ZONE_REP_CAPACITY) |
190 | return blkz->capacity << 9; | |
236d23a8 SK |
191 | return blkz->len << 9; |
192 | } | |
193 | ||
b7694961 DLM |
194 | int blkzoned_report_zones(struct thread_data *td, struct fio_file *f, |
195 | uint64_t offset, struct zbd_zone *zones, | |
196 | unsigned int nr_zones) | |
197 | { | |
198 | struct blk_zone_report *hdr = NULL; | |
199 | struct blk_zone *blkz; | |
200 | struct zbd_zone *z; | |
201 | unsigned int i; | |
202 | int fd = -1, ret; | |
203 | ||
204 | fd = open(f->file_name, O_RDONLY | O_LARGEFILE); | |
205 | if (fd < 0) | |
206 | return -errno; | |
207 | ||
208 | hdr = calloc(1, sizeof(struct blk_zone_report) + | |
209 | nr_zones * sizeof(struct blk_zone)); | |
210 | if (!hdr) { | |
211 | ret = -ENOMEM; | |
212 | goto out; | |
213 | } | |
214 | ||
215 | hdr->nr_zones = nr_zones; | |
216 | hdr->sector = offset >> 9; | |
217 | ret = ioctl(fd, BLKREPORTZONE, hdr); | |
218 | if (ret) { | |
219 | ret = -errno; | |
220 | goto out; | |
221 | } | |
222 | ||
223 | nr_zones = hdr->nr_zones; | |
f8779edf | 224 | blkz = (void *) hdr + sizeof(*hdr); |
b7694961 DLM |
225 | z = &zones[0]; |
226 | for (i = 0; i < nr_zones; i++, z++, blkz++) { | |
227 | z->start = blkz->start << 9; | |
228 | z->wp = blkz->wp << 9; | |
229 | z->len = blkz->len << 9; | |
236d23a8 | 230 | z->capacity = zone_capacity(hdr, blkz); |
b7694961 DLM |
231 | |
232 | switch (blkz->type) { | |
233 | case BLK_ZONE_TYPE_CONVENTIONAL: | |
234 | z->type = ZBD_ZONE_TYPE_CNV; | |
235 | break; | |
236 | case BLK_ZONE_TYPE_SEQWRITE_REQ: | |
237 | z->type = ZBD_ZONE_TYPE_SWR; | |
238 | break; | |
239 | case BLK_ZONE_TYPE_SEQWRITE_PREF: | |
240 | z->type = ZBD_ZONE_TYPE_SWP; | |
241 | break; | |
242 | default: | |
243 | td_verror(td, errno, "invalid zone type"); | |
244 | log_err("%s: invalid type for zone at sector %llu.\n", | |
245 | f->file_name, (unsigned long long)offset >> 9); | |
246 | ret = -EIO; | |
247 | goto out; | |
248 | } | |
249 | ||
250 | switch (blkz->cond) { | |
251 | case BLK_ZONE_COND_NOT_WP: | |
252 | z->cond = ZBD_ZONE_COND_NOT_WP; | |
253 | break; | |
254 | case BLK_ZONE_COND_EMPTY: | |
255 | z->cond = ZBD_ZONE_COND_EMPTY; | |
256 | break; | |
257 | case BLK_ZONE_COND_IMP_OPEN: | |
258 | z->cond = ZBD_ZONE_COND_IMP_OPEN; | |
259 | break; | |
260 | case BLK_ZONE_COND_EXP_OPEN: | |
261 | z->cond = ZBD_ZONE_COND_EXP_OPEN; | |
262 | break; | |
263 | case BLK_ZONE_COND_CLOSED: | |
264 | z->cond = ZBD_ZONE_COND_CLOSED; | |
265 | break; | |
266 | case BLK_ZONE_COND_FULL: | |
267 | z->cond = ZBD_ZONE_COND_FULL; | |
268 | break; | |
269 | case BLK_ZONE_COND_READONLY: | |
270 | case BLK_ZONE_COND_OFFLINE: | |
271 | default: | |
272 | /* Treat all these conditions as offline (don't use!) */ | |
273 | z->cond = ZBD_ZONE_COND_OFFLINE; | |
59b67452 | 274 | z->wp = z->start; |
b7694961 DLM |
275 | } |
276 | } | |
277 | ||
278 | ret = nr_zones; | |
279 | out: | |
280 | free(hdr); | |
281 | close(fd); | |
282 | ||
283 | return ret; | |
284 | } | |
285 | ||
286 | int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f, | |
287 | uint64_t offset, uint64_t length) | |
288 | { | |
289 | struct blk_zone_range zr = { | |
290 | .sector = offset >> 9, | |
291 | .nr_sectors = length >> 9, | |
292 | }; | |
b4824992 SK |
293 | int fd, ret = 0; |
294 | ||
295 | /* If the file is not yet opened, open it for this function. */ | |
296 | fd = f->fd; | |
297 | if (fd < 0) { | |
298 | fd = open(f->file_name, O_RDWR | O_LARGEFILE); | |
299 | if (fd < 0) | |
300 | return -errno; | |
301 | } | |
b7694961 | 302 | |
b4824992 SK |
303 | if (ioctl(fd, BLKRESETZONE, &zr) < 0) |
304 | ret = -errno; | |
b7694961 | 305 | |
b4824992 SK |
306 | if (f->fd < 0) |
307 | close(fd); | |
308 | ||
309 | return ret; | |
b7694961 | 310 | } |