From 95099d4622f8cb224d94e314c7a8e0df60b13f87 Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Sat, 09 Dec 2023 08:38:01 +0000
Subject: [PATCH] enable docker ppp
---
kernel/drivers/usb/serial/cp210x.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 351 insertions(+), 27 deletions(-)
diff --git a/kernel/drivers/usb/serial/cp210x.c b/kernel/drivers/usb/serial/cp210x.c
index 452c508..8a4a0d4 100644
--- a/kernel/drivers/usb/serial/cp210x.c
+++ b/kernel/drivers/usb/serial/cp210x.c
@@ -50,6 +50,9 @@
static int cp210x_port_probe(struct usb_serial_port *);
static int cp210x_port_remove(struct usb_serial_port *);
static void cp210x_dtr_rts(struct usb_serial_port *p, int on);
+static void cp210x_process_read_urb(struct urb *urb);
+static void cp210x_enable_event_mode(struct usb_serial_port *port);
+static void cp210x_disable_event_mode(struct usb_serial_port *port);
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x0404, 0x034C) }, /* NCR Retail IO Box */
@@ -131,6 +134,7 @@
{ USB_DEVICE(0x10C4, 0x83AA) }, /* Mark-10 Digital Force Gauge */
{ USB_DEVICE(0x10C4, 0x83D8) }, /* DekTec DTA Plus VHF/UHF Booster/Attenuator */
{ USB_DEVICE(0x10C4, 0x8411) }, /* Kyocera GPS Module */
+ { USB_DEVICE(0x10C4, 0x8414) }, /* Decagon USB Cable Adapter */
{ USB_DEVICE(0x10C4, 0x8418) }, /* IRZ Automation Teleport SG-10 GSM/GPRS Modem */
{ USB_DEVICE(0x10C4, 0x846E) }, /* BEI USB Sensor Interface (VCP) */
{ USB_DEVICE(0x10C4, 0x8470) }, /* Juniper Networks BX Series System Console */
@@ -195,6 +199,10 @@
{ USB_DEVICE(0x16DC, 0x0015) }, /* W-IE-NE-R Plein & Baus GmbH CML Control, Monitoring and Data Logger */
{ USB_DEVICE(0x17A8, 0x0001) }, /* Kamstrup Optical Eye/3-wire */
{ USB_DEVICE(0x17A8, 0x0005) }, /* Kamstrup M-Bus Master MultiPort 250D */
+ { USB_DEVICE(0x17A8, 0x0011) }, /* Kamstrup 444 MHz RF sniffer */
+ { USB_DEVICE(0x17A8, 0x0013) }, /* Kamstrup 870 MHz RF sniffer */
+ { USB_DEVICE(0x17A8, 0x0101) }, /* Kamstrup 868 MHz wM-Bus C-Mode Meter Reader (Int Ant) */
+ { USB_DEVICE(0x17A8, 0x0102) }, /* Kamstrup 868 MHz wM-Bus C-Mode Meter Reader (Ext Ant) */
{ USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */
{ USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */
{ USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */
@@ -257,13 +265,27 @@
u8 gpio_input;
#endif
u8 partnum;
+ speed_t min_speed;
speed_t max_speed;
bool use_actual_rate;
+ bool no_event_mode;
+};
+
+enum cp210x_event_state {
+ ES_DATA,
+ ES_ESCAPE,
+ ES_LSR,
+ ES_LSR_DATA_0,
+ ES_LSR_DATA_1,
+ ES_MSR
};
struct cp210x_port_private {
- __u8 bInterfaceNumber;
+ u8 bInterfaceNumber;
bool has_swapped_line_ctl;
+ bool event_mode;
+ enum cp210x_event_state event_state;
+ u8 lsr;
};
static struct usb_serial_driver cp210x_device = {
@@ -284,12 +306,14 @@
.unthrottle = usb_serial_generic_unthrottle,
.tiocmget = cp210x_tiocmget,
.tiocmset = cp210x_tiocmset,
+ .get_icount = usb_serial_generic_get_icount,
.attach = cp210x_attach,
.disconnect = cp210x_disconnect,
.release = cp210x_release,
.port_probe = cp210x_port_probe,
.port_remove = cp210x_port_remove,
- .dtr_rts = cp210x_dtr_rts
+ .dtr_rts = cp210x_dtr_rts,
+ .process_read_urb = cp210x_process_read_urb,
};
static struct usb_serial_driver * const serial_drivers[] = {
@@ -411,13 +435,22 @@
*/
#define PURGE_ALL 0x000f
+/* CP210X_EMBED_EVENTS */
+#define CP210X_ESCCHAR 0xec
+
+#define CP210X_LSR_OVERRUN BIT(1)
+#define CP210X_LSR_PARITY BIT(2)
+#define CP210X_LSR_FRAME BIT(3)
+#define CP210X_LSR_BREAK BIT(4)
+
+
/* CP210X_GET_FLOW/CP210X_SET_FLOW read/write these 0x10 bytes */
struct cp210x_flow_ctl {
__le32 ulControlHandshake;
__le32 ulFlowReplace;
__le32 ulXonLimit;
__le32 ulXoffLimit;
-} __packed;
+};
/* cp210x_flow_ctl::ulControlHandshake */
#define CP210X_SERIAL_DTR_MASK GENMASK(1, 0)
@@ -451,16 +484,16 @@
struct cp210x_pin_mode {
u8 eci;
u8 sci;
-} __packed;
+};
#define CP210X_PIN_MODE_MODEM 0
#define CP210X_PIN_MODE_GPIO BIT(0)
/*
- * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xf bytes.
- * Structure needs padding due to unused/unspecified bytes.
+ * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xf bytes
+ * on a CP2105 chip. Structure needs padding due to unused/unspecified bytes.
*/
-struct cp210x_config {
+struct cp210x_dual_port_config {
__le16 gpio_mode;
u8 __pad0[2];
__le16 reset_state;
@@ -471,6 +504,19 @@
u8 device_cfg;
} __packed;
+/*
+ * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xd bytes
+ * on a CP2104 chip. Structure needs padding due to unused/unspecified bytes.
+ */
+struct cp210x_single_port_config {
+ __le16 gpio_mode;
+ u8 __pad0[2];
+ __le16 reset_state;
+ u8 __pad1[4];
+ __le16 suspend_state;
+ u8 device_cfg;
+} __packed;
+
/* GPIO modes */
#define CP210X_SCI_GPIO_MODE_OFFSET 9
#define CP210X_SCI_GPIO_MODE_MASK GENMASK(11, 9)
@@ -478,10 +524,18 @@
#define CP210X_ECI_GPIO_MODE_OFFSET 2
#define CP210X_ECI_GPIO_MODE_MASK GENMASK(3, 2)
+#define CP210X_GPIO_MODE_OFFSET 8
+#define CP210X_GPIO_MODE_MASK GENMASK(11, 8)
+
/* CP2105 port configuration values */
#define CP2105_GPIO0_TXLED_MODE BIT(0)
#define CP2105_GPIO1_RXLED_MODE BIT(1)
#define CP2105_GPIO1_RS485_MODE BIT(2)
+
+/* CP2104 port configuration values */
+#define CP2104_GPIO0_TXLED_MODE BIT(0)
+#define CP2104_GPIO1_RXLED_MODE BIT(1)
+#define CP2104_GPIO2_RS485_MODE BIT(2)
/* CP2102N configuration array indices */
#define CP210X_2NCONFIG_CONFIG_VERSION_IDX 2
@@ -499,7 +553,7 @@
struct cp210x_gpio_write {
u8 mask;
u8 state;
-} __packed;
+};
/*
* Helper to get interface number when we only have struct usb_serial.
@@ -802,6 +856,7 @@
static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
int result;
result = cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_ENABLE);
@@ -813,21 +868,144 @@
/* Configure the termios structure */
cp210x_get_termios(tty, port);
- /* The baud rate must be initialised on cp2104 */
- if (tty)
+ if (tty) {
+ /* The baud rate must be initialised on cp2104 */
cp210x_change_speed(tty, port, NULL);
- return usb_serial_generic_open(tty, port);
+ if (I_INPCK(tty))
+ cp210x_enable_event_mode(port);
+ }
+
+ result = usb_serial_generic_open(tty, port);
+ if (result)
+ goto err_disable;
+
+ return 0;
+
+err_disable:
+ cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE);
+ port_priv->event_mode = false;
+
+ return result;
}
static void cp210x_close(struct usb_serial_port *port)
{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+
usb_serial_generic_close(port);
/* Clear both queues; cp2108 needs this to avoid an occasional hang */
cp210x_write_u16_reg(port, CP210X_PURGE, PURGE_ALL);
cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE);
+
+ /* Disabling the interface disables event-insertion mode. */
+ port_priv->event_mode = false;
+}
+
+static void cp210x_process_lsr(struct usb_serial_port *port, unsigned char lsr, char *flag)
+{
+ if (lsr & CP210X_LSR_BREAK) {
+ port->icount.brk++;
+ *flag = TTY_BREAK;
+ } else if (lsr & CP210X_LSR_PARITY) {
+ port->icount.parity++;
+ *flag = TTY_PARITY;
+ } else if (lsr & CP210X_LSR_FRAME) {
+ port->icount.frame++;
+ *flag = TTY_FRAME;
+ }
+
+ if (lsr & CP210X_LSR_OVERRUN) {
+ port->icount.overrun++;
+ tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+ }
+}
+
+static bool cp210x_process_char(struct usb_serial_port *port, unsigned char *ch, char *flag)
+{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+
+ switch (port_priv->event_state) {
+ case ES_DATA:
+ if (*ch == CP210X_ESCCHAR) {
+ port_priv->event_state = ES_ESCAPE;
+ break;
+ }
+ return false;
+ case ES_ESCAPE:
+ switch (*ch) {
+ case 0:
+ dev_dbg(&port->dev, "%s - escape char\n", __func__);
+ *ch = CP210X_ESCCHAR;
+ port_priv->event_state = ES_DATA;
+ return false;
+ case 1:
+ port_priv->event_state = ES_LSR_DATA_0;
+ break;
+ case 2:
+ port_priv->event_state = ES_LSR;
+ break;
+ case 3:
+ port_priv->event_state = ES_MSR;
+ break;
+ default:
+ dev_err(&port->dev, "malformed event 0x%02x\n", *ch);
+ port_priv->event_state = ES_DATA;
+ break;
+ }
+ break;
+ case ES_LSR_DATA_0:
+ port_priv->lsr = *ch;
+ port_priv->event_state = ES_LSR_DATA_1;
+ break;
+ case ES_LSR_DATA_1:
+ dev_dbg(&port->dev, "%s - lsr = 0x%02x, data = 0x%02x\n",
+ __func__, port_priv->lsr, *ch);
+ cp210x_process_lsr(port, port_priv->lsr, flag);
+ port_priv->event_state = ES_DATA;
+ return false;
+ case ES_LSR:
+ dev_dbg(&port->dev, "%s - lsr = 0x%02x\n", __func__, *ch);
+ port_priv->lsr = *ch;
+ cp210x_process_lsr(port, port_priv->lsr, flag);
+ port_priv->event_state = ES_DATA;
+ break;
+ case ES_MSR:
+ dev_dbg(&port->dev, "%s - msr = 0x%02x\n", __func__, *ch);
+ /* unimplemented */
+ port_priv->event_state = ES_DATA;
+ break;
+ }
+
+ return true;
+}
+
+static void cp210x_process_read_urb(struct urb *urb)
+{
+ struct usb_serial_port *port = urb->context;
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ unsigned char *ch = urb->transfer_buffer;
+ char flag;
+ int i;
+
+ if (!urb->actual_length)
+ return;
+
+ if (port_priv->event_mode) {
+ for (i = 0; i < urb->actual_length; i++, ch++) {
+ flag = TTY_NORMAL;
+
+ if (cp210x_process_char(port, ch, &flag))
+ continue;
+
+ tty_insert_flip_char(&port->port, *ch, flag);
+ }
+ } else {
+ tty_insert_flip_string(&port->port, ch, urb->actual_length);
+ }
+ tty_flip_buffer_push(&port->port);
}
/*
@@ -1088,13 +1266,10 @@
return cp210x_an205_table1[i].rate;
}
-static speed_t cp210x_get_actual_rate(struct usb_serial *serial, speed_t baud)
+static speed_t cp210x_get_actual_rate(speed_t baud)
{
- struct cp210x_serial_private *priv = usb_get_serial_data(serial);
unsigned int prescale = 1;
unsigned int div;
-
- baud = clamp(baud, 300u, priv->max_speed);
if (baud <= 365)
prescale = 4;
@@ -1138,20 +1313,18 @@
struct cp210x_serial_private *priv = usb_get_serial_data(serial);
u32 baud;
- baud = tty->termios.c_ospeed;
-
/*
* This maps the requested rate to the actual rate, a valid rate on
* cp2102 or cp2103, or to an arbitrary rate in [1M, max_speed].
*
* NOTE: B0 is not implemented.
*/
+ baud = clamp(tty->termios.c_ospeed, priv->min_speed, priv->max_speed);
+
if (priv->use_actual_rate)
- baud = cp210x_get_actual_rate(serial, baud);
+ baud = cp210x_get_actual_rate(baud);
else if (baud < 1000000)
baud = cp210x_get_an205_rate(baud);
- else if (baud > priv->max_speed)
- baud = priv->max_speed;
dev_dbg(&port->dev, "%s - setting baud rate to %u\n", __func__, baud);
if (cp210x_write_u32_reg(port, CP210X_SET_BAUDRATE, baud)) {
@@ -1163,6 +1336,45 @@
}
tty_encode_baud_rate(tty, baud, baud);
+}
+
+static void cp210x_enable_event_mode(struct usb_serial_port *port)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(port->serial);
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int ret;
+
+ if (port_priv->event_mode)
+ return;
+
+ if (priv->no_event_mode)
+ return;
+
+ port_priv->event_state = ES_DATA;
+ port_priv->event_mode = true;
+
+ ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, CP210X_ESCCHAR);
+ if (ret) {
+ dev_err(&port->dev, "failed to enable events: %d\n", ret);
+ port_priv->event_mode = false;
+ }
+}
+
+static void cp210x_disable_event_mode(struct usb_serial_port *port)
+{
+ struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+ int ret;
+
+ if (!port_priv->event_mode)
+ return;
+
+ ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, 0);
+ if (ret) {
+ dev_err(&port->dev, "failed to disable events: %d\n", ret);
+ return;
+ }
+
+ port_priv->event_mode = false;
}
static void cp210x_set_termios(struct tty_struct *tty,
@@ -1287,6 +1499,14 @@
sizeof(flow_ctl));
}
+ /*
+ * Enable event-insertion mode only if input parity checking is
+ * enabled for now.
+ */
+ if (I_INPCK(tty))
+ cp210x_enable_event_mode(port);
+ else
+ cp210x_disable_event_mode(port);
}
static int cp210x_tiocmset(struct tty_struct *tty,
@@ -1518,7 +1738,7 @@
{
struct cp210x_serial_private *priv = usb_get_serial_data(serial);
struct cp210x_pin_mode mode;
- struct cp210x_config config;
+ struct cp210x_dual_port_config config;
u8 intf_num = cp210x_interface_num(serial);
u8 iface_config;
int result;
@@ -1579,6 +1799,56 @@
return 0;
}
+static int cp2104_gpioconf_init(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ struct cp210x_single_port_config config;
+ u8 iface_config;
+ u8 gpio_latch;
+ int result;
+ u8 i;
+
+ result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_PORTCONFIG, &config,
+ sizeof(config));
+ if (result < 0)
+ return result;
+
+ priv->gc.ngpio = 4;
+
+ iface_config = config.device_cfg;
+ priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) &
+ CP210X_GPIO_MODE_MASK) >>
+ CP210X_GPIO_MODE_OFFSET);
+ gpio_latch = (u8)((le16_to_cpu(config.reset_state) &
+ CP210X_GPIO_MODE_MASK) >>
+ CP210X_GPIO_MODE_OFFSET);
+
+ /* mark all pins which are not in GPIO mode */
+ if (iface_config & CP2104_GPIO0_TXLED_MODE) /* GPIO 0 */
+ priv->gpio_altfunc |= BIT(0);
+ if (iface_config & CP2104_GPIO1_RXLED_MODE) /* GPIO 1 */
+ priv->gpio_altfunc |= BIT(1);
+ if (iface_config & CP2104_GPIO2_RS485_MODE) /* GPIO 2 */
+ priv->gpio_altfunc |= BIT(2);
+
+ /*
+ * Like CP2102N, CP2104 has also no strict input and output pin
+ * modes.
+ * Do the same input mode emulation as CP2102N.
+ */
+ for (i = 0; i < priv->gc.ngpio; ++i) {
+ /*
+ * Set direction to "input" iff pin is open-drain and reset
+ * value is 1.
+ */
+ if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i)))
+ priv->gpio_input |= BIT(i);
+ }
+
+ return 0;
+}
+
static int cp2102n_gpioconf_init(struct usb_serial *serial)
{
struct cp210x_serial_private *priv = usb_get_serial_data(serial);
@@ -1624,12 +1894,6 @@
if (config_version != 0x01)
return -ENOTSUPP;
- /*
- * We only support 4 GPIOs even on the QFN28 package, because
- * config locations of GPIOs 4-6 determined using reverse
- * engineering revealed conflicting offsets with other
- * documented functions. So we'll just play it safe for now.
- */
priv->gc.ngpio = 4;
/*
@@ -1654,6 +1918,19 @@
priv->gpio_altfunc |= BIT(3);
} else {
priv->gpio_altfunc = (gpio_ctrl >> 2) & 0x0f;
+ }
+
+ if (priv->partnum == CP210X_PARTNUM_CP2102N_QFN28) {
+ /*
+ * For the QFN28 package, GPIO4-6 are controlled by
+ * the low three bits of the mode/latch fields.
+ * Contrary to the document linked above, the bits for
+ * the SUSPEND pins are elsewhere. No alternate
+ * function is available for these pins.
+ */
+ priv->gc.ngpio = 7;
+ gpio_latch |= (gpio_rst_latch & 7) << 4;
+ priv->gpio_pushpull |= (gpio_pushpull & 7) << 4;
}
/*
@@ -1682,6 +1959,9 @@
int result;
switch (priv->partnum) {
+ case CP210X_PARTNUM_CP2104:
+ result = cp2104_gpioconf_init(serial);
+ break;
case CP210X_PARTNUM_CP2105:
result = cp2105_gpioconf_init(serial);
break;
@@ -1778,6 +2058,7 @@
{
struct cp210x_serial_private *priv = usb_get_serial_data(serial);
bool use_actual_rate = false;
+ speed_t min = 300;
speed_t max;
switch (priv->partnum) {
@@ -1800,6 +2081,7 @@
use_actual_rate = true;
max = 2000000; /* ECI */
} else {
+ min = 2400;
max = 921600; /* SCI */
}
break;
@@ -1814,8 +2096,49 @@
break;
}
+ priv->min_speed = min;
priv->max_speed = max;
priv->use_actual_rate = use_actual_rate;
+}
+
+static void cp2102_determine_quirks(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+ u8 *buf;
+ int ret;
+
+ buf = kmalloc(2, GFP_KERNEL);
+ if (!buf)
+ return;
+ /*
+ * Some (possibly counterfeit) CP2102 do not support event-insertion
+ * mode and respond differently to malformed vendor requests.
+ * Specifically, they return one instead of two bytes when sent a
+ * two-byte part-number request.
+ */
+ ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ CP210X_VENDOR_SPECIFIC, REQTYPE_DEVICE_TO_HOST,
+ CP210X_GET_PARTNUM, 0, buf, 2, USB_CTRL_GET_TIMEOUT);
+ if (ret == 1) {
+ dev_dbg(&serial->interface->dev,
+ "device does not support event-insertion mode\n");
+ priv->no_event_mode = true;
+ }
+
+ kfree(buf);
+}
+
+static void cp210x_determine_quirks(struct usb_serial *serial)
+{
+ struct cp210x_serial_private *priv = usb_get_serial_data(serial);
+
+ switch (priv->partnum) {
+ case CP210X_PARTNUM_CP2102:
+ cp2102_determine_quirks(serial);
+ break;
+ default:
+ break;
+ }
}
static int cp210x_attach(struct usb_serial *serial)
@@ -1838,6 +2161,7 @@
usb_set_serial_data(serial, priv);
+ cp210x_determine_quirks(serial);
cp210x_init_max_speed(serial);
result = cp210x_gpio_init(serial);
--
Gitblit v1.6.2