blob: 78e25fca9132a96b41720eb79325420221bb7fd4 [file] [log] [blame]
/*
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
*
* This file is released under the GPL.
*/
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "file.h"
#include "fio.h"
#include "lib/pow2.h"
#include "log.h"
#include "oslib/asprintf.h"
#include "smalloc.h"
#include "verify.h"
#include "zbd_types.h"
#include <linux/blkzoned.h>
#ifndef BLKFINISHZONE
#define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range)
#endif
/*
* If the uapi headers installed on the system lacks zone capacity support,
* use our local versions. If the installed headers are recent enough to
* support zone capacity, do not redefine any structs.
*/
#ifndef CONFIG_HAVE_REP_CAPACITY
#define BLK_ZONE_REP_CAPACITY (1 << 0)
struct blk_zone_v2 {
__u64 start; /* Zone start sector */
__u64 len; /* Zone length in number of sectors */
__u64 wp; /* Zone write pointer position */
__u8 type; /* Zone type */
__u8 cond; /* Zone condition */
__u8 non_seq; /* Non-sequential write resources active */
__u8 reset; /* Reset write pointer recommended */
__u8 resv[4];
__u64 capacity; /* Zone capacity in number of sectors */
__u8 reserved[24];
};
#define blk_zone blk_zone_v2
struct blk_zone_report_v2 {
__u64 sector;
__u32 nr_zones;
__u32 flags;
struct blk_zone zones[0];
};
#define blk_zone_report blk_zone_report_v2
#endif /* CONFIG_HAVE_REP_CAPACITY */
/*
* Read up to 255 characters from the first line of a file. Strip the trailing
* newline.
*/
static char *read_file(const char *path)
{
char line[256], *p = line;
FILE *f;
f = fopen(path, "rb");
if (!f)
return NULL;
if (!fgets(line, sizeof(line), f))
line[0] = '\0';
strsep(&p, "\n");
fclose(f);
return strdup(line);
}
/*
* Get the value of a sysfs attribute for a block device.
*
* Returns NULL on failure.
* Returns a pointer to a string on success.
* The caller is responsible for freeing the memory.
*/
static char *blkzoned_get_sysfs_attr(const char *file_name, const char *attr)
{
char *attr_path = NULL;
struct stat statbuf;
char *sys_devno_path = NULL;
char *part_attr_path = NULL;
char *part_str = NULL;
char sys_path[PATH_MAX];
ssize_t sz;
char *delim = NULL;
char *attr_str = NULL;
if (stat(file_name, &statbuf) < 0)
goto out;
if (asprintf(&sys_devno_path, "/sys/dev/block/%d:%d",
major(statbuf.st_rdev), minor(statbuf.st_rdev)) < 0)
goto out;
sz = readlink(sys_devno_path, sys_path, sizeof(sys_path) - 1);
if (sz < 0)
goto out;
sys_path[sz] = '\0';
/*
* If the device is a partition device, cut the device name in the
* canonical sysfs path to obtain the sysfs path of the holder device.
* e.g.: /sys/devices/.../sda/sda1 -> /sys/devices/.../sda
*/
if (asprintf(&part_attr_path, "/sys/dev/block/%s/partition",
sys_path) < 0)
goto out;
part_str = read_file(part_attr_path);
if (part_str && *part_str == '1') {
delim = strrchr(sys_path, '/');
if (!delim)
goto out;
*delim = '\0';
}
if (asprintf(&attr_path,
"/sys/dev/block/%s/%s", sys_path, attr) < 0)
goto out;
attr_str = read_file(attr_path);
out:
free(attr_path);
free(part_str);
free(part_attr_path);
free(sys_devno_path);
return attr_str;
}
int blkzoned_get_zoned_model(struct thread_data *td, struct fio_file *f,
enum zbd_zoned_model *model)
{
char *model_str = NULL;
if (f->filetype != FIO_TYPE_BLOCK)
return -EINVAL;
*model = ZBD_NONE;
model_str = blkzoned_get_sysfs_attr(f->file_name, "queue/zoned");
if (!model_str)
return 0;
dprint(FD_ZBD, "%s: zbd model string: %s\n", f->file_name, model_str);
if (strcmp(model_str, "host-aware") == 0)
*model = ZBD_HOST_AWARE;
else if (strcmp(model_str, "host-managed") == 0)
*model = ZBD_HOST_MANAGED;
free(model_str);
return 0;
}
int blkzoned_get_max_open_zones(struct thread_data *td, struct fio_file *f,
unsigned int *max_open_zones)
{
char *max_open_str;
if (f->filetype != FIO_TYPE_BLOCK)
return -EIO;
max_open_str = blkzoned_get_sysfs_attr(f->file_name, "queue/max_open_zones");
if (!max_open_str) {
*max_open_zones = 0;
return 0;
}
dprint(FD_ZBD, "%s: max open zones supported by device: %s\n",
f->file_name, max_open_str);
*max_open_zones = atoll(max_open_str);
free(max_open_str);
return 0;
}
int blkzoned_get_max_active_zones(struct thread_data *td, struct fio_file *f,
unsigned int *max_active_zones)
{
char *max_active_str;
if (f->filetype != FIO_TYPE_BLOCK)
return -EIO;
max_active_str = blkzoned_get_sysfs_attr(f->file_name, "queue/max_active_zones");
if (!max_active_str) {
*max_active_zones = 0;
return 0;
}
dprint(FD_ZBD, "%s: max active zones supported by device: %s\n",
f->file_name, max_active_str);
*max_active_zones = atoll(max_active_str);
free(max_active_str);
return 0;
}
static uint64_t zone_capacity(struct blk_zone_report *hdr,
struct blk_zone *blkz)
{
if (hdr->flags & BLK_ZONE_REP_CAPACITY)
return blkz->capacity << 9;
return blkz->len << 9;
}
int blkzoned_report_zones(struct thread_data *td, struct fio_file *f,
uint64_t offset, struct zbd_zone *zones,
unsigned int nr_zones)
{
struct blk_zone_report *hdr = NULL;
struct blk_zone *blkz;
struct zbd_zone *z;
unsigned int i;
int fd = -1, ret;
fd = open(f->file_name, O_RDONLY | O_LARGEFILE);
if (fd < 0)
return -errno;
hdr = calloc(1, sizeof(struct blk_zone_report) +
nr_zones * sizeof(struct blk_zone));
if (!hdr) {
ret = -ENOMEM;
goto out;
}
hdr->nr_zones = nr_zones;
hdr->sector = offset >> 9;
ret = ioctl(fd, BLKREPORTZONE, hdr);
if (ret) {
log_err("%s: BLKREPORTZONE ioctl failed, ret=%d, err=%d.\n",
f->file_name, ret, -errno);
ret = -errno;
goto out;
}
nr_zones = hdr->nr_zones;
blkz = (void *) hdr + sizeof(*hdr);
z = &zones[0];
for (i = 0; i < nr_zones; i++, z++, blkz++) {
z->start = blkz->start << 9;
z->wp = blkz->wp << 9;
z->len = blkz->len << 9;
z->capacity = zone_capacity(hdr, blkz);
switch (blkz->type) {
case BLK_ZONE_TYPE_CONVENTIONAL:
z->type = ZBD_ZONE_TYPE_CNV;
break;
case BLK_ZONE_TYPE_SEQWRITE_REQ:
z->type = ZBD_ZONE_TYPE_SWR;
break;
case BLK_ZONE_TYPE_SEQWRITE_PREF:
z->type = ZBD_ZONE_TYPE_SWP;
break;
default:
td_verror(td, errno, "invalid zone type");
log_err("%s: invalid type for zone at sector %llu.\n",
f->file_name, (unsigned long long)offset >> 9);
ret = -EIO;
goto out;
}
switch (blkz->cond) {
case BLK_ZONE_COND_NOT_WP:
z->cond = ZBD_ZONE_COND_NOT_WP;
break;
case BLK_ZONE_COND_EMPTY:
z->cond = ZBD_ZONE_COND_EMPTY;
break;
case BLK_ZONE_COND_IMP_OPEN:
z->cond = ZBD_ZONE_COND_IMP_OPEN;
break;
case BLK_ZONE_COND_EXP_OPEN:
z->cond = ZBD_ZONE_COND_EXP_OPEN;
break;
case BLK_ZONE_COND_CLOSED:
z->cond = ZBD_ZONE_COND_CLOSED;
break;
case BLK_ZONE_COND_FULL:
z->cond = ZBD_ZONE_COND_FULL;
break;
case BLK_ZONE_COND_READONLY:
case BLK_ZONE_COND_OFFLINE:
default:
/* Treat all these conditions as offline (don't use!) */
z->cond = ZBD_ZONE_COND_OFFLINE;
z->wp = z->start;
}
}
ret = nr_zones;
out:
free(hdr);
close(fd);
return ret;
}
int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f,
uint64_t offset, uint64_t length)
{
struct blk_zone_range zr = {
.sector = offset >> 9,
.nr_sectors = length >> 9,
};
int fd, ret = 0;
/* If the file is not yet opened, open it for this function. */
fd = f->fd;
if (fd < 0) {
fd = open(f->file_name, O_RDWR | O_LARGEFILE);
if (fd < 0)
return -errno;
}
if (ioctl(fd, BLKRESETZONE, &zr) < 0)
ret = -errno;
if (f->fd < 0)
close(fd);
return ret;
}
int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f,
uint64_t offset, uint64_t length)
{
struct blk_zone_range zr = {
.sector = offset >> 9,
.nr_sectors = length >> 9,
};
int fd, ret = 0;
/* If the file is not yet opened, open it for this function. */
fd = f->fd;
if (fd < 0) {
fd = open(f->file_name, O_RDWR | O_LARGEFILE);
if (fd < 0)
return -errno;
}
if (ioctl(fd, BLKFINISHZONE, &zr) < 0) {
ret = -errno;
/*
* Kernel versions older than 5.5 do not support BLKFINISHZONE
* and return the ENOTTY error code. These old kernels only
* support block devices that close zones automatically.
*/
if (ret == ENOTTY)
ret = 0;
}
if (f->fd < 0)
close(fd);
return ret;
}
int blkzoned_move_zone_wp(struct thread_data *td, struct fio_file *f,
struct zbd_zone *z, uint64_t length, const char *buf)
{
int fd, ret = 0;
/* If the file is not yet open, open it for this function */
fd = f->fd;
if (fd < 0) {
fd = open(f->file_name, O_WRONLY | O_DIRECT);
if (fd < 0)
return -errno;
}
/* If write data is not provided, fill zero to move the write pointer */
if (!buf) {
ret = fallocate(fd, FALLOC_FL_ZERO_RANGE, z->wp, length);
goto out;
}
if (pwrite(fd, buf, length, z->wp) < 0)
ret = -errno;
out:
if (f->fd < 0)
close(fd);
return ret;
}