diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index d14bf4d21983..11627035222c 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -9,6 +9,7 @@ greybus-y := core.o \ protocol.o \ control.o \ svc.o \ + firmware.o \ operation.o gb-phy-y := gpbridge.o \ diff --git a/drivers/staging/greybus/connection.c b/drivers/staging/greybus/connection.c index ef837c50fcfb..eae6ad143d0b 100644 --- a/drivers/staging/greybus/connection.c +++ b/drivers/staging/greybus/connection.c @@ -11,6 +11,10 @@ #include "greybus.h" +#define GB_CONNECTION_TS_KFIFO_ELEMENTS 2 +#define GB_CONNECTION_TS_KFIFO_LEN \ + (GB_CONNECTION_TS_KFIFO_ELEMENTS * sizeof(struct timeval)) + static DEFINE_SPINLOCK(gb_connections_lock); /* This is only used at initialization time; no locking is required. */ @@ -63,6 +67,29 @@ void greybus_data_rcvd(struct greybus_host_device *hd, u16 cport_id, } EXPORT_SYMBOL_GPL(greybus_data_rcvd); +void gb_connection_push_timestamp(struct gb_connection *connection) +{ + struct timeval tv; + + do_gettimeofday(&tv); + kfifo_in_locked(&connection->ts_kfifo, (void *)&tv, + sizeof(struct timeval), &connection->lock); +} +EXPORT_SYMBOL_GPL(gb_connection_push_timestamp); + +int gb_connection_pop_timestamp(struct gb_connection *connection, + struct timeval *tv) +{ + int retval; + + if (!kfifo_len(&connection->ts_kfifo)) + return -ENOMEM; + retval = kfifo_out_locked(&connection->ts_kfifo, (void *)tv, + sizeof(*tv), &connection->lock); + return retval; +} +EXPORT_SYMBOL_GPL(gb_connection_pop_timestamp); + static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -102,6 +129,7 @@ static void gb_connection_release(struct device *dev) struct gb_connection *connection = to_gb_connection(dev); destroy_workqueue(connection->wq); + kfifo_free(&connection->ts_kfifo); kfree(connection); } @@ -222,6 +250,10 @@ gb_connection_create_range(struct greybus_host_device *hd, if (!connection->wq) goto err_free_connection; + if (kfifo_alloc(&connection->ts_kfifo, GB_CONNECTION_TS_KFIFO_LEN, + GFP_KERNEL)) + goto err_free_connection; + connection->dev.parent = parent; connection->dev.bus = &greybus_bus_type; connection->dev.type = &greybus_connection_type; @@ -238,7 +270,7 @@ gb_connection_create_range(struct greybus_host_device *hd, pr_err("failed to add connection device for cport 0x%04hx\n", cport_id); - goto err_remove_ida; + goto err_free_kfifo; } spin_lock_irq(&gb_connections_lock); @@ -264,6 +296,8 @@ gb_connection_create_range(struct greybus_host_device *hd, return connection; +err_free_kfifo: + kfifo_free(&connection->ts_kfifo); err_free_connection: kfree(connection); err_remove_ida: @@ -394,7 +428,19 @@ int gb_connection_init(struct gb_connection *connection) * this for SVC as that is initiated by the SVC. */ if (connection->hd_cport_id != GB_SVC_CPORT_ID) { - ret = gb_protocol_get_version(connection, NULL, 0); + bool send_request = false; + + /* + * We need to send the protocol version of the firmware protocol + * supported by AP and so this special case. + */ + if (connection->protocol->id == GREYBUS_PROTOCOL_FIRMWARE) + send_request = true; + + // FIXME: Should we always send the protocol version AP can + // support ? + + ret = gb_protocol_get_version(connection, send_request); if (ret) { dev_err(&connection->dev, "Failed to get version CPort-%d (%d)\n", diff --git a/drivers/staging/greybus/connection.h b/drivers/staging/greybus/connection.h index 0dbbc202e953..a26a48033fc6 100644 --- a/drivers/staging/greybus/connection.h +++ b/drivers/staging/greybus/connection.h @@ -11,6 +11,7 @@ #define __CONNECTION_H #include +#include enum gb_connection_state { GB_CONNECTION_STATE_INVALID = 0, @@ -42,6 +43,7 @@ struct gb_connection { struct list_head operations; struct workqueue_struct *wq; + struct kfifo ts_kfifo; atomic_t op_cycle; @@ -65,6 +67,9 @@ void gb_hd_connections_exit(struct greybus_host_device *hd); void greybus_data_rcvd(struct greybus_host_device *hd, u16 cport_id, u8 *data, size_t length); +void gb_connection_push_timestamp(struct gb_connection *connection); +int gb_connection_pop_timestamp(struct gb_connection *connection, + struct timeval *tv); void gb_connection_bind_protocol(struct gb_connection *connection); diff --git a/drivers/staging/greybus/core.c b/drivers/staging/greybus/core.c index 9f105fb12ede..af71d027490c 100644 --- a/drivers/staging/greybus/core.c +++ b/drivers/staging/greybus/core.c @@ -308,8 +308,16 @@ static int __init gb_init(void) goto error_svc; } + retval = gb_firmware_protocol_init(); + if (retval) { + pr_err("gb_firmware_protocol_init failed\n"); + goto error_firmware; + } + return 0; /* Success */ +error_firmware: + gb_svc_protocol_exit(); error_svc: gb_control_protocol_exit(); error_control: @@ -327,6 +335,7 @@ module_init(gb_init); static void __exit gb_exit(void) { + gb_firmware_protocol_exit(); gb_svc_protocol_exit(); gb_control_protocol_exit(); gb_endo_exit(); diff --git a/drivers/staging/greybus/es1.c b/drivers/staging/greybus/es1.c index c1fab375bb0b..2aa271751ac1 100644 --- a/drivers/staging/greybus/es1.c +++ b/drivers/staging/greybus/es1.c @@ -15,6 +15,7 @@ #include "greybus.h" #include "kernel_ver.h" +#include "connection.h" /* Memory sizes for the buffers sent to/from the ES1 controller */ #define ES1_GBUF_MSG_SIZE_MAX 2048 @@ -215,6 +216,7 @@ static int message_send(struct greybus_host_device *hd, u16 cport_id, usb_sndbulkpipe(udev, es1->cport_out_endpoint), message->buffer, buffer_size, cport_out_callback, message); + gb_connection_push_timestamp(message->operation->connection); retval = usb_submit_urb(urb, gfp_mask); if (retval) { pr_err("error %d submitting URB\n", retval); diff --git a/drivers/staging/greybus/es2.c b/drivers/staging/greybus/es2.c index 558345cd80af..008685b2b15b 100644 --- a/drivers/staging/greybus/es2.c +++ b/drivers/staging/greybus/es2.c @@ -15,6 +15,7 @@ #include "greybus.h" #include "kernel_ver.h" +#include "connection.h" /* Memory sizes for the buffers sent to/from the ES1 controller */ #define ES1_GBUF_MSG_SIZE_MAX 2048 @@ -311,6 +312,7 @@ static int message_send(struct greybus_host_device *hd, u16 cport_id, es1->cport_out[bulk_ep_set].endpoint), message->buffer, buffer_size, cport_out_callback, message); + gb_connection_push_timestamp(message->operation->connection); retval = usb_submit_urb(urb, gfp_mask); if (retval) { pr_err("error %d submitting URB\n", retval); diff --git a/drivers/staging/greybus/firmware.c b/drivers/staging/greybus/firmware.c new file mode 100644 index 000000000000..13efaabb891b --- /dev/null +++ b/drivers/staging/greybus/firmware.c @@ -0,0 +1,199 @@ +/* + * FIRMWARE Greybus driver. + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#include + +#include "greybus.h" + +struct gb_firmware { + struct gb_connection *connection; + const struct firmware *fw; +}; + +static void free_firmware(struct gb_firmware *firmware) +{ + release_firmware(firmware->fw); + firmware->fw = NULL; +} + +/* This returns path of the firmware blob on the disk */ +static int download_firmware(struct gb_firmware *firmware, u8 stage) +{ + struct gb_connection *connection = firmware->connection; + struct gb_interface *intf = connection->bundle->intf; + char firmware_name[28]; + + /* Already have a firmware, free it */ + if (firmware->fw) + free_firmware(firmware); + + /* + * Create firmware name + * + * XXX Name it properly.. + */ + sprintf(firmware_name, "ara:%04x:%04x:%04x:%04x:%04x.fw", intf->unipro_mfg_id, + intf->unipro_prod_id, intf->ara_vend_id, intf->ara_prod_id, + stage); + + return request_firmware(&firmware->fw, firmware_name, &connection->dev); +} + +static int gb_firmware_size_request(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_firmware *firmware = connection->private; + struct gb_firmware_size_request *size_request = op->request->payload; + struct gb_firmware_size_response *size_response; + struct device *dev = &connection->dev; + int ret; + + if (op->request->payload_size != sizeof(*size_request)) { + dev_err(dev, "%s: illegal size of firmware size request (%zu != %zu)\n", + __func__, op->request->payload_size, + sizeof(*size_request)); + return -EINVAL; + } + + ret = download_firmware(firmware, size_request->stage); + if (ret) { + dev_err(dev, "%s: failed to download firmware (%d)\n", __func__, + ret); + return ret; + } + + if (!gb_operation_response_alloc(op, sizeof(*size_response), + GFP_KERNEL)) { + dev_err(dev, "%s: error allocating response\n", __func__); + free_firmware(firmware); + return -ENOMEM; + } + + size_response = op->response->payload; + size_response->size = cpu_to_le32(firmware->fw->size); + + return 0; +} + +static int gb_firmware_get_firmware(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_firmware *firmware = connection->private; + struct gb_firmware_get_firmware_request *firmware_request = op->request->payload; + struct gb_firmware_get_firmware_response *firmware_response; + struct device *dev = &connection->dev; + unsigned int offset, size; + + if (op->request->payload_size != sizeof(*firmware_request)) { + dev_err(dev, "%s: Illegal size of get firmware request (%zu %zu)\n", + __func__, op->request->payload_size, + sizeof(*firmware_request)); + return -EINVAL; + } + + if (!firmware->fw) { + dev_err(dev, "%s: firmware not available\n", __func__); + return -EINVAL; + } + + offset = le32_to_cpu(firmware_request->offset); + size = le32_to_cpu(firmware_request->size); + + if (!gb_operation_response_alloc(op, sizeof(*firmware_response) + size, + GFP_KERNEL)) { + dev_err(dev, "%s: error allocating response\n", __func__); + return -ENOMEM; + } + + firmware_response = op->response->payload; + memcpy(firmware_response->data, firmware->fw->data + offset, size); + + return 0; +} + +static int gb_firmware_ready_to_boot(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gb_firmware_ready_to_boot_request *rtb_request = op->request->payload; + struct device *dev = &connection->dev; + u8 stage, status; + + if (op->request->payload_size != sizeof(*rtb_request)) { + dev_err(dev, "%s: Illegal size of ready to boot request (%zu %zu)\n", + __func__, op->request->payload_size, + sizeof(*rtb_request)); + return -EINVAL; + } + + stage = rtb_request->stage; + status = rtb_request->status; + + /* Return error if the blob was invalid */ + if (status == GB_FIRMWARE_BOOT_STATUS_INVALID) + return -EINVAL; + + /* + * XXX Should we return error for insecure firmware? + */ + + return 0; +} + +static int gb_firmware_request_recv(u8 type, struct gb_operation *op) +{ + switch (type) { + case GB_FIRMWARE_TYPE_FIRMWARE_SIZE: + return gb_firmware_size_request(op); + case GB_FIRMWARE_TYPE_GET_FIRMWARE: + return gb_firmware_get_firmware(op); + case GB_FIRMWARE_TYPE_READY_TO_BOOT: + return gb_firmware_ready_to_boot(op); + default: + dev_err(&op->connection->dev, + "unsupported request: %hhu\n", type); + return -EINVAL; + } +} + +static int gb_firmware_connection_init(struct gb_connection *connection) +{ + struct gb_firmware *firmware; + + firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); + if (!firmware) + return -ENOMEM; + + firmware->connection = connection; + connection->private = firmware; + + return 0; +} + +static void gb_firmware_connection_exit(struct gb_connection *connection) +{ + struct gb_firmware *firmware = connection->private; + + /* Release firmware */ + if (firmware->fw) + free_firmware(firmware); + + connection->private = NULL; + kfree(firmware); +} + +static struct gb_protocol firmware_protocol = { + .name = "firmware", + .id = GREYBUS_PROTOCOL_FIRMWARE, + .major = GB_FIRMWARE_VERSION_MAJOR, + .minor = GB_FIRMWARE_VERSION_MINOR, + .connection_init = gb_firmware_connection_init, + .connection_exit = gb_firmware_connection_exit, + .request_recv = gb_firmware_request_recv, +}; +gb_builtin_protocol_driver(firmware_protocol); diff --git a/drivers/staging/greybus/firmware.h b/drivers/staging/greybus/firmware.h new file mode 100644 index 000000000000..548d297eec63 --- /dev/null +++ b/drivers/staging/greybus/firmware.h @@ -0,0 +1,16 @@ +/* + * Greybus firmware code + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#ifndef __FIRMWARE_H +#define __FIRMWARE_H + +int gb_firmware_protocol_init(void); +void gb_firmware_protocol_exit(void); + +#endif /* __FIRMWARE_H */ diff --git a/drivers/staging/greybus/greybus.h b/drivers/staging/greybus/greybus.h index cdade1e9b4c7..57265558c682 100644 --- a/drivers/staging/greybus/greybus.h +++ b/drivers/staging/greybus/greybus.h @@ -27,6 +27,7 @@ #include "manifest.h" #include "endo.h" #include "svc.h" +#include "firmware.h" #include "module.h" #include "control.h" #include "interface.h" diff --git a/drivers/staging/greybus/greybus_manifest.h b/drivers/staging/greybus/greybus_manifest.h index 9c4d7cae9bbb..687adf2cb645 100644 --- a/drivers/staging/greybus/greybus_manifest.h +++ b/drivers/staging/greybus/greybus_manifest.h @@ -43,6 +43,7 @@ enum greybus_protocol { GREYBUS_PROTOCOL_I2S_RECEIVER = 0x12, GREYBUS_PROTOCOL_I2S_TRANSMITTER = 0x13, GREYBUS_PROTOCOL_SVC = 0x14, + GREYBUS_PROTOCOL_FIRMWARE = 0x15, /* ... */ GREYBUS_PROTOCOL_RAW = 0xfe, GREYBUS_PROTOCOL_VENDOR = 0xff, @@ -70,6 +71,7 @@ enum greybus_class_type { GREYBUS_CLASS_I2S_RECEIVER = 0x12, GREYBUS_CLASS_I2S_TRANSMITTER = 0x13, GREYBUS_CLASS_SVC = 0x14, + GREYBUS_CLASS_FIRMWARE = 0x15, /* ... */ GREYBUS_CLASS_RAW = 0xfe, GREYBUS_CLASS_VENDOR = 0xff, diff --git a/drivers/staging/greybus/greybus_protocols.h b/drivers/staging/greybus/greybus_protocols.h index 3cfb92325a54..43a072881715 100644 --- a/drivers/staging/greybus/greybus_protocols.h +++ b/drivers/staging/greybus/greybus_protocols.h @@ -146,6 +146,60 @@ struct gb_control_disconnected_request { }; /* Control protocol [dis]connected response has no payload */ + +/* Firmware Protocol */ + +/* Version of the Greybus firmware protocol we support */ +#define GB_FIRMWARE_VERSION_MAJOR 0x00 +#define GB_FIRMWARE_VERSION_MINOR 0x01 + +/* Greybus firmware request types */ +#define GB_FIRMWARE_TYPE_INVALID 0x00 +#define GB_FIRMWARE_TYPE_PROTOCOL_VERSION 0x01 +#define GB_FIRMWARE_TYPE_FIRMWARE_SIZE 0x02 +#define GB_FIRMWARE_TYPE_GET_FIRMWARE 0x03 +#define GB_FIRMWARE_TYPE_READY_TO_BOOT 0x04 + +/* Greybus firmware boot stages */ +#define GB_FIRMWARE_BOOT_STAGE_ONE 0x01 /* Reserved for the boot ROM */ +#define GB_FIRMWARE_BOOT_STAGE_TWO 0x02 /* Firmware package to be loaded by the boot ROM */ +#define GB_FIRMWARE_BOOT_STAGE_THREE 0x03 /* Module personality package loaded by Stage 2 firmware */ + +/* Greybus firmware ready to boot status */ +#define GB_FIRMWARE_BOOT_STATUS_INVALID 0x00 /* Firmware blob could not be validated */ +#define GB_FIRMWARE_BOOT_STATUS_INSECURE 0x01 /* Firmware blob is valid but insecure */ +#define GB_FIRMWARE_BOOT_STATUS_SECURE 0x02 /* Firmware blob is valid and secure */ + +/* Max firmware data fetch size in bytes */ +#define GB_FIRMWARE_FETCH_MAX 2000 + +/* Firmware protocol firmware size request/response */ +struct gb_firmware_size_request { + __u8 stage; +}; + +struct gb_firmware_size_response { + __le32 size; +}; + +/* Firmware protocol get firmware request/response */ +struct gb_firmware_get_firmware_request { + __le32 offset; + __le32 size; +}; + +struct gb_firmware_get_firmware_response { + __u8 data[0]; +}; + +/* Firmware protocol Ready to boot request */ +struct gb_firmware_ready_to_boot_request { + __u8 stage; + __u8 status; +}; +/* Firmware protocol Ready to boot response has no payload */ + + /* I2C */ /* Version of the Greybus i2c protocol we support */ diff --git a/drivers/staging/greybus/interface.h b/drivers/staging/greybus/interface.h index e60a3705494e..38210ad4e631 100644 --- a/drivers/staging/greybus/interface.h +++ b/drivers/staging/greybus/interface.h @@ -28,6 +28,12 @@ struct gb_interface { char *product_string; u64 unique_id; + /* Information taken from the hotplug event */ + u32 unipro_mfg_id; + u32 unipro_prod_id; + u32 ara_vend_id; + u32 ara_prod_id; + struct gb_module *module; struct greybus_host_device *hd; }; diff --git a/drivers/staging/greybus/loopback.c b/drivers/staging/greybus/loopback.c index 88c329afd3ea..852b6beaecac 100644 --- a/drivers/staging/greybus/loopback.c +++ b/drivers/staging/greybus/loopback.c @@ -22,6 +22,8 @@ #include "greybus.h" +#define NSEC_PER_DAY 86400000000000ULL + struct gb_loopback_stats { u32 min; u32 max; @@ -219,6 +221,19 @@ static struct attribute *loopback_attrs[] = { }; ATTRIBUTE_GROUPS(loopback); +static void gb_loopback_calc_latency(struct gb_loopback *gb, + struct timeval *ts, struct timeval *te) +{ + u64 t1, t2; + + t1 = timeval_to_ns(ts); + t2 = timeval_to_ns(te); + if (t2 > t1) + gb->elapsed_nsecs = t2 - t1; + else + gb->elapsed_nsecs = NSEC_PER_DAY - t2 + t1; +} + static int gb_loopback_sink(struct gb_loopback *gb, u32 len) { struct timeval ts, te; @@ -236,7 +251,7 @@ static int gb_loopback_sink(struct gb_loopback *gb, u32 len) request, len + sizeof(*request), NULL, 0); do_gettimeofday(&te); - gb->elapsed_nsecs = timeval_to_ns(&te) - timeval_to_ns(&ts); + gb_loopback_calc_latency(gb, &ts, &te); kfree(request); return retval; @@ -265,7 +280,7 @@ static int gb_loopback_transfer(struct gb_loopback *gb, u32 len) request, len + sizeof(*request), response, len + sizeof(*response)); do_gettimeofday(&te); - gb->elapsed_nsecs = timeval_to_ns(&te) - timeval_to_ns(&ts); + gb_loopback_calc_latency(gb, &ts, &te); if (retval) goto gb_error; @@ -289,7 +304,7 @@ static int gb_loopback_ping(struct gb_loopback *gb) retval = gb_operation_sync(gb->connection, GB_LOOPBACK_TYPE_PING, NULL, 0, NULL, 0); do_gettimeofday(&te); - gb->elapsed_nsecs = timeval_to_ns(&te) - timeval_to_ns(&ts); + gb_loopback_calc_latency(gb, &ts, &te); return retval; } diff --git a/drivers/staging/greybus/protocol.c b/drivers/staging/greybus/protocol.c index b63e28c1b950..5bdc2c026efd 100644 --- a/drivers/staging/greybus/protocol.c +++ b/drivers/staging/greybus/protocol.c @@ -163,12 +163,20 @@ struct gb_protocol *gb_protocol_get(u8 id, u8 major, u8 minor) return protocol; } -int gb_protocol_get_version(struct gb_connection *connection, void *request, - int request_size) +int gb_protocol_get_version(struct gb_connection *connection, bool send_request) { struct gb_protocol_version_response response; + struct gb_protocol_version_response *request = NULL; + int request_size = 0; int retval; + if (send_request) { + response.major = connection->protocol->major; + response.minor = connection->protocol->minor; + request = &response; + request_size = sizeof(*request); + } + retval = gb_operation_sync(connection, GB_REQUEST_TYPE_PROTOCOL_VERSION, request, request_size, &response, sizeof(response)); diff --git a/drivers/staging/greybus/protocol.h b/drivers/staging/greybus/protocol.h index 34a7f185a638..87b5a1010de0 100644 --- a/drivers/staging/greybus/protocol.h +++ b/drivers/staging/greybus/protocol.h @@ -44,8 +44,7 @@ int gb_protocol_deregister(struct gb_protocol *protocol); __gb_protocol_register(protocol, THIS_MODULE) struct gb_protocol *gb_protocol_get(u8 id, u8 major, u8 minor); -int gb_protocol_get_version(struct gb_connection *connection, void *request, - int request_size); +int gb_protocol_get_version(struct gb_connection *connection, bool send_request); void gb_protocol_put(struct gb_protocol *protocol); diff --git a/drivers/staging/greybus/svc.c b/drivers/staging/greybus/svc.c index 16a37f22135c..5ff7cf3b2f0b 100644 --- a/drivers/staging/greybus/svc.c +++ b/drivers/staging/greybus/svc.c @@ -276,20 +276,12 @@ static void svc_process_hotplug(struct work_struct *work) struct device *dev = &connection->dev; struct gb_interface *intf; u8 intf_id, device_id; - u32 unipro_mfg_id; - u32 unipro_prod_id; - u32 ara_vend_id; - u32 ara_prod_id; int ret; /* * Grab the information we need. */ intf_id = hotplug->intf_id; - unipro_mfg_id = le32_to_cpu(hotplug->data.unipro_mfg_id); - unipro_prod_id = le32_to_cpu(hotplug->data.unipro_prod_id); - ara_vend_id = le32_to_cpu(hotplug->data.ara_vend_id); - ara_prod_id = le32_to_cpu(hotplug->data.ara_prod_id); intf = gb_interface_create(hd, intf_id); if (!intf) { @@ -298,6 +290,11 @@ static void svc_process_hotplug(struct work_struct *work) goto free_svc_hotplug; } + intf->unipro_mfg_id = le32_to_cpu(hotplug->data.unipro_mfg_id); + intf->unipro_prod_id = le32_to_cpu(hotplug->data.unipro_prod_id); + intf->ara_vend_id = le32_to_cpu(hotplug->data.ara_vend_id); + intf->ara_prod_id = le32_to_cpu(hotplug->data.ara_prod_id); + /* * Create a device id for the interface: * - device id 0 (GB_DEVICE_ID_SVC) belongs to the SVC