| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 1 | /** | 
 | 2 |  * TSIF driver client | 
 | 3 |  * | 
 | 4 |  * Character device that, being read | 
 | 5 |  * returns stream of TSIF packets. | 
 | 6 |  * | 
| Joel Nider | 5578bdb | 2011-08-12 09:37:11 +0300 | [diff] [blame] | 7 |  * Copyright (c) 2009-2011, Code Aurora Forum. All rights | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 8 |  * reserved. | 
 | 9 |  * | 
 | 10 |  * This program is free software; you can redistribute it and/or modify | 
 | 11 |  * it under the terms of the GNU General Public License version 2 and | 
 | 12 |  * only version 2 as published by the Free Software Foundation. | 
 | 13 |  * | 
 | 14 |  * This program is distributed in the hope that it will be useful, | 
 | 15 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 16 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 17 |  * GNU General Public License for more details. | 
 | 18 |  */ | 
 | 19 |  | 
 | 20 | #include <linux/module.h>       /* Needed by all modules */ | 
 | 21 | #include <linux/kernel.h>       /* Needed for KERN_INFO */ | 
 | 22 | #include <linux/cdev.h> | 
 | 23 | #include <linux/err.h>          /* IS_ERR etc. */ | 
 | 24 | #include <linux/fs.h> | 
 | 25 | #include <linux/device.h> | 
 | 26 | #include <linux/sched.h>        /* TASK_INTERRUPTIBLE */ | 
 | 27 |  | 
 | 28 | #include <linux/uaccess.h>        /* copy_to_user */ | 
 | 29 |  | 
 | 30 | #include <linux/tsif_api.h> | 
 | 31 |  | 
 | 32 | struct tsif_chrdev { | 
 | 33 | 	struct cdev cdev; | 
 | 34 | 	struct device *dev; | 
 | 35 | 	wait_queue_head_t wq_read; | 
 | 36 | 	void *cookie; | 
 | 37 | 	/* mirror for tsif data */ | 
 | 38 | 	void *data_buffer; | 
 | 39 | 	unsigned buf_size_packets; /**< buffer size in packets */ | 
 | 40 | 	unsigned ri, wi; | 
 | 41 | 	enum tsif_state state; | 
 | 42 | 	unsigned rptr; | 
 | 43 | }; | 
 | 44 |  | 
 | 45 | static ssize_t tsif_open(struct inode *inode, struct file *file) | 
 | 46 | { | 
 | 47 | 	int rc; | 
 | 48 | 	struct tsif_chrdev *the_dev = | 
 | 49 | 	       container_of(inode->i_cdev, struct tsif_chrdev, cdev); | 
 | 50 | 	if (!the_dev->cookie)  /* not bound yet */ | 
 | 51 | 		return -ENODEV; | 
 | 52 | 	file->private_data = the_dev; | 
 | 53 | 	rc = tsif_start(the_dev->cookie); | 
 | 54 | 	if (rc) | 
 | 55 | 		return rc; | 
 | 56 | 	tsif_get_info(the_dev->cookie, &the_dev->data_buffer, | 
 | 57 | 		      &the_dev->buf_size_packets); | 
 | 58 | 	the_dev->rptr = 0; | 
 | 59 | 	return nonseekable_open(inode, file); | 
 | 60 | } | 
 | 61 |  | 
 | 62 | static ssize_t tsif_release(struct inode *inode, struct file *filp) | 
 | 63 | { | 
 | 64 | 	struct tsif_chrdev *the_dev = filp->private_data; | 
 | 65 | 	tsif_stop(the_dev->cookie); | 
 | 66 | 	return 0; | 
 | 67 | } | 
 | 68 |  | 
 | 69 | static ssize_t tsif_read(struct file *filp, char __user *buf, size_t count, | 
 | 70 | 			 loff_t *f_pos) | 
 | 71 | { | 
 | 72 | 	int avail = 0; | 
 | 73 | 	int wi; | 
 | 74 | 	struct tsif_chrdev *the_dev = filp->private_data; | 
 | 75 | 	tsif_get_state(the_dev->cookie, &the_dev->ri, &the_dev->wi, | 
 | 76 | 		       &the_dev->state); | 
 | 77 | 	/* consistency check */ | 
 | 78 | 	if (the_dev->ri != (the_dev->rptr / TSIF_PKT_SIZE)) { | 
 | 79 | 		dev_err(the_dev->dev, | 
 | 80 | 			"%s: inconsistent read pointers: ri %d rptr %d\n", | 
 | 81 | 			__func__, the_dev->ri, the_dev->rptr); | 
 | 82 | 		the_dev->rptr = the_dev->ri * TSIF_PKT_SIZE; | 
 | 83 | 	} | 
 | 84 | 	/* ri == wi if no data */ | 
 | 85 | 	if (the_dev->ri == the_dev->wi) { | 
 | 86 | 		/* shall I block waiting for data? */ | 
 | 87 | 		if (filp->f_flags & O_NONBLOCK) { | 
 | 88 | 			if (the_dev->state == tsif_state_running) { | 
 | 89 | 				return -EAGAIN; | 
 | 90 | 			} else { | 
 | 91 | 				/* not running -> EOF */ | 
 | 92 | 				return 0; | 
 | 93 | 			} | 
 | 94 | 		} | 
 | 95 | 		if (wait_event_interruptible(the_dev->wq_read, | 
 | 96 | 		      (the_dev->ri != the_dev->wi) || | 
 | 97 | 		      (the_dev->state != tsif_state_running))) { | 
 | 98 | 			/* got signal -> tell FS to handle it */ | 
 | 99 | 			return -ERESTARTSYS; | 
 | 100 | 		} | 
 | 101 | 		if (the_dev->ri == the_dev->wi) { | 
 | 102 | 			/* still no data -> EOF */ | 
 | 103 | 			return 0; | 
 | 104 | 		} | 
 | 105 | 	} | 
 | 106 | 	/* contiguous chunk last up to wi or end of buffer */ | 
 | 107 | 	wi = (the_dev->wi > the_dev->ri) ? | 
 | 108 | 		the_dev->wi : the_dev->buf_size_packets; | 
 | 109 | 	avail = min(wi * TSIF_PKT_SIZE - the_dev->rptr, count); | 
 | 110 | 	if (copy_to_user(buf, the_dev->data_buffer + the_dev->rptr, avail)) | 
 | 111 | 		return -EFAULT; | 
 | 112 | 	the_dev->rptr = (the_dev->rptr + avail) % | 
 | 113 | 		(TSIF_PKT_SIZE * the_dev->buf_size_packets); | 
 | 114 | 	the_dev->ri = the_dev->rptr / TSIF_PKT_SIZE; | 
 | 115 | 	*f_pos += avail; | 
 | 116 | 	tsif_reclaim_packets(the_dev->cookie, the_dev->ri); | 
 | 117 | 	return avail; | 
 | 118 | } | 
 | 119 |  | 
 | 120 | static void tsif_notify(void *data) | 
 | 121 | { | 
 | 122 | 	struct tsif_chrdev *the_dev = data; | 
 | 123 | 	tsif_get_state(the_dev->cookie, &the_dev->ri, &the_dev->wi, | 
 | 124 | 		       &the_dev->state); | 
 | 125 | 	wake_up_interruptible(&the_dev->wq_read); | 
 | 126 | } | 
 | 127 |  | 
 | 128 | static const struct file_operations tsif_fops = { | 
 | 129 | 	.owner   = THIS_MODULE, | 
 | 130 | 	.read    = tsif_read, | 
 | 131 | 	.open    = tsif_open, | 
 | 132 | 	.release = tsif_release, | 
 | 133 | }; | 
 | 134 |  | 
 | 135 | static struct class *tsif_class; | 
 | 136 | static dev_t tsif_dev;  /**< 1-st dev_t from allocated range */ | 
 | 137 | static dev_t tsif_dev0; /**< next not yet assigned dev_t */ | 
 | 138 |  | 
 | 139 | static int tsif_init_one(struct tsif_chrdev *the_dev, int index) | 
 | 140 | { | 
 | 141 | 	int rc; | 
 | 142 | 	pr_info("%s[%d]\n", __func__, index); | 
 | 143 | 	cdev_init(&the_dev->cdev, &tsif_fops); | 
 | 144 | 	the_dev->cdev.owner = THIS_MODULE; | 
 | 145 | 	init_waitqueue_head(&the_dev->wq_read); | 
 | 146 | 	rc = cdev_add(&the_dev->cdev, tsif_dev0++, 1); | 
 | 147 | 	the_dev->dev = device_create(tsif_class, NULL, the_dev->cdev.dev, | 
 | 148 | 				     the_dev, "tsif%d", index); | 
 | 149 | 	if (IS_ERR(the_dev->dev)) { | 
 | 150 | 		rc = PTR_ERR(the_dev->dev); | 
 | 151 | 		pr_err("device_create failed: %d\n", rc); | 
 | 152 | 		goto err_create; | 
 | 153 | 	} | 
 | 154 | 	the_dev->cookie = tsif_attach(index, tsif_notify, the_dev); | 
 | 155 | 	if (IS_ERR(the_dev->cookie)) { | 
 | 156 | 		rc = PTR_ERR(the_dev->cookie); | 
 | 157 | 		pr_err("tsif_attach failed: %d\n", rc); | 
 | 158 | 		goto err_attach; | 
 | 159 | 	} | 
 | 160 | 	/* now data buffer is not allocated yet */ | 
 | 161 | 	tsif_get_info(the_dev->cookie, &the_dev->data_buffer, NULL); | 
 | 162 | 	dev_info(the_dev->dev, | 
 | 163 | 		 "Device %d.%d attached to TSIF, buffer size %d\n", | 
 | 164 | 		 MAJOR(the_dev->cdev.dev), MINOR(the_dev->cdev.dev), | 
 | 165 | 		 the_dev->buf_size_packets); | 
 | 166 | 	return 0; | 
 | 167 | err_attach: | 
 | 168 | 	device_destroy(tsif_class, the_dev->cdev.dev); | 
 | 169 | err_create: | 
 | 170 | 	cdev_del(&the_dev->cdev); | 
 | 171 | 	return rc; | 
 | 172 | } | 
 | 173 |  | 
 | 174 | static void tsif_exit_one(struct tsif_chrdev *the_dev) | 
 | 175 | { | 
 | 176 | 	dev_info(the_dev->dev, "%s\n", __func__); | 
 | 177 | 	tsif_detach(the_dev->cookie); | 
 | 178 | 	device_destroy(tsif_class, the_dev->cdev.dev); | 
 | 179 | 	cdev_del(&the_dev->cdev); | 
 | 180 | } | 
 | 181 |  | 
 | 182 | #define TSIF_NUM_DEVS 1 /**< support this many devices */ | 
 | 183 |  | 
 | 184 | struct tsif_chrdev the_devices[TSIF_NUM_DEVS]; | 
 | 185 |  | 
 | 186 | static int __init mod_init(void) | 
 | 187 | { | 
 | 188 | 	int rc; | 
| Joel Nider | 5578bdb | 2011-08-12 09:37:11 +0300 | [diff] [blame] | 189 | 	int instance; | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 190 | 	rc = alloc_chrdev_region(&tsif_dev, 0, TSIF_NUM_DEVS, "tsif"); | 
 | 191 | 	if (rc) { | 
 | 192 | 		pr_err("alloc_chrdev_region failed: %d\n", rc); | 
 | 193 | 		goto err_devrgn; | 
 | 194 | 	} | 
 | 195 | 	tsif_dev0 = tsif_dev; | 
 | 196 | 	tsif_class = class_create(THIS_MODULE, "tsif"); | 
 | 197 | 	if (IS_ERR(tsif_class)) { | 
 | 198 | 		rc = PTR_ERR(tsif_class); | 
 | 199 | 		pr_err("Error creating tsif class: %d\n", rc); | 
 | 200 | 		goto err_class; | 
 | 201 | 	} | 
| Joel Nider | 5578bdb | 2011-08-12 09:37:11 +0300 | [diff] [blame] | 202 | 	instance = tsif_get_active(); | 
 | 203 | 	if (instance >= 0) | 
 | 204 | 		rc = tsif_init_one(&the_devices[0], instance); | 
 | 205 | 	else | 
 | 206 | 		rc = instance; | 
| Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 207 | 	if (rc) | 
 | 208 | 		goto err_init1; | 
 | 209 | 	return 0; | 
 | 210 | err_init1: | 
 | 211 | 	class_destroy(tsif_class); | 
 | 212 | err_class: | 
 | 213 | 	unregister_chrdev_region(tsif_dev, TSIF_NUM_DEVS); | 
 | 214 | err_devrgn: | 
 | 215 | 	return rc; | 
 | 216 | } | 
 | 217 |  | 
 | 218 | static void __exit mod_exit(void) | 
 | 219 | { | 
 | 220 | 	tsif_exit_one(&the_devices[0]); | 
 | 221 | 	class_destroy(tsif_class); | 
 | 222 | 	unregister_chrdev_region(tsif_dev, TSIF_NUM_DEVS); | 
 | 223 | } | 
 | 224 |  | 
 | 225 | module_init(mod_init); | 
 | 226 | module_exit(mod_exit); | 
 | 227 |  | 
 | 228 | MODULE_DESCRIPTION("TSIF character device interface"); | 
 | 229 | MODULE_LICENSE("GPL v2"); | 
 | 230 |  |