Angel Pons | 8a3453f | 2020-04-02 23:48:19 +0200 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
Kyösti Mälkki | 034cf63 | 2020-01-05 08:12:00 +0200 | [diff] [blame] | 2 | |
| 3 | #include <console/console.h> |
| 4 | #include <string.h> |
| 5 | #include <cbfs.h> |
| 6 | #include <option.h> |
| 7 | #include <pc80/mc146818rtc.h> |
| 8 | #include <types.h> |
| 9 | |
| 10 | /* option_table.h is autogenerated */ |
| 11 | #include "option_table.h" |
| 12 | |
| 13 | /* Don't warn for checking >= LB_CKS_RANGE_START even though it may be 0. */ |
| 14 | #pragma GCC diagnostic ignored "-Wtype-limits" |
| 15 | |
| 16 | /* |
| 17 | * This routine returns the value of the requested bits. |
Elyes HAOUAS | 2119d0b | 2020-02-16 10:01:33 +0100 | [diff] [blame] | 18 | * input bit = bit count from the beginning of the CMOS image |
Kyösti Mälkki | 034cf63 | 2020-01-05 08:12:00 +0200 | [diff] [blame] | 19 | * length = number of bits to include in the value |
| 20 | * ret = a character pointer to where the value is to be returned |
| 21 | * returns CB_SUCCESS = successful, cb_err code if an error occurred |
| 22 | */ |
| 23 | static enum cb_err get_cmos_value(unsigned long bit, unsigned long length, |
| 24 | void *vret) |
| 25 | { |
| 26 | unsigned char *ret; |
| 27 | unsigned long byte, byte_bit; |
| 28 | unsigned long i; |
| 29 | unsigned char uchar; |
| 30 | |
| 31 | /* |
| 32 | * The table is checked when it is built to ensure all |
| 33 | * values are valid. |
| 34 | */ |
| 35 | ret = vret; |
| 36 | byte = bit / 8; /* find the byte where the data starts */ |
| 37 | byte_bit = bit % 8; /* find the bit in the byte where the data starts */ |
| 38 | if (length < 9) { /* one byte or less */ |
| 39 | uchar = cmos_read(byte); /* load the byte */ |
| 40 | uchar >>= byte_bit; /* shift the bits to byte align */ |
| 41 | /* clear unspecified bits */ |
| 42 | ret[0] = uchar & ((1 << length) - 1); |
| 43 | } else { /* more than one byte so transfer the whole bytes */ |
| 44 | for (i = 0; length; i++, length -= 8, byte++) { |
| 45 | /* load the byte */ |
| 46 | ret[i] = cmos_read(byte); |
| 47 | } |
| 48 | } |
| 49 | return CB_SUCCESS; |
| 50 | } |
| 51 | |
| 52 | static enum cb_err locate_cmos_layout(struct region_device *rdev) |
| 53 | { |
| 54 | uint32_t cbfs_type = CBFS_COMPONENT_CMOS_LAYOUT; |
Kyösti Mälkki | fcbbb91 | 2020-04-20 10:21:39 +0300 | [diff] [blame] | 55 | static struct cbfsf fh; |
Kyösti Mälkki | 034cf63 | 2020-01-05 08:12:00 +0200 | [diff] [blame] | 56 | |
| 57 | /* |
| 58 | * In case VBOOT is enabled and this function is called from SMM, |
| 59 | * we have multiple CMOS layout files and to locate them we'd need to |
| 60 | * include VBOOT into SMM... |
| 61 | * |
| 62 | * Support only one CMOS layout in the 'COREBOOT' region for now. |
| 63 | */ |
| 64 | if (!region_device_sz(&(fh.data))) { |
| 65 | if (cbfs_locate_file_in_region(&fh, "COREBOOT", "cmos_layout.bin", |
| 66 | &cbfs_type)) { |
| 67 | printk(BIOS_ERR, "RTC: cmos_layout.bin could not be found. " |
| 68 | "Options are disabled\n"); |
| 69 | return CB_CMOS_LAYOUT_NOT_FOUND; |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | cbfs_file_data(rdev, &fh); |
| 74 | |
| 75 | return CB_SUCCESS; |
| 76 | } |
| 77 | |
| 78 | enum cb_err cmos_get_option(void *dest, const char *name) |
| 79 | { |
| 80 | struct cmos_option_table *ct; |
| 81 | struct region_device rdev; |
| 82 | struct cmos_entries *ce; |
| 83 | size_t namelen; |
| 84 | int found = 0; |
| 85 | |
| 86 | /* Figure out how long name is */ |
| 87 | namelen = strnlen(name, CMOS_MAX_NAME_LENGTH); |
| 88 | |
| 89 | if (locate_cmos_layout(&rdev) != CB_SUCCESS) { |
| 90 | return CB_CMOS_LAYOUT_NOT_FOUND; |
| 91 | } |
| 92 | ct = rdev_mmap_full(&rdev); |
| 93 | if (!ct) { |
| 94 | printk(BIOS_ERR, "RTC: cmos_layout.bin could not be mapped. " |
| 95 | "Options are disabled\n"); |
| 96 | |
| 97 | return CB_CMOS_LAYOUT_NOT_FOUND; |
| 98 | } |
| 99 | |
| 100 | /* find the requested entry record */ |
| 101 | ce = (struct cmos_entries *)((unsigned char *)ct + ct->header_length); |
| 102 | for (; ce->tag == LB_TAG_OPTION; |
| 103 | ce = (struct cmos_entries *)((unsigned char *)ce + ce->size)) { |
| 104 | if (memcmp(ce->name, name, namelen) == 0) { |
| 105 | found = 1; |
| 106 | break; |
| 107 | } |
| 108 | } |
| 109 | if (!found) { |
| 110 | printk(BIOS_DEBUG, "No CMOS option '%s'.\n", name); |
| 111 | rdev_munmap(&rdev, ct); |
| 112 | return CB_CMOS_OPTION_NOT_FOUND; |
| 113 | } |
| 114 | |
| 115 | if (!cmos_checksum_valid(LB_CKS_RANGE_START, LB_CKS_RANGE_END, LB_CKS_LOC)) { |
| 116 | rdev_munmap(&rdev, ct); |
| 117 | return CB_CMOS_CHECKSUM_INVALID; |
| 118 | } |
| 119 | if (get_cmos_value(ce->bit, ce->length, dest) != CB_SUCCESS) { |
| 120 | rdev_munmap(&rdev, ct); |
| 121 | return CB_CMOS_ACCESS_ERROR; |
| 122 | } |
| 123 | rdev_munmap(&rdev, ct); |
| 124 | return CB_SUCCESS; |
| 125 | } |
| 126 | |
| 127 | static enum cb_err set_cmos_value(unsigned long bit, unsigned long length, |
| 128 | void *vret) |
| 129 | { |
| 130 | unsigned char *ret; |
| 131 | unsigned long byte, byte_bit; |
| 132 | unsigned long i; |
| 133 | unsigned char uchar, mask; |
| 134 | unsigned int chksum_update_needed = 0; |
| 135 | |
| 136 | ret = vret; |
| 137 | byte = bit / 8; /* find the byte where the data starts */ |
| 138 | byte_bit = bit % 8; /* find the bit where the data starts */ |
| 139 | if (length <= 8) { /* one byte or less */ |
| 140 | mask = (1 << length) - 1; |
| 141 | mask <<= byte_bit; |
| 142 | |
| 143 | uchar = cmos_read(byte); |
| 144 | uchar &= ~mask; |
| 145 | uchar |= (ret[0] << byte_bit); |
| 146 | cmos_write(uchar, byte); |
| 147 | if (byte >= LB_CKS_RANGE_START && byte <= LB_CKS_RANGE_END) |
| 148 | chksum_update_needed = 1; |
| 149 | } else { /* more that one byte so transfer the whole bytes */ |
| 150 | if (byte_bit || length % 8) |
| 151 | return CB_ERR_ARG; |
| 152 | |
| 153 | for (i = 0; length; i++, length -= 8, byte++) { |
| 154 | cmos_write(ret[i], byte); |
| 155 | if (byte >= LB_CKS_RANGE_START && |
| 156 | byte <= LB_CKS_RANGE_END) |
| 157 | chksum_update_needed = 1; |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | if (chksum_update_needed) { |
| 162 | cmos_set_checksum(LB_CKS_RANGE_START, LB_CKS_RANGE_END, |
| 163 | LB_CKS_LOC); |
| 164 | } |
| 165 | return CB_SUCCESS; |
| 166 | } |
| 167 | |
| 168 | enum cb_err cmos_set_option(const char *name, void *value) |
| 169 | { |
| 170 | struct cmos_option_table *ct; |
| 171 | struct region_device rdev; |
| 172 | struct cmos_entries *ce; |
| 173 | unsigned long length; |
| 174 | size_t namelen; |
| 175 | int found = 0; |
| 176 | |
| 177 | /* Figure out how long name is */ |
| 178 | namelen = strnlen(name, CMOS_MAX_NAME_LENGTH); |
| 179 | |
| 180 | if (locate_cmos_layout(&rdev) != CB_SUCCESS) { |
| 181 | return CB_CMOS_LAYOUT_NOT_FOUND; |
| 182 | } |
| 183 | ct = rdev_mmap_full(&rdev); |
| 184 | if (!ct) { |
| 185 | printk(BIOS_ERR, "RTC: cmos_layout.bin could not be mapped. " |
| 186 | "Options are disabled\n"); |
| 187 | |
| 188 | return CB_CMOS_LAYOUT_NOT_FOUND; |
| 189 | } |
| 190 | |
| 191 | /* find the requested entry record */ |
| 192 | ce = (struct cmos_entries *)((unsigned char *)ct + ct->header_length); |
| 193 | for (; ce->tag == LB_TAG_OPTION; |
| 194 | ce = (struct cmos_entries *)((unsigned char *)ce + ce->size)) { |
| 195 | if (memcmp(ce->name, name, namelen) == 0) { |
| 196 | found = 1; |
| 197 | break; |
| 198 | } |
| 199 | } |
| 200 | if (!found) { |
| 201 | printk(BIOS_DEBUG, "WARNING: No CMOS option '%s'.\n", name); |
| 202 | rdev_munmap(&rdev, ct); |
| 203 | return CB_CMOS_OPTION_NOT_FOUND; |
| 204 | } |
| 205 | |
| 206 | length = ce->length; |
| 207 | if (ce->config == 's') { |
| 208 | length = MAX(strlen((const char *)value) * 8, ce->length - 8); |
| 209 | /* make sure the string is null terminated */ |
| 210 | if (set_cmos_value(ce->bit + ce->length - 8, 8, &(u8[]){0}) |
| 211 | != CB_SUCCESS) { |
| 212 | rdev_munmap(&rdev, ct); |
| 213 | return CB_CMOS_ACCESS_ERROR; |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | if (set_cmos_value(ce->bit, length, value) != CB_SUCCESS) { |
| 218 | rdev_munmap(&rdev, ct); |
| 219 | return CB_CMOS_ACCESS_ERROR; |
| 220 | } |
| 221 | |
| 222 | rdev_munmap(&rdev, ct); |
| 223 | return CB_SUCCESS; |
| 224 | } |
Kyösti Mälkki | b2680a1 | 2020-01-04 18:04:39 +0200 | [diff] [blame] | 225 | |
| 226 | int cmos_lb_cks_valid(void) |
| 227 | { |
| 228 | return cmos_checksum_valid(LB_CKS_RANGE_START, LB_CKS_RANGE_END, LB_CKS_LOC); |
| 229 | } |
| 230 | |
Kyösti Mälkki | b2680a1 | 2020-01-04 18:04:39 +0200 | [diff] [blame] | 231 | void sanitize_cmos(void) |
| 232 | { |
Bill XIE | 51ce41c | 2020-03-29 21:40:04 +0800 | [diff] [blame] | 233 | const unsigned char *cmos_default; |
| 234 | const bool cmos_need_reset = |
| 235 | CONFIG(STATIC_OPTION_TABLE) || cmos_error() || !cmos_lb_cks_valid(); |
| 236 | size_t length = 128; |
| 237 | size_t i; |
| 238 | |
| 239 | if (CONFIG(TPM_MEASURED_BOOT) || cmos_need_reset) { |
Julius Werner | 834b3ec | 2020-03-04 16:52:08 -0800 | [diff] [blame^] | 240 | cmos_default = cbfs_map("cmos.default", &length); |
Bill XIE | 51ce41c | 2020-03-29 21:40:04 +0800 | [diff] [blame] | 241 | |
| 242 | if (!cmos_default || !cmos_need_reset) |
| 243 | return; |
| 244 | |
| 245 | u8 control_state = cmos_disable_rtc(); |
| 246 | for (i = 14; i < MIN(128, length); i++) |
| 247 | cmos_write_inner(cmos_default[i], i); |
| 248 | cmos_restore_rtc(control_state); |
| 249 | } |
Kyösti Mälkki | b2680a1 | 2020-01-04 18:04:39 +0200 | [diff] [blame] | 250 | } |