xref: /openbmc/qemu/migration/multifd-uadk.c (revision 10eb3721)
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 
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 
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 = &param;
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 
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  */
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  */
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 
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  */
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  */
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  */
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  */
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 
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