Ejemplo n.º 1
0
/*!
 * Undo everything done by rng_init() and place driver in fail mode.
 *
 * Deregister from SCC, stop tasklet, shutdown the RNG.  Leave the register
 * map in place in case other drivers call rng_read/write_register()
 *
 * @return void
 */
static void rng_cleanup(void)
{
	struct clk *clk;

#ifdef FSL_HAVE_RNGA
	scc_stop_monitoring_security_failure(rng_sec_failure);
#endif

	clk = clk_get(NULL, "rng_clk");
	clk_disable(clk);
	if (task_started) {
		os_dev_stop_task(rng_entropy_task);
	}

	if (rng_base != NULL) {
		/* mask off RNG interrupts */
		RNG_MASK_ALL_INTERRUPTS();
		RNG_SLEEP();

		if (rng_irq_set) {
			/* unmap the interrupts from the IRQ lines */
			os_deregister_interrupt(INT_RNG);
			rng_irq_set = FALSE;
		}
		LOG_KDIAG("Leaving rng driver status as failed");
		rng_availability = RNG_STATUS_FAILED;
	} else {
		LOG_KDIAG("Leaving rng driver status as unimplemented");
		rng_availability = RNG_STATUS_UNIMPLEMENTED;
	}
	LOG_KDIAG("Cleaned up");
}				/* rng_cleanup */
/******************************************************************************
*
* CAUTION: NONE
*
* MODIFICATION HISTORY:
*
* Date         Person     Change
* 30/07/2003   MW         Initial Creation
******************************************************************************/
int sah_Intr_Init(wait_queue_head_t * wait_queue)
{

#ifdef DIAG_DRV_INTERRUPT
	char err_string[200];
#endif

	int result;

#ifdef KERNEL_TEST
	SAHARA_INT_PTR = sah_Intr_Top_Half;
#endif

	/* Set queue used by the interrupt handler to match the driver interface */
	int_queue = wait_queue;

	/* Request use of the Interrupt line. */
	result = request_irq(SAHARA_IRQ,
			     sah_Intr_Top_Half, 0, SAHARA_NAME, NULL);

#ifdef DIAG_DRV_INTERRUPT
	if (result != 0) {
		sprintf(err_string, "Cannot use SAHARA interrupt line %d. "
			"request_irq() return code is %i.", SAHARA_IRQ, result);
		LOG_KDIAG(err_string);
	} else {
		sprintf(err_string,
			"SAHARA driver registered for interrupt %d. ",
			SAHARA_IRQ);
		LOG_KDIAG(err_string);
	}
#endif

	return result;
}
Ejemplo n.º 3
0
/******************************************************************************
*
* CAUTION:  This does not kfree() the entry. Does not check to see if the entry
*           actually belongs to the queue.
******************************************************************************/
void sah_Queue_Remove_Any_Entry(sah_Queue * q, sah_Head_Desc * entry)
{
	sah_Head_Desc *prev_entry = NULL;
	sah_Head_Desc *next_entry = NULL;

	if ((q == NULL) || (entry == NULL)) {
#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT
		LOG_KDIAG("Null pointer input.");
#endif
		return;
	}

	if (q->count == 1) {
		/* If q is the only entry in the queue. */
		q->tail = NULL;
		q->head = NULL;
		q->count = 0;
	} else if (q->count > 1) {
		/* There are 2 or more entries in the queue. */

#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT
		if ((entry->next == NULL) && (entry->prev == NULL)) {
			LOG_KDIAG
			    ("Queue is not empty yet both next and prev pointers"
			     " are NULL");
		}
#endif

		if (entry->next == NULL) {
			/* If this is the end of the queue */
			prev_entry = entry->prev;
			prev_entry->next = NULL;
			q->tail = prev_entry;
		} else if (entry->prev == NULL) {
			/* If this is the head of the queue */
			next_entry = entry->next;
			next_entry->prev = NULL;
			q->head = next_entry;
		} else {
			/* If this is somewhere in the middle of the queue */
			prev_entry = entry->prev;
			next_entry = entry->next;
			prev_entry->next = next_entry;
			next_entry->prev = prev_entry;
		}
		q->count--;
	}
	/*
	 * Otherwise we are removing an entry from an empty queue.
	 * Don't do anything in the product code
	 */
#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT
	else {
		LOG_KDIAG("Trying to remove an entry from an empty queue.");
	}
#endif

	entry->next = NULL;
	entry->prev = NULL;
}
Ejemplo n.º 4
0
/******************************************************************************
*
* CAUTION: NONE
******************************************************************************/
void sah_Queue_Append_Entry (sah_Queue *q, sah_Head_Desc *entry)
{
    sah_Head_Desc *tail_entry = NULL;

    if ((q == NULL) || (entry == NULL)) {
#ifdef DIAG_DRV_QUEUE
        LOG_KDIAG ("Null pointer input.");
#endif
        return;
    }

    if (q->count == 0) {
        /* The queue is empty */
        q->head = entry;
        q->tail = entry;
        entry->next = NULL;
        entry->prev = NULL;
    }
    else {
        /* The queue is not empty */
        tail_entry = q->tail;
        tail_entry->next = entry;
        entry->next = NULL;
        entry->prev = tail_entry;
        q->tail = entry;
    }
    q->count++;
}
Ejemplo n.º 5
0
/*!
 * Dump chain of links to the log.
 *
 * @brief Dump chain of links
 *
 * @param    prefix    Text to put in front of dumped data
 * @param    link      Kernel virtual address of start of chain of links
 *
 * @return   void
 */
