usnic_uiom_interval_tree.c (7412B)
1/* 2 * Copyright (c) 2014, Cisco Systems, Inc. All rights reserved. 3 * 4 * This software is available to you under a choice of one of two 5 * licenses. You may choose to be licensed under the terms of the GNU 6 * General Public License (GPL) Version 2, available from the file 7 * COPYING in the main directory of this source tree, or the 8 * BSD license below: 9 * 10 * Redistribution and use in source and binary forms, with or 11 * without modification, are permitted provided that the following 12 * conditions are met: 13 * 14 * - Redistributions of source code must retain the above 15 * copyright notice, this list of conditions and the following 16 * disclaimer. 17 * 18 * - Redistributions in binary form must reproduce the above 19 * copyright notice, this list of conditions and the following 20 * disclaimer in the documentation and/or other materials 21 * provided with the distribution. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 27 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 28 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 29 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 * SOFTWARE. 31 * 32 */ 33 34#include <linux/init.h> 35#include <linux/list.h> 36#include <linux/slab.h> 37#include <linux/list_sort.h> 38 39#include <linux/interval_tree_generic.h> 40#include "usnic_uiom_interval_tree.h" 41 42#define START(node) ((node)->start) 43#define LAST(node) ((node)->last) 44 45#define MAKE_NODE(node, start, end, ref_cnt, flags, err, err_out) \ 46 do { \ 47 node = usnic_uiom_interval_node_alloc(start, \ 48 end, ref_cnt, flags); \ 49 if (!node) { \ 50 err = -ENOMEM; \ 51 goto err_out; \ 52 } \ 53 } while (0) 54 55#define MARK_FOR_ADD(node, list) (list_add_tail(&node->link, list)) 56 57#define MAKE_NODE_AND_APPEND(node, start, end, ref_cnt, flags, err, \ 58 err_out, list) \ 59 do { \ 60 MAKE_NODE(node, start, end, \ 61 ref_cnt, flags, err, \ 62 err_out); \ 63 MARK_FOR_ADD(node, list); \ 64 } while (0) 65 66#define FLAGS_EQUAL(flags1, flags2, mask) \ 67 (((flags1) & (mask)) == ((flags2) & (mask))) 68 69static struct usnic_uiom_interval_node* 70usnic_uiom_interval_node_alloc(long int start, long int last, int ref_cnt, 71 int flags) 72{ 73 struct usnic_uiom_interval_node *interval = kzalloc(sizeof(*interval), 74 GFP_ATOMIC); 75 if (!interval) 76 return NULL; 77 78 interval->start = start; 79 interval->last = last; 80 interval->flags = flags; 81 interval->ref_cnt = ref_cnt; 82 83 return interval; 84} 85 86static int interval_cmp(void *priv, const struct list_head *a, 87 const struct list_head *b) 88{ 89 struct usnic_uiom_interval_node *node_a, *node_b; 90 91 node_a = list_entry(a, struct usnic_uiom_interval_node, link); 92 node_b = list_entry(b, struct usnic_uiom_interval_node, link); 93 94 /* long to int */ 95 if (node_a->start < node_b->start) 96 return -1; 97 else if (node_a->start > node_b->start) 98 return 1; 99 100 return 0; 101} 102 103static void 104find_intervals_intersection_sorted(struct rb_root_cached *root, 105 unsigned long start, unsigned long last, 106 struct list_head *list) 107{ 108 struct usnic_uiom_interval_node *node; 109 110 INIT_LIST_HEAD(list); 111 112 for (node = usnic_uiom_interval_tree_iter_first(root, start, last); 113 node; 114 node = usnic_uiom_interval_tree_iter_next(node, start, last)) 115 list_add_tail(&node->link, list); 116 117 list_sort(NULL, list, interval_cmp); 118} 119 120int usnic_uiom_get_intervals_diff(unsigned long start, unsigned long last, 121 int flags, int flag_mask, 122 struct rb_root_cached *root, 123 struct list_head *diff_set) 124{ 125 struct usnic_uiom_interval_node *interval, *tmp; 126 int err = 0; 127 long int pivot = start; 128 LIST_HEAD(intersection_set); 129 130 INIT_LIST_HEAD(diff_set); 131 132 find_intervals_intersection_sorted(root, start, last, 133 &intersection_set); 134 135 list_for_each_entry(interval, &intersection_set, link) { 136 if (pivot < interval->start) { 137 MAKE_NODE_AND_APPEND(tmp, pivot, interval->start - 1, 138 1, flags, err, err_out, 139 diff_set); 140 pivot = interval->start; 141 } 142 143 /* 144 * Invariant: Set [start, pivot] is either in diff_set or root, 145 * but not in both. 146 */ 147 148 if (pivot > interval->last) { 149 continue; 150 } else if (pivot <= interval->last && 151 FLAGS_EQUAL(interval->flags, flags, 152 flag_mask)) { 153 pivot = interval->last + 1; 154 } 155 } 156 157 if (pivot <= last) 158 MAKE_NODE_AND_APPEND(tmp, pivot, last, 1, flags, err, err_out, 159 diff_set); 160 161 return 0; 162 163err_out: 164 list_for_each_entry_safe(interval, tmp, diff_set, link) { 165 list_del(&interval->link); 166 kfree(interval); 167 } 168 169 return err; 170} 171 172void usnic_uiom_put_interval_set(struct list_head *intervals) 173{ 174 struct usnic_uiom_interval_node *interval, *tmp; 175 list_for_each_entry_safe(interval, tmp, intervals, link) 176 kfree(interval); 177} 178 179int usnic_uiom_insert_interval(struct rb_root_cached *root, unsigned long start, 180 unsigned long last, int flags) 181{ 182 struct usnic_uiom_interval_node *interval, *tmp; 183 unsigned long istart, ilast; 184 int iref_cnt, iflags; 185 unsigned long lpivot = start; 186 int err = 0; 187 LIST_HEAD(to_add); 188 LIST_HEAD(intersection_set); 189 190 find_intervals_intersection_sorted(root, start, last, 191 &intersection_set); 192 193 list_for_each_entry(interval, &intersection_set, link) { 194 /* 195 * Invariant - lpivot is the left edge of next interval to be 196 * inserted 197 */ 198 istart = interval->start; 199 ilast = interval->last; 200 iref_cnt = interval->ref_cnt; 201 iflags = interval->flags; 202 203 if (istart < lpivot) { 204 MAKE_NODE_AND_APPEND(tmp, istart, lpivot - 1, iref_cnt, 205 iflags, err, err_out, &to_add); 206 } else if (istart > lpivot) { 207 MAKE_NODE_AND_APPEND(tmp, lpivot, istart - 1, 1, flags, 208 err, err_out, &to_add); 209 lpivot = istart; 210 } else { 211 lpivot = istart; 212 } 213 214 if (ilast > last) { 215 MAKE_NODE_AND_APPEND(tmp, lpivot, last, iref_cnt + 1, 216 iflags | flags, err, err_out, 217 &to_add); 218 MAKE_NODE_AND_APPEND(tmp, last + 1, ilast, iref_cnt, 219 iflags, err, err_out, &to_add); 220 } else { 221 MAKE_NODE_AND_APPEND(tmp, lpivot, ilast, iref_cnt + 1, 222 iflags | flags, err, err_out, 223 &to_add); 224 } 225 226 lpivot = ilast + 1; 227 } 228 229 if (lpivot <= last) 230 MAKE_NODE_AND_APPEND(tmp, lpivot, last, 1, flags, err, err_out, 231 &to_add); 232 233 list_for_each_entry_safe(interval, tmp, &intersection_set, link) { 234 usnic_uiom_interval_tree_remove(interval, root); 235 kfree(interval); 236 } 237 238 list_for_each_entry(interval, &to_add, link) 239 usnic_uiom_interval_tree_insert(interval, root); 240 241 return 0; 242 243err_out: 244 list_for_each_entry_safe(interval, tmp, &to_add, link) 245 kfree(interval); 246 247 return err; 248} 249 250void usnic_uiom_remove_interval(struct rb_root_cached *root, 251 unsigned long start, unsigned long last, 252 struct list_head *removed) 253{ 254 struct usnic_uiom_interval_node *interval; 255 256 for (interval = usnic_uiom_interval_tree_iter_first(root, start, last); 257 interval; 258 interval = usnic_uiom_interval_tree_iter_next(interval, 259 start, 260 last)) { 261 if (--interval->ref_cnt == 0) 262 list_add_tail(&interval->link, removed); 263 } 264 265 list_for_each_entry(interval, removed, link) 266 usnic_uiom_interval_tree_remove(interval, root); 267} 268 269INTERVAL_TREE_DEFINE(struct usnic_uiom_interval_node, rb, 270 unsigned long, __subtree_last, 271 START, LAST, , usnic_uiom_interval_tree)