From 1c7c51347f2e2ff5920cee1b54c683e2af06bd81 Mon Sep 17 00:00:00 2001 From: Sarthak Kukreti Date: Wed, 1 Apr 2020 17:15:48 -0700 Subject: [PATCH 01/10] platform/chrome: chromeos_pstore: set user space log size On x86 ChromiumOS devices, the pmsg_size is set to 0 (check /sys/module/ramoops/parameters/pmsg_size): this prevents use of pstore-pmsg, even if CONFIG_PSTORE_PMSG is enabled. Set pmsg_size to a value that is consistent with the size used on non-x86 ChromiumOS devices. Signed-off-by: Sarthak Kukreti Reviewed-by: Kees Cook Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/chromeos_pstore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/chrome/chromeos_pstore.c b/drivers/platform/chrome/chromeos_pstore.c index d13770785fb5..82dea8cb5da1 100644 --- a/drivers/platform/chrome/chromeos_pstore.c +++ b/drivers/platform/chrome/chromeos_pstore.c @@ -57,6 +57,7 @@ static struct ramoops_platform_data chromeos_ramoops_data = { .record_size = 0x40000, .console_size = 0x20000, .ftrace_size = 0x20000, + .pmsg_size = 0x20000, .dump_oops = 1, }; From ad35da94b61785ddc1186095c3e488c5c0af6bd2 Mon Sep 17 00:00:00 2001 From: Bernardo Perez Priego Date: Thu, 2 Apr 2020 15:33:30 -0700 Subject: [PATCH 02/10] platform/chrome: wilco_ec: Provide correct output format to 'h1_gpio' file Function 'h1_gpio_get' is receiving 'val' parameter of type u64, this is being passed to 'send_ec_cmd' as type u8, thus, result is stored in least significant byte. Due to output format, the whole 'val' value was being displayed when any of the most significant bytes are different than zero. This fix will make sure only least significant byte is displayed regardless of remaining bytes value. Signed-off-by: Bernardo Perez Priego Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/wilco_ec/debugfs.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c index df5a5f6c3ec6..a812788a0bdc 100644 --- a/drivers/platform/chrome/wilco_ec/debugfs.c +++ b/drivers/platform/chrome/wilco_ec/debugfs.c @@ -208,7 +208,12 @@ static int send_ec_cmd(struct wilco_ec_device *ec, u8 sub_cmd, u8 *out_val) */ static int h1_gpio_get(void *arg, u64 *val) { - return send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val); + int ret; + + ret = send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val); + if (ret == 0) + *val &= 0xFF; + return ret; } DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n"); From 0f706b4fac8b19fd0a4b4fef24dbadb23a3eb0c1 Mon Sep 17 00:00:00 2001 From: Jett Rink Date: Fri, 10 Apr 2020 10:23:04 -0600 Subject: [PATCH 03/10] platform/chrome: cros_ec_ishtp: skip old cros_ec responses The ISHTP layer can give us old responses that we already gave up on. We do not want to interpret these old responses as the current response we are waiting for. The cros_ish should only have one request in flight at a time. We send the request and wait for the response from the ISH. If the ISH is too slow to respond we give up on that request and we can send a new request. The ISH may still send the response to the request that timed out and without this we treat the old response as the response to the current command. This is a condition that should not normally happen but it has been observed with a bad ISH image. So add a token to the request header which is copied into the response header when the ISH processes the message to ensure that response is for the current request. Signed-off-by: Jett Rink Signed-off-by: Mathew King Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_ishtp.c | 32 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index 93a71e93a2f1..e673a7f738fc 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -48,7 +48,8 @@ static const guid_t cros_ish_guid = struct header { u8 channel; u8 status; - u8 reserved[2]; + u8 token; + u8 reserved; } __packed; struct cros_ish_out_msg { @@ -90,6 +91,7 @@ static DECLARE_RWSEM(init_lock); * data exceeds this value, we log an error. * @size: Actual size of data received from firmware. * @error: 0 for success, negative error code for a failure in process_recv(). + * @token: Expected token for response that we are waiting on. * @received: Set to true on receiving a valid firmware response to host command * @wait_queue: Wait queue for host to wait for firmware response. */ @@ -98,6 +100,7 @@ struct response_info { size_t max_size; size_t size; int error; + u8 token; bool received; wait_queue_head_t wait_queue; }; @@ -162,6 +165,7 @@ static int ish_send(struct ishtp_cl_data *client_data, u8 *out_msg, size_t out_size, u8 *in_msg, size_t in_size) { + static u8 next_token; int rv; struct header *out_hdr = (struct header *)out_msg; struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl; @@ -174,8 +178,11 @@ static int ish_send(struct ishtp_cl_data *client_data, client_data->response.data = in_msg; client_data->response.max_size = in_size; client_data->response.error = 0; + client_data->response.token = next_token++; client_data->response.received = false; + out_hdr->token = client_data->response.token; + rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size); if (rv) { dev_err(cl_data_to_dev(client_data), @@ -249,6 +256,19 @@ static void process_recv(struct ishtp_cl *cros_ish_cl, switch (in_msg->hdr.channel) { case CROS_EC_COMMAND: + if (client_data->response.received) { + dev_err(dev, + "Previous firmware message not yet processed\n"); + goto end_error; + } + + if (client_data->response.token != in_msg->hdr.token) { + dev_err_ratelimited(dev, + "Dropping old response token %d\n", + in_msg->hdr.token); + goto end_error; + } + /* Sanity check */ if (!client_data->response.data) { dev_err(dev, @@ -257,13 +277,6 @@ static void process_recv(struct ishtp_cl *cros_ish_cl, goto error_wake_up; } - if (client_data->response.received) { - dev_err(dev, - "Previous firmware message not yet processed\n"); - client_data->response.error = -EINVAL; - goto error_wake_up; - } - if (data_len > client_data->response.max_size) { dev_err(dev, "Received buffer size %zu is larger than allocated buffer %zu\n", @@ -289,9 +302,10 @@ static void process_recv(struct ishtp_cl *cros_ish_cl, memcpy(client_data->response.data, rb_in_proc->buffer.data, data_len); +error_wake_up: /* Set flag before waking up the caller */ client_data->response.received = true; -error_wake_up: + /* Wake the calling thread */ wake_up_interruptible(&client_data->response.wait_queue); From fd167f7a4a6079c14a0c940e9103495b171279fb Mon Sep 17 00:00:00 2001 From: Jett Rink Date: Fri, 10 Apr 2020 10:23:05 -0600 Subject: [PATCH 04/10] platform/chrome: cros_ec_ishtp: free ishtp buffer before sending event Recycle the ISH buffer before notifying of a response or an event. Often a new message is sent in response to an event and in high traffic scenarios this can lead to exhausting all available buffers. We can ensure we are using the fewest buffers possible by freeing buffers as soon as they are used. Signed-off-by: Jett Rink Signed-off-by: Mathew King Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_ishtp.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index e673a7f738fc..ed794a7ddba9 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -303,6 +303,10 @@ static void process_recv(struct ishtp_cl *cros_ish_cl, rb_in_proc->buffer.data, data_len); error_wake_up: + /* Free the buffer since we copied data or didn't need it */ + ishtp_cl_io_rb_recycle(rb_in_proc); + rb_in_proc = NULL; + /* Set flag before waking up the caller */ client_data->response.received = true; @@ -312,12 +316,14 @@ error_wake_up: break; case CROS_MKBP_EVENT: + /* Free the buffer. This is just an event without data */ + ishtp_cl_io_rb_recycle(rb_in_proc); + rb_in_proc = NULL; /* * Set timestamp from beginning of function since we actually * got an incoming MKBP event */ client_data->ec_dev->last_event_time = timestamp; - /* The event system doesn't send any data in buffer */ schedule_work(&client_data->work_ec_evt); break; @@ -327,8 +333,9 @@ error_wake_up: } end_error: - /* Free the buffer */ - ishtp_cl_io_rb_recycle(rb_in_proc); + /* Free the buffer if we already haven't */ + if (rb_in_proc) + ishtp_cl_io_rb_recycle(rb_in_proc); up_read(&init_lock); } From 7110f5f0e5ab329b6ed840c06e7321bbf331ccf5 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 14 Apr 2020 22:29:41 -0700 Subject: [PATCH 05/10] platform/chrome: cros_ec_typec: Use notifier for updates Register a listener for the cros-usbpd-notifier, and update port state when a notification comes in. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/Kconfig | 1 + drivers/platform/chrome/cros_ec_typec.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 03ea5129ed0c..a484ab2c91ff 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -217,6 +217,7 @@ config CROS_EC_SYSFS config CROS_EC_TYPEC tristate "ChromeOS EC Type-C Connector Control" depends on MFD_CROS_EC_DEV && TYPEC + depends on CROS_USBPD_NOTIFY default MFD_CROS_EC_DEV help If you say Y here, you get support for accessing Type C connector diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 874269c07073..d444dd7422a2 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,7 @@ struct cros_typec_data { struct typec_port *ports[EC_USB_PD_MAX_PORTS]; /* Initial capabilities for each port. */ struct typec_capability *caps[EC_USB_PD_MAX_PORTS]; + struct notifier_block nb; }; static int cros_typec_parse_port_props(struct typec_capability *cap, @@ -272,6 +274,22 @@ static int cros_typec_get_cmd_version(struct cros_typec_data *typec) return 0; } +static int cros_ec_typec_event(struct notifier_block *nb, + unsigned long host_event, void *_notify) +{ + struct cros_typec_data *typec = container_of(nb, struct cros_typec_data, + nb); + int ret, i; + + for (i = 0; i < typec->num_ports; i++) { + ret = cros_typec_port_update(typec, i); + if (ret < 0) + dev_warn(typec->dev, "Update failed for port: %d\n", i); + } + + return NOTIFY_OK; +} + #ifdef CONFIG_ACPI static const struct acpi_device_id cros_typec_acpi_id[] = { { "GOOG0014", 0 }, @@ -332,6 +350,11 @@ static int cros_typec_probe(struct platform_device *pdev) goto unregister_ports; } + typec->nb.notifier_call = cros_ec_typec_event; + ret = cros_usbpd_register_notify(&typec->nb); + if (ret < 0) + goto unregister_ports; + return 0; unregister_ports: From 5fed73b84f52a0595cc3f11b6f4fd3746a797e34 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 14 Apr 2020 22:29:42 -0700 Subject: [PATCH 06/10] platform/chrome: cros_ec_typec: Add struct for port data Add a separate struct for storing port data, including Type C connector class struct pointers and caps. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_typec.c | 48 ++++++++++++++++--------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index d444dd7422a2..56ded09a60ff 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -17,6 +17,13 @@ #define DRV_NAME "cros-ec-typec" +/* Per port data. */ +struct cros_typec_port { + struct typec_port *port; + /* Initial capabilities for the port. */ + struct typec_capability caps; +}; + /* Platform-specific data for the Chrome OS EC Type C controller. */ struct cros_typec_data { struct device *dev; @@ -24,9 +31,7 @@ struct cros_typec_data { int num_ports; unsigned int cmd_ver; /* Array of ports, indexed by port number. */ - struct typec_port *ports[EC_USB_PD_MAX_PORTS]; - /* Initial capabilities for each port. */ - struct typec_capability *caps[EC_USB_PD_MAX_PORTS]; + struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; struct notifier_block nb; }; @@ -76,14 +81,25 @@ static int cros_typec_parse_port_props(struct typec_capability *cap, return 0; } +static void cros_unregister_ports(struct cros_typec_data *typec) +{ + int i; + + for (i = 0; i < typec->num_ports; i++) { + if (!typec->ports[i]) + continue; + typec_unregister_port(typec->ports[i]->port); + } +} + static int cros_typec_init_ports(struct cros_typec_data *typec) { struct device *dev = typec->dev; struct typec_capability *cap; struct fwnode_handle *fwnode; + struct cros_typec_port *cros_port; const char *port_prop; int ret; - int i; int nports; u32 port_num = 0; @@ -115,22 +131,23 @@ static int cros_typec_init_ports(struct cros_typec_data *typec) dev_dbg(dev, "Registering port %d\n", port_num); - cap = devm_kzalloc(dev, sizeof(*cap), GFP_KERNEL); - if (!cap) { + cros_port = devm_kzalloc(dev, sizeof(*cros_port), GFP_KERNEL); + if (!cros_port) { ret = -ENOMEM; goto unregister_ports; } - typec->caps[port_num] = cap; + typec->ports[port_num] = cros_port; + cap = &cros_port->caps; ret = cros_typec_parse_port_props(cap, fwnode, dev); if (ret < 0) goto unregister_ports; - typec->ports[port_num] = typec_register_port(dev, cap); - if (IS_ERR(typec->ports[port_num])) { + cros_port->port = typec_register_port(dev, cap); + if (IS_ERR(cros_port->port)) { dev_err(dev, "Failed to register port %d\n", port_num); - ret = PTR_ERR(typec->ports[port_num]); + ret = PTR_ERR(cros_port->port); goto unregister_ports; } } @@ -138,8 +155,7 @@ static int cros_typec_init_ports(struct cros_typec_data *typec) return 0; unregister_ports: - for (i = 0; i < typec->num_ports; i++) - typec_unregister_port(typec->ports[i]); + cros_unregister_ports(typec); return ret; } @@ -177,7 +193,7 @@ static int cros_typec_ec_command(struct cros_typec_data *typec, static void cros_typec_set_port_params_v0(struct cros_typec_data *typec, int port_num, struct ec_response_usb_pd_control *resp) { - struct typec_port *port = typec->ports[port_num]; + struct typec_port *port = typec->ports[port_num]->port; enum typec_orientation polarity; if (!resp->enabled) @@ -194,7 +210,7 @@ static void cros_typec_set_port_params_v0(struct cros_typec_data *typec, static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, int port_num, struct ec_response_usb_pd_control_v1 *resp) { - struct typec_port *port = typec->ports[port_num]; + struct typec_port *port = typec->ports[port_num]->port; enum typec_orientation polarity; if (!(resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) @@ -358,9 +374,7 @@ static int cros_typec_probe(struct platform_device *pdev) return 0; unregister_ports: - for (i = 0; i < typec->num_ports; i++) - if (typec->ports[i]) - typec_unregister_port(typec->ports[i]); + cros_unregister_ports(typec); return ret; } From 9d33ea331032332882d4cc1a38a9d0e237126bd0 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 14 Apr 2020 22:29:43 -0700 Subject: [PATCH 07/10] platform/chrome: cros_ec_typec: Register port partner Register (and unregister) the port partner when a connect (and disconnect) is detected. Co-developed-by: Jon Flatley Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_typec.c | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 56ded09a60ff..eda57db26f8d 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -22,6 +22,9 @@ struct cros_typec_port { struct typec_port *port; /* Initial capabilities for the port. */ struct typec_capability caps; + struct typec_partner *partner; + /* Port partner PD identity info. */ + struct usb_pd_identity p_identity; }; /* Platform-specific data for the Chrome OS EC Type C controller. */ @@ -190,6 +193,30 @@ static int cros_typec_ec_command(struct cros_typec_data *typec, return ret; } +static int cros_typec_add_partner(struct cros_typec_data *typec, int port_num, + bool pd_en) +{ + struct cros_typec_port *port = typec->ports[port_num]; + struct typec_partner_desc p_desc = { + .usb_pd = pd_en, + }; + int ret = 0; + + /* + * Fill an initial PD identity, which will then be updated with info + * from the EC. + */ + p_desc.identity = &port->p_identity; + + port->partner = typec_register_partner(port->port, &p_desc); + if (IS_ERR(port->partner)) { + ret = PTR_ERR(port->partner); + port->partner = NULL; + } + + return ret; +} + static void cros_typec_set_port_params_v0(struct cros_typec_data *typec, int port_num, struct ec_response_usb_pd_control *resp) { @@ -212,6 +239,8 @@ static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, { struct typec_port *port = typec->ports[port_num]->port; enum typec_orientation polarity; + bool pd_en; + int ret; if (!(resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) polarity = TYPEC_ORIENTATION_NONE; @@ -226,6 +255,25 @@ static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, TYPEC_SOURCE : TYPEC_SINK); typec_set_vconn_role(port, resp->role & PD_CTRL_RESP_ROLE_VCONN ? TYPEC_SOURCE : TYPEC_SINK); + + /* Register/remove partners when a connect/disconnect occurs. */ + if (resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED) { + if (typec->ports[port_num]->partner) + return; + + pd_en = resp->enabled & PD_CTRL_RESP_ENABLED_PD_CAPABLE; + ret = cros_typec_add_partner(typec, port_num, pd_en); + if (!ret) + dev_warn(typec->dev, + "Failed to register partner on port: %d\n", + port_num); + } else { + if (!typec->ports[port_num]->partner) + return; + + typec_unregister_partner(typec->ports[port_num]->partner); + typec->ports[port_num]->partner = NULL; + } } static int cros_typec_port_update(struct cros_typec_data *typec, int port_num) From 89d9c24b391646308b6c1d3a68c8c521c9b8c8e9 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 21 Apr 2020 17:41:51 -0700 Subject: [PATCH 08/10] platform/chrome: typec: Fix ret value check error cros_typec_add_partner() returns 0 on success, so check for "ret" instead of "!ret" as an error. Signed-off-by: Prashant Malani Fixes: 9d33ea331032 ("platform/chrome: cros_ec_typec: Register port partner") Signed-off-by: Benson Leung --- drivers/platform/chrome/cros_ec_typec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index eda57db26f8d..66b8d21092af 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -263,7 +263,7 @@ static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, pd_en = resp->enabled & PD_CTRL_RESP_ENABLED_PD_CAPABLE; ret = cros_typec_add_partner(typec, port_num, pd_en); - if (!ret) + if (ret) dev_warn(typec->dev, "Failed to register partner on port: %d\n", port_num); From c032699ef9d59bf9f953e3bff2eed45839dbbf71 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Tue, 14 Apr 2020 22:13:13 +0200 Subject: [PATCH 09/10] platform/chrome: cros_ec_i2c: Appease the kernel-doc deity Replace a comment starting with /** by simply /* to avoid having it interpreted as a kernel-doc comment. Signed-off-by: Enric Balletbo i Serra Signed-off-by: Benson Leung --- drivers/platform/chrome/cros_ec_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrome/cros_ec_i2c.c index 6119eccd8a18..30c8938c27d5 100644 --- a/drivers/platform/chrome/cros_ec_i2c.c +++ b/drivers/platform/chrome/cros_ec_i2c.c @@ -16,7 +16,7 @@ #include "cros_ec.h" -/** +/* * Request format for protocol v3 * byte 0 0xda (EC_COMMAND_PROTOCOL_3) * byte 1-8 struct ec_host_request From bbb7ad49b8350b79261ce087c8e101d92f15533d Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Tue, 14 Apr 2020 22:12:39 +0200 Subject: [PATCH 10/10] platform/chrome: cros_usbpd_logger: Add __printf annotation to append_str() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the compiler to verify the format strings vs the types of the arguments. Also, silence the warning (triggered by W=1): cros_usbpd_logger.c:55:2: warning: function ‘append_str’ might be a candidate for ‘gnu_printf’ format attribute [-Wsuggest-attribute=format] Signed-off-by: Enric Balletbo i Serra Signed-off-by: Benson Leung --- drivers/platform/chrome/cros_usbpd_logger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/chrome/cros_usbpd_logger.c b/drivers/platform/chrome/cros_usbpd_logger.c index 7de3ea75ef46..d16931203d82 100644 --- a/drivers/platform/chrome/cros_usbpd_logger.c +++ b/drivers/platform/chrome/cros_usbpd_logger.c @@ -46,6 +46,7 @@ static const char * const fault_names[] = { "---", "OCP", "fast OCP", "OVP", "Discharge" }; +__printf(3, 4) static int append_str(char *buf, int pos, const char *fmt, ...) { va_list args;