static void km_Dump_Link(const char *prefix, const sah_Link * link)
{
	while (link != NULL) {
		km_Dump_Words(prefix, (unsigned *)link,
			      3 /* # words in h/w link */ );
		if (link->flags & SAH_STORED_KEY_INFO) {
#ifdef CAN_DUMP_SCC_DATA
			uint32_t len;
#endif

#ifdef CAN_DUMP_SCC_DATA
			{
				char buf[50];

				scc_get_slot_info(link->ownerid, link->slot, (uint32_t *) & link->data,	/* RED key address */
						  &len);	/* key length */
				sprintf(buf, "  SCC slot %d: ", link->slot);
				km_Dump_Words(buf,
					      (void *)IO_ADDRESS((uint32_t)
								 link->data),
					      link->len / 4);
			}
#else
			sprintf(Diag_msg, "  SCC slot %d", link->slot);
			LOG_KDIAG(Diag_msg);
#endif
		} else if (link->data != NULL) {
			km_Dump_Region("  Data", link->data, link->len);
		}

		link = link->next;
	}
}
Ejemplo n.º 6
0
/*!
 * Dump given region of data to the log.
 *
 * @brief Dump data
 *
 * @param    prefix    Text to put in front of dumped data
 * @param    data      Kernel virtual address of start of region to dump
 * @param    length    Amount of data to dump
 *
 * @return   void
*/
void km_Dump_Region(const char *prefix, const unsigned char *data,
		    unsigned length)
{
	unsigned count;
	char *output;
	unsigned data_len;

	sprintf(Diag_msg, "%s (%08X,%u):", prefix, (uint32_t) data, length);

	/* Restrict amount of data to dump */
	if (length > MAX_DUMP) {
		data_len = MAX_DUMP;
	} else {
		data_len = length;
	}

	/* We've already printed some text in output buffer, skip over it */
	output = Diag_msg + strlen(Diag_msg);

	for (count = 0; count < data_len; count++) {
		if (count % 4 == 0) {
			*output++ = ' ';
		}
		sprintf(output, "%02X", *data++);
		output += 2;
	}

	LOG_KDIAG(Diag_msg);
}
Ejemplo n.º 7
0
static void *my_malloc(void *ref, size_t n)
{
	register void *mem;

#ifndef DIAG_MEM_ERRORS
	mem = os_alloc_memory(n, GFP_KERNEL);

#else
	{
		uint32_t rand;
		/* are we feeling lucky ? */
		os_get_random_bytes(&rand, sizeof(rand));
		if ((rand % DIAG_MEM_CONST) == 0) {
			mem = 0;
		} else {
			mem = os_alloc_memory(n, GFP_ATOMIC);
		}
	}
#endif				/* DIAG_MEM_ERRORS */

#ifdef DIAG_MEM
	sprintf(Diag_msg, "API kmalloc: %p for %d\n", mem, n);
	LOG_KDIAG(Diag_msg);
#endif
	ref = 0;		/* unused param warning */
	return mem;
}
Ejemplo n.º 8
0
static void my_free(void* ref, void* ptr) {
    ref = 0;                    /* unused param warning */
#ifdef DIAG_MEM
    sprintf(Diag_msg, "API kfree: %p\n", ptr);
    LOG_KDIAG(Diag_msg);
#endif
    os_free_memory(ptr);
}
Ejemplo n.º 9
0
/*!
 * This function writes the Descriptor Chain to the kernel driver.
 *
 * @brief    Writes the Descriptor Chain to the kernel driver.
 *
 * @param    dar   A pointer to a Descriptor Chain of type sah_Head_Desc
 * @param    uco   The user context object
 *
 * @return    A return code of type #fsl_shw_return_t.
 */
