1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * KOBIL USB Smart Card Terminal Driver |
4 | * |
5 | * Copyright (C) 2002 KOBIL Systems GmbH |
6 | * Author: Thomas Wahrenbruch |
7 | * |
8 | * Contact: linuxusb@kobil.de |
9 | * |
10 | * This program is largely derived from work by the linux-usb group |
11 | * and associated source files. Please see the usb/serial files for |
12 | * individual credits and copyrights. |
13 | * |
14 | * Thanks to Greg Kroah-Hartman (greg@kroah.com) for his help and |
15 | * patience. |
16 | * |
17 | * Supported readers: USB TWIN, KAAN Standard Plus and SecOVID Reader Plus |
18 | * (Adapter K), B1 Professional and KAAN Professional (Adapter B) |
19 | */ |
20 | |
21 | |
22 | #include <linux/kernel.h> |
23 | #include <linux/errno.h> |
24 | #include <linux/slab.h> |
25 | #include <linux/tty.h> |
26 | #include <linux/tty_driver.h> |
27 | #include <linux/tty_flip.h> |
28 | #include <linux/module.h> |
29 | #include <linux/spinlock.h> |
30 | #include <linux/uaccess.h> |
31 | #include <linux/usb.h> |
32 | #include <linux/usb/serial.h> |
33 | #include <linux/ioctl.h> |
34 | #include "kobil_sct.h" |
35 | |
36 | #define DRIVER_AUTHOR "KOBIL Systems GmbH - http://www.kobil.com" |
37 | #define DRIVER_DESC "KOBIL USB Smart Card Terminal Driver (experimental)" |
38 | |
39 | #define KOBIL_VENDOR_ID 0x0D46 |
40 | #define KOBIL_ADAPTER_B_PRODUCT_ID 0x2011 |
41 | #define KOBIL_ADAPTER_K_PRODUCT_ID 0x2012 |
42 | #define KOBIL_USBTWIN_PRODUCT_ID 0x0078 |
43 | #define KOBIL_KAAN_SIM_PRODUCT_ID 0x0081 |
44 | |
45 | #define KOBIL_TIMEOUT 500 |
46 | #define KOBIL_BUF_LENGTH 300 |
47 | |
48 | |
49 | /* Function prototypes */ |
50 | static int kobil_port_probe(struct usb_serial_port *probe); |
51 | static void kobil_port_remove(struct usb_serial_port *probe); |
52 | static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port); |
53 | static void kobil_close(struct usb_serial_port *port); |
54 | static int kobil_write(struct tty_struct *tty, struct usb_serial_port *port, |
55 | const unsigned char *buf, int count); |
56 | static unsigned int kobil_write_room(struct tty_struct *tty); |
57 | static int kobil_ioctl(struct tty_struct *tty, |
58 | unsigned int cmd, unsigned long arg); |
59 | static int kobil_tiocmget(struct tty_struct *tty); |
60 | static int kobil_tiocmset(struct tty_struct *tty, |
61 | unsigned int set, unsigned int clear); |
62 | static void kobil_read_int_callback(struct urb *urb); |
63 | static void kobil_write_int_callback(struct urb *urb); |
64 | static void kobil_set_termios(struct tty_struct *tty, |
65 | struct usb_serial_port *port, |
66 | const struct ktermios *old); |
67 | static void kobil_init_termios(struct tty_struct *tty); |
68 | |
69 | static const struct usb_device_id id_table[] = { |
70 | { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_B_PRODUCT_ID) }, |
71 | { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_ADAPTER_K_PRODUCT_ID) }, |
72 | { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_USBTWIN_PRODUCT_ID) }, |
73 | { USB_DEVICE(KOBIL_VENDOR_ID, KOBIL_KAAN_SIM_PRODUCT_ID) }, |
74 | { } /* Terminating entry */ |
75 | }; |
76 | MODULE_DEVICE_TABLE(usb, id_table); |
77 | |
78 | static struct usb_serial_driver kobil_device = { |
79 | .driver = { |
80 | .owner = THIS_MODULE, |
81 | .name = "kobil" , |
82 | }, |
83 | .description = "KOBIL USB smart card terminal" , |
84 | .id_table = id_table, |
85 | .num_ports = 1, |
86 | .num_interrupt_out = 1, |
87 | .port_probe = kobil_port_probe, |
88 | .port_remove = kobil_port_remove, |
89 | .ioctl = kobil_ioctl, |
90 | .set_termios = kobil_set_termios, |
91 | .init_termios = kobil_init_termios, |
92 | .tiocmget = kobil_tiocmget, |
93 | .tiocmset = kobil_tiocmset, |
94 | .open = kobil_open, |
95 | .close = kobil_close, |
96 | .write = kobil_write, |
97 | .write_room = kobil_write_room, |
98 | .read_int_callback = kobil_read_int_callback, |
99 | .write_int_callback = kobil_write_int_callback, |
100 | }; |
101 | |
102 | static struct usb_serial_driver * const serial_drivers[] = { |
103 | &kobil_device, NULL |
104 | }; |
105 | |
106 | struct kobil_private { |
107 | unsigned char buf[KOBIL_BUF_LENGTH]; /* buffer for the APDU to send */ |
108 | int filled; /* index of the last char in buf */ |
109 | int cur_pos; /* index of the next char to send in buf */ |
110 | __u16 device_type; |
111 | }; |
112 | |
113 | |
114 | static int kobil_port_probe(struct usb_serial_port *port) |
115 | { |
116 | struct usb_serial *serial = port->serial; |
117 | struct kobil_private *priv; |
118 | |
119 | priv = kmalloc(size: sizeof(struct kobil_private), GFP_KERNEL); |
120 | if (!priv) |
121 | return -ENOMEM; |
122 | |
123 | priv->filled = 0; |
124 | priv->cur_pos = 0; |
125 | priv->device_type = le16_to_cpu(serial->dev->descriptor.idProduct); |
126 | |
127 | switch (priv->device_type) { |
128 | case KOBIL_ADAPTER_B_PRODUCT_ID: |
129 | dev_dbg(&serial->dev->dev, "KOBIL B1 PRO / KAAN PRO detected\n" ); |
130 | break; |
131 | case KOBIL_ADAPTER_K_PRODUCT_ID: |
132 | dev_dbg(&serial->dev->dev, "KOBIL KAAN Standard Plus / SecOVID Reader Plus detected\n" ); |
133 | break; |
134 | case KOBIL_USBTWIN_PRODUCT_ID: |
135 | dev_dbg(&serial->dev->dev, "KOBIL USBTWIN detected\n" ); |
136 | break; |
137 | case KOBIL_KAAN_SIM_PRODUCT_ID: |
138 | dev_dbg(&serial->dev->dev, "KOBIL KAAN SIM detected\n" ); |
139 | break; |
140 | } |
141 | usb_set_serial_port_data(port, data: priv); |
142 | |
143 | return 0; |
144 | } |
145 | |
146 | |
147 | static void kobil_port_remove(struct usb_serial_port *port) |
148 | { |
149 | struct kobil_private *priv; |
150 | |
151 | priv = usb_get_serial_port_data(port); |
152 | kfree(objp: priv); |
153 | } |
154 | |
155 | static void kobil_init_termios(struct tty_struct *tty) |
156 | { |
157 | /* Default to echo off and other sane device settings */ |
158 | tty->termios.c_lflag = 0; |
159 | tty->termios.c_iflag &= ~(ISIG | ICANON | ECHO | IEXTEN | XCASE); |
160 | tty->termios.c_iflag |= IGNBRK | IGNPAR | IXOFF; |
161 | /* do NOT translate CR to CR-NL (0x0A -> 0x0A 0x0D) */ |
162 | tty->termios.c_oflag &= ~ONLCR; |
163 | } |
164 | |
165 | static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port) |
166 | { |
167 | struct device *dev = &port->dev; |
168 | int result = 0; |
169 | struct kobil_private *priv; |
170 | unsigned char *transfer_buffer; |
171 | int transfer_buffer_length = 8; |
172 | |
173 | priv = usb_get_serial_port_data(port); |
174 | |
175 | /* allocate memory for transfer buffer */ |
176 | transfer_buffer = kzalloc(size: transfer_buffer_length, GFP_KERNEL); |
177 | if (!transfer_buffer) |
178 | return -ENOMEM; |
179 | |
180 | /* get hardware version */ |
181 | result = usb_control_msg(dev: port->serial->dev, |
182 | usb_rcvctrlpipe(port->serial->dev, 0), |
183 | SUSBCRequest_GetMisc, |
184 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN, |
185 | SUSBCR_MSC_GetHWVersion, |
186 | index: 0, |
187 | data: transfer_buffer, |
188 | size: transfer_buffer_length, |
189 | KOBIL_TIMEOUT |
190 | ); |
191 | dev_dbg(dev, "%s - Send get_HW_version URB returns: %i\n" , __func__, result); |
192 | if (result >= 3) { |
193 | dev_dbg(dev, "Hardware version: %i.%i.%i\n" , transfer_buffer[0], |
194 | transfer_buffer[1], transfer_buffer[2]); |
195 | } |
196 | |
197 | /* get firmware version */ |
198 | result = usb_control_msg(dev: port->serial->dev, |
199 | usb_rcvctrlpipe(port->serial->dev, 0), |
200 | SUSBCRequest_GetMisc, |
201 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN, |
202 | SUSBCR_MSC_GetFWVersion, |
203 | index: 0, |
204 | data: transfer_buffer, |
205 | size: transfer_buffer_length, |
206 | KOBIL_TIMEOUT |
207 | ); |
208 | dev_dbg(dev, "%s - Send get_FW_version URB returns: %i\n" , __func__, result); |
209 | if (result >= 3) { |
210 | dev_dbg(dev, "Firmware version: %i.%i.%i\n" , transfer_buffer[0], |
211 | transfer_buffer[1], transfer_buffer[2]); |
212 | } |
213 | |
214 | if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID || |
215 | priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) { |
216 | /* Setting Baudrate, Parity and Stopbits */ |
217 | result = usb_control_msg(dev: port->serial->dev, |
218 | usb_sndctrlpipe(port->serial->dev, 0), |
219 | SUSBCRequest_SetBaudRateParityAndStopBits, |
220 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, |
221 | SUSBCR_SBR_9600 | SUSBCR_SPASB_EvenParity | |
222 | SUSBCR_SPASB_1StopBit, |
223 | index: 0, |
224 | NULL, |
225 | size: 0, |
226 | KOBIL_TIMEOUT |
227 | ); |
228 | dev_dbg(dev, "%s - Send set_baudrate URB returns: %i\n" , __func__, result); |
229 | |
230 | /* reset all queues */ |
231 | result = usb_control_msg(dev: port->serial->dev, |
232 | usb_sndctrlpipe(port->serial->dev, 0), |
233 | SUSBCRequest_Misc, |
234 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, |
235 | SUSBCR_MSC_ResetAllQueues, |
236 | index: 0, |
237 | NULL, |
238 | size: 0, |
239 | KOBIL_TIMEOUT |
240 | ); |
241 | dev_dbg(dev, "%s - Send reset_all_queues URB returns: %i\n" , __func__, result); |
242 | } |
243 | if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || |
244 | priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID || |
245 | priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { |
246 | /* start reading (Adapter B 'cause PNP string) */ |
247 | result = usb_submit_urb(urb: port->interrupt_in_urb, GFP_KERNEL); |
248 | dev_dbg(dev, "%s - Send read URB returns: %i\n" , __func__, result); |
249 | } |
250 | |
251 | kfree(objp: transfer_buffer); |
252 | return 0; |
253 | } |
254 | |
255 | |
256 | static void kobil_close(struct usb_serial_port *port) |
257 | { |
258 | /* FIXME: Add rts/dtr methods */ |
259 | usb_kill_urb(urb: port->interrupt_out_urb); |
260 | usb_kill_urb(urb: port->interrupt_in_urb); |
261 | } |
262 | |
263 | |
264 | static void kobil_read_int_callback(struct urb *urb) |
265 | { |
266 | int result; |
267 | struct usb_serial_port *port = urb->context; |
268 | unsigned char *data = urb->transfer_buffer; |
269 | int status = urb->status; |
270 | |
271 | if (status) { |
272 | dev_dbg(&port->dev, "%s - Read int status not zero: %d\n" , __func__, status); |
273 | return; |
274 | } |
275 | |
276 | if (urb->actual_length) { |
277 | usb_serial_debug_data(dev: &port->dev, function: __func__, size: urb->actual_length, |
278 | data); |
279 | tty_insert_flip_string(port: &port->port, chars: data, size: urb->actual_length); |
280 | tty_flip_buffer_push(port: &port->port); |
281 | } |
282 | |
283 | result = usb_submit_urb(urb: port->interrupt_in_urb, GFP_ATOMIC); |
284 | dev_dbg(&port->dev, "%s - Send read URB returns: %i\n" , __func__, result); |
285 | } |
286 | |
287 | |
288 | static void kobil_write_int_callback(struct urb *urb) |
289 | { |
290 | } |
291 | |
292 | |
293 | static int kobil_write(struct tty_struct *tty, struct usb_serial_port *port, |
294 | const unsigned char *buf, int count) |
295 | { |
296 | int length = 0; |
297 | int result = 0; |
298 | int todo = 0; |
299 | struct kobil_private *priv; |
300 | |
301 | if (count == 0) { |
302 | dev_dbg(&port->dev, "%s - write request of 0 bytes\n" , __func__); |
303 | return 0; |
304 | } |
305 | |
306 | priv = usb_get_serial_port_data(port); |
307 | |
308 | if (count > (KOBIL_BUF_LENGTH - priv->filled)) { |
309 | dev_dbg(&port->dev, "%s - Error: write request bigger than buffer size\n" , __func__); |
310 | return -ENOMEM; |
311 | } |
312 | |
313 | /* Copy data to buffer */ |
314 | memcpy(priv->buf + priv->filled, buf, count); |
315 | usb_serial_debug_data(dev: &port->dev, function: __func__, size: count, data: priv->buf + priv->filled); |
316 | priv->filled = priv->filled + count; |
317 | |
318 | /* only send complete block. TWIN, KAAN SIM and adapter K |
319 | use the same protocol. */ |
320 | if (((priv->device_type != KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 2) && (priv->filled >= (priv->buf[1] + 3))) || |
321 | ((priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) && (priv->filled > 3) && (priv->filled >= (priv->buf[2] + 4)))) { |
322 | /* stop reading (except TWIN and KAAN SIM) */ |
323 | if ((priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) |
324 | || (priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID)) |
325 | usb_kill_urb(urb: port->interrupt_in_urb); |
326 | |
327 | todo = priv->filled - priv->cur_pos; |
328 | |
329 | while (todo > 0) { |
330 | /* max 8 byte in one urb (endpoint size) */ |
331 | length = min(todo, port->interrupt_out_size); |
332 | /* copy data to transfer buffer */ |
333 | memcpy(port->interrupt_out_buffer, |
334 | priv->buf + priv->cur_pos, length); |
335 | port->interrupt_out_urb->transfer_buffer_length = length; |
336 | |
337 | priv->cur_pos = priv->cur_pos + length; |
338 | result = usb_submit_urb(urb: port->interrupt_out_urb, |
339 | GFP_ATOMIC); |
340 | dev_dbg(&port->dev, "%s - Send write URB returns: %i\n" , __func__, result); |
341 | todo = priv->filled - priv->cur_pos; |
342 | |
343 | if (todo > 0) |
344 | msleep(msecs: 24); |
345 | } |
346 | |
347 | priv->filled = 0; |
348 | priv->cur_pos = 0; |
349 | |
350 | /* start reading (except TWIN and KAAN SIM) */ |
351 | if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID || |
352 | priv->device_type == KOBIL_ADAPTER_K_PRODUCT_ID) { |
353 | result = usb_submit_urb(urb: port->interrupt_in_urb, |
354 | GFP_ATOMIC); |
355 | dev_dbg(&port->dev, "%s - Send read URB returns: %i\n" , __func__, result); |
356 | } |
357 | } |
358 | return count; |
359 | } |
360 | |
361 | |
362 | static unsigned int kobil_write_room(struct tty_struct *tty) |
363 | { |
364 | /* FIXME */ |
365 | return 8; |
366 | } |
367 | |
368 | |
369 | static int kobil_tiocmget(struct tty_struct *tty) |
370 | { |
371 | struct usb_serial_port *port = tty->driver_data; |
372 | struct kobil_private *priv; |
373 | int result; |
374 | unsigned char *transfer_buffer; |
375 | int transfer_buffer_length = 8; |
376 | |
377 | priv = usb_get_serial_port_data(port); |
378 | if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID |
379 | || priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { |
380 | /* This device doesn't support ioctl calls */ |
381 | return -EINVAL; |
382 | } |
383 | |
384 | /* allocate memory for transfer buffer */ |
385 | transfer_buffer = kzalloc(size: transfer_buffer_length, GFP_KERNEL); |
386 | if (!transfer_buffer) |
387 | return -ENOMEM; |
388 | |
389 | result = usb_control_msg(dev: port->serial->dev, |
390 | usb_rcvctrlpipe(port->serial->dev, 0), |
391 | SUSBCRequest_GetStatusLineState, |
392 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN, |
393 | value: 0, |
394 | index: 0, |
395 | data: transfer_buffer, |
396 | size: transfer_buffer_length, |
397 | KOBIL_TIMEOUT); |
398 | |
399 | dev_dbg(&port->dev, "Send get_status_line_state URB returns: %i\n" , |
400 | result); |
401 | if (result < 1) { |
402 | if (result >= 0) |
403 | result = -EIO; |
404 | goto out_free; |
405 | } |
406 | |
407 | dev_dbg(&port->dev, "Statusline: %02x\n" , transfer_buffer[0]); |
408 | |
409 | result = 0; |
410 | if ((transfer_buffer[0] & SUSBCR_GSL_DSR) != 0) |
411 | result = TIOCM_DSR; |
412 | out_free: |
413 | kfree(objp: transfer_buffer); |
414 | return result; |
415 | } |
416 | |
417 | static int kobil_tiocmset(struct tty_struct *tty, |
418 | unsigned int set, unsigned int clear) |
419 | { |
420 | struct usb_serial_port *port = tty->driver_data; |
421 | struct device *dev = &port->dev; |
422 | struct kobil_private *priv; |
423 | int result; |
424 | int dtr = 0; |
425 | int rts = 0; |
426 | |
427 | /* FIXME: locking ? */ |
428 | priv = usb_get_serial_port_data(port); |
429 | if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID |
430 | || priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { |
431 | /* This device doesn't support ioctl calls */ |
432 | return -EINVAL; |
433 | } |
434 | |
435 | if (set & TIOCM_RTS) |
436 | rts = 1; |
437 | if (set & TIOCM_DTR) |
438 | dtr = 1; |
439 | if (clear & TIOCM_RTS) |
440 | rts = 0; |
441 | if (clear & TIOCM_DTR) |
442 | dtr = 0; |
443 | |
444 | if (priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) { |
445 | if (dtr != 0) |
446 | dev_dbg(dev, "%s - Setting DTR\n" , __func__); |
447 | else |
448 | dev_dbg(dev, "%s - Clearing DTR\n" , __func__); |
449 | result = usb_control_msg(dev: port->serial->dev, |
450 | usb_sndctrlpipe(port->serial->dev, 0), |
451 | SUSBCRequest_SetStatusLinesOrQueues, |
452 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, |
453 | value: ((dtr != 0) ? SUSBCR_SSL_SETDTR : SUSBCR_SSL_CLRDTR), |
454 | index: 0, |
455 | NULL, |
456 | size: 0, |
457 | KOBIL_TIMEOUT); |
458 | } else { |
459 | if (rts != 0) |
460 | dev_dbg(dev, "%s - Setting RTS\n" , __func__); |
461 | else |
462 | dev_dbg(dev, "%s - Clearing RTS\n" , __func__); |
463 | result = usb_control_msg(dev: port->serial->dev, |
464 | usb_sndctrlpipe(port->serial->dev, 0), |
465 | SUSBCRequest_SetStatusLinesOrQueues, |
466 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, |
467 | value: ((rts != 0) ? SUSBCR_SSL_SETRTS : SUSBCR_SSL_CLRRTS), |
468 | index: 0, |
469 | NULL, |
470 | size: 0, |
471 | KOBIL_TIMEOUT); |
472 | } |
473 | dev_dbg(dev, "%s - Send set_status_line URB returns: %i\n" , __func__, result); |
474 | return (result < 0) ? result : 0; |
475 | } |
476 | |
477 | static void kobil_set_termios(struct tty_struct *tty, |
478 | struct usb_serial_port *port, |
479 | const struct ktermios *old) |
480 | { |
481 | struct kobil_private *priv; |
482 | int result; |
483 | unsigned short urb_val = 0; |
484 | int c_cflag = tty->termios.c_cflag; |
485 | speed_t speed; |
486 | |
487 | priv = usb_get_serial_port_data(port); |
488 | if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || |
489 | priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) { |
490 | /* This device doesn't support ioctl calls */ |
491 | tty_termios_copy_hw(new: &tty->termios, old); |
492 | return; |
493 | } |
494 | |
495 | speed = tty_get_baud_rate(tty); |
496 | switch (speed) { |
497 | case 1200: |
498 | urb_val = SUSBCR_SBR_1200; |
499 | break; |
500 | default: |
501 | speed = 9600; |
502 | fallthrough; |
503 | case 9600: |
504 | urb_val = SUSBCR_SBR_9600; |
505 | break; |
506 | } |
507 | urb_val |= (c_cflag & CSTOPB) ? SUSBCR_SPASB_2StopBits : |
508 | SUSBCR_SPASB_1StopBit; |
509 | if (c_cflag & PARENB) { |
510 | if (c_cflag & PARODD) |
511 | urb_val |= SUSBCR_SPASB_OddParity; |
512 | else |
513 | urb_val |= SUSBCR_SPASB_EvenParity; |
514 | } else |
515 | urb_val |= SUSBCR_SPASB_NoParity; |
516 | tty->termios.c_cflag &= ~CMSPAR; |
517 | tty_encode_baud_rate(tty, ibaud: speed, obaud: speed); |
518 | |
519 | result = usb_control_msg(dev: port->serial->dev, |
520 | usb_sndctrlpipe(port->serial->dev, 0), |
521 | SUSBCRequest_SetBaudRateParityAndStopBits, |
522 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, |
523 | value: urb_val, |
524 | index: 0, |
525 | NULL, |
526 | size: 0, |
527 | KOBIL_TIMEOUT |
528 | ); |
529 | if (result) { |
530 | dev_err(&port->dev, "failed to update line settings: %d\n" , |
531 | result); |
532 | } |
533 | } |
534 | |
535 | static int kobil_ioctl(struct tty_struct *tty, |
536 | unsigned int cmd, unsigned long arg) |
537 | { |
538 | struct usb_serial_port *port = tty->driver_data; |
539 | struct kobil_private *priv = usb_get_serial_port_data(port); |
540 | int result; |
541 | |
542 | if (priv->device_type == KOBIL_USBTWIN_PRODUCT_ID || |
543 | priv->device_type == KOBIL_KAAN_SIM_PRODUCT_ID) |
544 | /* This device doesn't support ioctl calls */ |
545 | return -ENOIOCTLCMD; |
546 | |
547 | switch (cmd) { |
548 | case TCFLSH: |
549 | result = usb_control_msg(dev: port->serial->dev, |
550 | usb_sndctrlpipe(port->serial->dev, 0), |
551 | SUSBCRequest_Misc, |
552 | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, |
553 | SUSBCR_MSC_ResetAllQueues, |
554 | index: 0, |
555 | NULL, |
556 | size: 0, |
557 | KOBIL_TIMEOUT |
558 | ); |
559 | |
560 | dev_dbg(&port->dev, |
561 | "%s - Send reset_all_queues (FLUSH) URB returns: %i\n" , |
562 | __func__, result); |
563 | return (result < 0) ? -EIO: 0; |
564 | default: |
565 | return -ENOIOCTLCMD; |
566 | } |
567 | } |
568 | |
569 | module_usb_serial_driver(serial_drivers, id_table); |
570 | |
571 | MODULE_AUTHOR(DRIVER_AUTHOR); |
572 | MODULE_DESCRIPTION(DRIVER_DESC); |
573 | MODULE_LICENSE("GPL" ); |
574 | |