drivers/misc: Aspeed LPC snoop output using misc chardev
Provides the data bytes snooped over the LPC snoop bus to userspace as a (blocking) misc character device. Bytes output from the host using LPC I/O transactions to the snooped port can be watched or retrieved from the character device using a simple command like this: ~# od -w1 -A n -t x1 /dev/aspeed-lpc-snoop0 10 de ad c0 ff ee Signed-off-by: Robert Lippert <rlippert@google.com> Signed-off-by: Patrick Venture <venture@google.com> Signed-off-by: Benjamin Fair <benjaminfair@google.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									7f3838fb8c
								
							
						
					
					
						commit
						3772e5da44
					
				| @ -16,12 +16,15 @@ | |||||||
| 
 | 
 | ||||||
| #include <linux/bitops.h> | #include <linux/bitops.h> | ||||||
| #include <linux/interrupt.h> | #include <linux/interrupt.h> | ||||||
|  | #include <linux/fs.h> | ||||||
| #include <linux/kfifo.h> | #include <linux/kfifo.h> | ||||||
| #include <linux/mfd/syscon.h> | #include <linux/mfd/syscon.h> | ||||||
|  | #include <linux/miscdevice.h> | ||||||
| #include <linux/module.h> | #include <linux/module.h> | ||||||
| #include <linux/of.h> | #include <linux/of.h> | ||||||
| #include <linux/of_device.h> | #include <linux/of_device.h> | ||||||
| #include <linux/platform_device.h> | #include <linux/platform_device.h> | ||||||
|  | #include <linux/poll.h> | ||||||
| #include <linux/regmap.h> | #include <linux/regmap.h> | ||||||
| 
 | 
 | ||||||
