1 /* 2 * Multifd qpl compression accelerator implementation 3 * 4 * Copyright (c) 2023 Intel Corporation 5 * 6 * Authors: 7 * Yuan Liu<yuan1.liu@intel.com> 8 * 9 * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 * See the COPYING file in the top-level directory. 11 */ 12 13 #include "qemu/osdep.h" 14 #include "qemu/module.h" 15 #include "qapi/error.h" 16 #include "qapi/qapi-types-migration.h" 17 #include "exec/ramblock.h" 18 #include "multifd.h" 19 #include "qpl/qpl.h" 20 21 /* Maximum number of retries to resubmit a job if IAA work queues are full */ 22 #define MAX_SUBMIT_RETRY_NUM (3) 23 24 typedef struct { 25 /* the QPL hardware path job */ 26 qpl_job *job; 27 /* indicates if fallback to software path is required */ 28 bool fallback_sw_path; 29 /* output data from the software path */ 30 uint8_t *sw_output; 31 /* output data length from the software path */ 32 uint32_t sw_output_len; 33 } QplHwJob; 34 35 typedef struct { 36 /* array of hardware jobs, the number of jobs equals the number pages */ 37 QplHwJob *hw_jobs; 38 /* the QPL software job for the slow path and software fallback */ 39 qpl_job *sw_job; 40 /* the number of pages that the QPL needs to process at one time */ 41 uint32_t page_num; 42 /* array of compressed page buffers */ 43 uint8_t *zbuf; 44 /* array of compressed page lengths */ 45 uint32_t *zlen; 46 /* the status of the hardware device */ 47 bool hw_avail; 48 } QplData; 49 50 /** 51 * check_hw_avail: check if IAA hardware is available 52 * 53 * If the IAA hardware does not exist or is unavailable, 54 * the QPL hardware job initialization will fail. 55 * 56 * Returns true if IAA hardware is available, otherwise false. 57 * 58 * @job_size: indicates the hardware job size if hardware is available 59 */ 60 static bool check_hw_avail(uint32_t *job_size) 61 { 62 qpl_path_t path = qpl_path_hardware; 63 uint32_t size = 0; 64 qpl_job *job; 65 66 if (qpl_get_job_size(path, &size) != QPL_STS_OK) { 67 return false; 68 } 69 assert(size > 0); 70 job = g_malloc0(size); 71 if (qpl_init_job(path, job) != QPL_STS_OK) { 72 g_free(job); 73 return false; 74 } 75 g_free(job); 76 *job_size = size; 77 return true; 78 } 79 80 /** 81 * multifd_qpl_free_sw_job: clean up software job 82 * 83 * Free the software job resources. 84 * 85 * @qpl: pointer to the QplData structure 86 */ 87 static void multifd_qpl_free_sw_job(QplData *qpl) 88 { 89 assert(qpl); 90 if (qpl->sw_job) { 91 qpl_fini_job(qpl->sw_job); 92 g_free(qpl->sw_job); 93 qpl->sw_job = NULL; 94 } 95 } 96 97 /** 98 * multifd_qpl_free_jobs: clean up hardware jobs 99 * 100 * Free all hardware job resources. 101 * 102 * @qpl: pointer to the QplData structure 103 */ 104 static void multifd_qpl_free_hw_job(QplData *qpl) 105 { 106 assert(qpl); 107 if (qpl->hw_jobs) { 108 for (int i = 0; i < qpl->page_num; i++) { 109 qpl_fini_job(qpl->hw_jobs[i].job); 110 g_free(qpl->hw_jobs[i].job); 111 qpl->hw_jobs[i].job = NULL; 112 } 113 g_free(qpl->hw_jobs); 114 qpl->hw_jobs = NULL; 115 } 116 } 117 118 /** 119 * multifd_qpl_init_sw_job: initialize a software job 120 * 121 * Use the QPL software path to initialize a job 122 * 123 * @qpl: pointer to the QplData structure 124 * @errp: pointer to an error 125 */ 126 static int multifd_qpl_init_sw_job(QplData *qpl, Error **errp) 127 { 128 qpl_path_t path = qpl_path_software; 129 uint32_t size = 0; 130 qpl_job *job = NULL; 131 qpl_status status; 132 133 status = qpl_get_job_size(path, &size); 134 if (status != QPL_STS_OK) { 135 error_setg(errp, "qpl_get_job_size failed with error %d", status); 136 return -1; 137 } 138 job = g_malloc0(size); 139 status = qpl_init_job(path, job); 140 if (status != QPL_STS_OK) { 141 error_setg(errp, "qpl_init_job failed with error %d", status); 142 g_free(job); 143 return -1; 144 } 145 qpl->sw_job = job; 146 return 0; 147 } 148 149 /** 150 * multifd_qpl_init_jobs: initialize hardware jobs 151 * 152 * Use the QPL hardware path to initialize jobs 153 * 154 * @qpl: pointer to the QplData structure 155 * @size: the size of QPL hardware path job 156 * @errp: pointer to an error 157 */ 158 static void multifd_qpl_init_hw_job(QplData *qpl, uint32_t size, Error **errp) 159 { 160 qpl_path_t path = qpl_path_hardware; 161 qpl_job *job = NULL; 162 qpl_status status; 163 164 qpl->hw_jobs = g_new0(QplHwJob, qpl->page_num); 165 for (int i = 0; i < qpl->page_num; i++) { 166 job = g_malloc0(size); 167 status = qpl_init_job(path, job); 168 /* the job initialization should succeed after check_hw_avail */ 169 assert(status == QPL_STS_OK); 170 qpl->hw_jobs[i].job = job; 171 } 172 } 173 174 /** 175 * multifd_qpl_init: initialize QplData structure 176 * 177 * Allocate and initialize a QplData structure 178 * 179 * Returns a QplData pointer on success or NULL on error 180 * 181 * @num: the number of pages 182 * @size: the page size 183 * @errp: pointer to an error 184 */ 185 static QplData *multifd_qpl_init(uint32_t num, uint32_t size, Error **errp) 186 { 187 uint32_t job_size = 0; 188 QplData *qpl; 189 190 qpl = g_new0(QplData, 1); 191 qpl->page_num = num; 192 if (multifd_qpl_init_sw_job(qpl, errp) != 0) { 193 g_free(qpl); 194 return NULL; 195 } 196 qpl->hw_avail = check_hw_avail(&job_size); 197 if (qpl->hw_avail) { 198 multifd_qpl_init_hw_job(qpl, job_size, errp); 199 } 200 qpl->zbuf = g_malloc0(size * num); 201 qpl->zlen = g_new0(uint32_t, num); 202 return qpl; 203 } 204 205 /** 206 * multifd_qpl_deinit: clean up QplData structure 207 * 208 * Free jobs, buffers and the QplData structure 209 * 210 * @qpl: pointer to the QplData structure 211 */ 212 static void multifd_qpl_deinit(QplData *qpl) 213 { 214 if (qpl) { 215 multifd_qpl_free_sw_job(qpl); 216 multifd_qpl_free_hw_job(qpl); 217 g_free(qpl->zbuf); 218 g_free(qpl->zlen); 219 g_free(qpl); 220 } 221 } 222 223 /** 224 * multifd_qpl_send_setup: set up send side 225 * 226 * Set up the channel with QPL compression. 227 * 228 * Returns 0 on success or -1 on error 229 * 230 * @p: Params for the channel being used 231 * @errp: pointer to an error 232 */ 233 static int multifd_qpl_send_setup(MultiFDSendParams *p, Error **errp) 234 { 235 QplData *qpl; 236 uint32_t page_size = multifd_ram_page_size(); 237 uint32_t page_count = multifd_ram_page_count(); 238 239 qpl = multifd_qpl_init(page_count, page_size, errp); 240 if (!qpl) { 241 return -1; 242 } 243 p->compress_data = qpl; 244 245 /* 246 * the page will be compressed independently and sent using an IOV. The 247 * additional two IOVs are used to store packet header and compressed data 248 * length 249 */ 250 p->iov = g_new0(struct iovec, page_count + 2); 251 return 0; 252 } 253 254 /** 255 * multifd_qpl_send_cleanup: clean up send side 256 * 257 * Close the channel and free memory. 258 * 259 * @p: Params for the channel being used 260 * @errp: pointer to an error 261 */ 262 static void multifd_qpl_send_cleanup(MultiFDSendParams *p, Error **errp) 263 { 264 multifd_qpl_deinit(p->compress_data); 265 p->compress_data = NULL; 266 g_free(p->iov); 267 p->iov = NULL; 268 } 269 270 /** 271 * multifd_qpl_prepare_job: prepare the job 272 * 273 * Set the QPL job parameters and properties. 274 * 275 * @job: pointer to the qpl_job structure 276 * @is_compression: indicates compression and decompression 277 * @input: pointer to the input data buffer 278 * @input_len: the length of the input data 279 * @output: pointer to the output data buffer 280 * @output_len: the length of the output data 281 */ 282 static void multifd_qpl_prepare_job(qpl_job *job, bool is_compression, 283 uint8_t *input, uint32_t input_len, 284 uint8_t *output, uint32_t output_len) 285 { 286 job->op = is_compression ? qpl_op_compress : qpl_op_decompress; 287 job->next_in_ptr = input; 288 job->next_out_ptr = output; 289 job->available_in = input_len; 290 job->available_out = output_len; 291 job->flags = QPL_FLAG_FIRST | QPL_FLAG_LAST | QPL_FLAG_OMIT_VERIFY; 292 /* only supports compression level 1 */ 293 job->level = 1; 294 } 295 296 /** 297 * multifd_qpl_prepare_comp_job: prepare the compression job 298 * 299 * Set the compression job parameters and properties. 300 * 301 * @job: pointer to the qpl_job structure 302 * @input: pointer to the input data buffer 303 * @output: pointer to the output data buffer 304 * @size: the page size 305 */ 306 static void multifd_qpl_prepare_comp_job(qpl_job *job, uint8_t *input, 307 uint8_t *output, uint32_t size) 308 { 309 /* 310 * Set output length to less than the page size to force the job to 311 * fail in case it compresses to a larger size. We'll send that page 312 * without compression and skip the decompression operation on the 313 * destination. 314 */ 315 multifd_qpl_prepare_job(job, true, input, size, output, size - 1); 316 } 317 318 /** 319 * multifd_qpl_prepare_decomp_job: prepare the decompression job 320 * 321 * Set the decompression job parameters and properties. 322 * 323 * @job: pointer to the qpl_job structure 324 * @input: pointer to the input data buffer 325 * @len: the length of the input data 326 * @output: pointer to the output data buffer 327 * @size: the page size 328 */ 329 static void multifd_qpl_prepare_decomp_job(qpl_job *job, uint8_t *input, 330 uint32_t len, uint8_t *output, 331 uint32_t size) 332 { 333 multifd_qpl_prepare_job(job, false, input, len, output, size); 334 } 335 336 /** 337 * multifd_qpl_fill_iov: fill in the IOV 338 * 339 * Fill in the QPL packet IOV 340 * 341 * @p: Params for the channel being used 342 * @data: pointer to the IOV data 343 * @len: The length of the IOV data 344 */ 345 static void multifd_qpl_fill_iov(MultiFDSendParams *p, uint8_t *data, 346 uint32_t len) 347 { 348 p->iov[p->iovs_num].iov_base = data; 349 p->iov[p->iovs_num].iov_len = len; 350 p->iovs_num++; 351 p->next_packet_size += len; 352 } 353 354 /** 355 * multifd_qpl_fill_packet: fill the compressed page into the QPL packet 356 * 357 * Fill the compressed page length and IOV into the QPL packet 358 * 359 * @idx: The index of the compressed length array 360 * @p: Params for the channel being used 361 * @data: pointer to the compressed page buffer 362 * @len: The length of the compressed page 363 */ 364 static void multifd_qpl_fill_packet(uint32_t idx, MultiFDSendParams *p, 365 uint8_t *data, uint32_t len) 366 { 367 QplData *qpl = p->compress_data; 368 369 qpl->zlen[idx] = cpu_to_be32(len); 370 multifd_qpl_fill_iov(p, data, len); 371 } 372 373 /** 374 * multifd_qpl_submit_job: submit a job to the hardware 375 * 376 * Submit a QPL hardware job to the IAA device 377 * 378 * Returns true if the job is submitted successfully, otherwise false. 379 * 380 * @job: pointer to the qpl_job structure 381 */ 382 static bool multifd_qpl_submit_job(qpl_job *job) 383 { 384 qpl_status status; 385 uint32_t num = 0; 386 387 retry: 388 status = qpl_submit_job(job); 389 if (status == QPL_STS_QUEUES_ARE_BUSY_ERR) { 390 if (num < MAX_SUBMIT_RETRY_NUM) { 391 num++; 392 goto retry; 393 } 394 } 395 return (status == QPL_STS_OK); 396 } 397 398 /** 399 * multifd_qpl_compress_pages_slow_path: compress pages using slow path 400 * 401 * Compress the pages using software. If compression fails, the uncompressed 402 * page will be sent. 403 * 404 * @p: Params for the channel being used 405 */ 406 static void multifd_qpl_compress_pages_slow_path(MultiFDSendParams *p) 407 { 408 QplData *qpl = p->compress_data; 409 MultiFDPages_t *pages = p->pages; 410 uint32_t size = p->page_size; 411 qpl_job *job = qpl->sw_job; 412 uint8_t *zbuf = qpl->zbuf; 413 uint8_t *buf; 414 415 for (int i = 0; i < pages->normal_num; i++) { 416 buf = pages->block->host + pages->offset[i]; 417 multifd_qpl_prepare_comp_job(job, buf, zbuf, size); 418 if (qpl_execute_job(job) == QPL_STS_OK) { 419 multifd_qpl_fill_packet(i, p, zbuf, job->total_out); 420 } else { 421 /* send the uncompressed page */ 422 multifd_qpl_fill_packet(i, p, buf, size); 423 } 424 zbuf += size; 425 } 426 } 427 428 /** 429 * multifd_qpl_compress_pages: compress pages 430 * 431 * Submit the pages to the IAA hardware for compression. If hardware 432 * compression fails, it falls back to software compression. If software 433 * compression also fails, the uncompressed page is sent. 434 * 435 * @p: Params for the channel being used 436 */ 437 static void multifd_qpl_compress_pages(MultiFDSendParams *p) 438 { 439 QplData *qpl = p->compress_data; 440 MultiFDPages_t *pages = p->pages; 441 uint32_t size = p->page_size; 442 QplHwJob *hw_job; 443 uint8_t *buf; 444 uint8_t *zbuf; 445 446 for (int i = 0; i < pages->normal_num; i++) { 447 buf = pages->block->host + pages->offset[i]; 448 zbuf = qpl->zbuf + (size * i); 449 hw_job = &qpl->hw_jobs[i]; 450 multifd_qpl_prepare_comp_job(hw_job->job, buf, zbuf, size); 451 if (multifd_qpl_submit_job(hw_job->job)) { 452 hw_job->fallback_sw_path = false; 453 } else { 454 /* 455 * The IAA work queue is full, any immediate subsequent job 456 * submission is likely to fail, sending the page via the QPL 457 * software path at this point gives us a better chance of 458 * finding the queue open for the next pages. 459 */ 460 hw_job->fallback_sw_path = true; 461 multifd_qpl_prepare_comp_job(qpl->sw_job, buf, zbuf, size); 462 if (qpl_execute_job(qpl->sw_job) == QPL_STS_OK) { 463 hw_job->sw_output = zbuf; 464 hw_job->sw_output_len = qpl->sw_job->total_out; 465 } else { 466 hw_job->sw_output = buf; 467 hw_job->sw_output_len = size; 468 } 469 } 470 } 471 472 for (int i = 0; i < pages->normal_num; i++) { 473 buf = pages->block->host + pages->offset[i]; 474 zbuf = qpl->zbuf + (size * i); 475 hw_job = &qpl->hw_jobs[i]; 476 if (hw_job->fallback_sw_path) { 477 multifd_qpl_fill_packet(i, p, hw_job->sw_output, 478 hw_job->sw_output_len); 479 continue; 480 } 481 if (qpl_wait_job(hw_job->job) == QPL_STS_OK) { 482 multifd_qpl_fill_packet(i, p, zbuf, hw_job->job->total_out); 483 } else { 484 /* send the uncompressed page */ 485 multifd_qpl_fill_packet(i, p, buf, size); 486 } 487 } 488 } 489 490 /** 491 * multifd_qpl_send_prepare: prepare data to be able to send 492 * 493 * Create a compressed buffer with all the pages that we are going to 494 * send. 495 * 496 * Returns 0 on success or -1 on error 497 * 498 * @p: Params for the channel being used 499 * @errp: pointer to an error 500 */ 501 static int multifd_qpl_send_prepare(MultiFDSendParams *p, Error **errp) 502 { 503 QplData *qpl = p->compress_data; 504 MultiFDPages_t *pages = p->pages; 505 uint32_t len = 0; 506 507 if (!multifd_send_prepare_common(p)) { 508 goto out; 509 } 510 511 /* The first IOV is used to store the compressed page lengths */ 512 len = pages->normal_num * sizeof(uint32_t); 513 multifd_qpl_fill_iov(p, (uint8_t *) qpl->zlen, len); 514 if (qpl->hw_avail) { 515 multifd_qpl_compress_pages(p); 516 } else { 517 multifd_qpl_compress_pages_slow_path(p); 518 } 519 520 out: 521 p->flags |= MULTIFD_FLAG_QPL; 522 multifd_send_fill_packet(p); 523 return 0; 524 } 525 526 /** 527 * multifd_qpl_recv_setup: set up receive side 528 * 529 * Create the compressed channel and buffer. 530 * 531 * Returns 0 on success or -1 on error 532 * 533 * @p: Params for the channel being used 534 * @errp: pointer to an error 535 */ 536 static int multifd_qpl_recv_setup(MultiFDRecvParams *p, Error **errp) 537 { 538 QplData *qpl; 539 uint32_t page_size = multifd_ram_page_size(); 540 uint32_t page_count = multifd_ram_page_count(); 541 542 qpl = multifd_qpl_init(page_count, page_size, errp); 543 if (!qpl) { 544 return -1; 545 } 546 p->compress_data = qpl; 547 return 0; 548 } 549 550 /** 551 * multifd_qpl_recv_cleanup: set up receive side 552 * 553 * Close the channel and free memory. 554 * 555 * @p: Params for the channel being used 556 */ 557 static void multifd_qpl_recv_cleanup(MultiFDRecvParams *p) 558 { 559 multifd_qpl_deinit(p->compress_data); 560 p->compress_data = NULL; 561 } 562 563 /** 564 * multifd_qpl_process_and_check_job: process and check a QPL job 565 * 566 * Process the job and check whether the job output length is the 567 * same as the specified length 568 * 569 * Returns true if the job execution succeeded and the output length 570 * is equal to the specified length, otherwise false. 571 * 572 * @job: pointer to the qpl_job structure 573 * @is_hardware: indicates whether the job is a hardware job 574 * @len: Specified output length 575 * @errp: pointer to an error 576 */ 577 static bool multifd_qpl_process_and_check_job(qpl_job *job, bool is_hardware, 578 uint32_t len, Error **errp) 579 { 580 qpl_status status; 581 582 status = (is_hardware ? qpl_wait_job(job) : qpl_execute_job(job)); 583 if (status != QPL_STS_OK) { 584 error_setg(errp, "qpl job failed with error %d", status); 585 return false; 586 } 587 if (job->total_out != len) { 588 error_setg(errp, "qpl decompressed len %u, expected len %u", 589 job->total_out, len); 590 return false; 591 } 592 return true; 593 } 594 595 /** 596 * multifd_qpl_decompress_pages_slow_path: decompress pages using slow path 597 * 598 * Decompress the pages using software 599 * 600 * Returns 0 on success or -1 on error 601 * 602 * @p: Params for the channel being used 603 * @errp: pointer to an error 604 */ 605 static int multifd_qpl_decompress_pages_slow_path(MultiFDRecvParams *p, 606 Error **errp) 607 { 608 QplData *qpl = p->compress_data; 609 uint32_t size = p->page_size; 610 qpl_job *job = qpl->sw_job; 611 uint8_t *zbuf = qpl->zbuf; 612 uint8_t *addr; 613 uint32_t len; 614 615 for (int i = 0; i < p->normal_num; i++) { 616 len = qpl->zlen[i]; 617 addr = p->host + p->normal[i]; 618 /* the page is uncompressed, load it */ 619 if (len == size) { 620 memcpy(addr, zbuf, size); 621 zbuf += size; 622 continue; 623 } 624 multifd_qpl_prepare_decomp_job(job, zbuf, len, addr, size); 625 if (!multifd_qpl_process_and_check_job(job, false, size, errp)) { 626 return -1; 627 } 628 zbuf += len; 629 } 630 return 0; 631 } 632 633 /** 634 * multifd_qpl_decompress_pages: decompress pages 635 * 636 * Decompress the pages using the IAA hardware. If hardware 637 * decompression fails, it falls back to software decompression. 638 * 639 * Returns 0 on success or -1 on error 640 * 641 * @p: Params for the channel being used 642 * @errp: pointer to an error 643 */ 644 static int multifd_qpl_decompress_pages(MultiFDRecvParams *p, Error **errp) 645 { 646 QplData *qpl = p->compress_data; 647 uint32_t size = p->page_size; 648 uint8_t *zbuf = qpl->zbuf; 649 uint8_t *addr; 650 uint32_t len; 651 qpl_job *job; 652 653 for (int i = 0; i < p->normal_num; i++) { 654 addr = p->host + p->normal[i]; 655 len = qpl->zlen[i]; 656 /* the page is uncompressed if received length equals the page size */ 657 if (len == size) { 658 memcpy(addr, zbuf, size); 659 zbuf += size; 660 continue; 661 } 662 663 job = qpl->hw_jobs[i].job; 664 multifd_qpl_prepare_decomp_job(job, zbuf, len, addr, size); 665 if (multifd_qpl_submit_job(job)) { 666 qpl->hw_jobs[i].fallback_sw_path = false; 667 } else { 668 /* 669 * The IAA work queue is full, any immediate subsequent job 670 * submission is likely to fail, sending the page via the QPL 671 * software path at this point gives us a better chance of 672 * finding the queue open for the next pages. 673 */ 674 qpl->hw_jobs[i].fallback_sw_path = true; 675 job = qpl->sw_job; 676 multifd_qpl_prepare_decomp_job(job, zbuf, len, addr, size); 677 if (!multifd_qpl_process_and_check_job(job, false, size, errp)) { 678 return -1; 679 } 680 } 681 zbuf += len; 682 } 683 684 for (int i = 0; i < p->normal_num; i++) { 685 /* ignore pages that have already been processed */ 686 if (qpl->zlen[i] == size || qpl->hw_jobs[i].fallback_sw_path) { 687 continue; 688 } 689 690 job = qpl->hw_jobs[i].job; 691 if (!multifd_qpl_process_and_check_job(job, true, size, errp)) { 692 return -1; 693 } 694 } 695 return 0; 696 } 697 /** 698 * multifd_qpl_recv: read the data from the channel into actual pages 699 * 700 * Read the compressed buffer, and uncompress it into the actual 701 * pages. 702 * 703 * Returns 0 on success or -1 on error 704 * 705 * @p: Params for the channel being used 706 * @errp: pointer to an error 707 */ 708 static int multifd_qpl_recv(MultiFDRecvParams *p, Error **errp) 709 { 710 QplData *qpl = p->compress_data; 711 uint32_t in_size = p->next_packet_size; 712 uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK; 713 uint32_t len = 0; 714 uint32_t zbuf_len = 0; 715 int ret; 716 717 if (flags != MULTIFD_FLAG_QPL) { 718 error_setg(errp, "multifd %u: flags received %x flags expected %x", 719 p->id, flags, MULTIFD_FLAG_QPL); 720 return -1; 721 } 722 multifd_recv_zero_page_process(p); 723 if (!p->normal_num) { 724 assert(in_size == 0); 725 return 0; 726 } 727 728 /* read compressed page lengths */ 729 len = p->normal_num * sizeof(uint32_t); 730 assert(len < in_size); 731 ret = qio_channel_read_all(p->c, (void *) qpl->zlen, len, errp); 732 if (ret != 0) { 733 return ret; 734 } 735 for (int i = 0; i < p->normal_num; i++) { 736 qpl->zlen[i] = be32_to_cpu(qpl->zlen[i]); 737 assert(qpl->zlen[i] <= p->page_size); 738 zbuf_len += qpl->zlen[i]; 739 } 740 741 /* read compressed pages */ 742 assert(in_size == len + zbuf_len); 743 ret = qio_channel_read_all(p->c, (void *) qpl->zbuf, zbuf_len, errp); 744 if (ret != 0) { 745 return ret; 746 } 747 748 if (qpl->hw_avail) { 749 return multifd_qpl_decompress_pages(p, errp); 750 } 751 return multifd_qpl_decompress_pages_slow_path(p, errp); 752 } 753 754 static MultiFDMethods multifd_qpl_ops = { 755 .send_setup = multifd_qpl_send_setup, 756 .send_cleanup = multifd_qpl_send_cleanup, 757 .send_prepare = multifd_qpl_send_prepare, 758 .recv_setup = multifd_qpl_recv_setup, 759 .recv_cleanup = multifd_qpl_recv_cleanup, 760 .recv = multifd_qpl_recv, 761 }; 762 763 static void multifd_qpl_register(void) 764 { 765 multifd_register_ops(MULTIFD_COMPRESSION_QPL, &multifd_qpl_ops); 766 } 767 768 migration_init(multifd_qpl_register); 769