// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2011 Red Hat, Inc.
*
* This file is released under the GPL.
*/
#include "dm-space-map.h"
#include "dm-space-map-common.h"
#include "dm-space-map-metadata.h"
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/device-mapper.h>
#include <linux/kernel.h>
#define DM_MSG_PREFIX "space map metadata"
/*----------------------------------------------------------------*/
/*
* An edge triggered threshold.
*/
struct threshold {
bool threshold_set;
bool value_set;
dm_block_t threshold;
dm_block_t current_value;
dm_sm_threshold_fn fn;
void *context;
};
static void threshold_init(struct threshold *t)
{
t->threshold_set = false;
t->value_set = false;
}
static void set_threshold(struct threshold *t, dm_block_t value,
dm_sm_threshold_fn fn, void *context)
{
t->threshold_set = true;
t->threshold = value;
t->fn = fn;
t->context = context;
}
static bool below_threshold(struct threshold *t, dm_block_t value)
{
return t->threshold_set && value <= t->threshold;
}
static bool threshold_already_triggered(struct threshold *t)
{
return t->value_set && below_threshold(t, t->current_value);
}
static void check_threshold(struct threshold *t, dm_block_t value)
{
if (below_threshold(t, value) &&
!threshold_already_triggered(t))
t->fn(t->context);
t->value_set = true;
t->current_value = value;
}
/*----------------------------------------------------------------*/
/*
* Space map interface.
*
* The low level disk format is written using the standard btree and
* transaction manager. This means that performing disk operations may
* cause us to recurse into the space map in order to allocate new blocks.
* For this reason we have a pool of pre-allocated blocks large enough to
* service any metadata_ll_disk operation.
*/
/*
* FIXME: we should calculate this based on the size of the device.
* Only the metadata space map needs this functionality.
*/
#define MAX_RECURSIVE_ALLOCATIONS 1024
enum block_op_type {
BOP_INC,
BOP_DEC
};
struct block_op {
enum block_op_type type;
dm_block_t b;
dm_block_t e;
};
struct bop_ring_buffer {
unsigned int begin;
unsigned int end;
struct block_op bops[MAX_RECURSIVE_ALLOCATIONS + 1];
};
static void brb_init(struct bop_ring_buffer *brb)
{
brb->begin = 0;
brb->end = 0;
}
static bool brb_empty(struct bop_ring_buffer *brb)
{
return brb->begin == brb->end;
}
static unsigned int brb_next(struct bop_ring_buffer *brb, unsigned int old)
{
unsigned int r = old + 1;
return r >= ARRAY_SIZE(brb->bops) ? 0 : r;
}
static int brb_push(struct bop_ring_buffer *brb,
enum block_op_type type, dm_block_t b, dm_block_t e)
{
struct block_op *bop;
unsigned int next = brb_next(brb, brb->end);
/*
* We don't allow the last bop to be filled, this way we can
* differentiate between full and empty.
*/
if (next == brb->begin)
return -ENOMEM;
bop = brb->bops + brb->end;
bop->type = type;
bop->b = b;
bop->e = e;
brb->end = next;
return 0;
}
static int brb_peek(struct bop_ring_buffer *brb, struct block_op *result)
{
struct block_op *bop;
if (brb_empty(brb))
return -ENODATA;
bop = brb->bops + brb->begin;
memcpy(result, bop, sizeof(*result));
return 0;
}
static int brb_pop(struct bop_ring_buffer *brb)
{
if (brb_empty(brb))
return -ENODATA;
brb->begin = brb_next(brb, brb->begin);
return 0;
}
/*----------------------------------------------------------------*/
struct sm_metadata {
struct dm_space_map sm;
struct ll_disk ll;
struct ll_disk old_ll;
dm_block_t begin;
unsigned int recursion_count;
unsigned int allocated_this_transaction;
struct bop_ring_buffer uncommitted;
struct threshold threshold;
};
static int add_bop(struct sm_metadata *smm, enum block_op_type type, dm_block_t b, dm_block_t e)
{
int r = brb_push(&smm->uncommitted, type, b, e);
if (r) {
DMERR("too many recursive allocations");
return -ENOMEM;
}
return 0;
}
static int commit_bop(struct sm_metadata *smm, struct block_op *op)
{
int r = 0;
int32_t nr_allocations;
switch (op->type) {
case BOP_INC:
r = sm_ll_inc(&smm->ll, op->b, op->e, &