fsl_shw_return_t adaptor_Exec_Descriptor_Chain(
        sah_Head_Desc *dar,
        fsl_shw_uco_t *uco)
{
    sah_Head_Desc *kernel_space_desc = NULL;
    fsl_shw_return_t code = FSL_RETURN_OK_S;
    int os_error_code = 0;
    unsigned blocking_mode = dar->uco_flags & FSL_UCO_BLOCKING_MODE;

#ifdef DIAG_ADAPTOR
    km_Dump_Chain(&dar->desc);
#endif

    dar->user_info = uco;
    dar->user_desc = dar;

    /* This code has been shamelessly copied from sah_driver_interface.c */
    /* It needs to be moved somewhere common ... */
    kernel_space_desc = sah_Physicalise_Descriptors(dar);

    if ( kernel_space_desc == NULL ) {
        /* We may have failed due to a -EFAULT as well, but we will return
         * -ENOMEM since either way it is a memory related failure. */
        code = FSL_RETURN_NO_RESOURCE_S;
#ifdef DIAG_DRV_IF
        LOG_KDIAG("sah_Physicalise_Descriptors() failed\n");
#endif
    } else {
        if (blocking_mode) {
#ifdef SAHARA_POLL_MODE
            os_error_code = sah_Handle_Poll(dar);
#else
            os_error_code = sah_blocking_mode(dar);
#endif
            if (os_error_code != 0) {
                code = FSL_RETURN_ERROR_S;
            }
            else {              /* status of actual operation */
                code = dar->result;
            }

            /* free this chain */
            if (!(uco->flags & FSL_UCO_SAVE_DESC_CHAIN)) {
                sah_Descriptor_Chain_Destroy(uco->mem_util, &dar);
            }
        } else {
#ifdef SAHARA_POLL_MODE
            sah_Handle_Poll(dar);
#else
            /* just put someting in the DAR */
            sah_Queue_Manager_Append_Entry(dar);
#endif /* SAHARA_POLL_MODE */
       }
    }

    return code;
}
Ejemplo n.º 10
0
/*!
*******************************************************************************
* This function is the Bottom Half of the interrupt handler.  It calls
* #sah_postprocess_queue() to complete the processing of the Descriptor Chains
* which were finished by the hardware.
*
* @brief     SAHARA Interrupt Handler Bottom Half
*
* @param    data   Part of the kernel prototype.
*
* @return   void
*/
static void sah_Intr_Bottom_Half(unsigned long reset_flag)
{
#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT)
	LOG_KDIAG("Bottom half of Sahara's interrupt handler called.");
#endif
	sah_postprocess_queue(*(unsigned long *)reset_flag);

	return;
}
Ejemplo n.º 11
0
/******************************************************************************
*
* CAUTION:  This function does not free any queue entries.
*
******************************************************************************/
void sah_Queue_Destroy (sah_Queue *q)
{
#ifdef DIAG_DRV_QUEUE
    if (q == NULL) {
        LOG_KDIAG ("Trying to kfree() a NULL pointer.");
    }
    else {
        if (q->count != 0) {
            LOG_KDIAG ("Trying to destroy a queue that is not empty.");
        }
    }
#endif

    if (q != NULL) {
        os_free_memory(q);
        q = NULL;
    }
}
Ejemplo n.º 12
0
/*!
*******************************************************************************
* This function is the Top Half of the interrupt handler.  It updates the
* status of any finished descriptor chains and then tries to add any pending
* requests into the hardware.  It then queues the bottom half to complete
* operations on the finished chains.
*
* @brief     SAHARA Interrupt Handler Top Half
*
* @param    irq     Part of the kernel prototype.
* @param    dev_id  Part of the kernel prototype.
*
* @return   An IRQ_RETVAL() -- non-zero to that function means 'handled'
*/
static irqreturn_t sah_Intr_Top_Half(int irq, void *dev_id)
{
#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT)
	LOG_KDIAG("Top half of Sahara's interrupt handler called.");
#endif

	interrupt_count++;
	reset_flag = sah_Handle_Interrupt(sah_HW_Read_Status());

	/* Schedule the Bottom Half of the Interrupt. */
	tasklet_schedule(&BH_task);

	/* To get rid of the unused parameter warnings. */
	irq = 0;
	dev_id = NULL;
	return IRQ_RETVAL(1);
}
Ejemplo n.º 13
0
/*!
 * Dump given wors of data to the log.
 *
 * @brief Dump data
 *
 * @param    prefix       Text to put in front of dumped data
 * @param    data         Kernel virtual address of start of region to dump
 * @param    word_count   Amount of data to dump
 *
 * @return   void
*/
void km_Dump_Words(const char *prefix, const unsigned *data,
		   unsigned word_count)
{
	char *output;

	sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, (uint32_t) data,
		word_count);

	/* We've already printed some text in output buffer, skip over it */
	output = Diag_msg + strlen(Diag_msg);

	while (word_count--) {
		sprintf(output, "%08X ", *data++);
		output += 9;
	}

	LOG_KDIAG(Diag_msg);
}
Ejemplo n.º 14
0
/*!
 * Register #rng_irq() as the interrupt handler for #INT_RNG.
 *
 * @return OS_ERROR_OK_S on success, os_error_code on failure
 */
