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