/**
* libf2fs_zoned.c
*
* Copyright (c) 2016 Western Digital Corporation.
* Written by: Damien Le Moal <damien.lemoal@wdc.com>
*
* Dual licensed under the GPL or LGPL version 2 licenses.
*/
#define _LARGEFILE64_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifndef ANDROID_WINDOWS_HOST
#include <sys/ioctl.h>
#endif
#include <libgen.h>
#include <f2fs_fs.h>
#ifdef HAVE_LINUX_BLKZONED_H
void f2fs_get_zoned_model(int i)
{
struct device_info *dev = c.devices + i;
char str[128];
FILE *file;
int res;
/* Check that this is a zoned block device */
snprintf(str, sizeof(str),
"/sys/block/%s/queue/zoned",
basename(dev->path));
file = fopen(str, "r");
if (!file)
goto not_zoned;
memset(str, 0, sizeof(str));
res = fscanf(file, "%s", str);
fclose(file);
if (res != 1)
goto not_zoned;
if (strcmp(str, "host-aware") == 0) {
dev->zoned_model = F2FS_ZONED_HA;
return;
}
if (strcmp(str, "host-managed") == 0) {
dev->zoned_model = F2FS_ZONED_HM;
return;
}
not_zoned:
dev->zoned_model = F2FS_ZONED_NONE;
}
int f2fs_get_zone_blocks(int i)
{
struct device_info *dev = c.devices + i;
uint64_t sectors;
char str[128];
FILE *file;
int res;
/* Get zone size */
dev->zone_blocks = 0;
snprintf(str, sizeof(str),
"/sys/block/%s/queue/chunk_sectors",
basename(dev->path));
file = fopen(str, "r");
if (!file)
return -1;
memset(str, 0, sizeof(str));
res = fscanf(file, "%s", str);
fclose(file);
if (res != 1)
return -1;
sectors = atol(str);
if (!sectors)
return -1;
dev->zone_blocks = sectors >> (F2FS_BLKSIZE_BITS - 9);
sectors = (sectors << 9) / c.sector_size;
/*
* Total number of zones: there may
* be a last smaller runt zone.
*/
dev->nr_zones = dev->total_sectors / sectors;
if (dev->total_sectors % sectors)
dev->nr_zones++;
return 0;
}
#define F2FS_REPORT_ZONES_BUFSZ 524288
int f2fs_check_zones(int j)
{
struct device_info *dev = c.devices + j;
struct blk_zone_report *rep;
struct blk_zone *blkz;
unsigned int i, n = 0;
u_int64_t total_sectors;
u_int64_t sector;
int last_is_conv = 1;
int ret = -1;
rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
if (!rep) {
ERR_MSG("No memory for report zones\n");
return -ENOMEM;
}
dev->nr_rnd_zones = 0;
sector = 0;
total_sectors = (dev->total_sectors * c.sector_size) >> 9;
while (sector < total_sectors) {
/* Get zone info */
memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
rep->sector = sector;
rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
/ sizeof(struct blk_zone);
ret = ioctl(dev->fd, BLKREPORTZONE, rep);
if (ret != 0) {
ret = -errno;
ERR_MSG("ioctl BLKREPORTZONE failed\n");
goto out;
}
if (!rep->nr_zones)
break;
blkz = (struct blk_zone *)(rep + 1);
for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
if (blk_zone_cond(blkz) == BLK_ZONE_COND_READONLY ||
blk_zone_cond(blkz) == BLK_ZONE_COND_OFFLINE)
last_is_conv = 0;
if (blk_zone_conv(blkz) ||
blk_zone_seq_pref(blkz)) {
if (last_is_conv)
dev->nr_rnd_zones++;
} else {
last_is_conv = 0;
}
if (blk_zone_conv(blkz)) {
DBG(2,
"Zone %05u: Conventional, cond 0x%x (%s), sector %llu, %llu sectors\n",
n,
blk_zone_cond(blkz),
blk_zone_cond_str(blkz),
blk_zone_sector(blkz),
blk_zone_length(blkz));
} else {
DBG(2,
"Zone %05u: type 0x%x (%s), cond 0x%x (%s), need_reset %d, "
"non_seq %d, sector %llu, %llu sectors, wp sector %llu\n",
n,
blk_zone_type(blkz),
blk_zone_type_str(blkz),
blk_zone_cond(blkz),
blk_zone_cond_str(blkz),
blk_zone_need_reset(blkz),
blk_zone_non_seq(blkz),
blk_zone_sector(blkz),
blk_zone_length(blkz),
blk_zone_wp_sector(blkz));
}
sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
n++;
blkz++;
}
}
if (sector != total_sectors) {
ERR_MSG("Invalid zones: last sector reported is %llu, expected %llu\n",
(unsigned long long)(sector << 9) / c.sector_size,
(unsigned long long)dev->total_sectors);
ret = -1;
goto out;
}
if (n != dev->nr_zones) {
ERR_MSG("Inconsistent number of zones: expected %u zones, got %u\n",
dev->nr_zones, n);
ret = -1;
goto out;
}
if (dev->zoned_model == F2FS_ZONED_HM &&
!dev->nr_rnd_zones) {
ERR_MSG("No conventional zone for super block\n");
ret = -1;
}
out:
free(rep);
return ret;
}
int f2fs_reset_zones(int j)
{
struct device_info *dev = c.devices + j;
struct blk_zone_report *rep;
struct blk_zone *blkz;
struct blk_zone_range range;
u_int64_t total_sectors;
u_int64_t sector;
unsigned int i;
int ret = -1;
rep = malloc(F2FS_REPORT_ZONES_BUFSZ);
if (!rep) {
ERR_MSG("No memory for report zones\n");
return -1;
}
sector = 0;
total_sectors = (dev->total_sectors * c.sector_size) >> 9;
while (sector < total_sectors) {
/* Get zone info */
memset(rep, 0, F2FS_REPORT_ZONES_BUFSZ);
rep->sector = sector;
rep->nr_zones = (F2FS_REPORT_ZONES_BUFSZ - sizeof(struct blk_zone_report))
/ sizeof(struct blk_zone);
ret = ioctl(dev->fd, BLKREPORTZONE, rep);
if (ret != 0) {
ret = -errno;
ERR_MSG("ioctl BLKREPORTZONES failed\n");
goto out;
}
if (!rep->nr_zones)
break;
blkz = (struct blk_zone *)(rep + 1);
for (i = 0; i < rep->nr_zones && sector < total_sectors; i++) {
if (blk_zone_seq(blkz) &&
!blk_zone_empty(blkz)) {
/* Non empty sequential zone: reset */
range.sector = blk_zone_sector(blkz);
range.nr_sectors = blk_zone_length(blkz);
ret = ioctl(dev->fd, BLKRESETZONE, &range);
if (ret != 0) {
ret = -errno;
ERR_MSG("ioctl BLKRESETZONE failed\n");
goto out;
}
}
sector = blk_zone_sector(blkz) + blk_zone_length(blkz);
blkz++;
}
}
out:
free(rep);
if (!ret)
MSG(0, "Info: Discarded %"PRIu64" MB\n", (sector << 9) >> 20);
return ret;
}
#else
void f2fs_get_zoned_model(int i)
{
struct device_info *dev = c.devices + i;
c.zoned_mode = 0;
dev->zoned_model = F2FS_ZONED_NONE;
}
int f2fs_get_zone_blocks(int i)
{
struct device_info *dev = c.devices + i;
c.zoned_mode = 0;
dev->nr_zones = 0;
dev->zone_blocks = 0;
dev->zoned_model = F2FS_ZONED_NONE;
return 0;
}
int f2fs_check_zones(int i)
{
ERR_MSG("%d: Zoned block devices are not supported\n", i);
return -1;
}
int f2fs_reset_zones(int i)
{
ERR_MSG("%d: Zoned block devices are not supported\n", i);
return -1;
}
#endif