mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 08:44:41 +00:00 
			
		
		
		
	hwrng: timeriomem - update to support more than one device
timeriomem_rng only supports a single device instance. This patch enables multiple timeriomem_rng devices to coexist as well as adds some additional error checking. Signed-off-by: Alexander Clouter <alex@digriz.org.uk> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
		
							parent
							
								
									57ae1b0532
								
							
						
					
					
						commit
						1907da78bf
					
				
					 2 changed files with 119 additions and 62 deletions
				
			
		|  | @ -25,117 +25,179 @@ | |||
| #include <linux/platform_device.h> | ||||
| #include <linux/hw_random.h> | ||||
| #include <linux/io.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/timeriomem-rng.h> | ||||
| #include <linux/jiffies.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/timer.h> | ||||
| #include <linux/completion.h> | ||||
| 
 | ||||
| static struct timeriomem_rng_data *timeriomem_rng_data; | ||||
| struct timeriomem_rng_private_data { | ||||
| 	void __iomem		*io_base; | ||||
| 	unsigned int		expires; | ||||
| 	unsigned int		period; | ||||
| 	unsigned int		present:1; | ||||
| 
 | ||||
| static void timeriomem_rng_trigger(unsigned long); | ||||
| static DEFINE_TIMER(timeriomem_rng_timer, timeriomem_rng_trigger, 0, 0); | ||||
| 	struct timer_list	timer; | ||||
| 	struct completion	completion; | ||||
| 
 | ||||
| 	struct hwrng		timeriomem_rng_ops; | ||||
| }; | ||||
| 
 | ||||
| #define to_rng_priv(rng) \ | ||||
| 		((struct timeriomem_rng_private_data *)rng->priv) | ||||
| 
 | ||||
| /*
 | ||||
|  * have data return 1, however return 0 if we have nothing | ||||
|  */ | ||||
| static int timeriomem_rng_data_present(struct hwrng *rng, int wait) | ||||
| { | ||||
| 	if (rng->priv == 0) | ||||
| 		return 1; | ||||
| 	struct timeriomem_rng_private_data *priv = to_rng_priv(rng); | ||||
| 
 | ||||
| 	if (!wait || timeriomem_rng_data->present) | ||||
| 		return timeriomem_rng_data->present; | ||||
| 	if (!wait || priv->present) | ||||
| 		return priv->present; | ||||
| 
 | ||||
| 	wait_for_completion(&timeriomem_rng_data->completion); | ||||
| 	wait_for_completion(&priv->completion); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static int timeriomem_rng_data_read(struct hwrng *rng, u32 *data) | ||||
| { | ||||
| 	struct timeriomem_rng_private_data *priv = to_rng_priv(rng); | ||||
| 	unsigned long cur; | ||||
| 	s32 delay; | ||||
| 
 | ||||
| 	*data = readl(timeriomem_rng_data->address); | ||||
| 	*data = readl(priv->io_base); | ||||
| 
 | ||||
| 	if (rng->priv != 0) { | ||||
| 		cur = jiffies; | ||||
| 	cur = jiffies; | ||||
| 
 | ||||
| 		delay = cur - timeriomem_rng_timer.expires; | ||||
| 		delay = rng->priv - (delay % rng->priv); | ||||
| 	delay = cur - priv->expires; | ||||
| 	delay = priv->period - (delay % priv->period); | ||||
| 
 | ||||
| 		timeriomem_rng_timer.expires = cur + delay; | ||||
| 		timeriomem_rng_data->present = 0; | ||||
| 	priv->expires = cur + delay; | ||||
| 	priv->present = 0; | ||||
| 
 | ||||
| 		init_completion(&timeriomem_rng_data->completion); | ||||
| 		add_timer(&timeriomem_rng_timer); | ||||
| 	} | ||||
| 	INIT_COMPLETION(priv->completion); | ||||
| 	mod_timer(&priv->timer, priv->expires); | ||||
| 
 | ||||
| 	return 4; | ||||
| } | ||||
| 
 | ||||
| static void timeriomem_rng_trigger(unsigned long dummy) | ||||
| static void timeriomem_rng_trigger(unsigned long data) | ||||
| { | ||||
| 	timeriomem_rng_data->present = 1; | ||||
| 	complete(&timeriomem_rng_data->completion); | ||||
| } | ||||
| 	struct timeriomem_rng_private_data *priv | ||||
| 			= (struct timeriomem_rng_private_data *)data; | ||||
| 
 | ||||
| static struct hwrng timeriomem_rng_ops = { | ||||
| 	.name		= "timeriomem", | ||||
| 	.data_present	= timeriomem_rng_data_present, | ||||
| 	.data_read	= timeriomem_rng_data_read, | ||||
| 	.priv		= 0, | ||||
| }; | ||||
| 	priv->present = 1; | ||||
| 	complete(&priv->completion); | ||||
| } | ||||
| 
 | ||||
