xref: /openbmc/qemu/target/i386/sev.c (revision 354908ce)
1 /*
2  * QEMU SEV support
3  *
4  * Copyright Advanced Micro Devices 2016-2018
5  *
6  * Author:
7  *      Brijesh Singh <brijesh.singh@amd.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 
14 #include "qemu/osdep.h"
15 
16 #include <linux/kvm.h>
17 #include <linux/psp-sev.h>
18 
19 #include <sys/ioctl.h>
20 
21 #include "qapi/error.h"
22 #include "qom/object_interfaces.h"
23 #include "qemu/base64.h"
24 #include "qemu/module.h"
25 #include "sysemu/kvm.h"
26 #include "sev_i386.h"
27 #include "sysemu/sysemu.h"
28 #include "sysemu/runstate.h"
29 #include "trace.h"
30 #include "migration/blocker.h"
31 
32 #define TYPE_SEV_GUEST "sev-guest"
33 #define SEV_GUEST(obj)                                          \
34     OBJECT_CHECK(SevGuestState, (obj), TYPE_SEV_GUEST)
35 
36 typedef struct SevGuestState SevGuestState;
37 
38 /**
39  * SevGuestState:
40  *
41  * The SevGuestState object is used for creating and managing a SEV
42  * guest.
43  *
44  * # $QEMU \
45  *         -object sev-guest,id=sev0 \
46  *         -machine ...,memory-encryption=sev0
47  */
48 struct SevGuestState {
49     Object parent_obj;
50 
51     /* configuration parameters */
52     char *sev_device;
53     uint32_t policy;
54     char *dh_cert_file;
55     char *session_file;
56     uint32_t cbitpos;
57     uint32_t reduced_phys_bits;
58 
59     /* runtime state */
60     uint32_t handle;
61     uint8_t api_major;
62     uint8_t api_minor;
63     uint8_t build_id;
64     uint64_t me_mask;
65     int sev_fd;
66     SevState state;
67     gchar *measurement;
68 };
69 
70 #define DEFAULT_GUEST_POLICY    0x1 /* disable debug */
71 #define DEFAULT_SEV_DEVICE      "/dev/sev"
72 
73 static SevGuestState *sev_guest;
74 static Error *sev_mig_blocker;
75 
76 static const char *const sev_fw_errlist[] = {
77     "",
78     "Platform state is invalid",
79     "Guest state is invalid",
80     "Platform configuration is invalid",
81     "Buffer too small",
82     "Platform is already owned",
83     "Certificate is invalid",
84     "Policy is not allowed",
85     "Guest is not active",
86     "Invalid address",
87     "Bad signature",
88     "Bad measurement",
89     "Asid is already owned",
90     "Invalid ASID",
91     "WBINVD is required",
92     "DF_FLUSH is required",
93     "Guest handle is invalid",
94     "Invalid command",
95     "Guest is active",
96     "Hardware error",
97     "Hardware unsafe",
98     "Feature not supported",
99     "Invalid parameter"
100 };
101 
102 #define SEV_FW_MAX_ERROR      ARRAY_SIZE(sev_fw_errlist)
103 
104 static int
105 sev_ioctl(int fd, int cmd, void *data, int *error)
106 {
107     int r;
108     struct kvm_sev_cmd input;
109 
110     memset(&input, 0x0, sizeof(input));
111 
112     input.id = cmd;
113     input.sev_fd = fd;
114     input.data = (__u64)(unsigned long)data;
115 
116     r = kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_OP, &input);
117 
118     if (error) {
119         *error = input.error;
120     }
121 
122     return r;
123 }
124 
125 static int
126 sev_platform_ioctl(int fd, int cmd, void *data, int *error)
127 {
128     int r;
129     struct sev_issue_cmd arg;
130 
131     arg.cmd = cmd;
132     arg.data = (unsigned long)data;
133     r = ioctl(fd, SEV_ISSUE_CMD, &arg);
134     if (error) {
135         *error = arg.error;
136     }
137 
138     return r;
139 }
140 
141 static const char *
142 fw_error_to_str(int code)
143 {
144     if (code < 0 || code >= SEV_FW_MAX_ERROR) {
145         return "unknown error";
146     }
147 
148     return sev_fw_errlist[code];
149 }
150 
151 static bool
152 sev_check_state(const SevGuestState *sev, SevState state)
153 {
154     assert(sev);
155     return sev->state == state ? true : false;
156 }
157 
158 static void
159 sev_set_guest_state(SevGuestState *sev, SevState new_state)
160 {
161     assert(new_state < SEV_STATE__MAX);
162     assert(sev);
163 
164     trace_kvm_sev_change_state(SevState_str(sev->state),
165                                SevState_str(new_state));
166     sev->state = new_state;
167 }
168 
169 static void
170 sev_ram_block_added(RAMBlockNotifier *n, void *host, size_t size)
171 {
172     int r;
173     struct kvm_enc_region range;
174     ram_addr_t offset;
175     MemoryRegion *mr;
176 
177     /*
178      * The RAM device presents a memory region that should be treated
179      * as IO region and should not be pinned.
180      */
181     mr = memory_region_from_host(host, &offset);
182     if (mr && memory_region_is_ram_device(mr)) {
183         return;
184     }
185 
186     range.addr = (__u64)(unsigned long)host;
187     range.size = size;
188 
189     trace_kvm_memcrypt_register_region(host, size);
190     r = kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_REG_REGION, &range);
191     if (r) {
192         error_report("%s: failed to register region (%p+%#zx) error '%s'",
193                      __func__, host, size, strerror(errno));
194         exit(1);
195     }
196 }
197 
198 static void
199 sev_ram_block_removed(RAMBlockNotifier *n, void *host, size_t size)
200 {
201     int r;
202     struct kvm_enc_region range;
203     ram_addr_t offset;
204     MemoryRegion *mr;
205 
206     /*
207      * The RAM device presents a memory region that should be treated
208      * as IO region and should not have been pinned.
209      */
210     mr = memory_region_from_host(host, &offset);
211     if (mr && memory_region_is_ram_device(mr)) {
212         return;
213     }
214 
215     range.addr = (__u64)(unsigned long)host;
216     range.size = size;
217 
218     trace_kvm_memcrypt_unregister_region(host, size);
219     r = kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_UNREG_REGION, &range);
220     if (r) {
221         error_report("%s: failed to unregister region (%p+%#zx)",
222                      __func__, host, size);
223     }
224 }
225 
226 static struct RAMBlockNotifier sev_ram_notifier = {
227     .ram_block_added = sev_ram_block_added,
228     .ram_block_removed = sev_ram_block_removed,
229 };
230 
231 static void
232 sev_guest_finalize(Object *obj)
233 {
234 }
235 
236 static char *
237 sev_guest_get_session_file(Object *obj, Error **errp)
238 {
239     SevGuestState *s = SEV_GUEST(obj);
240 
241     return s->session_file ? g_strdup(s->session_file) : NULL;
242 }
243 
244 static void
245 sev_guest_set_session_file(Object *obj, const char *value, Error **errp)
246 {
247     SevGuestState *s = SEV_GUEST(obj);
248 
249     s->session_file = g_strdup(value);
250 }
251 
252 static char *
253 sev_guest_get_dh_cert_file(Object *obj, Error **errp)
254 {
255     SevGuestState *s = SEV_GUEST(obj);
256 
257     return g_strdup(s->dh_cert_file);
258 }
259 
260 static void
261 sev_guest_set_dh_cert_file(Object *obj, const char *value, Error **errp)
262 {
263     SevGuestState *s = SEV_GUEST(obj);
264 
265     s->dh_cert_file = g_strdup(value);
266 }
267 
268 static char *
269 sev_guest_get_sev_device(Object *obj, Error **errp)
270 {
271     SevGuestState *sev = SEV_GUEST(obj);
272 
273     return g_strdup(sev->sev_device);
274 }
275 
276 static void
277 sev_guest_set_sev_device(Object *obj, const char *value, Error **errp)
278 {
279     SevGuestState *sev = SEV_GUEST(obj);
280 
281     sev->sev_device = g_strdup(value);
282 }
283 
284 static void
285 sev_guest_class_init(ObjectClass *oc, void *data)
286 {
287     object_class_property_add_str(oc, "sev-device",
288                                   sev_guest_get_sev_device,
289                                   sev_guest_set_sev_device);
290     object_class_property_set_description(oc, "sev-device",
291             "SEV device to use");
292     object_class_property_add_str(oc, "dh-cert-file",
293                                   sev_guest_get_dh_cert_file,
294                                   sev_guest_set_dh_cert_file);
295     object_class_property_set_description(oc, "dh-cert-file",
296             "guest owners DH certificate (encoded with base64)");
297     object_class_property_add_str(oc, "session-file",
298                                   sev_guest_get_session_file,
299                                   sev_guest_set_session_file);
300     object_class_property_set_description(oc, "session-file",
301             "guest owners session parameters (encoded with base64)");
302 }
303 
304 static void
305 sev_guest_instance_init(Object *obj)
306 {
307     SevGuestState *sev = SEV_GUEST(obj);
308 
309     sev->sev_device = g_strdup(DEFAULT_SEV_DEVICE);
310     sev->policy = DEFAULT_GUEST_POLICY;
311     object_property_add_uint32_ptr(obj, "policy", &sev->policy,
312                                    OBJ_PROP_FLAG_READWRITE);
313     object_property_add_uint32_ptr(obj, "handle", &sev->handle,
314                                    OBJ_PROP_FLAG_READWRITE);
315     object_property_add_uint32_ptr(obj, "cbitpos", &sev->cbitpos,
316                                    OBJ_PROP_FLAG_READWRITE);
317     object_property_add_uint32_ptr(obj, "reduced-phys-bits",
318                                    &sev->reduced_phys_bits,
319                                    OBJ_PROP_FLAG_READWRITE);
320 }
321 
322 /* sev guest info */
323 static const TypeInfo sev_guest_info = {
324     .parent = TYPE_OBJECT,
325     .name = TYPE_SEV_GUEST,
326     .instance_size = sizeof(SevGuestState),
327     .instance_finalize = sev_guest_finalize,
328     .class_init = sev_guest_class_init,
329     .instance_init = sev_guest_instance_init,
330     .interfaces = (InterfaceInfo[]) {
331         { TYPE_USER_CREATABLE },
332         { }
333     }
334 };
335 
336 static SevGuestState *
337 lookup_sev_guest_info(const char *id)
338 {
339     Object *obj;
340     SevGuestState *info;
341 
342     obj = object_resolve_path_component(object_get_objects_root(), id);
343     if (!obj) {
344         return NULL;
345     }
346 
347     info = (SevGuestState *)
348             object_dynamic_cast(obj, TYPE_SEV_GUEST);
349     if (!info) {
350         return NULL;
351     }
352 
353     return info;
354 }
355 
356 bool
357 sev_enabled(void)
358 {
359     return !!sev_guest;
360 }
361 
362 uint64_t
363 sev_get_me_mask(void)
364 {
365     return sev_guest ? sev_guest->me_mask : ~0;
366 }
367 
368 uint32_t
369 sev_get_cbit_position(void)
370 {
371     return sev_guest ? sev_guest->cbitpos : 0;
372 }
373 
374 uint32_t
375 sev_get_reduced_phys_bits(void)
376 {
377     return sev_guest ? sev_guest->reduced_phys_bits : 0;
378 }
379 
380 SevInfo *
381 sev_get_info(void)
382 {
383     SevInfo *info;
384 
385     info = g_new0(SevInfo, 1);
386     info->enabled = sev_enabled();
387 
388     if (info->enabled) {
389         info->api_major = sev_guest->api_major;
390         info->api_minor = sev_guest->api_minor;
391         info->build_id = sev_guest->build_id;
392         info->policy = sev_guest->policy;
393         info->state = sev_guest->state;
394         info->handle = sev_guest->handle;
395     }
396 
397     return info;
398 }
399 
400 static int
401 sev_get_pdh_info(int fd, guchar **pdh, size_t *pdh_len, guchar **cert_chain,
402                  size_t *cert_chain_len)
403 {
404     guchar *pdh_data = NULL;
405     guchar *cert_chain_data = NULL;
406     struct sev_user_data_pdh_cert_export export = {};
407     int err, r;
408 
409     /* query the certificate length */
410     r = sev_platform_ioctl(fd, SEV_PDH_CERT_EXPORT, &export, &err);
411     if (r < 0) {
412         if (err != SEV_RET_INVALID_LEN) {
413             error_report("failed to export PDH cert ret=%d fw_err=%d (%s)",
414                          r, err, fw_error_to_str(err));
415             return 1;
416         }
417     }
418 
419     pdh_data = g_new(guchar, export.pdh_cert_len);
420     cert_chain_data = g_new(guchar, export.cert_chain_len);
421     export.pdh_cert_address = (unsigned long)pdh_data;
422     export.cert_chain_address = (unsigned long)cert_chain_data;
423 
424     r = sev_platform_ioctl(fd, SEV_PDH_CERT_EXPORT, &export, &err);
425     if (r < 0) {
426         error_report("failed to export PDH cert ret=%d fw_err=%d (%s)",
427                      r, err, fw_error_to_str(err));
428         goto e_free;
429     }
430 
431     *pdh = pdh_data;
432     *pdh_len = export.pdh_cert_len;
433     *cert_chain = cert_chain_data;
434     *cert_chain_len = export.cert_chain_len;
435     return 0;
436 
437 e_free:
438     g_free(pdh_data);
439     g_free(cert_chain_data);
440     return 1;
441 }
442 
443 SevCapability *
444 sev_get_capabilities(void)
445 {
446     SevCapability *cap = NULL;
447     guchar *pdh_data = NULL;
448     guchar *cert_chain_data = NULL;
449     size_t pdh_len = 0, cert_chain_len = 0;
450     uint32_t ebx;
451     int fd;
452 
453     fd = open(DEFAULT_SEV_DEVICE, O_RDWR);
454     if (fd < 0) {
455         error_report("%s: Failed to open %s '%s'", __func__,
456                      DEFAULT_SEV_DEVICE, strerror(errno));
457         return NULL;
458     }
459 
460     if (sev_get_pdh_info(fd, &pdh_data, &pdh_len,
461                          &cert_chain_data, &cert_chain_len)) {
462         goto out;
463     }
464 
465     cap = g_new0(SevCapability, 1);
466     cap->pdh = g_base64_encode(pdh_data, pdh_len);
467     cap->cert_chain = g_base64_encode(cert_chain_data, cert_chain_len);
468 
469     host_cpuid(0x8000001F, 0, NULL, &ebx, NULL, NULL);
470     cap->cbitpos = ebx & 0x3f;
471 
472     /*
473      * When SEV feature is enabled, we loose one bit in guest physical
474      * addressing.
475      */
476     cap->reduced_phys_bits = 1;
477 
478 out:
479     g_free(pdh_data);
480     g_free(cert_chain_data);
481     close(fd);
482     return cap;
483 }
484 
485 static int
486 sev_read_file_base64(const char *filename, guchar **data, gsize *len)
487 {
488     gsize sz;
489     gchar *base64;
490     GError *error = NULL;
491 
492     if (!g_file_get_contents(filename, &base64, &sz, &error)) {
493         error_report("failed to read '%s' (%s)", filename, error->message);
494         return -1;
495     }
496 
497     *data = g_base64_decode(base64, len);
498     return 0;
499 }
500 
501 static int
502 sev_launch_start(SevGuestState *sev)
503 {
504     gsize sz;
505     int ret = 1;
506     int fw_error, rc;
507     struct kvm_sev_launch_start *start;
508     guchar *session = NULL, *dh_cert = NULL;
509 
510     start = g_new0(struct kvm_sev_launch_start, 1);
511 
512     start->handle = sev->handle;
513     start->policy = sev->policy;
514     if (sev->session_file) {
515         if (sev_read_file_base64(sev->session_file, &session, &sz) < 0) {
516             goto out;
517         }
518         start->session_uaddr = (unsigned long)session;
519         start->session_len = sz;
520     }
521 
522     if (sev->dh_cert_file) {
523         if (sev_read_file_base64(sev->dh_cert_file, &dh_cert, &sz) < 0) {
524             goto out;
525         }
526         start->dh_uaddr = (unsigned long)dh_cert;
527         start->dh_len = sz;
528     }
529 
530     trace_kvm_sev_launch_start(start->policy, session, dh_cert);
531     rc = sev_ioctl(sev->sev_fd, KVM_SEV_LAUNCH_START, start, &fw_error);
532     if (rc < 0) {
533         error_report("%s: LAUNCH_START ret=%d fw_error=%d '%s'",
534                 __func__, ret, fw_error, fw_error_to_str(fw_error));
535         goto out;
536     }
537 
538     sev_set_guest_state(sev, SEV_STATE_LAUNCH_UPDATE);
539     sev->handle = start->handle;
540     ret = 0;
541 
542 out:
543     g_free(start);
544     g_free(session);
545     g_free(dh_cert);
546     return ret;
547 }
548 
549 static int
550 sev_launch_update_data(SevGuestState *sev, uint8_t *addr, uint64_t len)
551 {
552     int ret, fw_error;
553     struct kvm_sev_launch_update_data update;
554 
555     if (!addr || !len) {
556         return 1;
557     }
558 
559     update.uaddr = (__u64)(unsigned long)addr;
560     update.len = len;
561     trace_kvm_sev_launch_update_data(addr, len);
562     ret = sev_ioctl(sev->sev_fd, KVM_SEV_LAUNCH_UPDATE_DATA,
563                     &update, &fw_error);
564     if (ret) {
565         error_report("%s: LAUNCH_UPDATE ret=%d fw_error=%d '%s'",
566                 __func__, ret, fw_error, fw_error_to_str(fw_error));
567     }
568 
569     return ret;
570 }
571 
572 static void
573 sev_launch_get_measure(Notifier *notifier, void *unused)
574 {
575     SevGuestState *sev = sev_guest;
576     int ret, error;
577     guchar *data;
578     struct kvm_sev_launch_measure *measurement;
579 
580     if (!sev_check_state(sev, SEV_STATE_LAUNCH_UPDATE)) {
581         return;
582     }
583 
584     measurement = g_new0(struct kvm_sev_launch_measure, 1);
585 
586     /* query the measurement blob length */
587     ret = sev_ioctl(sev->sev_fd, KVM_SEV_LAUNCH_MEASURE,
588                     measurement, &error);
589     if (!measurement->len) {
590         error_report("%s: LAUNCH_MEASURE ret=%d fw_error=%d '%s'",
591                      __func__, ret, error, fw_error_to_str(errno));
592         goto free_measurement;
593     }
594 
595     data = g_new0(guchar, measurement->len);
596     measurement->uaddr = (unsigned long)data;
597 
598     /* get the measurement blob */
599     ret = sev_ioctl(sev->sev_fd, KVM_SEV_LAUNCH_MEASURE,
600                     measurement, &error);
601     if (ret) {
602         error_report("%s: LAUNCH_MEASURE ret=%d fw_error=%d '%s'",
603                      __func__, ret, error, fw_error_to_str(errno));
604         goto free_data;
605     }
606 
607     sev_set_guest_state(sev, SEV_STATE_LAUNCH_SECRET);
608 
609     /* encode the measurement value and emit the event */
610     sev->measurement = g_base64_encode(data, measurement->len);
611     trace_kvm_sev_launch_measurement(sev->measurement);
612 
613 free_data:
614     g_free(data);
615 free_measurement:
616     g_free(measurement);
617 }
618 
619 char *
620 sev_get_launch_measurement(void)
621 {
622     if (sev_guest &&
623         sev_guest->state >= SEV_STATE_LAUNCH_SECRET) {
624         return g_strdup(sev_guest->measurement);
625     }
626 
627     return NULL;
628 }
629 
630 static Notifier sev_machine_done_notify = {
631     .notify = sev_launch_get_measure,
632 };
633 
634 static void
635 sev_launch_finish(SevGuestState *sev)
636 {
637     int ret, error;
638     Error *local_err = NULL;
639 
640     trace_kvm_sev_launch_finish();
641     ret = sev_ioctl(sev->sev_fd, KVM_SEV_LAUNCH_FINISH, 0, &error);
642     if (ret) {
643         error_report("%s: LAUNCH_FINISH ret=%d fw_error=%d '%s'",
644                      __func__, ret, error, fw_error_to_str(error));
645         exit(1);
646     }
647 
648     sev_set_guest_state(sev, SEV_STATE_RUNNING);
649 
650     /* add migration blocker */
651     error_setg(&sev_mig_blocker,
652                "SEV: Migration is not implemented");
653     ret = migrate_add_blocker(sev_mig_blocker, &local_err);
654     if (local_err) {
655         error_report_err(local_err);
656         error_free(sev_mig_blocker);
657         exit(1);
658     }
659 }
660 
661 static void
662 sev_vm_state_change(void *opaque, int running, RunState state)
663 {
664     SevGuestState *sev = opaque;
665 
666     if (running) {
667         if (!sev_check_state(sev, SEV_STATE_RUNNING)) {
668             sev_launch_finish(sev);
669         }
670     }
671 }
672 
673 void *
674 sev_guest_init(const char *id)
675 {
676     SevGuestState *sev;
677     char *devname;
678     int ret, fw_error;
679     uint32_t ebx;
680     uint32_t host_cbitpos;
681     struct sev_user_data_status status = {};
682 
683     sev = lookup_sev_guest_info(id);
684     if (!sev) {
685         error_report("%s: '%s' is not a valid '%s' object",
686                      __func__, id, TYPE_SEV_GUEST);
687         goto err;
688     }
689 
690     sev_guest = sev;
691     sev->state = SEV_STATE_UNINIT;
692 
693     host_cpuid(0x8000001F, 0, NULL, &ebx, NULL, NULL);
694     host_cbitpos = ebx & 0x3f;
695 
696     if (host_cbitpos != sev->cbitpos) {
697         error_report("%s: cbitpos check failed, host '%d' requested '%d'",
698                      __func__, host_cbitpos, sev->cbitpos);
699         goto err;
700     }
701 
702     if (sev->reduced_phys_bits < 1) {
703         error_report("%s: reduced_phys_bits check failed, it should be >=1,"
704                      " requested '%d'", __func__, sev->reduced_phys_bits);
705         goto err;
706     }
707 
708     sev->me_mask = ~(1UL << sev->cbitpos);
709 
710     devname = object_property_get_str(OBJECT(sev), "sev-device", NULL);
711     sev->sev_fd = open(devname, O_RDWR);
712     if (sev->sev_fd < 0) {
713         error_report("%s: Failed to open %s '%s'", __func__,
714                      devname, strerror(errno));
715     }
716     g_free(devname);
717     if (sev->sev_fd < 0) {
718         goto err;
719     }
720 
721     ret = sev_platform_ioctl(sev->sev_fd, SEV_PLATFORM_STATUS, &status,
722                              &fw_error);
723     if (ret) {
724         error_report("%s: failed to get platform status ret=%d "
725                      "fw_error='%d: %s'", __func__, ret, fw_error,
726                      fw_error_to_str(fw_error));
727         goto err;
728     }
729     sev->build_id = status.build;
730     sev->api_major = status.api_major;
731     sev->api_minor = status.api_minor;
732 
733     trace_kvm_sev_init();
734     ret = sev_ioctl(sev->sev_fd, KVM_SEV_INIT, NULL, &fw_error);
735     if (ret) {
736         error_report("%s: failed to initialize ret=%d fw_error=%d '%s'",
737                      __func__, ret, fw_error, fw_error_to_str(fw_error));
738         goto err;
739     }
740 
741     ret = sev_launch_start(sev);
742     if (ret) {
743         error_report("%s: failed to create encryption context", __func__);
744         goto err;
745     }
746 
747     ram_block_notifier_add(&sev_ram_notifier);
748     qemu_add_machine_init_done_notifier(&sev_machine_done_notify);
749     qemu_add_vm_change_state_handler(sev_vm_state_change, sev);
750 
751     return sev;
752 err:
753     sev_guest = NULL;
754     return NULL;
755 }
756 
757 int
758 sev_encrypt_data(void *handle, uint8_t *ptr, uint64_t len)
759 {
760     SevGuestState *sev = handle;
761 
762     assert(sev);
763 
764     /* if SEV is in update state then encrypt the data else do nothing */
765     if (sev_check_state(sev, SEV_STATE_LAUNCH_UPDATE)) {
766         return sev_launch_update_data(sev, ptr, len);
767     }
768 
769     return 0;
770 }
771 
772 static void
773 sev_register_types(void)
774 {
775     type_register_static(&sev_guest_info);
776 }
777 
778 type_init(sev_register_types);
779