| /* |
| * This file is part of the coreboot project. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; version 2 of the License. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <console/console.h> |
| #include <string.h> |
| #include <cbfs.h> |
| #include <option.h> |
| #include <pc80/mc146818rtc.h> |
| #include <types.h> |
| |
| /* option_table.h is autogenerated */ |
| #include "option_table.h" |
| |
| /* Don't warn for checking >= LB_CKS_RANGE_START even though it may be 0. */ |
| #pragma GCC diagnostic ignored "-Wtype-limits" |
| |
| /* |
| * This routine returns the value of the requested bits. |
| * input bit = bit count from the beginning of the CMOS image |
| * length = number of bits to include in the value |
| * ret = a character pointer to where the value is to be returned |
| * returns CB_SUCCESS = successful, cb_err code if an error occurred |
| */ |
| static enum cb_err get_cmos_value(unsigned long bit, unsigned long length, |
| void *vret) |
| { |
| unsigned char *ret; |
| unsigned long byte, byte_bit; |
| unsigned long i; |
| unsigned char uchar; |
| |
| /* |
| * The table is checked when it is built to ensure all |
| * values are valid. |
| */ |
| ret = vret; |
| byte = bit / 8; /* find the byte where the data starts */ |
| byte_bit = bit % 8; /* find the bit in the byte where the data starts */ |
| if (length < 9) { /* one byte or less */ |
| uchar = cmos_read(byte); /* load the byte */ |
| uchar >>= byte_bit; /* shift the bits to byte align */ |
| /* clear unspecified bits */ |
| ret[0] = uchar & ((1 << length) - 1); |
| } else { /* more than one byte so transfer the whole bytes */ |
| for (i = 0; length; i++, length -= 8, byte++) { |
| /* load the byte */ |
| ret[i] = cmos_read(byte); |
| } |
| } |
| return CB_SUCCESS; |
| } |
| |
| static enum cb_err locate_cmos_layout(struct region_device *rdev) |
| { |
| uint32_t cbfs_type = CBFS_COMPONENT_CMOS_LAYOUT; |
| MAYBE_STATIC_BSS struct cbfsf fh = {}; |
| |
| /* |
| * In case VBOOT is enabled and this function is called from SMM, |
| * we have multiple CMOS layout files and to locate them we'd need to |
| * include VBOOT into SMM... |
| * |
| * Support only one CMOS layout in the 'COREBOOT' region for now. |
| */ |
| if (!region_device_sz(&(fh.data))) { |
| if (cbfs_locate_file_in_region(&fh, "COREBOOT", "cmos_layout.bin", |
| &cbfs_type)) { |
| printk(BIOS_ERR, "RTC: cmos_layout.bin could not be found. " |
| "Options are disabled\n"); |
| return CB_CMOS_LAYOUT_NOT_FOUND; |
| } |
| } |
| |
| cbfs_file_data(rdev, &fh); |
| |
| return CB_SUCCESS; |
| } |
| |
| enum cb_err cmos_get_option(void *dest, const char *name) |
| { |
| struct cmos_option_table *ct; |
| struct region_device rdev; |
| struct cmos_entries *ce; |
| size_t namelen; |
| int found = 0; |
| |
| /* Figure out how long name is */ |
| namelen = strnlen(name, CMOS_MAX_NAME_LENGTH); |
| |
| if (locate_cmos_layout(&rdev) != CB_SUCCESS) { |
| return CB_CMOS_LAYOUT_NOT_FOUND; |
| } |
| ct = rdev_mmap_full(&rdev); |
| if (!ct) { |
| printk(BIOS_ERR, "RTC: cmos_layout.bin could not be mapped. " |
| "Options are disabled\n"); |
| |
| return CB_CMOS_LAYOUT_NOT_FOUND; |
| } |
| |
| /* find the requested entry record */ |
| ce = (struct cmos_entries *)((unsigned char *)ct + ct->header_length); |
| for (; ce->tag == LB_TAG_OPTION; |
| ce = (struct cmos_entries *)((unsigned char *)ce + ce->size)) { |
| if (memcmp(ce->name, name, namelen) == 0) { |
| found = 1; |
| break; |
| } |
| } |
| if (!found) { |
| printk(BIOS_DEBUG, "No CMOS option '%s'.\n", name); |
| rdev_munmap(&rdev, ct); |
| return CB_CMOS_OPTION_NOT_FOUND; |
| } |
| |
| if (!cmos_checksum_valid(LB_CKS_RANGE_START, LB_CKS_RANGE_END, LB_CKS_LOC)) { |
| rdev_munmap(&rdev, ct); |
| return CB_CMOS_CHECKSUM_INVALID; |
| } |
| if (get_cmos_value(ce->bit, ce->length, dest) != CB_SUCCESS) { |
| rdev_munmap(&rdev, ct); |
| return CB_CMOS_ACCESS_ERROR; |
| } |
| rdev_munmap(&rdev, ct); |
| return CB_SUCCESS; |
| } |
| |
| static enum cb_err set_cmos_value(unsigned long bit, unsigned long length, |
| void *vret) |
| { |
| unsigned char *ret; |
| unsigned long byte, byte_bit; |
| unsigned long i; |
| unsigned char uchar, mask; |
| unsigned int chksum_update_needed = 0; |
| |
| ret = vret; |
| byte = bit / 8; /* find the byte where the data starts */ |
| byte_bit = bit % 8; /* find the bit where the data starts */ |
| if (length <= 8) { /* one byte or less */ |
| mask = (1 << length) - 1; |
| mask <<= byte_bit; |
| |
| uchar = cmos_read(byte); |
| uchar &= ~mask; |
| uchar |= (ret[0] << byte_bit); |
| cmos_write(uchar, byte); |
| if (byte >= LB_CKS_RANGE_START && byte <= LB_CKS_RANGE_END) |
| chksum_update_needed = 1; |
| } else { /* more that one byte so transfer the whole bytes */ |
| if (byte_bit || length % 8) |
| return CB_ERR_ARG; |
| |
| for (i = 0; length; i++, length -= 8, byte++) { |
| cmos_write(ret[i], byte); |
| if (byte >= LB_CKS_RANGE_START && |
| byte <= LB_CKS_RANGE_END) |
| chksum_update_needed = 1; |
| } |
| } |
| |
| if (chksum_update_needed) { |
| cmos_set_checksum(LB_CKS_RANGE_START, LB_CKS_RANGE_END, |
| LB_CKS_LOC); |
| } |
| return CB_SUCCESS; |
| } |
| |
| enum cb_err cmos_set_option(const char *name, void *value) |
| { |
| struct cmos_option_table *ct; |
| struct region_device rdev; |
| struct cmos_entries *ce; |
| unsigned long length; |
| size_t namelen; |
| int found = 0; |
| |
| /* Figure out how long name is */ |
| namelen = strnlen(name, CMOS_MAX_NAME_LENGTH); |
| |
| if (locate_cmos_layout(&rdev) != CB_SUCCESS) { |
| return CB_CMOS_LAYOUT_NOT_FOUND; |
| } |
| ct = rdev_mmap_full(&rdev); |
| if (!ct) { |
| printk(BIOS_ERR, "RTC: cmos_layout.bin could not be mapped. " |
| "Options are disabled\n"); |
| |
| return CB_CMOS_LAYOUT_NOT_FOUND; |
| } |
| |
| /* find the requested entry record */ |
| ce = (struct cmos_entries *)((unsigned char *)ct + ct->header_length); |
| for (; ce->tag == LB_TAG_OPTION; |
| ce = (struct cmos_entries *)((unsigned char *)ce + ce->size)) { |
| if (memcmp(ce->name, name, namelen) == 0) { |
| found = 1; |
| break; |
| } |
| } |
| if (!found) { |
| printk(BIOS_DEBUG, "WARNING: No CMOS option '%s'.\n", name); |
| rdev_munmap(&rdev, ct); |
| return CB_CMOS_OPTION_NOT_FOUND; |
| } |
| |
| length = ce->length; |
| if (ce->config == 's') { |
| length = MAX(strlen((const char *)value) * 8, ce->length - 8); |
| /* make sure the string is null terminated */ |
| if (set_cmos_value(ce->bit + ce->length - 8, 8, &(u8[]){0}) |
| != CB_SUCCESS) { |
| rdev_munmap(&rdev, ct); |
| return CB_CMOS_ACCESS_ERROR; |
| } |
| } |
| |
| if (set_cmos_value(ce->bit, length, value) != CB_SUCCESS) { |
| rdev_munmap(&rdev, ct); |
| return CB_CMOS_ACCESS_ERROR; |
| } |
| |
| rdev_munmap(&rdev, ct); |
| return CB_SUCCESS; |
| } |
| |
| int cmos_lb_cks_valid(void) |
| { |
| return cmos_checksum_valid(LB_CKS_RANGE_START, LB_CKS_RANGE_END, LB_CKS_LOC); |
| } |
| |
| |
| void sanitize_cmos(void) |
| { |
| const unsigned char *cmos_default; |
| const bool cmos_need_reset = |
| CONFIG(STATIC_OPTION_TABLE) || cmos_error() || !cmos_lb_cks_valid(); |
| size_t length = 128; |
| size_t i; |
| |
| if (CONFIG(TPM_MEASURED_BOOT) || cmos_need_reset) { |
| cmos_default = cbfs_boot_map_with_leak("cmos.default", |
| CBFS_COMPONENT_CMOS_DEFAULT, &length); |
| |
| if (!cmos_default || !cmos_need_reset) |
| return; |
| |
| u8 control_state = cmos_disable_rtc(); |
| for (i = 14; i < MIN(128, length); i++) |
| cmos_write_inner(cmos_default[i], i); |
| cmos_restore_rtc(control_state); |
| } |
| } |