Blame view

drivers/pps/pps.c 8.83 KB
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  /*
   * PPS core file
   *
   *
   * Copyright (C) 2005-2009   Rodolfo Giometti <giometti@linux.it>
   *
   *   This program is free software; you can redistribute it and/or modify
   *   it under the terms of the GNU General Public License as published by
   *   the Free Software Foundation; either version 2 of the License, or
   *   (at your option) any later version.
   *
   *   This program is distributed in the hope that it will be useful,
   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *   GNU General Public License for more details.
   *
   *   You should have received a copy of the GNU General Public License
   *   along with this program; if not, write to the Free Software
   *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
7f7cce741   Alexander Gordeev   pps: convert prin...
21
  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
22
23
24
25
26
27
28
  
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/init.h>
  #include <linux/sched.h>
  #include <linux/uaccess.h>
  #include <linux/idr.h>
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
29
  #include <linux/mutex.h>
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
30
31
32
  #include <linux/cdev.h>
  #include <linux/poll.h>
  #include <linux/pps_kernel.h>
083e58666   Alexander Gordeev   pps: move idr stu...
33
  #include <linux/slab.h>
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
34

717c03366   Alexander Gordeev   pps: add kernel c...
35
  #include "kc.h"
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
36
37
38
39
40
41
  /*
   * Local variables
   */
  
  static dev_t pps_devt;
  static struct class *pps_class;
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
42
  static DEFINE_MUTEX(pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
43
  static DEFINE_IDR(pps_idr);
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  /*
   * Char device methods
   */
  
  static unsigned int pps_cdev_poll(struct file *file, poll_table *wait)
  {
  	struct pps_device *pps = file->private_data;
  
  	poll_wait(file, &pps->queue, wait);
  
  	return POLLIN | POLLRDNORM;
  }
  
  static int pps_cdev_fasync(int fd, struct file *file, int on)
  {
  	struct pps_device *pps = file->private_data;
  	return fasync_helper(fd, file, on, &pps->async_queue);
  }
  
  static long pps_cdev_ioctl(struct file *file,
  		unsigned int cmd, unsigned long arg)
  {
  	struct pps_device *pps = file->private_data;
  	struct pps_kparams params;
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
68
69
70
71
72
73
  	void __user *uarg = (void __user *) arg;
  	int __user *iuarg = (int __user *) arg;
  	int err;
  
  	switch (cmd) {
  	case PPS_GETPARAMS:
7f7cce741   Alexander Gordeev   pps: convert prin...
74
75
  		dev_dbg(pps->dev, "PPS_GETPARAMS
  ");
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
76

cbf83cc5a   Rodolfo Giometti   pps: locking sche...
77
78
79
80
81
82
83
84
  		spin_lock_irq(&pps->lock);
  
  		/* Get the current parameters */
  		params = pps->params;
  
  		spin_unlock_irq(&pps->lock);
  
  		err = copy_to_user(uarg, &params, sizeof(struct pps_kparams));
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
85
86
87
88
89
90
  		if (err)
  			return -EFAULT;
  
  		break;
  
  	case PPS_SETPARAMS:
7f7cce741   Alexander Gordeev   pps: convert prin...
91
92
  		dev_dbg(pps->dev, "PPS_SETPARAMS
  ");
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
93
94
95
96
97
98
99
100
101
  
  		/* Check the capabilities */
  		if (!capable(CAP_SYS_TIME))
  			return -EPERM;
  
  		err = copy_from_user(&params, uarg, sizeof(struct pps_kparams));
  		if (err)
  			return -EFAULT;
  		if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
7f7cce741   Alexander Gordeev   pps: convert prin...
102
103
  			dev_dbg(pps->dev, "capture mode unspecified (%x)
  ",
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
104
105
106
107
108
109
  								params.mode);
  			return -EINVAL;
  		}
  
  		/* Check for supported capabilities */
  		if ((params.mode & ~pps->info.mode) != 0) {
7f7cce741   Alexander Gordeev   pps: convert prin...
110
111
  			dev_dbg(pps->dev, "unsupported capabilities (%x)
  ",
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
112
113
114
115
116
117
118
119
120
121
122
123
  								params.mode);
  			return -EINVAL;
  		}
  
  		spin_lock_irq(&pps->lock);
  
  		/* Save the new parameters */
  		pps->params = params;
  
  		/* Restore the read only parameters */
  		if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
  			/* section 3.3 of RFC 2783 interpreted */
7f7cce741   Alexander Gordeev   pps: convert prin...
124
125
  			dev_dbg(pps->dev, "time format unspecified (%x)
  ",
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
126
127
128
129
130
131
132
133
134
135
136
137
  								params.mode);
  			pps->params.mode |= PPS_TSFMT_TSPEC;
  		}
  		if (pps->info.mode & PPS_CANWAIT)
  			pps->params.mode |= PPS_CANWAIT;
  		pps->params.api_version = PPS_API_VERS;
  
  		spin_unlock_irq(&pps->lock);
  
  		break;
  
  	case PPS_GETCAP:
7f7cce741   Alexander Gordeev   pps: convert prin...
138
139
  		dev_dbg(pps->dev, "PPS_GETCAP
  ");
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
140
141
142
143
144
145
  
  		err = put_user(pps->info.mode, iuarg);
  		if (err)
  			return -EFAULT;
  
  		break;
86d921f9e   Alexander Gordeev   pps: declare vari...
146
147
  	case PPS_FETCH: {
  		struct pps_fdata fdata;
3003d55b5   Alexander Gordeev   pps: fix race in ...
148
  		unsigned int ev;
86d921f9e   Alexander Gordeev   pps: declare vari...
149

7f7cce741   Alexander Gordeev   pps: convert prin...
150
151
  		dev_dbg(pps->dev, "PPS_FETCH
  ");
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
152
153
154
155
  
  		err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
  		if (err)
  			return -EFAULT;
3003d55b5   Alexander Gordeev   pps: fix race in ...
156
  		ev = pps->last_ev;
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
157
158
159
  
  		/* Manage the timeout */
  		if (fdata.timeout.flags & PPS_TIME_INVALID)
3003d55b5   Alexander Gordeev   pps: fix race in ...
160
161
  			err = wait_event_interruptible(pps->queue,
  					ev != pps->last_ev);
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
162
  		else {
86d921f9e   Alexander Gordeev   pps: declare vari...
163
  			unsigned long ticks;
7f7cce741   Alexander Gordeev   pps: convert prin...
164
165
  			dev_dbg(pps->dev, "timeout %lld.%09d
  ",
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
166
167
168
169
170
171
172
  					(long long) fdata.timeout.sec,
  					fdata.timeout.nsec);
  			ticks = fdata.timeout.sec * HZ;
  			ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ);
  
  			if (ticks != 0) {
  				err = wait_event_interruptible_timeout(
3003d55b5   Alexander Gordeev   pps: fix race in ...
173
174
175
  						pps->queue,
  						ev != pps->last_ev,
  						ticks);
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
176
177
178
179
180
181
182
  				if (err == 0)
  					return -ETIMEDOUT;
  			}
  		}
  
  		/* Check for pending signals */
  		if (err == -ERESTARTSYS) {
7f7cce741   Alexander Gordeev   pps: convert prin...
183
184
  			dev_dbg(pps->dev, "pending signal caught
  ");
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
  			return -EINTR;
  		}
  
  		/* Return the fetched timestamp */
  		spin_lock_irq(&pps->lock);
  
  		fdata.info.assert_sequence = pps->assert_sequence;
  		fdata.info.clear_sequence = pps->clear_sequence;
  		fdata.info.assert_tu = pps->assert_tu;
  		fdata.info.clear_tu = pps->clear_tu;
  		fdata.info.current_mode = pps->current_mode;
  
  		spin_unlock_irq(&pps->lock);
  
  		err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
  		if (err)
  			return -EFAULT;
  
  		break;
86d921f9e   Alexander Gordeev   pps: declare vari...
204
  	}
717c03366   Alexander Gordeev   pps: add kernel c...
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
  	case PPS_KC_BIND: {
  		struct pps_bind_args bind_args;
  
  		dev_dbg(pps->dev, "PPS_KC_BIND
  ");
  
  		/* Check the capabilities */
  		if (!capable(CAP_SYS_TIME))
  			return -EPERM;
  
  		if (copy_from_user(&bind_args, uarg,
  					sizeof(struct pps_bind_args)))
  			return -EFAULT;
  
  		/* Check for supported capabilities */
  		if ((bind_args.edge & ~pps->info.mode) != 0) {
  			dev_err(pps->dev, "unsupported capabilities (%x)
  ",
  					bind_args.edge);
  			return -EINVAL;
  		}
  
  		/* Validate parameters roughly */
  		if (bind_args.tsformat != PPS_TSFMT_TSPEC ||
  				(bind_args.edge & ~PPS_CAPTUREBOTH) != 0 ||
  				bind_args.consumer != PPS_KC_HARDPPS) {
  			dev_err(pps->dev, "invalid kernel consumer bind"
  					" parameters (%x)
  ", bind_args.edge);
  			return -EINVAL;
  		}
  
  		err = pps_kc_bind(pps, &bind_args);
  		if (err < 0)
  			return err;
  
  		break;
  	}
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
243
244
  	default:
  		return -ENOTTY;
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
245
246
247
248
249
250
251
252
253
  	}
  
  	return 0;
  }
  
  static int pps_cdev_open(struct inode *inode, struct file *file)
  {
  	struct pps_device *pps = container_of(inode->i_cdev,
  						struct pps_device, cdev);
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
254
255
256
257
258
259
260
  	file->private_data = pps;
  
  	return 0;
  }
  
  static int pps_cdev_release(struct inode *inode, struct file *file)
  {
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  	return 0;
  }
  
  /*
   * Char device stuff
   */
  
  static const struct file_operations pps_cdev_fops = {
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
  	.poll		= pps_cdev_poll,
  	.fasync		= pps_cdev_fasync,
  	.unlocked_ioctl	= pps_cdev_ioctl,
  	.open		= pps_cdev_open,
  	.release	= pps_cdev_release,
  };
083e58666   Alexander Gordeev   pps: move idr stu...
277
278
279
280
281
282
  static void pps_device_destruct(struct device *dev)
  {
  	struct pps_device *pps = dev_get_drvdata(dev);
  
  	/* release id here to protect others from using it while it's
  	 * still in use */
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
283
  	mutex_lock(&pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
284
  	idr_remove(&pps_idr, pps->id);
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
285
  	mutex_unlock(&pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
286
287
288
289
  
  	kfree(dev);
  	kfree(pps);
  }
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
290
291
292
  int pps_register_cdev(struct pps_device *pps)
  {
  	int err;
5e196d34a   Alexander Gordeev   pps: access pps d...
293
  	dev_t devt;
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
294
  	mutex_lock(&pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
295
  	/* Get new ID for the new PPS source */
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
296
297
  	if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) {
  		mutex_unlock(&pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
298
  		return -ENOMEM;
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
299
  	}
083e58666   Alexander Gordeev   pps: move idr stu...
300
301
302
303
304
  
  	/* Now really allocate the PPS source.
  	 * After idr_get_new() calling the new source will be freely available
  	 * into the kernel.
  	 */
083e58666   Alexander Gordeev   pps: move idr stu...
305
  	err = idr_get_new(&pps_idr, pps, &pps->id);
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
306
  	mutex_unlock(&pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
307
308
309
310
311
312
313
314
315
316
317
318
  
  	if (err < 0)
  		return err;
  
  	pps->id &= MAX_ID_MASK;
  	if (pps->id >= PPS_MAX_SOURCES) {
  		pr_err("%s: too many PPS sources in the system
  ",
  					pps->info.name);
  		err = -EBUSY;
  		goto free_idr;
  	}
5e196d34a   Alexander Gordeev   pps: access pps d...
319
  	devt = MKDEV(MAJOR(pps_devt), pps->id);
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
320

eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
321
322
  	cdev_init(&pps->cdev, &pps_cdev_fops);
  	pps->cdev.owner = pps->info.owner;
5e196d34a   Alexander Gordeev   pps: access pps d...
323
  	err = cdev_add(&pps->cdev, devt, 1);
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
324
  	if (err) {
7f7cce741   Alexander Gordeev   pps: convert prin...
325
326
  		pr_err("%s: failed to add char device %d:%d
  ",
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
327
  				pps->info.name, MAJOR(pps_devt), pps->id);
083e58666   Alexander Gordeev   pps: move idr stu...
328
  		goto free_idr;
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
329
  	}
5e196d34a   Alexander Gordeev   pps: access pps d...
330
  	pps->dev = device_create(pps_class, pps->info.dev, devt, pps,
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
331
  							"pps%d", pps->id);
054b2b13c   Joonwoo Park   pps: fix incorrec...
332
  	if (IS_ERR(pps->dev))
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
333
  		goto del_cdev;
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
334

083e58666   Alexander Gordeev   pps: move idr stu...
335
  	pps->dev->release = pps_device_destruct;
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
336
337
338
339
340
341
342
343
  	pr_debug("source %s got cdev (%d:%d)
  ", pps->info.name,
  			MAJOR(pps_devt), pps->id);
  
  	return 0;
  
  del_cdev:
  	cdev_del(&pps->cdev);
083e58666   Alexander Gordeev   pps: move idr stu...
344
  free_idr:
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
345
  	mutex_lock(&pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
346
  	idr_remove(&pps_idr, pps->id);
2a5cd6e2f   Alexander Gordeev   pps: make idr loc...
347
  	mutex_unlock(&pps_idr_lock);
083e58666   Alexander Gordeev   pps: move idr stu...
348

eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
349
350
351
352
353
  	return err;
  }
  
  void pps_unregister_cdev(struct pps_device *pps)
  {
5e196d34a   Alexander Gordeev   pps: access pps d...
354
  	device_destroy(pps_class, pps->dev->devt);
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
  	cdev_del(&pps->cdev);
  }
  
  /*
   * Module stuff
   */
  
  static void __exit pps_exit(void)
  {
  	class_destroy(pps_class);
  	unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
  }
  
  static int __init pps_init(void)
  {
  	int err;
  
  	pps_class = class_create(THIS_MODULE, "pps");
  	if (!pps_class) {
7f7cce741   Alexander Gordeev   pps: convert prin...
374
375
  		pr_err("failed to allocate class
  ");
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
376
377
378
379
380
381
  		return -ENOMEM;
  	}
  	pps_class->dev_attrs = pps_attrs;
  
  	err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
  	if (err < 0) {
7f7cce741   Alexander Gordeev   pps: convert prin...
382
383
  		pr_err("failed to allocate char device region
  ");
eae9d2ba0   Rodolfo Giometti   LinuxPPS: core su...
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
  		goto remove_class;
  	}
  
  	pr_info("LinuxPPS API ver. %d registered
  ", PPS_API_VERS);
  	pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti "
  		"<giometti@linux.it>
  ", PPS_VERSION);
  
  	return 0;
  
  remove_class:
  	class_destroy(pps_class);
  
  	return err;
  }
  
  subsys_initcall(pps_init);
  module_exit(pps_exit);
  
  MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
  MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
  MODULE_LICENSE("GPL");