linux/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2013, Mellanox Technologies 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 * OpenIB.org 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#include <asm-generic/kmap_types.h>
  34#include <linux/kernel.h>
  35#include <linux/module.h>
  36#include <linux/mlx5/driver.h>
  37#include <linux/mlx5/cmd.h>
  38#include "mlx5_core.h"
  39
  40enum {
  41        MLX5_PAGES_CANT_GIVE    = 0,
  42        MLX5_PAGES_GIVE         = 1,
  43        MLX5_PAGES_TAKE         = 2
  44};
  45
  46enum {
  47        MLX5_BOOT_PAGES         = 1,
  48        MLX5_INIT_PAGES         = 2,
  49        MLX5_POST_INIT_PAGES    = 3
  50};
  51
  52struct mlx5_pages_req {
  53        struct mlx5_core_dev *dev;
  54        u32     func_id;
  55        s32     npages;
  56        struct work_struct work;
  57};
  58
  59struct fw_page {
  60        struct rb_node  rb_node;
  61        u64             addr;
  62        struct page     *page;
  63        u16             func_id;
  64};
  65
  66struct mlx5_query_pages_inbox {
  67        struct mlx5_inbox_hdr   hdr;
  68        u8                      rsvd[8];
  69};
  70
  71struct mlx5_query_pages_outbox {
  72        struct mlx5_outbox_hdr  hdr;
  73        __be16                  rsvd;
  74        __be16                  func_id;
  75        __be32                  num_pages;
  76};
  77
  78struct mlx5_manage_pages_inbox {
  79        struct mlx5_inbox_hdr   hdr;
  80        __be16                  rsvd;
  81        __be16                  func_id;
  82        __be32                  num_entries;
  83        __be64                  pas[0];
  84};
  85
  86struct mlx5_manage_pages_outbox {
  87        struct mlx5_outbox_hdr  hdr;
  88        __be32                  num_entries;
  89        u8                      rsvd[4];
  90        __be64                  pas[0];
  91};
  92
  93static int insert_page(struct mlx5_core_dev *dev, u64 addr, struct page *page, u16 func_id)
  94{
  95        struct rb_root *root = &dev->priv.page_root;
  96        struct rb_node **new = &root->rb_node;
  97        struct rb_node *parent = NULL;
  98        struct fw_page *nfp;
  99        struct fw_page *tfp;
 100
 101        while (*new) {
 102                parent = *new;
 103                tfp = rb_entry(parent, struct fw_page, rb_node);
 104                if (tfp->addr < addr)
 105                        new = &parent->rb_left;
 106                else if (tfp->addr > addr)
 107                        new = &parent->rb_right;
 108                else
 109                        return -EEXIST;
 110        }
 111
 112        nfp = kmalloc(sizeof(*nfp), GFP_KERNEL);
 113        if (!nfp)
 114                return -ENOMEM;
 115
 116        nfp->addr = addr;
 117        nfp->page = page;
 118        nfp->func_id = func_id;
 119
 120        rb_link_node(&nfp->rb_node, parent, new);
 121        rb_insert_color(&nfp->rb_node, root);
 122
 123        return 0;
 124}
 125
 126static struct page *remove_page(struct mlx5_core_dev *dev, u64 addr)
 127{
 128        struct rb_root *root = &dev->priv.page_root;
 129        struct rb_node *tmp = root->rb_node;
 130        struct page *result = NULL;
 131        struct fw_page *tfp;
 132
 133        while (tmp) {
 134                tfp = rb_entry(tmp, struct fw_page, rb_node);
 135                if (tfp->addr < addr) {
 136                        tmp = tmp->rb_left;
 137                } else if (tfp->addr > addr) {
 138                        tmp = tmp->rb_right;
 139                } else {
 140                        rb_erase(&tfp->rb_node, root);
 141                        result = tfp->page;
 142                        kfree(tfp);
 143                        break;
 144                }
 145        }
 146
 147        return result;
 148}
 149
 150static int mlx5_cmd_query_pages(struct mlx5_core_dev *dev, u16 *func_id,
 151                                s32 *npages, int boot)
 152{
 153        struct mlx5_query_pages_inbox   in;
 154        struct mlx5_query_pages_outbox  out;
 155        int err;
 156
 157        memset(&in, 0, sizeof(in));
 158        memset(&out, 0, sizeof(out));
 159        in.hdr.opcode = cpu_to_be16(MLX5_CMD_OP_QUERY_PAGES);
 160        in.hdr.opmod = boot ? cpu_to_be16(MLX5_BOOT_PAGES) : cpu_to_be16(MLX5_INIT_PAGES);
 161
 162        err = mlx5_cmd_exec(dev, &in, sizeof(in), &out, sizeof(out));
 163        if (err)
 164                return err;
 165
 166        if (out.hdr.status)
 167                return mlx5_cmd_status_to_err(&out.hdr);
 168
 169        *npages = be32_to_cpu(out.num_pages);
 170        *func_id = be16_to_cpu(out.func_id);
 171
 172        return err;
 173}
 174
 175static int give_pages(struct mlx5_core_dev *dev, u16 func_id, int npages,
 176                      int notify_fail)
 177{
 178        struct mlx5_manage_pages_inbox *in;
 179        struct mlx5_manage_pages_outbox out;
 180        struct page *page;
 181        int inlen;
 182        u64 addr;
 183        int err;
 184        int i;
 185
 186        inlen = sizeof(*in) + npages * sizeof(in->pas[0]);
 187        in = mlx5_vzalloc(inlen);
 188        if (!in) {
 189                mlx5_core_warn(dev, "vzalloc failed %d\n", inlen);
 190                return -ENOMEM;
 191        }
 192        memset(&out, 0, sizeof(out));
 193
 194        for (i = 0; i < npages; i++) {
 195                page = alloc_page(GFP_HIGHUSER);
 196                if (!page) {
 197                        err = -ENOMEM;
 198                        mlx5_core_warn(dev, "failed to allocate page\n");
 199                        goto out_alloc;
 200                }
 201                addr = dma_map_page(&dev->pdev->dev, page, 0,
 202                                    PAGE_SIZE, DMA_BIDIRECTIONAL);
 203                if (dma_mapping_error(&dev->pdev->dev, addr)) {
 204                        mlx5_core_warn(dev, "failed dma mapping page\n");
 205                        __free_page(page);
 206                        err = -ENOMEM;
 207                        goto out_alloc;
 208                }
 209                err = insert_page(dev, addr, page, func_id);
 210                if (err) {
 211                        mlx5_core_err(dev, "failed to track allocated page\n");
 212                        dma_unmap_page(&dev->pdev->dev, addr, PAGE_SIZE, DMA_BIDIRECTIONAL);
 213                        __free_page(page);
 214                        err = -ENOMEM;
 215                        goto out_alloc;
 216                }
 217                in->pas[i] = cpu_to_be64(addr);
 218        }
 219
 220        in->hdr.opcode = cpu_to_be16(MLX5_CMD_OP_MANAGE_PAGES);
 221        in->hdr.opmod = cpu_to_be16(MLX5_PAGES_GIVE);
 222        in->func_id = cpu_to_be16(func_id);
 223        in->num_entries = cpu_to_be32(npages);
 224        err = mlx5_cmd_exec(dev, in, inlen, &out, sizeof(out));
 225        mlx5_core_dbg(dev, "err %d\n", err);
 226        if (err) {
 227                mlx5_core_warn(dev, "func_id 0x%x, npages %d, err %d\n", func_id, npages, err);
 228                goto out_alloc;
 229        }
 230        dev->priv.fw_pages += npages;
 231
 232        if (out.hdr.status) {
 233                err = mlx5_cmd_status_to_err(&out.hdr);
 234                if (err) {
 235                        mlx5_core_warn(dev, "func_id 0x%x, npages %d, status %d\n", func_id, npages, out.hdr.status);
 236                        goto out_alloc;
 237                }
 238        }
 239
 240        mlx5_core_dbg(dev, "err %d\n", err);
 241
 242        goto out_free;
 243
 244out_alloc:
 245        if (notify_fail) {
 246                memset(in, 0, inlen);
 247                memset(&out, 0, sizeof(out));
 248                in->hdr.opcode = cpu_to_be16(MLX5_CMD_OP_MANAGE_PAGES);
 249                in->hdr.opmod = cpu_to_be16(MLX5_PAGES_CANT_GIVE);
 250                if (mlx5_cmd_exec(dev, in, sizeof(*in), &out, sizeof(out)))
 251                        mlx5_core_warn(dev, "\n");
 252        }
 253        for (i--; i >= 0; i--) {
 254                addr = be64_to_cpu(in->pas[i]);
 255                page = remove_page(dev, addr);
 256                if (!page) {
 257                        mlx5_core_err(dev, "BUG: can't remove page at addr 0x%llx\n",
 258                                      addr);
 259                        continue;
 260                }
 261                dma_unmap_page(&dev->pdev->dev, addr, PAGE_SIZE, DMA_BIDIRECTIONAL);
 262                __free_page(page);
 263        }
 264
 265out_free:
 266        mlx5_vfree(in);
 267        return err;
 268}
 269
 270static int reclaim_pages(struct mlx5_core_dev *dev, u32 func_id, int npages,
 271                         int *nclaimed)
 272{
 273        struct mlx5_manage_pages_inbox   in;
 274        struct mlx5_manage_pages_outbox *out;
 275        struct page *page;
 276        int num_claimed;
 277        int outlen;
 278        u64 addr;
 279        int err;
 280        int i;
 281
 282        memset(&in, 0, sizeof(in));
 283        outlen = sizeof(*out) + npages * sizeof(out->pas[0]);
 284        out = mlx5_vzalloc(outlen);
 285        if (!out)
 286                return -ENOMEM;
 287
 288        in.hdr.opcode = cpu_to_be16(MLX5_CMD_OP_MANAGE_PAGES);
 289        in.hdr.opmod = cpu_to_be16(MLX5_PAGES_TAKE);
 290        in.func_id = cpu_to_be16(func_id);
 291        in.num_entries = cpu_to_be32(npages);
 292        mlx5_core_dbg(dev, "npages %d, outlen %d\n", npages, outlen);
 293        err = mlx5_cmd_exec(dev, &in, sizeof(in), out, outlen);
 294        if (err) {
 295                mlx5_core_err(dev, "failed recliaming pages\n");
 296                goto out_free;
 297        }
 298        dev->priv.fw_pages -= npages;
 299
 300        if (out->hdr.status) {
 301                err = mlx5_cmd_status_to_err(&out->hdr);
 302                goto out_free;
 303        }
 304
 305        num_claimed = be32_to_cpu(out->num_entries);
 306        if (nclaimed)
 307                *nclaimed = num_claimed;
 308
 309        for (i = 0; i < num_claimed; i++) {
 310                addr = be64_to_cpu(out->pas[i]);
 311                page = remove_page(dev, addr);
 312                if (!page) {
 313                        mlx5_core_warn(dev, "FW reported unknown DMA address 0x%llx\n", addr);
 314                } else {
 315                        dma_unmap_page(&dev->pdev->dev, addr, PAGE_SIZE, DMA_BIDIRECTIONAL);
 316                        __free_page(page);
 317                }
 318        }
 319
 320out_free:
 321        mlx5_vfree(out);
 322        return err;
 323}
 324
 325static void pages_work_handler(struct work_struct *work)
 326{
 327        struct mlx5_pages_req *req = container_of(work, struct mlx5_pages_req, work);
 328        struct mlx5_core_dev *dev = req->dev;
 329        int err = 0;
 330
 331        if (req->npages < 0)
 332                err = reclaim_pages(dev, req->func_id, -1 * req->npages, NULL);
 333        else if (req->npages > 0)
 334                err = give_pages(dev, req->func_id, req->npages, 1);
 335
 336        if (err)
 337                mlx5_core_warn(dev, "%s fail %d\n", req->npages < 0 ?
 338                               "reclaim" : "give", err);
 339
 340        kfree(req);
 341}
 342
 343void mlx5_core_req_pages_handler(struct mlx5_core_dev *dev, u16 func_id,
 344                                 s32 npages)
 345{
 346        struct mlx5_pages_req *req;
 347
 348        req = kzalloc(sizeof(*req), GFP_ATOMIC);
 349        if (!req) {
 350                mlx5_core_warn(dev, "failed to allocate pages request\n");
 351                return;
 352        }
 353
 354        req->dev = dev;
 355        req->func_id = func_id;
 356        req->npages = npages;
 357        INIT_WORK(&req->work, pages_work_handler);
 358        queue_work(dev->priv.pg_wq, &req->work);
 359}
 360
 361int mlx5_satisfy_startup_pages(struct mlx5_core_dev *dev, int boot)
 362{
 363        u16 uninitialized_var(func_id);
 364        s32 uninitialized_var(npages);
 365        int err;
 366
 367        err = mlx5_cmd_query_pages(dev, &func_id, &npages, boot);
 368        if (err)
 369                return err;
 370
 371        mlx5_core_dbg(dev, "requested %d %s pages for func_id 0x%x\n",
 372                      npages, boot ? "boot" : "init", func_id);
 373
 374        return give_pages(dev, func_id, npages, 0);
 375}
 376
 377static int optimal_reclaimed_pages(void)
 378{
 379        struct mlx5_cmd_prot_block *block;
 380        struct mlx5_cmd_layout *lay;
 381        int ret;
 382
 383        ret = (sizeof(lay->in) + sizeof(block->data) -
 384               sizeof(struct mlx5_manage_pages_outbox)) / 8;
 385
 386        return ret;
 387}
 388
 389int mlx5_reclaim_startup_pages(struct mlx5_core_dev *dev)
 390{
 391        unsigned long end = jiffies + msecs_to_jiffies(5000);
 392        struct fw_page *fwp;
 393        struct rb_node *p;
 394        int err;
 395
 396        do {
 397                p = rb_first(&dev->priv.page_root);
 398                if (p) {
 399                        fwp = rb_entry(p, struct fw_page, rb_node);
 400                        err = reclaim_pages(dev, fwp->func_id, optimal_reclaimed_pages(), NULL);
 401                        if (err) {
 402                                mlx5_core_warn(dev, "failed reclaiming pages (%d)\n", err);
 403                                return err;
 404                        }
 405                }
 406                if (time_after(jiffies, end)) {
 407                        mlx5_core_warn(dev, "FW did not return all pages. giving up...\n");
 408                        break;
 409                }
 410        } while (p);
 411
 412        return 0;
 413}
 414
 415void mlx5_pagealloc_init(struct mlx5_core_dev *dev)
 416{
 417        dev->priv.page_root = RB_ROOT;
 418}
 419
 420void mlx5_pagealloc_cleanup(struct mlx5_core_dev *dev)
 421{
 422        /* nothing */
 423}
 424
 425int mlx5_pagealloc_start(struct mlx5_core_dev *dev)
 426{
 427        dev->priv.pg_wq = create_singlethread_workqueue("mlx5_page_allocator");
 428        if (!dev->priv.pg_wq)
 429                return -ENOMEM;
 430
 431        return 0;
 432}
 433
 434void mlx5_pagealloc_stop(struct mlx5_core_dev *dev)
 435{
 436        destroy_workqueue(dev->priv.pg_wq);
 437}
 438