Merge branch 'vsock' of https://github.com/MPinna/fio
[fio.git] / oslib / linux-blkzoned.c
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 #ifndef BLKFINISHZONE
26 #define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range)
27 #endif
28
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
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
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)
88 {
89         char *attr_path = NULL;
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;
97         char *attr_str = NULL;
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
127         if (asprintf(&attr_path,
128                      "/sys/dev/block/%s/%s", sys_path, attr) < 0)
129                 goto out;
130
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
146         if (f->filetype != FIO_TYPE_BLOCK)
147                 return -EINVAL;
148
149         *model = ZBD_NONE;
150
151         model_str = blkzoned_get_sysfs_attr(f->file_name, "queue/zoned");
152         if (!model_str)
153                 return 0;
154
155         dprint(FD_ZBD, "%s: zbd model string: %s\n", f->file_name, model_str);
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;
160
161         free(model_str);
162
163         return 0;
164 }
165
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");
175         if (!max_open_str) {
176                 *max_open_zones = 0;
177                 return 0;
178         }
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
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
212 static uint64_t zone_capacity(struct blk_zone_report *hdr,
213                               struct blk_zone *blkz)
214 {
215         if (hdr->flags & BLK_ZONE_REP_CAPACITY)
216                 return blkz->capacity << 9;
217         return blkz->len << 9;
218 }
219
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                 log_err("%s: BLKREPORTZONE ioctl failed, ret=%d, err=%d.\n",
246                         f->file_name, ret, -errno);
247                 ret = -errno;
248                 goto out;
249         }
250
251         nr_zones = hdr->nr_zones;
252         blkz = (void *) hdr + sizeof(*hdr);
253         z = &zones[0];
254         for (i = 0; i < nr_zones; i++, z++, blkz++) {
255                 z->start = blkz->start << 9;
256                 z->wp = blkz->wp << 9;
257                 z->len = blkz->len << 9;
258                 z->capacity = zone_capacity(hdr, blkz);
259
260                 switch (blkz->type) {
261                 case BLK_ZONE_TYPE_CONVENTIONAL:
262                         z->type = ZBD_ZONE_TYPE_CNV;
263                         break;
264                 case BLK_ZONE_TYPE_SEQWRITE_REQ:
265                         z->type = ZBD_ZONE_TYPE_SWR;
266                         break;
267                 case BLK_ZONE_TYPE_SEQWRITE_PREF:
268                         z->type = ZBD_ZONE_TYPE_SWP;
269                         break;
270                 default:
271                         td_verror(td, errno, "invalid zone type");
272                         log_err("%s: invalid type for zone at sector %llu.\n",
273                                 f->file_name, (unsigned long long)offset >> 9);
274                         ret = -EIO;
275                         goto out;
276                 }
277
278                 switch (blkz->cond) {
279                 case BLK_ZONE_COND_NOT_WP:
280                         z->cond = ZBD_ZONE_COND_NOT_WP;
281                         break;
282                 case BLK_ZONE_COND_EMPTY:
283                         z->cond = ZBD_ZONE_COND_EMPTY;
284                         break;
285                 case BLK_ZONE_COND_IMP_OPEN:
286                         z->cond = ZBD_ZONE_COND_IMP_OPEN;
287                         break;
288                 case BLK_ZONE_COND_EXP_OPEN:
289                         z->cond = ZBD_ZONE_COND_EXP_OPEN;
290                         break;
291                 case BLK_ZONE_COND_CLOSED:
292                         z->cond = ZBD_ZONE_COND_CLOSED;
293                         break;
294                 case BLK_ZONE_COND_FULL:
295                         z->cond = ZBD_ZONE_COND_FULL;
296                         break;
297                 case BLK_ZONE_COND_READONLY:
298                 case BLK_ZONE_COND_OFFLINE:
299                 default:
300                         /* Treat all these conditions as offline (don't use!) */
301                         z->cond = ZBD_ZONE_COND_OFFLINE;
302                         z->wp = z->start;
303                 }
304         }
305
306         ret = nr_zones;
307 out:
308         free(hdr);
309         close(fd);
310
311         return ret;
312 }
313
314 int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f,
315                       uint64_t offset, uint64_t length)
316 {
317         struct blk_zone_range zr = {
318                 .sector         = offset >> 9,
319                 .nr_sectors     = length >> 9,
320         };
321         int fd, ret = 0;
322
323         /* If the file is not yet opened, open it for this function. */
324         fd = f->fd;
325         if (fd < 0) {
326                 fd = open(f->file_name, O_RDWR | O_LARGEFILE);
327                 if (fd < 0)
328                         return -errno;
329         }
330
331         if (ioctl(fd, BLKRESETZONE, &zr) < 0)
332                 ret = -errno;
333
334         if (f->fd < 0)
335                 close(fd);
336
337         return ret;
338 }
339
340 int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f,
341                          uint64_t offset, uint64_t length)
342 {
343         struct blk_zone_range zr = {
344                 .sector         = offset >> 9,
345                 .nr_sectors     = length >> 9,
346         };
347         int fd, ret = 0;
348
349         /* If the file is not yet opened, open it for this function. */
350         fd = f->fd;
351         if (fd < 0) {
352                 fd = open(f->file_name, O_RDWR | O_LARGEFILE);
353                 if (fd < 0)
354                         return -errno;
355         }
356
357         if (ioctl(fd, BLKFINISHZONE, &zr) < 0) {
358                 ret = -errno;
359                 /*
360                  * Kernel versions older than 5.5 do not support BLKFINISHZONE
361                  * and return the ENOTTY error code. These old kernels only
362                  * support block devices that close zones automatically.
363                  */
364                 if (ret == ENOTTY)
365                         ret = 0;
366         }
367
368         if (f->fd < 0)
369                 close(fd);
370
371         return ret;
372 }