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