1 /*
2 * Multifd UADK compression accelerator implementation
3 *
4 * Copyright (c) 2024 Huawei Technologies R & D (UK) Ltd
5 *
6 * Authors:
7 * Shameer Kolothum <shameerali.kolothum.thodi@huawei.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 "exec/ramblock.h"
17 #include "migration.h"
18 #include "multifd.h"
19 #include "options.h"
20 #include "qemu/error-report.h"
21 #include "uadk/wd_comp.h"
22 #include "uadk/wd_sched.h"
23
24 struct wd_data {
25 handle_t handle;
26 uint8_t *buf;
27 uint32_t *buf_hdr;
28 };
29
uadk_hw_init(void)30 static bool uadk_hw_init(void)
31 {
32 char alg[] = "zlib";
33 int ret;
34
35 ret = wd_comp_init2(alg, SCHED_POLICY_RR, TASK_HW);
36 if (ret && ret != -WD_EEXIST) {
37 return false;
38 } else {
39 return true;
40 }
41 }
42
multifd_uadk_init_sess(uint32_t count,uint32_t page_size,bool compress,Error ** errp)43 static struct wd_data *multifd_uadk_init_sess(uint32_t count,
44 uint32_t page_size,
45 bool compress, Error **errp)
46 {
47 struct wd_comp_sess_setup ss = {0};
48 struct sched_params param = {0};
49 uint32_t size = count * page_size;
50 struct wd_data *wd;
51
52 wd = g_new0(struct wd_data, 1);
53
54 if (uadk_hw_init()) {
55 ss.alg_type = WD_ZLIB;
56 if (compress) {
57 ss.op_type = WD_DIR_COMPRESS;
58 /* Add an additional page for handling output > input */
59 size += page_size;
60 } else {
61 ss.op_type = WD_DIR_DECOMPRESS;
62 }
63 /* We use default level 1 compression and 4K window size */
64 param.type = ss.op_type;
65 ss.sched_param = ¶m;
66
67 wd->handle = wd_comp_alloc_sess(&ss);
68 if (!wd->handle) {
69 error_setg(errp, "multifd: failed wd_comp_alloc_sess");
70 goto out;
71 }
72 } else {
73 /* For CI test use */
74 warn_report_once("UADK hardware not available. Switch to no compression mode");
75 }
76
77 wd->buf = g_try_malloc(size);
78 if (!wd->buf) {
79 error_setg(errp, "multifd: out of mem for uadk buf");
80 goto out_free_sess;
81 }
82 wd->buf_hdr = g_new0(uint32_t, count);
83 return wd;
84
85 out_free_sess:
86 if (wd->handle) {
87 wd_comp_free_sess(wd->handle);
88 }
89 out:
90 wd_comp_uninit2();
91 g_free(wd);
92 return NULL;
93 }
94
multifd_uadk_uninit_sess(struct wd_data * wd)95 static void multifd_uadk_uninit_sess(struct wd_data *wd)
96 {
97 if (wd->handle) {
98 wd_comp_free_sess(wd->handle);
99 }
100 wd_comp_uninit2();
101 g_free(wd->buf);
102 g_free(wd->buf_hdr);
103 g_free(wd);
104 }
105
106 /**
107 * multifd_uadk_send_setup: setup send side
108 *
109 * Returns 0 for success or -1 for error
110 *
111 * @p: Params for the channel that we are using
112 * @errp: pointer to an error
113 */
multifd_uadk_send_setup(MultiFDSendParams * p,Error ** errp)114 static int multifd_uadk_send_setup(MultiFDSendParams *p, Error **errp)
115 {
116 struct wd_data *wd;
117
118 wd = multifd_uadk_init_sess(p->page_count, p->page_size, true, errp);
119 if (!wd) {
120 return -1;
121 }
122
123 p->compress_data = wd;
124 assert(p->iov == NULL);
125 /*
126 * Each page will be compressed independently and sent using an IOV. The
127 * additional two IOVs are used to store packet header and compressed data
128 * length
129 */
130
131 p->iov = g_new0(struct iovec, p->page_count + 2);
132 return 0;
133 }
134
135 /**
136 * multifd_uadk_send_cleanup: cleanup send side
137 *
138 * Close the channel and return memory.
139 *
140 * @p: Params for the channel that we are using
141 * @errp: pointer to an error
142 */
multifd_uadk_send_cleanup(MultiFDSendParams * p,Error ** errp)143 static void multifd_uadk_send_cleanup(MultiFDSendParams *p, Error **errp)
144 {
145 struct wd_data *wd = p->compress_data;
146
147 multifd_uadk_uninit_sess(wd);
148 p->compress_data = NULL;
149 g_free(p->iov);
150 p->iov = NULL;
151 }
152
prepare_next_iov(MultiFDSendParams * p,void * base,uint32_t len)153 static inline void prepare_next_iov(MultiFDSendParams *p, void *base,
154 uint32_t len)
155 {
156 p->iov[p->iovs_num].iov_base = (uint8_t *)base;
157 p->iov[p->iovs_num].iov_len = len;
158 p->next_packet_size += len;
159 p->iovs_num++;
160 }
161
162 /**
163 * multifd_uadk_send_prepare: prepare data to be able to send
164 *
165 * Create a compressed buffer with all the pages that we are going to
166 * send.
167 *
168 * Returns 0 for success or -1 for error
169 *
170 * @p: Params for the channel that we are using
171 * @errp: pointer to an error
172 */
multifd_uadk_send_prepare(MultiFDSendParams * p,Error ** errp)173 static int multifd_uadk_send_prepare(MultiFDSendParams *p, Error **errp)
174 {
175 struct wd_data *uadk_data = p->compress_data;
176 uint32_t hdr_size;
177 uint8_t *buf = uadk_data->buf;
178 int ret = 0;
179
180 if (!multifd_send_prepare_common(p)) {
181 goto out;
182 }
183
184 hdr_size = p->pages->normal_num * sizeof(uint32_t);
185 /* prepare the header that stores the lengths of all compressed data */
186 prepare_next_iov(p, uadk_data->buf_hdr, hdr_size);
187
188 for (int i = 0; i < p->pages->normal_num; i++) {
189 struct wd_comp_req creq = {
190 .op_type = WD_DIR_COMPRESS,
191 .src = p->pages->block->host + p->pages->offset[i],
192 .src_len = p->page_size,
193 .dst = buf,
194 /* Set dst_len to double the src in case compressed out >= page_size */
195 .dst_len = p->page_size * 2,
196 };
197
198 if (uadk_data->handle) {
199 ret = wd_do_comp_sync(uadk_data->handle, &creq);
200 if (ret || creq.status) {
201 error_setg(errp, "multifd %u: failed compression, ret %d status %d",
202 p->id, ret, creq.status);
203 return -1;
204 }
205 if (creq.dst_len < p->page_size) {
206 uadk_data->buf_hdr[i] = cpu_to_be32(creq.dst_len);
207 prepare_next_iov(p, buf, creq.dst_len);
208 buf += creq.dst_len;
209 }
210 }
211 /*
212 * Send raw data if no UADK hardware or if compressed out >= page_size.
213 * We might be better off sending raw data if output is slightly less
214 * than page_size as well because at the receive end we can skip the
215 * decompression. But it is tricky to find the right number here.
216 */
217 if (!uadk_data->handle || creq.dst_len >= p->page_size) {
218 uadk_data->buf_hdr[i] = cpu_to_be32(p->page_size);
219 prepare_next_iov(p, p->pages->block->host + p->pages->offset[i],
220 p->page_size);
221 buf += p->page_size;
222 }
223 }
224 out:
225 p->flags |= MULTIFD_FLAG_UADK;
226 multifd_send_fill_packet(p);
227 return 0;
228 }
229
230 /**
231 * multifd_uadk_recv_setup: setup receive side
232 *
233 * Create the compressed channel and buffer.
234 *
235 * Returns 0 for success or -1 for error
236 *
237 * @p: Params for the channel that we are using
238 * @errp: pointer to an error
239 */
multifd_uadk_recv_setup(MultiFDRecvParams * p,Error ** errp)240 static int multifd_uadk_recv_setup(MultiFDRecvParams *p, Error **errp)
241 {
242 struct wd_data *wd;
243
244 wd = multifd_uadk_init_sess(p->page_count, p->page_size, false, errp);
245 if (!wd) {
246 return -1;
247 }
248 p->compress_data = wd;
249 return 0;
250 }
251
252 /**
253 * multifd_uadk_recv_cleanup: cleanup receive side
254 *
255 * Close the channel and return memory.
256 *
257 * @p: Params for the channel that we are using
258 */
multifd_uadk_recv_cleanup(MultiFDRecvParams * p)259 static void multifd_uadk_recv_cleanup(MultiFDRecvParams *p)
260 {
261 struct wd_data *wd = p->compress_data;
262
263 multifd_uadk_uninit_sess(wd);
264 p->compress_data = NULL;
265 }
266
267 /**
268 * multifd_uadk_recv: read the data from the channel into actual pages
269 *
270 * Read the compressed buffer, and uncompress it into the actual
271 * pages.
272 *
273 * Returns 0 for success or -1 for error
274 *
275 * @p: Params for the channel that we are using
276 * @errp: pointer to an error
277 */
multifd_uadk_recv(MultiFDRecvParams * p,Error ** errp)278 static int multifd_uadk_recv(MultiFDRecvParams *p, Error **errp)
279 {
280 struct wd_data *uadk_data = p->compress_data;
281 uint32_t in_size = p->next_packet_size;
282 uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK;
283 uint32_t hdr_len = p->normal_num * sizeof(uint32_t);
284 uint32_t data_len = 0;
285 uint8_t *buf = uadk_data->buf;
286 int ret = 0;
287
288 if (flags != MULTIFD_FLAG_UADK) {
289 error_setg(errp, "multifd %u: flags received %x flags expected %x",
290 p->id, flags, MULTIFD_FLAG_ZLIB);
291 return -1;
292 }
293
294 multifd_recv_zero_page_process(p);
295 if (!p->normal_num) {
296 assert(in_size == 0);
297 return 0;
298 }
299
300 /* read compressed data lengths */
301 assert(hdr_len < in_size);
302 ret = qio_channel_read_all(p->c, (void *) uadk_data->buf_hdr,
303 hdr_len, errp);
304 if (ret != 0) {
305 return ret;
306 }
307
308 for (int i = 0; i < p->normal_num; i++) {
309 uadk_data->buf_hdr[i] = be32_to_cpu(uadk_data->buf_hdr[i]);
310 data_len += uadk_data->buf_hdr[i];
311 assert(uadk_data->buf_hdr[i] <= p->page_size);
312 }
313
314 /* read compressed data */
315 assert(in_size == hdr_len + data_len);
316 ret = qio_channel_read_all(p->c, (void *)buf, data_len, errp);
317 if (ret != 0) {
318 return ret;
319 }
320
321 for (int i = 0; i < p->normal_num; i++) {
322 struct wd_comp_req creq = {
323 .op_type = WD_DIR_DECOMPRESS,
324 .src = buf,
325 .src_len = uadk_data->buf_hdr[i],
326 .dst = p->host + p->normal[i],
327 .dst_len = p->page_size,
328 };
329
330 if (uadk_data->buf_hdr[i] == p->page_size) {
331 memcpy(p->host + p->normal[i], buf, p->page_size);
332 buf += p->page_size;
333 continue;
334 }
335
336 if (unlikely(!uadk_data->handle)) {
337 error_setg(errp, "multifd %u: UADK HW not available for decompression",
338 p->id);
339 return -1;
340 }
341
342 ret = wd_do_comp_sync(uadk_data->handle, &creq);
343 if (ret || creq.status) {
344 error_setg(errp, "multifd %u: failed decompression, ret %d status %d",
345 p->id, ret, creq.status);
346 return -1;
347 }
348 if (creq.dst_len != p->page_size) {
349 error_setg(errp, "multifd %u: decompressed length error", p->id);
350 return -1;
351 }
352 buf += uadk_data->buf_hdr[i];
353 }
354
355 return 0;
356 }
357
358 static MultiFDMethods multifd_uadk_ops = {
359 .send_setup = multifd_uadk_send_setup,
360 .send_cleanup = multifd_uadk_send_cleanup,
361 .send_prepare = multifd_uadk_send_prepare,
362 .recv_setup = multifd_uadk_recv_setup,
363 .recv_cleanup = multifd_uadk_recv_cleanup,
364 .recv = multifd_uadk_recv,
365 };
366
multifd_uadk_register(void)367 static void multifd_uadk_register(void)
368 {
369 multifd_register_ops(MULTIFD_COMPRESSION_UADK, &multifd_uadk_ops);
370 }
371 migration_init(multifd_uadk_register);
372