/******************************************************************************* * * Copyright (c) 2008 Loc Ho * * 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; either version 2 of the License, or * (at your option) any later version. * * 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. * * * Detail Description: * This file defines ioctl structures for the Linux CryptoAPI interface. It * provides user space applications accesss into the Linux CryptoAPI * functionalities. * * @file trng4xx.c * * This file provides access to the AMCC PPC4XX TRNG for Linux. * ******************************************************************************* */ #include #include #include #include #include #include #include #include #include "trng4xx.h" #define TRNG4XX_VER_STR "0.1" #define PFX KBUILD_MODNAME ": " struct trng4xx_dev { struct resource res; u32 irq; volatile char __iomem *csr; struct semaphore access_prot; u32 datum; }; struct hal_config { struct of_device *ofdev; }; static struct trng4xx_dev trng4xx_dev; static int trng4xx_irq_handler(int irq, void * id); static void trng4xx_chk_overflow(void); int trng4xx_config_set(struct hal_config *cfg) { struct device_node *rng_np = cfg->ofdev->node; int rc = 0; rc = of_address_to_resource(rng_np, 0, &trng4xx_dev.res); if (rc) return -ENODEV; trng4xx_dev.csr = ioremap(trng4xx_dev.res.start, trng4xx_dev.res.end - trng4xx_dev.res.start + 1); if (trng4xx_dev.csr == NULL) { printk(KERN_ERR PFX "unable to ioremap 0x%02X_%08X size %d\n", (u32) (trng4xx_dev.res.start >> 32), (u32) trng4xx_dev.res.start, (u32) (trng4xx_dev.res.end - trng4xx_dev.res.start + 1)); return -ENOMEM; } #if 0 printk("TRNG1 0x%02X_%08X size %d\n", (u32) (trng4xx_dev.res.start >> 32), (u32) trng4xx_dev.res.start, (u32) (trng4xx_dev.res.end - trng4xx_dev.res.start + 1)); #endif trng4xx_dev.irq = of_irq_to_resource(rng_np, 0, NULL); printk ("TRNG IRQ = %d\n",trng4xx_dev.irq); if (trng4xx_dev.irq == NO_IRQ) { /* Un-map CSR */ iounmap(trng4xx_dev.csr); trng4xx_dev.csr = NULL; return -EINVAL; } return 0; } int trng4xx_pka_config_clear(void) { iounmap(trng4xx_dev.csr); return 0; } inline int trng4xx_hw_read32(u32 reg_addr, u32 *data_val) { *data_val = in_be32((volatile unsigned __iomem *) (trng4xx_dev.csr + reg_addr)); return 0; } inline int trng4xx_hw_write32(u32 reg_addr, u32 data_val) { out_be32((volatile unsigned __iomem *) (trng4xx_dev.csr + reg_addr), data_val); return 0; } int trng4xx_hw_init(void) { int rc; rc = request_irq(trng4xx_dev.irq, trng4xx_irq_handler, 0, "TRNG", NULL); if (rc != 0) { printk(KERN_ERR PFX "failed to register interrupt IRQ %d\n", trng4xx_dev.irq); return rc; } return 0; } int trng4xx_hw_deinit(void) { free_irq(trng4xx_dev.irq, NULL); return 0; } static int trng4xx_irq_handler(int irq, void * id) { /* TRNG Alarm Counter overflow */ trng4xx_chk_overflow(); return 0; } /******************************************************************************* * TRNG Functions ******************************************************************************* */ static void trng4xx_chk_overflow(void) { /* TRNG Alarm Counter overflow */ int rc; u32 val; struct trng4xx_cfg { u32 ring1_delay_sel : 3; u32 ring2_delay_sel : 3; u32 reset_cnt : 6; } __attribute__((packed)); rc = trng4xx_hw_write32(TRNG4XX_ALARMCNT_ADDR, val); if (rc != 0) return; if (val > 128) { struct trng4xx_cfg *trng4xx_cfg; /* Alarm count is half, reset it */ rc = trng4xx_hw_read32(TRNG4XX_CFG_ADDR, &val); if (rc != 0) return; trng4xx_cfg = (struct trng4xx_cfg *) &val; ++trng4xx_cfg->ring1_delay_sel; trng4xx_cfg->ring2_delay_sel = (~trng4xx_cfg->ring1_delay_sel) & 0x07; rc = trng4xx_hw_write32(TRNG4XX_CFG_ADDR, val); if (rc != 0) return; trng4xx_hw_write32(TRNG4XX_ALARMCNT_ADDR, 0x00000000); if (rc != 0) return; } } int trng4xx_random(u32 *rand_val) { u32 val = 0; int rc; #define MAX_TRY 3 u16 try_cnt = 0; down(&trng4xx_dev.access_prot); do { rc = trng4xx_hw_read32(TRNG4XX_STATUS_ADDR, &val); if (rc != 0) goto err; } while((val & TRNG4XX_STATUS_BUSY) && ++try_cnt <= MAX_TRY); if (val & TRNG4XX_STATUS_BUSY) { rc = -EINPROGRESS; goto err; } rc = trng4xx_hw_read32(TRNG4XX_OUTPUT_ADDR, rand_val); err: //printk("status busy\n"); up(&trng4xx_dev.access_prot); return rc; } EXPORT_SYMBOL_GPL(trng4xx_random); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) static int trng4xx_data_present(struct hwrng *rng) #else static int trng4xx_data_present(struct hwrng *rng, int wait) #endif { struct trng4xx_dev *dev = (struct trng4xx_dev *) rng->priv; int i; u32 val; printk("inside %s:\n", __FUNCTION__); down(&trng4xx_dev.access_prot); /* We should not take more than 200??? us */ for (i = 0; i < 20; i++) { trng4xx_hw_read32(TRNG4XX_STATUS_ADDR, &val); if (!(val & TRNG4XX_STATUS_BUSY)) { trng4xx_hw_read32(TRNG4XX_OUTPUT_ADDR, &dev->datum); break; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) break; #else if (!wait) break; udelay(10); #endif } up(&trng4xx_dev.access_prot); return (val & TRNG4XX_STATUS_BUSY) ? 0 : 1; } static int trng4xx_data_read(struct hwrng *rng, u32 *data) { struct trng4xx_dev *dev = (struct trng4xx_dev *) rng->priv; *data = dev->datum; printk("data read = %u\n", *data); return 0; } static int trng4xx_init(struct hwrng *rng) { return trng4xx_hw_init(); } static void trng4xx_cleanup(struct hwrng *rng) { trng4xx_hw_deinit(); } static struct hwrng trng4xx_func = { .name = "ppc4xx-trng", .init = trng4xx_init, .cleanup = trng4xx_cleanup, .data_present = trng4xx_data_present, .data_read = trng4xx_data_read, .priv = (unsigned long) &trng4xx_dev, }; /******************************************************************************* * * Setup Driver with platform registration * ******************************************************************************* */ static int __devinit trng4xx_probe(struct of_device *ofdev, const struct of_device_id *match) { struct hal_config hw_cfg; int rc; u32 value, value2; u32 ctrl_val, control_value; hw_cfg.ofdev = ofdev; rc = trng4xx_config_set(&hw_cfg); if (rc != 0) return rc; #if 1 printk( "AMCC 4xx TRNG v%s @0x%02X_%08X size %d IRQ %d\n", TRNG4XX_VER_STR, (u32) (trng4xx_dev.res.start >> 32), (u32) trng4xx_dev.res.start, (u32) (trng4xx_dev.res.end - trng4xx_dev.res.start + 1), trng4xx_dev.irq); #endif init_MUTEX(&trng4xx_dev.access_prot); rc = hwrng_register(&trng4xx_func); if (rc) { printk(KERN_ERR PFX "AMCC 4xx TRNG registering failed error %d\n", rc); goto err; } trng4xx_hw_read32(TRNG4XX_CNTL_ADDR, &ctrl_val); //printk ("cntl value = 0x%x\n", ctrl_val); value = ctrl_val | TRNG4XX_CNTL_TST_ALARM; //printk ("value to write = 0x%x\n", value); /*rc = trng4xx_hw_write32(TRNG4XX_CNTL_ADDR, value); if (rc != 0) printk ("ERROR: writing\n"); trng4xx_hw_read32(TRNG4XX_CNTL_ADDR, &control_value); printk ("control value = 0x%x\n", control_value);*/ /*value = 0; printk ("checking random value register\n"); rc = trng4xx_random (&value); printk ("checking random value register\n"); rc = trng4xx_random (&value2);*/ return rc; err: trng4xx_pka_config_clear(); return rc; } static int __devexit trng4xx_remove(struct of_device *dev) { hwrng_unregister(&trng4xx_func); trng4xx_pka_config_clear(); return 0; } static struct of_device_id trng4xx_match[] = { { .compatible = "ppc4xx-trng", }, { .compatible = "amcc,ppc4xx-trng", }, { }, }; static struct of_platform_driver trng4xx_driver = { .name = "ppc4xx-trng", .match_table = trng4xx_match, .probe = trng4xx_probe, .remove = trng4xx_remove, }; static int __init mod_init(void) { printk("entered mod_init\n"); return of_register_platform_driver(&trng4xx_driver); } static void __exit mod_exit(void) { of_unregister_platform_driver(&trng4xx_driver); } module_init(mod_init); module_exit(mod_exit); MODULE_DESCRIPTION("AMCC 4xx True Random Number Generator"); MODULE_LICENSE("GPL");