| #define DEVICE_NAME	"aspeed-lpc-snoop" | #define DEVICE_NAME	"aspeed-lpc-snoop" | ||||||
| @ -59,20 +62,70 @@ struct aspeed_lpc_snoop_model_data { | |||||||
| 	unsigned int has_hicrb_ensnp; | 	unsigned int has_hicrb_ensnp; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct aspeed_lpc_snoop_channel { | ||||||
|  | 	struct kfifo		fifo; | ||||||
|  | 	wait_queue_head_t	wq; | ||||||
|  | 	struct miscdevice	miscdev; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| struct aspeed_lpc_snoop { | struct aspeed_lpc_snoop { | ||||||
| 	struct regmap		*regmap; | 	struct regmap		*regmap; | ||||||
| 	int			irq; | 	int			irq; | ||||||
| 	struct kfifo		snoop_fifo[NUM_SNOOP_CHANNELS]; | 	struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file) | ||||||
|  | { | ||||||
|  | 	return container_of(file->private_data, | ||||||
|  | 			    struct aspeed_lpc_snoop_channel, | ||||||
|  | 			    miscdev); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static ssize_t snoop_file_read(struct file *file, char __user *buffer, | ||||||
|  | 				size_t count, loff_t *ppos) | ||||||
|  | { | ||||||
|  | 	struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); | ||||||
|  | 	unsigned int copied; | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	if (kfifo_is_empty(&chan->fifo)) { | ||||||
|  | 		if (file->f_flags & O_NONBLOCK) | ||||||
|  | 			return -EAGAIN; | ||||||
|  | 		ret = wait_event_interruptible(chan->wq, | ||||||
|  | 				!kfifo_is_empty(&chan->fifo)); | ||||||
|  | 		if (ret == -ERESTARTSYS) | ||||||
|  | 			return -EINTR; | ||||||
|  | 	} | ||||||
|  | 	ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); | ||||||
|  | 
 | ||||||
|  | 	return ret ? ret : copied; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static unsigned int snoop_file_poll(struct file *file, | ||||||
|  | 				    struct poll_table_struct *pt) | ||||||
|  | { | ||||||
|  | 	struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); | ||||||
|  | 
 | ||||||
|  | 	poll_wait(file, &chan->wq, pt); | ||||||
|  | 	return !kfifo_is_empty(&chan->fifo) ? POLLIN : 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct file_operations snoop_fops = { | ||||||
|  | 	.owner  = THIS_MODULE, | ||||||
|  | 	.read   = snoop_file_read, | ||||||
|  | 	.poll   = snoop_file_poll, | ||||||
|  | 	.llseek = noop_llseek, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ | /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ | ||||||
| static void put_fifo_with_discard(struct kfifo *fifo, u8 val) | static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val) | ||||||
| { | { | ||||||
| 	if (!kfifo_initialized(fifo)) | 	if (!kfifo_initialized(&chan->fifo)) | ||||||
| 		return; | 		return; | ||||||
| 	if (kfifo_is_full(fifo)) | 	if (kfifo_is_full(&chan->fifo)) | ||||||
| 		kfifo_skip(fifo); | 		kfifo_skip(&chan->fifo); | ||||||
| 	kfifo_put(fifo, val); | 	kfifo_put(&chan->fifo, val); | ||||||
|  | 	wake_up_interruptible(&chan->wq); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) | static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) | ||||||
| @ -97,12 +150,12 @@ static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) | |||||||
| 	if (reg & HICR6_STR_SNP0W) { | 	if (reg & HICR6_STR_SNP0W) { | ||||||
| 		u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; | 		u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; | ||||||
| 
 | 
 | ||||||
| 		put_fifo_with_discard(&lpc_snoop->snoop_fifo[0], val); | 		put_fifo_with_discard(&lpc_snoop->chan[0], val); | ||||||
| 	} | 	} | ||||||
| 	if (reg & HICR6_STR_SNP1W) { | 	if (reg & HICR6_STR_SNP1W) { | ||||||
| 		u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; | 		u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; | ||||||
| 
 | 
 | ||||||
| 		put_fifo_with_discard(&lpc_snoop->snoop_fifo[1], val); | 		put_fifo_with_discard(&lpc_snoop->chan[1], val); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return IRQ_HANDLED; | 	return IRQ_HANDLED; | ||||||
| @ -139,12 +192,22 @@ static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, | |||||||
| 	const struct aspeed_lpc_snoop_model_data *model_data = | 	const struct aspeed_lpc_snoop_model_data *model_data = | ||||||
| 		of_device_get_match_data(dev); | 		of_device_get_match_data(dev); | ||||||
| 
 | 
 | ||||||
|  | 	init_waitqueue_head(&lpc_snoop->chan[channel].wq); | ||||||
| 	/* Create FIFO datastructure */ | 	/* Create FIFO datastructure */ | ||||||
| 	rc = kfifo_alloc(&lpc_snoop->snoop_fifo[channel], | 	rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, | ||||||
| 			 SNOOP_FIFO_SIZE, GFP_KERNEL); | 			 SNOOP_FIFO_SIZE, GFP_KERNEL); | ||||||
| 	if (rc) | 	if (rc) | ||||||
| 		return rc; | 		return rc; | ||||||
| 
 | 
 | ||||||
|  | 	lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; | ||||||
|  | 	lpc_snoop->chan[channel].miscdev.name = | ||||||
|  | 		devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); | ||||||
|  | 	lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; | ||||||
|  | 	lpc_snoop->chan[channel].miscdev.parent = dev; | ||||||
|  | 	rc = misc_register(&lpc_snoop->chan[channel].miscdev); | ||||||
|  | 	if (rc) | ||||||
|  | 		return rc; | ||||||
|  | 
 | ||||||
| 	/* Enable LPC snoop channel at requested port */ | 	/* Enable LPC snoop channel at requested port */ | ||||||
| 	switch (channel) { | 	switch (channel) { | ||||||
| 	case 0: | 	case 0: | ||||||
| @ -191,7 +254,8 @@ static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	kfifo_free(&lpc_snoop->snoop_fifo[channel]); | 	kfifo_free(&lpc_snoop->chan[channel].fifo); | ||||||
|  | 	misc_deregister(&lpc_snoop->chan[channel].miscdev); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int aspeed_lpc_snoop_probe(struct platform_device *pdev) | static int aspeed_lpc_snoop_probe(struct platform_device *pdev) | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user