Merge branch 'fix/928' of https://github.com/larsks/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
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
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
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)
85 {
86         char *attr_path = NULL;
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;
94         char *attr_str = NULL;
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
124         if (asprintf(&attr_path,
125                      "/sys/dev/block/%s/%s", sys_path, attr) < 0)
126                 goto out;
127
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
143         if (f->filetype != FIO_TYPE_BLOCK) {
144                 *model = ZBD_IGNORE;
145                 return 0;
146         }
147
148         *model = ZBD_NONE;
149
150         model_str = blkzoned_get_sysfs_attr(f->file_name, "queue/zoned");
151         if (!model_str)
152                 return 0;
153
154         dprint(FD_ZBD, "%s: zbd model string: %s\n", f->file_name, model_str);
155         if (strcmp(model_str, "host-aware") == 0)
156                 *model = ZBD_HOST_AWARE;
157         else if (strcmp(model_str, "host-managed") == 0)
158                 *model = ZBD_HOST_MANAGED;
159
160         free(model_str);
161
162         return 0;
163 }
164
165 int blkzoned_get_max_open_zones(struct thread_data *td, struct fio_file *f,
166                                 unsigned int *max_open_zones)
167 {
168         char *max_open_str;
169
170         if (f->filetype != FIO_TYPE_BLOCK)
171                 return -EIO;
172
173         max_open_str = blkzoned_get_sysfs_attr(f->file_name, "queue/max_open_zones");
174         if (!max_open_str)
175                 return 0;
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
186 static uint64_t zone_capacity(struct blk_zone_report *hdr,
187                               struct blk_zone *blkz)
188 {
189         if (hdr->flags & BLK_ZONE_REP_CAPACITY)
190                 return blkz->capacity << 9;
191         return blkz->len << 9;
192 }
193
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;
224         blkz = (void *) hdr + sizeof(*hdr);
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;
230                 z->capacity = zone_capacity(hdr, blkz);
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;
274                         z->wp = z->start;
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         };
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         }
302
303         if (ioctl(fd, BLKRESETZONE, &zr) < 0)
304                 ret = -errno;
305
306         if (f->fd < 0)
307                 close(fd);
308
309         return ret;
310 }