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 32 static 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 55 static int (*alias_n_tty_open)(struct tty_struct *tty); 56 57 static 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 92 err_unregister: 93 pps_unregister_source(pps); 94 return ret; 95 } 96 97 static void (*alias_n_tty_close)(struct tty_struct *tty); 98 99 static 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 112 static struct tty_ldisc_ops pps_ldisc_ops; 113 114 /* 115 * Module stuff 116 */ 117 118 static 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 146 static 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 157 module_init(pps_tty_init); 158 module_exit(pps_tty_cleanup); 159 160 MODULE_ALIAS_LDISC(N_PPS); 161 MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); 162 MODULE_DESCRIPTION("PPS TTY device driver"); 163 MODULE_LICENSE("GPL"); 164