efiboot: Add support for SMBIOS 2.x tables.diff -r1.9 -r1.10 src/sys/stand/efiboot/efiacpi.c
(jmcneill)
--- src/sys/stand/efiboot/efiacpi.c 2021/05/21 21:53:15 1.9
+++ src/sys/stand/efiboot/efiacpi.c 2021/07/23 21:33:00 1.10
@@ -1,14 +1,14 @@ | @@ -1,14 +1,14 @@ | |||
1 | /* $NetBSD: efiacpi.c,v 1.9 2021/05/21 21:53:15 jmcneill Exp $ */ | 1 | /* $NetBSD: efiacpi.c,v 1.10 2021/07/23 21:33:00 jmcneill Exp $ */ | |
2 | 2 | |||
3 | /*- | 3 | /*- | |
4 | * Copyright (c) 2018 The NetBSD Foundation, Inc. | 4 | * Copyright (c) 2018 The NetBSD Foundation, Inc. | |
5 | * All rights reserved. | 5 | * All rights reserved. | |
6 | * | 6 | * | |
7 | * This code is derived from software contributed to The NetBSD Foundation | 7 | * This code is derived from software contributed to The NetBSD Foundation | |
8 | * by Jared McNeill <jmcneill@invisible.ca>. | 8 | * by Jared McNeill <jmcneill@invisible.ca>. | |
9 | * | 9 | * | |
10 | * Redistribution and use in source and binary forms, with or without | 10 | * Redistribution and use in source and binary forms, with or without | |
11 | * modification, are permitted provided that the following conditions | 11 | * modification, are permitted provided that the following conditions | |
12 | * are met: | 12 | * are met: | |
13 | * 1. Redistributions of source code must retain the above copyright | 13 | * 1. Redistributions of source code must retain the above copyright | |
14 | * notice, this list of conditions and the following disclaimer. | 14 | * notice, this list of conditions and the following disclaimer. | |
@@ -42,43 +42,48 @@ struct acpi_rdsp { | @@ -42,43 +42,48 @@ struct acpi_rdsp { | |||
42 | uint32_t rsdtphys; | 42 | uint32_t rsdtphys; | |
43 | uint32_t length; | 43 | uint32_t length; | |
44 | uint64_t xsdtphys; | 44 | uint64_t xsdtphys; | |
45 | uint8_t extcsum; | 45 | uint8_t extcsum; | |
46 | uint8_t reserved[3]; | 46 | uint8_t reserved[3]; | |
47 | }; | 47 | }; | |
48 | 48 | |||
49 | #include <libfdt.h> | 49 | #include <libfdt.h> | |
50 | 50 | |||
51 | #define ACPI_FDT_SIZE (128 * 1024) | 51 | #define ACPI_FDT_SIZE (128 * 1024) | |
52 | 52 | |||
53 | static EFI_GUID Acpi20TableGuid = ACPI_20_TABLE_GUID; | 53 | static EFI_GUID Acpi20TableGuid = ACPI_20_TABLE_GUID; | |
54 | static EFI_GUID Smbios3TableGuid = SMBIOS3_TABLE_GUID; | 54 | static EFI_GUID Smbios3TableGuid = SMBIOS3_TABLE_GUID; | |
55 | static EFI_GUID SmbiosTableGuid = SMBIOS_TABLE_GUID; | |||
55 | 56 | |||
56 | static int acpi_enable = 1; | 57 | static int acpi_enable = 1; | |
57 | static void *acpi_root = NULL; | 58 | static void *acpi_root = NULL; | |
58 | static void *smbios3_table = NULL; | 59 | static void *smbios_table = NULL; | |
59 | 60 | |||
60 | int | 61 | int | |
61 | efi_acpi_probe(void) | 62 | efi_acpi_probe(void) | |
62 | { | 63 | { | |
63 | EFI_STATUS status; | 64 | EFI_STATUS status; | |
64 | 65 | |||
65 | status = LibGetSystemConfigurationTable(&Acpi20TableGuid, &acpi_root); | 66 | status = LibGetSystemConfigurationTable(&Acpi20TableGuid, &acpi_root); | |
66 | if (EFI_ERROR(status)) | 67 | if (EFI_ERROR(status)) | |
67 | return EIO; | 68 | return EIO; | |
68 | 69 | |||
69 | status = LibGetSystemConfigurationTable(&Smbios3TableGuid, &smbios3_table); | 70 | status = LibGetSystemConfigurationTable(&Smbios3TableGuid, &smbios_table); | |
70 | if (EFI_ERROR(status)) | 71 | if (EFI_ERROR(status)) { | |
71 | smbios3_table = NULL; | 72 | status = LibGetSystemConfigurationTable(&SmbiosTableGuid, &smbios_table); | |
73 | } | |||
74 | if (EFI_ERROR(status)) { | |||
75 | smbios_table = NULL; | |||
76 | } | |||
72 | 77 | |||
73 | return 0; | 78 | return 0; | |
74 | } | 79 | } | |
75 | 80 | |||
76 | int | 81 | int | |
77 | efi_acpi_available(void) | 82 | efi_acpi_available(void) | |
78 | { | 83 | { | |
79 | return acpi_root != NULL; | 84 | return acpi_root != NULL; | |
80 | } | 85 | } | |
81 | 86 | |||
82 | int | 87 | int | |
83 | efi_acpi_enabled(void) | 88 | efi_acpi_enabled(void) | |
84 | { | 89 | { | |
@@ -93,28 +98,28 @@ efi_acpi_enable(int enable) | @@ -93,28 +98,28 @@ efi_acpi_enable(int enable) | |||
93 | 98 | |||
94 | static char model_buf[128]; | 99 | static char model_buf[128]; | |
95 | 100 | |||
96 | static const char * | 101 | static const char * | |
97 | efi_acpi_get_model(void) | 102 | efi_acpi_get_model(void) | |
98 | { | 103 | { | |
99 | struct smbtable smbios; | 104 | struct smbtable smbios; | |
100 | struct smbios_sys *psys; | 105 | struct smbios_sys *psys; | |
101 | const char *s; | 106 | const char *s; | |
102 | char *buf; | 107 | char *buf; | |
103 | 108 | |||
104 | memset(model_buf, 0, sizeof(model_buf)); | 109 | memset(model_buf, 0, sizeof(model_buf)); | |
105 | 110 | |||
106 | if (smbios3_table != NULL) { | 111 | if (smbios_table != NULL) { | |
107 | smbios_init(smbios3_table); | 112 | smbios_init(smbios_table); | |
108 | 113 | |||
109 | buf = model_buf; | 114 | buf = model_buf; | |
110 | smbios.cookie = 0; | 115 | smbios.cookie = 0; | |
111 | if (smbios_find_table(SMBIOS_TYPE_SYSTEM, &smbios)) { | 116 | if (smbios_find_table(SMBIOS_TYPE_SYSTEM, &smbios)) { | |
112 | psys = smbios.tblhdr; | 117 | psys = smbios.tblhdr; | |
113 | if ((s = smbios_get_string(&smbios, psys->vendor, buf, 64)) != NULL) { | 118 | if ((s = smbios_get_string(&smbios, psys->vendor, buf, 64)) != NULL) { | |
114 | buf += strlen(s); | 119 | buf += strlen(s); | |
115 | *buf++ = ' '; | 120 | *buf++ = ' '; | |
116 | } | 121 | } | |
117 | smbios_get_string(&smbios, psys->product, buf, 64); | 122 | smbios_get_string(&smbios, psys->product, buf, 64); | |
118 | } | 123 | } | |
119 | } | 124 | } | |
120 | 125 | |||
@@ -126,27 +131,27 @@ efi_acpi_get_model(void) | @@ -126,27 +131,27 @@ efi_acpi_get_model(void) | |||
126 | 131 | |||
127 | void | 132 | void | |
128 | efi_acpi_show(void) | 133 | efi_acpi_show(void) | |
129 | { | 134 | { | |
130 | struct acpi_rdsp *rsdp = acpi_root; | 135 | struct acpi_rdsp *rsdp = acpi_root; | |
131 | 136 | |||
132 | if (!efi_acpi_available()) | 137 | if (!efi_acpi_available()) | |
133 | return; | 138 | return; | |
134 | 139 | |||
135 | printf("ACPI: v%02d %c%c%c%c%c%c\n", rsdp->revision, | 140 | printf("ACPI: v%02d %c%c%c%c%c%c\n", rsdp->revision, | |
136 | rsdp->oemid[0], rsdp->oemid[1], rsdp->oemid[2], | 141 | rsdp->oemid[0], rsdp->oemid[1], rsdp->oemid[2], | |
137 | rsdp->oemid[3], rsdp->oemid[4], rsdp->oemid[5]); | 142 | rsdp->oemid[3], rsdp->oemid[4], rsdp->oemid[5]); | |
138 | 143 | |||
139 | if (smbios3_table) | 144 | if (smbios_table) | |
140 | printf("SMBIOS: %s\n", efi_acpi_get_model()); | 145 | printf("SMBIOS: %s\n", efi_acpi_get_model()); | |
141 | } | 146 | } | |
142 | 147 | |||
143 | int | 148 | int | |
144 | efi_acpi_create_fdt(void) | 149 | efi_acpi_create_fdt(void) | |
145 | { | 150 | { | |
146 | int error; | 151 | int error; | |
147 | void *fdt; | 152 | void *fdt; | |
148 | 153 | |||
149 | if (acpi_root == NULL) | 154 | if (acpi_root == NULL) | |
150 | return EINVAL; | 155 | return EINVAL; | |
151 | 156 | |||
152 | fdt = AllocatePool(ACPI_FDT_SIZE); | 157 | fdt = AllocatePool(ACPI_FDT_SIZE); | |
@@ -156,21 +161,21 @@ efi_acpi_create_fdt(void) | @@ -156,21 +161,21 @@ efi_acpi_create_fdt(void) | |||
156 | error = fdt_create_empty_tree(fdt, ACPI_FDT_SIZE); | 161 | error = fdt_create_empty_tree(fdt, ACPI_FDT_SIZE); | |
157 | if (error) | 162 | if (error) | |
158 | return EIO; | 163 | return EIO; | |
159 | 164 | |||
160 | const char *model = efi_acpi_get_model(); | 165 | const char *model = efi_acpi_get_model(); | |
161 | 166 | |||
162 | fdt_setprop_string(fdt, fdt_path_offset(fdt, "/"), "compatible", "netbsd,generic-acpi"); | 167 | fdt_setprop_string(fdt, fdt_path_offset(fdt, "/"), "compatible", "netbsd,generic-acpi"); | |
163 | fdt_setprop_string(fdt, fdt_path_offset(fdt, "/"), "model", model); | 168 | fdt_setprop_string(fdt, fdt_path_offset(fdt, "/"), "model", model); | |
164 | fdt_setprop_cell(fdt, fdt_path_offset(fdt, "/"), "#address-cells", 2); | 169 | fdt_setprop_cell(fdt, fdt_path_offset(fdt, "/"), "#address-cells", 2); | |
165 | fdt_setprop_cell(fdt, fdt_path_offset(fdt, "/"), "#size-cells", 2); | 170 | fdt_setprop_cell(fdt, fdt_path_offset(fdt, "/"), "#size-cells", 2); | |
166 | 171 | |||
167 | fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"), "chosen"); | 172 | fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"), "chosen"); | |
168 | fdt_setprop_u64(fdt, fdt_path_offset(fdt, "/chosen"), "netbsd,acpi-root-table", (uint64_t)(uintptr_t)acpi_root); | 173 | fdt_setprop_u64(fdt, fdt_path_offset(fdt, "/chosen"), "netbsd,acpi-root-table", (uint64_t)(uintptr_t)acpi_root); | |
169 | if (smbios3_table) | 174 | if (smbios_table) | |
170 | fdt_setprop_u64(fdt, fdt_path_offset(fdt, "/chosen"), "netbsd,smbios-table", (uint64_t)(uintptr_t)smbios3_table); | 175 | fdt_setprop_u64(fdt, fdt_path_offset(fdt, "/chosen"), "netbsd,smbios-table", (uint64_t)(uintptr_t)smbios_table); | |
171 | 176 | |||
172 | fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"), "acpi"); | 177 | fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"), "acpi"); | |
173 | fdt_setprop_string(fdt, fdt_path_offset(fdt, "/acpi"), "compatible", "netbsd,acpi"); | 178 | fdt_setprop_string(fdt, fdt_path_offset(fdt, "/acpi"), "compatible", "netbsd,acpi"); | |
174 | 179 | |||
175 | return efi_fdt_set_data(fdt); | 180 | return efi_fdt_set_data(fdt); | |
176 | } | 181 | } |
--- src/sys/stand/efiboot/smbios.c 2019/12/27 09:45:27 1.2
+++ src/sys/stand/efiboot/smbios.c 2021/07/23 21:33:00 1.3
@@ -1,14 +1,14 @@ | @@ -1,14 +1,14 @@ | |||
1 | /* $NetBSD: smbios.c,v 1.2 2019/12/27 09:45:27 msaitoh Exp $ */ | 1 | /* $NetBSD: smbios.c,v 1.3 2021/07/23 21:33:00 jmcneill Exp $ */ | |
2 | 2 | |||
3 | /* | 3 | /* | |
4 | * Copyright (c) 1999 The NetBSD Foundation, Inc. | 4 | * Copyright (c) 1999 The NetBSD Foundation, Inc. | |
5 | * All rights reserved. | 5 | * All rights reserved. | |
6 | * | 6 | * | |
7 | * This code is derived from software contributed to The NetBSD Foundation | 7 | * This code is derived from software contributed to The NetBSD Foundation | |
8 | * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, | 8 | * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, | |
9 | * NASA Ames Research Center. | 9 | * NASA Ames Research Center. | |
10 | * | 10 | * | |
11 | * Redistribution and use in source and binary forms, with or without | 11 | * Redistribution and use in source and binary forms, with or without | |
12 | * modification, are permitted provided that the following conditions | 12 | * modification, are permitted provided that the following conditions | |
13 | * are met: | 13 | * are met: | |
14 | * 1. Redistributions of source code must retain the above copyright | 14 | * 1. Redistributions of source code must retain the above copyright | |
@@ -72,65 +72,93 @@ | @@ -72,65 +72,93 @@ | |||
72 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | 72 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
73 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | 73 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
74 | * IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT, | 74 | * IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT, | |
75 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 75 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
76 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 76 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
77 | * SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | 77 | * SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
78 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | 78 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
79 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | 79 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
80 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | 80 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |
81 | * THE POSSIBILITY OF SUCH DAMAGE. | 81 | * THE POSSIBILITY OF SUCH DAMAGE. | |
82 | */ | 82 | */ | |
83 | 83 | |||
84 | #include <sys/cdefs.h> | 84 | #include <sys/cdefs.h> | |
85 | __KERNEL_RCSID(0, "$NetBSD: smbios.c,v 1.2 2019/12/27 09:45:27 msaitoh Exp $"); | 85 | __KERNEL_RCSID(0, "$NetBSD: smbios.c,v 1.3 2021/07/23 21:33:00 jmcneill Exp $"); | |
86 | 86 | |||
87 | #include <sys/param.h> | 87 | #include <sys/param.h> | |
88 | 88 | |||
89 | #include "efiboot.h" | 89 | #include "efiboot.h" | |
90 | #include "smbios.h" | 90 | #include "smbios.h" | |
91 | 91 | |||
92 | struct smbios_entry smbios_entry; | 92 | struct smbios_entry smbios_entry; | |
93 | 93 | |||
94 | void | 94 | static void | |
95 | smbios_init(uint8_t *p) | 95 | smbios2_init(uint8_t *p) | |
96 | { | |||
97 | const struct smbhdr *sh = (const struct smbhdr *)p; | |||
98 | ||||
99 | smbios_entry.addr = (void *)(uintptr_t)sh->addr; | |||
100 | smbios_entry.len = sh->size; | |||
101 | smbios_entry.rev = 0; | |||
102 | smbios_entry.mjr = sh->majrev; | |||
103 | smbios_entry.min = sh->minrev; | |||
104 | smbios_entry.doc = 0; | |||
105 | smbios_entry.count = sh->count; | |||
106 | } | |||
107 | ||||
108 | static void | |||
109 | smbios3_init(uint8_t *p) | |||
96 | { | 110 | { | |
97 | const struct smb3hdr *sh = (const struct smb3hdr *)p; | 111 | const struct smb3hdr *sh = (const struct smb3hdr *)p; | |
98 | 112 | |||
99 | smbios_entry.addr = (void *)(uintptr_t)sh->addr; | 113 | smbios_entry.addr = (void *)(uintptr_t)sh->addr; | |
100 | smbios_entry.len = sh->size; | 114 | smbios_entry.len = sh->size; | |
101 | smbios_entry.rev = sh->eprev; | 115 | smbios_entry.rev = sh->eprev; | |
102 | smbios_entry.mjr = sh->majrev; | 116 | smbios_entry.mjr = sh->majrev; | |
103 | smbios_entry.min = sh->minrev; | 117 | smbios_entry.min = sh->minrev; | |
104 | smbios_entry.doc = sh->docrev; | 118 | smbios_entry.doc = sh->docrev; | |
105 | smbios_entry.count = UINT16_MAX; | 119 | smbios_entry.count = UINT16_MAX; | |
106 | } | 120 | } | |
107 | 121 | |||
122 | void | |||
123 | smbios_init(uint8_t *p) | |||
124 | { | |||
125 | if (memcmp(p, "_SM3_", 5) == 0) { | |||
126 | smbios3_init(p); | |||
127 | } else if (memcmp(p, "_SM_", 4) == 0) { | |||
128 | smbios2_init(p); | |||
129 | } | |||
130 | } | |||
131 | ||||
108 | /* | 132 | /* | |
109 | * smbios_find_table() takes a caller supplied smbios struct type and | 133 | * smbios_find_table() takes a caller supplied smbios struct type and | |
110 | * a pointer to a handle (struct smbtable) returning one if the structure | 134 | * a pointer to a handle (struct smbtable) returning one if the structure | |
111 | * is successfully located and zero otherwise. Callers should take care | 135 | * is successfully located and zero otherwise. Callers should take care | |
112 | * to initilize the cookie field of the smbtable structure to zero before | 136 | * to initilize the cookie field of the smbtable structure to zero before | |
113 | * the first invocation of this function. | 137 | * the first invocation of this function. | |
114 | * Multiple tables of the same type can be located by repeadtly calling | 138 | * Multiple tables of the same type can be located by repeadtly calling | |
115 | * smbios_find_table with the same arguments. | 139 | * smbios_find_table with the same arguments. | |
116 | */ | 140 | */ | |
117 | int | 141 | int | |
118 | smbios_find_table(uint8_t type, struct smbtable *st) | 142 | smbios_find_table(uint8_t type, struct smbtable *st) | |
119 | { | 143 | { | |
120 | uint8_t *va, *end; | 144 | uint8_t *va, *end; | |
121 | struct smbtblhdr *hdr; | 145 | struct smbtblhdr *hdr; | |
122 | int ret = 0, tcount = 1; | 146 | int ret = 0, tcount = 1; | |
123 | 147 | |||
148 | if (smbios_entry.addr == 0) { | |||
149 | return 0; | |||
150 | } | |||
151 | ||||
124 | va = smbios_entry.addr; | 152 | va = smbios_entry.addr; | |
125 | end = va + smbios_entry.len; | 153 | end = va + smbios_entry.len; | |
126 | 154 | |||
127 | /* | 155 | /* | |
128 | * The cookie field of the smtable structure is used to locate | 156 | * The cookie field of the smtable structure is used to locate | |
129 | * multiple instances of a table of an arbitrary type. Following the | 157 | * multiple instances of a table of an arbitrary type. Following the | |
130 | * sucessful location of a table, the type is encoded as bits 0:7 of | 158 | * sucessful location of a table, the type is encoded as bits 0:7 of | |
131 | * the cookie value, the offset in terms of the number of structures | 159 | * the cookie value, the offset in terms of the number of structures | |
132 | * preceding that referenced by the handle is encoded in bits 15:31. | 160 | * preceding that referenced by the handle is encoded in bits 15:31. | |
133 | */ | 161 | */ | |
134 | if ((st->cookie & 0xfff) == type && st->cookie >> 16) { | 162 | if ((st->cookie & 0xfff) == type && st->cookie >> 16) { | |
135 | if ((uint8_t *)st->hdr >= va && (uint8_t *)st->hdr < end) { | 163 | if ((uint8_t *)st->hdr >= va && (uint8_t *)st->hdr < end) { | |
136 | hdr = st->hdr; | 164 | hdr = st->hdr; | |
@@ -163,26 +191,30 @@ smbios_find_table(uint8_t type, struct s | @@ -163,26 +191,30 @@ smbios_find_table(uint8_t type, struct s | |||
163 | va+=2; | 191 | va+=2; | |
164 | } | 192 | } | |
165 | 193 | |||
166 | return ret; | 194 | return ret; | |
167 | } | 195 | } | |
168 | 196 | |||
169 | char * | 197 | char * | |
170 | smbios_get_string(struct smbtable *st, uint8_t indx, char *dest, size_t len) | 198 | smbios_get_string(struct smbtable *st, uint8_t indx, char *dest, size_t len) | |
171 | { | 199 | { | |
172 | uint8_t *va, *end; | 200 | uint8_t *va, *end; | |
173 | char *ret = NULL; | 201 | char *ret = NULL; | |
174 | int i; | 202 | int i; | |
175 | 203 | |||
204 | if (smbios_entry.addr == 0) { | |||
205 | return NULL; | |||
206 | } | |||
207 | ||||
176 | va = (uint8_t *)st->hdr + st->hdr->size; | 208 | va = (uint8_t *)st->hdr + st->hdr->size; | |
177 | end = smbios_entry.addr + smbios_entry.len; | 209 | end = smbios_entry.addr + smbios_entry.len; | |
178 | for (i = 1; va < end && i < indx && *va; i++) | 210 | for (i = 1; va < end && i < indx && *va; i++) | |
179 | while (*va++) | 211 | while (*va++) | |
180 | ; | 212 | ; | |
181 | if (i == indx) { | 213 | if (i == indx) { | |
182 | if (va + len < end) { | 214 | if (va + len < end) { | |
183 | ret = dest; | 215 | ret = dest; | |
184 | memcpy(ret, va, len); | 216 | memcpy(ret, va, len); | |
185 | ret[len - 1] = '\0'; | 217 | ret[len - 1] = '\0'; | |
186 | } | 218 | } | |
187 | } | 219 | } | |
188 | 220 |