0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0046
0047 #include <linux/atomic.h>
0048 #include <linux/list.h>
0049 #include <linux/mm.h>
0050 #include <linux/module.h>
0051 #include <linux/preempt.h>
0052 #include <linux/slab.h>
0053 #include <linux/spinlock.h>
0054 #include <linux/zpool.h>
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068 #define NCHUNKS_ORDER 6
0069
0070 #define CHUNK_SHIFT (PAGE_SHIFT - NCHUNKS_ORDER)
0071 #define CHUNK_SIZE (1 << CHUNK_SHIFT)
0072 #define ZHDR_SIZE_ALIGNED CHUNK_SIZE
0073 #define NCHUNKS ((PAGE_SIZE - ZHDR_SIZE_ALIGNED) >> CHUNK_SHIFT)
0074
0075 struct zbud_pool;
0076
0077 struct zbud_ops {
0078 int (*evict)(struct zbud_pool *pool, unsigned long handle);
0079 };
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090
0091
0092
0093
0094
0095
0096
0097
0098
0099
0100
0101 struct zbud_pool {
0102 spinlock_t lock;
0103 union {
0104
0105
0106
0107
0108 struct list_head buddied;
0109 struct list_head unbuddied[NCHUNKS];
0110 };
0111 struct list_head lru;
0112 u64 pages_nr;
0113 const struct zbud_ops *ops;
0114 struct zpool *zpool;
0115 const struct zpool_ops *zpool_ops;
0116 };
0117
0118
0119
0120
0121
0122
0123
0124
0125
0126 struct zbud_header {
0127 struct list_head buddy;
0128 struct list_head lru;
0129 unsigned int first_chunks;
0130 unsigned int last_chunks;
0131 bool under_reclaim;
0132 };
0133
0134
0135
0136
0137
0138 enum buddy {
0139 FIRST,
0140 LAST
0141 };
0142
0143
0144 static int size_to_chunks(size_t size)
0145 {
0146 return (size + CHUNK_SIZE - 1) >> CHUNK_SHIFT;
0147 }
0148
0149 #define for_each_unbuddied_list(_iter, _begin) \
0150 for ((_iter) = (_begin); (_iter) < NCHUNKS; (_iter)++)
0151
0152
0153 static struct zbud_header *init_zbud_page(struct page *page)
0154 {
0155 struct zbud_header *zhdr = page_address(page);
0156 zhdr->first_chunks = 0;
0157 zhdr->last_chunks = 0;
0158 INIT_LIST_HEAD(&zhdr->buddy);
0159 INIT_LIST_HEAD(&zhdr->lru);
0160 zhdr->under_reclaim = false;
0161 return zhdr;
0162 }
0163
0164
0165 static void free_zbud_page(struct zbud_header *zhdr)
0166 {
0167 __free_page(virt_to_page(zhdr));
0168 }
0169
0170
0171
0172
0173
0174 static unsigned long encode_handle(struct zbud_header *zhdr, enum buddy bud)
0175 {
0176 unsigned long handle;
0177
0178
0179
0180
0181
0182
0183
0184 handle = (unsigned long)zhdr;
0185 if (bud == FIRST)
0186
0187 handle += ZHDR_SIZE_ALIGNED;
0188 else
0189 handle += PAGE_SIZE - (zhdr->last_chunks << CHUNK_SHIFT);
0190 return handle;
0191 }
0192
0193
0194 static struct zbud_header *handle_to_zbud_header(unsigned long handle)
0195 {
0196 return (struct zbud_header *)(handle & PAGE_MASK);
0197 }
0198
0199
0200 static int num_free_chunks(struct zbud_header *zhdr)
0201 {
0202
0203
0204
0205
0206 return NCHUNKS - zhdr->first_chunks - zhdr->last_chunks;
0207 }
0208
0209
0210
0211
0212
0213
0214
0215
0216
0217
0218
0219
0220 static struct zbud_pool *zbud_create_pool(gfp_t gfp, const struct zbud_ops *ops)
0221 {
0222 struct zbud_pool *pool;
0223 int i;
0224
0225 pool = kzalloc(sizeof(struct zbud_pool), gfp);
0226 if (!pool)
0227 return NULL;
0228 spin_lock_init(&pool->lock);
0229 for_each_unbuddied_list(i, 0)
0230 INIT_LIST_HEAD(&pool->unbuddied[i]);
0231 INIT_LIST_HEAD(&pool->buddied);
0232 INIT_LIST_HEAD(&pool->lru);
0233 pool->pages_nr = 0;
0234 pool->ops = ops;
0235 return pool;
0236 }
0237
0238
0239
0240
0241
0242
0243
0244 static void zbud_destroy_pool(struct zbud_pool *pool)
0245 {
0246 kfree(pool);
0247 }
0248
0249
0250
0251
0252
0253
0254
0255
0256
0257
0258
0259
0260
0261
0262
0263
0264
0265
0266
0267
0268 static int zbud_alloc(struct zbud_pool *pool, size_t size, gfp_t gfp,
0269 unsigned long *handle)
0270 {
0271 int chunks, i, freechunks;
0272 struct zbud_header *zhdr = NULL;
0273 enum buddy bud;
0274 struct page *page;
0275
0276 if (!size || (gfp & __GFP_HIGHMEM))
0277 return -EINVAL;
0278 if (size > PAGE_SIZE - ZHDR_SIZE_ALIGNED - CHUNK_SIZE)
0279 return -ENOSPC;
0280 chunks = size_to_chunks(size);
0281 spin_lock(&pool->lock);
0282
0283
0284 for_each_unbuddied_list(i, chunks) {
0285 if (!list_empty(&pool->unbuddied[i])) {
0286 zhdr = list_first_entry(&pool->unbuddied[i],
0287 struct zbud_header, buddy);
0288 list_del(&zhdr->buddy);
0289 if (zhdr->first_chunks == 0)
0290 bud = FIRST;
0291 else
0292 bud = LAST;
0293 goto found;
0294 }
0295 }
0296
0297
0298 spin_unlock(&pool->lock);
0299 page = alloc_page(gfp);
0300 if (!page)
0301 return -ENOMEM;
0302 spin_lock(&pool->lock);
0303 pool->pages_nr++;
0304 zhdr = init_zbud_page(page);
0305 bud = FIRST;
0306
0307 found:
0308 if (bud == FIRST)
0309 zhdr->first_chunks = chunks;
0310 else
0311 zhdr->last_chunks = chunks;
0312
0313 if (zhdr->first_chunks == 0 || zhdr->last_chunks == 0) {
0314
0315 freechunks = num_free_chunks(zhdr);
0316 list_add(&zhdr->buddy, &pool->unbuddied[freechunks]);
0317 } else {
0318
0319 list_add(&zhdr->buddy, &pool->buddied);
0320 }
0321
0322
0323 if (!list_empty(&zhdr->lru))
0324 list_del(&zhdr->lru);
0325 list_add(&zhdr->lru, &pool->lru);
0326
0327 *handle = encode_handle(zhdr, bud);
0328 spin_unlock(&pool->lock);
0329
0330 return 0;
0331 }
0332
0333
0334
0335
0336
0337
0338
0339
0340
0341
0342
0343 static void zbud_free(struct zbud_pool *pool, unsigned long handle)
0344 {
0345 struct zbud_header *zhdr;
0346 int freechunks;
0347
0348 spin_lock(&pool->lock);
0349 zhdr = handle_to_zbud_header(handle);
0350
0351
0352 if ((handle - ZHDR_SIZE_ALIGNED) & ~PAGE_MASK)
0353 zhdr->last_chunks = 0;
0354 else
0355 zhdr->first_chunks = 0;
0356
0357 if (zhdr->under_reclaim) {
0358
0359 spin_unlock(&pool->lock);
0360 return;
0361 }
0362
0363
0364 list_del(&zhdr->buddy);
0365
0366 if (zhdr->first_chunks == 0 && zhdr->last_chunks == 0) {
0367
0368 list_del(&zhdr->lru);
0369 free_zbud_page(zhdr);
0370 pool->pages_nr--;
0371 } else {
0372
0373 freechunks = num_free_chunks(zhdr);
0374 list_add(&zhdr->buddy, &pool->unbuddied[freechunks]);
0375 }
0376
0377 spin_unlock(&pool->lock);
0378 }
0379
0380
0381
0382
0383
0384
0385
0386
0387
0388
0389
0390
0391
0392
0393
0394
0395
0396
0397
0398
0399
0400
0401
0402
0403
0404
0405
0406
0407
0408
0409
0410
0411
0412
0413
0414
0415 static int zbud_reclaim_page(struct zbud_pool *pool, unsigned int retries)
0416 {
0417 int i, ret, freechunks;
0418 struct zbud_header *zhdr;
0419 unsigned long first_handle = 0, last_handle = 0;
0420
0421 spin_lock(&pool->lock);
0422 if (!pool->ops || !pool->ops->evict || list_empty(&pool->lru) ||
0423 retries == 0) {
0424 spin_unlock(&pool->lock);
0425 return -EINVAL;
0426 }
0427 for (i = 0; i < retries; i++) {
0428 zhdr = list_last_entry(&pool->lru, struct zbud_header, lru);
0429 list_del(&zhdr->lru);
0430 list_del(&zhdr->buddy);
0431
0432 zhdr->under_reclaim = true;
0433
0434
0435
0436
0437 first_handle = 0;
0438 last_handle = 0;
0439 if (zhdr->first_chunks)
0440 first_handle = encode_handle(zhdr, FIRST);
0441 if (zhdr->last_chunks)
0442 last_handle = encode_handle(zhdr, LAST);
0443 spin_unlock(&pool->lock);
0444
0445
0446 if (first_handle) {
0447 ret = pool->ops->evict(pool, first_handle);
0448 if (ret)
0449 goto next;
0450 }
0451 if (last_handle) {
0452 ret = pool->ops->evict(pool, last_handle);
0453 if (ret)
0454 goto next;
0455 }
0456 next:
0457 spin_lock(&pool->lock);
0458 zhdr->under_reclaim = false;
0459 if (zhdr->first_chunks == 0 && zhdr->last_chunks == 0) {
0460
0461
0462
0463
0464 free_zbud_page(zhdr);
0465 pool->pages_nr--;
0466 spin_unlock(&pool->lock);
0467 return 0;
0468 } else if (zhdr->first_chunks == 0 ||
0469 zhdr->last_chunks == 0) {
0470
0471 freechunks = num_free_chunks(zhdr);
0472 list_add(&zhdr->buddy, &pool->unbuddied[freechunks]);
0473 } else {
0474
0475 list_add(&zhdr->buddy, &pool->buddied);
0476 }
0477
0478
0479 list_add(&zhdr->lru, &pool->lru);
0480 }
0481 spin_unlock(&pool->lock);
0482 return -EAGAIN;
0483 }
0484
0485
0486
0487
0488
0489
0490
0491
0492
0493
0494
0495
0496
0497 static void *zbud_map(struct zbud_pool *pool, unsigned long handle)
0498 {
0499 return (void *)(handle);
0500 }
0501
0502
0503
0504
0505
0506
0507 static void zbud_unmap(struct zbud_pool *pool, unsigned long handle)
0508 {
0509 }
0510
0511
0512
0513
0514
0515
0516
0517
0518 static u64 zbud_get_pool_size(struct zbud_pool *pool)
0519 {
0520 return pool->pages_nr;
0521 }
0522
0523
0524
0525
0526
0527 static int zbud_zpool_evict(struct zbud_pool *pool, unsigned long handle)
0528 {
0529 if (pool->zpool && pool->zpool_ops && pool->zpool_ops->evict)
0530 return pool->zpool_ops->evict(pool->zpool, handle);
0531 else
0532 return -ENOENT;
0533 }
0534
0535 static const struct zbud_ops zbud_zpool_ops = {
0536 .evict = zbud_zpool_evict
0537 };
0538
0539 static void *zbud_zpool_create(const char *name, gfp_t gfp,
0540 const struct zpool_ops *zpool_ops,
0541 struct zpool *zpool)
0542 {
0543 struct zbud_pool *pool;
0544
0545 pool = zbud_create_pool(gfp, zpool_ops ? &zbud_zpool_ops : NULL);
0546 if (pool) {
0547 pool->zpool = zpool;
0548 pool->zpool_ops = zpool_ops;
0549 }
0550 return pool;
0551 }
0552
0553 static void zbud_zpool_destroy(void *pool)
0554 {
0555 zbud_destroy_pool(pool);
0556 }
0557
0558 static int zbud_zpool_malloc(void *pool, size_t size, gfp_t gfp,
0559 unsigned long *handle)
0560 {
0561 return zbud_alloc(pool, size, gfp, handle);
0562 }
0563 static void zbud_zpool_free(void *pool, unsigned long handle)
0564 {
0565 zbud_free(pool, handle);
0566 }
0567
0568 static int zbud_zpool_shrink(void *pool, unsigned int pages,
0569 unsigned int *reclaimed)
0570 {
0571 unsigned int total = 0;
0572 int ret = -EINVAL;
0573
0574 while (total < pages) {
0575 ret = zbud_reclaim_page(pool, 8);
0576 if (ret < 0)
0577 break;
0578 total++;
0579 }
0580
0581 if (reclaimed)
0582 *reclaimed = total;
0583
0584 return ret;
0585 }
0586
0587 static void *zbud_zpool_map(void *pool, unsigned long handle,
0588 enum zpool_mapmode mm)
0589 {
0590 return zbud_map(pool, handle);
0591 }
0592 static void zbud_zpool_unmap(void *pool, unsigned long handle)
0593 {
0594 zbud_unmap(pool, handle);
0595 }
0596
0597 static u64 zbud_zpool_total_size(void *pool)
0598 {
0599 return zbud_get_pool_size(pool) * PAGE_SIZE;
0600 }
0601
0602 static struct zpool_driver zbud_zpool_driver = {
0603 .type = "zbud",
0604 .sleep_mapped = true,
0605 .owner = THIS_MODULE,
0606 .create = zbud_zpool_create,
0607 .destroy = zbud_zpool_destroy,
0608 .malloc = zbud_zpool_malloc,
0609 .free = zbud_zpool_free,
0610 .shrink = zbud_zpool_shrink,
0611 .map = zbud_zpool_map,
0612 .unmap = zbud_zpool_unmap,
0613 .total_size = zbud_zpool_total_size,
0614 };
0615
0616 MODULE_ALIAS("zpool-zbud");
0617
0618 static int __init init_zbud(void)
0619 {
0620
0621 BUILD_BUG_ON(sizeof(struct zbud_header) > ZHDR_SIZE_ALIGNED);
0622 pr_info("loaded\n");
0623
0624 zpool_register_driver(&zbud_zpool_driver);
0625
0626 return 0;
0627 }
0628
0629 static void __exit exit_zbud(void)
0630 {
0631 zpool_unregister_driver(&zbud_zpool_driver);
0632 pr_info("unloaded\n");
0633 }
0634
0635 module_init(init_zbud);
0636 module_exit(exit_zbud);
0637
0638 MODULE_LICENSE("GPL");
0639 MODULE_AUTHOR("Seth Jennings <sjennings@variantweb.net>");
0640 MODULE_DESCRIPTION("Buddy Allocator for Compressed Pages");