static os_error_code rng_setup_interrupt_handling(void)
{
	os_error_code error_code;

	/*
	 * Install interrupt service routine for the RNG. Ignore the
	 * assigned IRQ number.
	 */
	error_code = os_register_interrupt(RNG_DRIVER_NAME, INT_RNG,
					   OS_DEV_ISR_REF(rng_irq));
	if (error_code != OS_ERROR_OK_S) {
		LOG_KDIAG("RNG Driver: Error installing Interrupt Handler");
	} else {
		rng_irq_set = TRUE;
		RNG_UNMASK_ALL_INTERRUPTS();
	}

	return error_code;
}				/* rng_setup_interrupt_handling */
Ejemplo n.º 15
0
/******************************************************************************
*
* CAUTION:  This function may sleep in low-memory situations, as it uses
*           kmalloc ( ..., GFP_KERNEL).
******************************************************************************/
sah_Queue *sah_Queue_Construct (void)
{
    sah_Queue *q = (sah_Queue *)os_alloc_memory (sizeof(sah_Queue),
                                                    GFP_KERNEL);

    if (q != NULL) {
        /* Initialise the queue to an empty state. */
        q->head = NULL;
        q->tail = NULL;
        q->count = 0;
    }
#ifdef DIAG_DRV_QUEUE
    else {
        LOG_KDIAG ("kmalloc() failed.");
    }
#endif

    return q;
}
Ejemplo n.º 16
0
/*!
 * Make sure that register access is legal.
 *
 * Verify that, if in secure mode, only safe registers are used.
 * For any register access, make sure that read-only registers are not written
 * and that write-only registers are not read.  This check also disallows any
 * access to the RNG's Output FIFO, to prevent other drivers from draining the
 * FIFO and causing an underflow condition.
 *
 * This routine is only for checking accesses by other than this driver.
 *
 * @param  offset   The (byte) offset within the RNG block
 *                      of the register to be accessed.  See
 *                      @ref rngregs for meanings.
 * @param access_write  0 for read, anything else for write
 *
 * @return 0 if invalid, 1 if OK.
 */