| static int timeriomem_rng_probe(struct platform_device *pdev) | ||||
| { | ||||
| 	struct timeriomem_rng_data *pdata = pdev->dev.platform_data; | ||||
| 	struct timeriomem_rng_private_data *priv; | ||||
| 	struct resource *res; | ||||
| 	int ret; | ||||
| 	int err = 0; | ||||
| 	int period; | ||||
| 
 | ||||
| 	if (!pdata) { | ||||
| 		dev_err(&pdev->dev, "timeriomem_rng_data is missing\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||||
| 
 | ||||
| 	if (!res) | ||||
| 		return -ENOENT; | ||||
| 		return -ENXIO; | ||||
| 
 | ||||
| 	timeriomem_rng_data = pdev->dev.platform_data; | ||||
| 
 | ||||
| 	timeriomem_rng_data->address = ioremap(res->start, resource_size(res)); | ||||
| 	if (!timeriomem_rng_data->address) | ||||
| 		return -EIO; | ||||
| 
 | ||||
| 	if (timeriomem_rng_data->period != 0 | ||||
| 		&& usecs_to_jiffies(timeriomem_rng_data->period) > 0) { | ||||
| 		timeriomem_rng_timer.expires = jiffies; | ||||
| 
 | ||||
| 		timeriomem_rng_ops.priv = usecs_to_jiffies( | ||||
| 						timeriomem_rng_data->period); | ||||
| 	if (res->start % 4 != 0 || resource_size(res) != 4) { | ||||
| 		dev_err(&pdev->dev, | ||||
| 			"address must be four bytes wide and aligned\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 	timeriomem_rng_data->present = 1; | ||||
| 
 | ||||
| 	ret = hwrng_register(&timeriomem_rng_ops); | ||||
| 	if (ret) | ||||
| 		goto failed; | ||||
| 	/* Allocate memory for the device structure (and zero it) */ | ||||
| 	priv = kzalloc(sizeof(struct timeriomem_rng_private_data), GFP_KERNEL); | ||||
| 	if (!priv) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate device structure.\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 
 | ||||
| 	platform_set_drvdata(pdev, priv); | ||||
| 
 | ||||
| 	period = pdata->period; | ||||
| 
 | ||||
| 	priv->period = usecs_to_jiffies(period); | ||||
| 	if (priv->period < 1) { | ||||
| 		dev_err(&pdev->dev, "period is less than one jiffy\n"); | ||||
| 		err = -EINVAL; | ||||
| 		goto out_free; | ||||
| 	} | ||||
| 
 | ||||
| 	priv->expires	= jiffies; | ||||
| 	priv->present	= 1; | ||||
| 
 | ||||
| 	init_completion(&priv->completion); | ||||
| 	complete(&priv->completion); | ||||
| 
 | ||||
| 	setup_timer(&priv->timer, timeriomem_rng_trigger, (unsigned long)priv); | ||||
| 
 | ||||
| 	priv->timeriomem_rng_ops.name		= dev_name(&pdev->dev); | ||||
| 	priv->timeriomem_rng_ops.data_present	= timeriomem_rng_data_present; | ||||
| 	priv->timeriomem_rng_ops.data_read	= timeriomem_rng_data_read; | ||||
| 	priv->timeriomem_rng_ops.priv		= (unsigned long)priv; | ||||
| 
 | ||||
| 	if (!request_mem_region(res->start, resource_size(res), | ||||
| 				dev_name(&pdev->dev))) { | ||||
| 		dev_err(&pdev->dev, "request_mem_region failed\n"); | ||||
| 		err = -EBUSY; | ||||
| 		goto out_timer; | ||||
| 	} | ||||
| 
 | ||||
| 	priv->io_base = ioremap(res->start, resource_size(res)); | ||||
| 	if (priv->io_base == NULL) { | ||||
| 		dev_err(&pdev->dev, "ioremap failed\n"); | ||||
| 		err = -EIO; | ||||
| 		goto out_release_io; | ||||
| 	} | ||||
| 
 | ||||
| 	err = hwrng_register(&priv->timeriomem_rng_ops); | ||||
| 	if (err) { | ||||
| 		dev_err(&pdev->dev, "problem registering\n"); | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n", | ||||
| 			timeriomem_rng_data->address, | ||||
| 			timeriomem_rng_data->period); | ||||
| 			priv->io_base, period); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| failed: | ||||
| 	dev_err(&pdev->dev, "problem registering\n"); | ||||
| 	iounmap(timeriomem_rng_data->address); | ||||
| 
 | ||||
| 	return ret; | ||||
| out: | ||||
| 	iounmap(priv->io_base); | ||||
| out_release_io: | ||||
| 	release_mem_region(res->start, resource_size(res)); | ||||
| out_timer: | ||||
| 	del_timer_sync(&priv->timer); | ||||
| out_free: | ||||
| 	platform_set_drvdata(pdev, NULL); | ||||
| 	kfree(priv); | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static int timeriomem_rng_remove(struct platform_device *pdev) | ||||
| { | ||||
| 	del_timer_sync(&timeriomem_rng_timer); | ||||
| 	hwrng_unregister(&timeriomem_rng_ops); | ||||
| 	struct timeriomem_rng_private_data *priv = platform_get_drvdata(pdev); | ||||
| 	struct resource *res; | ||||
| 
 | ||||
| 	iounmap(timeriomem_rng_data->address); | ||||
| 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||||
| 
 | ||||
| 	hwrng_unregister(&priv->timeriomem_rng_ops); | ||||
| 
 | ||||
| 	del_timer_sync(&priv->timer); | ||||
| 	iounmap(priv->io_base); | ||||
| 	release_mem_region(res->start, resource_size(res)); | ||||
| 	platform_set_drvdata(pdev, NULL); | ||||
| 	kfree(priv); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
|  |  | |||
|  | @ -8,12 +8,7 @@ | |||
|  * published by the Free Software Foundation. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/completion.h> | ||||
| 
 | ||||
| struct timeriomem_rng_data { | ||||
| 	struct completion	completion; | ||||
| 	unsigned int		present:1; | ||||
| 
 | ||||
| 	void __iomem		*address; | ||||
| 
 | ||||
| 	/* measures in usecs */ | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Alexander Clouter
						Alexander Clouter