open-cas-linux/modules/cas_cache/classifier.c
Michal Mielewczyk 1e5355eba1 Extending 'configure' script
Functions and macros dependent on different kernel versions are now generated
before compilation basing on current kernel capabilities instead of hardcoding
them for specific kernels.

Signed-off-by: Michal Mielewczyk <michal.mielewczyk@intel.com>
2019-05-30 06:29:07 -04:00

985 lines
23 KiB
C

/*
* Copyright(c) 2019 Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#include "cas_cache.h"
#include "linux_kernel_version.h"
#include "classifier.h"
#include "classifier_defs.h"
#include <linux/namei.h>
/* Kernel log prefix */
#define CAS_CLS_LOG_PREFIX OCF_PREFIX_SHORT"[Classifier]"
/* Production version logs */
#define CAS_CLS_MSG(severity, format, ...) \
printk(severity CAS_CLS_LOG_PREFIX " " format, ##__VA_ARGS__);
/* Set to 1 to enable debug logs */
#define CAS_CLASSIFIER_CLS_DEBUG 0
#if 1 == CAS_CLASSIFIER_CLS_DEBUG
/* Debug log */
#define CAS_CLS_DEBUG_MSG(format, ...) \
CAS_CLS_MSG(KERN_INFO, format, ##__VA_ARGS__)
/* Trace log */
#define CAS_CLS_DEBUG_TRACE(format, ...) \
trace_printk(format, ##__VA_ARGS__)
#else
#define CAS_CLS_DEBUG_MSG(format, ...)
#define CAS_CLS_DEBUG_TRACE(format, ...)
#endif
/* Done condition test - always accepts and stops evaluation */
static cas_cls_eval_t _cas_cls_done_test(struct cas_classifier *cls,
struct cas_cls_condition *c, struct cas_cls_io *io,
ocf_part_id_t part_id)
{
cas_cls_eval_t ret = {.yes = 1, .stop = 1};
return ret;
}
/* Metadata condition test */
static cas_cls_eval_t _cas_cls_metadata_test(struct cas_classifier *cls,
struct cas_cls_condition *c, struct cas_cls_io *io,
ocf_part_id_t part_id)
{
if (!io->page)
return cas_cls_eval_no;
if (PageAnon(io->page))
return cas_cls_eval_no;
if (PageSlab(io->page) || PageCompound(io->page)) {
/* A filesystem issues IO on pages that does not belongs
* to the file page cache. It means that it is a
* part of metadata
*/
return cas_cls_eval_yes;
}
if (!io->page->mapping) {
/* XFS case, page are allocated internally and do not
* have references into inode
*/
return cas_cls_eval_yes;
}
if (!io->inode)
return cas_cls_eval_no;
if (S_ISBLK(io->inode->i_mode)) {
/* EXT3 and EXT4 case. Metadata IO is performed into pages
* of block device cache
*/
return cas_cls_eval_yes;
}
if (S_ISDIR(io->inode->i_mode)) {
return cas_cls_eval_yes;
}
return cas_cls_eval_no;
}
/* Direct I/O condition test function */
static cas_cls_eval_t _cas_cls_direct_test(struct cas_classifier *cls,
struct cas_cls_condition *c, struct cas_cls_io *io,
ocf_part_id_t part_id)
{
if (!io->page)
return cas_cls_eval_no;
if (PageAnon(io->page))
return cas_cls_eval_yes;
return cas_cls_eval_no;
}
/* Generic condition constructor for conditions without operands (e.g. direct,
* metadata) */
static int _cas_cls_generic_ctr(struct cas_classifier *cls,
struct cas_cls_condition *c, char *data)
{
if (data) {
CAS_CLS_MSG(KERN_ERR, "Unexpected operand in condition\n");
return -EINVAL;
}
return 0;
}
/* Generic condition destructor */
static void _cas_cls_generic_dtr(struct cas_classifier *cls,
struct cas_cls_condition *c)
{
if (c->context)
kfree(c->context);
c->context = NULL;
}
/* Numeric condition constructor. @data is expected to contain either
* plain number string or range specifier (e.g. "gt:4096"). */
static int _cas_cls_numeric_ctr(struct cas_classifier* cls,
struct cas_cls_condition *c, char *data)
{
struct cas_cls_numeric *ctx;
int result;
char *ptr;
if (!data || strlen(data) == 0) {
CAS_CLS_MSG(KERN_ERR, "Missing numeric condition operand\n");
return -EINVAL;
}
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->operator = cas_cls_numeric_eq;
ptr = strpbrk(data, ":");
if (ptr) {
/* Terminate sub-string containing arithmetic operator */
*ptr = '\0';
++ptr;
if (!strcmp(data, "eq")) {
ctx->operator = cas_cls_numeric_eq;
} else if (!strcmp(data, "ne")) {
ctx->operator = cas_cls_numeric_ne;
} else if (!strcmp(data, "lt")) {
ctx->operator = cas_cls_numeric_lt;
} else if (!strcmp(data, "gt")) {
ctx->operator = cas_cls_numeric_gt;
} else if (!strcmp(data, "le")) {
ctx->operator = cas_cls_numeric_le;
} else if (!strcmp(data, "ge")) {
ctx->operator = cas_cls_numeric_ge;
} else {
CAS_CLS_MSG(KERN_ERR, "Invalid numeric operator \n");
result = -EINVAL;
goto error;
}
} else {
/* Plain number case */
ptr = data;
}
result = kstrtou64(ptr, 10, &ctx->v_u64);
if (result) {
CAS_CLS_MSG(KERN_ERR, "Invalid numeric operand\n");
goto error;
}
CAS_CLS_DEBUG_MSG("\t\t - Using operator %d with value %llu\n",
ctx->operator, ctx->v_u64);
c->context = ctx;
return 0;
error:
kfree(ctx);
return result;
}
/* Unsigned int numeric test function */
static cas_cls_eval_t _cas_cls_numeric_test_u(
struct cas_cls_condition *c, uint64_t val)
{
struct cas_cls_numeric *ctx = c->context;
switch (ctx->operator) {
case cas_cls_numeric_eq:
return val == ctx->v_u64 ? cas_cls_eval_yes : cas_cls_eval_no;
case cas_cls_numeric_ne:
return val != ctx->v_u64 ? cas_cls_eval_yes : cas_cls_eval_no;
case cas_cls_numeric_lt:
return val < ctx->v_u64 ? cas_cls_eval_yes : cas_cls_eval_no;
case cas_cls_numeric_gt:
return val > ctx->v_u64 ? cas_cls_eval_yes : cas_cls_eval_no;
case cas_cls_numeric_le:
return val <= ctx->v_u64 ? cas_cls_eval_yes : cas_cls_eval_no;
case cas_cls_numeric_ge:
return val >= ctx->v_u64 ? cas_cls_eval_yes : cas_cls_eval_no;
}
return cas_cls_eval_no;
}
#ifdef CAS_WLTH_SUPPORT
/* Write lifetime hint condition test */
static cas_cls_eval_t _cas_cls_wlth_test(struct cas_classifier *cls,
struct cas_cls_condition *c, struct cas_cls_io *io,
ocf_part_id_t part_id)
{
return _cas_cls_numeric_test_u(c, io->bio->bi_write_hint);
}
#endif
/* Io class test function */
static cas_cls_eval_t _cas_cls_io_class_test(struct cas_classifier *cls,
struct cas_cls_condition *c, struct cas_cls_io *io,
ocf_part_id_t part_id)
{
return _cas_cls_numeric_test_u(c, part_id);
}
/* File size test function */
static cas_cls_eval_t _cas_cls_file_size_test(
struct cas_classifier *cls, struct cas_cls_condition *c,
struct cas_cls_io *io, ocf_part_id_t part_id)
{
if (!io->inode)
return cas_cls_eval_no;
if (S_ISBLK(io->inode->i_mode))
return cas_cls_eval_no;
if (!S_ISREG(io->inode->i_mode))
return cas_cls_eval_no;
return _cas_cls_numeric_test_u(c, i_size_read(io->inode));
}
/* Resolve path to inode */
static void _cas_cls_directory_resolve(struct cas_classifier *cls,
struct cas_cls_directory *ctx)
{
struct path path;
struct inode *inode;
int error;
int o_res;
unsigned long o_ino;
o_res = ctx->resolved;
o_ino = ctx->i_ino;
error = kern_path(ctx->pathname, LOOKUP_FOLLOW, &path);
if (error) {
ctx->resolved = 0;
if (o_res) {
CAS_CLS_DEBUG_MSG("Removed inode resolution for %s\n",
ctx->pathname);
}
return;
}
inode = path.dentry->d_inode;
ctx->i_ino = inode->i_ino;
ctx->resolved = 1;
path_put(&path);
if (!o_res) {
CAS_CLS_DEBUG_MSG("Resolved %s to inode: %lu\n", ctx->pathname,
ctx->i_ino);
} else if (o_ino != ctx->i_ino) {
CAS_CLS_DEBUG_MSG("Changed inode resolution for %s: %lu => %lu"
"\n", ctx->pathname, o_ino, ctx->i_ino);
}
}
/* Inode resolving work entry point */
static void _cas_cls_directory_resolve_work(struct work_struct *work)
{
struct cas_cls_directory *ctx;
ctx = container_of(work, struct cas_cls_directory, d_work.work);
_cas_cls_directory_resolve(ctx->cls, ctx);
queue_delayed_work(ctx->cls->wq, &ctx->d_work,
msecs_to_jiffies(ctx->resolved ? 5000 : 1000));
}
/* Get unaliased dentry for given dir inode */
static struct dentry *_cas_cls_dir_get_inode_dentry(struct inode *inode)
{
struct dentry *d = NULL, *iter;
CAS_ALIAS_NODE_TYPE *pos; /* alias list current element */
if (CAS_DENTRY_LIST_EMPTY(&inode->i_dentry))
return NULL;
spin_lock(&inode->i_lock);
if (S_ISDIR(inode->i_mode))
goto unlock;
CAS_INODE_FOR_EACH_DENTRY(pos, &inode->i_dentry) {
iter = CAS_ALIAS_NODE_TO_DENTRY(pos);
spin_lock(&iter->d_lock);
if (!d_unhashed(iter))
d = iter;
spin_unlock(&iter->d_lock);
if (d)
break;
}
unlock:
spin_unlock(&inode->i_lock);
return d;
}
/* Directory condition test function */
static cas_cls_eval_t _cas_cls_directory_test(
struct cas_classifier *cls, struct cas_cls_condition *c,
struct cas_cls_io *io, ocf_part_id_t part_id)
{
struct cas_cls_directory *ctx;
struct inode *inode, *p_inode;
struct dentry *dentry, *p_dentry;
ctx = c->context;
inode = io->inode;
if (!inode || !ctx->resolved)
return cas_cls_eval_no;
/* I/O target inode dentry */
dentry = _cas_cls_dir_get_inode_dentry(inode);
if (!dentry)
return cas_cls_eval_no;
/* Walk up directory tree starting from I/O destination
* dir until current dir inode matches condition inode or top
* directory is reached. */
while (inode) {
if (inode->i_ino == ctx->i_ino)
return cas_cls_eval_yes;
spin_lock(&dentry->d_lock);
p_dentry = dentry->d_parent;
if (!p_dentry) {
spin_unlock(&dentry->d_lock);
return cas_cls_eval_no;
}
p_inode = p_dentry->d_inode;
spin_unlock(&dentry->d_lock);
if (p_inode != inode) {
inode = p_inode;
dentry = p_dentry;
} else {
inode = NULL;
}
}
return cas_cls_eval_no;
}
/* Directory condition constructor */
static int _cas_cls_directory_ctr(struct cas_classifier *cls,
struct cas_cls_condition *c, char *data)
{
struct cas_cls_directory *ctx;
if (!data || strlen(data) == 0) {
CAS_CLS_MSG(KERN_ERR, "Missing directory specifier\n");
return -EINVAL;
}
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->cls = cls;
ctx->resolved = 0;
ctx->pathname = kstrdup(data, GFP_KERNEL);
if (!ctx->pathname) {
kfree(ctx);
return -ENOMEM;
}
INIT_DELAYED_WORK(&ctx->d_work, _cas_cls_directory_resolve_work);
queue_delayed_work(cls->wq, &ctx->d_work,
msecs_to_jiffies(10));
c->context = ctx;
return 0;
}
/* Directory condition destructor */
static void _cas_cls_directory_dtr(struct cas_classifier *cls,
struct cas_cls_condition *c)
{
struct cas_cls_directory *ctx;
ctx = c->context;
if (!ctx)
return;
cancel_delayed_work_sync(&ctx->d_work);
kfree(ctx->pathname);
kfree(ctx);
}
/* Array of condition handlers */
static struct cas_cls_condition_handler _handlers[] = {
{ "done", _cas_cls_done_test, _cas_cls_generic_ctr },
{ "metadata", _cas_cls_metadata_test, _cas_cls_generic_ctr },
{ "direct", _cas_cls_direct_test, _cas_cls_generic_ctr },
{ "io_class", _cas_cls_io_class_test, _cas_cls_numeric_ctr,
_cas_cls_generic_dtr },
{ "file_size", _cas_cls_file_size_test, _cas_cls_numeric_ctr,
_cas_cls_generic_dtr },
{ "directory", _cas_cls_directory_test, _cas_cls_directory_ctr,
_cas_cls_directory_dtr },
#ifdef CAS_WLTH_SUPPORT
{ "wlth", _cas_cls_wlth_test, _cas_cls_numeric_ctr,
_cas_cls_generic_dtr},
#endif
{ NULL }
};
/* Get condition handler for condition string token */
static struct cas_cls_condition_handler *_cas_cls_lookup_handler(
const char *token)
{
struct cas_cls_condition_handler *h = _handlers;
while (h->token) {
if (strcmp(h->token, token) == 0)
return h;
h++;
}
return NULL;
}
/* Deallocate condition */
static void _cas_cls_free_condition(struct cas_classifier *cls,
struct cas_cls_condition *c)
{
if (c->handler->dtr)
c->handler->dtr(cls, c);
kfree(c);
}
/* Allocate condition */
static struct cas_cls_condition * _cas_cls_create_condition(
struct cas_classifier *cls, const char *token,
char *data, int l_op)
{
struct cas_cls_condition_handler *h;
struct cas_cls_condition *c;
int result;
h = _cas_cls_lookup_handler(token);
if (!h) {
CAS_CLS_DEBUG_MSG("Cannot find handler for condition"
" %s\n", token);
return ERR_PTR(-ENOENT);
}
c = kmalloc(sizeof(*c), GFP_KERNEL);
if (!c)
return ERR_PTR(-ENOMEM);
c->handler = h;
c->context = NULL;
c->l_op = l_op;
if (c->handler->ctr) {
result = c->handler->ctr(cls, c, data);
if (result) {
kfree(c);
return ERR_PTR(result);
}
}
CAS_CLS_DEBUG_MSG("\t\t - Created condition %s\n", token);
return c;
}
/* Read single codnition from text input and return cas_cls_condition
* representation. *rule pointer is advanced to point to next condition.
* Input @rule string is modified to speed up parsing (selected bytes are
* overwritten with 0).
*
* *l_op contains logical operator from previous condition and gets overwritten
* with operator read from currently parsed condition.
*
* Returns pointer to condition if successfull.
* Returns NULL if no more conditions in string.
* Returns error pointer in case of syntax or runtime error.
*/
static struct cas_cls_condition *_cas_cls_parse_condition(
struct cas_classifier *cls, char **rule,
enum cas_cls_logical_op *l_op)
{
char *token = *rule; /* Condition token substring (e.g. file_size) */
char *operand = NULL; /* Operand substring (e.g. "lt:4096" or path) */
char *ptr; /* Current position in input string */
char *last = token; /* Last seen substring in condition */
char op = 'X'; /* Logical operator at the end of condition */
struct cas_cls_condition *c; /* Output condition */
if (**rule == '\0') {
/* Empty condition */
return NULL;
}
ptr = strpbrk(*rule, ":&|");
if (!ptr) {
/* No operands in condition (e.g. "metadata"), no logical
* operators following condition - we're done with parsing. */
goto create;
}
if (*ptr == ':') {
/* Operand found - terminate token string and move forward. */
*ptr = '\0';
ptr += 1;
operand = ptr;
last = ptr;
ptr = strpbrk(ptr, "&|");
if (!ptr) {
/* No operator past condition - create rule and exit */
goto create;
}
}
/* Remember operator value and zero target byte to terminate previous
* string (token or operand) */
op = *ptr;
*ptr = '\0';
create:
c = _cas_cls_create_condition(cls, token, operand, *l_op);
*l_op = (op == '|' ? cas_cls_logical_or : cas_cls_logical_and);
/* Set *rule to character past current condition and logical operator */
if (ptr) {
/* Set pointer for next iteration */
*rule = ptr + 1;
} else {
/* Set pointer to terminating zero */
*rule = last + strlen(last);
}
return c;
}
/* Parse all conditions in rule text description. @rule might be overwritten */
static int _cas_cls_parse_conditions(struct cas_classifier *cls,
struct cas_cls_rule *r, char *rule)
{
char *start;
struct cas_cls_condition *c;
enum cas_cls_logical_op l_op = cas_cls_logical_or;
start = rule;
for (;;) {
c = _cas_cls_parse_condition(cls, &start, &l_op);
if (IS_ERR(c))
return PTR_ERR(c);
if (!c)
break;
list_add_tail(&c->list, &r->conditions);
}
return 0;
}
static struct cas_classifier* cas_get_classifier(ocf_cache_t cache)
{
struct cache_priv *cache_priv = ocf_cache_get_priv(cache);
ENV_BUG_ON(!cache_priv);
return cache_priv->classifier;
}
static void cas_set_classifier(ocf_cache_t cache,
struct cas_classifier* cls)
{
struct cache_priv *cache_priv = ocf_cache_get_priv(cache);
ENV_BUG_ON(!cache_priv);
cache_priv->classifier = cls;
}
void _cas_cls_rule_destroy(struct cas_classifier *cls,
struct cas_cls_rule *r)
{
struct list_head *item, *n;
struct cas_cls_condition *c = NULL;
if (!r)
return;
list_for_each_safe(item, n, &r->conditions) {
c = list_entry(item, struct cas_cls_condition, list);
list_del(item);
_cas_cls_free_condition(cls, c);
}
kfree(r);
}
/* Destroy rule */
void cas_cls_rule_destroy(ocf_cache_t cache, struct cas_cls_rule *r)
{
struct cas_classifier *cls = cas_get_classifier(cache);
BUG_ON(!cls);
_cas_cls_rule_destroy(cls, r);
}
/* Create rule from text description. @rule might be overwritten */
static struct cas_cls_rule *_cas_cls_rule_create(struct cas_classifier *cls,
ocf_part_id_t part_id, char *rule)
{
struct cas_cls_rule *r;
int result;
if (part_id == 0 || rule[0] == '\0')
return NULL;
r = kmalloc(sizeof(*r), GFP_KERNEL);
if (!r)
return ERR_PTR(-ENOMEM);
r->part_id = part_id;
INIT_LIST_HEAD(&r->conditions);
result = _cas_cls_parse_conditions(cls, r, rule);
if (result) {
_cas_cls_rule_destroy(cls, r);
return ERR_PTR(result);
}
return r;
}
/* Update rule associated with given io class */
void cas_cls_rule_apply(ocf_cache_t cache,
ocf_part_id_t part_id, struct cas_cls_rule *new)
{
struct cas_classifier *cls;
struct cas_cls_rule *old = NULL, *elem;
struct list_head *item, *_n;
cls = cas_get_classifier(cache);
BUG_ON(!cls);
write_lock(&cls->lock);
/* Walk through list of rules in reverse order (tail to head), visiting
* rules from high to low part_id */
list_for_each_prev_safe(item, _n, &cls->rules) {
elem = list_entry(item, struct cas_cls_rule, list);
if (elem->part_id == part_id) {
old = elem;
list_del(item);
}
if (elem->part_id < part_id)
break;
}
/* Insert new element past loop cursor */
if (new)
list_add(&new->list, item);
write_unlock(&cls->lock);
_cas_cls_rule_destroy(cls, old);
if (old)
CAS_CLS_DEBUG_MSG("Removed rule for class %d\n", part_id);
if (new)
CAS_CLS_DEBUG_MSG("New rule for for class %d\n", part_id);
return;
}
/*
* Translate classification rule error from linux error code to CAS error code.
* Internal classifier functions use PTR_ERR / ERR_PTR macros to propagate
* error in pointers. These macros do not work well with CAS error codes, so
* this function is used to form fine-grained CAS error code when returning
* from classifier management function.
*/
static int _cas_cls_rule_err_to_cass_err(int err)
{
switch (err) {
case -ENOENT:
return KCAS_ERR_CLS_RULE_UNKNOWN_CONDITION;
case -EINVAL:
return KCAS_ERR_CLS_RULE_INVALID_SYNTAX;
default:
return err;
}
}
/* Create and apply classification rule for given class id */
static int _cas_cls_rule_init(ocf_cache_t cache, ocf_part_id_t part_id)
{
struct cas_classifier *cls;
struct ocf_io_class_info *info;
struct cas_cls_rule *r;
int result;
cls = cas_get_classifier(cache);
if (!cls)
return -EINVAL;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
result = ocf_cache_io_class_get_info(cache, part_id, info);
if (result) {
if (result == -OCF_ERR_IO_CLASS_NOT_EXIST)
result = 0;
goto exit;
}
if (strnlen(info->name, sizeof(info->name)) == sizeof(info->name)) {
CAS_CLS_MSG(KERN_ERR, "IO class name not null terminated\n");
result = -EINVAL;
goto exit;
}
r = _cas_cls_rule_create(cls, part_id, info->name);
if (IS_ERR(r)) {
result = _cas_cls_rule_err_to_cass_err(PTR_ERR(r));
goto exit;
}
cas_cls_rule_apply(cache, part_id, r);
exit:
kfree(info);
return result;
}
/* Create classification rule from text description */
int cas_cls_rule_create(ocf_cache_t cache,
ocf_part_id_t part_id, const char* rule,
struct cas_cls_rule **cls_rule)
{
struct cas_cls_rule *r = NULL;
struct cas_classifier *cls;
char *_rule;
int ret;
if (!cls_rule)
return -EINVAL;
cls = cas_get_classifier(cache);
if (!cls)
return -EINVAL;
if (strnlen(rule, OCF_IO_CLASS_NAME_MAX) == OCF_IO_CLASS_NAME_MAX) {
CAS_CLS_MSG(KERN_ERR, "IO class name not null terminated\n");
return -EINVAL;
}
/* Make description copy as _cas_cls_rule_create might modify input
* string */
_rule = kstrdup(rule, GFP_KERNEL);
if (!_rule)
return -ENOMEM;
r = _cas_cls_rule_create(cls, part_id, _rule);
if (IS_ERR(r))
ret = _cas_cls_rule_err_to_cass_err(PTR_ERR(r));
else {
CAS_CLS_DEBUG_MSG("Created rule: %s => %d\n", rule, part_id);
*cls_rule = r;
ret = 0;
}
kfree(_rule);
return ret;
}
/* Deinitialize classifier and remove rules */
void cas_cls_deinit(ocf_cache_t cache)
{
struct cas_classifier *cls;
struct list_head *item, *n;
struct cas_cls_rule *r = NULL;
cls = cas_get_classifier(cache);
ENV_BUG_ON(!cls);
list_for_each_safe(item, n, &cls->rules) {
r = list_entry(item, struct cas_cls_rule, list);
list_del(item);
_cas_cls_rule_destroy(cls, r);
}
destroy_workqueue(cls->wq);
kfree(cls);
cas_set_classifier(cache, NULL);
CAS_CLS_MSG(KERN_INFO, "Deinitialized IO classifier\n");
return;
}
/* Initialize classifier context */
static struct cas_classifier *_cas_cls_init(void)
{
struct cas_classifier *cls;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&cls->rules);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
cls->wq = alloc_workqueue("kcas_clsd", WQ_UNBOUND | WQ_FREEZABLE, 1);
#else
cls->wq = create_singlethread_workqueue("kcas_clsd");
#endif
if (!cls->wq) {
kfree(cls);
return ERR_PTR(-ENOMEM);
}
rwlock_init(&cls->lock);
CAS_CLS_MSG(KERN_INFO, "Initialized IO classifier\n");
return cls;
}
/* Initialize classifier and create rules for existing I/O classes */
int cas_cls_init(ocf_cache_t cache)
{
struct cas_classifier *cls;
unsigned result = 0;
unsigned i;
cls = _cas_cls_init();
if (IS_ERR(cls))
return PTR_ERR(cls);
cas_set_classifier(cache, cls);
/* Update rules for all I/O classes except 0 - this is default for all
* unclassified I/O */
for (i = 1; i < OCF_IO_CLASS_MAX; i++) {
result = _cas_cls_rule_init(cache, i);
if (result)
break;
}
if (result)
cas_cls_deinit(cache);
return result;
}
/* Determine whether io matches rule */
static cas_cls_eval_t cas_cls_process_rule(struct cas_classifier *cls,
struct cas_cls_rule *r, struct cas_cls_io *io,
ocf_part_id_t *part_id)
{
struct list_head *item;
struct cas_cls_condition *c;
cas_cls_eval_t ret = cas_cls_eval_no, rr;
CAS_CLS_DEBUG_TRACE(" Processing rule for class %d\n", r->part_id);
list_for_each(item, &r->conditions) {
c = list_entry(item, struct cas_cls_condition, list);
if (!ret.yes && c->l_op == cas_cls_logical_and)
break;
rr = c->handler->test(cls, c, io, *part_id);
CAS_CLS_DEBUG_TRACE(" Processing condition %s => %d, stop:%d "
"(l_op: %d)\n", c->handler->token, rr.yes,
rr.stop, (int)c->l_op);
ret.yes = (c->l_op == cas_cls_logical_and) ?
rr.yes && ret.yes :
rr.yes || ret.yes;
ret.stop = rr.stop;
if (ret.stop)
break;
}
CAS_CLS_DEBUG_TRACE(" Rule %d output => %d stop: %d\n", r->part_id,
ret.yes, ret.stop);
return ret;
}
/* Fill in cas_cls_io for given bio - it is assumed that ctx is
* zeroed upon entry */
static void _cas_cls_get_bio_context(struct bio *bio,
struct cas_cls_io *ctx)
{
struct page *page = NULL;
if (!bio)
return;
ctx->bio = bio;
if (!CAS_SEGMENT_BVEC(bio_iovec(bio)))
return;
page = bio_page(bio);
if (!page)
return;
ctx->page = page;
if (PageAnon(page))
return;
if (PageSlab(page) || PageCompound(page))
return;
if (!page->mapping)
return;
ctx->inode = page->mapping->host;
return;
}
/* Determine I/O class for bio */
ocf_part_id_t cas_cls_classify(ocf_cache_t cache, struct bio *bio)
{
struct cas_classifier *cls;
struct cas_cls_io io = {};
struct list_head *item;
struct cas_cls_rule *r;
ocf_part_id_t part_id = 0;
cas_cls_eval_t ret;
cls = cas_get_classifier(cache);
ENV_BUG_ON(!cls);
_cas_cls_get_bio_context(bio, &io);
read_lock(&cls->lock);
CAS_CLS_DEBUG_TRACE("%s\n", "Starting processing");
list_for_each(item, &cls->rules) {
r = list_entry(item, struct cas_cls_rule, list);
ret = cas_cls_process_rule(cls, r, &io, &part_id);
if (ret.yes)
part_id = r->part_id;
if (ret.stop)
break;
}
read_unlock(&cls->lock);
return part_id;
}