static int rng_check_register_accessible(uint32_t offset, int access_write)
{
	int return_code = FALSE;	/* invalid */
	uint32_t secure = RNG_GET_HIGH_ASSURANCE();

	/* First check for RNG in Secure Mode -- most registers inaccessible.
	 * Also disallowing access to RNG_OUTPUT_FIFO except by the driver.
	 */
	if (!
#ifdef FSL_HAVE_RNGA
	    (secure &&
	     ((offset == RNGA_OUTPUT_FIFO) ||
	      (offset == RNGA_MODE) ||
	      (offset == RNGA_VERIFICATION_CONTROL) ||
	      (offset == RNGA_OSCILLATOR_CONTROL_COUNTER) ||
	      (offset == RNGA_OSCILLATOR1_COUNTER) ||
	      (offset == RNGA_OSCILLATOR2_COUNTER) ||
	      (offset == RNGA_OSCILLATOR_COUNTER_STATUS)))
#else				/* RNGB or RNGC */
	    (secure &&
	     ((offset == RNGC_FIFO) ||
	      (offset == RNGC_VERIFICATION_CONTROL) ||
	      (offset == RNGC_OSC_COUNTER_CONTROL) ||
	      (offset == RNGC_OSC_COUNTER) ||
	      (offset == RNGC_OSC_COUNTER_STATUS)))
#endif
	    ) {

		/*  Passed that test.  Either not in high assurance, and/or are
		   checking register that is always available.  Now check
		   R/W permissions. */
		if (access_write == RNG_CHECK_READ) {	/* read request */
			/* Only the entropy register is write-only */
#ifdef FSL_HAVE_RNGC
			/* No registers are write-only */
			return_code = TRUE;
#else				/* else RNGA or RNGB */
#ifdef FSL_HAVE_RNGA
			if (1) {
#else
			if (!(offset == RNGB_ENTROPY)) {
#endif
				return_code = TRUE;	/* Let all others be read */
			} else {
				pr_debug
				    ("RNG: Offset %04x denied read access\n",
				     offset);
			}
#endif				/* RNGA or RNGB */
		} /* read */
		else {		/* access_write means write */
			/* Check against list of non-writable registers */
			if (!
#ifdef FSL_HAVE_RNGA
			    ((offset == RNGA_STATUS) ||
			     (offset == RNGA_OUTPUT_FIFO) ||
			     (offset == RNGA_OSCILLATOR1_COUNTER) ||
			     (offset == RNGA_OSCILLATOR2_COUNTER) ||
			     (offset == RNGA_OSCILLATOR_COUNTER_STATUS))
#else				/* FSL_HAVE_RNGB or FSL_HAVE_RNGC */
			    ((offset == RNGC_STATUS) ||
			     (offset == RNGC_FIFO) ||
			     (offset == RNGC_OSC_COUNTER) ||
			     (offset == RNGC_OSC_COUNTER_STATUS))
#endif
			    ) {
				return_code = TRUE;	/* can be written */
			} else {
				LOG_KDIAG_ARGS
				    ("Offset %04x denied write access", offset);
			}
		}		/* write */
	} /* not high assurance and inaccessible register... */
	else {
		LOG_KDIAG_ARGS("Offset %04x denied high-assurance access",
			       offset);
	}

	return return_code;
}				/* rng_check_register_accessible */
#endif				/*  RNG_REGISTER_PEEK_POKE */

/*****************************************************************************/
/* fn rng_irq()                                                             */
/*****************************************************************************/
/*!
 * This is the interrupt handler for the RNG.  It is only ever invoked if the
 * RNG detects a FIFO Underflow error.
 *
 * If the error is a Security Violation, this routine will
 * set the #rng_availability to #RNG_STATUS_FAILED, as the entropy pool may
 * have been corrupted.  The RNG will also be placed into low power mode.  The
 * SCC will have noticed the problem as well.
 *
 * The other possibility, if the RNG is not in High Assurance mode, would be
 * simply a FIFO Underflow.  No special action, other than to
 * clear the interrupt, is taken.
 */
OS_DEV_ISR(rng_irq)
{
	int handled = FALSE;	/* assume interrupt isn't from RNG */

	LOG_KDIAG("rng irq!");

	if (RNG_SEED_DONE()) {
		complete(&rng_seed_done);
		RNG_CLEAR_ALL_STATUS();
		handled = TRUE;
	}

	if (RNG_SELF_TEST_DONE()) {
		complete(&rng_self_testing);
		RNG_CLEAR_ALL_STATUS();
		handled = TRUE;
	}
	/* Look to see whether RNG needs attention */
	if (RNG_HAS_ERROR()) {
		if (RNG_GET_HIGH_ASSURANCE()) {
			RNG_SLEEP();
			rng_availability = RNG_STATUS_FAILED;
			RNG_MASK_ALL_INTERRUPTS();
		}
		handled = TRUE;
		/* Clear the interrupt */
		RNG_CLEAR_ALL_STATUS();

	}
	os_dev_isr_return(handled);
}				/* rng_irq */

/*****************************************************************************/
/* fn map_RNG_memory()                                                      */
/*****************************************************************************/
/*!
 * Place the RNG's memory into kernel virtual space.
 *
 * @return OS_ERROR_OK_S on success, os_error_code on failure
 */
static os_error_code rng_map_RNG_memory(void)
{
	os_error_code error_code = OS_ERROR_FAIL_S;

	rng_base = os_map_device(RNG_BASE_ADDR, RNG_ADDRESS_RANGE);
	if (rng_base == NULL) {
		/* failure ! */
		LOG_KDIAG("RNG Driver: ioremap failed.");
	} else {
		error_code = OS_ERROR_OK_S;
	}

	return error_code;
}				/* rng_map_RNG_memory */
Ejemplo n.º 17
0
/*!
 * This function copies words from the RNG FIFO into the caller's buffer.
 *
 *
 * @param random_p    Location to copy random data
 * @param count_words Number of words to copy
 *
 * @return An error code.
 */
static fsl_shw_return_t rng_drain_fifo(uint32_t * random_p, int count_words)
{

	int words_in_rng;	/* Number of words available now in RNG */
	fsl_shw_return_t code = FSL_RETURN_ERROR_S;
	int sequential_count = 0;	/* times through big while w/empty FIFO */
	int fifo_empty_count = 0;	/* number of times FIFO was empty */
	int max_sequential = 0;	/* max times 0 seen in a row */
#if !defined(FSL_HAVE_RNGA)
	int count_for_reseed = 0;
	INIT_COMPLETION(rng_seed_done);
#endif
#if !defined(FSL_HAVE_RNGA)
	if (RNG_RESEED()) {
		do {
			LOG_KDIAG("Reseeding RNG");

			RNG_CLEAR_ERR();
			RNG_SEED_GEN();
			wait_for_completion(&rng_seed_done);
			if (count_for_reseed == 3) {
				os_printk(KERN_ALERT
					  "Device was not able to enter RESEED Mode\n");
				code = FSL_RETURN_INTERNAL_ERROR_S;
			}
			count_for_reseed++;
		} while (RNG_CHECK_SEED_ERR());
	}
#endif
	/* Copy all of them in.  Stop if pool fills. */
	while ((rng_availability == RNG_STATUS_OK) && (count_words > 0)) {
		/* Ask RNG how many words currently in FIFO */
		words_in_rng = RNG_GET_WORDS_IN_FIFO();
		if (words_in_rng == 0) {
			++sequential_count;
			fifo_empty_count++;
			if (sequential_count > max_sequential) {
				max_sequential = sequential_count;
			}
			if (sequential_count >= RNG_MAX_TRIES) {
				LOG_KDIAG_ARGS("FIFO staying empty (%d)",
					       words_in_rng);
				code = FSL_RETURN_NO_RESOURCE_S;
				break;
			}
		} else {
			/* Found at least one word */
			sequential_count = 0;
			/* Now adjust: words_in_rng = MAX(count_words, words_in_rng) */
			words_in_rng = (count_words < words_in_rng)
			    ? count_words : words_in_rng;
		}		/* else found words */

#ifdef RNG_FORCE_FIFO_UNDERFLOW
		/*
		 * For unit test, force occasional extraction of more words than
		 * available.  This should cause FIFO Underflow, and IRQ invocation.
		 */
		words_in_rng = count_words;
#endif

		/* Copy out all available & neeeded data */
		while (words_in_rng-- > 0) {
			*random_p++ = RNG_READ_FIFO();
			count_words--;
		}
	}			/* while words still needed */

	if (count_words == 0) {
		code = FSL_RETURN_OK_S;
	}
	if (fifo_empty_count != 0) {
		LOG_KDIAG_ARGS("FIFO empty %d times, max loop count %d",
			       fifo_empty_count, max_sequential);
	}

	return code;
}				/* rng_drain_fifo */