summaryrefslogtreecommitdiff
path: root/src/pc80
diff options
context:
space:
mode:
authorLuc Verhaegen <libv@skynet.be>2009-06-03 10:47:19 +0000
committerLuc Verhaegen <libv@skynet.be>2009-06-03 10:47:19 +0000
commit9ceae905f10a555835db0af072c3adfff98b3a7b (patch)
tree1ea238ee0dbf2b4c4ed206326ed4bf2457f9a018 /src/pc80
parenta922b3195b77a3cc82bafad20dd3dfcfd2a61bc0 (diff)
CMOS: Add set_option and rework get_option.
To ease some of my debugging pain on the unichrome, i decided i needed to move FB size selection into cmos, so i could test a size and then reset it to the default after loading this value so that the next reboot uses the (working) default again. This meant implementing set_option in parallel to get_option. get_option was then found to have inversed argument ordering (like outb) and passing char * and then depending on the cmos layout length, which made me feel quite uncomfortable. Since we either have reserved space (which we shouldn't do anything with in these two functions), an enum or a hexadecimal value, unsigned int seemed like the way to go. So all users of get_option now have their arguments inversed and switched from using ints to unsigned ints now. The way get_cmos_value was implemented forced us to not overlap byte and to have multibyte values be byte aligned. This logic is now adapted to do a full uint32_t read (when needed) at any offset and any length up to 32, and the shifting all happens inside an uint32_t as well. set_cmos_value was implemented similarly. Both routines have been extensively tested in a quick separate little program as it is not easy to get this stuff right. build_opt_tbl.c was altered to function correctly within these new parameters. The enum value retrieval has been changed strol(..., NULL, 10) to stroul(..., NULL, 0), so that we not only are able to use unsigned ints now but so that we also interprete hex values correctly. The 32bit limit gets imposed on all entries not marked reserved, an unused "user_data" field that appeared in a lot of cmos.layouts has been changed to reserved as well. Signed-off-by: Luc Verhaegen <libv@skynet.be> Acked-by: Peter Stuge <peter@stuge.se> git-svn-id: svn://svn.coreboot.org/coreboot/trunk@4332 2b7e53f0-3cfb-0310-b3e9-8179ed1497e1
Diffstat (limited to 'src/pc80')
-rw-r--r--src/pc80/mc146818rtc.c160
1 files changed, 124 insertions, 36 deletions
diff --git a/src/pc80/mc146818rtc.c b/src/pc80/mc146818rtc.c
index 0baa389c1f..55f161f29e 100644
--- a/src/pc80/mc146818rtc.c
+++ b/src/pc80/mc146818rtc.c
@@ -197,41 +197,69 @@ void rtc_init(int invalid)
#if USE_OPTION_TABLE == 1
-/* 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
- output the value placed in ret
- returns 0 = successful, -1 = an error occurred
-*/
-static int get_cmos_value(unsigned long bit, unsigned long length, void *vret)
+/*
+ * Functions to save/return values stored in the 256byte cmos.
+ *
+ * To be able to use space maximally we want to only store as many bits as
+ * needed, and not be limited by byte boundaries. We therefor clamp the size
+ * down to an unsigned int. Since the values that we are allowed to touch are
+ * either an enum or a hexadecimal value, this size should suit most purposes.
+ *
+ * These two functions are doing bitshifting, and are therefor a bit
+ * nontrivial. To understand these operations, first read the ones outside the
+ * loop. The ones inside the loop are just adding i to the same calculations,
+ * with the shift twice inverted, as negative shifts aren't nice.
+ */
+static unsigned int
+get_cmos_value(int bit, int length)
{
- 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 that one byte so transfer the whole bytes */
- for(i=0;length;i++,length-=8,byte++) {
- /* load the byte */
- ret[i]=cmos_read(byte);
- }
+ unsigned int tmp;
+ int i;
+
+ /* negative left shift --> right shift */
+ tmp = cmos_read(bit / 8) >> (bit % 8);
+
+ for (i = 1; (8 * i) < ((bit % 8) + length); i++)
+ tmp |= cmos_read((bit / 8) + i) << ((8 * i) - (bit % 8));
+
+ /* 1 << 32 - 1 isn't cool inside an int */
+ if (length != 32)
+ tmp &= (1 << length) - 1;
+
+ return tmp;
+}
+
+static void
+set_cmos_value(int bit, int length, unsigned int value)
+{
+ unsigned int mask;
+ unsigned char cmos;
+ int i;
+
+ /* 1 << 32 - 1 isn't cool inside an int */
+ if (length != 32)
+ mask = (1 << length) - 1;
+ else
+ mask = -1;
+
+ value &= mask;
+
+ /* negative right shifts --> left shifts */
+ cmos = cmos_read(bit / 8);
+ cmos &= ~(mask << (bit % 8));
+ cmos |= value << (bit % 8);
+ cmos_write(cmos, bit / 8);
+
+ for (i = 1; (8 * i) < ((bit % 8) + length); i++) {
+ cmos = cmos_read((bit / 8) + i);
+ cmos &= ~(mask >> ((8 * i) - (bit % 8)));
+ cmos |= value >> ((8 * i) - (bit % 8));
+ cmos_write(cmos, (bit / 8) + i);
}
- return 0;
}
-int get_option(void *dest, char *name)
+int
+get_option(char *name, unsigned int *value)
{
extern struct cmos_option_table option_table;
struct cmos_option_table *ct;
@@ -241,7 +269,7 @@ int get_option(void *dest, char *name)
/* Figure out how long name is */
namelen = strnlen(name, CMOS_MAX_NAME_LENGTH);
-
+
/* find the requested entry record */
ct=&option_table;
ce=(struct cmos_entries*)((unsigned char *)ct + ct->header_length);
@@ -256,12 +284,72 @@ int get_option(void *dest, char *name)
printk_err("ERROR: No cmos option '%s'\n", name);
return(-2);
}
-
- if(get_cmos_value(ce->bit, ce->length, dest))
- return(-3);
+
+ if (ce->length > 32) {
+ printk_err("ERROR: cmos option '%s' is too large.\n", name);
+ return -3;
+ }
+
+
+ *value = get_cmos_value(ce->bit, ce->length);
+
if(!rtc_checksum_valid(LB_CKS_RANGE_START,
- LB_CKS_RANGE_END,LB_CKS_LOC))
+ LB_CKS_RANGE_END,LB_CKS_LOC))
return(-4);
return(0);
}
+
+int
+set_option(char *name, unsigned int value)
+{
+ extern struct cmos_option_table option_table;
+ struct cmos_option_table *ct;
+ struct cmos_entries *ce;
+ size_t namelen;
+ int found = 0;
+
+ /* Figure out how long name is */
+ namelen = strnlen(name, CMOS_MAX_NAME_LENGTH);
+
+ /* find the requested entry record */
+ ct = &option_table;
+ 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_err("ERROR: Unknown cmos option '%s'\n", name);
+ return(-2);
+ }
+
+ if (ce->length > 32) {
+ printk_err("ERROR: cmos option '%s' is too large.\n", name);
+ return -3;
+ }
+
+ set_cmos_value(ce->bit, ce->length, value);
+
+ /* We should not update the checksum here. */
+
+ return 0;
+}
+#else
+int
+get_option(char *name, unsigned int *value)
+{
+ return -2;
+}
+
+int
+set_option(char *name, unsigned int value)
+{
+ return -2;
+}
+
#endif /* USE_OPTION_TABLE */