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  */
read_file(const char * path)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  */
blkzoned_get_sysfs_attr(const char * file_name,const char * attr)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 
blkzoned_get_zoned_model(struct thread_data * td,struct fio_file * f,enum zbd_zoned_model * model)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 		return -EINVAL;
145 
146 	*model = ZBD_NONE;
147 
148 	model_str = blkzoned_get_sysfs_attr(f->file_name, "queue/zoned");
149 	if (!model_str)
150 		return 0;
151 
152 	dprint(FD_ZBD, "%s: zbd model string: %s\n", f->file_name, model_str);
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;
157 
158 	free(model_str);
159 
160 	return 0;
161 }
162 
blkzoned_get_max_open_zones(struct thread_data * td,struct fio_file * f,unsigned int * max_open_zones)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");
172 	if (!max_open_str) {
173 		*max_open_zones = 0;
174 		return 0;
175 	}
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 
zone_capacity(struct blk_zone_report * hdr,struct blk_zone * blkz)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 
blkzoned_report_zones(struct thread_data * td,struct fio_file * f,uint64_t offset,struct zbd_zone * zones,unsigned int nr_zones)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 
blkzoned_reset_wp(struct thread_data * td,struct fio_file * f,uint64_t offset,uint64_t length)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 }
311