Merge branch 'master' into LIBGCRYPT-1-7-BRANCH
[libgcrypt.git] / random / rndhw.c
index 775d90f..8e50751 100644 (file)
@@ -1,5 +1,6 @@
 /* rndhw.c  - Access to the external random daemon
  * Copyright (C) 2007  Free Software Foundation, Inc.
+ * Copyright (C) 2012  Dmitry Kasatkin
  *
  * This file is part of Libgcrypt.
  *
 # endif
 #endif /*ENABLE_PADLOCK_SUPPORT*/
 
+#undef USE_DRNG
+#ifdef ENABLE_DRNG_SUPPORT
+# ifdef HAVE_GCC_ATTRIBUTE_ALIGNED
+#  if (defined (__i386__) && SIZEOF_UNSIGNED_LONG == 4) || defined(__x86_64__)
+#   define USE_DRNG 1
+#  endif
+# endif
+#endif /*ENABLE_RDRAND_SUPPORT*/
+
+typedef void (*add_fn_t)(const void*, size_t, enum random_origins);
 
 /* Keep track on whether the RNG has problems.  */
 static volatile int rng_failed;
@@ -58,7 +69,7 @@ poll_padlock (void (*add)(const void*, size_t, enum random_origins),
   nbytes = 0;
   while (nbytes < 64)
     {
-#ifdef __x86_64__
+#if defined(__x86_64__) && SIZEOF_VOID_P == 8
       asm volatile
         ("movq %1, %%rdi\n\t"         /* Set buffer.  */
          "xorq %%rdx, %%rdx\n\t"      /* Request up to 8 bytes.  */
@@ -109,6 +120,56 @@ poll_padlock (void (*add)(const void*, size_t, enum random_origins),
 #endif /*USE_PADLOCK*/
 
 
+#ifdef USE_DRNG
+# define RDRAND_RETRY_LOOPS    10
+# define RDRAND_INT    ".byte 0x0f,0xc7,0xf0"
+# if defined(__x86_64__) && SIZEOF_UNSIGNED_LONG == 8
+#  define RDRAND_LONG  ".byte 0x48,0x0f,0xc7,0xf0"
+# else
+#  define RDRAND_LONG  RDRAND_INT
+# endif
+static inline int
+rdrand_long (unsigned long *v)
+{
+  int ok;
+  asm volatile ("1: " RDRAND_LONG "\n\t"
+                "jc 2f\n\t"
+                "decl %0\n\t"
+                "jnz 1b\n\t"
+                "2:"
+                : "=r" (ok), "=a" (*v)
+                : "0" (RDRAND_RETRY_LOOPS)
+                : "cc");
+  return ok;
+}
+
+
+static inline int
+rdrand_nlong (unsigned long *v, int count)
+{
+  while (count--)
+    if (!rdrand_long(v++))
+      return 0;
+  return 1;
+}
+
+
+static size_t
+poll_drng (add_fn_t add, enum random_origins origin, int fast)
+{
+  volatile char buffer[64] __attribute__ ((aligned (8)));
+  unsigned int nbytes = sizeof (buffer);
+
+  (void)fast;
+
+  if (!rdrand_nlong ((unsigned long *)buffer, sizeof(buffer)/sizeof(long)))
+    return 0;
+  (*add)((void *)buffer, nbytes, origin);
+  return nbytes;
+}
+#endif /*USE_DRNG*/
+
+
 int
 _gcry_rndhw_failed_p (void)
 {
@@ -125,6 +186,10 @@ _gcry_rndhw_poll_fast (void (*add)(const void*, size_t, enum random_origins),
   (void)add;
   (void)origin;
 
+#ifdef USE_DRNG
+  if ((_gcry_get_hw_features () & HWF_INTEL_RDRAND))
+    poll_drng (add, origin, 1);
+#endif
 #ifdef USE_PADLOCK
   if ((_gcry_get_hw_features () & HWF_PADLOCK_RNG))
     poll_padlock (add, origin, 1);
@@ -143,6 +208,10 @@ _gcry_rndhw_poll_slow (void (*add)(const void*, size_t, enum random_origins),
   (void)add;
   (void)origin;
 
+#ifdef USE_DRNG
+  if ((_gcry_get_hw_features () & HWF_INTEL_RDRAND))
+    nbytes += poll_drng (add, origin, 0);
+#endif
 #ifdef USE_PADLOCK
   if ((_gcry_get_hw_features () & HWF_PADLOCK_RNG))
     nbytes += poll_padlock (add, origin, 0);