968 lines
22 KiB
C
968 lines
22 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;
|
|
}
|
|
|
|
/* 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;
|
|
ALIAS_NODE_TYPE *pos; /* alias list current element */
|
|
|
|
if (DENTRY_LIST_EMPTY(&inode->i_dentry))
|
|
return NULL;
|
|
|
|
spin_lock(&inode->i_lock);
|
|
|
|
if (S_ISDIR(inode->i_mode))
|
|
goto unlock;
|
|
|
|
INODE_FOR_EACH_DENTRY(pos, &inode->i_dentry) {
|
|
iter = ALIAS_NODE_TO_DENTRY(pos);
|
|
spin_lock(&iter->d_lock);
|
|
if (!d_unhashed(iter))
|
|
d = iter;
|
|
spin_unlock(&d->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 },
|
|
{ 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(ocf_cache_t cache)
|
|
{
|
|
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(cache);
|
|
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 (!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;
|
|
}
|
|
|