struct glink_msg { /* New members MUST be added within the __struct_group() macro below. */
__struct_group(glink_msg_hdr, hdr, __packed,
__le16 cmd;
__le16 param1;
__le32 param2;
);
u8 data[];
} __packed;
static_assert(offsetof(struct glink_msg, data) == sizeof(struct glink_msg_hdr), "struct member likely outside of __struct_group()");
/** * struct glink_defer_cmd - deferred incoming control message * @node: list node * @msg: message header * @data: payload of the message * * Copy of a received control message, to be added to @rx_queue and processed * by @rx_work of @qcom_glink.
*/ struct glink_defer_cmd { struct list_head node;
struct glink_msg_hdr msg;
u8 data[];
};
/** * struct glink_core_rx_intent - RX intent * RX intent * * @data: pointer to the data (may be NULL for zero-copy) * @id: remote or local intent ID * @size: size of the original intent (do not modify) * @reuse: To mark if the intent can be reused after first use * @in_use: To mark if intent is already in use for the channel * @offset: next write offset (initially 0) * @node: list node
*/ struct glink_core_rx_intent { void *data;
u32 id;
size_t size; bool reuse; bool in_use;
u32 offset;
struct list_head node;
};
/** * struct qcom_glink - driver context, relates to one remote subsystem * @dev: reference to the associated struct device * @label: identifier of the glink edge * @rx_pipe: pipe object for receive FIFO * @tx_pipe: pipe object for transmit FIFO * @rx_work: worker for handling received control messages * @rx_lock: protects the @rx_queue * @rx_queue: queue of received control messages to be processed in @rx_work * @tx_lock: synchronizes operations on the tx fifo * @idr_lock: synchronizes @lcids and @rcids modifications * @lcids: idr of all channels with a known local channel id * @rcids: idr of all channels with a known remote channel id * @features: remote features * @intentless: flag to indicate that there is no intent * @tx_avail_notify: Waitqueue for pending tx tasks * @sent_read_notify: flag to check cmd sent or not * @abort_tx: flag indicating that all tx attempts should fail
*/ struct qcom_glink { struct device *dev;
/** * struct glink_channel - internal representation of a channel * @rpdev: rpdev reference, only used for primary endpoints * @ept: rpmsg endpoint this channel is associated with * @glink: qcom_glink context handle * @refcount: refcount for the channel object * @recv_lock: guard for @ept.cb * @name: unique channel name/identifier * @lcid: channel id, in local space * @rcid: channel id, in remote space * @intent_lock: lock for protection of @liids, @riids * @liids: idr of all local intents * @riids: idr of all remote intents * @intent_work: worker responsible for transmitting rx_done packets * @done_intents: list of intents that needs to be announced rx_done * @buf: receive buffer, for gathering fragments * @buf_offset: write offset in @buf * @buf_size: size of current @buf * @open_ack: completed once remote has acked the open-request * @open_req: completed once open-request has been received * @intent_req_lock: Synchronises multiple intent requests * @intent_req_result: Result of intent request * @intent_received: flag indicating that an intent has been received * @intent_req_wq: wait queue for intent_req signalling
*/ struct glink_channel { struct rpmsg_endpoint ept;
/** * qcom_glink_send_open_req() - send a GLINK_CMD_OPEN request to the remote * @glink: Ptr to the glink edge * @channel: Ptr to the channel that the open req is sent * * Allocates a local channel id and sends a GLINK_CMD_OPEN message to the remote. * Will return with refcount held, regardless of outcome. * * Return: 0 on success, negative errno otherwise.
*/ staticint qcom_glink_send_open_req(struct qcom_glink *glink, struct glink_channel *channel)
{
DEFINE_RAW_FLEX(struct glink_msg, req, data, GLINK_NAME_SIZE); int name_len = strlen(channel->name) + 1; int req_len = ALIGN(sizeof(*req) + name_len, 8); int ret; unsignedlong flags;
kref_get(&channel->refcount);
spin_lock_irqsave(&glink->idr_lock, flags);
ret = idr_alloc_cyclic(&glink->lcids, channel,
RPM_GLINK_CID_MIN, RPM_GLINK_CID_MAX,
GFP_ATOMIC);
spin_unlock_irqrestore(&glink->idr_lock, flags); if (ret < 0) return ret;
staticvoid qcom_glink_rx_done(struct qcom_glink *glink, struct glink_channel *channel, struct glink_core_rx_intent *intent)
{ /* We don't send RX_DONE to intentless systems */ if (glink->intentless) {
kfree(intent->data);
kfree(intent); return;
}
/* Take it off the tree of receive intents */ if (!intent->reuse) {
spin_lock(&channel->intent_lock);
idr_remove(&channel->liids, intent->id);
spin_unlock(&channel->intent_lock);
}
/* Schedule the sending of a rx_done indication */
spin_lock(&channel->intent_lock);
list_add_tail(&intent->node, &channel->done_intents);
spin_unlock(&channel->intent_lock);
schedule_work(&channel->intent_work);
}
/** * qcom_glink_receive_version() - receive version/features from remote system * * @glink: pointer to transport interface * @version: remote version * @features: remote features * * This function is called in response to a remote-initiated version/feature * negotiation sequence.
*/ staticvoid qcom_glink_receive_version(struct qcom_glink *glink,
u32 version,
u32 features)
{
trace_qcom_glink_cmd_version_rx(glink->label, version, features);
switch (version) { case 0: break; case GLINK_VERSION_1:
glink->features &= features;
fallthrough; default:
qcom_glink_send_version_ack(glink); break;
}
}
/** * qcom_glink_receive_version_ack() - receive negotiation ack from remote system * * @glink: pointer to transport interface * @version: remote version response * @features: remote features response * * This function is called in response to a local-initiated version/feature * negotiation sequence and is the counter-offer from the remote side based * upon the initial version and feature set requested.
*/ staticvoid qcom_glink_receive_version_ack(struct qcom_glink *glink,
u32 version,
u32 features)
{
trace_qcom_glink_cmd_version_ack_rx(glink->label, version, features);
switch (version) { case 0: /* Version negotiation failed */ break; case GLINK_VERSION_1: if (features == glink->features) break;
/** * qcom_glink_send_intent_req_ack() - convert an rx intent request ack cmd to * wire format and transmit * @glink: The transport to transmit on. * @channel: The glink channel * @granted: The request response to encode. * * Return: 0 on success or standard Linux error code.
*/ staticint qcom_glink_send_intent_req_ack(struct qcom_glink *glink, struct glink_channel *channel, bool granted)
{ struct glink_msg msg;
/** * qcom_glink_advertise_intent - convert an rx intent cmd to wire format and * transmit * @glink: The transport to transmit on. * @channel: The local channel * @intent: The intent to pass on to remote. * * Return: 0 on success or standard Linux error code.
*/ staticint qcom_glink_advertise_intent(struct qcom_glink *glink, struct glink_channel *channel, struct glink_core_rx_intent *intent)
{ struct command {
__le16 id;
__le16 lcid;
__le32 count;
__le32 size;
__le32 liid;
} __packed; struct command cmd;
if (!intent) {
spin_unlock_irqrestore(&channel->intent_lock, flags);
dev_err(glink->dev, "invalid intent id received\n"); return;
}
intent->in_use = false;
if (!reuse) {
idr_remove(&channel->riids, intent->id);
kfree(intent);
}
spin_unlock_irqrestore(&channel->intent_lock, flags);
if (reuse) {
WRITE_ONCE(channel->intent_received, true);
wake_up_all(&channel->intent_req_wq);
}
}
/** * qcom_glink_handle_intent_req() - Receive a request for rx_intent * from remote side * @glink: Pointer to the transport interface * @cid: Remote channel ID * @size: size of the intent * * The function searches for the local channel to which the request for * rx_intent has arrived and allocates and notifies the remote back
*/ staticvoid qcom_glink_handle_intent_req(struct qcom_glink *glink,
u32 cid, size_t size)
{ struct glink_core_rx_intent *intent; struct glink_channel *channel; unsignedlong flags;
if (glink->intentless) { /* Might have an ongoing, fragmented, message to append */ if (!channel->buf) {
intent = kzalloc(sizeof(*intent), GFP_ATOMIC); if (!intent) return -ENOMEM;
/* Handle message when no fragments remain to be received */ if (!left_size) {
spin_lock(&channel->recv_lock); if (channel->ept.cb) {
channel->ept.cb(channel->ept.rpdev,
intent->data,
intent->offset,
channel->ept.priv,
RPMSG_ADDR_ANY);
}
spin_unlock(&channel->recv_lock);
switch (cmd) { case GLINK_CMD_VERSION: case GLINK_CMD_VERSION_ACK: case GLINK_CMD_CLOSE: case GLINK_CMD_CLOSE_ACK: case GLINK_CMD_RX_INTENT_REQ:
ret = qcom_glink_rx_defer(glink, 0); break; case GLINK_CMD_OPEN_ACK:
ret = qcom_glink_rx_open_ack(glink, param1); break; case GLINK_CMD_OPEN: /* upper 16 bits of param2 are the "prio" field */
ret = qcom_glink_rx_defer(glink, param2 & 0xffff); break; case GLINK_CMD_TX_DATA: case GLINK_CMD_TX_DATA_CONT:
ret = qcom_glink_rx_data(glink, avail); break; case GLINK_CMD_READ_NOTIF:
qcom_glink_rx_read_notif(glink); break; case GLINK_CMD_INTENT:
qcom_glink_handle_intent(glink, param1, param2, avail); break; case GLINK_CMD_RX_DONE:
qcom_glink_handle_rx_done(glink, param1, param2, false); break; case GLINK_CMD_RX_DONE_W_REUSE:
qcom_glink_handle_rx_done(glink, param1, param2, true); break; case GLINK_CMD_RX_INTENT_REQ_ACK:
qcom_glink_handle_intent_req_ack(glink, param1, param2); break; case GLINK_CMD_SIGNALS:
qcom_glink_handle_signals(glink, param1, param2); break; default:
dev_err(glink->dev, "unhandled rx cmd: %d\n", cmd);
ret = -EINVAL; break;
}
if (ret) break;
}
}
EXPORT_SYMBOL(qcom_glink_native_rx);
channel = qcom_glink_alloc_channel(glink, name); if (IS_ERR(channel)) return ERR_CAST(channel);
ret = qcom_glink_send_open_req(glink, channel); if (ret) goto release_channel;
ret = wait_for_completion_timeout(&channel->open_ack, 5 * HZ); if (!ret) goto err_timeout;
ret = wait_for_completion_timeout(&channel->open_req, 5 * HZ); if (!ret) goto err_timeout;
qcom_glink_send_open_ack(glink, channel);
return channel;
err_timeout: /* qcom_glink_send_open_req() did register the channel in lcids*/
spin_lock_irqsave(&glink->idr_lock, flags);
idr_remove(&glink->lcids, channel->lcid);
spin_unlock_irqrestore(&glink->idr_lock, flags);
ret = qcom_glink_send_open_req(glink, channel); if (ret) goto close_link;
ret = wait_for_completion_timeout(&channel->open_ack, 5 * HZ); if (!ret) {
ret = -ETIMEDOUT; goto close_link;
}
return 0;
close_link: /* * Send a close request to "undo" our open-ack. The close-ack will * release qcom_glink_send_open_req() reference and the last reference * will be relesed after receiving remote_close or transport unregister * by calling qcom_glink_native_remove().
*/
qcom_glink_send_close_req(glink, channel);
if (glink->intentless || !completion_done(&channel->open_ack)) return 0;
prop = of_find_property(np, "qcom,intents", NULL); if (prop) {
val = prop->value;
num_groups = prop->length / sizeof(u32) / 2;
}
/* Channel is now open, advertise base set of intents */ while (num_groups--) {
size = be32_to_cpup(val++);
num_intents = be32_to_cpup(val++); while (num_intents--) {
intent = qcom_glink_alloc_intent(glink, channel, size, true); if (!intent) break;
ret = qcom_glink_tx(glink, &req, sizeof(req), data + offset, chunk_size, wait); if (ret) { /* Mark intent available if we failed */ if (intent)
intent->in_use = false; return ret;
}
ret = rpmsg_register_device(rpdev); if (ret) goto rcid_remove;
channel->rpdev = rpdev;
}
return 0;
rcid_remove:
spin_lock_irqsave(&glink->idr_lock, flags);
idr_remove(&glink->rcids, channel->rcid);
channel->rcid = 0;
spin_unlock_irqrestore(&glink->idr_lock, flags);
free_channel: /* Release the reference, iff we took it */ if (create_device)
kref_put(&channel->refcount, qcom_glink_channel_release);
void qcom_glink_native_remove(struct qcom_glink *glink)
{ struct glink_channel *channel; unsignedlong flags; int cid; int ret;
qcom_glink_cancel_rx_work(glink);
/* Fail all attempts at sending messages */
spin_lock_irqsave(&glink->tx_lock, flags);
glink->abort_tx = true;
wake_up_all(&glink->tx_avail_notify);
spin_unlock_irqrestore(&glink->tx_lock, flags);
/* Abort any senders waiting for intent requests */
spin_lock_irqsave(&glink->idr_lock, flags);
idr_for_each_entry(&glink->lcids, channel, cid)
qcom_glink_intent_req_abort(channel);
spin_unlock_irqrestore(&glink->idr_lock, flags);
ret = device_for_each_child(glink->dev, NULL, qcom_glink_remove_device); if (ret)
dev_warn(glink->dev, "Can't remove GLINK devices: %d\n", ret);
/* Release any defunct local channels, waiting for close-ack */
idr_for_each_entry(&glink->lcids, channel, cid)
kref_put(&channel->refcount, qcom_glink_channel_release);
/* Release any defunct local channels, waiting for close-req */
idr_for_each_entry(&glink->rcids, channel, cid)
kref_put(&channel->refcount, qcom_glink_channel_release);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.