| 1 | |
| 2 |
|
| 3 | |
| 4 | * Xen frontend/backend page directory based shared buffer
|
| 5 | * helper module.
|
| 6 | *
|
| 7 | * Copyright (C) 2018 EPAM Systems Inc.
|
| 8 | *
|
| 9 | * Author: Oleksandr Andrushchenko <[email protected]>
|
| 10 |
|
| 11 |
|
| 12 | #include <linux/module.h>
|
| 13 | #include <linux/errno.h>
|
| 14 | #include <linux/mm.h>
|
| 15 |
|
| 16 | #include <asm/xen/hypervisor.h>
|
| 17 | #include <xen/balloon.h>
|
| 18 | #include <xen/xen.h>
|
| 19 | #include <xen/xenbus.h>
|
| 20 | #include <xen/interface/io/ring.h>
|
| 21 |
|
| 22 | #include <xen/xen-front-pgdir-shbuf.h>
|
| 23 |
|
| 24 | |
| 25 | * This structure represents the structure of a shared page
|
| 26 | * that contains grant references to the pages of the shared
|
| 27 | * buffer. This structure is common to many Xen para-virtualized
|
| 28 | * protocols at include/xen/interface/io/
|
| 29 |
|
| 30 | struct xen_page_directory {
|
| 31 | grant_ref_t gref_dir_next_page;
|
| 32 | #define XEN_GREF_LIST_END 0
|
| 33 | grant_ref_t gref[];
|
| 34 | };
|
| 35 |
|
| 36 | |
| 37 | * Shared buffer ops which are differently implemented
|
| 38 | * depending on the allocation mode, e.g. if the buffer
|
| 39 | * is allocated by the corresponding backend or frontend.
|
| 40 | * Some of the operations.
|
| 41 |
|
| 42 | struct xen_front_pgdir_shbuf_ops {
|
| 43 | |
| 44 | * Calculate number of grefs required to handle this buffer,
|
| 45 | * e.g. if grefs are required for page directory only or the buffer
|
| 46 | * pages as well.
|
| 47 |
|
| 48 | void (*calc_num_grefs)(struct xen_front_pgdir_shbuf *buf);
|
| 49 |
|
| 50 |
|
| 51 | void (*fill_page_dir)(struct xen_front_pgdir_shbuf *buf);
|
| 52 |
|
| 53 |
|
| 54 | int (*grant_refs_for_buffer)(struct xen_front_pgdir_shbuf *buf,
|
| 55 | grant_ref_t *priv_gref_head, int gref_idx);
|
| 56 |
|
| 57 |
|
| 58 | int (*map)(struct xen_front_pgdir_shbuf *buf);
|
| 59 |
|
| 60 |
|
| 61 | int (*unmap)(struct xen_front_pgdir_shbuf *buf);
|
| 62 | };
|
| 63 |
|
| 64 | |
| 65 | * Get granted reference to the very first page of the
|
| 66 | * page directory. Usually this is passed to the backend,
|
| 67 | * so it can find/fill the grant references to the buffer's
|
| 68 | * pages.
|
| 69 | *
|
| 70 | * \param buf shared buffer which page directory is of interest.
|
| 71 | * \return granted reference to the very first page of the
|
| 72 | * page directory.
|
| 73 |
|
| 74 | grant_ref_t
|
| 75 | xen_front_pgdir_shbuf_get_dir_start(struct xen_front_pgdir_shbuf *buf)
|
| 76 | {
|
| 77 | if (!buf->grefs)
|
| 78 | return INVALID_GRANT_REF;
|
| 79 |
|
| 80 | return buf->grefs[0];
|
| 81 | }
|
| 82 | EXPORT_SYMBOL_GPL(xen_front_pgdir_shbuf_get_dir_start);
|
| 83 |
|
| 84 | |
| 85 | * Map granted references of the shared buffer.
|
| 86 | *
|
| 87 | * Depending on the shared buffer mode of allocation
|
| 88 | * (be_alloc flag) this can either do nothing (for buffers
|
| 89 | * shared by the frontend itself) or map the provided granted
|
| 90 | * references onto the backing storage (buf->pages).
|
| 91 | *
|
| 92 | * \param buf shared buffer which grants to be mapped.
|
| 93 | * \return zero on success or a negative number on failure.
|
| 94 |
|
| 95 | int xen_front_pgdir_shbuf_map(struct xen_front_pgdir_shbuf *buf)
|
| 96 | {
|
| 97 | if (buf->ops && buf->ops->map)
|
| 98 | return buf->ops->map(buf);
|
| 99 |
|
| 100 |
|
| 101 | return 0;
|
| 102 | }
|
| 103 | EXPORT_SYMBOL_GPL(xen_front_pgdir_shbuf_map);
|
| 104 |
|
| 105 | |
| 106 | * Unmap granted references of the shared buffer.
|
| 107 | *
|
| 108 | * Depending on the shared buffer mode of allocation
|
| 109 | * (be_alloc flag) this can either do nothing (for buffers
|
| 110 | * shared by the frontend itself) or unmap the provided granted
|
| 111 | * references.
|
| 112 | *
|
| 113 | * \param buf shared buffer which grants to be unmapped.
|
| 114 | * \return zero on success or a negative number on failure.
|
| 115 |
|
| 116 | int xen_front_pgdir_shbuf_unmap(struct xen_front_pgdir_shbuf *buf)
|
| 117 | {
|
| 118 | if (buf->ops && buf->ops->unmap)
|
| 119 | return buf->ops->unmap(buf);
|
| 120 |
|
| 121 |
|
| 122 | return 0;
|
| 123 | }
|
| 124 | EXPORT_SYMBOL_GPL(xen_front_pgdir_shbuf_unmap);
|
| 125 |
|
| 126 | |
| 127 | * Free all the resources of the shared buffer.
|
| 128 | *
|
| 129 | * \param buf shared buffer which resources to be freed.
|
| 130 |
|
| 131 | void xen_front_pgdir_shbuf_free(struct xen_front_pgdir_shbuf *buf)
|
| 132 | {
|
| 133 | if (buf->grefs) {
|
| 134 | int i;
|
| 135 |
|
| 136 | for (i = 0; i < buf->num_grefs; i++)
|
| 137 | if (buf->grefs[i] != INVALID_GRANT_REF)
|
| 138 | gnttab_end_foreign_access(buf->grefs[i], NULL);
|
| 139 | }
|
| 140 | kfree(buf->grefs);
|
| 141 | kfree(buf->directory);
|
| 142 | }
|
| 143 | EXPORT_SYMBOL_GPL(xen_front_pgdir_shbuf_free);
|
| 144 |
|
| 145 | |
| 146 | * Number of grefs a page can hold with respect to the
|
| 147 | * struct xen_page_directory header.
|
| 148 |
|
| 149 | #define XEN_NUM_GREFS_PER_PAGE ((PAGE_SIZE - \
|
| 150 | offsetof(struct xen_page_directory, \
|
| 151 | gref)) / sizeof(grant_ref_t))
|
| 152 |
|
| 153 | |
| 154 | * Get the number of pages the page directory consumes itself.
|
| 155 | *
|
| 156 | * \param buf shared buffer.
|
| 157 |
|
| 158 | static int get_num_pages_dir(struct xen_front_pgdir_shbuf *buf)
|
| 159 | {
|
| 160 | return DIV_ROUND_UP(buf->num_pages, XEN_NUM_GREFS_PER_PAGE);
|
| 161 | }
|
| 162 |
|
| 163 | |
| 164 | * Calculate the number of grant references needed to share the buffer
|
| 165 | * and its pages when backend allocates the buffer.
|
| 166 | *
|
| 167 | * \param buf shared buffer.
|
| 168 |
|
| 169 | static void backend_calc_num_grefs(struct xen_front_pgdir_shbuf *buf)
|
| 170 | {
|
| 171 |
|
| 172 | buf->num_grefs = get_num_pages_dir(buf);
|
| 173 | }
|
| 174 |
|
| 175 | |
| 176 | * Calculate the number of grant references needed to share the buffer
|
| 177 | * and its pages when frontend allocates the buffer.
|
| 178 | *
|
| 179 | * \param buf shared buffer.
|
| 180 |
|
| 181 | static void guest_calc_num_grefs(struct xen_front_pgdir_shbuf *buf)
|
| 182 | {
|
| 183 | |
| 184 | * Number of pages the page directory consumes itself
|
| 185 | * plus grefs for the buffer pages.
|
| 186 |
|
| 187 | buf->num_grefs = get_num_pages_dir(buf) + buf->num_pages;
|
| 188 | }
|
| 189 |
|
| 190 | #define xen_page_to_vaddr(page) \
|
| 191 | ((uintptr_t)pfn_to_kaddr(page_to_xen_pfn(page)))
|
| 192 |
|
| 193 | |
| 194 | * Unmap the buffer previously mapped with grant references
|
| 195 | * provided by the backend.
|
| 196 | *
|
| 197 | * \param buf shared buffer.
|
| 198 | * \return zero on success or a negative number on failure.
|
| 199 |
|
| 200 | static int backend_unmap(struct xen_front_pgdir_shbuf *buf)
|
| 201 | {
|
| 202 | struct gnttab_unmap_grant_ref *unmap_ops;
|
| 203 | int i, ret;
|
| 204 |
|
| 205 | if (!buf->pages || !buf->backend_map_handles || !buf->grefs)
|
| 206 | return 0;
|
| 207 |
|
| 208 | unmap_ops = kzalloc_objs(*unmap_ops, buf->num_pages);
|
| 209 | if (!unmap_ops)
|
| 210 | return -ENOMEM;
|
| 211 |
|
| 212 | for (i = 0; i < buf->num_pages; i++) {
|
| 213 | phys_addr_t addr;
|
| 214 |
|
| 215 | addr = xen_page_to_vaddr(buf->pages[i]);
|
| 216 | gnttab_set_unmap_op(&unmap_ops[i], addr, GNTMAP_host_map,
|
| 217 | buf->backend_map_handles[i]);
|
| 218 | }
|
| 219 |
|
| 220 | ret = gnttab_unmap_refs(unmap_ops, NULL, buf->pages,
|
| 221 | buf->num_pages);
|
| 222 |
|
| 223 | for (i = 0; i < buf->num_pages; i++) {
|
| 224 | if (unlikely(unmap_ops[i].status != GNTST_okay))
|
| 225 | dev_err(&buf->xb_dev->dev,
|
| 226 | "Failed to unmap page %d: %d\n",
|
| 227 | i, unmap_ops[i].status);
|
| 228 | }
|
| 229 |
|
| 230 | if (ret)
|
| 231 | dev_err(&buf->xb_dev->dev,
|
| 232 | "Failed to unmap grant references, ret %d", ret);
|
| 233 |
|
| 234 | kfree(unmap_ops);
|
| 235 | kfree(buf->backend_map_handles);
|
| 236 | buf->backend_map_handles = NULL;
|
| 237 | return ret;
|
| 238 | }
|
| 239 |
|
| 240 | |
| 241 | * Map the buffer with grant references provided by the backend.
|
| 242 | *
|
| 243 | * \param buf shared buffer.
|
| 244 | * \return zero on success or a negative number on failure.
|
| 245 |
|
| 246 | static int backend_map(struct xen_front_pgdir_shbuf *buf)
|
| 247 | {
|
| 248 | struct gnttab_map_grant_ref *map_ops = NULL;
|
| 249 | unsigned char *ptr;
|
| 250 | int ret, cur_gref, cur_dir_page, cur_page, grefs_left;
|
| 251 |
|
| 252 | map_ops = kzalloc_objs(*map_ops, buf->num_pages);
|
| 253 | if (!map_ops)
|
| 254 | return -ENOMEM;
|
| 255 |
|
| 256 | buf->backend_map_handles = kzalloc_objs(*buf->backend_map_handles,
|
| 257 | buf->num_pages);
|
| 258 | if (!buf->backend_map_handles) {
|
| 259 | kfree(map_ops);
|
| 260 | return -ENOMEM;
|
| 261 | }
|
| 262 |
|
| 263 | |
| 264 | * Read page directory to get grefs from the backend: for external
|
| 265 | * buffer we only allocate buf->grefs for the page directory,
|
| 266 | * so buf->num_grefs has number of pages in the page directory itself.
|
| 267 |
|
| 268 | ptr = buf->directory;
|
| 269 | grefs_left = buf->num_pages;
|
| 270 | cur_page = 0;
|
| 271 | for (cur_dir_page = 0; cur_dir_page < buf->num_grefs; cur_dir_page++) {
|
| 272 | struct xen_page_directory *page_dir =
|
| 273 | (struct xen_page_directory *)ptr;
|
| 274 | int to_copy = XEN_NUM_GREFS_PER_PAGE;
|
| 275 |
|
| 276 | if (to_copy > grefs_left)
|
| 277 | to_copy = grefs_left;
|
| 278 |
|
| 279 | for (cur_gref = 0; cur_gref < to_copy; cur_gref++) {
|
| 280 | phys_addr_t addr;
|
| 281 |
|
| 282 | addr = xen_page_to_vaddr(buf->pages[cur_page]);
|
| 283 | gnttab_set_map_op(&map_ops[cur_page], addr,
|
| 284 | GNTMAP_host_map,
|
| 285 | page_dir->gref[cur_gref],
|
| 286 | buf->xb_dev->otherend_id);
|
| 287 | cur_page++;
|
| 288 | }
|
| 289 |
|
| 290 | grefs_left -= to_copy;
|
| 291 | ptr += PAGE_SIZE;
|
| 292 | }
|
| 293 | ret = gnttab_map_refs(map_ops, NULL, buf->pages, buf->num_pages);
|
| 294 |
|
| 295 |
|
| 296 | for (cur_page = 0; cur_page < buf->num_pages; cur_page++) {
|
| 297 | if (likely(map_ops[cur_page].status == GNTST_okay)) {
|
| 298 | buf->backend_map_handles[cur_page] =
|
| 299 | map_ops[cur_page].handle;
|
| 300 | } else {
|
| 301 | buf->backend_map_handles[cur_page] =
|
| 302 | INVALID_GRANT_HANDLE;
|
| 303 | if (!ret)
|
| 304 | ret = -ENXIO;
|
| 305 | dev_err(&buf->xb_dev->dev,
|
| 306 | "Failed to map page %d: %d\n",
|
| 307 | cur_page, map_ops[cur_page].status);
|
| 308 | }
|
| 309 | }
|
| 310 |
|
| 311 | if (ret) {
|
| 312 | dev_err(&buf->xb_dev->dev,
|
| 313 | "Failed to map grant references, ret %d", ret);
|
| 314 | backend_unmap(buf);
|
| 315 | }
|
| 316 |
|
| 317 | kfree(map_ops);
|
| 318 | return ret;
|
| 319 | }
|
| 320 |
|
| 321 | |
| 322 | * Fill page directory with grant references to the pages of the
|
| 323 | * page directory itself.
|
| 324 | *
|
| 325 | * The grant references to the buffer pages are provided by the
|
| 326 | * backend in this case.
|
| 327 | *
|
| 328 | * \param buf shared buffer.
|
| 329 |
|
| 330 | static void backend_fill_page_dir(struct xen_front_pgdir_shbuf *buf)
|
| 331 | {
|
| 332 | struct xen_page_directory *page_dir;
|
| 333 | unsigned char *ptr;
|
| 334 | int i, num_pages_dir;
|
| 335 |
|
| 336 | ptr = buf->directory;
|
| 337 | num_pages_dir = get_num_pages_dir(buf);
|
| 338 |
|
| 339 |
|
| 340 | for (i = 0; i < num_pages_dir - 1; i++) {
|
| 341 | page_dir = (struct xen_page_directory *)ptr;
|
| 342 |
|
| 343 | page_dir->gref_dir_next_page = buf->grefs[i + 1];
|
| 344 | ptr += PAGE_SIZE;
|
| 345 | }
|
| 346 |
|
| 347 | page_dir = (struct xen_page_directory *)ptr;
|
| 348 | page_dir->gref_dir_next_page = XEN_GREF_LIST_END;
|
| 349 | }
|
| 350 |
|
| 351 | |
| 352 | * Fill page directory with grant references to the pages of the
|
| 353 | * page directory and the buffer we share with the backend.
|
| 354 | *
|
| 355 | * \param buf shared buffer.
|
| 356 |
|
| 357 | static void guest_fill_page_dir(struct xen_front_pgdir_shbuf *buf)
|
| 358 | {
|
| 359 | unsigned char *ptr;
|
| 360 | int cur_gref, grefs_left, to_copy, i, num_pages_dir;
|
| 361 |
|
| 362 | ptr = buf->directory;
|
| 363 | num_pages_dir = get_num_pages_dir(buf);
|
| 364 |
|
| 365 | |
| 366 | * While copying, skip grefs at start, they are for pages
|
| 367 | * granted for the page directory itself.
|
| 368 |
|
| 369 | cur_gref = num_pages_dir;
|
| 370 | grefs_left = buf->num_pages;
|
| 371 | for (i = 0; i < num_pages_dir; i++) {
|
| 372 | struct xen_page_directory *page_dir =
|
| 373 | (struct xen_page_directory *)ptr;
|
| 374 |
|
| 375 | if (grefs_left <= XEN_NUM_GREFS_PER_PAGE) {
|
| 376 | to_copy = grefs_left;
|
| 377 | page_dir->gref_dir_next_page = XEN_GREF_LIST_END;
|
| 378 | } else {
|
| 379 | to_copy = XEN_NUM_GREFS_PER_PAGE;
|
| 380 | page_dir->gref_dir_next_page = buf->grefs[i + 1];
|
| 381 | }
|
| 382 | memcpy(&page_dir->gref, &buf->grefs[cur_gref],
|
| 383 | to_copy * sizeof(grant_ref_t));
|
| 384 | ptr += PAGE_SIZE;
|
| 385 | grefs_left -= to_copy;
|
| 386 | cur_gref += to_copy;
|
| 387 | }
|
| 388 | }
|
| 389 |
|
| 390 | |
| 391 | * Grant references to the frontend's buffer pages.
|
| 392 | *
|
| 393 | * These will be shared with the backend, so it can
|
| 394 | * access the buffer's data.
|
| 395 | *
|
| 396 | * \param buf shared buffer.
|
| 397 | * \return zero on success or a negative number on failure.
|
| 398 |
|
| 399 | static int guest_grant_refs_for_buffer(struct xen_front_pgdir_shbuf *buf,
|
| 400 | grant_ref_t *priv_gref_head,
|
| 401 | int gref_idx)
|
| 402 | {
|
| 403 | int i, cur_ref, otherend_id;
|
| 404 |
|
| 405 | otherend_id = buf->xb_dev->otherend_id;
|
| 406 | for (i = 0; i < buf->num_pages; i++) {
|
| 407 | cur_ref = gnttab_claim_grant_reference(priv_gref_head);
|
| 408 | if (cur_ref < 0)
|
| 409 | return cur_ref;
|
| 410 |
|
| 411 | gnttab_grant_foreign_access_ref(cur_ref, otherend_id,
|
| 412 | xen_page_to_gfn(buf->pages[i]),
|
| 413 | 0);
|
| 414 | buf->grefs[gref_idx++] = cur_ref;
|
| 415 | }
|
| 416 | return 0;
|
| 417 | }
|
| 418 |
|
| 419 | |
| 420 | * Grant all the references needed to share the buffer.
|
| 421 | *
|
| 422 | * Grant references to the page directory pages and, if
|
| 423 | * needed, also to the pages of the shared buffer data.
|
| 424 | *
|
| 425 | * \param buf shared buffer.
|
| 426 | * \return zero on success or a negative number on failure.
|
| 427 |
|
| 428 | static int grant_references(struct xen_front_pgdir_shbuf *buf)
|
| 429 | {
|
| 430 | grant_ref_t priv_gref_head;
|
| 431 | int ret, i, j, cur_ref;
|
| 432 | int otherend_id, num_pages_dir;
|
| 433 |
|
| 434 | ret = gnttab_alloc_grant_references(buf->num_grefs, &priv_gref_head);
|
| 435 | if (ret < 0) {
|
| 436 | dev_err(&buf->xb_dev->dev,
|
| 437 | "Cannot allocate grant references\n");
|
| 438 | return ret;
|
| 439 | }
|
| 440 |
|
| 441 | otherend_id = buf->xb_dev->otherend_id;
|
| 442 | j = 0;
|
| 443 | num_pages_dir = get_num_pages_dir(buf);
|
| 444 | for (i = 0; i < num_pages_dir; i++) {
|
| 445 | unsigned long frame;
|
| 446 |
|
| 447 | cur_ref = gnttab_claim_grant_reference(&priv_gref_head);
|
| 448 | if (cur_ref < 0) {
|
| 449 | ret = cur_ref;
|
| 450 | goto out_free_refs;
|
| 451 | }
|
| 452 |
|
| 453 | frame = xen_page_to_gfn(virt_to_page(buf->directory +
|
| 454 | PAGE_SIZE * i));
|
| 455 | gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0);
|
| 456 | buf->grefs[j++] = cur_ref;
|
| 457 | }
|
| 458 |
|
| 459 | if (buf->ops->grant_refs_for_buffer) {
|
| 460 | ret = buf->ops->grant_refs_for_buffer(buf, &priv_gref_head, j);
|
| 461 | if (ret)
|
| 462 | goto out_free_refs;
|
| 463 | }
|
| 464 |
|
| 465 | ret = 0;
|
| 466 | out_free_refs:
|
| 467 | gnttab_free_grant_references(priv_gref_head);
|
| 468 | return ret;
|
| 469 | }
|
| 470 |
|
| 471 | |
| 472 | * Allocate all required structures to mange shared buffer.
|
| 473 | *
|
| 474 | * \param buf shared buffer.
|
| 475 | * \return zero on success or a negative number on failure.
|
| 476 |
|
| 477 | static int alloc_storage(struct xen_front_pgdir_shbuf *buf)
|
| 478 | {
|
| 479 | buf->grefs = kzalloc_objs(*buf->grefs, buf->num_grefs);
|
| 480 | if (!buf->grefs)
|
| 481 | return -ENOMEM;
|
| 482 |
|
| 483 | buf->directory = kcalloc(get_num_pages_dir(buf), PAGE_SIZE, GFP_KERNEL);
|
| 484 | if (!buf->directory)
|
| 485 | return -ENOMEM;
|
| 486 |
|
| 487 | return 0;
|
| 488 | }
|
| 489 |
|
| 490 | |
| 491 | * For backend allocated buffers we don't need grant_refs_for_buffer
|
| 492 | * as those grant references are allocated at backend side.
|
| 493 |
|
| 494 | static const struct xen_front_pgdir_shbuf_ops backend_ops = {
|
| 495 | .calc_num_grefs = backend_calc_num_grefs,
|
| 496 | .fill_page_dir = backend_fill_page_dir,
|
| 497 | .map = backend_map,
|
| 498 | .unmap = backend_unmap
|
| 499 | };
|
| 500 |
|
| 501 | |
| 502 | * For locally granted references we do not need to map/unmap
|
| 503 | * the references.
|
| 504 |
|
| 505 | static const struct xen_front_pgdir_shbuf_ops local_ops = {
|
| 506 | .calc_num_grefs = guest_calc_num_grefs,
|
| 507 | .fill_page_dir = guest_fill_page_dir,
|
| 508 | .grant_refs_for_buffer = guest_grant_refs_for_buffer,
|
| 509 | };
|
| 510 |
|
| 511 | |
| 512 | * Allocate a new instance of a shared buffer.
|
| 513 | *
|
| 514 | * \param cfg configuration to be used while allocating a new shared buffer.
|
| 515 | * \return zero on success or a negative number on failure.
|
| 516 |
|
| 517 | int xen_front_pgdir_shbuf_alloc(struct xen_front_pgdir_shbuf_cfg *cfg)
|
| 518 | {
|
| 519 | struct xen_front_pgdir_shbuf *buf = cfg->pgdir;
|
| 520 | int ret;
|
| 521 |
|
| 522 | if (cfg->be_alloc)
|
| 523 | buf->ops = &backend_ops;
|
| 524 | else
|
| 525 | buf->ops = &local_ops;
|
| 526 | buf->xb_dev = cfg->xb_dev;
|
| 527 | buf->num_pages = cfg->num_pages;
|
| 528 | buf->pages = cfg->pages;
|
| 529 |
|
| 530 | buf->ops->calc_num_grefs(buf);
|
| 531 |
|
| 532 | ret = alloc_storage(buf);
|
| 533 | if (ret)
|
| 534 | goto fail;
|
| 535 |
|
| 536 | ret = grant_references(buf);
|
| 537 | if (ret)
|
| 538 | goto fail;
|
| 539 |
|
| 540 | buf->ops->fill_page_dir(buf);
|
| 541 |
|
| 542 | return 0;
|
| 543 |
|
| 544 | fail:
|
| 545 | xen_front_pgdir_shbuf_free(buf);
|
| 546 | return ret;
|
| 547 | }
|
| 548 | EXPORT_SYMBOL_GPL(xen_front_pgdir_shbuf_alloc);
|
| 549 |
|
| 550 | MODULE_DESCRIPTION("Xen frontend/backend page directory based "
|
| 551 | "shared buffer handling");
|
| 552 | MODULE_AUTHOR("Oleksandr Andrushchenko");
|
| 553 | MODULE_LICENSE("GPL");
|