1/* 2 * Copyright 2013, Michael Ellerman, IBM Corporation. 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 7 * 2 of the License, or (at your option) any later version. 8 */ 9 10#define pr_fmt(fmt) "powernv-rng: " fmt 11 12#include <linux/kernel.h> 13#include <linux/of.h> 14#include <linux/of_address.h> 15#include <linux/of_platform.h> 16#include <linux/slab.h> 17#include <linux/smp.h> 18#include <asm/archrandom.h> 19#include <asm/io.h> 20#include <asm/prom.h> 21#include <asm/machdep.h> 22#include <asm/smp.h> 23 24 25struct powernv_rng { 26 void __iomem *regs; 27 void __iomem *regs_real; 28 unsigned long mask; 29}; 30 31static DEFINE_PER_CPU(struct powernv_rng *, powernv_rng); 32 33 34int powernv_hwrng_present(void) 35{ 36 struct powernv_rng *rng; 37 38 rng = get_cpu_var(powernv_rng); 39 put_cpu_var(rng); 40 return rng != NULL; 41} 42 43static unsigned long rng_whiten(struct powernv_rng *rng, unsigned long val) 44{ 45 unsigned long parity; 46 47 /* Calculate the parity of the value */ 48 asm ("popcntd %0,%1" : "=r" (parity) : "r" (val)); 49 50 /* xor our value with the previous mask */ 51 val ^= rng->mask; 52 53 /* update the mask based on the parity of this value */ 54 rng->mask = (rng->mask << 1) | (parity & 1); 55 56 return val; 57} 58 59int powernv_get_random_real_mode(unsigned long *v) 60{ 61 struct powernv_rng *rng; 62 63 rng = raw_cpu_read(powernv_rng); 64 65 *v = rng_whiten(rng, in_rm64(rng->regs_real)); 66 67 return 1; 68} 69 70int powernv_get_random_long(unsigned long *v) 71{ 72 struct powernv_rng *rng; 73 74 rng = get_cpu_var(powernv_rng); 75 76 *v = rng_whiten(rng, in_be64(rng->regs)); 77 78 put_cpu_var(rng); 79 80 return 1; 81} 82EXPORT_SYMBOL_GPL(powernv_get_random_long); 83 84static __init void rng_init_per_cpu(struct powernv_rng *rng, 85 struct device_node *dn) 86{ 87 int chip_id, cpu; 88 89 chip_id = of_get_ibm_chip_id(dn); 90 if (chip_id == -1) 91 pr_warn("No ibm,chip-id found for %s.\n", dn->full_name); 92 93 for_each_possible_cpu(cpu) { 94 if (per_cpu(powernv_rng, cpu) == NULL || 95 cpu_to_chip_id(cpu) == chip_id) { 96 per_cpu(powernv_rng, cpu) = rng; 97 } 98 } 99} 100 101static __init int rng_create(struct device_node *dn) 102{ 103 struct powernv_rng *rng; 104 struct resource res; 105 unsigned long val; 106 107 rng = kzalloc(sizeof(*rng), GFP_KERNEL); 108 if (!rng) 109 return -ENOMEM; 110 111 if (of_address_to_resource(dn, 0, &res)) { 112 kfree(rng); 113 return -ENXIO; 114 } 115 116 rng->regs_real = (void __iomem *)res.start; 117 118 rng->regs = of_iomap(dn, 0); 119 if (!rng->regs) { 120 kfree(rng); 121 return -ENXIO; 122 } 123 124 val = in_be64(rng->regs); 125 rng->mask = val; 126 127 rng_init_per_cpu(rng, dn); 128 129 pr_info_once("Registering arch random hook.\n"); 130 131 ppc_md.get_random_seed = powernv_get_random_long; 132 133 return 0; 134} 135 136static __init int rng_init(void) 137{ 138 struct device_node *dn; 139 int rc; 140 141 for_each_compatible_node(dn, NULL, "ibm,power-rng") { 142 rc = rng_create(dn); 143 if (rc) { 144 pr_err("Failed creating rng for %s (%d).\n", 145 dn->full_name, rc); 146 continue; 147 } 148 149 /* Create devices for hwrng driver */ 150 of_platform_device_create(dn, NULL, NULL); 151 } 152 153 return 0; 154} 155machine_subsys_initcall(powernv, rng_init); 156