xref: /freebsd/sys/dev/random/nehemiah.c (revision ce3adf4362fcca6a43e500b2531f0038adbfbd21)
1 /*-
2  * Copyright (c) 2013 David E. O'Brien <obrien@NUXI.org>
3  * Copyright (c) 2004 Mark R V Murray
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer
11  *    in this position and unchanged.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/time.h>
34 #include <sys/lock.h>
35 #include <sys/mutex.h>
36 #include <sys/module.h>
37 #include <sys/selinfo.h>
38 #include <sys/systm.h>
39 #include <sys/kernel.h>
40 
41 #include <machine/pcb.h>
42 #include <machine/md_var.h>
43 #include <machine/specialreg.h>
44 
45 #include <dev/random/random_adaptors.h>
46 #include <dev/random/randomdev.h>
47 
48 #define RANDOM_BLOCK_SIZE	256
49 #define CIPHER_BLOCK_SIZE	16
50 
51 static void random_nehemiah_init(void);
52 static void random_nehemiah_deinit(void);
53 static int random_nehemiah_read(void *, int);
54 
55 struct random_adaptor random_nehemiah = {
56 	.ident = "Hardware, VIA Nehemiah",
57 	.init = random_nehemiah_init,
58 	.deinit = random_nehemiah_deinit,
59 	.read = random_nehemiah_read,
60 	.write = (random_write_func_t *)random_null_func,
61 	.reseed = (random_reseed_func_t *)random_null_func,
62 	.seeded = 1,
63 };
64 
65 union VIA_ACE_CW {
66 	uint64_t raw;
67 	struct {
68 		u_int round_count : 4;
69 		u_int algorithm_type : 3;
70 		u_int key_generation_type : 1;
71 		u_int intermediate : 1;
72 		u_int decrypt : 1;
73 		u_int key_size : 2;
74 		u_int filler0 : 20;
75 		u_int filler1 : 32;
76 		u_int filler2 : 32;
77 		u_int filler3 : 32;
78 	} field;
79 };
80 
81 /* The extra 7 is to allow an 8-byte write on the last byte of the
82  * arrays.  The ACE wants the AES data 16-byte/128-bit aligned, and
83  * it _always_ writes n*64 bits. The RNG does not care about alignment,
84  * and it always writes n*32 bits or n*64 bits.
85  */
86 static uint8_t key[CIPHER_BLOCK_SIZE+7]	__aligned(16);
87 static uint8_t iv[CIPHER_BLOCK_SIZE+7]	__aligned(16);
88 static uint8_t in[RANDOM_BLOCK_SIZE+7]	__aligned(16);
89 static uint8_t out[RANDOM_BLOCK_SIZE+7]	__aligned(16);
90 
91 static union VIA_ACE_CW acw		__aligned(16);
92 
93 static struct fpu_kern_ctx *fpu_ctx_save;
94 
95 static struct mtx random_nehemiah_mtx;
96 
97 /* ARGSUSED */
98 static __inline size_t
99 VIA_RNG_store(void *buf)
100 {
101 #ifdef __GNUCLIKE_ASM
102 	uint32_t retval = 0;
103 	uint32_t rate = 0;
104 
105 	/* The .byte line is really VIA C3 "xstore" instruction */
106 	__asm __volatile(
107 		"movl	$0,%%edx		\n\t"
108 		".byte	0x0f, 0xa7, 0xc0"
109 			: "=a" (retval), "+d" (rate), "+D" (buf)
110 			:
111 			: "memory"
112 	);
113 	if (rate == 0)
114 		return (retval&0x1f);
115 #endif
116 	return (0);
117 }
118 
119 /* ARGSUSED */
120 static __inline void
121 VIA_ACE_cbc(void *in, void *out, size_t count, void *key, union VIA_ACE_CW *cw, void *iv)
122 {
123 #ifdef __GNUCLIKE_ASM
124 	/* The .byte line is really VIA C3 "xcrypt-cbc" instruction */
125 	__asm __volatile(
126 		"pushf				\n\t"
127 		"popf				\n\t"
128 		"rep				\n\t"
129 		".byte	0x0f, 0xa7, 0xc8"
130 			: "+a" (iv), "+c" (count), "+D" (out), "+S" (in)
131 			: "b" (key), "d" (cw)
132 			: "cc", "memory"
133 		);
134 #endif
135 }
136 
137 static void
138 random_nehemiah_init(void)
139 {
140 	acw.raw = 0ULL;
141 	acw.field.round_count = 12;
142 
143 	mtx_init(&random_nehemiah_mtx, "random nehemiah", NULL, MTX_DEF);
144 	fpu_ctx_save = fpu_kern_alloc_ctx(FPU_KERN_NORMAL);
145 }
146 
147 void
148 random_nehemiah_deinit(void)
149 {
150 
151 	fpu_kern_free_ctx(fpu_ctx_save);
152 	mtx_destroy(&random_nehemiah_mtx);
153 }
154 
155 static int
156 random_nehemiah_read(void *buf, int c)
157 {
158 	int i, error;
159 	size_t count, ret;
160 	uint8_t *p;
161 
162 	mtx_lock(&random_nehemiah_mtx);
163 	error = fpu_kern_enter(curthread, fpu_ctx_save, FPU_KERN_NORMAL);
164 	if (error != 0) {
165 		mtx_unlock(&random_nehemiah_mtx);
166 		return (0);
167 	}
168 
169 	/* Get a random AES key */
170 	count = 0;
171 	p = key;
172 	do {
173 		ret = VIA_RNG_store(p);
174 		p += ret;
175 		count += ret;
176 	} while (count < CIPHER_BLOCK_SIZE);
177 
178 	/* Get a random AES IV */
179 	count = 0;
180 	p = iv;
181 	do {
182 		ret = VIA_RNG_store(p);
183 		p += ret;
184 		count += ret;
185 	} while (count < CIPHER_BLOCK_SIZE);
186 
187 	/* Get a block of random bytes */
188 	count = 0;
189 	p = in;
190 	do {
191 		ret = VIA_RNG_store(p);
192 		p += ret;
193 		count += ret;
194 	} while (count < RANDOM_BLOCK_SIZE);
195 
196 	/* This is a Davies-Meyer hash of the most paranoid variety; the
197 	 * key, IV and the data are all read directly from the hardware RNG.
198 	 * All of these are used precisely once.
199 	 */
200 	VIA_ACE_cbc(in, out, RANDOM_BLOCK_SIZE/CIPHER_BLOCK_SIZE,
201 	    key, &acw, iv);
202 	for (i = 0; i < RANDOM_BLOCK_SIZE; i++)
203 		out[i] ^= in[i];
204 
205 	c = MIN(RANDOM_BLOCK_SIZE, c);
206 	memcpy(buf, out, (size_t)c);
207 
208 	fpu_kern_leave(curthread, fpu_ctx_save);
209 	mtx_unlock(&random_nehemiah_mtx);
210 	return (c);
211 }
212 
213 static int
214 nehemiah_modevent(module_t mod, int type, void *unused)
215 {
216 
217 	switch (type) {
218 	case MOD_LOAD:
219 		if (via_feature_rng & VIA_HAS_RNG) {
220 			random_adaptor_register("nehemiah", &random_nehemiah);
221 			EVENTHANDLER_INVOKE(random_adaptor_attach,
222 			    &random_nehemiah);
223 			return (0);
224 		} else {
225 #ifndef KLD_MODULE
226 			if (bootverbose)
227 #endif
228 				printf(
229 			    "%s: VIA RNG feature is not present on this CPU\n",
230 				    random_nehemiah.ident);
231 			return (0);
232 		}
233 	}
234 
235 	return (EINVAL);
236 }
237 
238 RANDOM_ADAPTOR_MODULE(nehemiah, nehemiah_modevent, 1);
239