Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 Sergey Senozhatsky. |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU General Public License |
| 6 | * as published by the Free Software Foundation; either version |
| 7 | * 2 of the License, or (at your option) any later version. |
| 8 | */ |
| 9 | |
| 10 | #include <linux/kernel.h> |
| 11 | #include <linux/string.h> |
Sergey Senozhatsky | fcfa8d9 | 2014-04-07 15:38:20 -0700 | [diff] [blame] | 12 | #include <linux/err.h> |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 13 | #include <linux/slab.h> |
| 14 | #include <linux/wait.h> |
| 15 | #include <linux/sched.h> |
| 16 | |
| 17 | #include "zcomp.h" |
| 18 | #include "zcomp_lzo.h" |
Sergey Senozhatsky | 6e76668 | 2014-04-07 15:38:18 -0700 | [diff] [blame] | 19 | #ifdef CONFIG_ZRAM_LZ4_COMPRESS |
| 20 | #include "zcomp_lz4.h" |
| 21 | #endif |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 22 | |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 23 | /* |
| 24 | * single zcomp_strm backend |
| 25 | */ |
| 26 | struct zcomp_strm_single { |
| 27 | struct mutex strm_lock; |
| 28 | struct zcomp_strm *zstrm; |
| 29 | }; |
| 30 | |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 31 | /* |
| 32 | * multi zcomp_strm backend |
| 33 | */ |
| 34 | struct zcomp_strm_multi { |
| 35 | /* protect strm list */ |
| 36 | spinlock_t strm_lock; |
| 37 | /* max possible number of zstrm streams */ |
| 38 | int max_strm; |
| 39 | /* number of available zstrm streams */ |
| 40 | int avail_strm; |
| 41 | /* list of available strms */ |
| 42 | struct list_head idle_strm; |
| 43 | wait_queue_head_t strm_wait; |
| 44 | }; |
| 45 | |
Sergey Senozhatsky | e46b8a0 | 2014-04-07 15:38:17 -0700 | [diff] [blame] | 46 | static struct zcomp_backend *backends[] = { |
| 47 | &zcomp_lzo, |
Sergey Senozhatsky | 6e76668 | 2014-04-07 15:38:18 -0700 | [diff] [blame] | 48 | #ifdef CONFIG_ZRAM_LZ4_COMPRESS |
| 49 | &zcomp_lz4, |
| 50 | #endif |
Sergey Senozhatsky | e46b8a0 | 2014-04-07 15:38:17 -0700 | [diff] [blame] | 51 | NULL |
| 52 | }; |
| 53 | |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 54 | static struct zcomp_backend *find_backend(const char *compress) |
| 55 | { |
Sergey Senozhatsky | e46b8a0 | 2014-04-07 15:38:17 -0700 | [diff] [blame] | 56 | int i = 0; |
| 57 | while (backends[i]) { |
| 58 | if (sysfs_streq(compress, backends[i]->name)) |
| 59 | break; |
| 60 | i++; |
| 61 | } |
| 62 | return backends[i]; |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | static void zcomp_strm_free(struct zcomp *comp, struct zcomp_strm *zstrm) |
| 66 | { |
| 67 | if (zstrm->private) |
| 68 | comp->backend->destroy(zstrm->private); |
| 69 | free_pages((unsigned long)zstrm->buffer, 1); |
| 70 | kfree(zstrm); |
| 71 | } |
| 72 | |
| 73 | /* |
| 74 | * allocate new zcomp_strm structure with ->private initialized by |
| 75 | * backend, return NULL on error |
| 76 | */ |
Minchan Kim | 75d8947 | 2016-01-14 15:22:32 -0800 | [diff] [blame] | 77 | static struct zcomp_strm *zcomp_strm_alloc(struct zcomp *comp, gfp_t flags) |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 78 | { |
Minchan Kim | 75d8947 | 2016-01-14 15:22:32 -0800 | [diff] [blame] | 79 | struct zcomp_strm *zstrm = kmalloc(sizeof(*zstrm), flags); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 80 | if (!zstrm) |
| 81 | return NULL; |
| 82 | |
Minchan Kim | 75d8947 | 2016-01-14 15:22:32 -0800 | [diff] [blame] | 83 | zstrm->private = comp->backend->create(flags); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 84 | /* |
| 85 | * allocate 2 pages. 1 for compressed data, plus 1 extra for the |
| 86 | * case when compressed size is larger than the original one |
| 87 | */ |
Minchan Kim | 75d8947 | 2016-01-14 15:22:32 -0800 | [diff] [blame] | 88 | zstrm->buffer = (void *)__get_free_pages(flags | __GFP_ZERO, 1); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 89 | if (!zstrm->private || !zstrm->buffer) { |
| 90 | zcomp_strm_free(comp, zstrm); |
| 91 | zstrm = NULL; |
| 92 | } |
| 93 | return zstrm; |
| 94 | } |
| 95 | |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 96 | /* |
| 97 | * get idle zcomp_strm or wait until other process release |
| 98 | * (zcomp_strm_release()) one for us |
| 99 | */ |
| 100 | static struct zcomp_strm *zcomp_strm_multi_find(struct zcomp *comp) |
| 101 | { |
| 102 | struct zcomp_strm_multi *zs = comp->stream; |
| 103 | struct zcomp_strm *zstrm; |
| 104 | |
| 105 | while (1) { |
| 106 | spin_lock(&zs->strm_lock); |
| 107 | if (!list_empty(&zs->idle_strm)) { |
| 108 | zstrm = list_entry(zs->idle_strm.next, |
| 109 | struct zcomp_strm, list); |
| 110 | list_del(&zstrm->list); |
| 111 | spin_unlock(&zs->strm_lock); |
| 112 | return zstrm; |
| 113 | } |
| 114 | /* zstrm streams limit reached, wait for idle stream */ |
| 115 | if (zs->avail_strm >= zs->max_strm) { |
| 116 | spin_unlock(&zs->strm_lock); |
| 117 | wait_event(zs->strm_wait, !list_empty(&zs->idle_strm)); |
| 118 | continue; |
| 119 | } |
| 120 | /* allocate new zstrm stream */ |
| 121 | zs->avail_strm++; |
| 122 | spin_unlock(&zs->strm_lock); |
Minchan Kim | 75d8947 | 2016-01-14 15:22:32 -0800 | [diff] [blame] | 123 | /* |
| 124 | * This function can be called in swapout/fs write path |
| 125 | * so we can't use GFP_FS|IO. And it assumes we already |
| 126 | * have at least one stream in zram initialization so we |
| 127 | * don't do best effort to allocate more stream in here. |
| 128 | * A default stream will work well without further multiple |
| 129 | * streams. That's why we use NORETRY | NOWARN. |
| 130 | */ |
| 131 | zstrm = zcomp_strm_alloc(comp, GFP_NOIO | __GFP_NORETRY | |
| 132 | __GFP_NOWARN); |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 133 | if (!zstrm) { |
| 134 | spin_lock(&zs->strm_lock); |
| 135 | zs->avail_strm--; |
| 136 | spin_unlock(&zs->strm_lock); |
| 137 | wait_event(zs->strm_wait, !list_empty(&zs->idle_strm)); |
| 138 | continue; |
| 139 | } |
| 140 | break; |
| 141 | } |
| 142 | return zstrm; |
| 143 | } |
| 144 | |
| 145 | /* add stream back to idle list and wake up waiter or free the stream */ |
| 146 | static void zcomp_strm_multi_release(struct zcomp *comp, struct zcomp_strm *zstrm) |
| 147 | { |
| 148 | struct zcomp_strm_multi *zs = comp->stream; |
| 149 | |
| 150 | spin_lock(&zs->strm_lock); |
| 151 | if (zs->avail_strm <= zs->max_strm) { |
| 152 | list_add(&zstrm->list, &zs->idle_strm); |
| 153 | spin_unlock(&zs->strm_lock); |
| 154 | wake_up(&zs->strm_wait); |
| 155 | return; |
| 156 | } |
| 157 | |
| 158 | zs->avail_strm--; |
| 159 | spin_unlock(&zs->strm_lock); |
| 160 | zcomp_strm_free(comp, zstrm); |
| 161 | } |
| 162 | |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 163 | /* change max_strm limit */ |
Minchan Kim | 60a726e | 2014-04-07 15:38:21 -0700 | [diff] [blame] | 164 | static bool zcomp_strm_multi_set_max_streams(struct zcomp *comp, int num_strm) |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 165 | { |
| 166 | struct zcomp_strm_multi *zs = comp->stream; |
| 167 | struct zcomp_strm *zstrm; |
| 168 | |
| 169 | spin_lock(&zs->strm_lock); |
| 170 | zs->max_strm = num_strm; |
| 171 | /* |
| 172 | * if user has lowered the limit and there are idle streams, |
| 173 | * immediately free as much streams (and memory) as we can. |
| 174 | */ |
| 175 | while (zs->avail_strm > num_strm && !list_empty(&zs->idle_strm)) { |
| 176 | zstrm = list_entry(zs->idle_strm.next, |
| 177 | struct zcomp_strm, list); |
| 178 | list_del(&zstrm->list); |
| 179 | zcomp_strm_free(comp, zstrm); |
| 180 | zs->avail_strm--; |
| 181 | } |
| 182 | spin_unlock(&zs->strm_lock); |
Minchan Kim | 60a726e | 2014-04-07 15:38:21 -0700 | [diff] [blame] | 183 | return true; |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 184 | } |
| 185 | |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 186 | static void zcomp_strm_multi_destroy(struct zcomp *comp) |
| 187 | { |
| 188 | struct zcomp_strm_multi *zs = comp->stream; |
| 189 | struct zcomp_strm *zstrm; |
| 190 | |
| 191 | while (!list_empty(&zs->idle_strm)) { |
| 192 | zstrm = list_entry(zs->idle_strm.next, |
| 193 | struct zcomp_strm, list); |
| 194 | list_del(&zstrm->list); |
| 195 | zcomp_strm_free(comp, zstrm); |
| 196 | } |
| 197 | kfree(zs); |
| 198 | } |
| 199 | |
| 200 | static int zcomp_strm_multi_create(struct zcomp *comp, int max_strm) |
| 201 | { |
| 202 | struct zcomp_strm *zstrm; |
| 203 | struct zcomp_strm_multi *zs; |
| 204 | |
| 205 | comp->destroy = zcomp_strm_multi_destroy; |
| 206 | comp->strm_find = zcomp_strm_multi_find; |
| 207 | comp->strm_release = zcomp_strm_multi_release; |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 208 | comp->set_max_streams = zcomp_strm_multi_set_max_streams; |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 209 | zs = kmalloc(sizeof(struct zcomp_strm_multi), GFP_KERNEL); |
| 210 | if (!zs) |
| 211 | return -ENOMEM; |
| 212 | |
| 213 | comp->stream = zs; |
| 214 | spin_lock_init(&zs->strm_lock); |
| 215 | INIT_LIST_HEAD(&zs->idle_strm); |
| 216 | init_waitqueue_head(&zs->strm_wait); |
| 217 | zs->max_strm = max_strm; |
| 218 | zs->avail_strm = 1; |
| 219 | |
Minchan Kim | 75d8947 | 2016-01-14 15:22:32 -0800 | [diff] [blame] | 220 | zstrm = zcomp_strm_alloc(comp, GFP_KERNEL); |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 221 | if (!zstrm) { |
| 222 | kfree(zs); |
| 223 | return -ENOMEM; |
| 224 | } |
| 225 | list_add(&zstrm->list, &zs->idle_strm); |
| 226 | return 0; |
| 227 | } |
| 228 | |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 229 | static struct zcomp_strm *zcomp_strm_single_find(struct zcomp *comp) |
| 230 | { |
| 231 | struct zcomp_strm_single *zs = comp->stream; |
| 232 | mutex_lock(&zs->strm_lock); |
| 233 | return zs->zstrm; |
| 234 | } |
| 235 | |
| 236 | static void zcomp_strm_single_release(struct zcomp *comp, |
| 237 | struct zcomp_strm *zstrm) |
| 238 | { |
| 239 | struct zcomp_strm_single *zs = comp->stream; |
| 240 | mutex_unlock(&zs->strm_lock); |
| 241 | } |
| 242 | |
Minchan Kim | 60a726e | 2014-04-07 15:38:21 -0700 | [diff] [blame] | 243 | static bool zcomp_strm_single_set_max_streams(struct zcomp *comp, int num_strm) |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 244 | { |
| 245 | /* zcomp_strm_single support only max_comp_streams == 1 */ |
Minchan Kim | 60a726e | 2014-04-07 15:38:21 -0700 | [diff] [blame] | 246 | return false; |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 247 | } |
| 248 | |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 249 | static void zcomp_strm_single_destroy(struct zcomp *comp) |
| 250 | { |
| 251 | struct zcomp_strm_single *zs = comp->stream; |
| 252 | zcomp_strm_free(comp, zs->zstrm); |
| 253 | kfree(zs); |
| 254 | } |
| 255 | |
| 256 | static int zcomp_strm_single_create(struct zcomp *comp) |
| 257 | { |
| 258 | struct zcomp_strm_single *zs; |
| 259 | |
| 260 | comp->destroy = zcomp_strm_single_destroy; |
| 261 | comp->strm_find = zcomp_strm_single_find; |
| 262 | comp->strm_release = zcomp_strm_single_release; |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 263 | comp->set_max_streams = zcomp_strm_single_set_max_streams; |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 264 | zs = kmalloc(sizeof(struct zcomp_strm_single), GFP_KERNEL); |
| 265 | if (!zs) |
| 266 | return -ENOMEM; |
| 267 | |
| 268 | comp->stream = zs; |
| 269 | mutex_init(&zs->strm_lock); |
Minchan Kim | 75d8947 | 2016-01-14 15:22:32 -0800 | [diff] [blame] | 270 | zs->zstrm = zcomp_strm_alloc(comp, GFP_KERNEL); |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 271 | if (!zs->zstrm) { |
| 272 | kfree(zs); |
| 273 | return -ENOMEM; |
| 274 | } |
| 275 | return 0; |
| 276 | } |
| 277 | |
Sergey Senozhatsky | e46b8a0 | 2014-04-07 15:38:17 -0700 | [diff] [blame] | 278 | /* show available compressors */ |
| 279 | ssize_t zcomp_available_show(const char *comp, char *buf) |
| 280 | { |
| 281 | ssize_t sz = 0; |
| 282 | int i = 0; |
| 283 | |
| 284 | while (backends[i]) { |
Sergey Senozhatsky | 4bbacd5 | 2015-06-25 15:00:29 -0700 | [diff] [blame] | 285 | if (!strcmp(comp, backends[i]->name)) |
Sergey Senozhatsky | 56b4e8c | 2014-04-07 15:38:22 -0700 | [diff] [blame] | 286 | sz += scnprintf(buf + sz, PAGE_SIZE - sz - 2, |
| 287 | "[%s] ", backends[i]->name); |
Sergey Senozhatsky | e46b8a0 | 2014-04-07 15:38:17 -0700 | [diff] [blame] | 288 | else |
Sergey Senozhatsky | 56b4e8c | 2014-04-07 15:38:22 -0700 | [diff] [blame] | 289 | sz += scnprintf(buf + sz, PAGE_SIZE - sz - 2, |
| 290 | "%s ", backends[i]->name); |
Sergey Senozhatsky | e46b8a0 | 2014-04-07 15:38:17 -0700 | [diff] [blame] | 291 | i++; |
| 292 | } |
Sergey Senozhatsky | 56b4e8c | 2014-04-07 15:38:22 -0700 | [diff] [blame] | 293 | sz += scnprintf(buf + sz, PAGE_SIZE - sz, "\n"); |
Sergey Senozhatsky | e46b8a0 | 2014-04-07 15:38:17 -0700 | [diff] [blame] | 294 | return sz; |
| 295 | } |
| 296 | |
Sergey Senozhatsky | d93435c | 2015-06-25 15:00:32 -0700 | [diff] [blame] | 297 | bool zcomp_available_algorithm(const char *comp) |
| 298 | { |
| 299 | return find_backend(comp) != NULL; |
| 300 | } |
| 301 | |
Minchan Kim | 60a726e | 2014-04-07 15:38:21 -0700 | [diff] [blame] | 302 | bool zcomp_set_max_streams(struct zcomp *comp, int num_strm) |
Sergey Senozhatsky | fe8eb12 | 2014-04-07 15:38:15 -0700 | [diff] [blame] | 303 | { |
| 304 | return comp->set_max_streams(comp, num_strm); |
| 305 | } |
| 306 | |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 307 | struct zcomp_strm *zcomp_strm_find(struct zcomp *comp) |
| 308 | { |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 309 | return comp->strm_find(comp); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 310 | } |
| 311 | |
| 312 | void zcomp_strm_release(struct zcomp *comp, struct zcomp_strm *zstrm) |
| 313 | { |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 314 | comp->strm_release(comp, zstrm); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 315 | } |
| 316 | |
| 317 | int zcomp_compress(struct zcomp *comp, struct zcomp_strm *zstrm, |
| 318 | const unsigned char *src, size_t *dst_len) |
| 319 | { |
| 320 | return comp->backend->compress(src, zstrm->buffer, dst_len, |
| 321 | zstrm->private); |
| 322 | } |
| 323 | |
| 324 | int zcomp_decompress(struct zcomp *comp, const unsigned char *src, |
| 325 | size_t src_len, unsigned char *dst) |
| 326 | { |
| 327 | return comp->backend->decompress(src, src_len, dst); |
| 328 | } |
| 329 | |
| 330 | void zcomp_destroy(struct zcomp *comp) |
| 331 | { |
Sergey Senozhatsky | 9cc9752 | 2014-04-07 15:38:13 -0700 | [diff] [blame] | 332 | comp->destroy(comp); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 333 | kfree(comp); |
| 334 | } |
| 335 | |
| 336 | /* |
| 337 | * search available compressors for requested algorithm. |
Sergey Senozhatsky | fcfa8d9 | 2014-04-07 15:38:20 -0700 | [diff] [blame] | 338 | * allocate new zcomp and initialize it. return compressing |
| 339 | * backend pointer or ERR_PTR if things went bad. ERR_PTR(-EINVAL) |
| 340 | * if requested algorithm is not supported, ERR_PTR(-ENOMEM) in |
Luis Henriques | 3aaf14d | 2015-09-17 16:01:40 -0700 | [diff] [blame] | 341 | * case of allocation error, or any other error potentially |
| 342 | * returned by functions zcomp_strm_{multi,single}_create. |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 343 | */ |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 344 | struct zcomp *zcomp_create(const char *compress, int max_strm) |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 345 | { |
| 346 | struct zcomp *comp; |
| 347 | struct zcomp_backend *backend; |
Luis Henriques | 3aaf14d | 2015-09-17 16:01:40 -0700 | [diff] [blame] | 348 | int error; |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 349 | |
| 350 | backend = find_backend(compress); |
| 351 | if (!backend) |
Sergey Senozhatsky | fcfa8d9 | 2014-04-07 15:38:20 -0700 | [diff] [blame] | 352 | return ERR_PTR(-EINVAL); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 353 | |
| 354 | comp = kzalloc(sizeof(struct zcomp), GFP_KERNEL); |
| 355 | if (!comp) |
Sergey Senozhatsky | fcfa8d9 | 2014-04-07 15:38:20 -0700 | [diff] [blame] | 356 | return ERR_PTR(-ENOMEM); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 357 | |
| 358 | comp->backend = backend; |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 359 | if (max_strm > 1) |
Luis Henriques | 3aaf14d | 2015-09-17 16:01:40 -0700 | [diff] [blame] | 360 | error = zcomp_strm_multi_create(comp, max_strm); |
Sergey Senozhatsky | beca3ec | 2014-04-07 15:38:14 -0700 | [diff] [blame] | 361 | else |
Luis Henriques | 3aaf14d | 2015-09-17 16:01:40 -0700 | [diff] [blame] | 362 | error = zcomp_strm_single_create(comp); |
| 363 | if (error) { |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 364 | kfree(comp); |
Luis Henriques | 3aaf14d | 2015-09-17 16:01:40 -0700 | [diff] [blame] | 365 | return ERR_PTR(error); |
Sergey Senozhatsky | e7e1ef4 | 2014-04-07 15:38:11 -0700 | [diff] [blame] | 366 | } |
| 367 | return comp; |
| 368 | } |