Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 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 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | // SPDX-License-Identifier: GPL-2.0 /* * Copyright 2018-2020 Broadcom. */ #include <linux/tty.h> #include <linux/tty_driver.h> #include <linux/tty_flip.h> #include "bcm_vk.h" /* TTYVK base offset is 0x30000 into BAR1 */ #define BAR1_TTYVK_BASE_OFFSET 0x300000 /* Each TTYVK channel (TO or FROM) is 0x10000 */ #define BAR1_TTYVK_CHAN_OFFSET 0x100000 /* Each TTYVK channel has TO and FROM, hence the * 2 */ #define BAR1_TTYVK_BASE(index) (BAR1_TTYVK_BASE_OFFSET + \ ((index) * BAR1_TTYVK_CHAN_OFFSET * 2)) /* TO TTYVK channel base comes before FROM for each index */ #define TO_TTYK_BASE(index) BAR1_TTYVK_BASE(index) #define FROM_TTYK_BASE(index) (BAR1_TTYVK_BASE(index) + \ BAR1_TTYVK_CHAN_OFFSET) struct bcm_vk_tty_chan { u32 reserved; u32 size; u32 wr; u32 rd; u32 *data; }; #define VK_BAR_CHAN(v, DIR, e) ((v)->DIR##_offset \ + offsetof(struct bcm_vk_tty_chan, e)) #define VK_BAR_CHAN_SIZE(v, DIR) VK_BAR_CHAN(v, DIR, size) #define VK_BAR_CHAN_WR(v, DIR) VK_BAR_CHAN(v, DIR, wr) #define VK_BAR_CHAN_RD(v, DIR) VK_BAR_CHAN(v, DIR, rd) #define VK_BAR_CHAN_DATA(v, DIR, off) (VK_BAR_CHAN(v, DIR, data) + (off)) #define VK_BAR0_REGSEG_TTY_DB_OFFSET 0x86c /* Poll every 1/10 of second - temp hack till we use MSI interrupt */ #define SERIAL_TIMER_VALUE (HZ / 10) static void bcm_vk_tty_poll(struct timer_list *t) { struct bcm_vk *vk = from_timer(vk, t, serial_timer); queue_work(vk->tty_wq_thread, &vk->tty_wq_work); mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE); } irqreturn_t bcm_vk_tty_irqhandler(int irq, void *dev_id) { struct bcm_vk *vk = dev_id; queue_work(vk->tty_wq_thread, &vk->tty_wq_work); return IRQ_HANDLED; } static void bcm_vk_tty_wq_handler(struct work_struct *work) { struct bcm_vk *vk = container_of(work, struct bcm_vk, tty_wq_work); struct bcm_vk_tty *vktty; int card_status; int count; int i; int wr; u8 c; card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS); if (BCM_VK_INTF_IS_DOWN(card_status)) return; for (i = 0; i < BCM_VK_NUM_TTY; i++) { count = 0; /* Check the card status that the tty channel is ready */ if ((card_status & BIT(i)) == 0) continue; vktty = &vk->tty[i]; /* Don't increment read index if tty app is closed */ if (!vktty->is_opened) continue; /* Fetch the wr offset in buffer from VK */ wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, from)); /* safe to ignore until bar read gives proper size */ if (vktty->from_size == 0) continue; if (wr >= vktty->from_size) { dev_err(&vk->pdev->dev, "ERROR: wq handler ttyVK%d wr:0x%x > 0x%x\n", i, wr, vktty->from_size); /* Need to signal and close device in this case */ continue; } /* * Simple read of circular buffer and * insert into tty flip buffer */ while (vk->tty[i].rd != wr) { c = vkread8(vk, BAR_1, VK_BAR_CHAN_DATA(vktty, from, vktty->rd)); vktty->rd++; if (vktty->rd >= vktty->from_size) vktty->rd = 0; tty_insert_flip_char(&vktty->port, c, TTY_NORMAL); count++; } if (count) { tty_flip_buffer_push(&vktty->port); /* Update read offset from shadow register to card */ vkwrite32(vk, vktty->rd, BAR_1, VK_BAR_CHAN_RD(vktty, from)); } } } static int bcm_vk_tty_open(struct tty_struct *tty, struct file *file) { int card_status; struct bcm_vk *vk; struct bcm_vk_tty *vktty; int index; /* initialize the pointer in case something fails */ tty->driver_data = NULL; vk = (struct bcm_vk *)dev_get_drvdata(tty->dev); index = tty->index; if (index >= BCM_VK_NUM_TTY) return -EINVAL; vktty = &vk->tty[index]; vktty->pid = task_pid_nr(current); vktty->to_offset = TO_TTYK_BASE(index); vktty->from_offset = FROM_TTYK_BASE(index); /* Do not allow tty device to be opened if tty on card not ready */ card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS); if (BCM_VK_INTF_IS_DOWN(card_status) || ((card_status & BIT(index)) == 0)) return -EBUSY; /* * Get shadow registers of the buffer sizes and the "to" write offset * and "from" read offset */ vktty->to_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, to)); vktty->wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, to)); vktty->from_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, from)); vktty->rd = vkread32(vk, BAR_1, VK_BAR_CHAN_RD(vktty, from)); vktty->is_opened = true; if (tty->count == 1 && !vktty->irq_enabled) { timer_setup(&vk->serial_timer, bcm_vk_tty_poll, 0); mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE); } return 0; } static void bcm_vk_tty_close(struct tty_struct *tty, struct file *file) { struct bcm_vk *vk = dev_get_drvdata(tty->dev); if (tty->index >= BCM_VK_NUM_TTY) return; vk->tty[tty->index].is_opened = false; if (tty->count == 1) del_timer_sync(&vk->serial_timer); } static void bcm_vk_tty_doorbell(struct bcm_vk *vk, u32 db_val) { vkwrite32(vk, db_val, BAR_0, VK_BAR0_REGSEG_DB_BASE + VK_BAR0_REGSEG_TTY_DB_OFFSET); } static ssize_t bcm_vk_tty_write(struct tty_struct *tty, const u8 *buffer, size_t count) { int index; struct bcm_vk *vk; struct bcm_vk_tty *vktty; size_t i; index = tty->index; vk = dev_get_drvdata(tty->dev); vktty = &vk->tty[index]; /* Simple write each byte to circular buffer */ for (i = 0; i < count; i++) { vkwrite8(vk, buffer[i], BAR_1, VK_BAR_CHAN_DATA(vktty, to, vktty->wr)); vktty->wr++; if (vktty->wr >= vktty->to_size) vktty->wr = 0; } /* Update write offset from shadow register to card */ vkwrite32(vk, vktty->wr, BAR_1, VK_BAR_CHAN_WR(vktty, to)); bcm_vk_tty_doorbell(vk, 0); return count; } static unsigned int bcm_vk_tty_write_room(struct tty_struct *tty) { struct bcm_vk *vk = dev_get_drvdata(tty->dev); return vk->tty[tty->index].to_size - 1; } static const struct tty_operations serial_ops = { .open = bcm_vk_tty_open, .close = bcm_vk_tty_close, .write = bcm_vk_tty_write, .write_room = bcm_vk_tty_write_room, }; int bcm_vk_tty_init(struct bcm_vk *vk, char *name) { int i; int err; struct tty_driver *tty_drv; struct device *dev = &vk->pdev->dev; tty_drv = tty_alloc_driver (BCM_VK_NUM_TTY, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); if (IS_ERR(tty_drv)) return PTR_ERR(tty_drv); /* Save struct tty_driver for uninstalling the device */ vk->tty_drv = tty_drv; /* initialize the tty driver */ tty_drv->driver_name = KBUILD_MODNAME; tty_drv->name = kstrdup(name, GFP_KERNEL); if (!tty_drv->name) { err = -ENOMEM; goto err_tty_driver_kref_put; } tty_drv->type = TTY_DRIVER_TYPE_SERIAL; tty_drv->subtype = SERIAL_TYPE_NORMAL; tty_drv->init_termios = tty_std_termios; tty_set_operations(tty_drv, &serial_ops); /* register the tty driver */ err = tty_register_driver(tty_drv); if (err) { dev_err(dev, "tty_register_driver failed\n"); goto err_kfree_tty_name; } for (i = 0; i < BCM_VK_NUM_TTY; i++) { struct device *tty_dev; tty_port_init(&vk->tty[i].port); tty_dev = tty_port_register_device_attr(&vk->tty[i].port, tty_drv, i, dev, vk, NULL); if (IS_ERR(tty_dev)) { err = PTR_ERR(tty_dev); goto unwind; } vk->tty[i].is_opened = false; } INIT_WORK(&vk->tty_wq_work, bcm_vk_tty_wq_handler); vk->tty_wq_thread = create_singlethread_workqueue("tty"); if (!vk->tty_wq_thread) { dev_err(dev, "Fail to create tty workqueue thread\n"); err = -ENOMEM; goto unwind; } return 0; unwind: while (--i >= 0) tty_port_unregister_device(&vk->tty[i].port, tty_drv, i); tty_unregister_driver(tty_drv); err_kfree_tty_name: kfree(tty_drv->name); tty_drv->name = NULL; err_tty_driver_kref_put: tty_driver_kref_put(tty_drv); return err; } void bcm_vk_tty_exit(struct bcm_vk *vk) { int i; del_timer_sync(&vk->serial_timer); for (i = 0; i < BCM_VK_NUM_TTY; ++i) { tty_port_unregister_device(&vk->tty[i].port, vk->tty_drv, i); tty_port_destroy(&vk->tty[i].port); } tty_unregister_driver(vk->tty_drv); kfree(vk->tty_drv->name); vk->tty_drv->name = NULL; tty_driver_kref_put(vk->tty_drv); } void bcm_vk_tty_terminate_tty_user(struct bcm_vk *vk) { struct bcm_vk_tty *vktty; int i; for (i = 0; i < BCM_VK_NUM_TTY; ++i) { vktty = &vk->tty[i]; if (vktty->pid) kill_pid(find_vpid(vktty->pid), SIGKILL, 1); } } void bcm_vk_tty_wq_exit(struct bcm_vk *vk) { cancel_work_sync(&vk->tty_wq_work); destroy_workqueue(vk->tty_wq_thread); } |