mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 01:10:04 +00:00
![Mika Westerberg](/assets/img/avatar_default.png)
If a request times out the response might arrive right after the request is failed. This response is pushed to the kfifo and next request will read it instead. Since it most likely will not pass our validation checks in parse_header() the next request will fail as well, and response to that request will be pushed to the kfifo, ad infinitum. We end up in a situation where all requests fail and no devices can be added anymore until the driver is unloaded and reloaded again. To overcome this, rework the control channel so that we will have a queue of outstanding requests. Each request will be handled in turn and the response is validated against what is expected. Unexpected packets (for example responses for requests that have been timed out) are dropped. This model is copied from Greybus implementation with small changes here and there to get it cope with Thunderbolt control packets. In addition the configuration packets support sequence number which the switch is supposed to copy from the request to response. We use this to drop responses that are already timed out. Taking advantage of the sequence number, we automatically retry configuration read/write 4 times before giving up. Also timeout is not a programming error so there is no need to trigger a scary backtrace (WARN), instead we just log a warning. After all Thunderbolt devices are hot-pluggable by definition which means user can unplug a device any time and that is totally acceptable. With this change there is no need to take the global domain lock when sending configuration packets anymore. This is useful when we add support for cross-domain (XDomain) communication later on. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> Reviewed-by: Yehezkel Bernat <yehezkel.bernat@intel.com> Reviewed-by: Michael Jamet <michael.jamet@intel.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Andreas Noever <andreas.noever@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
142 lines
4.5 KiB
C
142 lines
4.5 KiB
C
/*
|
|
* Thunderbolt Cactus Ridge driver - control channel and configuration commands
|
|
*
|
|
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
|
|
*/
|
|
|
|
#ifndef _TB_CFG
|
|
#define _TB_CFG
|
|
|
|
#include <linux/kref.h>
|
|
|
|
#include "nhi.h"
|
|
#include "tb_msgs.h"
|
|
|
|
/* control channel */
|
|
struct tb_ctl;
|
|
|
|
typedef void (*event_cb)(void *data, enum tb_cfg_pkg_type type,
|
|
const void *buf, size_t size);
|
|
|
|
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data);
|
|
void tb_ctl_start(struct tb_ctl *ctl);
|
|
void tb_ctl_stop(struct tb_ctl *ctl);
|
|
void tb_ctl_free(struct tb_ctl *ctl);
|
|
|
|
/* configuration commands */
|
|
|
|
#define TB_CFG_DEFAULT_TIMEOUT 5000 /* msec */
|
|
|
|
struct tb_cfg_result {
|
|
u64 response_route;
|
|
u32 response_port; /*
|
|
* If err = 1 then this is the port that send the
|
|
* error.
|
|
* If err = 0 and if this was a cfg_read/write then
|
|
* this is the the upstream port of the responding
|
|
* switch.
|
|
* Otherwise the field is set to zero.
|
|
*/
|
|
int err; /* negative errors, 0 for success, 1 for tb errors */
|
|
enum tb_cfg_error tb_error; /* valid if err == 1 */
|
|
};
|
|
|
|
struct ctl_pkg {
|
|
struct tb_ctl *ctl;
|
|
void *buffer;
|
|
struct ring_frame frame;
|
|
};
|
|
|
|
/**
|
|
* struct tb_cfg_request - Control channel request
|
|
* @kref: Reference count
|
|
* @ctl: Pointer to the control channel structure. Only set when the
|
|
* request is queued.
|
|
* @request_size: Size of the request packet (in bytes)
|
|
* @request_type: Type of the request packet
|
|
* @response: Response is stored here
|
|
* @response_size: Maximum size of one response packet
|
|
* @response_type: Expected type of the response packet
|
|
* @npackets: Number of packets expected to be returned with this request
|
|
* @match: Function used to match the incoming packet
|
|
* @copy: Function used to copy the incoming packet to @response
|
|
* @callback: Callback called when the request is finished successfully
|
|
* @callback_data: Data to be passed to @callback
|
|
* @flags: Flags for the request
|
|
* @work: Work item used to complete the request
|
|
* @result: Result after the request has been completed
|
|
* @list: Requests are queued using this field
|
|
*
|
|
* An arbitrary request over Thunderbolt control channel. For standard
|
|
* control channel message, one should use tb_cfg_read/write() and
|
|
* friends if possible.
|
|
*/
|
|
struct tb_cfg_request {
|
|
struct kref kref;
|
|
struct tb_ctl *ctl;
|
|
const void *request;
|
|
size_t request_size;
|
|
enum tb_cfg_pkg_type request_type;
|
|
void *response;
|
|
size_t response_size;
|
|
enum tb_cfg_pkg_type response_type;
|
|
size_t npackets;
|
|
bool (*match)(const struct tb_cfg_request *req,
|
|
const struct ctl_pkg *pkg);
|
|
bool (*copy)(struct tb_cfg_request *req, const struct ctl_pkg *pkg);
|
|
void (*callback)(void *callback_data);
|
|
void *callback_data;
|
|
unsigned long flags;
|
|
struct work_struct work;
|
|
struct tb_cfg_result result;
|
|
struct list_head list;
|
|
};
|
|
|
|
#define TB_CFG_REQUEST_ACTIVE 0
|
|
#define TB_CFG_REQUEST_CANCELED 1
|
|
|
|
struct tb_cfg_request *tb_cfg_request_alloc(void);
|
|
void tb_cfg_request_get(struct tb_cfg_request *req);
|
|
void tb_cfg_request_put(struct tb_cfg_request *req);
|
|
int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
|
|
void (*callback)(void *), void *callback_data);
|
|
void tb_cfg_request_cancel(struct tb_cfg_request *req, int err);
|
|
struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
|
|
struct tb_cfg_request *req, int timeout_msec);
|
|
|
|
static inline u64 tb_cfg_get_route(const struct tb_cfg_header *header)
|
|
{
|
|
return (u64) header->route_hi << 32 | header->route_lo;
|
|
}
|
|
|
|
static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
|
|
{
|
|
struct tb_cfg_header header = {
|
|
.route_hi = route >> 32,
|
|
.route_lo = route,
|
|
};
|
|
/* check for overflow, route_hi is not 32 bits! */
|
|
WARN_ON(tb_cfg_get_route(&header) != route);
|
|
return header;
|
|
}
|
|
|
|
int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
|
|
enum tb_cfg_error error);
|
|
struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
|
|
int timeout_msec);
|
|
struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
|
|
u64 route, u32 port,
|
|
enum tb_cfg_space space, u32 offset,
|
|
u32 length, int timeout_msec);
|
|
struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer,
|
|
u64 route, u32 port,
|
|
enum tb_cfg_space space, u32 offset,
|
|
u32 length, int timeout_msec);
|
|
int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
|
|
enum tb_cfg_space space, u32 offset, u32 length);
|
|
int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port,
|
|
enum tb_cfg_space space, u32 offset, u32 length);
|
|
int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route);
|
|
|
|
|
|
#endif
|