summaryrefslogtreecommitdiffstats
path: root/sys/dev/nvram/nvram.c
blob: 164287ef097a566435258ecc1a1d40db47ab2af1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*-
 * Copyright (c) 2007 Peter Wemm
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD$
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include <sys/proc.h>
#include <sys/sx.h>
#include <sys/uio.h>
#include <sys/module.h>

#include <isa/rtc.h>

/*
 * Linux-style /dev/nvram driver
 *
 * cmos ram starts at bytes 14 through 128, for a total of 114 bytes.
 * The driver exposes byte 14 as file offset 0.
 *
 * Offsets 2 through 31 are checksummed at offset 32, 33.
 * In order to avoid the possibility of making the machine unbootable at the
 * bios level (press F1 to continue!), we refuse to allow writes if we do
 * not see a pre-existing valid checksum.  If the existing sum is invalid,
 * then presumably we do not know how to make a sum that the bios will accept.
 */

#define NVRAM_FIRST	RTC_DIAG	/* 14 */
#define NVRAM_LAST	128

#define CKSUM_FIRST	2
#define CKSUM_LAST	31
#define CKSUM_MSB	32
#define CKSUM_LSB	33

static d_open_t		nvram_open;
static d_read_t		nvram_read;
static d_write_t	nvram_write;

static struct cdev *nvram_dev;
static struct sx nvram_lock;

static struct cdevsw nvram_cdevsw = {
	.d_version =	D_VERSION,
	.d_open =	nvram_open,
	.d_read =	nvram_read,
	.d_write =	nvram_write,
	.d_name =	"nvram",
};

static int
nvram_open(struct cdev *dev __unused, int flags, int fmt __unused,
    struct thread *td)
{
	int error = 0;

	if (flags & FWRITE)
		error = securelevel_gt(td->td_ucred, 0);

	return (error);
}

static int
nvram_read(struct cdev *dev, struct uio *uio, int flags)
{
	int nv_off;
	u_char v;
	int error = 0;

	while (uio->uio_resid > 0 && error == 0) {
		nv_off = uio->uio_offset + NVRAM_FIRST;
		if (nv_off < NVRAM_FIRST || nv_off >= NVRAM_LAST)
			return (0);	/* Signal EOF */
		/* Single byte at a time */
		v = rtcin(nv_off);
		error = uiomove(&v, 1, uio);
	}
	return (error);

}

static int
nvram_write(struct cdev *dev, struct uio *uio, int flags)
{
	int nv_off;
	u_char v;
	int error = 0;
	int i;
	uint16_t sum;

	sx_xlock(&nvram_lock);

	/* Assert that we understand the existing checksum first!  */
	sum = rtcin(NVRAM_FIRST + CKSUM_MSB) << 8 |
	      rtcin(NVRAM_FIRST + CKSUM_LSB);
	for (i = CKSUM_FIRST; i <= CKSUM_LAST; i++)
		sum -= rtcin(NVRAM_FIRST + i);
	if (sum != 0) {
		sx_xunlock(&nvram_lock);
		return (EIO);
	}
	/* Bring in user data and write */
	while (uio->uio_resid > 0 && error == 0) {
		nv_off = uio->uio_offset + NVRAM_FIRST;
		if (nv_off < NVRAM_FIRST || nv_off >= NVRAM_LAST) {
			sx_xunlock(&nvram_lock);
			return (0);	/* Signal EOF */
		}
		/* Single byte at a time */
		error = uiomove(&v, 1, uio);
		writertc(nv_off, v);
	}
	/* Recalculate checksum afterwards */
	sum = 0;
	for (i = CKSUM_FIRST; i <= CKSUM_LAST; i++)
		sum += rtcin(NVRAM_FIRST + i);
	writertc(NVRAM_FIRST + CKSUM_MSB, sum >> 8);
	writertc(NVRAM_FIRST + CKSUM_LSB, sum);
	sx_xunlock(&nvram_lock);
	return (error);
}

static int
nvram_modevent(module_t mod __unused, int type, void *data __unused)
{
	switch (type) {
	case MOD_LOAD:
		sx_init(&nvram_lock, "nvram");
		nvram_dev = make_dev(&nvram_cdevsw, 0,
		    UID_ROOT, GID_KMEM, 0640, "nvram");
		break;
	case MOD_UNLOAD:
	case MOD_SHUTDOWN:
		destroy_dev(nvram_dev);
		sx_destroy(&nvram_lock);
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}
DEV_MODULE(nvram, nvram_modevent, NULL);
OpenPOWER on IntegriCloud