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 = from_timer(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 | del_timer_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 | del_timer_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 | |