
Eliminate need to resolve cache based on the queue. This allows to share the queue between cache instances. The queue still holds pointer to a cache that owns the queue, but no management or io path relies on the queue -> cache mapping. Signed-off-by: Robert Baldyga <robert.baldyga@huawei.com> Signed-off-by: Michal Mielewczyk <michal.mielewczyk@huawei.com>
572 lines
12 KiB
C
572 lines
12 KiB
C
/*
|
|
* Copyright(c) 2012-2021 Intel Corporation
|
|
* Copyright(c) 2024 Huawei Technologies
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include "ocf/ocf.h"
|
|
#include "ocf_priv.h"
|
|
#include "ocf_core_priv.h"
|
|
#include "ocf_io_priv.h"
|
|
#include "metadata/metadata.h"
|
|
#include "engine/cache_engine.h"
|
|
#include "utils/utils_user_part.h"
|
|
#include "ocf_request.h"
|
|
|
|
struct ocf_core_volume {
|
|
ocf_core_t core;
|
|
};
|
|
|
|
ocf_cache_t ocf_core_get_cache(ocf_core_t core)
|
|
{
|
|
OCF_CHECK_NULL(core);
|
|
return core->volume.cache;
|
|
}
|
|
|
|
ocf_volume_t ocf_core_get_volume(ocf_core_t core)
|
|
{
|
|
OCF_CHECK_NULL(core);
|
|
return &core->volume;
|
|
}
|
|
|
|
ocf_volume_t ocf_core_get_front_volume(ocf_core_t core)
|
|
{
|
|
OCF_CHECK_NULL(core);
|
|
return &core->front_volume;
|
|
}
|
|
|
|
ocf_core_id_t ocf_core_get_id(ocf_core_t core)
|
|
{
|
|
struct ocf_cache *cache;
|
|
ocf_core_id_t core_id;
|
|
|
|
OCF_CHECK_NULL(core);
|
|
|
|
cache = core->volume.cache;
|
|
core_id = core - cache->core;
|
|
|
|
return core_id;
|
|
}
|
|
|
|
int ocf_core_get_by_name(ocf_cache_t cache, const char *name, size_t name_len,
|
|
ocf_core_t *core)
|
|
{
|
|
ocf_core_t i_core;
|
|
ocf_core_id_t i_core_id;
|
|
|
|
if (ocf_cache_is_standby(cache))
|
|
return -OCF_ERR_CACHE_STANDBY;
|
|
|
|
for_each_core(cache, i_core, i_core_id) {
|
|
if (!env_strncmp(ocf_core_get_name(i_core), OCF_CORE_NAME_SIZE,
|
|
name, name_len)) {
|
|
*core = i_core;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -OCF_ERR_CORE_NOT_EXIST;
|
|
}
|
|
|
|
const char *ocf_core_get_name(ocf_core_t core)
|
|
{
|
|
OCF_CHECK_NULL(core);
|
|
|
|
return core->conf_meta->name;
|
|
}
|
|
|
|
ocf_core_state_t ocf_core_get_state(ocf_core_t core)
|
|
{
|
|
OCF_CHECK_NULL(core);
|
|
|
|
return core->opened ?
|
|
ocf_core_state_active : ocf_core_state_inactive;
|
|
}
|
|
|
|
bool ocf_core_is_valid(ocf_cache_t cache, ocf_core_id_t id)
|
|
{
|
|
OCF_CHECK_NULL(cache);
|
|
|
|
if (id > OCF_CORE_ID_MAX)
|
|
return false;
|
|
|
|
if (!env_bit_test(id, cache->conf_meta->valid_core_bitmap))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t ocf_core_get_seq_cutoff_threshold(ocf_core_t core)
|
|
{
|
|
return env_atomic_read(&core->conf_meta->seq_cutoff_threshold);
|
|
}
|
|
|
|
ocf_seq_cutoff_policy ocf_core_get_seq_cutoff_policy(ocf_core_t core)
|
|
{
|
|
return env_atomic_read(&core->conf_meta->seq_cutoff_policy);
|
|
}
|
|
|
|
uint32_t ocf_core_get_seq_cutoff_promotion_count(ocf_core_t core)
|
|
{
|
|
return env_atomic_read(&core->conf_meta->seq_cutoff_promo_count);
|
|
}
|
|
|
|
bool ocf_core_get_seq_cutoff_promote_on_threshold(ocf_core_t core)
|
|
{
|
|
return env_atomic_read(&core->conf_meta->seq_cutoff_promote_on_threshold);
|
|
}
|
|
|
|
int ocf_core_visit(ocf_cache_t cache, ocf_core_visitor_t visitor, void *cntx,
|
|
bool only_opened)
|
|
{
|
|
ocf_core_id_t id;
|
|
int result = 0;
|
|
|
|
OCF_CHECK_NULL(cache);
|
|
|
|
if (ocf_cache_is_standby(cache))
|
|
return -OCF_ERR_CACHE_STANDBY;
|
|
|
|
if (!visitor)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
for (id = 0; id < OCF_CORE_MAX; id++) {
|
|
if (!env_bit_test(id, cache->conf_meta->valid_core_bitmap))
|
|
continue;
|
|
|
|
if (only_opened && !cache->core[id].opened)
|
|
continue;
|
|
|
|
result = visitor(&cache->core[id], cntx);
|
|
if (result)
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* *** HELPER FUNCTIONS *** */
|
|
|
|
static uint64_t _calc_dirty_for(uint64_t dirty_since)
|
|
{
|
|
uint64_t current_time = env_ticks_to_secs(env_get_tick_count());
|
|
|
|
return dirty_since ? (current_time - dirty_since) : 0;
|
|
}
|
|
|
|
struct ocf_request *ocf_io_to_req(struct ocf_io *io)
|
|
{
|
|
struct ocf_io_internal *ioi;
|
|
|
|
ioi = container_of(io, struct ocf_io_internal, io);
|
|
return container_of(ioi, struct ocf_request, ioi);
|
|
}
|
|
|
|
static inline ocf_core_t ocf_volume_to_core(ocf_volume_t volume)
|
|
{
|
|
struct ocf_core_volume *core_volume = ocf_volume_get_priv(volume);
|
|
|
|
return core_volume->core;
|
|
}
|
|
|
|
static inline void dec_counter_if_req_was_dirty(struct ocf_request *req)
|
|
{
|
|
if (!req->dirty)
|
|
return;
|
|
|
|
req->dirty = 0;
|
|
ocf_refcnt_dec(&req->cache->refcnt.dirty);
|
|
}
|
|
|
|
static inline int ocf_core_validate_io(struct ocf_io *io)
|
|
{
|
|
ocf_volume_t volume = ocf_io_get_volume(io);
|
|
ocf_core_t core = ocf_volume_to_core(volume);
|
|
|
|
if (io->addr + io->bytes > ocf_volume_get_length(volume))
|
|
return -OCF_ERR_INVAL;
|
|
|
|
if (io->io_class >= OCF_USER_IO_CLASS_MAX)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
if (io->dir != OCF_READ && io->dir != OCF_WRITE)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
if (!io->io_queue)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
if (!io->end)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
/* Core volume I/O must not be queued on management queue - this would
|
|
* break I/O accounting code, resulting in use-after-free type of errors
|
|
* after cache detach, core remove etc. */
|
|
if (io->io_queue == ocf_core_get_cache(core)->mngt_queue)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ocf_req_complete(struct ocf_request *req, int error)
|
|
{
|
|
/* Complete IO */
|
|
ocf_io_end(&req->ioi.io, error);
|
|
|
|
dec_counter_if_req_was_dirty(req);
|
|
|
|
/* Invalidate OCF IO, it is not valid after completion */
|
|
ocf_io_put(&req->ioi.io);
|
|
}
|
|
|
|
static int ocf_core_submit_io_fast(struct ocf_io *io, struct ocf_request *req,
|
|
ocf_core_t core, ocf_cache_t cache)
|
|
{
|
|
ocf_req_cache_mode_t original_cache_mode;
|
|
int fast;
|
|
|
|
if (req->d2c) {
|
|
return -OCF_ERR_IO;
|
|
}
|
|
|
|
original_cache_mode = req->cache_mode;
|
|
|
|
switch (req->cache_mode) {
|
|
case ocf_req_cache_mode_pt:
|
|
return -OCF_ERR_IO;
|
|
case ocf_req_cache_mode_wb:
|
|
case ocf_req_cache_mode_wo:
|
|
req->cache_mode = ocf_req_cache_mode_fast;
|
|
break;
|
|
default:
|
|
if (cache->use_submit_io_fast)
|
|
break;
|
|
|
|
if (io->dir == OCF_WRITE)
|
|
return -OCF_ERR_IO;
|
|
|
|
req->cache_mode = ocf_req_cache_mode_fast;
|
|
}
|
|
|
|
fast = ocf_engine_hndl_fast_req(req);
|
|
if (fast != OCF_FAST_PATH_NO)
|
|
return 0;
|
|
|
|
req->cache_mode = original_cache_mode;
|
|
return -OCF_ERR_IO;
|
|
}
|
|
|
|
static void ocf_core_volume_submit_io(struct ocf_io *io)
|
|
{
|
|
struct ocf_request *req;
|
|
ocf_core_t core;
|
|
ocf_cache_t cache;
|
|
int ret;
|
|
|
|
OCF_CHECK_NULL(io);
|
|
|
|
ret = ocf_core_validate_io(io);
|
|
if (ret < 0) {
|
|
ocf_io_end(io, ret);
|
|
return;
|
|
}
|
|
|
|
req = ocf_io_to_req(io);
|
|
core = ocf_volume_to_core(ocf_io_get_volume(io));
|
|
cache = ocf_core_get_cache(core);
|
|
|
|
if (unlikely(ocf_cache_is_standby(cache))) {
|
|
ocf_io_end(io, -OCF_ERR_CACHE_STANDBY);
|
|
return;
|
|
}
|
|
|
|
ret = ocf_req_alloc_map(req);
|
|
if (ret) {
|
|
ocf_io_end(io, ret);
|
|
return;
|
|
}
|
|
|
|
req->part_id = ocf_user_part_class2id(cache, io->io_class);
|
|
req->core = core;
|
|
req->complete = ocf_req_complete;
|
|
|
|
ocf_resolve_effective_cache_mode(cache, core, req);
|
|
|
|
ocf_core_update_stats(core, io);
|
|
|
|
ocf_io_get(io);
|
|
/* Prevent race condition */
|
|
ocf_req_get(req);
|
|
|
|
if (!ocf_core_submit_io_fast(io, req, core, cache)) {
|
|
ocf_core_seq_cutoff_update(core, req);
|
|
ocf_req_put(req);
|
|
return;
|
|
}
|
|
|
|
ocf_req_put(req);
|
|
ocf_req_clear_map(req);
|
|
ocf_core_seq_cutoff_update(core, req);
|
|
|
|
ret = ocf_engine_hndl_req(req);
|
|
if (ret) {
|
|
dec_counter_if_req_was_dirty(req);
|
|
ocf_io_end(io, ret);
|
|
ocf_io_put(io);
|
|
}
|
|
}
|
|
|
|
static void ocf_core_volume_submit_flush(struct ocf_io *io)
|
|
{
|
|
struct ocf_request *req;
|
|
ocf_core_t core;
|
|
ocf_cache_t cache;
|
|
int ret;
|
|
|
|
OCF_CHECK_NULL(io);
|
|
|
|
ret = ocf_core_validate_io(io);
|
|
if (ret < 0) {
|
|
ocf_io_end(io, ret);
|
|
return;
|
|
}
|
|
|
|
req = ocf_io_to_req(io);
|
|
core = ocf_volume_to_core(ocf_io_get_volume(io));
|
|
cache = ocf_core_get_cache(core);
|
|
|
|
if (unlikely(ocf_cache_is_standby(cache))) {
|
|
ocf_io_end(io, -OCF_ERR_CACHE_STANDBY);
|
|
return;
|
|
}
|
|
|
|
req->core = core;
|
|
req->complete = ocf_req_complete;
|
|
|
|
ocf_io_get(io);
|
|
|
|
ocf_engine_hndl_ops_req(req);
|
|
}
|
|
|
|
static void ocf_core_volume_submit_discard(struct ocf_io *io)
|
|
{
|
|
struct ocf_request *req;
|
|
ocf_core_t core;
|
|
ocf_cache_t cache;
|
|
int ret;
|
|
|
|
OCF_CHECK_NULL(io);
|
|
|
|
if (io->bytes == 0) {
|
|
ocf_io_end(io, -OCF_ERR_INVAL);
|
|
return;
|
|
}
|
|
|
|
ret = ocf_core_validate_io(io);
|
|
if (ret < 0) {
|
|
ocf_io_end(io, ret);
|
|
return;
|
|
}
|
|
|
|
req = ocf_io_to_req(io);
|
|
core = ocf_volume_to_core(ocf_io_get_volume(io));
|
|
cache = ocf_core_get_cache(core);
|
|
|
|
if (unlikely(ocf_cache_is_standby(cache))) {
|
|
ocf_io_end(io, -OCF_ERR_CACHE_STANDBY);
|
|
return;
|
|
}
|
|
|
|
ret = ocf_req_alloc_map_discard(req);
|
|
if (ret) {
|
|
ocf_io_end(io, -OCF_ERR_NO_MEM);
|
|
return;
|
|
}
|
|
|
|
req->core = core;
|
|
req->complete = ocf_req_complete;
|
|
|
|
ocf_io_get(io);
|
|
|
|
ocf_engine_hndl_discard_req(req);
|
|
}
|
|
|
|
/* *** VOLUME OPS *** */
|
|
|
|
static int ocf_core_volume_open(ocf_volume_t volume, void *volume_params)
|
|
{
|
|
struct ocf_core_volume *core_volume = ocf_volume_get_priv(volume);
|
|
const struct ocf_volume_uuid *uuid = ocf_volume_get_uuid(volume);
|
|
ocf_core_t core = (ocf_core_t)uuid->data;
|
|
|
|
core_volume->core = core;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ocf_core_volume_close(ocf_volume_t volume)
|
|
{
|
|
}
|
|
|
|
static unsigned int ocf_core_volume_get_max_io_size(ocf_volume_t volume)
|
|
{
|
|
ocf_core_t core = ocf_volume_to_core(volume);
|
|
|
|
return ocf_volume_get_max_io_size(&core->volume);
|
|
}
|
|
|
|
static uint64_t ocf_core_volume_get_byte_length(ocf_volume_t volume)
|
|
{
|
|
ocf_core_t core = ocf_volume_to_core(volume);
|
|
|
|
return ocf_volume_get_length(&core->volume);
|
|
}
|
|
|
|
|
|
/* *** IO OPS *** */
|
|
|
|
static int ocf_core_io_set_data(struct ocf_io *io,
|
|
ctx_data_t *data, uint32_t offset)
|
|
{
|
|
struct ocf_request *req;
|
|
|
|
OCF_CHECK_NULL(io);
|
|
|
|
if (!data || offset)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
req = ocf_io_to_req(io);
|
|
req->data = data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ctx_data_t *ocf_core_io_get_data(struct ocf_io *io)
|
|
{
|
|
struct ocf_request *req;
|
|
|
|
OCF_CHECK_NULL(io);
|
|
|
|
req = ocf_io_to_req(io);
|
|
return req->data;
|
|
}
|
|
|
|
const struct ocf_volume_properties ocf_core_volume_properties = {
|
|
.name = "OCF_Core",
|
|
.io_priv_size = 0, /* Not used - custom allocator */
|
|
.volume_priv_size = sizeof(struct ocf_core_volume),
|
|
.caps = {
|
|
.atomic_writes = 0,
|
|
},
|
|
.ops = {
|
|
.submit_io = ocf_core_volume_submit_io,
|
|
.submit_flush = ocf_core_volume_submit_flush,
|
|
.submit_discard = ocf_core_volume_submit_discard,
|
|
.submit_metadata = NULL,
|
|
|
|
.open = ocf_core_volume_open,
|
|
.close = ocf_core_volume_close,
|
|
.get_max_io_size = ocf_core_volume_get_max_io_size,
|
|
.get_length = ocf_core_volume_get_byte_length,
|
|
},
|
|
.io_ops = {
|
|
.set_data = ocf_core_io_set_data,
|
|
.get_data = ocf_core_io_get_data,
|
|
},
|
|
.deinit = NULL,
|
|
};
|
|
|
|
static int ocf_core_io_allocator_init(ocf_io_allocator_t allocator,
|
|
uint32_t priv_size, const char *name)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void ocf_core_io_allocator_deinit(ocf_io_allocator_t allocator)
|
|
{
|
|
}
|
|
|
|
static void *ocf_core_io_allocator_new(ocf_io_allocator_t allocator,
|
|
ocf_volume_t volume, ocf_queue_t queue,
|
|
uint64_t addr, uint32_t bytes, uint32_t dir)
|
|
{
|
|
ocf_core_t core = ocf_volume_to_core(volume);
|
|
struct ocf_request *req;
|
|
|
|
req = ocf_req_new(queue, core, addr, bytes, dir);
|
|
if (!req)
|
|
return NULL;
|
|
|
|
return &req->ioi;
|
|
}
|
|
|
|
static void ocf_core_io_allocator_del(ocf_io_allocator_t allocator, void *obj)
|
|
{
|
|
struct ocf_request *req;
|
|
|
|
req = container_of(obj, struct ocf_request, ioi);
|
|
ocf_req_put(req);
|
|
}
|
|
|
|
const struct ocf_io_allocator_type ocf_core_io_allocator_type = {
|
|
.ops = {
|
|
.allocator_init = ocf_core_io_allocator_init,
|
|
.allocator_deinit = ocf_core_io_allocator_deinit,
|
|
.allocator_new = ocf_core_io_allocator_new,
|
|
.allocator_del = ocf_core_io_allocator_del,
|
|
},
|
|
};
|
|
|
|
const struct ocf_volume_extended ocf_core_volume_extended = {
|
|
.allocator_type = &ocf_core_io_allocator_type,
|
|
};
|
|
|
|
int ocf_core_volume_type_init(ocf_ctx_t ctx)
|
|
{
|
|
return ocf_ctx_register_volume_type_internal(ctx, OCF_VOLUME_TYPE_CORE,
|
|
&ocf_core_volume_properties,
|
|
&ocf_core_volume_extended);
|
|
}
|
|
|
|
int ocf_core_get_info(ocf_core_t core, struct ocf_core_info *info)
|
|
{
|
|
ocf_cache_t cache;
|
|
|
|
OCF_CHECK_NULL(core);
|
|
|
|
cache = ocf_core_get_cache(core);
|
|
|
|
if (ocf_cache_is_standby(cache))
|
|
return -OCF_ERR_CACHE_STANDBY;
|
|
|
|
if (!info)
|
|
return -OCF_ERR_INVAL;
|
|
|
|
ENV_BUG_ON(env_memset(info, sizeof(*info), 0));
|
|
|
|
info->core_size_bytes = ocf_volume_get_length(&core->volume);
|
|
info->core_size = ocf_bytes_2_lines_round_up(cache,
|
|
info->core_size_bytes);
|
|
info->seq_cutoff_threshold = ocf_core_get_seq_cutoff_threshold(core);
|
|
info->seq_cutoff_policy = ocf_core_get_seq_cutoff_policy(core);
|
|
|
|
info->flushed = env_atomic_read(&core->flushed);
|
|
info->dirty = env_atomic_read(&core->runtime_meta->dirty_clines);
|
|
|
|
info->dirty_for = _calc_dirty_for(
|
|
env_atomic64_read(&core->runtime_meta->dirty_since));
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ocf_core_set_priv(ocf_core_t core, void *priv)
|
|
{
|
|
OCF_CHECK_NULL(core);
|
|
core->priv = priv;
|
|
}
|
|
|
|
void *ocf_core_get_priv(ocf_core_t core)
|
|
{
|
|
OCF_CHECK_NULL(core);
|
|
return core->priv;
|
|
}
|