linux-stable/drivers/staging/greybus/es2.c

1674 lines
39 KiB
C
Raw Normal View History

/*
* Greybus "AP" USB driver for "ES2" controller chips
*
* Copyright 2014-2015 Google Inc.
* Copyright 2014-2015 Linaro Ltd.
*
* Released under the GPLv2 only.
*/
#include <linux/kthread.h>
#include <linux/sizes.h>
#include <linux/usb.h>
#include <linux/kfifo.h>
#include <linux/debugfs.h>
#include <linux/list.h>
#include <asm/unaligned.h>
#include "greybus.h"
#include "greybus_trace.h"
#include "kernel_ver.h"
#include "connection.h"
/* Default timeout for USB vendor requests. */
#define ES2_USB_CTRL_TIMEOUT 500
/* Default timeout for ARPC CPort requests */
#define ES2_ARPC_CPORT_TIMEOUT 500
/* Fixed CPort numbers */
#define ES2_CPORT_CDSI0 16
#define ES2_CPORT_CDSI1 17
/* Memory sizes for the buffers sent to/from the ES2 controller */
#define ES2_GBUF_MSG_SIZE_MAX 2048
/* Memory sizes for the ARPC buffers */
#define ARPC_OUT_SIZE_MAX U16_MAX
#define ARPC_IN_SIZE_MAX 128
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x18d1, 0x1eaf) },
{ },
};
MODULE_DEVICE_TABLE(usb, id_table);
#define APB1_LOG_SIZE SZ_16K
/* Number of bulk in and bulk out couple */
#define NUM_BULKS 7
/* Expected number of bulk out endpoints */
#define NUM_BULKS_OUT NUM_BULKS
/* Expected number of bulk in endpoints (including ARPC endpoint) */
#define NUM_BULKS_IN (NUM_BULKS + 1)
/*
* Number of CPort IN urbs in flight at any point in time.
* Adjust if we are having stalls in the USB buffer due to not enough urbs in
* flight.
*/
#define NUM_CPORT_IN_URB 4
/* Number of CPort OUT urbs in flight at any point in time.
* Adjust if we get messages saying we are out of urbs in the system log.
*/
#define NUM_CPORT_OUT_URB (8 * NUM_BULKS)
/*
* Number of ARPC in urbs in flight at any point in time.
*/
#define NUM_ARPC_IN_URB 2
/*
* @endpoint: bulk in endpoint for CPort data
* @urb: array of urbs for the CPort in messages
* @buffer: array of buffers for the @cport_in_urb urbs
*/
struct es2_cport_in {
__u8 endpoint;
struct urb *urb[NUM_CPORT_IN_URB];
u8 *buffer[NUM_CPORT_IN_URB];
};
/*
* @endpoint: bulk out endpoint for CPort data
*/
struct es2_cport_out {
__u8 endpoint;
};
/**
* es2_ap_dev - ES2 USB Bridge to AP structure
* @usb_dev: pointer to the USB device we are.
* @usb_intf: pointer to the USB interface we are bound to.
* @hd: pointer to our gb_host_device structure
* @cport_in: endpoint, urbs and buffer for cport in messages
* @cport_out: endpoint for for cport out messages
* @cport_out_urb: array of urbs for the CPort out messages
* @cport_out_urb_busy: array of flags to see if the @cport_out_urb is busy or
* not.
* @cport_out_urb_cancelled: array of flags indicating whether the
* corresponding @cport_out_urb is being cancelled
* @cport_out_urb_lock: locks the @cport_out_urb_busy "list"
*
* @apb_log_task: task pointer for logging thread
* @apb_log_dentry: file system entry for the log file interface
* @apb_log_enable_dentry: file system entry for enabling logging
* @apb_log_fifo: kernel FIFO to carry logged data
* @arpc_urb: array of urbs for the ARPC in messages
* @arpc_buffer: array of buffers for the @arpc_urb urbs
* @arpc_endpoint_in: bulk in endpoint for APBridgeA RPC
* @arpc_id_cycle: gives an unique id to ARPC
* @arpc_lock: locks ARPC list
* @arpcs: list of in progress ARPCs
*/
struct es2_ap_dev {
struct usb_device *usb_dev;
struct usb_interface *usb_intf;
struct gb_host_device *hd;
struct es2_cport_in cport_in[NUM_BULKS];
struct es2_cport_out cport_out[NUM_BULKS];
struct urb *cport_out_urb[NUM_CPORT_OUT_URB];
bool cport_out_urb_busy[NUM_CPORT_OUT_URB];
bool cport_out_urb_cancelled[NUM_CPORT_OUT_URB];
spinlock_t cport_out_urb_lock;
bool cdsi1_in_use;
int *cport_to_ep;
struct task_struct *apb_log_task;
struct dentry *apb_log_dentry;
struct dentry *apb_log_enable_dentry;
DECLARE_KFIFO(apb_log_fifo, char, APB1_LOG_SIZE);
__u8 arpc_endpoint_in;
struct urb *arpc_urb[NUM_ARPC_IN_URB];
u8 *arpc_buffer[NUM_ARPC_IN_URB];
int arpc_id_cycle;
spinlock_t arpc_lock;
struct list_head arpcs;
};
/**
* cport_to_ep - information about cport to endpoints mapping
* @cport_id: the id of cport to map to endpoints
* @endpoint_in: the endpoint number to use for in transfer
* @endpoint_out: he endpoint number to use for out transfer
*/
struct cport_to_ep {
__le16 cport_id;
__u8 endpoint_in;
__u8 endpoint_out;
};
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
/**
* timesync_enable_request - Enable timesync in an APBridge
* @count: number of TimeSync Pulses to expect
* @frame_time: the initial FrameTime at the first TimeSync Pulse
* @strobe_delay: the expected delay in microseconds between each TimeSync Pulse
* @refclk: The AP mandated reference clock to run FrameTime at
*/
struct timesync_enable_request {
__u8 count;
__le64 frame_time;
__le32 strobe_delay;
__le32 refclk;
} __packed;
/**
* timesync_authoritative_request - Transmit authoritative FrameTime to APBridge
* @frame_time: An array of authoritative FrameTimes provided by the SVC
* and relayed to the APBridge by the AP
*/
struct timesync_authoritative_request {
__le64 frame_time[GB_TIMESYNC_MAX_STROBES];
} __packed;
struct arpc {
struct list_head list;
struct arpc_request_message *req;
struct arpc_response_message *resp;
struct completion response_received;
bool active;
};
static inline struct es2_ap_dev *hd_to_es2(struct gb_host_device *hd)
{
return (struct es2_ap_dev *)&hd->hd_priv;
}
static void cport_out_callback(struct urb *urb);
static void usb_log_enable(struct es2_ap_dev *es2);
static void usb_log_disable(struct es2_ap_dev *es2);
static int arpc_sync(struct es2_ap_dev *es2, u8 type, void *payload,
size_t size, int *result, unsigned int timeout);
/* Get the endpoints pair mapped to the cport */
static int cport_to_ep_pair(struct es2_ap_dev *es2, u16 cport_id)
{
if (cport_id >= es2->hd->num_cports)
return 0;
return es2->cport_to_ep[cport_id];
}
/* Disable for now until we work all of this out to keep a warning-free build */
#if 0
/* Test if the endpoints pair is already mapped to a cport */
static int ep_pair_in_use(struct es2_ap_dev *es2, int ep_pair)
{
int i;
for (i = 0; i < es2->hd->num_cports; i++) {
if (es2->cport_to_ep[i] == ep_pair)
return 1;
}
return 0;
}
/* Configure the endpoint mapping and send the request to APBridge */
static int map_cport_to_ep(struct es2_ap_dev *es2,
u16 cport_id, int ep_pair)
{
int retval;
struct cport_to_ep *cport_to_ep;
if (ep_pair < 0 || ep_pair >= NUM_BULKS)
return -EINVAL;
if (cport_id >= es2->hd->num_cports)
return -EINVAL;
if (ep_pair && ep_pair_in_use(es2, ep_pair))
return -EINVAL;
cport_to_ep = kmalloc(sizeof(*cport_to_ep), GFP_KERNEL);
if (!cport_to_ep)
return -ENOMEM;
es2->cport_to_ep[cport_id] = ep_pair;
cport_to_ep->cport_id = cpu_to_le16(cport_id);
cport_to_ep->endpoint_in = es2->cport_in[ep_pair].endpoint;
cport_to_ep->endpoint_out = es2->cport_out[ep_pair].endpoint;
retval = usb_control_msg(es2->usb_dev,
usb_sndctrlpipe(es2->usb_dev, 0),
GB_APB_REQUEST_EP_MAPPING,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
0x00, 0x00,
(char *)cport_to_ep,
sizeof(*cport_to_ep),
ES2_USB_CTRL_TIMEOUT);
if (retval == sizeof(*cport_to_ep))
retval = 0;
kfree(cport_to_ep);
return retval;
}
/* Unmap a cport: use the muxed endpoints pair */
static int unmap_cport(struct es2_ap_dev *es2, u16 cport_id)
{
return map_cport_to_ep(es2, cport_id, 0);
}
#endif
static int output_sync(struct es2_ap_dev *es2, void *req, u16 size, u8 cmd)
{
struct usb_device *udev = es2->usb_dev;
u8 *data;
int retval;
data = kmalloc(size, GFP_KERNEL);
if (!data)
return -ENOMEM;
memcpy(data, req, size);
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
cmd,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE,
0, 0, data, size, ES2_USB_CTRL_TIMEOUT);
if (retval < 0)
dev_err(&udev->dev, "%s: return error %d\n", __func__, retval);
else
retval = 0;
kfree(data);
return retval;
}
static void ap_urb_complete(struct urb *urb)
{
struct usb_ctrlrequest *dr = urb->context;
kfree(dr);
usb_free_urb(urb);
}
static int output_async(struct es2_ap_dev *es2, void *req, u16 size, u8 cmd)
{
struct usb_device *udev = es2->usb_dev;
struct urb *urb;
struct usb_ctrlrequest *dr;
u8 *buf;
int retval;
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!urb)
return -ENOMEM;
dr = kmalloc(sizeof(*dr) + size, GFP_ATOMIC);
if (!dr) {
usb_free_urb(urb);
return -ENOMEM;
}
buf = (u8 *)dr + sizeof(*dr);
memcpy(buf, req, size);
dr->bRequest = cmd;
dr->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE;
dr->wValue = 0;
dr->wIndex = 0;
dr->wLength = cpu_to_le16(size);
usb_fill_control_urb(urb, udev, usb_sndctrlpipe(udev, 0),
(unsigned char *)dr, buf, size,
ap_urb_complete, dr);
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval) {
usb_free_urb(urb);
kfree(dr);
}
return retval;
}
static int output(struct gb_host_device *hd, void *req, u16 size, u8 cmd,
bool async)
{
struct es2_ap_dev *es2 = hd_to_es2(hd);
if (async)
return output_async(es2, req, size, cmd);
return output_sync(es2, req, size, cmd);
}
static int es2_cport_in_enable(struct es2_ap_dev *es2,
struct es2_cport_in *cport_in)
{
struct urb *urb;
int ret;
int i;
for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
urb = cport_in->urb[i];
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(&es2->usb_dev->dev,
"failed to submit in-urb: %d\n", ret);
goto err_kill_urbs;
}
}
return 0;
err_kill_urbs:
for (--i; i >= 0; --i) {
urb = cport_in->urb[i];
usb_kill_urb(urb);
}
return ret;
}
static void es2_cport_in_disable(struct es2_ap_dev *es2,
struct es2_cport_in *cport_in)
{
struct urb *urb;
int i;
for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
urb = cport_in->urb[i];
usb_kill_urb(urb);
}
}
static int es2_arpc_in_enable(struct es2_ap_dev *es2)
{
struct urb *urb;
int ret;
int i;
for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
urb = es2->arpc_urb[i];
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(&es2->usb_dev->dev,
"failed to submit arpc in-urb: %d\n", ret);
goto err_kill_urbs;
}
}
return 0;
err_kill_urbs:
for (--i; i >= 0; --i) {
urb = es2->arpc_urb[i];
usb_kill_urb(urb);
}
return ret;
}
static void es2_arpc_in_disable(struct es2_ap_dev *es2)
{
struct urb *urb;
int i;
for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
urb = es2->arpc_urb[i];
usb_kill_urb(urb);
}
}
static struct urb *next_free_urb(struct es2_ap_dev *es2, gfp_t gfp_mask)
{
struct urb *urb = NULL;
unsigned long flags;
int i;
spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
/* Look in our pool of allocated urbs first, as that's the "fastest" */
for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
if (es2->cport_out_urb_busy[i] == false &&
es2->cport_out_urb_cancelled[i] == false) {
es2->cport_out_urb_busy[i] = true;
urb = es2->cport_out_urb[i];
break;
}
}
spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
if (urb)
return urb;
/*
* Crap, pool is empty, complain to the syslog and go allocate one
* dynamically as we have to succeed.
*/
dev_dbg(&es2->usb_dev->dev,
"No free CPort OUT urbs, having to dynamically allocate one!\n");
return usb_alloc_urb(0, gfp_mask);
}
static void free_urb(struct es2_ap_dev *es2, struct urb *urb)
{
unsigned long flags;
int i;
/*
* See if this was an urb in our pool, if so mark it "free", otherwise
* we need to free it ourselves.
*/
spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
if (urb == es2->cport_out_urb[i]) {
es2->cport_out_urb_busy[i] = false;
urb = NULL;
break;
}
}
spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
/* If urb is not NULL, then we need to free this urb */
usb_free_urb(urb);
}
/*
* We (ab)use the operation-message header pad bytes to transfer the
* cport id in order to minimise overhead.
*/
static void
gb_message_cport_pack(struct gb_operation_msg_hdr *header, u16 cport_id)
{
header->pad[0] = cport_id;
}
/* Clear the pad bytes used for the CPort id */
static void gb_message_cport_clear(struct gb_operation_msg_hdr *header)
{
header->pad[0] = 0;
}
/* Extract the CPort id packed into the header, and clear it */
static u16 gb_message_cport_unpack(struct gb_operation_msg_hdr *header)
{
u16 cport_id = header->pad[0];
gb_message_cport_clear(header);
return cport_id;
}
/*
* Returns zero if the message was successfully queued, or a negative errno
* otherwise.
*/
static int message_send(struct gb_host_device *hd, u16 cport_id,
struct gb_message *message, gfp_t gfp_mask)
{
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
size_t buffer_size;
int retval;
struct urb *urb;
int ep_pair;
unsigned long flags;
/*
* The data actually transferred will include an indication
* of where the data should be sent. Do one last check of
* the target CPort id before filling it in.
*/
if (!cport_id_valid(hd, cport_id)) {
dev_err(&udev->dev, "invalid cport %u\n", cport_id);
return -EINVAL;
}
/* Find a free urb */
urb = next_free_urb(es2, gfp_mask);
if (!urb)
return -ENOMEM;
spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
message->hcpriv = urb;
spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
/* Pack the cport id into the message header */
gb_message_cport_pack(message->header, cport_id);
buffer_size = sizeof(*message->header) + message->payload_size;
ep_pair = cport_to_ep_pair(es2, cport_id);
usb_fill_bulk_urb(urb, udev,
usb_sndbulkpipe(udev,
es2->cport_out[ep_pair].endpoint),
message->buffer, buffer_size,
cport_out_callback, message);
urb->transfer_flags |= URB_ZERO_PACKET;
trace_gb_message_submit(message);
retval = usb_submit_urb(urb, gfp_mask);
if (retval) {
dev_err(&udev->dev, "failed to submit out-urb: %d\n", retval);
spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
message->hcpriv = NULL;
spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
free_urb(es2, urb);
gb_message_cport_clear(message->header);
return retval;
}
return 0;
}
/*
* Can not be called in atomic context.
*/
static void message_cancel(struct gb_message *message)
{
struct gb_host_device *hd = message->operation->connection->hd;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct urb *urb;
int i;
might_sleep();
spin_lock_irq(&es2->cport_out_urb_lock);
urb = message->hcpriv;
/* Prevent dynamically allocated urb from being deallocated. */
usb_get_urb(urb);
/* Prevent pre-allocated urb from being reused. */
for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
if (urb == es2->cport_out_urb[i]) {
es2->cport_out_urb_cancelled[i] = true;
break;
}
}
spin_unlock_irq(&es2->cport_out_urb_lock);
usb_kill_urb(urb);
if (i < NUM_CPORT_OUT_URB) {
spin_lock_irq(&es2->cport_out_urb_lock);
es2->cport_out_urb_cancelled[i] = false;
spin_unlock_irq(&es2->cport_out_urb_lock);
}
usb_free_urb(urb);
}
static int cport_reset(struct gb_host_device *hd, u16 cport_id)
{
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
struct arpc_cport_reset_req req;
int retval;
int result;
switch (cport_id) {
case GB_SVC_CPORT_ID:
case ES2_CPORT_CDSI0:
case ES2_CPORT_CDSI1:
return 0;
}
req.cport_id = cpu_to_le16(cport_id);
retval = arpc_sync(es2, ARPC_TYPE_CPORT_RESET, &req, sizeof(req),
&result, ES2_ARPC_CPORT_TIMEOUT);
if (retval == -EREMOTEIO) {
dev_err(&udev->dev, "failed to reset cport %u: %d\n", cport_id,
result);
}
return retval;
}
static int es2_cport_allocate(struct gb_host_device *hd, int cport_id,
unsigned long flags)
{
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct ida *id_map = &hd->cport_id_map;
int ida_start, ida_end;
switch (cport_id) {
case ES2_CPORT_CDSI0:
case ES2_CPORT_CDSI1:
dev_err(&hd->dev, "cport %d not available\n", cport_id);
return -EBUSY;
}
if (flags & GB_CONNECTION_FLAG_OFFLOADED &&
flags & GB_CONNECTION_FLAG_CDSI1) {
if (es2->cdsi1_in_use) {
dev_err(&hd->dev, "CDSI1 already in use\n");
return -EBUSY;
}
es2->cdsi1_in_use = true;
return ES2_CPORT_CDSI1;
}
if (cport_id < 0) {
ida_start = 0;
ida_end = hd->num_cports;
} else if (cport_id < hd->num_cports) {
ida_start = cport_id;
ida_end = cport_id + 1;
} else {
dev_err(&hd->dev, "cport %d not available\n", cport_id);
return -EINVAL;
}
return ida_simple_get(id_map, ida_start, ida_end, GFP_KERNEL);
}
static void es2_cport_release(struct gb_host_device *hd, u16 cport_id)
{
struct es2_ap_dev *es2 = hd_to_es2(hd);
switch (cport_id) {
case ES2_CPORT_CDSI1:
es2->cdsi1_in_use = false;
return;
}
ida_simple_remove(&hd->cport_id_map, cport_id);
}
static int cport_enable(struct gb_host_device *hd, u16 cport_id,
unsigned long flags)
{
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
struct gb_apb_request_cport_flags *req;
u32 connection_flags;
int ret;
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return -ENOMEM;
connection_flags = 0;
if (flags & GB_CONNECTION_FLAG_CONTROL)
connection_flags |= GB_APB_CPORT_FLAG_CONTROL;
if (flags & GB_CONNECTION_FLAG_HIGH_PRIO)
connection_flags |= GB_APB_CPORT_FLAG_HIGH_PRIO;
req->flags = cpu_to_le32(connection_flags);
dev_dbg(&hd->dev, "%s - cport = %u, flags = %02x\n", __func__,
cport_id, connection_flags);
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
GB_APB_REQUEST_CPORT_FLAGS,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, cport_id, 0,
req, sizeof(*req), ES2_USB_CTRL_TIMEOUT);
if (ret != sizeof(*req)) {
dev_err(&udev->dev, "failed to set cport flags for port %d\n",
cport_id);
if (ret >= 0)
ret = -EIO;
goto out;
}
ret = 0;
out:
kfree(req);
return ret;
}
static int cport_disable(struct gb_host_device *hd, u16 cport_id)
{
int retval;
retval = cport_reset(hd, cport_id);
if (retval)
return retval;
return 0;
}
static int latency_tag_enable(struct gb_host_device *hd, u16 cport_id)
{
int retval;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
GB_APB_REQUEST_LATENCY_TAG_EN,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, cport_id, 0, NULL,
0, ES2_USB_CTRL_TIMEOUT);
if (retval < 0)
dev_err(&udev->dev, "Cannot enable latency tag for cport %d\n",
cport_id);
return retval;
}
static int latency_tag_disable(struct gb_host_device *hd, u16 cport_id)
{
int retval;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
GB_APB_REQUEST_LATENCY_TAG_DIS,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, cport_id, 0, NULL,
0, ES2_USB_CTRL_TIMEOUT);
if (retval < 0)
dev_err(&udev->dev, "Cannot disable latency tag for cport %d\n",
cport_id);
return retval;
}
static int cport_features_enable(struct gb_host_device *hd, u16 cport_id)
{
int retval;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
GB_APB_REQUEST_CPORT_FEAT_EN,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, cport_id, 0, NULL,
0, ES2_USB_CTRL_TIMEOUT);
if (retval < 0)
dev_err(&udev->dev, "Cannot enable CPort features for cport %u: %d\n",
cport_id, retval);
return retval;
}
static int cport_features_disable(struct gb_host_device *hd, u16 cport_id)
{
int retval;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
GB_APB_REQUEST_CPORT_FEAT_DIS,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, cport_id, 0, NULL,
0, ES2_USB_CTRL_TIMEOUT);
if (retval < 0)
dev_err(&udev->dev,
"Cannot disable CPort features for cport %u: %d\n",
cport_id, retval);
return retval;
}
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
static int timesync_enable(struct gb_host_device *hd, u8 count,
u64 frame_time, u32 strobe_delay, u32 refclk)
{
int retval;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
struct gb_control_timesync_enable_request *request;
request = kzalloc(sizeof(*request), GFP_KERNEL);
if (!request)
return -ENOMEM;
request->count = count;
request->frame_time = cpu_to_le64(frame_time);
request->strobe_delay = cpu_to_le32(strobe_delay);
request->refclk = cpu_to_le32(refclk);
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
REQUEST_TIMESYNC_ENABLE,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, 0, 0, request,
sizeof(*request), ES2_USB_CTRL_TIMEOUT);
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
if (retval < 0)
dev_err(&udev->dev, "Cannot enable timesync %d\n", retval);
kfree(request);
return retval;
}
static int timesync_disable(struct gb_host_device *hd)
{
int retval;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
REQUEST_TIMESYNC_DISABLE,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, 0, 0, NULL,
0, ES2_USB_CTRL_TIMEOUT);
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
if (retval < 0)
dev_err(&udev->dev, "Cannot disable timesync %d\n", retval);
return retval;
}
static int timesync_authoritative(struct gb_host_device *hd, u64 *frame_time)
{
int retval, i;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
struct timesync_authoritative_request *request;
request = kzalloc(sizeof(*request), GFP_KERNEL);
if (!request)
return -ENOMEM;
for (i = 0; i < GB_TIMESYNC_MAX_STROBES; i++)
request->frame_time[i] = cpu_to_le64(frame_time[i]);
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
REQUEST_TIMESYNC_AUTHORITATIVE,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, 0, 0, request,
sizeof(*request), ES2_USB_CTRL_TIMEOUT);
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
if (retval < 0)
dev_err(&udev->dev, "Cannot timesync authoritative out %d\n", retval);
kfree(request);
return retval;
}
static int timesync_get_last_event(struct gb_host_device *hd, u64 *frame_time)
{
int retval;
struct es2_ap_dev *es2 = hd_to_es2(hd);
struct usb_device *udev = es2->usb_dev;
__le64 *response_frame_time;
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
response_frame_time = kzalloc(sizeof(*response_frame_time), GFP_KERNEL);
if (!response_frame_time)
return -ENOMEM;
retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
REQUEST_TIMESYNC_GET_LAST_EVENT,
USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, 0, 0, response_frame_time,
sizeof(*response_frame_time),
ES2_USB_CTRL_TIMEOUT);
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
if (retval != sizeof(*response_frame_time)) {
dev_err(&udev->dev, "Cannot get last TimeSync event: %d\n",
retval);
if (retval >= 0)
retval = -EIO;
goto out;
}
*frame_time = le64_to_cpu(*response_frame_time);
retval = 0;
out:
kfree(response_frame_time);
return retval;
}
static struct gb_hd_driver es2_driver = {
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
.hd_priv_size = sizeof(struct es2_ap_dev),
.message_send = message_send,
.message_cancel = message_cancel,
.cport_allocate = es2_cport_allocate,
.cport_release = es2_cport_release,
.cport_enable = cport_enable,
.cport_disable = cport_disable,
greybus: hd: Add TimeSync APBridge commands This patch adds a number of USB Vendor commands to es2.c to enable TimeSync in the bridge. Adds: - es2.c::timesync_enable(u8 count, u64 frame_time, u32 strobe_delay, u32 refclk); Commands APBx to enable timers and clocks to track a pulse-train of incoming TIME_SYNC strobes with strobe_delay microseconds between each. Provides the reference clock the AP is using to track FrameTime. It is the responsibility of APBx to adequately track the FrameTime based on the indicated AP refclk. Once this command has succeeded APBx may not transition to a low-power state were FrameTime counters stop. This function is initiated from the timesync worker thread logic when re-synchronizing frame-time throughout the system. TimeSync is at this time enabled for all APBx active in the system i.e. currently APB2 will not receive TimeSync commands until it becomes a registered host-device in Greybus. - es2.c::timesync_disable(void) Commands APBx to discontinue tracking of FrameTime. After this operation completes APBx may transition to a low-power state where timer-clocks stop operating. - es2.c::timesync_authoritative(u64 *frame_time) Provides an authoritative time for each TIME_SYNC strobe to APBx. APBx must align its local FrameTime to the authoritative clock. - es2.c::timesync_get_last_event(u64 *frame_time) Returns the FrameTime at the last SVC_TIMESYNC_PING to the AP Module. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2016-05-15 18:37:48 +00:00
.latency_tag_enable = latency_tag_enable,
.latency_tag_disable = latency_tag_disable,
.output = output,
.cport_features_enable = cport_features_enable,
.cport_features_disable = cport_features_disable,
.timesync_enable = timesync_enable,
.timesync_disable = timesync_disable,
.timesync_authoritative = timesync_authoritative,
.timesync_get_last_event = timesync_get_last_event,
};
/* Common function to report consistent warnings based on URB status */
static int check_urb_status(struct urb *urb)
{
struct device *dev = &urb->dev->dev;
int status = urb->status;
switch (status) {
case 0:
return 0;
case -EOVERFLOW:
dev_err(dev, "%s: overflow actual length is %d\n",
__func__, urb->actual_length);
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
case -EILSEQ:
case -EPROTO:
/* device is gone, stop sending */
return status;
}
dev_err(dev, "%s: unknown status %d\n", __func__, status);
return -EAGAIN;
}
static void es2_destroy(struct es2_ap_dev *es2)
{
struct usb_device *udev;
int bulk_in;
int i;
debugfs_remove(es2->apb_log_enable_dentry);
usb_log_disable(es2);
/* Tear down everything! */
for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
struct urb *urb = es2->cport_out_urb[i];
if (!urb)
break;
usb_kill_urb(urb);
usb_free_urb(urb);
es2->cport_out_urb[i] = NULL;
es2->cport_out_urb_busy[i] = false; /* just to be anal */
}
for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
struct urb *urb = es2->arpc_urb[i];
if (!urb)
break;
usb_free_urb(urb);
kfree(es2->arpc_buffer[i]);
es2->arpc_buffer[i] = NULL;
}
for (bulk_in = 0; bulk_in < NUM_BULKS; bulk_in++) {
struct es2_cport_in *cport_in = &es2->cport_in[bulk_in];
for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
struct urb *urb = cport_in->urb[i];
if (!urb)
break;
usb_free_urb(urb);
kfree(cport_in->buffer[i]);
cport_in->buffer[i] = NULL;
}
}
kfree(es2->cport_to_ep);
/* release reserved CDSI0 and CDSI1 cports */
gb_hd_cport_release_reserved(es2->hd, ES2_CPORT_CDSI1);
gb_hd_cport_release_reserved(es2->hd, ES2_CPORT_CDSI0);
udev = es2->usb_dev;
gb_hd_put(es2->hd);
usb_put_dev(udev);
}
static void cport_in_callback(struct urb *urb)
{
struct gb_host_device *hd = urb->context;
struct device *dev = &urb->dev->dev;
struct gb_operation_msg_hdr *header;
int status = check_urb_status(urb);
int retval;
u16 cport_id;
if (status) {
if ((status == -EAGAIN) || (status == -EPROTO))
goto exit;
/* The urb is being unlinked */
if (status == -ENOENT || status == -ESHUTDOWN)
return;
dev_err(dev, "urb cport in error %d (dropped)\n", status);
return;
}
if (urb->actual_length < sizeof(*header)) {
dev_err(dev, "short message received\n");
goto exit;
}
/* Extract the CPort id, which is packed in the message header */
header = urb->transfer_buffer;
cport_id = gb_message_cport_unpack(header);
if (cport_id_valid(hd, cport_id)) {
greybus_data_rcvd(hd, cport_id, urb->transfer_buffer,
urb->actual_length);
} else {
dev_err(dev, "invalid cport id %u received\n", cport_id);
}
exit:
/* put our urb back in the request pool */
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval)
dev_err(dev, "failed to resubmit in-urb: %d\n", retval);
}
static void cport_out_callback(struct urb *urb)
{
struct gb_message *message = urb->context;
struct gb_host_device *hd = message->operation->connection->hd;
struct es2_ap_dev *es2 = hd_to_es2(hd);
int status = check_urb_status(urb);
unsigned long flags;
gb_message_cport_clear(message->header);
spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
message->hcpriv = NULL;
spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
/*
* Tell the submitter that the message send (attempt) is
* complete, and report the status.
*/
greybus_message_sent(hd, message, status);
free_urb(es2, urb);
}
static struct arpc *arpc_alloc(void *payload, u16 size, u8 type)
{
struct arpc *rpc;
if (size + sizeof(*rpc->req) > ARPC_OUT_SIZE_MAX)
return NULL;
rpc = kzalloc(sizeof(*rpc), GFP_KERNEL);
if (!rpc)
return NULL;
INIT_LIST_HEAD(&rpc->list);
rpc->req = kzalloc(sizeof(*rpc->req) + size, GFP_KERNEL);
if (!rpc->req)
goto err_free_rpc;
rpc->resp = kzalloc(sizeof(*rpc->resp), GFP_KERNEL);
if (!rpc->resp)
goto err_free_req;
rpc->req->type = type;
rpc->req->size = cpu_to_le16(sizeof(rpc->req) + size);
memcpy(rpc->req->data, payload, size);
init_completion(&rpc->response_received);
return rpc;
err_free_req:
kfree(rpc->req);
err_free_rpc:
kfree(rpc);
return NULL;
}
static void arpc_free(struct arpc *rpc)
{
kfree(rpc->req);
kfree(rpc->resp);
kfree(rpc);
}
static struct arpc *arpc_find(struct es2_ap_dev *es2, __le16 id)
{
struct arpc *rpc;
list_for_each_entry(rpc, &es2->arpcs, list) {
if (rpc->req->id == id)
return rpc;
}
return NULL;
}
static void arpc_add(struct es2_ap_dev *es2, struct arpc *rpc)
{
rpc->active = true;
rpc->req->id = cpu_to_le16(es2->arpc_id_cycle++);
list_add_tail(&rpc->list, &es2->arpcs);
}
static void arpc_del(struct es2_ap_dev *es2, struct arpc *rpc)
{
if (rpc->active) {
rpc->active = false;
list_del(&rpc->list);
}
}
static int arpc_send(struct es2_ap_dev *es2, struct arpc *rpc, int timeout)
{
struct usb_device *udev = es2->usb_dev;
int retval;
retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
APBA_REQUEST_ARPC_RUN,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE,
0, 0,
rpc->req, le16_to_cpu(rpc->req->size),
ES2_USB_CTRL_TIMEOUT);
if (retval != le16_to_cpu(rpc->req->size)) {
dev_err(&udev->dev,
"failed to send ARPC request %d: %d\n",
rpc->req->type, retval);
if (retval > 0)
retval = -EIO;
return retval;
}
return 0;
}
static int arpc_sync(struct es2_ap_dev *es2, u8 type, void *payload,
size_t size, int *result, unsigned int timeout)
{
struct arpc *rpc;
unsigned long flags;
int retval;
if (result)
*result = 0;
rpc = arpc_alloc(payload, size, type);
if (!rpc)
return -ENOMEM;
spin_lock_irqsave(&es2->arpc_lock, flags);
arpc_add(es2, rpc);
spin_unlock_irqrestore(&es2->arpc_lock, flags);
retval = arpc_send(es2, rpc, timeout);
if (retval)
goto out_arpc_del;
retval = wait_for_completion_interruptible_timeout(
&rpc->response_received,
msecs_to_jiffies(timeout));
if (retval <= 0) {
if (!retval)
retval = -ETIMEDOUT;
goto out_arpc_del;
}
if (rpc->resp->result) {
retval = -EREMOTEIO;
if (result)
*result = rpc->resp->result;
} else {
retval = 0;
}
out_arpc_del:
spin_lock_irqsave(&es2->arpc_lock, flags);
arpc_del(es2, rpc);
spin_unlock_irqrestore(&es2->arpc_lock, flags);
arpc_free(rpc);
if (retval < 0 && retval != -EREMOTEIO) {
dev_err(&es2->usb_dev->dev,
"failed to execute ARPC: %d\n", retval);
}
return retval;
}
static void arpc_in_callback(struct urb *urb)
{
struct es2_ap_dev *es2 = urb->context;
struct device *dev = &urb->dev->dev;
int status = check_urb_status(urb);
struct arpc *rpc;
struct arpc_response_message *resp;
unsigned long flags;
int retval;
if (status) {
if ((status == -EAGAIN) || (status == -EPROTO))
goto exit;
/* The urb is being unlinked */
if (status == -ENOENT || status == -ESHUTDOWN)
return;
dev_err(dev, "arpc in-urb error %d (dropped)\n", status);
return;
}
if (urb->actual_length < sizeof(*resp)) {
dev_err(dev, "short aprc response received\n");
goto exit;
}
resp = urb->transfer_buffer;
spin_lock_irqsave(&es2->arpc_lock, flags);
rpc = arpc_find(es2, resp->id);
if (!rpc) {
dev_err(dev, "invalid arpc response id received: %u\n",
le16_to_cpu(resp->id));
spin_unlock_irqrestore(&es2->arpc_lock, flags);
goto exit;
}
arpc_del(es2, rpc);
memcpy(rpc->resp, resp, sizeof(*resp));
complete(&rpc->response_received);
spin_unlock_irqrestore(&es2->arpc_lock, flags);
exit:
/* put our urb back in the request pool */
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval)
dev_err(dev, "failed to resubmit arpc in-urb: %d\n", retval);
}
#define APB1_LOG_MSG_SIZE 64
static void apb_log_get(struct es2_ap_dev *es2, char *buf)
{
int retval;
do {
retval = usb_control_msg(es2->usb_dev,
usb_rcvctrlpipe(es2->usb_dev, 0),
GB_APB_REQUEST_LOG,
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
0x00, 0x00,
buf,
APB1_LOG_MSG_SIZE,
ES2_USB_CTRL_TIMEOUT);
if (retval > 0)
kfifo_in(&es2->apb_log_fifo, buf, retval);
} while (retval > 0);
}
static int apb_log_poll(void *data)
{
struct es2_ap_dev *es2 = data;
char *buf;
buf = kmalloc(APB1_LOG_MSG_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
while (!kthread_should_stop()) {
msleep(1000);
apb_log_get(es2, buf);
}
kfree(buf);
return 0;
}
static ssize_t apb_log_read(struct file *f, char __user *buf,
size_t count, loff_t *ppos)
{
struct es2_ap_dev *es2 = f->f_inode->i_private;
ssize_t ret;
size_t copied;
char *tmp_buf;
if (count > APB1_LOG_SIZE)
count = APB1_LOG_SIZE;
tmp_buf = kmalloc(count, GFP_KERNEL);
if (!tmp_buf)
return -ENOMEM;
copied = kfifo_out(&es2->apb_log_fifo, tmp_buf, count);
ret = simple_read_from_buffer(buf, count, ppos, tmp_buf, copied);
kfree(tmp_buf);
return ret;
}
static const struct file_operations apb_log_fops = {
.read = apb_log_read,
};
static void usb_log_enable(struct es2_ap_dev *es2)
{
if (!IS_ERR_OR_NULL(es2->apb_log_task))
return;
/* get log from APB1 */
es2->apb_log_task = kthread_run(apb_log_poll, es2, "apb_log");
if (IS_ERR(es2->apb_log_task))
return;
/* XXX We will need to rename this per APB */
es2->apb_log_dentry = debugfs_create_file("apb_log", S_IRUGO,
gb_debugfs_get(), es2,
&apb_log_fops);
}
static void usb_log_disable(struct es2_ap_dev *es2)
{
if (IS_ERR_OR_NULL(es2->apb_log_task))
return;
debugfs_remove(es2->apb_log_dentry);
es2->apb_log_dentry = NULL;
kthread_stop(es2->apb_log_task);
es2->apb_log_task = NULL;
}
static ssize_t apb_log_enable_read(struct file *f, char __user *buf,
size_t count, loff_t *ppos)
{
struct es2_ap_dev *es2 = f->f_inode->i_private;
int enable = !IS_ERR_OR_NULL(es2->apb_log_task);
char tmp_buf[3];
sprintf(tmp_buf, "%d\n", enable);
return simple_read_from_buffer(buf, count, ppos, tmp_buf, 3);
}
static ssize_t apb_log_enable_write(struct file *f, const char __user *buf,
size_t count, loff_t *ppos)
{
int enable;
ssize_t retval;
struct es2_ap_dev *es2 = f->f_inode->i_private;
retval = kstrtoint_from_user(buf, count, 10, &enable);
if (retval)
return retval;
if (enable)
usb_log_enable(es2);
else
usb_log_disable(es2);
return count;
}
static const struct file_operations apb_log_enable_fops = {
.read = apb_log_enable_read,
.write = apb_log_enable_write,
};
static int apb_get_cport_count(struct usb_device *udev)
{
int retval;
__le16 *cport_count;
cport_count = kzalloc(sizeof(*cport_count), GFP_KERNEL);
if (!cport_count)
return -ENOMEM;
retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
GB_APB_REQUEST_CPORT_COUNT,
USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_INTERFACE, 0, 0, cport_count,
sizeof(*cport_count), ES2_USB_CTRL_TIMEOUT);
if (retval != sizeof(*cport_count)) {
dev_err(&udev->dev, "Cannot retrieve CPort count: %d\n",
retval);
if (retval >= 0)
retval = -EIO;
goto out;
}
retval = le16_to_cpu(*cport_count);
/* We need to fit a CPort ID in one byte of a message header */
if (retval > U8_MAX) {
retval = U8_MAX;
dev_warn(&udev->dev, "Limiting number of CPorts to U8_MAX\n");
}
out:
kfree(cport_count);
return retval;
}
/*
* The ES2 USB Bridge device has 15 endpoints
* 1 Control - usual USB stuff + AP -> APBridgeA messages
* 7 Bulk IN - CPort data in
* 7 Bulk OUT - CPort data out
*/
static int ap_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct es2_ap_dev *es2;
struct gb_host_device *hd;
struct usb_device *udev;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int bulk_in = 0;
int bulk_out = 0;
int retval;
int i;
int num_cports;
udev = usb_get_dev(interface_to_usbdev(interface));
num_cports = apb_get_cport_count(udev);
if (num_cports < 0) {
usb_put_dev(udev);
dev_err(&udev->dev, "Cannot retrieve CPort count: %d\n",
num_cports);
return num_cports;
}
hd = gb_hd_create(&es2_driver, &udev->dev, ES2_GBUF_MSG_SIZE_MAX,
num_cports);
if (IS_ERR(hd)) {
usb_put_dev(udev);
return PTR_ERR(hd);
}
es2 = hd_to_es2(hd);
es2->hd = hd;
es2->usb_intf = interface;
es2->usb_dev = udev;
spin_lock_init(&es2->cport_out_urb_lock);
INIT_KFIFO(es2->apb_log_fifo);
usb_set_intfdata(interface, es2);
/*
* Reserve the CDSI0 and CDSI1 CPorts so they won't be allocated
* dynamically.
*/
retval = gb_hd_cport_reserve(hd, ES2_CPORT_CDSI0);
if (retval)
goto error;
retval = gb_hd_cport_reserve(hd, ES2_CPORT_CDSI1);
if (retval)
goto error;
es2->cport_to_ep = kcalloc(hd->num_cports, sizeof(*es2->cport_to_ep),
GFP_KERNEL);
if (!es2->cport_to_ep) {
retval = -ENOMEM;
goto error;
}
/* find all bulk endpoints */
iface_desc = interface->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (usb_endpoint_is_bulk_in(endpoint)) {
if (bulk_in < NUM_BULKS)
es2->cport_in[bulk_in].endpoint =
endpoint->bEndpointAddress;
else
es2->arpc_endpoint_in =
endpoint->bEndpointAddress;
bulk_in++;
} else if (usb_endpoint_is_bulk_out(endpoint)) {
es2->cport_out[bulk_out++].endpoint =
endpoint->bEndpointAddress;
} else {
dev_err(&udev->dev,
"Unknown endpoint type found, address 0x%02x\n",
endpoint->bEndpointAddress);
}
}
if (bulk_in != NUM_BULKS_IN || bulk_out != NUM_BULKS_OUT) {
dev_err(&udev->dev, "Not enough endpoints found in device, aborting!\n");
retval = -ENODEV;
goto error;
}
/* Allocate buffers for our cport in messages */
for (bulk_in = 0; bulk_in < NUM_BULKS; bulk_in++) {
struct es2_cport_in *cport_in = &es2->cport_in[bulk_in];
for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
struct urb *urb;
u8 *buffer;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
buffer = kmalloc(ES2_GBUF_MSG_SIZE_MAX, GFP_KERNEL);
if (!buffer) {
retval = -ENOMEM;
goto error;
}
usb_fill_bulk_urb(urb, udev,
usb_rcvbulkpipe(udev,
cport_in->endpoint),
buffer, ES2_GBUF_MSG_SIZE_MAX,
cport_in_callback, hd);
cport_in->urb[i] = urb;
cport_in->buffer[i] = buffer;
}
}
/* Allocate buffers for ARPC in messages */
for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
struct urb *urb;
u8 *buffer;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
buffer = kmalloc(ARPC_IN_SIZE_MAX, GFP_KERNEL);
if (!buffer) {
retval = -ENOMEM;
goto error;
}
usb_fill_bulk_urb(urb, udev,
usb_rcvbulkpipe(udev,
es2->arpc_endpoint_in),
buffer, ARPC_IN_SIZE_MAX,
arpc_in_callback, es2);
es2->arpc_urb[i] = urb;
es2->arpc_buffer[i] = buffer;
}
/* Allocate urbs for our CPort OUT messages */
for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
struct urb *urb;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
es2->cport_out_urb[i] = urb;
es2->cport_out_urb_busy[i] = false; /* just to be anal */
}
/* XXX We will need to rename this per APB */
es2->apb_log_enable_dentry = debugfs_create_file("apb_log_enable",
greybus: esx: fix null-deref on hotplug events We must be prepared to receive hotplug events as soon as we submit the SVC URB. Since commit 2eb8a6a947d7 ("core: don't set up endo until host device is initialized") this is no longer the case as the endo would not have been setup, something which may lead to a null-pointer dereference in endo_get_module_id() when the interface is created (see oops below with an added dev_dbg for hd->endo). Fix this by setting up the endo before submitting the SVC URB. [ 28.810610] gb_interface_create - hd->endo = (null) [ 28.816020] Unable to handle kernel NULL pointer dereference at virtual address 0000022b [ 28.824952] pgd = c0004000 [ 28.827880] [0000022b] *pgd=00000000 [ 28.831913] Internal error: Oops: 17 [#1] PREEMPT ARM [ 28.837183] Modules linked in: gb_es1(O+) greybus(O) netconsole [ 28.843419] CPU: 0 PID: 21 Comm: kworker/u2:1 Tainted: G O 4.1.0-rc7 #12 [ 28.851576] Hardware name: Generic AM33XX (Flattened Device Tree) [ 28.857978] Workqueue: greybus_ap ap_process_event [greybus] [ 28.863890] task: cf2961c0 ti: cf29c000 task.ti: cf29c000 [ 28.869529] PC is at endo_get_module_id+0x18/0x88 [greybus] [ 28.875355] LR is at gb_interface_add+0x88/0x204 [greybus] [ 28.881070] pc : [<bf0052d4>] lr : [<bf005dac>] psr: 20070013 [ 28.881070] sp : cf29de08 ip : cf29de18 fp : cf29de14 [ 28.893021] r10: 00000001 r9 : 0000005a r8 : cd813ec6 [ 28.898461] r7 : 00000058 r6 : cf7fa200 r5 : 00000001 r4 : cf7fa20c [ 28.905261] r3 : 00000000 r2 : cf2961c0 r1 : 00000001 r0 : 00000000 [ 28.912067] Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel [ 28.919677] Control: 10c5387d Table: 8f508019 DAC: 00000015 [ 28.925663] Process kworker/u2:1 (pid: 21, stack limit = 0xcf29c210) [ 28.932279] Stack: (0xcf29de08 to 0xcf29e000) [ 28.936823] de00: cf29de44 cf29de18 bf005dac bf0052c8 00000058 cd813ec0 [ 28.945349] de20: cf58b60c bf00afe0 cf7fa200 cf58b600 0000005a 00000001 cf29de84 cf29de48 [ 28.953865] de40: bf004844 bf005d30 00000000 cf02d800 cf29de6c cf29de60 c00759a0 cf58b60c [ 28.962389] de60: cf2742c0 cf02d800 cf0c6000 cf29dea8 c07b745c 00000000 cf29dee4 cf29de88 [ 28.970908] de80: c005943c bf004560 00000001 00000000 c0059354 cf02d800 c0059c0c 00000001 [ 28.979426] dea0: 00000000 00000000 bf00b314 00000000 00000000 bf009144 c04e3710 cf02d800 [ 28.987945] dec0: cf2742d8 cf02d830 00000088 c0059bd0 00000000 cf2742c0 cf29df24 cf29dee8 [ 28.996464] dee0: c0059b78 c0059248 cf29c000 cf245d40 c0776890 c07b6bf3 00000000 00000000 [ 29.004983] df00: cf245d40 cf2742c0 c0059b20 00000000 00000000 00000000 cf29dfac cf29df28 [ 29.013502] df20: c005fe90 c0059b2c c07812d0 00000000 cf29df4c cf2742c0 00000000 00000001 [ 29.022025] df40: dead4ead ffffffff ffffffff c07c86b0 00000000 00000000 c05fd8e8 cf29df5c [ 29.030542] df60: cf29df5c 00000000 00000001 dead4ead ffffffff ffffffff c07c86b0 00000000 [ 29.039062] df80: 00000000 c05fd8e8 cf29df88 cf29df88 cf245d40 c005fd98 00000000 00000000 [ 29.047581] dfa0: 00000000 cf29dfb0 c00108f8 c005fda4 00000000 00000000 00000000 00000000 [ 29.056105] dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 [ 29.064623] dfe0: 00000000 00000000 00000000 00000000 00000013 00000000 ffff0000 ffff0000 [ 29.073178] [<bf0052d4>] (endo_get_module_id [greybus]) from [<bf005dac>] (gb_interface_add+0x88/0x204 [greybus]) [ 29.083887] [<bf005dac>] (gb_interface_add [greybus]) from [<bf004844>] (ap_process_event+0x2f0/0x4d8 [greybus]) [ 29.094527] [<bf004844>] (ap_process_event [greybus]) from [<c005943c>] (process_one_work+0x200/0x8e4) [ 29.104228] [<c005943c>] (process_one_work) from [<c0059b78>] (worker_thread+0x58/0x500) [ 29.112668] [<c0059b78>] (worker_thread) from [<c005fe90>] (kthread+0xf8/0x110) [ 29.120295] [<c005fe90>] (kthread) from [<c00108f8>] (ret_from_fork+0x14/0x3c) [ 29.127825] Code: e24cb004 e52de004 e8bd4000 e3510000 (e5d0c22b) [ 29.137481] ---[ end trace ad95c3c26bdc98ce ]--- Signed-off-by: Johan Hovold <johan@hovoldconsulting.com> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2015-06-23 12:17:41 +00:00
(S_IWUSR | S_IRUGO),
gb_debugfs_get(), es2,
&apb_log_enable_fops);
INIT_LIST_HEAD(&es2->arpcs);
spin_lock_init(&es2->arpc_lock);
if (es2_arpc_in_enable(es2))
goto error;
retval = gb_hd_add(hd);
if (retval)
goto err_disable_arpc_in;
for (i = 0; i < NUM_BULKS; ++i) {
retval = es2_cport_in_enable(es2, &es2->cport_in[i]);
if (retval)
goto err_disable_cport_in;
}
return 0;
err_disable_cport_in:
for (--i; i >= 0; --i)
es2_cport_in_disable(es2, &es2->cport_in[i]);
gb_hd_del(hd);
err_disable_arpc_in:
es2_arpc_in_disable(es2);
error:
es2_destroy(es2);
return retval;
}
static void ap_disconnect(struct usb_interface *interface)
{
struct es2_ap_dev *es2 = usb_get_intfdata(interface);
int i;
gb_hd_del(es2->hd);
for (i = 0; i < NUM_BULKS; ++i)
es2_cport_in_disable(es2, &es2->cport_in[i]);
es2_arpc_in_disable(es2);
es2_destroy(es2);
}
static struct usb_driver es2_ap_driver = {
.name = "es2_ap_driver",
.probe = ap_probe,
.disconnect = ap_disconnect,
.id_table = id_table,
.soft_unbind = 1,
};
module_usb_driver(es2_ap_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");