1/* 2 * pps-ldisc.c -- PPS line discipline 3 * 4 * 5 * Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20 */ 21 22#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 23 24#include <linux/module.h> 25#include <linux/serial_core.h> 26#include <linux/tty.h> 27#include <linux/pps_kernel.h> 28#include <linux/bug.h> 29 30#define PPS_TTY_MAGIC 0x0001 31 32static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status) 33{ 34 struct pps_device *pps; 35 struct pps_event_time ts; 36 37 pps_get_ts(&ts); 38 39 pps = pps_lookup_dev(tty); 40 /* 41 * This should never fail, but the ldisc locking is very 42 * convoluted, so don't crash just in case. 43 */ 44 if (WARN_ON_ONCE(pps == NULL)) 45 return; 46 47 /* Now do the PPS event report */ 48 pps_event(pps, &ts, status ? PPS_CAPTUREASSERT : 49 PPS_CAPTURECLEAR, NULL); 50 51 dev_dbg(pps->dev, "PPS %s at %lu\n", 52 status ? "assert" : "clear", jiffies); 53} 54 55static int (*alias_n_tty_open)(struct tty_struct *tty); 56 57static int pps_tty_open(struct tty_struct *tty) 58{ 59 struct pps_source_info info; 60 struct tty_driver *drv = tty->driver; 61 int index = tty->index + drv->name_base; 62 struct pps_device *pps; 63 int ret; 64 65 info.owner = THIS_MODULE; 66 info.dev = NULL; 67 snprintf(info.name, PPS_MAX_NAME_LEN, "%s%d", drv->driver_name, index); 68 snprintf(info.path, PPS_MAX_NAME_LEN, "/dev/%s%d", drv->name, index); 69 info.mode = PPS_CAPTUREBOTH | \ 70 PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ 71 PPS_CANWAIT | PPS_TSFMT_TSPEC; 72 73 pps = pps_register_source(&info, PPS_CAPTUREBOTH | \ 74 PPS_OFFSETASSERT | PPS_OFFSETCLEAR); 75 if (pps == NULL) { 76 pr_err("cannot register PPS source \"%s\"\n", info.path); 77 return -ENOMEM; 78 } 79 pps->lookup_cookie = tty; 80 81 /* Now open the base class N_TTY ldisc */ 82 ret = alias_n_tty_open(tty); 83 if (ret < 0) { 84 pr_err("cannot open tty ldisc \"%s\"\n", info.path); 85 goto err_unregister; 86 } 87 88 dev_info(pps->dev, "source \"%s\" added\n", info.path); 89 90 return 0; 91 92err_unregister: 93 pps_unregister_source(pps); 94 return ret; 95} 96 97static void (*alias_n_tty_close)(struct tty_struct *tty); 98 99static void pps_tty_close(struct tty_struct *tty) 100{ 101 struct pps_device *pps = pps_lookup_dev(tty); 102 103 alias_n_tty_close(tty); 104 105 if (WARN_ON(!pps)) 106 return; 107 108 dev_info(pps->dev, "removed\n"); 109 pps_unregister_source(pps); 110} 111 112static struct tty_ldisc_ops pps_ldisc_ops; 113 114/* 115 * Module stuff 116 */ 117 118static int __init pps_tty_init(void) 119{ 120 int err; 121 122 /* Inherit the N_TTY's ops */ 123 n_tty_inherit_ops(&pps_ldisc_ops); 124 125 /* Save N_TTY's open()/close() methods */ 126 alias_n_tty_open = pps_ldisc_ops.open; 127 alias_n_tty_close = pps_ldisc_ops.close; 128 129 /* Init PPS_TTY data */ 130 pps_ldisc_ops.owner = THIS_MODULE; 131 pps_ldisc_ops.magic = PPS_TTY_MAGIC; 132 pps_ldisc_ops.name = "pps_tty"; 133 pps_ldisc_ops.dcd_change = pps_tty_dcd_change; 134 pps_ldisc_ops.open = pps_tty_open; 135 pps_ldisc_ops.close = pps_tty_close; 136 137 err = tty_register_ldisc(N_PPS, &pps_ldisc_ops); 138 if (err) 139 pr_err("can't register PPS line discipline\n"); 140 else 141 pr_info("PPS line discipline registered\n"); 142 143 return err; 144} 145 146static void __exit pps_tty_cleanup(void) 147{ 148 int err; 149 150 err = tty_unregister_ldisc(N_PPS); 151 if (err) 152 pr_err("can't unregister PPS line discipline\n"); 153 else 154 pr_info("PPS line discipline removed\n"); 155} 156 157module_init(pps_tty_init); 158module_exit(pps_tty_cleanup); 159 160MODULE_ALIAS_LDISC(N_PPS); 161MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); 162MODULE_DESCRIPTION("PPS TTY device driver"); 163MODULE_LICENSE("GPL"); 164