ZLOOP_OPT_ZONE_APPEND = (1 << 9),
ZLOOP_OPT_ORDERED_ZONE_APPEND = (1 << 10),
ZLOOP_OPT_DISCARD_WRITE_CACHE = (1 << 11),
+ ZLOOP_OPT_MAX_OPEN_ZONES = (1 << 12),
};
static const match_table_t zloop_opt_tokens = {
{ ZLOOP_OPT_ZONE_APPEND, "zone_append=%u" },
{ ZLOOP_OPT_ORDERED_ZONE_APPEND, "ordered_zone_append" },
{ ZLOOP_OPT_DISCARD_WRITE_CACHE, "discard_write_cache" },
+ { ZLOOP_OPT_MAX_OPEN_ZONES, "max_open_zones=%u" },
{ ZLOOP_OPT_ERR, NULL }
};
#define ZLOOP_DEF_ZONE_SIZE ((256ULL * SZ_1M) >> SECTOR_SHIFT)
#define ZLOOP_DEF_NR_ZONES 64
#define ZLOOP_DEF_NR_CONV_ZONES 8
+#define ZLOOP_DEF_MAX_OPEN_ZONES 0
#define ZLOOP_DEF_BASE_DIR "/var/local/zloop"
#define ZLOOP_DEF_NR_QUEUES 1
#define ZLOOP_DEF_QUEUE_DEPTH 128
sector_t zone_size;
sector_t zone_capacity;
unsigned int nr_conv_zones;
+ unsigned int max_open_zones;
char *base_dir;
unsigned int nr_queues;
unsigned int queue_depth;
ZLOOP_ZONE_SEQ_ERROR,
};
+/*
+ * Zone descriptor.
+ * Locking order: z.lock -> z.wp_lock -> zlo.open_zones_lock
+ */
struct zloop_zone {
+ struct list_head open_zone_entry;
struct file *file;
unsigned long flags;
sector_t zone_capacity;
unsigned int nr_zones;
unsigned int nr_conv_zones;
+ unsigned int max_open_zones;
unsigned int block_size;
+ spinlock_t open_zones_lock;
+ struct list_head open_zones_lru_list;
+ unsigned int nr_open_zones;
+
struct zloop_zone zones[] __counted_by(nr_zones);
};
return blk_rq_pos(rq) >> zlo->zone_shift;
}
+/*
+ * Open an already open zone. This is mostly a no-op, except for the imp open ->
+ * exp open condition change that may happen. We also move a zone at the tail of
+ * the list of open zones so that if we need to
+ * implicitly close one open zone, we can do so in LRU order.
+ */
+static inline void zloop_lru_rotate_open_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ if (zlo->max_open_zones) {
+ spin_lock(&zlo->open_zones_lock);
+ list_move_tail(&zone->open_zone_entry,
+ &zlo->open_zones_lru_list);
+ spin_unlock(&zlo->open_zones_lock);
+ }
+}
+
+static inline void zloop_lru_remove_open_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ if (zone->cond == BLK_ZONE_COND_IMP_OPEN ||
+ zone->cond == BLK_ZONE_COND_EXP_OPEN) {
+ spin_lock(&zlo->open_zones_lock);
+ list_del_init(&zone->open_zone_entry);
+ zlo->nr_open_zones--;
+ spin_unlock(&zlo->open_zones_lock);
+ }
+}
+
+static inline bool zloop_can_open_zone(struct zloop_device *zlo)
+{
+ return !zlo->max_open_zones || zlo->nr_open_zones < zlo->max_open_zones;
+}
+
+/*
+ * If we have reached the maximum open zones limit, attempt to close an
+ * implicitly open zone (if we have any) so that we can implicitly open another
+ * zone without exceeding the maximum number of open zones.
+ */
+static bool zloop_close_imp_open_zone(struct zloop_device *zlo)
+{
+ struct zloop_zone *zone;
+
+ lockdep_assert_held(&zlo->open_zones_lock);
+
+ if (zloop_can_open_zone(zlo))
+ return true;
+
+ list_for_each_entry(zone, &zlo->open_zones_lru_list, open_zone_entry) {
+ if (zone->cond == BLK_ZONE_COND_IMP_OPEN) {
+ zone->cond = BLK_ZONE_COND_CLOSED;
+ list_del_init(&zone->open_zone_entry);
+ zlo->nr_open_zones--;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool zloop_open_closed_or_empty_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone,
+ bool explicit)
+{
+ spin_lock(&zlo->open_zones_lock);
+
+ if (explicit) {
+ /*
+ * Explicit open: we cannot allow this if we have reached the
+ * maximum open zones limit.
+ */
+ if (!zloop_can_open_zone(zlo))
+ goto fail;
+ zone->cond = BLK_ZONE_COND_EXP_OPEN;
+ } else {
+ /*
+ * Implicit open case: if we have reached the maximum open zones
+ * limit, try to close an implicitly open zone first.
+ */
+ if (!zloop_close_imp_open_zone(zlo))
+ goto fail;
+ zone->cond = BLK_ZONE_COND_IMP_OPEN;
+ }
+
+ zlo->nr_open_zones++;
+ list_add_tail(&zone->open_zone_entry,
+ &zlo->open_zones_lru_list);
+
+ spin_unlock(&zlo->open_zones_lock);
+
+ return true;
+
+fail:
+ spin_unlock(&zlo->open_zones_lock);
+
+ return false;
+}
+
+static bool zloop_do_open_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone, bool explicit)
+{
+ switch (zone->cond) {
+ case BLK_ZONE_COND_IMP_OPEN:
+ case BLK_ZONE_COND_EXP_OPEN:
+ if (explicit)
+ zone->cond = BLK_ZONE_COND_EXP_OPEN;
+ zloop_lru_rotate_open_zone(zlo, zone);
+ return true;
+ case BLK_ZONE_COND_EMPTY:
+ case BLK_ZONE_COND_CLOSED:
+ return zloop_open_closed_or_empty_zone(zlo, zone, explicit);
+ default:
+ return false;
+ }
+}
+
static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)
{
struct zloop_zone *zone = &zlo->zones[zone_no];
spin_lock_irqsave(&zone->wp_lock, flags);
if (!file_sectors) {
+ zloop_lru_remove_open_zone(zlo, zone);
zone->cond = BLK_ZONE_COND_EMPTY;
zone->wp = zone->start;
} else if (file_sectors == zlo->zone_capacity) {
+ zloop_lru_remove_open_zone(zlo, zone);
zone->cond = BLK_ZONE_COND_FULL;
zone->wp = ULLONG_MAX;
} else {
- zone->cond = BLK_ZONE_COND_CLOSED;
+ if (zone->cond != BLK_ZONE_COND_IMP_OPEN &&
+ zone->cond != BLK_ZONE_COND_EXP_OPEN)
+ zone->cond = BLK_ZONE_COND_CLOSED;
zone->wp = zone->start + file_sectors;
}
spin_unlock_irqrestore(&zone->wp_lock, flags);
goto unlock;
}
- switch (zone->cond) {
- case BLK_ZONE_COND_EXP_OPEN:
- break;
- case BLK_ZONE_COND_EMPTY:
- case BLK_ZONE_COND_CLOSED:
- case BLK_ZONE_COND_IMP_OPEN:
- zone->cond = BLK_ZONE_COND_EXP_OPEN;
- break;
- case BLK_ZONE_COND_FULL:
- default:
+ if (!zloop_do_open_zone(zlo, zone, true))
ret = -EIO;
- break;
- }
unlock:
mutex_unlock(&zone->lock);
case BLK_ZONE_COND_IMP_OPEN:
case BLK_ZONE_COND_EXP_OPEN:
spin_lock_irqsave(&zone->wp_lock, flags);
+ zloop_lru_remove_open_zone(zlo, zone);
if (zone->wp == zone->start)
zone->cond = BLK_ZONE_COND_EMPTY;
else
}
spin_lock_irqsave(&zone->wp_lock, flags);
+ zloop_lru_remove_open_zone(zlo, zone);
zone->cond = BLK_ZONE_COND_EMPTY;
zone->wp = zone->start;
clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
}
spin_lock_irqsave(&zone->wp_lock, flags);
+ zloop_lru_remove_open_zone(zlo, zone);
zone->cond = BLK_ZONE_COND_FULL;
zone->wp = ULLONG_MAX;
clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
}
/* Implicitly open the target zone. */
- if (zone->cond == BLK_ZONE_COND_CLOSED ||
- zone->cond == BLK_ZONE_COND_EMPTY)
- zone->cond = BLK_ZONE_COND_IMP_OPEN;
+ if (!zloop_do_open_zone(zlo, zone, false)) {
+ ret = -EIO;
+ goto out_unlock;
+ }
/*
* Advance the write pointer, unless ordered zone append is in use. If
if (!is_append || !zlo->ordered_zone_append) {
zone->wp += nr_sectors;
if (zone->wp == zone_end) {
+ zloop_lru_remove_open_zone(zlo, zone);
zone->cond = BLK_ZONE_COND_FULL;
zone->wp = ULLONG_MAX;
}
rq->__sector = zone->wp;
zone->wp += blk_rq_sectors(rq);
if (zone->wp >= zone_end) {
+ zloop_lru_remove_open_zone(zlo, zone);
zone->cond = BLK_ZONE_COND_FULL;
zone->wp = ULLONG_MAX;
}
int ret;
mutex_init(&zone->lock);
+ INIT_LIST_HEAD(&zone->open_zone_entry);
spin_lock_init(&zone->wp_lock);
zone->start = (sector_t)zone_no << zlo->zone_shift;
goto out;
}
+ if (opts->max_open_zones > nr_zones - opts->nr_conv_zones) {
+ pr_err("Invalid maximum number of open zones %u\n",
+ opts->max_open_zones);
+ goto out;
+ }
+
zlo = kvzalloc_flex(*zlo, zones, nr_zones);
if (!zlo) {
ret = -ENOMEM;
goto out;
}
WRITE_ONCE(zlo->state, Zlo_creating);
+ spin_lock_init(&zlo->open_zones_lock);
+ INIT_LIST_HEAD(&zlo->open_zones_lru_list);
ret = mutex_lock_killable(&zloop_ctl_mutex);
if (ret)
zlo->zone_capacity = zlo->zone_size;
zlo->nr_zones = nr_zones;
zlo->nr_conv_zones = opts->nr_conv_zones;
+ zlo->max_open_zones = opts->max_open_zones;
zlo->buffered_io = opts->buffered_io;
zlo->zone_append = opts->zone_append;
if (zlo->zone_append)
lim.logical_block_size = zlo->block_size;
if (zlo->zone_append)
lim.max_hw_zone_append_sectors = lim.max_hw_sectors;
+ lim.max_open_zones = zlo->max_open_zones;
zlo->tag_set.ops = &zloop_mq_ops;
zlo->tag_set.nr_hw_queues = opts->nr_queues;
opts->capacity = ZLOOP_DEF_ZONE_SIZE * ZLOOP_DEF_NR_ZONES;
opts->zone_size = ZLOOP_DEF_ZONE_SIZE;
opts->nr_conv_zones = ZLOOP_DEF_NR_CONV_ZONES;
+ opts->max_open_zones = ZLOOP_DEF_MAX_OPEN_ZONES;
opts->nr_queues = ZLOOP_DEF_NR_QUEUES;
opts->queue_depth = ZLOOP_DEF_QUEUE_DEPTH;
opts->buffered_io = ZLOOP_DEF_BUFFERED_IO;
}
opts->nr_conv_zones = token;
break;
+ case ZLOOP_OPT_MAX_OPEN_ZONES:
+ if (match_uint(args, &token)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ opts->max_open_zones = token;
+ break;
case ZLOOP_OPT_BASE_DIR:
p = match_strdup(args);
if (!p) {