1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2003 Sistina Software. |
4 | * Copyright (C) 2004-2005 Red Hat, Inc. All rights reserved. |
5 | * |
6 | * Module Author: Heinz Mauelshagen |
7 | * |
8 | * This file is released under the GPL. |
9 | * |
10 | * Round-robin path selector. |
11 | */ |
12 | |
13 | #include <linux/device-mapper.h> |
14 | |
15 | #include "dm-path-selector.h" |
16 | |
17 | #include <linux/slab.h> |
18 | #include <linux/module.h> |
19 | |
20 | #define DM_MSG_PREFIX "multipath round-robin" |
21 | #define RR_MIN_IO 1 |
22 | #define RR_VERSION "1.2.0" |
23 | |
24 | /* |
25 | *--------------------------------------------------------------- |
26 | * Path-handling code, paths are held in lists |
27 | *--------------------------------------------------------------- |
28 | */ |
29 | struct path_info { |
30 | struct list_head list; |
31 | struct dm_path *path; |
32 | unsigned int repeat_count; |
33 | }; |
34 | |
35 | static void free_paths(struct list_head *paths) |
36 | { |
37 | struct path_info *pi, *next; |
38 | |
39 | list_for_each_entry_safe(pi, next, paths, list) { |
40 | list_del(entry: &pi->list); |
41 | kfree(objp: pi); |
42 | } |
43 | } |
44 | |
45 | /* |
46 | *--------------------------------------------------------------- |
47 | * Round-robin selector |
48 | *--------------------------------------------------------------- |
49 | */ |
50 | struct selector { |
51 | struct list_head valid_paths; |
52 | struct list_head invalid_paths; |
53 | spinlock_t lock; |
54 | }; |
55 | |
56 | static struct selector *alloc_selector(void) |
57 | { |
58 | struct selector *s = kmalloc(size: sizeof(*s), GFP_KERNEL); |
59 | |
60 | if (s) { |
61 | INIT_LIST_HEAD(list: &s->valid_paths); |
62 | INIT_LIST_HEAD(list: &s->invalid_paths); |
63 | spin_lock_init(&s->lock); |
64 | } |
65 | |
66 | return s; |
67 | } |
68 | |
69 | static int rr_create(struct path_selector *ps, unsigned int argc, char **argv) |
70 | { |
71 | struct selector *s; |
72 | |
73 | s = alloc_selector(); |
74 | if (!s) |
75 | return -ENOMEM; |
76 | |
77 | ps->context = s; |
78 | return 0; |
79 | } |
80 | |
81 | static void rr_destroy(struct path_selector *ps) |
82 | { |
83 | struct selector *s = ps->context; |
84 | |
85 | free_paths(paths: &s->valid_paths); |
86 | free_paths(paths: &s->invalid_paths); |
87 | kfree(objp: s); |
88 | ps->context = NULL; |
89 | } |
90 | |
91 | static int rr_status(struct path_selector *ps, struct dm_path *path, |
92 | status_type_t type, char *result, unsigned int maxlen) |
93 | { |
94 | struct path_info *pi; |
95 | int sz = 0; |
96 | |
97 | if (!path) |
98 | DMEMIT("0 " ); |
99 | else { |
100 | switch (type) { |
101 | case STATUSTYPE_INFO: |
102 | break; |
103 | case STATUSTYPE_TABLE: |
104 | pi = path->pscontext; |
105 | DMEMIT("%u " , pi->repeat_count); |
106 | break; |
107 | |
108 | case STATUSTYPE_IMA: |
109 | *result = '\0'; |
110 | break; |
111 | } |
112 | } |
113 | |
114 | return sz; |
115 | } |
116 | |
117 | /* |
118 | * Called during initialisation to register each path with an |
119 | * optional repeat_count. |
120 | */ |
121 | static int rr_add_path(struct path_selector *ps, struct dm_path *path, |
122 | int argc, char **argv, char **error) |
123 | { |
124 | struct selector *s = ps->context; |
125 | struct path_info *pi; |
126 | unsigned int repeat_count = RR_MIN_IO; |
127 | char dummy; |
128 | unsigned long flags; |
129 | |
130 | if (argc > 1) { |
131 | *error = "round-robin ps: incorrect number of arguments" ; |
132 | return -EINVAL; |
133 | } |
134 | |
135 | /* First path argument is number of I/Os before switching path */ |
136 | if ((argc == 1) && (sscanf(argv[0], "%u%c" , &repeat_count, &dummy) != 1)) { |
137 | *error = "round-robin ps: invalid repeat count" ; |
138 | return -EINVAL; |
139 | } |
140 | |
141 | if (repeat_count > 1) { |
142 | DMWARN_LIMIT("repeat_count > 1 is deprecated, using 1 instead" ); |
143 | repeat_count = 1; |
144 | } |
145 | |
146 | /* allocate the path */ |
147 | pi = kmalloc(size: sizeof(*pi), GFP_KERNEL); |
148 | if (!pi) { |
149 | *error = "round-robin ps: Error allocating path context" ; |
150 | return -ENOMEM; |
151 | } |
152 | |
153 | pi->path = path; |
154 | pi->repeat_count = repeat_count; |
155 | |
156 | path->pscontext = pi; |
157 | |
158 | spin_lock_irqsave(&s->lock, flags); |
159 | list_add_tail(new: &pi->list, head: &s->valid_paths); |
160 | spin_unlock_irqrestore(lock: &s->lock, flags); |
161 | |
162 | return 0; |
163 | } |
164 | |
165 | static void rr_fail_path(struct path_selector *ps, struct dm_path *p) |
166 | { |
167 | unsigned long flags; |
168 | struct selector *s = ps->context; |
169 | struct path_info *pi = p->pscontext; |
170 | |
171 | spin_lock_irqsave(&s->lock, flags); |
172 | list_move(list: &pi->list, head: &s->invalid_paths); |
173 | spin_unlock_irqrestore(lock: &s->lock, flags); |
174 | } |
175 | |
176 | static int rr_reinstate_path(struct path_selector *ps, struct dm_path *p) |
177 | { |
178 | unsigned long flags; |
179 | struct selector *s = ps->context; |
180 | struct path_info *pi = p->pscontext; |
181 | |
182 | spin_lock_irqsave(&s->lock, flags); |
183 | list_move(list: &pi->list, head: &s->valid_paths); |
184 | spin_unlock_irqrestore(lock: &s->lock, flags); |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static struct dm_path *rr_select_path(struct path_selector *ps, size_t nr_bytes) |
190 | { |
191 | unsigned long flags; |
192 | struct selector *s = ps->context; |
193 | struct path_info *pi = NULL; |
194 | |
195 | spin_lock_irqsave(&s->lock, flags); |
196 | if (!list_empty(head: &s->valid_paths)) { |
197 | pi = list_entry(s->valid_paths.next, struct path_info, list); |
198 | list_move_tail(list: &pi->list, head: &s->valid_paths); |
199 | } |
200 | spin_unlock_irqrestore(lock: &s->lock, flags); |
201 | |
202 | return pi ? pi->path : NULL; |
203 | } |
204 | |
205 | static struct path_selector_type rr_ps = { |
206 | .name = "round-robin" , |
207 | .module = THIS_MODULE, |
208 | .table_args = 1, |
209 | .info_args = 0, |
210 | .create = rr_create, |
211 | .destroy = rr_destroy, |
212 | .status = rr_status, |
213 | .add_path = rr_add_path, |
214 | .fail_path = rr_fail_path, |
215 | .reinstate_path = rr_reinstate_path, |
216 | .select_path = rr_select_path, |
217 | }; |
218 | |
219 | static int __init dm_rr_init(void) |
220 | { |
221 | int r = dm_register_path_selector(type: &rr_ps); |
222 | |
223 | if (r < 0) |
224 | DMERR("register failed %d" , r); |
225 | |
226 | DMINFO("version " RR_VERSION " loaded" ); |
227 | |
228 | return r; |
229 | } |
230 | |
231 | static void __exit dm_rr_exit(void) |
232 | { |
233 | int r = dm_unregister_path_selector(type: &rr_ps); |
234 | |
235 | if (r < 0) |
236 | DMERR("unregister failed %d" , r); |
237 | } |
238 | |
239 | module_init(dm_rr_init); |
240 | module_exit(dm_rr_exit); |
241 | |
242 | MODULE_DESCRIPTION(DM_NAME " round-robin multipath path selector" ); |
243 | MODULE_AUTHOR("Sistina Software <dm-devel@redhat.com>" ); |
244 | MODULE_LICENSE("GPL" ); |
245 | |