| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2020 Oracle Corporation |
| * |
| * Module Author: Mike Christie |
| */ |
| #include "dm-path-selector.h" |
| |
| #include <linux/device-mapper.h> |
| #include <linux/module.h> |
| |
| #define DM_MSG_PREFIX "multipath io-affinity" |
| |
| struct path_info { |
| struct dm_path *path; |
| cpumask_var_t cpumask; |
| refcount_t refcount; |
| bool failed; |
| }; |
| |
| struct selector { |
| struct path_info **path_map; |
| cpumask_var_t path_mask; |
| atomic_t map_misses; |
| }; |
| |
| static void ioa_free_path(struct selector *s, unsigned int cpu) |
| { |
| struct path_info *pi = s->path_map[cpu]; |
| |
| if (!pi) |
| return; |
| |
| if (refcount_dec_and_test(&pi->refcount)) { |
| cpumask_clear_cpu(cpu, s->path_mask); |
| free_cpumask_var(pi->cpumask); |
| kfree(pi); |
| |
| s->path_map[cpu] = NULL; |
| } |
| } |
| |
| static int ioa_add_path(struct path_selector *ps, struct dm_path *path, |
| int argc, char **argv, char **error) |
| { |
| struct selector *s = ps->context; |
| struct path_info *pi = NULL; |
| unsigned int cpu; |
| int ret; |
| |
| if (argc != 1) { |
| *error = "io-affinity ps: invalid number of arguments"; |
| return -EINVAL; |
| } |
| |
| pi = kzalloc(sizeof(*pi), GFP_KERNEL); |
| if (!pi) { |
| *error = "io-affinity ps: Error allocating path context"; |
| return -ENOMEM; |
| } |
| |
| pi->path = path; |
| path->pscontext = pi; |
| refcount_set(&pi->refcount, 1); |
| |
| if (!zalloc_cpumask_var(&pi->cpumask, GFP_KERNEL)) { |
| *error = "io-affinity ps: Error allocating cpumask context"; |
| ret = -ENOMEM; |
| goto free_pi; |
| } |
| |
| ret = cpumask_parse(argv[0], pi->cpumask); |
| if (ret) { |
| *error = "io-affinity ps: invalid cpumask"; |
| ret = -EINVAL; |
| goto free_mask; |
| } |
| |
| for_each_cpu(cpu, pi->cpumask) { |
| if (cpu >= nr_cpu_ids) { |
| DMWARN_LIMIT("Ignoring mapping for CPU %u. Max CPU is %u", |
| cpu, nr_cpu_ids); |
| break; |
| } |
| |
| if (s->path_map[cpu]) { |
| DMWARN("CPU mapping for %u exists. Ignoring.", cpu); |
| continue; |
| } |
| |
| cpumask_set_cpu(cpu, s->path_mask); |
| s->path_map[cpu] = pi; |
| refcount_inc(&pi->refcount); |
| } |
| |
| if (refcount_dec_and_test(&pi->refcount)) { |
| *error = "io-affinity ps: No new/valid CPU mapping found"; |
| ret = -EINVAL; |
| goto free_mask; |
| } |
| |
| return 0; |
| |
| free_mask: |
| free_cpumask_var(pi->cpumask); |
| free_pi: |
| kfree(pi); |
| return ret; |
| } |
| |
| static int ioa_create(struct path_selector *ps, unsigned int argc, char **argv) |
| { |
| struct selector *s; |
| |
| s = kmalloc(sizeof(*s), GFP_KERNEL); |
| if (!s) |
| return -ENOMEM; |
| |
| s->path_map = kzalloc(nr_cpu_ids * sizeof(struct path_info *), |
| GFP_KERNEL); |
| if (!s->path_map) |
| goto free_selector; |
| |
| if (!zalloc_cpumask_var(&s->path_mask, GFP_KERNEL)) |
| goto free_map; |
| |
| atomic_set(&s->map_misses, 0); |
| ps->context = s; |
| return 0; |
| |
| free_map: |
| kfree(s->path_map); |
| free_selector: |
| kfree(s); |
| return -ENOMEM; |
| } |
| |
| static void ioa_destroy(struct path_selector *ps) |
| { |
| struct selector *s = ps->context; |
| unsigned int cpu; |
| |
| for_each_cpu(cpu, s->path_mask) |
| ioa_free_path(s, cpu); |
| |
| free_cpumask_var(s->path_mask); |
| kfree(s->path_map); |
| kfree(s); |
| |
| ps->context = NULL; |
| } |
| |
| static int ioa_status(struct path_selector *ps, struct dm_path *path, |
| status_type_t type, char *result, unsigned int maxlen) |
| { |
| struct selector *s = ps->context; |
| struct path_info *pi; |
| int sz = 0; |
| |
| if (!path) { |
| DMEMIT("0 "); |
| return sz; |
| } |
| |
| switch(type) { |
| case STATUSTYPE_INFO: |
| DMEMIT("%d ", atomic_read(&s->map_misses)); |
| break; |
| case STATUSTYPE_TABLE: |
| pi = path->pscontext; |
| DMEMIT("%*pb ", cpumask_pr_args(pi->cpumask)); |
| break; |
| case STATUSTYPE_IMA: |
| *result = '\0'; |
| break; |
| } |
| |
| return sz; |
| } |
| |
| static void ioa_fail_path(struct path_selector *ps, struct dm_path *p) |
| { |
| struct path_info *pi = p->pscontext; |
| |
| pi->failed = true; |
| } |
| |
| static int ioa_reinstate_path(struct path_selector *ps, struct dm_path *p) |
| { |
| struct path_info *pi = p->pscontext; |
| |
| pi->failed = false; |
| return 0; |
| } |
| |
| static struct dm_path *ioa_select_path(struct path_selector *ps, |
| size_t nr_bytes) |
| { |
| unsigned int cpu, node; |
| struct selector *s = ps->context; |
| const struct cpumask *cpumask; |
| struct path_info *pi; |
| int i; |
| |
| cpu = get_cpu(); |
| |
| pi = s->path_map[cpu]; |
| if (pi && !pi->failed) |
| goto done; |
| |
| /* |
| * Perf is not optimal, but we at least try the local node then just |
| * try not to fail. |
| */ |
| if (!pi) |
| atomic_inc(&s->map_misses); |
| |
| node = cpu_to_node(cpu); |
| cpumask = cpumask_of_node(node); |
| for_each_cpu(i, cpumask) { |
| pi = s->path_map[i]; |
| if (pi && !pi->failed) |
| goto done; |
| } |
| |
| for_each_cpu(i, s->path_mask) { |
| pi = s->path_map[i]; |
| if (pi && !pi->failed) |
| goto done; |
| } |
| pi = NULL; |
| |
| done: |
| put_cpu(); |
| return pi ? pi->path : NULL; |
| } |
| |
| static struct path_selector_type ioa_ps = { |
| .name = "io-affinity", |
| .module = THIS_MODULE, |
| .table_args = 1, |
| .info_args = 1, |
| .create = ioa_create, |
| .destroy = ioa_destroy, |
| .status = ioa_status, |
| .add_path = ioa_add_path, |
| .fail_path = ioa_fail_path, |
| .reinstate_path = ioa_reinstate_path, |
| .select_path = ioa_select_path, |
| }; |
| |
| static int __init dm_ioa_init(void) |
| { |
| int ret = dm_register_path_selector(&ioa_ps); |
| |
| if (ret < 0) |
| DMERR("register failed %d", ret); |
| return ret; |
| } |
| |
| static void __exit dm_ioa_exit(void) |
| { |
| int ret = dm_unregister_path_selector(&ioa_ps); |
| |
| if (ret < 0) |
| DMERR("unregister failed %d", ret); |
| } |
| |
| module_init(dm_ioa_init); |
| module_exit(dm_ioa_exit); |
| |
| MODULE_DESCRIPTION(DM_NAME " multipath path selector that selects paths based on the CPU IO is being executed on"); |
| MODULE_AUTHOR("Mike Christie <michael.christie@oracle.com>"); |
| MODULE_LICENSE("GPL"); |